Code — how this page is built

Under the hood

The actual source of the components on this page, read straight from the repo. Copy it, read it, or open it on GitHub.


FlowFieldGameview on GitHub ↗

Drives ~900 particles by sampling simplex noise for each one’s heading; low-alpha trails + a per-frame fade build the flow. Reseeding makes a new noise field.

'use client';

import { createNoise2D } from 'simplex-noise';
import * as React from 'react';

import { Win98Button } from '@/components/ui/win-98-button';

const W = 520;
const H = 400;
const N = 900;

export function FlowFieldGame() {
  const canvasRef = React.useRef<HTMLCanvasElement>(null);
  const [seed, setSeed] = React.useState(0);

  React.useEffect(() => {
    const ctx = canvasRef.current?.getContext('2d');
    if (!ctx) return;
    const noise = createNoise2D();
    ctx.fillStyle = '#f3eee1';
    ctx.fillRect(0, 0, W, H);
    const parts = Array.from({ length: N }, () => ({
      x: Math.random() * W,
      y: Math.random() * H,
      h: Math.floor(Math.random() * 60),
    }));
    let raf = 0;
    const scale = 0.0026;
    const tick = () => {
      ctx.fillStyle = 'rgba(243,238,225,0.035)';
      ctx.fillRect(0, 0, W, H);
      for (const p of parts) {
        const a = noise(p.x * scale, p.y * scale) * Math.PI * 4;
        const nx = p.x + Math.cos(a) * 1.4;
        const ny = p.y + Math.sin(a) * 1.4;
        ctx.strokeStyle = `hsla(${20 + p.h}, 55%, 22%, 0.5)`;
        ctx.beginPath();
        ctx.moveTo(p.x, p.y);
        ctx.lineTo(nx, ny);
        ctx.stroke();
        p.x = (nx + W) % W;
        p.y = (ny + H) % H;
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [seed]);

  return (
    <div className="flex w-full max-w-[560px] flex-col items-center gap-5">
      <canvas
        ref={canvasRef}
        width={W}
        height={H}
        className="w-full max-w-full rounded-md border border-foreground/15"
      />
      <Win98Button onClick={() => setSeed((s) => s + 1)} className="h-10 min-w-32 px-7 text-sm">
        New field
      </Win98Button>
      <div className="font-mono text-[11px] uppercase tracking-[0.12em] opacity-45">
        particles flowing through invisible noise
      </div>
    </div>
  );
}
↖ kaspirius