/* =========================================================
   RUNDOWN — production landing ("ONE PLAY" rebuild, 2026-07-18)
   Seven beats around one authored idea:
   1 LIGHTS (dusk arrival) · 2 THE PLAY LIVE · 3 THE FREEZE +
   scroll scrub (D3 signature) · 4 THE RESOLUTION = THE PACKAGE
   · 5 POSTGAME · 6 DEEP DIVES · 7 JOIN.
   All color from tokens.css. Palette sealed (docs/21).
   Motion: transform/opacity/clip-path only; the field engine
   (play.js) is the star and owns the pinned viewport.
   ========================================================= */

/* Inline glyph icons (see #gi-* sprite in index.html) — sized to the text,
   inherits currentColor, so ⏸ ✓ ▸ ↺ ● never render as OS color emoji. */
.gi {
  width: 1em;
  height: 1em;
  fill: currentColor;
  vertical-align: -0.125em;
  display: inline-block;
  flex: none;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; --svh: 100svh; }
/* ?strip=1 screenshot mode: cap full-viewport beats so a tall capture
   shows the whole narrative in one strip */
html.strip { --svh: 880px; }
/* clip, not hidden: overflow-x hidden would make <body> the scroll container
   and break window.scrollTo / scrollIntoView (and the ?at screenshot hooks) */
body { margin: 0; overflow-x: clip; background: var(--rd-forest-3); }
/* index.html sets viewport-fit=cover. In landscape the display cutout
   takes a 47px gutter on one side and the page draws straight under it:
   measured 2026-08-05 at 844x390, the pricing headline, all three price
   cells, the FAQ summary and the "Play the free preview" CTA all sat
   inside the inset. box-sizing is border-box globally (line 23), so the
   padding eats into the 92vw instead of widening it, and in portrait
   both insets are 0 and nothing moves. */
.container {
  width: min(1220px, 92vw); margin: 0 auto;
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}
img { max-width: 100%; }

/* instant mode (?at= screenshots + reduced-motion): every choreographed
   state jumps to its end frame */
html.instant .oneplay *, html.instant .oneplay *::before, html.instant .oneplay *::after {
  transition-duration: 0s !important;
  transition-delay: 0s !important;
  animation-duration: 0s !important;
  animation-delay: 0s !important;
}

/* ---------- buttons ---------- */
.btn {
  display: inline-block;
  font-family: var(--rd-display);
  font-size: 1.35rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: none;
  line-height: 1;
  padding: 15px 26px 13px;
  border-radius: 10px;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform var(--rd-motion), background var(--rd-motion), color var(--rd-motion);
}
.btn:hover { transform: translateY(-2px); }
.btn-primary { background: var(--rd-forest); color: var(--rd-cream); box-shadow: var(--rd-cta-lift), var(--rd-shadow-sm); }
.btn-primary:hover { background: var(--rd-forest-2); color: var(--rd-cream); }
.btn-gold { background: var(--rd-gold); color: var(--rd-forest-3); box-shadow: inset 0 -2px 0 rgba(8,58,45,0.45), var(--rd-shadow-gold); }
.btn-gold:hover { background: var(--rd-gold-2); color: var(--rd-forest-3); }
.btn, .btn:hover { text-decoration: none; }

/* =========================================================
   THE CHROME BUDGET (elevation directive D2, 2026-08-04)
   Mono is a precision instrument, not wallpaper. It is allowed in
   FOUR roles and nowhere else:
     1. broadcast bug + lower third   JetBrains 700, 11px min, 0.06em
     2. numerals and measures         JetBrains 700, tabular, 14px min
     3. status pills                  JetBrains 700, 11px min, 0.10em
     4. honesty labels                JetBrains 700, 11px min, 0.10em, AA
   Everything that was a section eyebrow is now the docs/21 §4.2 Eyebrow:
   Inter 700, 12px, 0.12em, uppercase. Floors that hold everywhere:
   no type under 11px, and tracking above 0.18em only at >=11px.
   ========================================================= */
.rd-eyebrow {
  font-family: var(--rd-body);
  font-size: 12px; font-weight: 700; line-height: 1.2;
  letter-spacing: 0.12em; text-transform: uppercase;
}

/* the shared kicker style */
.slate-kicker {
  font-size: 12px; font-weight: 700; letter-spacing: 0.12em;
  color: var(--rd-forest); margin: 0;
  border-left: 4px solid var(--rd-gold); padding-left: 12px;
}
@keyframes cue-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(5px); } }
@keyframes fade-up { 0% { opacity: 0; transform: translateY(14px); } 100% { opacity: 1; transform: translateY(0); } }
.cue-arrow { display: inline-block; animation: cue-bob 1.6s ease-in-out infinite; }

/* anchor aliases so old inbound links (#how, #swing-lab, #founding, #cover…) still land */
.anchor-alias { display: block; height: 0; }

/* =========================================================
   BEAT 1 — LIGHTS. Night-game arrival (~2.5s): the world starts
   near-BLACK, four stadium banks bloom on hard (flicker + settle),
   the ground pushes in as it lights, then the reversed lockup
   lands with weight + a gold underline sweep.
   All choreography is CSS opacity/transform, gated to html.js
   + no-preference; the default paint IS the finished lit frame
   (JS-off / reduced-motion correct by construction).
   html.arrival-skip (any scroll/tap/key — program.js) zeroes the
   durations so the finished frame lands instantly.
   ========================================================= */
.lights {
  position: relative;
  height: var(--svh);
  min-height: 560px;
  overflow: hidden;
  background: #041A13;
  /* the ONE real light standard in the plate (fixture at 23.0% / 7.5%
     of the frame) — bloom origin in viewport terms, re-derived per
     breakpoint below so the strike sits ON the pole at every crop */
  --pole-x: 20.7%;
  --pole-y: 7.5%;
}
.lights-art {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 24% 42%;
  /* golden hour regraded to dusk: static filter, never animated */
  filter: saturate(0.68) brightness(0.5) contrast(1.06);
}
/* wide frames crop the plate vertically — ride the crop toward the top
   so the light standard stays in frame, and move the bloom origin with it */
@media (min-aspect-ratio: 4/3) {
  .lights-art { object-position: 24% 20%; }
  .lights { --pole-x: 23%; --pole-y: 6.7%; }
}
@media (min-aspect-ratio: 16/9) {
  .lights-art { object-position: 24% 12%; }
  .lights { --pole-x: 23%; --pole-y: 6.7%; }
}
/* the dark of the dusk — static; the blackout below does the theater */
.lights-grade {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(120% 90% at 50% 108%, rgba(4,26,19,0.35), rgba(4,26,19,0) 55%),
    linear-gradient(180deg, rgba(4,26,19,0.72) 0%, rgba(4,26,19,0.5) 45%, rgba(4,26,19,0.78) 100%);
  opacity: 0.5;
}
/* the house-dark layer: opacity 1 = near-black, steps DOWN in hard
   drops as each bank comes on. Default (finished frame) is 0. */
.lights-blackout {
  position: absolute; inset: 0; pointer-events: none;
  background: #020D0A;
  opacity: 0;
}
/* the arrival hardware: ONE hard bank strike anchored to the real pole
   (--pole-x/y), then three ambient lifts of the same scene — sky glow,
   field bounce, the throw across the player. No phantom standards. */
.lights-bloom {
  position: absolute; inset: 0; pointer-events: none;
  mix-blend-mode: screen;
  opacity: 1;
}
.lb-1 { /* THE bank — blooms on the fixture itself, strikes from it */
  background: radial-gradient(30% 26% at var(--pole-x) var(--pole-y), rgba(255,252,244,0.5), rgba(245,241,232,0.26) 34%, rgba(244,196,48,0.11) 60%, transparent 78%);
  transform-origin: var(--pole-x) var(--pole-y);
}
.lb-2 { /* sky glow — the dome the bank throws up */
  background: radial-gradient(85% 55% at var(--pole-x) 0%, rgba(244,196,48,0.14), rgba(245,241,232,0.07) 45%, transparent 72%);
}
.lb-3 { /* field bounce — the dirt catching the light */
  background: radial-gradient(80% 45% at 45% 100%, rgba(245,241,232,0.14), rgba(244,196,48,0.05) 55%, transparent 75%);
}
.lb-4 { /* the throw across the player, downfield of the pole */
  background: radial-gradient(46% 52% at calc(var(--pole-x) + 12%) 55%, rgba(245,241,232,0.17), rgba(244,196,48,0.06) 55%, transparent 76%);
}
.lights-inner {
  position: absolute; inset: 0;
  display: grid; place-content: center; justify-items: center;
  gap: 22px; padding: 0 6vw;
  text-align: center;
}
.lights-lockup { margin: 0; line-height: 0; }
.lights-lockup img {
  width: clamp(270px, 40vw, 600px); height: auto; display: block;
}
/* the gold underline sweep under the lockup (display register, docs/21) */
.lights-rule {
  width: clamp(170px, 24vw, 360px); height: 5px;
  background: var(--rd-gold);
  border-radius: 3px;
  transform-origin: left center;
  margin-top: -4px;
  box-shadow: 0 2px 14px rgba(244,196,48,0.35);
}
.lights-line {
  margin: 0;
  font-size: 12px; font-weight: 700; letter-spacing: 0.12em;
  color: var(--rd-cream);
  padding-top: 4px;
}
/* The eyebrow token uppercases its run, which turned "iPhone" into "IPHONE".
   Apple's mark keeps its lowercase i, so this one span carries its own casing
   and is already typed in caps in the markup. */
.rd-eyebrow.keeps-case { text-transform: none; }
.lights-ctas {
  position: absolute; left: 50%; bottom: 56px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 18px;
  white-space: nowrap;
}
.app-store-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 120px;
  min-height: 44px;
  transition: transform var(--rd-motion);
}
.app-store-link:hover { transform: translateY(-2px); }
.app-store-link:focus-visible { outline: 3px solid var(--rd-gold); outline-offset: 3px; border-radius: 8px; }
.app-store-badge { display: block; width: 120px; height: 40px; }
/* 44px hit height without moving the layout: the padding is cancelled by an
   equal negative margin (the pattern used for every inline link on the page) */
.lights-alt {
  font-family: var(--rd-body); font-weight: 700; font-size: 1rem;
  color: var(--rd-cream);
  text-decoration: underline; text-decoration-thickness: 2px;
  text-decoration-color: var(--rd-gold); text-underline-offset: 4px;
  display: inline-flex; align-items: center;
  min-height: 44px; padding-block: 10px; margin-block: -10px;
}
.lights-alt:hover { color: var(--rd-gold); }
.lights-cue {
  /* absolutely positioned against the section, so the .container
     safe-area padding does not reach it: at 844x390 "THE PLAY" and its
     arrow sat inside the right-hand cutout gutter (L-SAFE) */
  position: absolute; right: calc(16px + env(safe-area-inset-right, 0px));
  bottom: 8px; z-index: 3;
  font-size: 12px; font-weight: 700; letter-spacing: 0.12em;
  color: var(--rd-cream); text-decoration: none;
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 6px;
}
.lights-cue:hover { color: var(--rd-gold); }

/* the arrival ritual (~2.5s): near-black hold → THE bank strikes hard on
   the pole (flicker, over-bloom, settle) → the sky glow, the throw across
   the player, and the field bounce lift the scene in turn (the blackout
   drops a step with each) + the ground pushes in → the title lands with
   weight → the gold rule sweeps → support copy.
   ANY scroll/tap/key: html.arrival-skip zeroes it all to the end frame. */
/* RETIMED 2026-08-04 (elevation directive D10): the ritual is unchanged, it
   just stops making a coach who arrived from a story link wait. The whole
   arrival compresses by ~420ms and the first button paints at ~2,280ms
   instead of 2,700ms. Nobody redesigns the strike. */
@media (prefers-reduced-motion: no-preference) {
  html.js .lights-art      { animation: ground-push 2200ms var(--rd-ease-camera) both; }
  html.js .lights-blackout { animation: house-lights 1560ms linear both; }
  html.js .lb-1 { animation: bank-on 780ms linear 240ms both; }
  html.js .lb-2 { animation: ambient-lift 820ms ease-out 560ms both; }
  html.js .lb-4 { animation: ambient-lift 780ms ease-out 860ms both; }
  html.js .lb-3 { animation: ambient-lift 860ms ease-out 1080ms both; }
  html.js .lights-lockup { animation: title-land 480ms var(--rd-ease-land) 1200ms both; }
  html.js .lights-rule   { animation: rule-sweep 420ms var(--rd-ease-land) 1520ms both; }
  html.js .lights-line   { animation: fade-up 360ms var(--rd-ease-settle) 1620ms both; }
  html.js .lights-ctas   { animation: ctas-up 380ms var(--rd-ease-land) 1900ms both; }
  html.js .lights-cue    { animation: cue-in 380ms var(--rd-ease-settle) 2100ms both; }
}
/* the skip: every arrival animation jumps to its 100% (fill: both) frame */
html.arrival-skip #lights, html.arrival-skip #lights * {
  animation-duration: 0s !important;
  animation-delay: 0s !important;
}
@keyframes ground-push {
  from { transform: scale(1.07) translateY(1.2%); }
  to   { transform: scale(1) translateY(0); }
}
/* hard drops, one per bank — house lights, not a fade */
@keyframes house-lights {
  0%, 15%  { opacity: 1; }
  17%, 34% { opacity: 0.8; }
  36%, 54% { opacity: 0.58; }
  56%, 73% { opacity: 0.34; }
  75%, 92% { opacity: 0.14; }
  100%     { opacity: 0; }
}
/* an ambient lift — the scene catching the bank's light: rise, slight
   over-lift, one flicker echo off the bank, settle. No strike of its own. */
@keyframes ambient-lift {
  0%   { opacity: 0; }
  45%  { opacity: 0.9; }
  60%  { opacity: 1; }
  74%  { opacity: 0.8; }
  100% { opacity: 1; }
}
/* THE bank coming on: hard strike → flicker dip → bloom past → settle */
@keyframes bank-on {
  0%   { opacity: 0;    transform: scale(0.72); }
  7%   { opacity: 1;    transform: scale(1.1); }
  14%  { opacity: 0.2;  transform: scale(1.02); }
  26%  { opacity: 1;    transform: scale(1.08); }
  42%  { opacity: 0.78; transform: scale(1.01); }
  100% { opacity: 1;    transform: scale(1); }
}
/* the title lands with weight: drops in big and stops on its mark.
   The 0.985 undershoot bounce came out 2026-08-04 — the stamp is the only
   spring left on the page (D3). */
@keyframes title-land {
  0%   { opacity: 0; transform: scale(1.32) translateY(2%); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}
/* the CTA row is centered by its own translateX(-50%) — its rise keeps it
   (a plain fade-up's fill frame would wipe the centering) */
@keyframes ctas-up {
  from { opacity: 0; transform: translate(-50%, 14px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
@keyframes rule-sweep {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
@keyframes cue-in { from { opacity: 0; } to { opacity: 1; } }

/* =========================================================
   BEATS 2 + 3 — one full-viewport stage that RUNS BY ITSELF.
   Normal-height section: scrolling just moves down the page
   while the broadcast loop (play.js) keeps performing.
   ========================================================= */
/* THE PLAY TAKES THE PHONE (elevation directive D1, 2026-08-04).
   Measured before: the field ran at 46.4% of a 390x844 viewport with ~286px
   of dead cream above it and ~227px below, the broadcast bug floating in the
   cream ABOVE the picture, and a 9px legend underneath explaining what the
   colours meant. A legend is a written admission that the picture failed.
   Now: zero cream in the beat. The ground bleeds edge to edge, the chrome
   sits ON the picture, and the legend is gone because the picture is legible.
   The bleed uses the sealed #rf-turf stops and the #rf-mow values verbatim
   (docs/21 §7.9) — this changes framing and scale, never dirt or palette. */
.oneplay { height: var(--svh); min-height: 520px; background: var(--rd-forest-3); position: relative; }
.play-pin {
  position: relative;
  height: 100%;
  min-height: 480px;
  overflow: hidden;
  background:
    repeating-linear-gradient(72deg, rgba(245,241,232,0.04) 0 28px, rgba(6,43,33,0.06) 28px 56px),
    radial-gradient(78% 78% at 50% 62%, #11614C 0%, #0B4D3C 55%, #083A2D 100%);
}
/* the horizon: above the far edge of the ground it is dusk, not more grass */
.play-pin::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 34%;
  background: linear-gradient(180deg, #052A20 0%, rgba(8,58,45,0.72) 46%, rgba(11,77,60,0) 100%);
  pointer-events: none; z-index: 1;
}
/* and the near edge falls off into shade, so the lower third has a ground */
.play-pin::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 30%;
  background: linear-gradient(0deg, rgba(5,42,32,0.78) 0%, rgba(5,42,32,0.28) 45%, rgba(5,42,32,0) 100%);
  pointer-events: none; z-index: 1;
}
/* THE RE-SET BEAT IS A CROSS-DISSOLVE, NOT A BLACKOUT (D3, 2026-09-02).
   It used to be `.viewport.resetting .fit { opacity: 0 }`: the stage faded to
   nothing while the bug still read FINAL, so for about half a second the whole
   section was an empty striped rectangle with one chrome pill in the corner.
   Captured at both 1440x900 and 390x844.
   Now play.js clones the finished stage into .reset-ghost, holds that clone at
   full opacity, resets the live stage underneath it, and dissolves the clone
   out over the incoming frame. The live .fit never drops below opacity 1, so
   no frame of the loop is an empty field. z-index 5 keeps the clone above the
   stage and below the bug (6) and the live stamp (7). */
.reset-ghost {
  position: absolute; inset: 0; z-index: 5;
  pointer-events: none; opacity: 1;
  transition: opacity 520ms var(--rd-ease-settle);
}
.reset-ghost.out { opacity: 0; }
.viewport.resetting .pbp { opacity: 0.55; }
/* the live stamp cuts instantly because its clone is already on screen inside
   the ghost, at the same coordinates, fading on the ghost's timeline.
   !important beats the filled stampin animation. */
.viewport.resetting .stamp { opacity: 0 !important; }

/* --- the 3D stage (system ported verbatim from field-3d) --- */
.viewport { position: relative; width: 100%; aspect-ratio: 620 / 548; min-height: 340px; }
.vp-full { position: absolute; inset: 0; aspect-ratio: auto; min-height: 0; }
.arena-glow {
  position: absolute; inset: -8% -6%;
  background: radial-gradient(58% 52% at 50% 44%, rgba(14,92,72,0.16), rgba(14,92,72,0.0) 70%);
  pointer-events: none;
}
.stage-shadow {
  position: absolute; left: 50%; bottom: 3%;
  width: 74%; height: 44px; transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(8,58,45,0.36), transparent);
  filter: blur(14px);
  pointer-events: none;
}
.fit {
  position: absolute; left: 50%; top: 50%;
  width: 600px; height: 560px;
  /* the trailing translateX runs in STAGE space, after the scale, so the nudge
     is measured in field units and rides --fit at every phone width. D4 uses
     it to balance the crop: at the phone camera the runner rounding third and
     the ball in right field sit at opposite edges of the glass, and centring
     the stage on world x 300 left her 5px outside and the ball 26px inside. */
  transform: translate(-50%, -51%) scale(var(--fit, 1)) translateX(var(--stage-nudge, 0px));
  pointer-events: none;
}
.stage3d { position: relative; width: 600px; height: 560px; perspective: 1150px; perspective-origin: 50% 26%; }
.rig { position: absolute; inset: 0; transform-style: preserve-3d; will-change: transform; }
.backdrop {
  position: absolute; left: -190px; top: -150px; width: 980px; height: 300px;
  transform: translateZ(-150px); pointer-events: none;
  opacity: 0.7;
  /* full-bleed stage: fade the sheet's side edges AND keep the haze above
     the field's horizon — overlapping the outfield read as a misregistered
     panel in freeze-frame QC */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 22%, #000 78%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0%, #000 22%, #000 78%, transparent 100%);
}
.haze {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(245,241,232,0) 0%, rgba(228,233,218,0.85) 36%, rgba(214,224,205,0.9) 62%, rgba(245,241,232,0) 100%);
  filter: blur(2px);
}
.crowd {
  position: absolute; left: 6%; right: 6%; height: 26px;
  background-image: radial-gradient(circle at 6px 9px, rgba(11,77,60,0.42) 2.6px, transparent 3.6px);
  background-size: 23px 15px;
  filter: blur(4.5px);
  border-radius: 50%;
}
.crowd-1 { top: 52%; opacity: 0.32; }
.crowd-2 { top: 63%; left: 12%; right: 12%; opacity: 0.2; background-size: 19px 13px; }
.world { position: absolute; inset: 0; transform: rotateX(40deg); transform-style: preserve-3d; will-change: transform; }
.ground { position: absolute; inset: 0; width: 100%; height: 100%; }
.dof-far {
  position: absolute; inset: 0; filter: blur(2px);
  -webkit-mask-image: linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.65) 12%, rgba(0,0,0,0) 27%);
          mask-image: linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.65) 12%, rgba(0,0,0,0) 27%);
  pointer-events: none;
}
.dof-far svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.sweep {
  position: absolute; inset: 0; border-radius: 26px;
  background: linear-gradient(112deg, transparent 34%, rgba(244,196,48,0.14) 46%, rgba(245,241,232,0.12) 51%, rgba(244,196,48,0.10) 56%, transparent 68%);
  background-size: 320% 320%; background-position: 0% 0%;
  mix-blend-mode: soft-light; opacity: 0; pointer-events: none;
}
.sweep.on { animation: sweep-run 2.6s var(--rd-ease-camera) both; }
@keyframes sweep-run {
  0% { opacity: 0; background-position: 0% 0%; }
  18% { opacity: 1; } 82% { opacity: 1; }
  100% { opacity: 0; background-position: 100% 100%; }
}
.peg { position: absolute; left: 0; top: 0; width: 0; height: 0; transform-style: preserve-3d; }
.peg .contact {
  position: absolute; left: -17px; top: -15px; width: 34px; height: 30px;
  background: radial-gradient(closest-side, rgba(5,42,32,0.5), transparent 72%);
  transform: translate3d(var(--shx, 0px), var(--shy, 0px), 0) scale(var(--shs, 1));
  opacity: var(--sho, 0.5);
}
.stand {
  position: absolute; bottom: 0; left: 0;
  transform-origin: 50% 100%;
  transform: translateX(-50%) rotateZ(var(--nyaw, 0deg)) rotateX(var(--tiltneg, -40deg)) scale(var(--k, 1));
  transform-style: preserve-3d;
  white-space: nowrap;
}
.disc {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%;
  font-family: var(--rd-mono); font-size: 15px; font-weight: 700; letter-spacing: 0.02em;
  border: 2.5px solid;
  box-shadow: 0 12px 18px -9px rgba(5, 42, 32, 0.65), inset 0 -3px 0 rgba(8, 58, 45, 0.16);
  margin-bottom: 4px;
}
.tok-d .disc { background: #F5F1E8; color: #0B4D3C; border-color: rgba(8,58,45,0.68); }
.tok-r .disc { background: #F4C430; color: #083A2D; border-color: #083A2D; width: 46px; height: 46px; font-size: 16px; }
.stand .leg {
  position: absolute; left: 50%; bottom: 0; width: 2.5px; height: 5px;
  margin-left: -1px; background: rgba(8,58,45,0.55); border-radius: 2px;
}
/* an invisible 2px marker standing on home plate. It is a real peg on the
   plane, so its screen position already carries the camera, the tilt, the
   yaw and the stage scale; play.js reads it to put the scorebook stamp on the
   plate instead of on a hard-coded percentage. */
.platepin { display: block; width: 2px; height: 2px; opacity: 0; }
/* dimensional players (elevation directive D1) — figures.js billboards.
   Discs above remain the reduced-motion / no-module fallback. */
.figstage { display: block; width: 56px; height: 58.5px; margin: 0 auto; overflow: visible; }
.fig-tok .contact {
  left: -19px; width: 38px;
  /* one light source, upper right: contact shadow pools lower-left */
  --shx: -3px; --shy: 1px;
}
.toklab {
  /* position tag at the figure's feet. It must stay at/above the billboard's
     bottom edge: the stand pivots on its base, so anything below the pivot
     rotates BEHIND the ground plane and vanishes (found in close-shot QC). */
  position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  font-family: var(--rd-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.02em;
  line-height: 1;
  color: var(--rd-cream);
  text-shadow: 0 1px 0 rgba(6,21,16,0.85), 0 0 4px rgba(6,21,16,0.65), 0 0 2px rgba(6,21,16,0.8);
  pointer-events: none;
}
.ball {
  position: relative; display: block; width: 23px; height: 23px; border-radius: 50%;
  margin: 0 0 1px;
  background: radial-gradient(circle at 34% 30%, #FFFFFF 0%, #FFFDF6 48%, #E4DEC9 100%);
  border: 3px solid #F4C430;
  box-shadow: 0 0 0 1.5px rgba(5, 42, 32, 0.3), 0 4px 10px rgba(5, 42, 32, 0.42);
  transform: translateY(calc(var(--h, 0) * -1px));
}
.ghost .ball { border-width: 2.5px; box-shadow: none; }
.wallseg {
  display: block; width: 82px; height: 20px; border-radius: 2px 2px 0 0;
  background: linear-gradient(180deg, #0B4A38 0%, #052A20 85%);
  border-top: 2px solid rgba(245,241,232,0.45);
  box-shadow: 0 5px 9px -5px rgba(5,42,32,0.55);
}
.wallseg.sign {
  display: flex; align-items: center; justify-content: center;
  font-family: var(--rd-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.10em;
  color: rgba(245,241,232,0.85);
}
.stage-vignette {
  position: absolute; inset: -2%;
  background: radial-gradient(78% 72% at 50% 46%, transparent 62%, rgba(8,58,45,0.14) 100%);
  pointer-events: none;
}
.freeze-flash {
  position: absolute; inset: 0; border-radius: 22px;
  background: radial-gradient(60% 55% at 62% 34%, rgba(245,241,232,0.75), rgba(244,196,48,0.22) 55%, transparent 78%);
  opacity: 0; pointer-events: none;
}
.freeze-flash.flash-now { animation: flashfade 620ms ease-out both; }
@keyframes flashfade { 0% { opacity: 0.95; } 100% { opacity: 0; } }

/* --- HUD (broadcast chrome ON the picture) ---
   One bug, top-left, inside the safe area. The network name left the first
   cell and LIVE folded into the state segment, which retired the separate
   8px "LIVE FROM THE PREVIEW · REAL APP" assertion under it. 11px is the
   floor here and it matches the game run's bug (G6). */
.bug {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 12px);
  left: calc(env(safe-area-inset-left, 0px) + 12px);
  z-index: 6; display: flex; align-items: center; gap: 10px; height: 30px;
  background: rgba(8,58,45,0.86);
  border: 1px solid rgba(244,196,48,0.55);
  border-radius: 8px; padding: 0 12px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
  color: var(--rd-cream); white-space: nowrap;
}
.bug b { color: var(--rd-gold); font-weight: 700; }
.bug-sep { width: 1px; height: 14px; background: rgba(245,241,232,0.28); }
.bug-state { color: var(--rd-gold); }
/* the lower third. Same plate geometry as the game run's teaching line (G2):
   forest at 82%, 3px gold left rule, docked at the bottom 12% of the picture. */
.pbp {
  position: absolute; left: 0; bottom: 12%;
  z-index: 6; margin: 0; min-height: 1.4em; max-width: min(46ch, 92%);
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
  color: var(--rd-cream);
  background: rgba(8,58,45,0.82);
  border-left: 3px solid var(--rd-gold);
  border-radius: 0 var(--rd-radius-sm) var(--rd-radius-sm) 0;
  padding: 9px 16px 9px 13px;
  transition: opacity var(--rd-dur-land) var(--rd-ease-land);
}
/* THE STAMP LANDS ON THE PLATE (D3, 2026-09-02).
   History: it was pinned dead centre over the resolution frame, then exiled to
   the lower-left corner, where it sat in dead turf and called a play happening
   somewhere else in the picture. A broadcast stamp lands over the play it
   calls, and this call is SAFE AT HOME, so it lands on home plate.
   --stamp-x / --stamp-y are written by play.js from the live screen position
   of a zero-size marker peg standing on HOME (300,468) in field coordinates,
   so the stamp tracks the camera at every breakpoint instead of trusting a
   percentage. The stamp sits just above the plate, scaling down onto it:
   transform-origin is the bottom centre, which is the point it stamps. */
.stamp {
  position: absolute; z-index: 7;
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  opacity: 0; pointer-events: none;
  left: var(--stamp-x, 50%); top: var(--stamp-y, 76%);
  max-width: min(74%, 480px);
  transform-origin: 50% 100%;
  transform: translate(-50%, -230%) rotate(-4deg) scale(1.5);
  border-radius: var(--rd-radius-sm);
  padding: 12px 26px 10px;
  box-shadow: var(--rd-shadow-lg);
}
.stamp-big {
  font-family: var(--rd-display); text-transform: uppercase; letter-spacing: 0.03em;
  font-size: clamp(2.4rem, 3.8vw, 3.6rem); line-height: 1; white-space: nowrap;
}
.stamp-sub { font-size: 12px; font-weight: 700; letter-spacing: 0.09em; white-space: nowrap; }
.stamp-out { background: var(--rd-gold); border-left: 6px solid var(--rd-forest-3); }
.stamp-out .stamp-big, .stamp-out .stamp-sub { color: var(--rd-forest-3); }
.stamp-safe { background: var(--rd-forest); border-left: 6px solid var(--rd-gold); box-shadow: 0 0 0 1.5px rgba(245,241,232,0.3), var(--rd-shadow-lg); }
.stamp-safe .stamp-big { color: var(--rd-cream); }
.stamp-safe .stamp-sub { color: var(--rd-gold); }
/* THE one overshoot left on the page. An umpire's call should punch. */
.stamp.stamped { animation: stampin 420ms cubic-bezier(0.2, 0.9, 0.3, 1.25) both; }
@keyframes stampin {
  0%   { opacity: 0; transform: translate(-50%, -230%) rotate(-4deg) scale(1.6); }
  100% { opacity: 1; transform: translate(-50%, -230%) rotate(-4deg) scale(1); }
}
/* broadcast grammar: the lower third clears when the call stamps. Two chrome
   objects arguing over the same corner is what the old centre-stamp did to
   the picture. */
.viewport:has(.stamp.stamped) .pbp { opacity: 0; }
.bench {
  position: absolute; left: 8px; bottom: 8px; z-index: 9;
  font-size: 11px; font-weight: 700; color: var(--rd-forest);
  background: rgba(245,241,232,0.92); border-radius: 6px; padding: 4px 8px;
  display: none;
}
.bench.on { display: block; }

/* --- BEAT 3: the tagline lands on the freeze ---
   D3, 2026-09-02. The plate was centred (place-content: center), which put the
   words straight across the one thing the section exists to sell: at the
   freeze the ball, the right fielder and the decision ring sit in the UPPER
   RIGHT of the picture at every breakpoint, because it is always the same
   play, and line one of the tagline ran right through them. Measured at
   1440x900 the ring sits at roughly x 910 / y 390 and line one occupied
   y 335-420 across x 425-1005.
   The plate now rails to the LOWER LEFT, which is turf at the freeze in both
   crops, and the type is left-aligned rather than centred so it belongs to the
   left edge instead of floating. The words and the throw stop competing for
   the same 400px. The locked tagline itself and its place in the beat are
   unchanged: it still lands on the freeze and lifts on the resolve. */
.tagline-plate {
  position: absolute; inset: 0; z-index: 8;
  display: grid; align-content: end; justify-items: start; gap: 16px;
  /* The bottom rail is measured off the lower third, not guessed: .pbp docks
     at 12% of the stage and runs about 62px tall once it wraps, so the tagline
     stops 14px above it at every breakpoint including 844x390 landscape.
     Percentage padding resolves against WIDTH in CSS, which is why the first
     version of this rule sat 110px too low on a phone and 110px too high in
     landscape. --svh is the height the beat actually gets. */
  padding-left: 6vw; padding-right: 6vw;
  padding-bottom: calc(var(--svh) * 0.12 + 76px);
  pointer-events: none;
  opacity: 0;
  transition: opacity 300ms ease;
}
.tagline-plate::before {
  /* §2.5 scrim, moved with the type: a pool under the lower-left block, so it
     never lays a grey wash over the decision in the upper right. */
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(58% 30% at 26% var(--scrim-y, 76%), rgba(5,42,32,0.66), rgba(5,42,32,0.32) 58%, transparent 80%);
}
.tagline-plate.on { opacity: 1; }
.tagline {
  margin: 0; text-align: left;
  font-family: var(--rd-display); text-transform: uppercase;
  /* capped at 6rem, down from 7.6rem: at 1440 the old size ran the block from
     y 404 to 626 and swallowed R2 whole at y 419-497. She is the runner the
     decision is about. The type now sits under her and left of the throw. */
  font-size: clamp(3.6rem, 6.6vw, 6rem); line-height: 0.92; letter-spacing: 0.01em;
  color: var(--rd-cream);
  text-shadow: 0 3px 26px rgba(5,42,32,0.85), 0 1px 4px rgba(5,42,32,0.7);
  transform-origin: left bottom;
  transform: scale(1.28);
  transition: transform var(--rd-dur-land) var(--rd-ease-land);
}
.tagline-plate.on .tagline { transform: scale(1); }

/* =========================================================
   BEAT 4 — THE RESOLUTION = THE PACKAGE
   ========================================================= */
.package { background: #052A20; color: var(--rd-cream); padding: clamp(70px, 11vh, 120px) 0 clamp(70px, 10vh, 110px); }
.package .slate-kicker { color: var(--rd-gold); }
.pk-head { display: grid; gap: 14px; margin-bottom: 30px; }
.pk-h {
  font-family: var(--rd-display); text-transform: uppercase;
  font-size: clamp(2.6rem, 6vw, 5rem); line-height: 0.94; letter-spacing: 0.01em;
  color: var(--rd-cream); margin: 0;
}
/* D5, 2026-09-02. THE GRID IS BROKEN ON PURPOSE.
   It was `repeat(2, minmax(0,1fr))` with three children, so cell 2,2 was empty
   for 748px of the right half at 1440 and the section argued that a shipped
   flagship, a planner that is available now and an early-access lab are three
   equal cards. Twelve columns now: the IQ Game takes the whole first row with
   the answer frame as its lead object, then Swing Lab and Coaches Corner run
   as a 7/5 pair. No cell is empty at any width. The phone still collapses to
   one column (the S4 rule in scripts/site-css-structure-gate.mjs). */
.pk-grid {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 22px; align-items: stretch;
}
.pk-panel { grid-column: 1 / -1; }
.pk-copy, .pk-meta { display: grid; gap: 12px; align-content: start; }
@media (min-width: 901px) {
  .pk-swing { grid-column: span 7; }
  /* the plan card takes the slack rather than leaving it under the panel: the
     pair stretches to a common height, and the artifact is the part of this
     panel that should grow into it. */
  .pk-corner { grid-column: span 5; grid-template-rows: auto auto auto 1fr auto; }
  /* The flagship row: the frozen field holds column one at roughly twice the
     area it had as a half-width card, and the decision deck (the three real
     options, the coaching line and the library key) rails beside it with the
     name and the button.
     This is not a marketing rearrangement of the product. The game does
     exactly this itself: app/styles.css:1127, the landscape rule, sets
     `.stage { right: 50% }` and pins `.call-deck` to `inset: 0 0 0 50%`, field
     on the left, decision deck on the right. Portrait keeps the options under
     the picture in both places, which is also what the app does.
     The figure goes display:contents so its parts can take their own cells;
     nothing about an option's geometry changes. */
  .pk-iq {
    grid-template-columns: minmax(0, 66fr) minmax(0, 34fr);
    column-gap: clamp(28px, 3.2vw, 52px);
    row-gap: 14px;
    align-items: start;
  }
  .pk-iq > .anchor-alias { grid-area: 1 / 1; }
  .pk-iq > .answer-frame { display: contents; }
  .pk-iq > .pk-copy { grid-column: 2; grid-row: 1; }
  .pk-iq .af-stage { grid-column: 1; grid-row: 1 / span 5; }
  .pk-iq .af-options { grid-column: 2; grid-row: 2; margin-top: 0; }
  .pk-iq .af-verdict { grid-column: 2; grid-row: 3; margin: 0; }
  .pk-iq .af-foot { grid-column: 2; grid-row: 4; margin: 0; }
  .pk-iq > .pk-meta { grid-column: 2; grid-row: 5; margin-top: 6px; }
}
.pk-panel {
  position: relative;
  background: linear-gradient(180deg, #083A2D 0%, #062B21 100%);
  border: 1px solid rgba(245,241,232,0.14);
  border-radius: var(--rd-radius);
  box-shadow: var(--rd-shadow-lg);
  padding: 26px 26px 20px;
  display: grid; gap: 12px; align-content: start;
}
.pk-beat {
  margin: 0;
  font-size: 12px; font-weight: 700; letter-spacing: 0.12em;
  color: var(--rd-gold);
  border-left: 4px solid var(--rd-gold); padding-left: 12px;
}
.pk-name {
  margin: 0; font-family: var(--rd-display); text-transform: uppercase;
  font-size: clamp(2rem, 3.4vw, 2.9rem); line-height: 0.95; letter-spacing: 0.015em;
  color: var(--rd-cream);
}
.pk-line { margin: 0; font-size: 0.98rem; line-height: 1.55; color: rgba(245,241,232,0.9); max-width: 52ch; }
/* the measure line. It was underlined edge to edge in 3px gold, which made the
   heaviest gold object on the page a rule under fine print. It gets a 48px
   gold rule above it instead (D2). */
/* a 48px gold rule ABOVE the measure line, not a full-width rule under it:
   the underline was the heaviest gold object on the page and it underlined
   fine print (D2). The rule is a pseudo-element so the line itself can wrap. */
.pk-stat {
  margin: 12px 0 0; padding-top: 10px; position: relative;
  font-size: 14px; font-weight: 700; letter-spacing: 0.04em;
  color: var(--rd-cream);
}
.pk-stat::before {
  content: ""; position: absolute; top: 0; left: 0;
  width: 48px; height: 3px; background: var(--rd-gold);
}
.pk-status { margin: 8px 0 0; font-size: 12px; font-weight: 700; letter-spacing: 0.12em; color: rgba(245,241,232,0.72); }
.pk-status a { color: var(--rd-gold); text-decoration-color: var(--rd-gold); }
.pk-iq { justify-content: start; align-content: start; gap: 14px; }
.pk-panel .swing-fan { margin-top: 10px; }
/* the on-device pull-quote, promoted out of the deleted deep-dive strip */
.pk-quote {
  margin: 4px 0 0; padding: 12px 16px;
  border-left: 3px solid var(--rd-gold);
  background: rgba(5,42,32,0.55);
  border-radius: 0 var(--rd-radius-sm) var(--rd-radius-sm) 0;
  font-size: 1.02rem; line-height: 1.5; color: var(--rd-cream);
  max-width: 46ch;
}

/* =========================================================
   THE ANSWER MOMENT (elevation directive D4)
   The flagship panel used to be type over a void: the only shipped product
   was the only panel with no picture, while the early-access one got the fan.
   This is the answer frame — the real field paint cropped 16:11 to the throw,
   the real options as chips, and the real coaching line under it.
   Loops every 6.5s; reduced motion paints the resolved frame and stops.
   ========================================================= */
.answer-frame { margin: 4px 0 0; }
.af-stage {
  position: relative; overflow: hidden;
  border-radius: var(--rd-radius-sm);
  box-shadow: 0 10px 30px -12px rgba(5,42,32,0.8);
  background: #083A2D;
}
.af-field { display: block; width: 100%; height: auto; }
.af-lane {
  stroke-dasharray: 1; stroke-dashoffset: 1;
  filter: drop-shadow(0 2px 4px rgba(5,42,32,0.55));
}
/* The cast. .af-tok / .af-run were a white disc and a gold disc — the incumbent's
   drawing, on our panel. They are now posed figures from figures.js (see the
   comment above the markup in index.html), so the only thing left to style is
   the ground contact under each one and the position label beside it. */
/* the contact shadow falls DOWN-LEFT, because the key light in this world is
   upper-right (figures.js lights every torso from x1=1, and app.js:1798 records
   a shadow falling right of a right-lit figure as a contradiction). */
.af-fig { filter: drop-shadow(-2px 2px 2px rgba(4,26,19,0.45)); }
.af-lab {
  font-family: var(--rd-mono); font-size: 13px; font-weight: 700;
  letter-spacing: 0.04em; text-anchor: middle;
  fill: var(--rd-cream);
  paint-order: stroke; stroke: rgba(5,42,32,0.85); stroke-width: 3px; stroke-linejoin: round;
}
.af-bag { fill: none; stroke: var(--rd-gold); stroke-width: 4; opacity: 0; }
.af-options {
  /* The options sit UNDER the picture, which is where the app draws them and the
     only place they fit: overlaid on a 390px phone they covered 78% of the
     frame, measured. The picture is the point. */
  margin-top: 8px; display: grid; gap: 6px; justify-items: stretch;
}
/* D1, 2026-09-02. These were 999px pills in cream-on-forest, which is not what
   the product draws. The IQ Game paints an option as a cream card at
   --rd-radius with a 2px border and a gold-on-forest mono letter badge
   (app/styles.css .option-btn / .opt-letter). The rules below are a port of
   that geometry, not an import: the marketing frame now shows the real UI. */
.af-option {
  position: relative;
  display: flex; align-items: flex-start; gap: 8px;
  min-height: 48px;
  padding: 9px 9px 9px 7px;
  max-width: 100%;
  font-family: var(--rd-body); font-size: 15px; font-weight: 400; line-height: 1.22;
  color: var(--rd-near-black); background: var(--rd-cream);
  border: 2px solid transparent;
  border-radius: var(--rd-radius);
  box-shadow: 0 6px 18px rgba(4,24,18,0.35);
  text-align: left;
  overflow: hidden;
  transition: background var(--rd-dur-land) var(--rd-ease-land),
              color var(--rd-dur-land) var(--rd-ease-land),
              border-color var(--rd-dur-land) var(--rd-ease-land);
}
.af-letter {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  flex-shrink: 0;
  background: var(--rd-forest); color: var(--rd-gold);
  border-radius: 50%;
  font-family: var(--rd-mono); font-weight: 700; font-size: 11px;
  transition: background var(--rd-dur-land) var(--rd-ease-land),
              color var(--rd-dur-land) var(--rd-ease-land);
}
.af-opt-text { display: block; }
.af-verdict {
  margin: 14px 0 0; font-size: 1rem; line-height: 1.5;
  color: rgba(245,241,232,0.92); max-width: 52ch;
  opacity: 0; transition: opacity var(--rd-dur-settle) var(--rd-ease-settle);
}
.af-foot {
  margin: 10px 0 0; font-size: 11px; font-weight: 700; letter-spacing: 0.10em;
  color: rgba(245,241,232,0.62);
}
@media (prefers-reduced-motion: no-preference) {
  .pk-iq.in .af-lane  { animation: af-draw 6500ms var(--rd-ease-land) 300ms infinite both; }
  .pk-iq.in .af-option:not(.af-pick) { animation: af-rest 6500ms var(--rd-ease-land) 300ms infinite both; }
  .pk-iq.in .af-pick  { animation: af-pick 6500ms var(--rd-ease-land) 300ms infinite both; }
  .pk-iq.in .af-pick .af-letter { animation: af-pick-badge 6500ms var(--rd-ease-land) 300ms infinite both; }
  .pk-iq.in .af-bag   { animation: af-bag  6500ms var(--rd-ease-land) 300ms infinite both; }
  .pk-iq.in .af-verdict { animation: af-say 6500ms linear 300ms infinite both; }
}
@keyframes af-draw { 0%, 8% { stroke-dashoffset: 1; } 16%, 92% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: 0; } }
@keyframes af-pick {
  0%, 6% { background: var(--rd-cream); color: var(--rd-near-black); border-color: transparent; }
  9% { background: var(--rd-gold); color: var(--rd-forest); border-color: var(--rd-gold); }
  13%, 92% { background: var(--rd-forest); color: var(--rd-cream); border-color: var(--rd-forest); }
  100% { background: var(--rd-forest); color: var(--rd-cream); border-color: var(--rd-forest); }
}
@keyframes af-pick-badge {
  0%, 6% { background: var(--rd-forest); color: var(--rd-gold); }
  9%, 100% { background: var(--rd-gold); color: var(--rd-forest); }
}
/* D14, 2026-09-02. The frame already resolved the way the game resolves: the
   option flashes gold then locks to forest (af-pick is a port of the app's
   optionCorrect, app/styles.css:996), the throw draws, the bag rings, the
   coaching line arrives. The one beat it was missing is what happens to the
   options you did NOT pick. The app steps them back to opacity 0.75
   (app/styles.css:1009, .option-btn.disabled:not(.correct):not(.wrong)), so
   the frame does the same, on the same clock. 0.75 is the app's number, not a
   taste number, and it keeps the copy readable. */
@keyframes af-rest {
  0%, 6% { opacity: 1; }
  13%, 92% { opacity: 0.75; }
  100% { opacity: 0.75; }
}
@keyframes af-bag { 0%, 14% { opacity: 0; } 22%, 92% { opacity: 1; } 100% { opacity: 1; } }
@keyframes af-say { 0%, 20% { opacity: 0; } 28%, 94% { opacity: 1; } 100% { opacity: 1; } }
/* no-JS and reduced motion get the RESOLVED frame, not an empty one. The
   coaching line was invisible without JS until 2026-08-04 — caught by the
   no-JS probe, not by eye. */
html:not(.js) .af-lane, .af-lane.static { stroke-dashoffset: 0; }
html:not(.js) .af-bag, html:not(.js) .af-verdict { opacity: 1; }
html:not(.js) .af-option:not(.af-pick) { opacity: 0.75; }
html:not(.js) .af-pick { background: var(--rd-forest); color: var(--rd-cream); border-color: var(--rd-forest); }
html:not(.js) .af-pick .af-letter { background: var(--rd-gold); color: var(--rd-forest); }
@media (prefers-reduced-motion: reduce) {
  .af-lane { stroke-dashoffset: 0; }
  .af-bag, .af-verdict { opacity: 1; }
  .af-option:not(.af-pick) { opacity: 0.75; }
  .af-pick { background: var(--rd-forest); color: var(--rd-cream); border-color: var(--rd-forest); }
  .af-pick .af-letter { background: var(--rd-gold); color: var(--rd-forest); }
}

/* the promoted CTA links (D5 + D6): 13px and 20px text links became 48px
   bordered secondary buttons, docs/21 §7.7 */
.cta-link {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: 0 20px; margin-top: 4px;
  font-family: var(--rd-body); font-size: 1rem; font-weight: 700;
  color: var(--rd-cream); text-decoration: none;
  border: 1.5px solid rgba(245,241,232,0.55); border-radius: 10px;
  background: transparent; width: fit-content;
  transition: background var(--rd-motion), color var(--rd-motion), border-color var(--rd-motion);
}
.cta-link::after { content: " →"; white-space: pre; }
.cta-link:hover { background: var(--rd-cream); color: var(--rd-forest-3); border-color: var(--rd-cream); }
.postgame .cta-link, .proof .cta-link {
  color: var(--rd-forest); border-color: var(--rd-forest);
}
.postgame .cta-link:hover, .proof .cta-link:hover { background: var(--rd-forest); color: var(--rd-cream); }

/* the swept-plane fan — Swing Lab's money visual (bat-tracer.js
   drawSweepFan register: filled gold quads, thin spokes, hot leading
   edge, bold contact chord) composed over the concept cage plate,
   real-values rail under it. */
.swing-fan { position: relative; margin: 0; }
/* the honesty label stays and gets bigger; it stops being a solid gold sticker
   that outshouted the fan it labels (D2 + docs/21 §7.8, wording untouched) */
/* D11, 2026-09-02. Square-cornered scorebook cell, not a 999px pill. The
   homepage carried four of them and this was the first: an honesty label is a
   label on a form, so it is drawn like one. Same family as .af-option, which
   is the app's own option geometry: token radius, 2px border, mono uppercase.
   Small cells take --rd-radius-sm; anything at control size takes
   --rd-radius, the way .af-option does. */
.fan-tag {
  position: absolute; top: -15px; left: 22px; z-index: 2;
  font-size: 11px; font-weight: 700; letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--rd-gold); background: var(--rd-forest-3);
  border: 2px solid var(--rd-gold);
  border-radius: var(--rd-radius-sm); padding: 5px 11px;
}
.fan-stage {
  position: relative; overflow: hidden;
  border: 1px solid rgba(245,241,232,0.16);
  border-radius: var(--rd-radius-lg);
  background: #041A13;
}
.fan-stage img { display: block; width: 100%; height: auto; }
.fan-sweep { position: absolute; inset: 0; width: 100%; height: 100%; mix-blend-mode: screen; }
/* the fan lands the way the app draws it: the swept surface fans open,
   spokes texture it, the hot edge draws launch→contact, the bat lands
   on the ball, then the rail reports */
.fan-quad, .fan-spokes, .fan-chord, .fan-ring { opacity: 0; }
.pk-swing.in .fq-1 { animation: fan-in 300ms ease 300ms both; }
.pk-swing.in .fq-2 { animation: fan-in 300ms ease 420ms both; }
.pk-swing.in .fq-3 { animation: fan-in 300ms ease 540ms both; }
.pk-swing.in .fq-4 { animation: fan-in 300ms ease 660ms both; }
.pk-swing.in .fq-5 { animation: fan-in 300ms ease 780ms both; }
.pk-swing.in .fan-spokes { animation: fan-in 400ms ease 900ms both; }
@keyframes fan-in { from { opacity: 0; } to { opacity: 1; } }
.fan-edge { stroke-dasharray: 1; stroke-dashoffset: 1; }
.pk-swing.in .fan-edge { animation: arc-draw var(--rd-dur-camera) var(--rd-ease-camera) 1000ms both; }
@keyframes arc-draw { to { stroke-dashoffset: 0; } }
.fan-chord, .fan-ring { transform-origin: center; transform-box: fill-box; }
.pk-swing.in .fan-chord { animation: cp-pop 380ms var(--rd-ease-land) 1850ms both; }
.pk-swing.in .fan-ring  { animation: cp-pop 380ms var(--rd-ease-land) 1950ms both; }
@keyframes cp-pop { 0% { opacity: 0; transform: scale(0.3); } 100% { opacity: 1; transform: scale(1); } }
.fan-rail {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 22px;
  margin-top: 14px;
}
.fan-met {
  display: inline-flex; align-items: center; gap: 8px; margin: 0;
  font-size: 11px; font-weight: 700; letter-spacing: 0.1em;
  color: rgba(245,241,232,0.75); white-space: nowrap;
  opacity: 0;
}
.fan-met > b:not(.band) { font-size: 16px; letter-spacing: 0.04em; color: var(--rd-cream); }
.pk-swing.in .fan-met:nth-child(1) { animation: fade-up 380ms ease 2150ms both; }
.pk-swing.in .fan-met:nth-child(2) { animation: fade-up 380ms ease 2300ms both; }
.band {
  font-style: normal; font-size: 11px; letter-spacing: 0.06em;
  border-radius: 3px; padding: 3px 6px 2px;
}
.band.on { color: var(--rd-cream); border: 1px solid rgba(245,241,232,0.5); }
.band.work { color: var(--rd-forest-3); background: var(--rd-gold); }
.read-result {
  margin: 14px 0 4px; text-align: center;
  font-size: 12px; font-weight: 700; letter-spacing: 0.1em;
  color: var(--rd-gold); opacity: 0;
}
.pk-swing.in .read-result { animation: fade-up 500ms ease 2600ms both; }

/* The .velo-* block was removed 2026-08-04: Velo was retired 2026-07-24 and
   archived to _retired/velo/. Dead CSS for a dead product. */

/* The .corner-run ring panel was merged into the plan card head 2026-08-04
   (D8): the plan card is the better artifact and two boards in one panel was
   the third telling of the same idea. #run-clock still ticks, in the head. */
.plan-live { display: inline-flex; align-items: center; gap: 6px; color: var(--rd-gold); white-space: nowrap; flex: none; }
.plan-live .gi { width: 0.7em; height: 0.7em; }
.plan-live b { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }

/* --- THE LINEUP card (kept nearly verbatim; the summary object) --- */
/* D5, 2026-09-02: the card was centred under a flush-left H2, which put two
   alignment systems in one block. The card rails to the same left edge as its
   own heading and as every other head on the page. */
.roster-wrap { display: grid; gap: 18px; justify-items: start; margin-top: clamp(40px, 6vh, 80px); }
.roster-h {
  font-family: var(--rd-display); text-transform: uppercase;
  font-size: clamp(2.6rem, 6vw, 5rem); line-height: 0.94; letter-spacing: 0.01em;
  color: var(--rd-cream); margin: 0; justify-self: start;
}
.roster-card {
  /* widened with the left-align: an 820px card railed left left a 400px band
     of empty forest to its right, which is the same leftover the package grid
     just lost. The rows are dot-leadered, so the extra width lengthens the
     leaders, which is what a lineup card is supposed to look like. */
  width: min(980px, 100%);
  margin-top: 14px;
  background: var(--rd-cream);
  border-radius: var(--rd-radius);
  box-shadow: var(--rd-shadow-lg);
  overflow: hidden;
  color: var(--rd-near-black);
}
.roster-top {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--rd-forest); color: var(--rd-cream);
  font-size: 12px; font-weight: 700; letter-spacing: 0.12em;
  padding: 13px 20px;
}
.roster-rows { list-style: none; margin: 0; padding: 0; }
.ro-row {
  display: grid; grid-template-columns: 64px 1fr auto;
  gap: 18px; align-items: center;
  padding: 18px 20px;
  border-top: 1px dashed rgba(15, 19, 17, 0.28);
  text-decoration: none; color: inherit;
  transition: background var(--rd-motion), transform var(--rd-motion);
}
.roster-rows li:first-child .ro-row { border-top: 0; }
.ro-row:hover {
  background: color-mix(in srgb, var(--rd-gold) 12%, var(--rd-cream));
  transform: translateX(4px);
}
.ro-row:hover .ro-name { text-decoration: underline; text-decoration-color: var(--rd-gold); text-decoration-thickness: 3px; text-underline-offset: 5px; }
.ro-num {
  font-family: var(--rd-display); font-size: 2.5rem; line-height: 0.85;
  color: var(--rd-forest); text-align: center;
  border-bottom: 3px solid var(--rd-gold); padding-bottom: 4px;
}
.ro-main { display: grid; gap: 4px; min-width: 0; }
.ro-line { display: flex; align-items: baseline; gap: 12px; min-width: 0; }
.ro-name {
  font-family: var(--rd-display); text-transform: uppercase;
  font-size: 1.7rem; line-height: 1; letter-spacing: 0.02em;
  color: var(--rd-forest); white-space: nowrap;
}
.ro-dots { flex: 1; min-width: 24px; border-bottom: 2px dotted rgba(15, 19, 17, 0.35); transform: translateY(-4px); }
.ro-pos { font-size: 11px; font-weight: 700; letter-spacing: 0.12em; color: var(--rd-cool-gray); white-space: nowrap; }
.ro-hook { font-family: var(--rd-body); font-size: 0.94rem; line-height: 1.45; color: var(--rd-fg); max-width: 52ch; }
/* D11: the lineup card's status cell. A scorebook has boxes, not lozenges. */
.ro-status {
  font-size: 11px; font-weight: 700; letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--rd-forest); border: 2px solid var(--rd-forest);
  border-radius: var(--rd-radius-sm); padding: 4px 9px 3px;
  white-space: nowrap;
}
/* the card's foot used to point deeper into the page ("tap any line to see it
   play"). It points into the product now (D5). */
.roster-foot {
  margin: 0; padding: 14px 20px 18px;
  border-top: 1px solid var(--rd-border);
  display: grid; gap: 12px; justify-items: center;
}
.roster-foot-line { margin: 0; font-size: 12px; letter-spacing: 0.12em; color: var(--rd-fg-muted); text-align: center; }
.roster-foot .btn { font-size: 1.25rem; }

/* =========================================================
   THE PROOF BAND (elevation directive D7)
   Between the package and the price, because the page used to show a coach
   an invoice in act three and the evidence in act four. Every line here is a
   claim about the product that we can already stand behind. There is no
   adoption proof on this page and none is invented.
   ========================================================= */
/* D12, 2026-09-02. A 2x2 grid of numbered claims is the single most common
   AI-built "why us" block there is, and at 507px this was the shortest section
   on the page, so it read as filler between the products and the price. It is
   one left-railed column now: four statements set large in Inter with real
   leading, the numbers holding the rail. Four short paragraphs at 26px out-read
   four cards at 16px, and the asymmetry breaks a grid rhythm the page had been
   running for 2,500px. The horizontal rules are gone with the grid; the numbers
   and their gold underscores are the only rule left, which is why they sit in a
   rail rather than above the copy.
   The ordinals stay in Bebas on purpose: .ro-num in the lineup card sets
   ordinals in Bebas too, and splitting them across two faces would be the same
   two-systems problem this pass has been closing everywhere else. */
.proof { background: var(--rd-cream-2); padding: clamp(48px, 8vh, 96px) 0; }
.proof-inner { display: grid; gap: 30px; }
.proof-rows {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(30px, 4vh, 46px);
}
.proof-row {
  display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: clamp(16px, 2vw, 30px);
  align-items: start;
}
.proof-num {
  font-family: var(--rd-display); font-size: clamp(2.2rem, 3vw, 3rem); line-height: 0.86;
  font-variant-numeric: tabular-nums;
  color: var(--rd-forest); border-bottom: 3px solid var(--rd-gold);
  padding-bottom: 4px; text-align: left;
}
.proof-copy { display: grid; gap: 10px; max-width: 60ch; }
.proof-h {
  font-family: var(--rd-body); font-weight: 600; letter-spacing: -0.005em;
  font-size: clamp(1.5rem, 2vw, 1.75rem); line-height: 1.32; color: var(--rd-forest);
}
.proof-line { font-size: 1.02rem; line-height: 1.62; color: var(--rd-fg); max-width: 58ch; font-variant-numeric: tabular-nums; }

/* =========================================================
   BEAT 5 — POSTGAME (pricing + tier fold + founders + FAQ)
   ========================================================= */
.postgame { background: var(--rd-cream); padding: clamp(52px, 8vh, 110px) 0 clamp(48px, 7vh, 92px); }
.postgame-wrap { display: grid; gap: clamp(28px, 4.5vh, 56px); }
.pg-pricing { display: grid; gap: 0; }
.pricing-h { font-size: clamp(2.6rem, 5.5vw, 4.5rem); line-height: 0.98; margin: 0 0 28px; }
.price-row {
  display: grid; grid-template-columns: repeat(3, 1fr);
  border-top: 2px solid var(--rd-forest);
}
.price-cell {
  display: grid; align-content: start; gap: 6px;
  padding: 20px 22px 20px 0;
}
.price-cell + .price-cell { border-left: 1px solid var(--rd-border); padding-left: 26px; }
.price-amt { font-family: var(--rd-mono); font-variant-numeric: tabular-nums; font-size: clamp(2.3rem, 4.1vw, 3.6rem); font-weight: 700; color: var(--rd-forest); line-height: 1; }
/* D7, 2026-09-02. The team price was `US$199 <small>/ C$249</small>`, which
   wrapped after the slash, doubled the word-space in the mono face, and left
   the gold highlighter running under C$249 alone and overshooting the glyphs
   by the width of the cell. The pair now sets one size down so it holds one
   line at every breakpoint, the second currency is a demoted run rather than
   <small>, and the highlighter is on the inline text run, so it starts and
   stops at the glyphs. The whole rail dropped from clamp(2.6,4.6vw,4) to
   clamp(2.3,4.1vw,3.6) so all three figures hold one scale: the longest one
   sets the size, and the featured cell is marked by the gold rule, not by
   being smaller than the two free tiers. */
.price-run { display: inline; white-space: nowrap; }
.price-alt { font-size: 0.45em; letter-spacing: 0.01em; margin-left: 0.3em; color: var(--rd-forest-2); }
.price-star .price-amt { color: var(--rd-forest); width: fit-content; }
.price-star .price-run { box-shadow: inset 0 -0.18em 0 color-mix(in srgb, var(--rd-gold) 55%, transparent); }
.price-name { font-family: var(--rd-display); text-transform: uppercase; letter-spacing: 0.04em; font-size: 1.35rem; color: var(--rd-forest-2); }
.price-note { font-size: 0.92rem; line-height: 1.5; color: var(--rd-fg); max-width: 30ch; font-variant-numeric: tabular-nums; }
.price-line { font-size: 1.15rem; font-weight: 600; color: var(--rd-forest); margin: 26px 0 14px; font-variant-numeric: tabular-nums; }
.price-foot { color: var(--rd-fg-muted); margin: 14px 0 0; font-variant-numeric: tabular-nums; }
/* This was authored as a bare text link, where padding-block 12 plus
   margin-block -12 is the standard way to buy a 48px tap target without
   moving anything. It later gained .cta-link, which makes it a real bordered
   button with its own min-height 48px, so the tap hack became redundant AND
   the negative margin pulled the drawn border 12px into the fine print
   underneath. The overlap was visible on the live site. .cta-link already
   carries the tap target, the display mode and the box; only the colour is
   local. Do not reinstate the negative margin on an element that draws a
   border. */
.price-detail-link { color: var(--rd-forest); font-weight: 700; }
/* the coach seat is the pricing model, not a footnote */
.price-coach {
  display: grid; grid-template-columns: auto 1fr; gap: 6px 22px; align-items: baseline;
  margin-top: 26px; padding: 18px 22px;
  background: var(--rd-forest); color: var(--rd-cream);
  border-radius: var(--rd-radius-sm);
  border-left: 6px solid var(--rd-gold);
}
.pc-amt { margin: 0; font-size: 14px; font-weight: 700; letter-spacing: 0.10em; color: var(--rd-gold); white-space: nowrap; font-variant-numeric: tabular-nums; }
.price-coach .pc-line { margin: 0; font-size: 1rem; line-height: 1.5; color: rgba(245,241,232,0.92); max-width: 62ch; }

/* the tier fold */
.lineup-fold {
  border-top: 2px solid var(--rd-forest);
  border-bottom: 1px solid var(--rd-border);
}
.lineup-fold > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 20px; flex-wrap: wrap;
  padding: 22px 2px;
}
.lineup-fold > summary::-webkit-details-marker { display: none; }
.lineup-fold .lineup-h { margin: 0; flex: 1; }
.lf-mark {
  font-family: var(--rd-display); font-size: 1.9rem; line-height: 0.8;
  color: var(--rd-gold-2);
}
.lineup-fold[open] .lf-mark, .faq-fold[open] .lf-mark { color: var(--rd-forest); font-size: 0; }
.lineup-fold[open] .lf-mark::before, .faq-fold[open] .lf-mark::before { content: "–"; font-size: 1.9rem; }
.lineup-fold > .lineup-grid { margin-top: 14px; }
.lineup-h { font-family: var(--rd-display); text-transform: uppercase; color: var(--rd-forest); font-size: clamp(1.7rem, 3.4vw, 2.5rem); line-height: 0.98; letter-spacing: 0.02em; }
.lineup-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 18px; align-items: stretch;
}
.lineup-tier {
  position: relative;
  background: #FFFFFF;
  border: 1px solid var(--rd-border);
  border-top: 3px solid var(--rd-forest);
  border-radius: var(--rd-radius-sm);
  padding: 22px 20px 18px;
  display: flex; flex-direction: column; gap: 10px;
  box-shadow: var(--rd-shadow-sm);
}
.tier-featured { border-top-color: var(--rd-gold); box-shadow: var(--rd-shadow); }
.tier-name {
  font-family: var(--rd-display); text-transform: uppercase;
  font-size: 1.6rem; letter-spacing: 0.03em; line-height: 1;
  color: var(--rd-forest);
}
.tier-price { display: flex; align-items: baseline; gap: 6px; font-family: var(--rd-mono); font-variant-numeric: tabular-nums; }
.tier-amt { font-size: 2rem; font-weight: 700; color: var(--rd-forest); line-height: 1; }
.tier-per { font-size: 0.82rem; font-weight: 700; color: var(--rd-cool-gray); }
.tier-rows { list-style: none; margin: 4px 0 0; padding: 0; display: grid; }
.tier-rows li {
  font-size: 0.9rem; line-height: 1.45; color: var(--rd-fg);
  padding: 7px 0; border-top: 1px solid var(--rd-border);
}
.tier-note { margin: auto 0 0; padding-top: 10px; font-size: 0.85rem; color: var(--rd-fg-muted); }
.lineup .price-line { margin-top: 30px; }

/* founders — the locker-room speech, kept as the dark moment of postgame */
.founders {
  background:
    radial-gradient(90% 70% at 50% 110%, #0B4D3C 0%, #083A2D 55%, #052A20 100%);
  color: var(--rd-cream);
  border-radius: var(--rd-radius-lg);
  padding: clamp(48px, 8vh, 90px) clamp(24px, 5vw, 64px);
  box-shadow: var(--rd-shadow-lg);
}
.founders .slate-kicker { color: var(--rd-gold); margin-bottom: 4px; }
.founders-inner { display: grid; gap: 22px; justify-items: start; }
.founders-aside { display: grid; gap: 18px; justify-items: start; align-content: start; }
.speech { display: grid; gap: 4px; }
/* D9, 2026-09-02. All three lines were set at clamp(2.4rem, 6.6vw, 5.8rem),
   so a twelve-word renewal condition was the largest type on the site,
   louder than any product statement. The speech now has a real hierarchy:
   the offer at full display scale, the price at half, and the condition at
   body weight in the gold. */
.speech-line {
  font-family: var(--rd-display); text-transform: uppercase;
  font-size: clamp(2.4rem, 6.6vw, 5.8rem); line-height: 0.96; letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--rd-cream); margin: 0;
}
.speech-line.sp-2 { font-size: clamp(1.2rem, 3.3vw, 2.9rem); line-height: 1.02; }
.speech-line.sp-3 {
  font-family: var(--rd-body); text-transform: none;
  font-size: 1.02rem; font-weight: 600; line-height: 1.55; letter-spacing: 0;
  max-width: 46ch; margin-top: 14px;
}
.speech-line.gold { color: var(--rd-gold); }
/* elevation directive D4b: the speech lands line by line, like a coach
   delivering it — same lineup-card rise as the arrival, gold line last.
   Reduced motion zeroes the durations (static); no-JS is forced visible. */
.founders .speech-line {
  opacity: 0; transform: translateY(0.35em);
  clip-path: inset(100% 0 -8% 0);
  transition: opacity 140ms linear,
              transform var(--rd-dur-settle) var(--rd-ease-settle),
              clip-path var(--rd-dur-settle) var(--rd-ease-settle);
}
.founders.in .speech-line {
  opacity: 1; transform: translateY(0);
  clip-path: inset(-12% -3% -12% -3%);
}
.founders.in .speech-line:nth-child(1) { transition-delay: 60ms; }
.founders.in .speech-line:nth-child(2) { transition-delay: 320ms; }
.founders.in .speech-line:nth-child(3) { transition-delay: 580ms; }
.founders.in .speech-line:nth-child(4) { transition-delay: 900ms; }
/* the body copy ran seven lines on a phone under the largest type on the page.
   The paragraph came down, not the type. */
.founders-body { font-size: 1.02rem; line-height: 1.6; color: rgba(245,241,232,0.9); max-width: 54ch; margin: 0; }
.founders-body b { color: var(--rd-cream); }
.fo-was { color: rgba(245,241,232,0.55); text-decoration-thickness: 2px; margin-right: 2px; }
.fo-now {
  color: var(--rd-forest-3); background: var(--rd-gold);
  border-radius: 4px; padding: 1px 7px; font-weight: 700;
}
/* the 7/5 split that closes the empty right half of the panel. The kicker
   holds row 1 on its own; row 2 runs the speech against the aside. */
@media (min-width: 900px) {
  .founders-inner {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: clamp(32px, 4vw, 64px);
    align-items: start;
  }
  .founders-inner > .slate-kicker { grid-column: 1; grid-row: 1; }
  .founders-inner > .speech { grid-column: 1; grid-row: 2; }
  .founders-aside { grid-column: 2; grid-row: 2; }
}
.founders .founders-body, .founders .btn { transition: opacity var(--rd-dur-land) linear, transform var(--rd-dur-settle) var(--rd-ease-settle); }
.founders:not(.in) .founders-body, .founders:not(.in) .btn { opacity: 0; transform: translateY(20px); }
.founders.in .founders-body { transition-delay: 1150ms; }
.founders.in .btn { transition-delay: 1300ms; }

/* the FAQ, folded to one strip */
.faq-fold {
  border-top: 2px solid var(--rd-forest);
  border-bottom: 1px solid var(--rd-border);
}
.faq-fold > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 20px;
  padding: 22px 2px;
}
.faq-fold > summary::-webkit-details-marker { display: none; }
.faq-h {
  flex: 1; margin: 0;
  font-family: var(--rd-display); text-transform: uppercase;
  color: var(--rd-forest);
  font-size: clamp(1.7rem, 3.4vw, 2.5rem); line-height: 0.98; letter-spacing: 0.02em;
}
.faq { display: grid; max-width: 860px; padding: 6px 0 22px; }
.faq details { border-top: 1px solid rgba(15,19,17,0.14); }
.faq details:last-child { border-bottom: 1px solid rgba(15,19,17,0.14); }
.faq summary {
  cursor: pointer; list-style: none;
  display: flex; justify-content: space-between; align-items: baseline; gap: 18px;
  padding: 14px 2px;
  font-family: var(--rd-body); font-weight: 700; font-size: 0.98rem;
  color: var(--rd-forest);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; font-family: var(--rd-display); font-size: 1.5rem; line-height: 0.8;
  color: var(--rd-gold-2); flex: none;
}
.faq details[open] summary::after { content: "–"; color: var(--rd-forest); }
.faq details > p {
  margin: 0 0 18px; padding: 0 2px;
  font-size: 0.98rem; line-height: 1.6; color: var(--rd-fg); max-width: 74ch;
}
.faq a { color: var(--rd-forest); font-weight: 600; }

/* =========================================================
   ABSORBED FROM THE DEEP-DIVE STRIP (deleted 2026-08-04, D8)
   Only the objects the package panels took with them survive:
   the plan card, the fence photograph, and its honesty credit.
   ========================================================= */
.dive-photo img { display: block; width: 100%; height: auto; border-radius: var(--rd-radius); box-shadow: var(--rd-shadow); }
.dive-credit { margin: 8px 0 0; font-size: 11px; font-weight: 700; letter-spacing: 0.10em; color: rgba(245,241,232,0.7); }
.corner-photo { margin: 14px 0 0; }
.good-mark {
  font-family: var(--rd-display); text-transform: uppercase; letter-spacing: 0.04em;
  font-size: 1.35rem; margin-right: 6px;
  color: var(--rd-gold); border-bottom: 3px solid var(--rd-gold);
}

/* the Corner plan card (cream lineup-card register) */
.corner-plan {
  background: var(--rd-cream);
  border: 1px solid var(--rd-border);
  border-radius: var(--rd-radius);
  box-shadow: var(--rd-shadow);
  overflow: hidden;
  color: var(--rd-near-black);
  display: flex; flex-direction: column;
  margin-top: 14px;
}
.plan-head {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  min-height: 44px;
  background: var(--rd-forest); color: var(--rd-cream);
  font-size: 12px; font-weight: 700; letter-spacing: 0.12em;
  padding: 12px 18px;
}
/* D14, 2026-09-02. The plan card is a schedule, so it arrives like one being
   built: the constraints the coach set land first, then the blocks fill in
   TIME ORDER, each one wiping in from its start time, then the card's own
   claim about how fast that was. Before this the rows shared four delays and
   rows four through eight arrived together, which said nothing.
   Everything animated here is opacity, transform and clip-path, the same
   three properties the rest of the page moves, and the whole sequence is
   1.16s. prefers-reduced-motion zeroes it globally at the foot of this file,
   and the no-JS block below forces the finished state. */
.plan-constraints { display: flex; flex-wrap: wrap; gap: 8px; padding: 14px 18px 4px; }
.pc-chip { opacity: 0; transform: translateY(6px); transition: opacity 160ms linear, transform var(--rd-dur-land) var(--rd-ease-land); }
.pk-corner.in .pc-chip { opacity: 1; transform: none; }
.pk-corner.in .pc-chip:nth-child(1) { transition-delay: 80ms; }
.pk-corner.in .pc-chip:nth-child(2) { transition-delay: 140ms; }
.pk-corner.in .pc-chip:nth-child(3) { transition-delay: 200ms; }
.pk-corner.in .pc-chip:nth-child(n+4) { transition-delay: 260ms; }
/* D11: the practice plan's constraints. These are the coach's inputs written
   back at him, which is a form field's job, so they are drawn as cells. */
.pc-chip {
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--rd-forest); border: 2px solid var(--rd-forest);
  border-radius: var(--rd-radius-sm); padding: 4px 9px;
}
.plan-rows { list-style: none; margin: 10px 0 0; padding: 0 0 6px; flex: 1; }
.plan-row {
  display: grid; grid-template-columns: 52px 1fr auto;
  gap: 12px; align-items: baseline;
  padding: 9px 18px;
  border-top: 1px solid var(--rd-border);
  opacity: 0; transform: translateX(-14px);
  clip-path: inset(0 100% 0 0);
  transition: opacity 170ms linear,
              transform var(--rd-dur-settle) var(--rd-ease-settle),
              clip-path var(--rd-dur-settle) var(--rd-ease-settle);
}
.pk-corner.in .plan-row { opacity: 1; transform: translateX(0); clip-path: inset(0 0 0 0); }
.pk-corner.in .plan-row:nth-child(1) { transition-delay: 320ms; }
.pk-corner.in .plan-row:nth-child(2) { transition-delay: 415ms; }
.pk-corner.in .plan-row:nth-child(3) { transition-delay: 510ms; }
.pk-corner.in .plan-row:nth-child(4) { transition-delay: 605ms; }
.pk-corner.in .plan-row:nth-child(5) { transition-delay: 700ms; }
.pk-corner.in .plan-row:nth-child(6) { transition-delay: 795ms; }
.pk-corner.in .plan-row:nth-child(7) { transition-delay: 890ms; }
.pk-corner.in .plan-row:nth-child(8) { transition-delay: 985ms; }
.pk-corner.in .plan-row:nth-child(n+9) { transition-delay: 1010ms; }
/* the plan table is a timetable: mono numerals are the correct register here,
   and they come from the object, not from a class on every cell (D2). */
.pr-t, .pr-len { font-family: var(--rd-mono); font-variant-numeric: tabular-nums; }
.pr-t { font-size: 12px; font-weight: 700; color: var(--rd-fg-muted); }
.pr-name { font-family: var(--rd-display); text-transform: uppercase; letter-spacing: 0.03em; font-size: 1.2rem; line-height: 1; color: var(--rd-forest); }
.pr-len { font-size: 12px; font-weight: 700; color: var(--rd-forest-2); }
.pr-group { background: var(--rd-cream-2); }
.pr-sub { padding-left: 40px; }
.pr-sub .pr-name { font-size: 1.02rem; color: var(--rd-forest-2); }
.pr-retest { border-left: 4px solid var(--rd-gold); background: color-mix(in srgb, var(--rd-gold) 12%, var(--rd-cream)); }
.pr-retest .pr-name { color: var(--rd-forest); }
.plan-foot {
  font-size: 12px; font-weight: 700; letter-spacing: 0.12em;
  color: var(--rd-fg-muted); padding: 10px 18px 14px; margin: 0;
  border-top: 1px solid var(--rd-border);
  /* the claim lands after the thing it is claiming about */
  opacity: 0; transition: opacity var(--rd-dur-settle) linear;
}
.pk-corner.in .plan-foot { opacity: 1; transition-delay: 1120ms; }

/* =========================================================
   BEAT 7 — THE CLOSE (D9) + FOOTER
   The page's last full act was cream on cream and ended quietly, with a
   centred form and a headline that led with the count. It closes dark now,
   on the field art, and the form IS the lineup card — the strongest object
   on the page is also the closing object, which shuts the loop opened at
   beat 4. Input 56px / 16px (never smaller: iOS zooms the viewport under
   16px), button 56px, chips 44px.
   ========================================================= */
.waitlist {
  position: relative; overflow: hidden;
  background: radial-gradient(90% 70% at 50% 8%, #0B4D3C 0%, #083A2D 55%, #052A20 100%);
  color: var(--rd-cream);
  padding: clamp(56px, 10vh, 120px) 0;
}
.wl-art {
  position: absolute; inset: -10% -20%; opacity: 0.10; pointer-events: none;
  transform: scale(1.6) rotate(-4deg); transform-origin: 50% 30%;
}
.wl-art svg { width: 100%; height: 100%; display: block; }
/* D13, 2026-09-02. The close was the canonical AI newsletter block: centred
   head, centred sub, centred card, and the only fully centre-aligned
   composition on a page that is left-railed everywhere else, so it arrived as
   a different site. The chrome is unchanged, it is still the lineup card,
   and the centring is gone. Above 900px the head runs flush left against the
   form on the right in the same 7/5 the founders block uses. */
.waitlist-inner { position: relative; z-index: 1; display: grid; gap: 16px; justify-items: start; text-align: left; }
.wl-h {
  font-family: var(--rd-display); text-transform: uppercase;
  font-size: clamp(2.6rem, 6vw, 4.8rem); line-height: 0.94; letter-spacing: 0.01em;
  color: var(--rd-cream); margin: 0;
}
.wl-sub { font-size: 1.1rem; color: rgba(245,241,232,0.88); margin: 0 0 8px; }
.waitlist-form {
  width: min(560px, 100%); display: block; text-align: left; justify-self: stretch;
  background: var(--rd-cream); color: var(--rd-near-black);
  border-radius: var(--rd-radius); box-shadow: var(--rd-shadow-lg); overflow: hidden;
}
.wl-body { display: grid; gap: 14px; padding: 18px 20px 4px; }
.wl-label { display: block; color: var(--rd-cool-gray); margin-bottom: 6px; padding-block: 15px; margin-top: -15px; }
.wl-row { display: block; width: 100%; }
.wl-row input {
  width: 100%; min-width: 0; height: 56px;
  font-family: var(--rd-body); font-size: 16px;
  color: var(--rd-near-black); background: #FFFFFF;
  border: 2px solid var(--rd-forest); border-radius: 10px;
  padding: 0 16px;
}
.wl-row input:focus { outline: 3px solid color-mix(in srgb, var(--rd-gold) 60%, transparent); outline-offset: 1px; }
.wl-submit { width: 100%; min-height: 56px; text-align: center; }
.wl-foot {
  margin: 0; padding: 14px 20px 16px;
  border-top: 1px solid var(--rd-border);
  color: var(--rd-fg-muted); text-align: center;
  /* the 7/5 split narrowed the card, and this line is 4px too long for it,
     so it broke after "IN ONE". Balance splits it on a separator instead. */
  text-wrap: balance;
}
.wl-meta { display: flex; gap: 10px; flex-wrap: wrap; }
.chip-radio { position: relative; cursor: pointer; display: inline-flex; align-items: center; min-height: 44px; }
.chip-radio input { position: absolute; opacity: 0; pointer-events: none; width: 0; height: 0; }
/* D11: the role selector. It is a real control at 44px, so it takes the
   control radius --rd-radius, the same as .af-option, and the same 2px border.
   Mono uppercase because it now reads as a box a coach ticks. */
.chip-radio span {
  display: inline-flex; align-items: center; min-height: 44px;
  font-family: var(--rd-mono); font-size: 12px; font-weight: 700;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--rd-forest);
  border: 2px solid var(--rd-forest); border-radius: var(--rd-radius);
  padding: 0 16px;
  transition: background var(--rd-motion), color var(--rd-motion);
}
.chip-radio input:checked + span { background: var(--rd-forest); color: var(--rd-cream); }
.chip-radio input:focus-visible + span { outline: 3px solid var(--rd-gold); outline-offset: 2px; }
.wl-status { min-height: 1.4em; font-size: 0.95rem; font-weight: 600; color: var(--rd-forest); margin: 0 0 6px; }
.wl-preview { position: relative; z-index: 1; color: rgba(245,241,232,0.75); margin: 6px 0 0; }
.wl-preview a { color: var(--rd-gold); text-decoration-color: var(--rd-gold); }

@media (min-width: 901px) {
  .waitlist-inner {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    /* rows 1 and 2 hug their copy and row 3 absorbs whatever the form is
       taller by, so the head, the sub and the preview line stack tight
       instead of being spread down a column by the card beside them */
    grid-template-rows: auto auto 1fr;
    column-gap: clamp(32px, 4vw, 72px);
    align-items: start;
  }
  .waitlist-inner > .wl-h { grid-column: 1; grid-row: 1; }
  .waitlist-inner > .wl-sub { grid-column: 1; grid-row: 2; max-width: 44ch; }
  .waitlist-inner > .wl-preview { grid-column: 1; grid-row: 3; max-width: 44ch; align-self: start; }
  .waitlist-inner > .waitlist-form { grid-column: 2; grid-row: 1 / span 3; width: 100%; }
}

.footer { background: #052A20; color: var(--rd-cream); padding: 34px 0; }
.footer-inner { display: grid; gap: 14px; justify-items: center; text-align: center; }
.foot-brand { display: flex; align-items: center; gap: 12px; }
.foot-tag { font-family: var(--rd-display); text-transform: uppercase; letter-spacing: 0.05em; font-size: 1.15rem; color: var(--rd-cream); }
.foot-links { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; }
.foot-links a { color: rgba(245,241,232,0.85); text-decoration: none; font-size: 0.9rem; font-weight: 600; }
.foot-links a:hover { color: var(--rd-gold); }
.foot-socials { display: flex; gap: 18px; }
.foot-socials a { color: rgba(245,241,232,0.7); display: inline-flex; }
.foot-socials a:hover { color: var(--rd-gold); }
.foot-socials svg { width: 18px; height: 18px; }
.foot-legal { color: rgba(245,241,232,0.55); margin: 0; }

/* =========================================================
   REVEAL CHOREOGRAPHY — one grammar, scroll-position-driven
   ========================================================= */
/* ONE reveal (D3). The old pair ran opacity 600ms against transform 650ms, so
   every reveal finished twice and read soft. Opacity is linear and short, the
   move is the settle curve, and a group staggers 90ms per child to a cap of 4. */
.pk-head, .pk-panel, .roster-wrap, .proof-inner, .pg-pricing, .waitlist-inner > * {
  transition: opacity var(--rd-dur-land) linear,
              transform var(--rd-dur-settle) var(--rd-ease-settle);
}
.pk-head:not(.in), .pk-panel:not(.in), .roster-wrap:not(.in),
.proof-inner:not(.in), .pg-pricing:not(.in) { opacity: 0; transform: translateY(24px); }
.pk-head.in, .pk-panel.in, .roster-wrap.in, .proof-inner.in, .pg-pricing.in { opacity: 1; transform: translateY(0); }
.waitlist:not(.in) .waitlist-inner > * { opacity: 0; transform: translateY(24px); }
.waitlist.in .waitlist-inner > * { opacity: 1; transform: translateY(0); }
.pk-panel.pk-swing.in { transition-delay: 90ms; }
.pk-panel.pk-corner.in { transition-delay: 180ms; }
.waitlist.in .waitlist-inner > *:nth-child(2) { transition-delay: 90ms; }
.waitlist.in .waitlist-inner > *:nth-child(3) { transition-delay: 180ms; }
.waitlist.in .waitlist-inner > *:nth-child(n+4) { transition-delay: 270ms; }

/* =========================================================
   THE STICKY ACTION BAR (elevation directive D5)
   Measured before: three buttons on the whole page, and 5,109px — six phone
   screens — between the hero and the next one. Interest peaks at the play and
   there was nowhere to put it. Phone only; program.js gates it on scroll and
   stands it down when the waitlist arrives.
   ========================================================= */
.stickybar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  /* width is pinned, not inferred: a fixed bar whose contents are wider than
     the glass stretched the layout viewport to 413px at 390 and put a
     horizontal scroll on the whole page. Found by probe, not by eye. */
  width: 100%; max-width: 100%; overflow: hidden;
  display: none; align-items: center; justify-content: space-between; gap: 10px;
  min-height: 60px;
  padding: 0 14px calc(env(safe-area-inset-bottom, 0px));
  background: var(--rd-forest-3);
  border-top: 2px solid var(--rd-gold);
  transform: translateY(100%);
  transition: transform var(--rd-dur-land) var(--rd-ease-land);
}
.stickybar.on { transform: translateY(0); }
.sb-cta {
  font-size: 1rem; padding: 11px 13px 9px; min-height: 44px;
  display: inline-flex; align-items: center; white-space: nowrap;
  flex: 0 1 auto; min-width: 0;
}
.sb-alt {
  color: var(--rd-cream); font-weight: 700; font-size: 0.85rem;
  white-space: nowrap; flex: none;
  text-decoration: underline; text-decoration-thickness: 2px;
  text-decoration-color: var(--rd-gold); text-underline-offset: 4px;
  display: inline-flex; align-items: center; min-height: 44px;
}
@media (max-width: 820px) { .stickybar { display: flex; } }
@media (prefers-reduced-motion: reduce) { .stickybar { transition: none; } }
/* D8, 2026-09-02. The bar owns the bottom 60px plus the home-indicator inset
   for the whole scroll below 820px, so every block that ends at the fold gave
   up its last line to it. Each one now carries that height as bottom
   clearance. It is a margin, not padding, because three of these last children
   are buttons and padding would have grown the tap target to 108px instead of
   moving it clear. */
:root { --sb-h: calc(60px + env(safe-area-inset-bottom, 0px)); }
@media (max-width: 820px) {
  .pk-panel > :last-child:not([data-primary-cta]),
  .roster-foot > :last-child:not([data-primary-cta]),
  .proof-inner > :last-child:not([data-primary-cta]),
  .pg-pricing > :last-child:not([data-primary-cta]),
  .founders-aside > :last-child:not([data-primary-cta]),
  .lineup > :last-child:not([data-primary-cta]) { margin-bottom: var(--sb-h); }
}

/* =========================================================
   THE TAP FLOOR (elevation directive D6)
   Every interactive element clears 44px on touch. Inline links inside body
   copy keep their visual size and grow their hit box with padding cancelled
   by an equal negative margin, so nothing on the page moves.
   ========================================================= */
.faq summary, .lineup-fold > summary, .faq-fold > summary { min-height: 44px; align-items: center; }
.faq details > p a,
.price-line a, .wl-preview a, .pk-status a, .tier-note a {
  display: inline-block; padding-block: 12px; margin-block: -12px;
}
.foot-links a {
  /* min-width too: D6 set the height floor and left "FAQ" and "Terms"
     at 28.2px wide, two short words sitting 20px apart in a row of six
     (phone-layout-gate L-TAP, 2026-08-05) */
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px;
}
.foot-socials a { min-width: 44px; min-height: 44px; align-items: center; justify-content: center; }
.ro-row { min-height: 44px; }
.btn { min-height: 44px; }

/* =========================================================
   MOBILE
   ========================================================= */
.wide-only { display: inline; }
@media (max-width: 900px) {
  .wide-only { display: none; }

  /* Keep the eyebrow on one line. Once the App Store badge joined the CTA
     stack, Safari's shorter visual viewport could let a wrapped "PLAYERS"
     line paint into the preview button below it. */
  .lights-inner { padding-inline: 3vw; }
  .lights-lockup img { width: clamp(240px, 72vw, 420px); }
  .lights-line { font-size: 11px; letter-spacing: 0.07em; white-space: nowrap; }
  .lights-ctas { bottom: 64px; flex-direction: column; gap: 12px; width: 84vw; white-space: normal; }
  .lights-ctas .btn { width: 100%; text-align: center; }
  .lights-ctas .app-store-link { flex: none; }
  /* The stacked actions already establish the phone's scroll path. The old
     corner cue became an orphan beside the App Store badge; keep it for the
     wider composition and remove it from portrait phones. */
  .lights-cue { display: none; }
  /* the inset has to be repeated here: this block is (max-width: 900px)
     and a landscape phone is 844 wide, so it was overriding the
     safe-area rule up at .lights-cue and putting THE PLAY back under
     the display cutout (L-SAFE, 844x390) */
  .lights-cue { right: calc(16px + env(safe-area-inset-right, 0px)); bottom: 14px; }

  /* the picture rides higher on a phone: the dusk band above the wall is the
     frame's headroom, not its subject, and a third of the frame was headroom.
     42% balances it against the near-turf apron the lower third and the stamp
     stand on. */
  .fit { top: 42%; --stage-nudge: 16px; }
  .bug { gap: 7px; padding: 0 9px; font-size: 11px; }
  .pbp { font-size: 12px; padding: 8px 12px 8px 10px; }
  .stamp { padding: 10px 18px 9px; }
  .stamp-big { font-size: 2rem; }
  .stamp-sub { font-size: 11px; }
  .tagline { font-size: clamp(2.9rem, 13vw, 4.6rem); }
  /* D4 took the phone field from 54.7% to 70.3% of the glass, which closed the
     turf the tagline used to stand on: measured at 360x740 the band between
     the catcher and the lower third is 62px against an 86px block. There is no
     empty band left on a phone, so the title card lands ON the picture, in the
     one place that costs nothing: the turf between the mound and the plate.
     Above it the runner, the cutoff, the right fielder, the ball and the ring
     all stay clear; below it home plate and the catcher stay clear. 0.36 of
     the beat height puts its baseline just above the catcher at 360, 390 and
     430, and above the lower third in 844x390 landscape. */
  .tagline-plate { padding-bottom: calc(var(--svh) * 0.36); --scrim-y: 56%; }

  .package { padding: 44px 0 48px; }
  .pk-grid { grid-template-columns: 1fr; gap: 22px; }
  .pk-panel { padding: 22px 18px 16px; }
  .band { font-size: 11px; padding: 2px 5px 1px; }
  .fan-rail { gap: 6px 14px; }
  .fan-met { font-size: 11px; }
  .fan-met > b:not(.band) { font-size: 14px; }

  .roster-wrap { margin-top: 36px; }
  .roster-card { margin-top: 8px; }
  .roster-top { font-size: 11px; letter-spacing: 0.10em; padding: 11px 14px; }
  /* D10, 2026-09-02. The phone used to drop the dot leaders and push the
     status pill onto its own line, so the lineup card stopped being a lineup
     card and became three stacked text blocks. The row keeps its three
     columns and its leaders; the position eyebrow is what moves to a second
     line, because it is the one part of the row that is not the concept. */
  .ro-row { grid-template-columns: 40px minmax(0, 1fr) auto; gap: 6px 12px; padding: 14px 14px; }
  .ro-num { font-size: 1.9rem; }
  .ro-line { flex-wrap: wrap; gap: 0 10px; }
  .ro-name { font-size: 1.35rem; }
  .ro-dots { display: block; min-width: 16px; }
  .ro-pos { flex: 0 0 100%; margin-top: 5px; }
  .ro-hook { font-size: 0.9rem; }
  /* the pill stays at 11px: the phone-layout gate's L-FONT floor. Room is
     bought with tracking and padding, never with type size. */
  .ro-status { grid-column: 3; grid-row: 1; align-self: center; justify-self: end; font-size: 11px; letter-spacing: 0.07em; padding: 4px 8px 3px; }
  .roster-foot-line { font-size: 11px; letter-spacing: 0.10em; }

  .price-row { grid-template-columns: 1fr; border-top: 0; }
  .price-cell { border-top: 2px solid var(--rd-forest); padding: 16px 0; }
  .price-cell + .price-cell { border-left: 0; padding-left: 0; }
  .price-coach { grid-template-columns: 1fr; gap: 8px; padding: 16px 18px; }
  .lineup-grid { grid-template-columns: 1fr; gap: 26px; }
  .founders { padding: 44px 22px; }

  .proof-rows { grid-template-columns: 1fr; gap: 28px; }
  .proof-row { grid-template-columns: 52px minmax(0, 1fr); gap: 14px; }
  .proof-num { font-size: 1.9rem; }
  .proof-h { font-size: 1.32rem; line-height: 1.35; }

  /* the sticky bar owns the bottom 60px, so the last act clears it */
  .waitlist { padding-bottom: 96px; }
  .af-option { font-size: 14px; padding: 8px 8px 8px 7px; }
  .pk-quote { font-size: 0.98rem; }

  .pr-name { font-size: 1.05rem; }
  .pr-sub { padding-left: 26px; }
  .faq summary { font-size: 0.98rem; }

  .wl-h { font-size: clamp(2.4rem, 11vw, 3.4rem); }
  .wl-body { padding: 16px 16px 4px; }
}
/* Below 380px COACHES CORNER at 1.35rem pushed the leaders onto a line of
   their own, which is the same failure in a smaller costume. Measured at 360
   and 320: one step down on the name and the pill holds all three rows. */
@media (max-width: 380px) {
  .ro-row { grid-template-columns: 34px minmax(0, 1fr) auto; gap: 6px 9px; padding: 13px 11px; }
  .ro-num { font-size: 1.7rem; }
  .ro-name { font-size: min(1.15rem, 5.2vw); }
  .ro-status { font-size: 11px; letter-spacing: 0.02em; padding: 4px 5px 3px; }
  .ro-dots { min-width: 12px; }
}
/* iPhone Safari exposes less vertical room while its address bar is visible
   than a nominal 390x844 device emulation. Compact and lift only the portrait
   lockup so the eyebrow keeps a real gap above the three-action stack. */
@media (max-width: 900px) and (max-height: 760px) and (orientation: portrait) {
  .lights-inner { gap: 16px; transform: translateY(-34px); }
  .lights-lockup img { width: clamp(230px, 66vw, 300px); }
}
@media (max-width: 900px) and (orientation: landscape) {
  .lights-cue { display: inline-flex; }
}
@media (min-width: 901px) and (max-width: 1180px) {
  .lineup-grid { grid-template-columns: repeat(2, 1fr); gap: 26px 18px; }
}

/* =========================================================
   NO-JS FALLBACK — the resolved page, fully readable.
   (program.js stamps html.js; without it nothing may hide.)
   The lights beat is already end-state by default (animations
   are gated to html.js). The stage paints the static field with
   the tagline over it.
   ========================================================= */
html:not(.js) .tagline-plate { opacity: 1; }
html:not(.js) .tagline { transform: scale(1); }
html:not(.js) .pk-head, html:not(.js) .pk-panel, html:not(.js) .roster-wrap,
html:not(.js) .proof-inner, html:not(.js) .pg-pricing,
html:not(.js) .waitlist-inner > * { opacity: 1 !important; transform: none !important; }
html:not(.js) .speech-line, html:not(.js) .founders-body, html:not(.js) .founders .btn { opacity: 1 !important; transform: none !important; clip-path: none !important; }
html:not(.js) .plan-row { opacity: 1; transform: none; clip-path: none; }
html:not(.js) .pc-chip, html:not(.js) .plan-foot { opacity: 1; transform: none; }
html:not(.js) .fan-edge { stroke-dashoffset: 0; }
html:not(.js) .fan-quad, html:not(.js) .fan-spokes, html:not(.js) .fan-chord,
html:not(.js) .fan-ring, html:not(.js) .fan-met, html:not(.js) .read-result { opacity: 1; }

/* =========================================================
   REDUCED MOTION — everything lands, nothing moves.
   The stage shows the frozen decision frame, then steps three
   static frames on a slow timer (play.js) — no interpolation,
   no input required. Arrival paints the finished lit frame.
   ========================================================= */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001s !important;
    animation-delay: 0s !important;
    transition-duration: 0.001s !important;
    transition-delay: 0s !important;
  }
  .chip-dot, .sweep, .cue-arrow, .vs-dot { animation: none !important; }
  .stamp.stamped { animation: none; opacity: 1; transform: translate(-50%, -230%) rotate(-4deg) scale(1); }
  .freeze-flash.flash-now { animation: none; }
}
