@layer tokens {
  :root {
    /* ---- Surfaces -------------------------------------------------------
       Four levels, lit by one overhead source. Raised is lighter, recessed is
       darker — physically true, and functionally necessary, because the big bone
       numerals sit *in* the wells and a light well would swallow them.

       The separation between levels is only 1.09–1.40:1, which is deliberately
       almost invisible. Surface level never carries meaning on its own in this
       design; the bevel does the telling. */
    --ground: #151a2b; /* the page */
    --panel: #1d2333; /* cards, the instrument plate, settings rows */
    --rail: #262e42; /* raised: key faces, seat plates, bezel rings */
    --well: #0d1119; /* recessed: dial windows, the readout */

    /* ---- Markings -------------------------------------------------------
       Warm bone is reserved for the numbers that matter. Everything the machine
       says *about itself* — labels, captions, its own chrome — is cool grey.
       That turns warm-on-cool from a palette note into a hierarchy tool, and it
       is what keeps this from reading as generic dark mode.

       Measured against the surfaces (WCAG 2.1 contrast):
         bone       on well 16.8  panel 13.6  rail 11.4  ground 14.6
         bone-label on well  7.6  panel  6.2  rail  5.2  ground  6.6
         bone-faint on well  5.6  panel  4.6  rail  3.8  ground  4.9
       bone-faint is the floor, not a licence to go dimmer: at 3.8 on --rail it
       is already only legal for large text, so it is used for 1rem-and-up only. */
    --bone: #f2ede3;
    --bone-label: #a9b4c9;
    --bone-faint: #9099ae;

    /* Hairlines. --edge is decorative and may be faint; --edge-control bounds
       something you can operate and therefore holds 3:1 on every surface above,
       per WCAG 1.4.11. Keep them distinct — collapsing them is how a control
       boundary quietly becomes invisible. */
    --edge: #3a445c;
    --edge-control: #6b7695;

    /* ---- Signal ---------------------------------------------------------
       One accent, one meaning: AMBER MEANS TIME IS AGAINST YOU. The final
       quarter of the clock uses it. The wrong-answer lockout uses it. Nothing
       else does.

       The clock is bone for the first three quarters. Amber for the whole race
       would be an alarm that is always on, which is not an alarm. */
    --signal: #ffb020;
    --signal-dim: rgb(255 176 32 / 22%);

    /* Iron red, for destructive host controls only — remove a player, leave the
       room. Never fires during a round, which is the point: in an arithmetic
       race wrong answers are constant, and constant red is punishing.

       Lifted from #e0705f, which measured 4.29:1 against --rail and so missed AA on
       the one surface it is actually used on: the Remove button's own key face. */
    --danger: #e88374;

    /* ---- Player hues ---------------------------------------------------
       Six anodized aluminium colours, all outside the 30–55° band so none can
       be mistaken for the amber signal.

       A filled chip in one of these takes DARK ink, never bone: bone on these
       hues measures 1.33–2.71:1 and fails badly. And hue is never the only
       signal — every seat also carries an engraved numeral at its bezel's 12
       o'clock index, which is both the accessibility answer and exactly what a
       watch dial does with an applied index. */
    --seat-1: #f2607e; /* rose    345° */
    --seat-2: #5b8cff; /* cobalt  222° */
    --seat-3: #3ecfa0; /* teal    163° */
    --seat-4: #c98bff; /* violet  275° */
    --seat-5: #8ad8f5; /* ice     196° */
    --seat-6: #a8e05f; /* lime     88° */

    /* Ink for text sitting on a filled seat hue. */
    --seat-ink: #101423;

    /* Components read --seat and never branch on which player they are. */
    --seat: var(--seat-1);

    /* A house number belongs to nobody, so it gets no hue. */
    --house: var(--bone-faint);

    /* ---- Type -----------------------------------------------------------
       Two families, three roles.

       Archivo carries display and body both, at different widths. Using the
       width axis for the role split is the whole reason to choose a variable
       grotesque here; a second family for body would leave the axis idle and
       spend 90 KB proving nothing. Widths are driven by `font-stretch`, not
       `font-variation-settings`, because the latter clobbers inherited
       font-weight.

       DM Mono is the utility face: clocks, scores, the room code, micro-labels.
       Word Race deliberately has no monospace, so a mono face is itself a point
       of sibling difference — and here it is earned, because a clock whose
       digits change width is a clock that jitters. */
    --font-display: "Archivo", system-ui, -apple-system, sans-serif;
    --font-body: "Archivo", system-ui, -apple-system, sans-serif;
    --font-data: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

    --wdth-display: 112%;
    --wdth-headline: 108%;
    --wdth-operand: 104%;
    --wdth-body: 100%;

    --text-2xs: 0.6875rem; /* 11px — mono caps labels only */
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: clamp(1.0625rem, 1rem + 0.35vw, 1.25rem);
    --text-xl: clamp(1.25rem, 1.1rem + 0.8vw, 1.625rem);
    --text-2xl: clamp(1.625rem, 1.35rem + 1.5vw, 2.5rem);

    /* The hero is sized by whichever axis is tighter. A width-only clamp fits a
       narrow phone and then overflows a 1280x720 laptop, where there is plenty
       of width and almost no height. Folding svh into the middle term keeps the
       whole hero inside the first view on a short window as well as a narrow
       one — the one lesson worth copying wholesale from the sibling game. */
    --text-3xl: clamp(2rem, min(1.6rem + 3vw, 6.5svh), 4.25rem);

    --dial-size: clamp(6rem, 3rem + 15vw, 11rem);
    /* Large enough that the timer arc ringing it is readable: the operator disc is
       also the stopwatch face, which is the one place this design spends its
       boldness. A smaller disc would make the sweep decorative. */
    --disc-size: calc(var(--dial-size) * 0.74);
    /* Circumference of the arc circle, r=46 in a 100x100 viewBox. Named because the
       dash offset is computed from it in two places. */
    --arc-length: 289.03;
    --arc-fraction: 1;
    --text-operand: clamp(2.25rem, 1rem + 6.5vw, 4.5rem);
    --text-operator: clamp(1.75rem, 1rem + 4vw, 3rem);
    --text-readout: clamp(2.5rem, 1.2rem + 7vw, 5rem);
    --text-clock: clamp(1.125rem, 0.9rem + 1.6vw, 1.75rem);
    --text-count: clamp(5rem, 2.5rem + 18vw, 12rem);

    /* Floor is 44px: the touch-target minimum, and load-bearing in landscape
       where the vh term would otherwise give 28px. */
    --key-size: clamp(2.75rem, 13vw, 3.5rem);

    --track-display: -0.015em;
    --track-operand: 0.02em;
    --track-body-sm: 0.005em; /* Archivo at 450 is slightly tight as small prose */
    --track-caps: 0.16em;
    --track-data: 0.04em;

    /* ---- Space --------------------------------------------------------- */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-7: 3rem;
    --space-8: 4rem;

    --gutter: clamp(1rem, 0.6rem + 2.5vw, 2.5rem);
    --measure: 34rem;
    --shell-max: 78rem;

    /* ---- Shape ---------------------------------------------------------
       Tight machined fillets, and the round form wherever content is not
       genuinely linear. Rectangles are permitted for settings rows, prose and
       toasts — things that really are lists of lines.

       There are no tilt tokens. Word Race's hand-tilted cards are the single
       most characteristic thing about it, and their absence here is the loudest
       statement this design makes: a machined part that is not square to true is
       a defective part. */
    --hair: 1px;
    --stroke: 2px;

    --radius-xs: 3px;
    --radius-sm: 6px;
    --radius-md: 10px;
    --radius-lg: 14px;
    --radius-round: 50%;

    /* ---- Depth ---------------------------------------------------------
       The counterpart to Word Race's hard offset shadow. There, depth is a
       sticker lifted off a sheet; here it is a machined edge catching a single
       overhead light. Because the surface levels are nearly identical, these
       bevels are doing all of the work of telling raised from recessed. */

    /* Raised: lit top edge, shadowed bottom edge, a thin cast below. */
    --bevel-raised:
      inset 0 1px 0 rgb(242 237 227 / 8%), inset 0 -1px 0 rgb(0 0 0 / 40%),
      0 1px 0 rgb(0 0 0 / 45%);

    /* Raised, hovered: the top edge brightens. Never a translate — a bolted
       part does not move when you look at it. */
    --bevel-hover:
      inset 0 1px 0 rgb(242 237 227 / 16%), inset 0 -1px 0 rgb(0 0 0 / 40%),
      0 1px 0 rgb(0 0 0 / 45%);

    /* Pressed: the lit edge moves to the bottom and the face sinks. This is the
       interaction a player performs a hundred times a match, so it has to feel
       as good as the sibling game's squash. */
    --bevel-pressed:
      inset 0 2px 4px rgb(0 0 0 / 55%), inset 0 -1px 0 rgb(242 237 227 / 5%);

    /* Recessed. The primitive Word Race does not have at all. */
    --bevel-inset:
      inset 0 2px 3px rgb(0 0 0 / 55%), inset 0 -1px 0 rgb(242 237 227 / 6%);

    --bevel-inset-deep:
      inset 0 3px 6px rgb(0 0 0 / 65%), inset 0 -1px 0 rgb(242 237 227 / 7%);

    /* Rings. --ring-live is the only glow in the system and it is off by
       default; it arrives with the final quarter of the clock. */
    --ring-seat: 0 0 0 var(--stroke) var(--seat);
    --ring-live: 0 0 0 1px var(--signal), 0 0 12px -2px rgb(255 176 32 / 45%);

    /* ---- Motion --------------------------------------------------------
       --ease-detent is the house easing and the thesis in one token: about 10%
       overshoot, settling hard. Word Race's --ease-boing overshoots 56% and
       wobbles. An instrument clicks; it does not bounce. */
    --dur-instant: 90ms; /* a key face lighting under a finger */
    --dur-fast: 140ms;
    --dur-base: 220ms;
    --dur-detent: 260ms; /* the operator disc landing */
    --dur-sweep: 620ms; /* the arc resetting between rounds */

    --ease-detent: cubic-bezier(0.16, 1.1, 0.3, 1);
    --ease-out: cubic-bezier(0.2, 0.7, 0.3, 1);
    --ease-in-out: cubic-bezier(0.5, 0, 0.35, 1);
    /* The one justified linear in the codebase: a clock must not ease. */
    --ease-clock: linear;

    /* ---- Texture -------------------------------------------------------
       Image-free and localised. Word Race's page-wide grain earns its place
       because riso genuinely is a noise process; machined metal's character is
       edges and light, so a full-viewport overlay here would be either
       invisible or cheap — and it would cost a full-screen composite every
       frame during the race, which is the one moment frames matter.

       Instead: circular brushing inside the dial well, which is literally what
       a sunburst watch dial looks like, painted once per dial. */
    --dial-brush: repeating-conic-gradient(
      from 0deg,
      rgb(242 237 227 / 3.5%) 0deg 0.5deg,
      transparent 0.5deg 1.4deg
    );

    /* Sixty index ticks around a bezel, generated. */
    --dial-ticks: repeating-conic-gradient(
      from -0.35deg,
      rgb(242 237 227 / 22%) 0deg 0.7deg,
      transparent 0.7deg 6deg
    );

    /* A fine vertical brush for large flat faces. */
    --panel-brush: repeating-linear-gradient(
      90deg,
      rgb(242 237 227 / 2.5%) 0 1px,
      transparent 1px 3px
    );

    --arc-width: 3px;

    --z-board: 1;
    --z-sticky: 20;
    --z-toast: 60;
    --z-overlay: 80;
    --z-modal: 90;
  }

  /* The instrument powers up for the countdown and the race: the brushing comes
     forward and the timer arc thickens. Settles again at the result. */
  :root[data-armed="true"] {
    --arc-width: 4px;
  }

  /* A short portrait phone — a 320x568 SE, say — still has to fit the dial, the
     readout, the keypad AND the score rail in one view, because the bottom row of
     keys being a scroll away means you cannot type a zero. So the dial scales off
     the short axis here too, and the keypad is pinned to its 44px floor rather than
     growing with the width. Anything merely explanatory gives up its space as well;
     see the matching block in layout.css. */
  @media (height <= 40rem) and (orientation: portrait) {
    :root {
      --dial-size: clamp(3.75rem, 1rem + 11vh, 5.5rem);
      --text-operand: clamp(1.5rem, 0.5rem + 5vh, 2.5rem);
      --text-operator: clamp(1.125rem, 0.4rem + 3.4vh, 1.75rem);
      --text-readout: clamp(1.5rem, 0.5rem + 5vh, 2.5rem);
      --text-clock: clamp(0.8125rem, 0.4rem + 2.2vh, 1.125rem);
      --text-count: clamp(3rem, 0.5rem + 22vh, 6rem);
      --key-size: 2.75rem;
    }
  }

  /* A phone held sideways has almost no vertical room, so everything on the dial
     scales off the short axis instead of the long one. Token overrides belong
     with the tokens, not scattered through the component layer.

     The 44px floor on --key-size is what makes this viable at all: 9vh of a
     320px-tall viewport is 28.8px, and the floor rescues it. That floor is also
     precisely why the board must split into two columns in landscape — four rows
     of 44px keys is 208px of a 320px viewport, so the dial cannot sit above it. */
  @media (height <= 32rem) and (orientation: landscape) {
    :root {
      --dial-size: clamp(4.5rem, 1rem + 26vh, 8rem);
      --text-operand: clamp(1.75rem, 0.5rem + 8vh, 3.25rem);
      --text-operator: clamp(1.25rem, 0.5rem + 5vh, 2rem);
      --text-readout: clamp(2rem, 0.5rem + 9vh, 3.75rem);
      --text-clock: clamp(1rem, 0.5rem + 3vh, 1.375rem);
      --text-count: clamp(3rem, 0.5rem + 22vh, 6rem);
      --key-size: clamp(2.75rem, 0.5rem + 9vh, 3.25rem);
    }
  }
}
