/* Linepoint — site styles

   Colour comes only from css/tokens.css (Arcade, the default Face, plus the
   ten per-Face swatch triples). There is no hex in this file: every colour is
   a var(), or a color-mix() of vars where a tint or a hairline is needed.

   Non-colour constants below are the app's own locked globals and Arcade
   geometry, cited to docs/brand/tokens.md in the linepoint repo:
     - radius 13px, rail-thickness 5px, marker height 34px, ring 13px/2px,
       cap 7px/1px  ("Geometry, per Face" > Arcade)
     - tracking-label 0.09em, weight-hero 900, weight-ui 800  ("Type, per Face")
     - glow 16px  ("Face palettes" > Arcade)
     - reveal beats 0 / 120 / 240 / 380 ms  (product.md, "The reveal")
   The two bezel radii are device geometry (an iPhone screen corner), not Face
   tokens — the app has no phone-frame token to cite. */

:root {
  /* Layout — locked globals, not themeable (tokens.md, "Locked globals") */
  --page: 1120px;
  --gutter: clamp(20px, 5vw, 40px);
  --section-y: clamp(56px, 8vw, 96px);
  --measure: 64ch;

  /* Arcade geometry */
  --radius: 13px;
  --radius-lg: 20px;
  --rail-h: 5px;
  --marker-h: 34px;
  --ring: 13px;
  --ring-stroke: 2px;
  --cap: 7px;
  --cap-radius: 1px;
  --glow: 16px;

  /* Arcade type */
  --track: 0.09em;
  --w-hero: 900;
  --w-ui: 800;

  --fs-display: clamp(2.6rem, 10.5vw, 5rem);
  --fs-h2: clamp(1.6rem, 4.4vw, 2.4rem);
  --fs-h3: clamp(1.1rem, 2.4vw, 1.25rem);
  --fs-body: clamp(1rem, 1.9vw, 1.0625rem);
  --fs-small: 0.875rem;
  --fs-label: 0.75rem;

  /* The reveal's beat timings (product.md, "The reveal") */
  --beat-commit: 0ms;
  --beat-target: 120ms;
  --beat-verdict: 240ms;
  --beat-score: 380ms;
  --dur-reveal: 240ms;
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  /* The third of the three shared curves (tokens.md, "Motion easing"),
     here because Newsprint's settle is the one on the page that uses it. */
  --ease-sharp: cubic-bezier(.4, 0, .2, 1);

  /* Arcade's settle — the marker's overshoot and return, the token where
     physics stands in for personality (tokens.md, "Motion, per Face" >
     Arcade: SPRING / 420ms / overshoot 1 / 3 rings) */
  --dur-settle: 420ms;
  --ease-settle: cubic-bezier(.34, 1.56, .64, 1);

  /* Echo reach — the wear-mark's half-height, the app's own REACH
     (linepoint/src/components/EchoField.tsx, REACH = 34) */
  --echo-reach: 34px;

  /* Device geometry for the screenshot bezels */
  --bezel-radius: 44px;
  --screen-radius: 34px;

  /* Derived surfaces — every one a mix of tokens, never a new value */
  --panel: color-mix(in oklab, var(--ground-lift) 46%, transparent);
  --hairline: color-mix(in oklab, var(--rail) 78%, transparent);
}

/* ===== RESET ===== */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  min-height: 100%;
  overflow-x: hidden;
  background-color: var(--ground);
  color: var(--ink);
  font-family: var(--face-ui);
  font-size: var(--fs-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

/* The Face's own ground-fill, held still behind the page the way the app
   holds it still behind the round (tokens.md, "Surface and fx"). */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: var(--ground-fill);
  background-repeat: no-repeat;
  background-size: cover;
}

h1, h2, h3 { margin: 0; line-height: 1.15; text-wrap: balance; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
img, svg { max-width: 100%; }
img { height: auto; }

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--signal-target);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  padding: 10px 16px;
  background: var(--ink);
  color: var(--ground);
  font-weight: var(--w-ui);
  z-index: 10;
}
.skip-link:focus { left: 12px; top: 12px; }

/* ===== HEADER ===== */
.site-head {
  max-width: var(--page);
  margin: 0 auto;
  padding: 20px var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
}

.wordmark-mark {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  display: block;
}

.wordmark-text {
  font-weight: var(--w-hero);
  font-size: 1.0625rem;
  letter-spacing: -0.01em;
}

.head-link {
  font-size: var(--fs-small);
  font-weight: var(--w-ui);
  text-decoration: none;
  color: var(--ink-dim);
  padding: 8px 14px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
}
.head-link:hover { color: var(--ink); border-color: var(--rail-tick); }

/* Subpage header nav: two pills (Home + the other subpage) where the main
   page has one CTA — same shape, so the chrome reads as one component. */
.head-links { display: flex; gap: 10px; }

/* ===== 1 · HERO ===== */
.hero {
  padding: clamp(28px, 6vw, 56px) var(--gutter) var(--section-y);
}

.hero-inner {
  max-width: 760px;
  margin: 0 auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
}

.hero-mark {
  width: clamp(132px, 30vw, 196px);
  aspect-ratio: 1;
  border-radius: 22.5%;
  overflow: hidden;
  /* The mark carries the Face's own bloom, at the Face's own glow radius */
  box-shadow:
    0 0 calc(var(--glow) * 3) color-mix(in oklab, var(--signal-guess) 16%, transparent),
    0 24px 60px color-mix(in oklab, var(--ground) 80%, transparent);
}
.icon-mark { display: block; width: 100%; height: 100%; }

.hero-title {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-display);
  font-weight: var(--w-hero);
  letter-spacing: -0.035em;
  line-height: 0.95;
  margin: -4px 0 0;
}

.hero-lede {
  max-width: 44ch;
  color: var(--ink-dim);
  font-size: clamp(1.0625rem, 2.4vw, 1.1875rem);
  /* Five short sentences in a row: the two-word ones are bound with
     non-breaking spaces in the markup, and `pretty` keeps the last line off
     a single orphaned word. */
  text-wrap: pretty;
}

/* Once a whole beat fits on one line, keep it on one: an inline-block breaks
   between its siblings before it breaks inside itself, so the lede lands as
   invitation / turn rather than mid-sentence. Below that width the sentence
   has to wrap where it can, and only the bound pairs are protected. */
@media (min-width: 600px) {
  .lede-beat { display: inline-block; }
}

.lede-target {
  color: var(--ink);
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-weight: var(--w-ui);
}

/* ===== 1b · THE RAIL =====
   The hero's instrument, and the only thing on the page that plays.
   Nothing here is a new form: the rail, its end ticks, the guess bar with its
   diamond cap, the target's ring, and the error bracket are the beat
   diagram's `.scale-*` parts at the same Arcade geometry, made live; the
   readout borrows `.payoff-eyebrow`, `.verdict`, and `.verdict-at` outright.
   Two departures from the diagram, both the app's own: the bracket hangs
   below the line rather than above it (`.bracket`, linepoint/src/index.css —
   above, it would fight the diamond cap), and the target carries the three
   rings Arcade's `ring-count` asks for. */
.rail {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* The whole band is the hit target: 76px tall, full width, and tall enough
   to hold the cap above the markers and the bracket below them. */
.rail-band {
  position: relative;
  display: block;
  width: 100%;
  height: 76px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  -webkit-appearance: none;
  appearance: none;
  cursor: crosshair;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* Inset by half a marker so 0 and 1,000 land on the tick centres, not
   half a tick outside the band. */
.rail-track {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--rail-h) / 2);
  right: calc(var(--rail-h) / 2);
}
.rail-track > span,
.rail-marks > span { position: absolute; }

.rail-line {
  left: 0;
  right: 0;
  top: 50%;
  height: var(--rail-h);
  margin-top: calc(var(--rail-h) / -2);
  border-radius: calc(var(--rail-h) / 2);
  background: var(--rail);
}

.rail-marks { inset: 0; pointer-events: none; }

/* Every marker straddles the line, the way the app's do. */
.rail-tick,
.rail-guess,
.rail-target,
.rail-aim {
  top: 50%;
  width: var(--rail-h);
  height: var(--marker-h);
  margin-top: calc(var(--marker-h) / -2);
  border-radius: calc(var(--rail-h) / 2);
  transform: translateX(-50%);
}

.rail-tick { background: var(--rail-tick); }

.rail-guess {
  background: var(--signal-guess);
  box-shadow: 0 0 var(--glow) color-mix(in oklab, var(--signal-guess) 60%, transparent);
}
.rail-guess::after {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(var(--cap) / -1);
  width: var(--cap);
  height: var(--cap);
  margin-left: calc(var(--cap) / -2);
  border-radius: var(--cap-radius);
  background: var(--signal-guess);
  transform: rotate(45deg);
}

.rail-target {
  background: var(--signal-target);
  box-shadow: 0 0 var(--glow) color-mix(in oklab, var(--signal-target) 60%, transparent);
}
.rail-target::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--ring);
  height: var(--ring);
  margin: calc(var(--ring) / -2) 0 0 calc(var(--ring) / -2);
  border: var(--ring-stroke) solid var(--signal-target);
  border-radius: 50%;
}

/* Arcade's three rings, propagating outward from the target. */
.rail-ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--ring);
  height: var(--ring);
  border: var(--ring-stroke) solid var(--signal-target);
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}

/* Where the guess will land: shown while a pointer is over the band or the
   band has keyboard focus, in the guess's own colour at a whisper. */
.rail-aim {
  width: 1px;
  background: var(--signal-guess);
  opacity: 0;
  transition: opacity 120ms var(--ease-out);
}
.rail.is-aiming .rail-aim { opacity: 0.5; }

/* The error span, hung below the line between the two markers. */
.rail-bracket {
  top: calc(50% + var(--marker-h) / 2 + 6px);
  height: 10px;
  border: 1px solid color-mix(in oklab, var(--ink) 55%, transparent);
  border-top: 0;
}

/* Echoes: short wear-marks straddling the line, ±34px, faint at both ends —
   tally scratches on the instrument, not hairlines through the page. */
.rail-echo {
  top: 50%;
  width: 1px;
  height: calc(var(--echo-reach) * 2);
  margin-top: calc(var(--echo-reach) * -1);
  transform: translateX(-50%);
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--ink) 0%, transparent),
    var(--ink),
    color-mix(in oklab, var(--ink) 0%, transparent));
  opacity: 0.14;
}

.rail-ends {
  display: flex;
  justify-content: space-between;
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-small);
  line-height: 1;
  color: var(--ink-dim);
}

.rail-readout {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

.rail-say { min-height: 1.6em; line-height: 1.6; }
.rail-hint { color: var(--ink-dim); }
.rail-join { color: var(--ink-faint); }

/* The round, said once, for anything that reads rather than looks. */
.rail-said {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.rail-pay {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

/* The one element the hero lets near signal-reward, and still smaller than
   the number above it (mood.md — nothing louder than the number). */
.rail-score {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.5rem, 5vw, 1.9rem);
  font-weight: var(--w-hero);
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: var(--signal-reward);
  text-shadow: 0 0 var(--glow) color-mix(in oklab, var(--signal-reward) 45%, transparent);
  /* Held wide enough for the largest score this line can pay, so the count
     up doesn't shove the button along with it. */
  min-width: 5ch;
  text-align: left;
}

.rail-again {
  font-family: var(--face-ui);
  font-size: var(--fs-small);
  font-weight: var(--w-ui);
  color: var(--ink-dim);
  background: none;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 5px 14px;
  cursor: pointer;
  transition: color 160ms var(--ease-out), border-color 160ms var(--ease-out);
}
.rail-again:hover { color: var(--ink); border-color: var(--rail-tick); }

/* --- Script off: a static diagram. The target sits at 437, one mark is on
   the line, and nothing claims to be tappable. --- */
.rail-guess,
.rail-bracket,
.rail-aim,
.rail-hint,
.rail-result,
.rail-pay { display: none; }

/* --- Script on: the round is armed, so the answer goes back into hiding
   and the readout reserves its own space before anything can move. --- */
.rail.is-js .rail-band { cursor: crosshair; }
.rail.is-js .rail-hint { display: inline; }
.rail.is-js .rail-aim { display: block; }
.rail.is-js .rail-guess,
.rail.is-js .rail-bracket { display: block; opacity: 0; }
.rail.is-js .rail-target { opacity: 0; }
.rail.is-js .rail-pay { display: flex; visibility: hidden; }

/* --- The reveal, on the app's beats: commit 0 ms, target +120, verdict and
   its bracket +240, score +380. The delays live in the script, which adds
   one class per beat; the CSS only says what each beat looks like. --- */
.rail.is-guess .rail-guess {
  opacity: 1;
  animation: rail-settle var(--dur-settle) var(--ease-settle) both;
}
.rail.is-guess .rail-aim { opacity: 0; }

.rail.is-target .rail-target {
  opacity: 1;
  transition: opacity var(--dur-reveal) var(--ease-out);
}
.rail.is-target .rail-ring {
  animation: rail-ring-out calc(var(--dur-reveal) * 3) ease-out both;
}
.rail.is-target .rail-ring:nth-child(2) { animation-delay: 120ms; }
.rail.is-target .rail-ring:nth-child(3) { animation-delay: 240ms; }

.rail.is-verdict .rail-bracket {
  opacity: 1;
  animation: rail-bracket-draw var(--dur-reveal) var(--ease-out) both;
}
.rail.is-exact .rail-bracket { display: none; }
.rail.is-verdict .rail-hint { display: none; }
.rail.is-verdict .rail-result { display: inline; }

.rail.is-score .rail-pay { visibility: visible; }

@keyframes rail-settle {
  from { transform: translateX(-50%) scaleY(0.35); }
  to { transform: translateX(-50%) scaleY(1); }
}

@keyframes rail-ring-out {
  from { opacity: 0.9; transform: translate(-50%, -50%) scale(0.6); }
  to { opacity: 0; transform: translate(-50%, -50%) scale(5); }
}

@keyframes rail-bracket-draw {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* ===== STORE BADGE (hand-built — no remote asset) ===== */
.actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-top: 6px;
}

.store-badge {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 12px 22px 12px 18px;
  border-radius: var(--radius);
  background: var(--ink);
  color: var(--ground);
  text-decoration: none;
  transition: transform 160ms var(--ease-out), box-shadow 160ms var(--ease-out);
  box-shadow: 0 10px 30px color-mix(in oklab, var(--ground) 70%, transparent);
}
.store-badge:hover {
  transform: translateY(-2px);
  box-shadow: 0 16px 38px color-mix(in oklab, var(--ground) 70%, transparent);
}
.store-badge:active { transform: translateY(0); }

.store-badge-glyph {
  width: 24px;
  height: 24px;
  flex: none;
  fill: var(--ground);
}

.store-badge-text {
  display: flex;
  flex-direction: column;
  line-height: 1.1;
  text-align: left;
}
.store-badge-text small {
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
  opacity: 0.72;
}
.store-badge-text strong {
  font-size: 1.0625rem;
  font-weight: var(--w-ui);
  letter-spacing: -0.01em;
}

.store-note {
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

/* ===== SECTION SHELL ===== */
.section {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--section-y) var(--gutter);
}

.section-head {
  max-width: var(--measure);
  margin-bottom: clamp(36px, 6vw, 56px);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.section-head h2 {
  font-size: var(--fs-h2);
  font-weight: var(--w-hero);
  letter-spacing: -0.02em;
}
.section-lede { color: var(--ink-dim); }

/* ===== 2 · HOW TO PLAY ===== */
.beats {
  display: grid;
  gap: clamp(28px, 4vw, 40px);
}

/* The scale: rail, two end ticks, the guess, the target, and the reveal's
   error bracket between the two markers, plus the four figures the reveal
   screen would print (images/shot-02.png): the two end values under their
   ticks, the guess above its marker, the target below. This compact build is
   the phone-width version — same rail, smaller geometry, one row of figures
   below instead of two, so it fits above beat 1 without pushing the section
   wide. The three-column build below (760px) restores the reveal's own
   two-row split and Arcade's real marker geometry. */
.beats-scale {
  display: block;
  position: relative;
  height: 76px;
  margin-bottom: clamp(20px, 5vw, 28px);
}
.beats-scale > span { position: absolute; }

.scale-rail {
  left: 0;
  right: 0;
  top: 34px;
  height: 4px;
  border-radius: 2px;
  background: var(--rail);
}
.scale-tick {
  top: 24px;
  width: 4px;
  height: 24px;
  border-radius: 2px;
  background: var(--rail-tick);
}
.scale-tick--start { left: 0; }
.scale-tick--end { right: 0; }

.scale-guess, .scale-target {
  top: 24px;
  width: 4px;
  height: 24px;
  border-radius: 2px;
  transform: translateX(-50%);
}
.scale-guess {
  left: 16.667%;
  background: var(--signal-guess);
  box-shadow: 0 0 10px color-mix(in oklab, var(--signal-guess) 55%, transparent);
}
.scale-guess::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -5px;
  width: 6px;
  height: 6px;
  margin-left: -3px;
  border-radius: 1px;
  background: var(--signal-guess);
  transform: rotate(45deg);
}
.scale-target {
  left: 50%;
  background: var(--signal-target);
  box-shadow: 0 0 10px color-mix(in oklab, var(--signal-target) 55%, transparent);
}
.scale-target::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border: 1.5px solid var(--signal-target);
  border-radius: 50%;
}
.scale-bracket {
  left: 16.667%;
  width: 33.333%;
  top: 10px;
  height: 10px;
  border: 1px solid color-mix(in oklab, var(--ink) 55%, transparent);
  border-bottom: 0;
}

/* The four figures. End labels sit flush to the rail's own ends rather than
   centred on the tick, so a three-digit range value never bleeds past the
   section's gutter at 320px — the same flush convention the hero's
   `.rail-ends` already uses. The guess and target values are interior points
   with room either side, so they centre on their marker the way the reveal
   screen's `.marker-label` does. */
.scale-label,
.scale-value {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  white-space: nowrap;
}
.scale-label {
  top: 54px;
  font-size: 0.6875rem;
  color: var(--ink-faint);
}
.scale-label--start { left: 0; }
.scale-label--end { right: 0; }

.scale-value {
  font-size: 0.6875rem;
  font-weight: var(--w-ui);
  color: var(--ink-dim);
  transform: translateX(-50%);
}
.scale-value--guess { left: 16.667%; top: 0; }
.scale-value--target { left: 50%; top: 54px; }

.beat-time {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-label);
  font-weight: var(--w-ui);
  letter-spacing: var(--track);
  color: var(--ink-dim);
  margin-bottom: 10px;
}

.beat h3 {
  font-size: var(--fs-h3);
  font-weight: var(--w-ui);
  letter-spacing: -0.005em;
  margin-bottom: 10px;
}

.beat > p:not(.beat-time) { color: var(--ink-dim); }

/* The four verdicts, each in the tone colour the app gives it
   (voice.md, "Verdicts" — perfect / very-close / close / miss). */
.verdicts {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-top: 1px solid var(--hairline);
  padding-top: 14px;
}
.verdicts li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.verdict { font-weight: var(--w-ui); }
.verdict--perfect { color: var(--signal-target); }
.verdict--very-close { color: var(--ink); }
.verdict--close { color: var(--signal-guess); }
.verdict--miss { color: var(--signal-danger); }
.verdict-at {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

/* The one element on the page allowed signal-reward: a score
   (mood.md — "the only element on screen using that token"). */
.payoff {
  margin-top: 18px;
  border-top: 1px solid var(--hairline);
  padding-top: 14px;
}
.payoff-eyebrow {
  font-size: var(--fs-label);
  font-weight: var(--w-ui);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ink-dim);
}
.payoff-value {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2.4rem, 7vw, 3.1rem);
  font-weight: var(--w-hero);
  letter-spacing: -0.03em;
  line-height: 1.05;
  color: var(--signal-reward);
  text-shadow: 0 0 var(--glow) color-mix(in oklab, var(--signal-reward) 45%, transparent);
}
.payoff-note {
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

@media (min-width: 760px) {
  .beats { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  /* The three-column build gets room to spare, so it restores Arcade's real
     geometry (rail-h, marker-h, cap, ring — the same tokens the hero rail
     plays with) and the reveal screen's actual two-row split: end labels
     close under the rail, the target value further down, same as
     images/shot-02.png. */
  .beats-scale {
    grid-column: 1 / -1;
    height: 118px;
    margin-bottom: 4px;
  }

  .scale-rail {
    top: calc(38px + (var(--marker-h) - var(--rail-h)) / 2);
    height: var(--rail-h);
    border-radius: calc(var(--rail-h) / 2);
  }
  .scale-tick,
  .scale-guess,
  .scale-target {
    top: 38px;
    width: var(--rail-h);
    height: var(--marker-h);
    border-radius: calc(var(--rail-h) / 2);
  }
  .scale-guess::after {
    top: calc(var(--cap) / -1);
    width: var(--cap);
    height: var(--cap);
    margin-left: calc(var(--cap) / -2);
    border-radius: var(--cap-radius);
    box-shadow: 0 0 var(--glow) color-mix(in oklab, var(--signal-guess) 60%, transparent);
  }
  .scale-guess { box-shadow: 0 0 var(--glow) color-mix(in oklab, var(--signal-guess) 60%, transparent); }
  .scale-target { box-shadow: 0 0 var(--glow) color-mix(in oklab, var(--signal-target) 60%, transparent); }
  .scale-target::after {
    width: var(--ring);
    height: var(--ring);
    margin: calc(var(--ring) / -2) 0 0 calc(var(--ring) / -2);
    border-width: var(--ring-stroke);
  }
  .scale-bracket {
    top: 20px;
    height: 14px;
  }

  .scale-label,
  .scale-value { font-size: var(--fs-small); }
  .scale-label { top: 80px; }
  .scale-value--target { top: 98px; }
}

/* ===== 3 · TWO WAYS TO COMMIT ===== */
.modes {
  display: grid;
  gap: 16px;
  margin-bottom: clamp(32px, 5vw, 48px);
}

.mode {
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: clamp(20px, 3vw, 28px);
}
.mode h3 {
  font-size: var(--fs-h3);
  font-weight: var(--w-ui);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 10px;
}
.mode > p { color: var(--ink-dim); }

.mode-badge {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-label);
  font-weight: var(--w-ui);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--signal-target);
  border: 1px solid color-mix(in oklab, var(--signal-target) 45%, transparent);
  border-radius: 999px;
  padding: 3px 10px;
}

.mode-note {
  margin-top: 12px;
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

/* Capped so the row label and its numbers stay in the same glance at wide
   viewports — uncapped, the table stretched to the section's full 1120px and
   put 900px of empty ground between what a row measures and what it paid. */
.payout { margin: 0; max-width: 44rem; }
.payout figcaption {
  font-size: var(--fs-label);
  font-weight: var(--w-ui);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 14px;
}
.payout-scroll { overflow-x: auto; }

/* Each row's own error bracket: a guess mark measured off a fixed target
   ring, the same grammar as the reveal's `.bracket` (linepoint/src/index.css)
   and this page's own `.rail-bracket` — open on the side facing the rail,
   closed on the side facing away. The gap is a custom property so one rule
   set draws every width; a 0-gap row (an exact hit) never gets a bracket or
   a second mark, matching `.is-exact .bracket { display: none }`. */
.miss-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.miss-glyph {
  position: relative;
  width: 56px;
  height: 24px;
  flex: none;
}
.miss-target {
  position: absolute;
  top: 50%;
  right: 0;
  width: 9px;
  height: 9px;
  margin-top: -4.5px;
  border: 1.5px solid var(--signal-target);
  border-radius: 50%;
}
.miss-guess {
  position: absolute;
  top: 50%;
  right: var(--gap, 0px);
  width: 3px;
  height: 12px;
  margin-top: -6px;
  border-radius: 1.5px;
  background: var(--signal-guess);
}
.miss-guess::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -5px;
  width: 5px;
  height: 5px;
  margin-left: -2.5px;
  border-radius: 1px;
  background: var(--signal-guess);
  transform: rotate(45deg);
}
.miss-bracket {
  position: absolute;
  top: calc(50% + 7px);
  right: 2px;
  width: var(--gap, 0px);
  height: 5px;
  border: 1px solid color-mix(in oklab, var(--ink) 55%, transparent);
  border-top: 0;
}
.miss-glyph--10 { --gap: 6px; }
.miss-glyph--20 { --gap: 11px; }
.miss-glyph--50 { --gap: 28px; }
.miss-glyph--80 { --gap: 44px; }

.miss-figs { color: inherit; }

.payout-table {
  width: 100%;
  min-width: 320px;
  border-collapse: collapse;
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
}
.payout-table th,
.payout-table td {
  text-align: right;
  padding: 12px 8px;
  border-bottom: 1px solid var(--hairline);
  white-space: nowrap;
}
.payout-table thead th {
  font-size: var(--fs-label);
  font-weight: var(--w-ui);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ink-dim);
}
.payout-table thead th:first-child,
.payout-table tbody th {
  text-align: left;
}
.payout-table tbody th {
  font-weight: 400;
  color: var(--ink-dim);
}
.payout-table td { font-weight: var(--w-ui); }
.payout-table th:first-child, .payout-table td:first-child { padding-left: 0; }
.payout-table th:last-child, .payout-table td:last-child { padding-right: 0; }
.payout-table tbody tr:last-child th,
.payout-table tbody tr:last-child td { border-bottom: 0; }

.payout-note {
  margin-top: 14px;
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

@media (min-width: 700px) {
  .modes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* At desk width the section becomes one panel: the two ways to commit on the
   left, what each one pays on the right — the choice and its price in the
   same glance, with no dead ground to the right of the table. */
@media (min-width: 1060px) {
  #modes {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
    column-gap: clamp(36px, 4vw, 64px);
    align-items: start;
  }
  #modes .section-head { grid-column: 1 / -1; }
  #modes .modes {
    grid-template-columns: minmax(0, 1fr);
    margin-bottom: 0;
  }
  #modes .payout { max-width: none; }
}

/* ===== 4 · RANKED =====
   The board is the app's leaderboard grammar in the site's shell: panel,
   hairline rows, tabular numerals. The fiftieth row's name wears
   signal-target because it is exactly that, the target — the run to beat.
   Totals stay ink-dim: signal-reward remains the payoff value's alone. */
.ranked-grid {
  display: grid;
  gap: clamp(24px, 4vw, 48px);
  align-items: start;
}

.rank-board {
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: clamp(20px, 3vw, 28px);
}

.rank-eyebrow {
  font-size: var(--fs-label);
  font-weight: var(--w-ui);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 14px;
}

.rank-rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rank-row {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 10px 0;
  border-top: 1px solid var(--hairline);
}
.rank-rows .rank-row:first-child { border-top: 0; padding-top: 0; }

.rank-pos {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  min-width: 2ch;
}

.rank-name { font-weight: var(--w-ui); }

.rank-run {
  margin-left: auto;
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
}

.rank-row--gap {
  justify-content: center;
  color: var(--ink-faint);
  font-size: var(--fs-small);
}

.rank-row--door .rank-name {
  color: var(--signal-target);
  text-shadow: 0 0 var(--glow) color-mix(in oklab, var(--signal-target) 35%, transparent);
}

.ranked-copy {
  max-width: var(--measure);
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ranked-copy p { color: var(--ink-dim); }
.ranked-copy strong { color: var(--ink); font-weight: var(--w-ui); }

@media (min-width: 860px) {
  .ranked-grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    column-gap: clamp(36px, 4vw, 64px);
  }
}

/* ===== 7 · SHARP EYE =====
   The emblem is a line-art brain with the number line run straight through
   it — brand strokes only (rail, rail-tick), the signals glowing inside.
   Beneath, the practice strip: the beats scale's guess-and-target grammar,
   four reps left to right with the error bracket shrinking. */
.sharp-grid {
  display: grid;
  gap: clamp(24px, 4vw, 48px);
  align-items: center;
}

.sharp-brain svg {
  display: block;
  width: min(100%, 420px);
  height: auto;
  margin: 0 auto;
}

.brain-line {
  fill: none;
  stroke: var(--rail);
  stroke-width: 3;
  stroke-linecap: round;
}
.brain-line--outer {
  stroke: var(--rail-tick);
  stroke-width: 3.5;
  fill: color-mix(in oklab, var(--ground-lift) 55%, transparent);
}

.practice {
  margin-top: clamp(32px, 5vw, 48px);
  border-top: 1px solid var(--hairline);
  padding-top: clamp(20px, 3vw, 28px);
}

.practice-eyebrow {
  font-size: var(--fs-label);
  font-weight: var(--w-ui);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 18px;
}

.rep-rows {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: clamp(18px, 3vw, 32px);
}

.rep-row {
  display: grid;
  grid-template-columns: 1fr auto;
  row-gap: 6px;
  align-items: center;
}
.rep-row svg {
  grid-column: 1 / -1;
  display: block;
  width: 100%;
  height: auto;
}

.rep-rail { fill: var(--rail); }
.rep-tick { fill: var(--rail-tick); }
.rep-bracket { fill: var(--ink-faint); }
.rep-guess { fill: var(--signal-guess); }
.rep-target { fill: var(--signal-target); }
.rep-ring { stroke: var(--signal-target); }

.rep-off {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-small);
  color: var(--ink-dim);
  justify-self: start;
}

.rep-row .verdict {
  font-size: var(--fs-small);
  justify-self: end;
}

.practice-note {
  margin-top: 18px;
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

.sharp-copy {
  max-width: var(--measure);
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.sharp-copy p { color: var(--ink-dim); }

@media (min-width: 860px) {
  .sharp-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
    column-gap: clamp(36px, 4vw, 64px);
  }
}

/* ===== 5 · TEN FACES =====
   The cabinet, three tiles of it. Arcade leads because it is the Face the app
   opens in, so it carries the equipped badge the app pins over a tile shot
   ("In play", ThemeGallery.tsx:59, at the badge's own geometry). The seven
   that are not shown reuse the locked tile's grammar instead: a condition and
   nothing else, on the shell's own dim ground rather than a Face's palette —
   what a player sees before the rung is earned. */
.faces {
  display: grid;
  gap: 16px;
}

.face {
  /* Each Face supplies only the three swatch tokens it publishes; the rail
     and its end ticks are that Face's ground moved toward its target, so no
     colour is invented for any of the three. */
  --f-rail: color-mix(in oklab, var(--f-target) 24%, var(--f-ground));
  --f-tick: color-mix(in oklab, var(--f-target) 46%, var(--f-ground));
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--panel);
}

.face-instrument {
  position: relative;
  background: var(--f-ground);
  padding: 14px 16px;
  border-bottom: 1px solid var(--hairline);
}
.face-instrument svg { display: block; width: 100%; height: auto; }

.mini-rail { fill: var(--f-rail); }
.mini-tick { fill: var(--f-tick); }
.mini-guess { fill: var(--f-guess); }
.mini-target { fill: var(--f-target); }
.mini-ring { stroke: var(--f-target); }

.face-meta { padding: 16px 16px 18px; }

.face h3 {
  font-size: 1rem;
  font-weight: var(--w-ui);
  letter-spacing: -0.005em;
}

.face-unlock {
  padding-top: 6px;
  font-size: var(--fs-small);
  color: var(--signal-target);
}

.face-line {
  padding-top: 8px;
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

/* The equipped badge, at the app's own geometry (index.css, `.tile-badge`):
   a signal-reward pill in the shot's top-left corner. */
.face-badge {
  position: absolute;
  top: 9px;
  left: 9px;
  padding: 5px 9px;
  border-radius: 999px;
  background: var(--signal-reward);
  color: var(--ground);
  font-weight: var(--w-ui);
  font-size: 0.625rem;
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Arcade leads: full width, and the only instrument on the page carrying the
   Face's own ground-fill, the way the app holds it behind a round. */
.face--lead .face-instrument {
  background-image: var(--ground-fill);
  background-repeat: no-repeat;
  background-size: cover;
  /* Same side padding as the pair, so the instrument runs just as wide, with
     more air above and below it — the lead is bigger even where the column
     count is one. */
  padding: 28px 16px;
}
.face--lead h3 { font-size: var(--fs-h3); }

/* Palette and motion, per Face, both copied from the same source of truth:
   the three swatch rows of tokens.md "Face palettes", and that Face's row of
   tokens.md "Motion, per Face". Overshoot is the token where physics stands
   in for personality — Arcade kicks, Candy kicks further and for longer, and
   Newsprint computes a 0px kick on the sharp curve, because a pen does not
   overshoot. */
.face--arcade {
  --f-ground: var(--face-arcade-ground);
  --f-target: var(--face-arcade-target);
  --f-guess: var(--face-arcade-guess);
  --f-ease-settle: var(--ease-settle);
  --f-dur-settle: var(--dur-settle);
  --f-overshoot: 1;
}
.face--newsprint {
  --f-ground: var(--face-newsprint-ground);
  --f-target: var(--face-newsprint-target);
  --f-guess: var(--face-newsprint-guess);
  --f-ease-settle: var(--ease-sharp);
  --f-dur-settle: 180ms;
  --f-overshoot: 0;
}
.face--candy {
  --f-ground: var(--face-candy-ground);
  --f-target: var(--face-candy-target);
  --f-guess: var(--face-candy-guess);
  --f-ease-settle: var(--ease-settle);
  --f-dur-settle: 460ms;
  --f-overshoot: 1.2;
}

@media (min-width: 620px) {
  .faces { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .face--lead { grid-column: 1 / -1; }
}

/* Wide enough for the lead's instrument to run long and the words to sit
   beside it, which is the only place the featured card differs in kind. */
@media (min-width: 860px) {
  .face--lead {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    align-items: center;
  }
  .face--lead .face-instrument {
    border-bottom: 0;
    border-right: 1px solid var(--hairline);
    padding: 28px;
  }
  .face--lead .face-meta { padding: 24px 28px; }
}

/* --- The seven that are not shown ------------------------------------- */
.faces-dare {
  margin-top: clamp(32px, 5vw, 44px);
  max-width: var(--measure);
  color: var(--ink-dim);
  font-size: var(--fs-small);
}

/* Seven tiles, eight cells: the mystery takes two, which squares the row and
   gives the one Face that withholds its name the width its extra line needs.
   Dense flow backfills the cell that split leaves behind, so the row never
   ends on a stranded tile. */
.faces-locked {
  margin-top: 14px;
  display: grid;
  grid-auto-flow: dense;
  gap: 8px;
}

/* No palette leaks out of these: the ground is the shell's own lift, so a
   covered Face reads as an instrument with the lights off, not as a swatch. */
.locked {
  /* One step under the name, but well clear of `ink-faint`, which the page
     spends on glyphs rather than on anything a visitor has to read. */
  --locked-sub: color-mix(in oklab, var(--ink-dim) 70%, var(--ink-faint));
  min-width: 0;
  max-width: 100%;
  padding: 10px 14px;
  border: 1px solid color-mix(in oklab, var(--rail) 46%, transparent);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--ground-lift) 34%, transparent);
}

.locked-name {
  display: block;
  font-size: var(--fs-small);
  font-weight: var(--w-ui);
  line-height: 1.3;
  color: var(--ink-dim);
}

.locked-cond {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-label);
  line-height: 1.4;
  color: var(--locked-sub);
}

/* Graphite: the one rung the cabinet will not name. Its three strings are the
   app's, in the app's order — the glyph sits where a condition would, on the
   numeric face the lock's progress line uses. */
.locked--mystery .locked-cond,
.locked-prog {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
}
.locked--mystery .locked-cond { color: var(--ink-dim); }

.locked-prog {
  display: block;
  margin-top: 3px;
  font-size: var(--fs-label);
  line-height: 1.4;
  color: var(--locked-sub);
}

@media (min-width: 380px) {
  .faces-locked { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .locked--mystery { grid-column: span 2; }
}
@media (min-width: 900px) {
  .faces-locked { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ===== 5 · SCREENSHOTS ===== */
.shots {
  display: flex;
  gap: 20px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 12px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

.shot {
  margin: 0;
  flex: 0 0 min(272px, 74vw);
  scroll-snap-align: center;
}

/* Instrument bezel around each real capture */
.shot-frame {
  padding: 8px;
  background: var(--ground);
  border: 1px solid var(--rail);
  border-radius: var(--bezel-radius);
  box-shadow: 0 18px 44px color-mix(in oklab, var(--ground) 72%, transparent);
}
.shot-frame img {
  display: block;
  width: 100%;
  border-radius: var(--screen-radius);
}

.shot figcaption {
  margin-top: 14px;
  text-align: center;
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

@media (min-width: 900px) {
  .shots {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(20px, 3vw, 32px);
    overflow: visible;
    row-gap: 40px;
  }
  .shot { flex: none; }
}

/* ===== 6 · CTA ===== */
.cta-inner {
  max-width: 620px;
  margin: 0 auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: clamp(36px, 6vw, 56px) clamp(20px, 4vw, 40px);
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
}
.cta-inner h2 {
  font-family: var(--face-numeric);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.9rem, 6vw, 2.9rem);
  font-weight: var(--w-hero);
  letter-spacing: -0.03em;
}
.cta-inner > p {
  color: var(--ink-dim);
  max-width: 44ch;
}

/* ===== 7 · FOOTER ===== */
.site-foot {
  border-top: 1px solid var(--hairline);
  padding: clamp(40px, 6vw, 64px) var(--gutter) 40px;
}

.foot-inner {
  max-width: var(--page);
  margin: 0 auto;
  display: grid;
  gap: 28px;
}

.foot-brand p {
  margin-top: 12px;
  color: var(--ink-dim);
  font-size: var(--fs-small);
  max-width: 34ch;
}

.foot-col h2 {
  font-size: var(--fs-label);
  font-weight: var(--w-ui);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 10px;
}
.foot-col p {
  font-size: var(--fs-small);
  color: var(--ink-dim);
  max-width: 34ch;
}
.foot-col a { color: var(--ink); text-decoration-color: var(--rail-tick); }
.foot-col a:hover { text-decoration-color: var(--signal-target); }

.foot-legal {
  max-width: var(--page);
  margin: 36px auto 0;
  font-size: var(--fs-small);
  color: var(--ink-dim);
}

@media (min-width: 760px) {
  .foot-inner { grid-template-columns: 1.4fr 1fr 1fr; gap: 40px; }
}

/* ===== 8 · LEGAL / SUPPORT PAGES =====
   privacy.html and support.html share this one block — same page shell
   (site-head / site-foot), same tokens, a plain-prose layout instead of the
   marketing page's sections. */
.legal-page {
  max-width: 720px;
  margin: 0 auto;
  padding: clamp(28px, 6vw, 48px) var(--gutter) var(--section-y);
}

.legal-page h1 {
  font-size: clamp(2rem, 6vw, 2.75rem);
  font-weight: var(--w-hero);
  letter-spacing: -0.02em;
  margin-bottom: 8px;
}

.legal-page .updated,
.legal-page .subtitle {
  color: var(--ink-dim);
  font-size: var(--fs-small);
  margin-bottom: clamp(32px, 5vw, 48px);
}

.legal-page h2 {
  font-size: var(--fs-h3);
  font-weight: var(--w-ui);
  letter-spacing: -0.005em;
  margin: 40px 0 14px;
}

.legal-page p {
  color: var(--ink-dim);
  margin-bottom: 16px;
}

.legal-page ul {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0 0 16px;
}
.legal-page ul li {
  position: relative;
  padding-left: 20px;
  color: var(--ink-dim);
}
.legal-page ul li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.6em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--rail-tick);
}
.legal-page ul li strong { color: var(--ink); font-weight: var(--w-ui); }

.legal-page a {
  color: var(--ink);
  text-decoration-color: var(--rail-tick);
}
.legal-page a:hover { text-decoration-color: var(--signal-target); }

.legal-divider {
  height: 1px;
  background: var(--hairline);
  margin: 40px 0;
}

.faq-item {
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: clamp(18px, 3vw, 24px);
  margin-bottom: 14px;
}
.faq-item h3 {
  font-size: var(--fs-h3);
  font-weight: var(--w-ui);
  letter-spacing: -0.005em;
  margin-bottom: 8px;
}
.faq-item p { margin: 0; color: var(--ink-dim); }

.contact-card {
  margin-top: 20px;
  padding: clamp(28px, 4vw, 40px);
  text-align: center;
  background: var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
}
.contact-card h2 {
  font-size: var(--fs-h3);
  font-weight: var(--w-hero);
  margin-bottom: 8px;
}
.contact-card p { color: var(--ink-dim); margin-bottom: 20px; }

/* `.legal-page a { color: var(--ink); }` outweighs a single class on
   specificity, so the override needs the extra qualifier. */
.legal-page .contact-btn {
  display: inline-flex;
  align-items: center;
  padding: 12px 26px;
  border-radius: var(--radius);
  background: var(--ink);
  color: var(--ground);
  font-weight: var(--w-ui);
  text-decoration: none;
  transition: transform 160ms var(--ease-out), box-shadow 160ms var(--ease-out);
  box-shadow: 0 10px 30px color-mix(in oklab, var(--ground) 70%, transparent);
}
.contact-btn:hover { transform: translateY(-2px); }

/* ===== MOTION =====
   One rhythm, the app's: commit 0 ms, target +120 ms, verdict +240 ms,
   score +380 ms (product.md, "The reveal"). The hero plays it on load in CSS
   alone, so the page needs no script to be readable; the three beats play it
   on scroll, and only once main.js has claimed them. */
@media (prefers-reduced-motion: no-preference) {
  .load-beat {
    opacity: 0;
    animation: beat-in var(--dur-reveal) var(--ease-out) forwards;
  }
  .load-beat[data-load="0"] { animation-delay: var(--beat-commit); }
  .load-beat[data-load="1"] { animation-delay: var(--beat-target); }
  .load-beat[data-load="2"] { animation-delay: var(--beat-verdict); }
  .load-beat[data-load="3"] { animation-delay: var(--beat-score); }

  .js-beats .beat {
    opacity: 0;
    transform: translateY(10px);
    transition: opacity var(--dur-reveal) var(--ease-out),
                transform var(--dur-reveal) var(--ease-out);
  }
  .js-beats .beat.is-in { opacity: 1; transform: none; }

  /* The three shown Faces settle their guess marker as they scroll in: it
     drops onto the rail and lands with that Face's own motion — the kick is
     the app's own `overshoot × 9px` (linepoint/src/index.css, @keyframes
     settle), run on that Face's ease and duration. One settle each, then the
     card is still. */
  .js-settle .face[data-settle] .mini-guess-mark {
    transform: translate(0, -52px);
  }
  .js-settle .face[data-settle].is-settled .mini-guess-mark {
    transform: none;
    animation: mini-settle var(--f-dur-settle) var(--f-ease-settle) both;
  }
}

@keyframes mini-settle {
  from { transform: translate(calc(var(--f-overshoot) * 9px), -52px); }
  to { transform: translate(0, 0); }
}

@keyframes beat-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

/* The brand's motion has a sanctioned off switch: the whole sequence
   collapses to a single 120 ms fade (product.md, "The reveal"). */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .load-beat { animation: fade-in var(--beat-target) linear forwards; }
  .js-beats .beat { opacity: 0; transition: opacity var(--beat-target) linear; }
  .js-beats .beat.is-in { opacity: 1; }
  .store-badge { transition: none; }
  .contact-btn { transition: none; }

  /* The rail's reveal collapses the same way: the script drops all four
     beats at once and the score jumps to its total, so nothing here is left
     to animate. Rings are pure flourish — the app hides them too. */
  .rail.is-guess .rail-guess,
  .rail.is-verdict .rail-bracket { animation: none; }
  .rail.is-target .rail-target { transition: none; }
  .rail-ring { display: none; }
  .rail-aim { transition: none; }
  .rail-again { transition: none; }
}

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
