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.


Rolls N fair d6 with Math.random and renders each die as a 3×3 pip grid (a lookup table maps each value to lit cells). A brief interval gives the tumble.

'use client';

import * as React from 'react';

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

const PIPS: Record<number, [number, number][]> = {
  1: [[1, 1]],
  2: [[0, 0], [2, 2]],
  3: [[0, 0], [1, 1], [2, 2]],
  4: [[0, 0], [0, 2], [2, 0], [2, 2]],
  5: [[0, 0], [0, 2], [1, 1], [2, 0], [2, 2]],
  6: [[0, 0], [0, 2], [1, 0], [1, 2], [2, 0], [2, 2]],
};

export function DiceGame() {
  const [dice, setDice] = React.useState<number[]>([1, 1]);
  const [count, setCount] = React.useState(2);
  const [rolling, setRolling] = React.useState(false);

  const roll = () => {
    if (rolling) return;
    setRolling(true);
    let ticks = 0;
    const id = window.setInterval(() => {
      setDice(Array.from({ length: count }, () => 1 + Math.floor(Math.random() * 6)));
      if (++ticks > 9) {
        window.clearInterval(id);
        setRolling(false);
      }
    }, 60);
  };

  const total = dice.reduce((a, b) => a + b, 0);

  return (
    <div className="flex flex-col items-center gap-7">
      <div className="flex flex-wrap items-center justify-center gap-4">
        {dice.map((d, i) => (
          <Die key={i} value={d} rolling={rolling} />
        ))}
      </div>

      <div className="font-mono text-sm tracking-wide opacity-75">total {total}</div>

      <div className="flex items-center gap-2">
        <Win98Button onClick={roll} disabled={rolling} className="h-10 min-w-28 px-6 text-sm">
          Roll
        </Win98Button>
        <Win98Button
          onClick={() => setCount((c) => (c % 5) + 1)}
          disabled={rolling}
          aria-label="change number of dice"
        >
          {count}d6
        </Win98Button>
      </div>
    </div>
  );
}

function Die({ value, rolling }: { value: number; rolling: boolean }) {
  return (
    <div
      className={`grid h-24 w-24 grid-cols-3 grid-rows-3 gap-1 rounded-2xl border-2 border-foreground bg-[#faf7ef] p-3 transition-transform ${
        rolling ? 'animate-pulse' : ''
      }`}
    >
      {Array.from({ length: 9 }, (_, i) => {
        const r = Math.floor(i / 3);
        const c = i % 3;
        const on = PIPS[value]?.some(([pr, pc]) => pr === r && pc === c);
        return (
          <span key={i} className="flex items-center justify-center">
            {on ? <span className="h-3.5 w-3.5 rounded-full bg-foreground" /> : null}
          </span>
        );
      })}
    </div>
  );
}
↖ kaspirius