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.


Hashes the word to seed a mulberry32 PRNG, then draws a fixed program of circles/rects/triangles entirely from that stream — making the result deterministic per word.

'use client';

import * as React from 'react';

const SIZE = 500;

function hashString(s: string): number {
  let h = 2166136261 >>> 0;
  for (let i = 0; i < s.length; i++) {
    h ^= s.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}
function mulberry32(a: number) {
  return () => {
    a |= 0;
    a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

export function SeedArtGame() {
  const canvasRef = React.useRef<HTMLCanvasElement>(null);
  const [word, setWord] = React.useState('kaspirius');

  React.useEffect(() => {
    const ctx = canvasRef.current?.getContext('2d');
    if (!ctx) return;
    const rng = mulberry32(hashString(word || ' '));
    const hue = Math.floor(rng() * 360);
    const bg = `hsl(${hue} 30% 94%)`;
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, SIZE, SIZE);
    const shapes = 18 + Math.floor(rng() * 26);
    for (let i = 0; i < shapes; i++) {
      const h = (hue + Math.floor(rng() * 80) - 40 + 360) % 360;
      ctx.fillStyle = `hsl(${h} ${40 + rng() * 45}% ${30 + rng() * 45}%)`;
      ctx.globalAlpha = 0.55 + rng() * 0.4;
      const x = rng() * SIZE;
      const y = rng() * SIZE;
      const s = 12 + rng() * 130;
      const kind = rng();
      ctx.beginPath();
      if (kind < 0.4) {
        ctx.arc(x, y, s / 2, 0, Math.PI * 2);
        ctx.fill();
      } else if (kind < 0.75) {
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate(rng() * Math.PI);
        ctx.fillRect(-s / 2, -s / 2, s, s * (0.3 + rng()));
        ctx.restore();
      } else {
        ctx.moveTo(x, y);
        ctx.lineTo(x + (rng() - 0.5) * s * 2, y + (rng() - 0.5) * s * 2);
        ctx.lineTo(x + (rng() - 0.5) * s * 2, y + (rng() - 0.5) * s * 2);
        ctx.closePath();
        ctx.fill();
      }
    }
    ctx.globalAlpha = 1;
  }, [word]);

  return (
    <div className="flex w-full max-w-[520px] flex-col items-center gap-5">
      <canvas ref={canvasRef} width={SIZE} height={SIZE} className="w-full max-w-full rounded-md border border-foreground/15" />
      <input
        value={word}
        onChange={(e) => setWord(e.target.value)}
        spellCheck={false}
        placeholder="type a word…"
        className="w-full max-w-xs border-b border-foreground/30 bg-transparent px-1 py-2 text-center font-mono text-sm outline-none placeholder:opacity-40"
      />
      <div className="font-mono text-[11px] uppercase tracking-[0.12em] opacity-45">
        the same word always makes the same piece
      </div>
    </div>
  );
}
↖ kaspirius