@layer tokens {
  :root {
    /* ---- Surfaces -------------------------------------------------------
       Warm paper, seen through weather. Three levels only, and they are barely
       apart on purpose: 1.09:1 between paper and paper-deep. Surface level never
       carries meaning here — it separates *your* lane from your opponent's by
       recession, and nothing else.

       This is the first of three deliberate departures from the siblings.
       Word Race's paper is a hot pink riso sheet; this one is oat, desaturated,
       and quiet. Same material, opposite volume. */
    --paper: #faf6f0; /* your lane, and the page */
    --paper-deep: #f2ece2; /* the opponent's lane, recessed */
    --paper-edge: #e4dbcd; /* the floor line, puddles, hairline edges */

    /* ---- Ink ------------------------------------------------------------
       Measured against every surface above (WCAG 2.1 contrast):

         ink        paper 12.20  deep 11.18  edge 9.58
         ink-soft   paper  6.19  deep  5.67  edge 4.86   AA at any size
         ink-faint  paper  3.69  deep  3.38  edge 2.90   large text only

       ink-faint is the typed-so-far prefix of a falling word. It is legal only
       because a falling word is set at --text-fall, which is far into large-text
       territory — do not reach for it at body size, and never on --paper-edge,
       where it measures 2.90 and is illegal at every size. */
    --ink: #33302b;
    --ink-soft: #5f5c56;
    --ink-faint: #847f77;
    --ink-hairline: rgb(51 48 43 / 12%);

    /* ---- Players --------------------------------------------------------
       Two hues, and they are the only saturated colour in the game. Dusty blue
       is always you; clay is always the storm coming at you. They are not
       interchangeable and they never swap by seat order — "the blue one is me"
       has to be true in every match or the pressure gauge is unreadable.

         lane-you    paper 3.30  deep 3.03   passes 1.4.11 (3:1) for UI
         lane-rival  paper 3.42  deep 3.13   passes 1.4.11 (3:1) for UI

       Both fail on --paper-edge (2.59 and 2.68). They are used for underlines,
       the gauge fill and large labels — never for body text, and never on the
       edge surface. Hue is also never the only signal: your lane is the nearer
       and larger one, and both are labelled. */
    --lane-you: #6b8ca3;
    --lane-rival: #b5765c;

    /* Sage, for a word cleared. paper 4.24, deep 3.89, edge 3.33 — legal on all
       three, which matters because the ripple crosses the floor line. */
    --calm: #5f7d62;

    /* Dim gold, for pressure inbound. paper 3.74, deep 3.43. Darkened hard from
       the obvious #c9a227, which measured 2.25 and would have made the one
       element the player must read under time pressure the least legible thing
       on screen. */
    --warn: #9c7a18;

    /* Red, and the only unambiguously loud colour in the game — for characters typed
       wrong and still sitting on the word. paper 5.95, deep 5.46, edge 4.67, so it
       passes AA at any size on every surface.

       This one is allowed to break the palette's manners. Everything else here is
       desaturated because the game is quiet, but an error the player has to notice and
       undo is the one thing that must not be tasteful about it. It is still a warm red
       rather than a pure one, so it belongs to the paper. */
    --error: #b02f22;

    /* Components read --lane-ink and never branch on which player they are. */
    --lane-ink: var(--lane-you);

    /* ---- Type -----------------------------------------------------------
       A text serif for the words that fall, and the system sans for everything
       else. Two faces, and deliberately no monospace.

       That absence is the second departure. Number Clash earns a mono face
       because a clock whose digits change width is a clock that jitters; Word
       Race has none at all. A serif is this game's signature, and it is not
       decoration: the words ARE the interface here, there is no board and no
       tile, so the only thing carrying the game's character is the shape of the
       letters you are typing. Newsreader is a low-contrast text serif that holds
       up at speed and at size, which a display serif would not. */
    --font-fall: "Newsreader", Georgia, "Times New Roman", serif;
    --font-ui: system-ui, -apple-system, "Segoe UI", sans-serif;

    --text-2xs: 0.6875rem;
    --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.5rem, 1.3rem + 1.2vw, 2.25rem);

    /* The hero is sized by whichever axis is tighter — the one lesson worth
       copying wholesale from both siblings. A width-only clamp fits a narrow
       phone and then overflows a 1280x720 laptop, where there is width to spare
       and almost no height. */
    --text-3xl: clamp(2rem, min(1.6rem + 3vw, 6.5svh), 4.25rem);

    /* A falling word in your own lane, and in the opponent's smaller strip. */
    --text-fall: clamp(1.375rem, 0.9rem + 1.9vw, 2.125rem);
    --text-fall-rival: clamp(0.875rem, 0.7rem + 0.9vw, 1.25rem);
    --text-count: clamp(4.5rem, 2rem + 17vw, 11rem);

    --track-fall: 0.02em; /* a shade open: the eye is scanning, not reading */
    --track-caps: 0.14em;
    --track-tight: -0.01em;

    /* ---- 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: 74rem;

    /* ---- Shape ---------------------------------------------------------
       Soft, square to true, and almost never bounded. There are no tilt tokens
       and no thick outlines: a falling word is type on paper, not a sticker and
       not a machined part. Radii exist for the few real containers — cards,
       buttons, the gauge — and nothing else gets a border at all.

       Hairlines only. The moment this game grows a 3px outline it becomes Word
       Race, and the moment it grows a bevel it becomes Number Clash. */
    --hair: 1px;
    --stroke: 2px;

    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 14px;
    --radius-pill: 999px;

    /* ---- Lane geometry -------------------------------------------------- */
    --lane-min-height: clamp(20rem, 58svh, 34rem);
    --lane-gutter: clamp(2.25rem, 5vw, 4rem); /* where the gauge lives */
    --floor-weight: 2px;
    --puddle-height: 3px;

    /* ---- Depth ---------------------------------------------------------
       Almost none. One diffuse shadow for cards that genuinely float, and a
       recession wash for the opponent's lane. No hard offset (Word Race's
       sticker lift) and no bevel (Number Clash's machined edge) — this game's
       depth cue is atmospheric, so it is blur and tint, never an edge. */
    --lift: 0 1px 2px rgb(51 48 43 / 4%), 0 8px 24px -16px rgb(51 48 43 / 18%);
    --lift-raised: 0 2px 4px rgb(51 48 43 / 5%), 0 18px 40px -22px rgb(51 48 43 / 24%);

    /* ---- Motion --------------------------------------------------------
       --ease-drift is the house easing and the third departure, the sharpest of
       them. Word Race's --ease-boing overshoots 56% and wobbles. Number Clash's
       --ease-detent overshoots 10% and settles hard. --ease-drift overshoots
       ZERO and settles slow: nothing in this game bounces and nothing clicks.
       Things arrive the way weather arrives.

       The fall itself is not eased at all — it is integrated per frame in
       game/Storm.js, because a word under gravity is a simulation, not a
       transition. These tokens are for arrivals, dissolves and the gauge. */
    --dur-fast: 140ms;
    --dur-base: 240ms;
    --dur-settle: 420ms;
    --dur-slow: 620ms;

    --ease-drift: cubic-bezier(0.25, 0.6, 0.35, 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: a fuse burning down must not ease. */
    --ease-fuse: linear;

    /* ---- Texture -------------------------------------------------------
       One radial wash per lane, painted once, and that is the whole texture
       budget. Word Race can afford a full-viewport feTurbulence grain because
       riso genuinely is a noise process and its board is static between rounds.
       This game runs a continuous 60fps simulation, so a page-wide overlay would
       cost a full-screen composite on every frame of the only thing that
       matters. Number Clash already reasons this way about its own texture; here
       the argument binds harder. */
    --lane-wash: radial-gradient(
      120% 80% at 50% 0%,
      rgb(107 140 163 / 5%) 0%,
      transparent 60%
    );

    --rival-wash: radial-gradient(
      120% 80% at 50% 0%,
      rgb(181 118 92 / 5%) 0%,
      transparent 60%
    );

    --z-lane: 1;
    --z-gauge: 10;
    --z-sticky: 20;
    --z-toast: 60;
    --z-overlay: 80;
    --z-modal: 90;
  }

  /* Heavy weather. Set on the root once gravity passes HEAVY_WEATHER_GRAVITY,
     rather than restyling every falling word: one attribute flip, and the lane
     tightens as a whole. The wash cools and strengthens — the storm is closing
     in, and the paper is getting wet. */
  :root[data-weather="heavy"] {
    --lane-wash: radial-gradient(
      120% 90% at 50% 0%,
      rgb(107 140 163 / 13%) 0%,
      transparent 72%
    );
    --track-fall: 0.01em;
  }

  /* A short window — a 1280x720 laptop, or a phone in landscape — has almost no
     vertical room, and a lane that needs scrolling is a lane you cannot play.
     Everything on the lane scales off the short axis here. */
  @media (height <= 40rem) {
    :root {
      /* Floor of 7rem, not 11. A lane must fit inside the viewport without
         scrolling — a board you have to scroll is a board you cannot play, because
         the words you are not looking at are the ones about to land. At 11rem the
         stacked layout needed 454px in a 422px window and the page scrolled. */
      --lane-min-height: clamp(7rem, 34svh, 14rem);
      --text-fall: clamp(1rem, 0.6rem + 2.4vh, 1.5rem);
      --text-fall-rival: clamp(0.75rem, 0.5rem + 1.4vh, 1rem);
      --text-count: clamp(3rem, 0.5rem + 20vh, 6rem);
    }
  }

  /* Reduced motion keeps the fall — that is the game — and drops everything
     decorative around it. Declared here with the tokens rather than scattered
     through the component layer, so there is one place to check what a
     motion-sensitive player actually sees. */
  @media (prefers-reduced-motion: reduce) {
    :root {
      --dur-fast: 1ms;
      --dur-base: 1ms;
      --dur-settle: 1ms;
      --dur-slow: 1ms;
    }
  }
}
