/* ============================================================================
   Tiny Track Party — the "Toy Box" theme
   ----------------------------------------------------------------------------
   ONE source of truth for the look. Shared by the display and the controller
   (both <link> this before their own page CSS). To add a new UI element in the
   right style, reach for a TOKEN (a --custom-property) or a KIT class below
   before writing bespoke CSS — that keeps every screen consistent for free.

   The language is SIX ROLES, each meaning the same thing on the phone and the
   TV, so what a thing looks like says what it does:
     PAPER  — white cards, flat. Things you read.
     FROST  — every full-screen layer (pause, settings, the link screens, the
              results): the screen behind it blurred and lightened, with the
              content straight on it. Never a card on a scrim.
     ACTION — ONE solid colour button per screen, the step that moves the game
              on (red when it leaves). .btn
     CHOICE — everything else you press: white with a faint edge. The picked
              one is washed in the colour it picks and ringed in it; a
              finger on one washes it paler in that same colour. Out of
              reach is flat grey. The phone's colour is your livery (its
              --pick/--press), the TV's is blue.
              The ghost button, .icon-btn, and the pages' tiles.
     LABEL  — a tinted pill with coloured text, never a solid fill: tags,
              counts, the legend. .pill
     GLASS  — dark tinted readouts over the live 3D view (lap, the countdown
              strip, the scan hint). Never something you press.
   Two things keep a solid colour on purpose: the phone's driving pads (each is
   the action under a thumb) and the name tags over the race (whose car is
   whose). Nothing has a ledge or a drop shadow: a press squashes the thing
   pressed and darkens it, or washes a white one.
   Chrome colours are red/green/blue/purple ONLY — yellow/amber and pink are
   liveries, never chrome. Celebration is RED.

   ONE :root block, and scripts/gen-design-tokens.mjs bakes it into
   design-tokens.json for the TV shells, which draw the same six roles from it.

   Layout:
     1. @font-face        — self-hosted Fredoka + Nunito (see /assets/fonts)
     2. :root tokens      — palette, type, radii, shadows  (the design system)
     3. base / reset      — box-sizing, default type, .hidden
     4. component kit     — .card .btn .chip .pill .field .dot  (reusable bits)
     5. wordmark          — the one die-cut TINY TRACK / PARTY! badge
     6. paper stage       — .scene__* (sky, grass band, clouds)

   Pages own their LAYOUT (grid, positioning); the theme owns the LANGUAGE
   (colour, type, surface, shadow). Keep it that way.
   ========================================================================= */

/* 1. Fonts — variable woff2, one file per family, weight interpolated. ----- */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 300 700;            /* variable axis */
  font-display: swap;
  /* Relative to THIS FILE (shared/ is one level below the game root), never
     document-absolute: hosting the tree under a prefix makes the document root
     stop being the game root. Same rule as shared/assetUrl.js. */
  src: url('../assets/fonts/fredoka-variable.woff2') format('woff2');
}
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 200 1000;           /* variable axis */
  font-display: swap;
  src: url('../assets/fonts/nunito-variable.woff2') format('woff2');
}

/* 2. Design tokens -------------------------------------------------------- */
:root {
  /* -- car palette (mirrors CAR_COLORS in shared/protocol.js) --------------
     Liveries only — amber/pink/orange/cyan exist HERE and nowhere in chrome. */
  --car-red:    #e6492d;
  --car-amber:  #f2b134;
  --car-green:  #2bb673;
  --car-blue:   #2d9cdb;
  --car-purple: #9b51e0;
  --car-pink:   #eb5e9c;
  --car-orange: #f2784b;
  --car-cyan:   #56ccf2;

  /* -- chrome palette (the 4 colours + paper/ink)   ------------------ */
  --paper:  #F3F5FA;             /* the ground: phone screens, full boards     */
  --ink:    #2A2735;             /* text, dark chips — never #000             */
  --red:    #FF5040;             /* energy, celebration, BRAKE                */
  --green:  #22C46E;             /* go / ready / primary CTA                  */
  --blue:   #339CF2;             /* info                                      */
  --purple: #A259E6;             /* items                                     */

  /* -- semantic roles ---------------------------------------------------- */
  --brand:  var(--green);        /* primary action                            */
  --accent: var(--red);          /* highlights, celebration (was amber — RED) */
  --danger: var(--red);          /* brake / destructive                       */
  --item:   var(--purple);       /* ITEM button, item accents                 */
  --car:    var(--brand);        /* per-player livery (controller sets this)  */

  /* -- connection quality (controller latency chip) ---------------------- */
  --ping-good: var(--green);     /* low latency      */
  --ping-ok:   var(--blue);      /* moderate         */
  --ping-bad:  var(--red);       /* high / no signal */

  /* -- ink (text) -------------------------------------------------------- */
  /* --ink above is headings / primary text                                  */
  --ink-2: #5B6175;              /* body / secondary                          */
  --ink-3: #8F95A6;              /* muted / captions / open slots             */

  /* -- surfaces ----------------------------------------------------------- */
  --surface:   #ffffff;          /* PAPER: white cards                        */
  --surface-2: #ECEEF3;          /* wells, locked tiles                       */
  --hairline:  #E6E9F0;          /* row rules inside a card                   */
  --grass:     #93CC72;          /* the paper stage's grass band              */

  /* -- the six roles' surfaces (see the header) ---------------------------
     A CHOICE is a white face with a faint edge; the picked one is washed and
     a finger on one washes it paler. The TV's wash is blue; the phone
     re-points --pick/--press at the player's livery (controller.css). GLASS
     is only the dark readouts over the live 3D view; FROST is every
     full-screen layer: the screen behind blurred and lightened. */
  --edge:       #DFE3EA;         /* a choice's faint edge                     */
  --pick:       color-mix(in srgb, var(--blue) 26%, #fff);
  --press:      color-mix(in srgb, var(--blue) 14%, #fff);
  --glass:      rgba(24, 28, 44, 0.55);
  --frost:      rgba(243, 245, 250, 0.88);
  --frost-r:    14px;            /* the frost's blur radius                   */
  --frost-blur: blur(var(--frost-r));
  --focus-ring: 3px solid var(--blue);

  /* -- type -------------------------------------------------------------- */
  --font-display: 'Fredoka', system-ui, sans-serif;   /* everything display-ish */
  --font-body:    'Nunito', system-ui, -apple-system, sans-serif; /* rare running text */

  /* -- radii ------------------------------------------------------------- */
  --r-sm:   12px;
  --r:      18px;
  --r-lg:   24px;
  --r-pill: 999px;

  /* -- shadows ---------------------------------------------------------------
     NONE, and no ledges: depth is not a signal anywhere. What says "press
     me" is a solid colour (an action) or a white face with an edge (a
     choice). The tokens stay so a rule written against them still parses;
     each is `none`. --shadow-ink is the stage scenery's soft drop (clouds). */
  --shadow-ink:   rgba(20, 30, 50, 0.16);             /* the stage's soft drop */
  --shadow-pop:   none;
  --shadow-card:  none;
  --shadow-float: none;
  --btn-ledge:    rgba(42, 39, 53, 0.9);              /* the push-button ledge */
  /* Ledge geometry — ONE depth for every push-button in the app (chrome buttons
     AND the big BRAKE/ITEM drive pads), so nothing sits deeper than anything
     else. --btn-sink MUST stay under --btn-drop: press travel equal to the drop
     would punch the button through its own ledge and flatten it. The pressed
     ledge is always the difference, so the two can never drift apart. */
  --btn-drop:     4px;                                /* ledge depth at rest   */
  --btn-sink:     3px;                                /* travel while pressed  */
  /* How long the spring BACK takes. Going down is always 0s (see .btn:active),
     so this is the only press duration there is — and every pressable surface
     in the game reads it, the phone's tiles included (controller.css). It was
     re-typed in seven places when it moved from 60ms to 50ms, which is six
     places to miss. */
  --press-up:     0.05s;

  /* -- safe zone (TV overscan) ---------------------------------------------
     A television may crop the edges of the picture it is handed, and neither
     the browser nor Android can ask it how much: overscan is a property of the
     panel, not of the signal.

     2.5%, WHICH IS THE REAL FIGURE AND NOT GOOGLE'S. The classic "televisions
     crop about 5%" means 5% of the PICTURE, so ~2.5% per edge; Google's TV
     guidance doubles that to 5% a side, which is the CRT-era worst case and
     dates from 2014. A modern panel fed a 1:1 HDMI signal crops nothing at all.
     Shipped at 5% first and rejected on sight: it pushed the chrome so far in
     that it read as floating rather than as cornered.

     FRACTIONS OF THE SURFACE, not lengths, because overscan is a percentage and
     an authored px is not: 48px is 2.5% of a 1920 board and 3.75% of a 1280 one.
     Each shell converts once and reports the pair to `ttp_display_safe_insets`,
     which is what insets the per-cell HUD rects — see public/display/CLAUDE.md.
     Applied to EVERY edge of every cell and not only to the screen's own, which
     is a look decision argued where it is applied (ttp_display_core.cc).
     tvOS does NOT read these: it reports the safe area the system gives it,
     which is a real 90/60 pt rather than an assumption.

     The steer bar is deliberately NOT inset by them. It is the one HUD element
     the player also has in their hand, and moving it up crowds the car in a
     stacked cell — TtpRendererFrame.cpp says so where it places the bar. */
  --safe-frac-x:  0.025;                              /* per side, of width    */
  --safe-frac-y:  0.025;                              /* per side, of height   */

  /* -- track map (the circuit mini-map's marks) -----------------------------
     The marks a circuit's mini-map is drawn with, in the schematic's OWN
     viewBox units (`ttp_track_schematic_json` projects into 0 0 256 256, and
     `schematic.cc` writes that literal). Not lengths: a tile is drawn at
     whatever size its slot is, so a px width would be a second, contradictory
     scale.

     THEY ARE HERE BECAUSE THREE PLATFORMS DRAW THIS PICTURE. The projection is
     C++'s and the strokes are each shell's, so the widths were spelled once in
     the CSS below, once in Swift and once in Kotlin, with nothing that could
     see all three — the exact shape rule 1 exists to stop. As :root tokens they
     ride `design-tokens.json`, which both TV shells already read at boot. */
  --track-map-casing:     23;                         /* white casing under    */
  --track-map-road:       14;                         /* the ink road over it  */
  --track-map-start-r:    13;                         /* start/finish dot      */
  --track-map-start-ring:  4;                         /* its white ring        */
}

/* 3. Base / reset --------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

body {
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.hidden { display: none !important; }

/* 4. Component kit -------------------------------------------------------- */

/* Card — PAPER: the white panel everything you read sits on. No outline. */
.card {
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
}

/* Button — an ACTION: a solid colour face, white text, flat. Set --btn-bg to
   recolour. Pressed = it squashes a little and darkens. The ghost modifier
   below is the CHOICE version: white with the faint edge, washed while pressed.

   Three ways to look pressed, and they mean different things: `:active` is the
   browser's, `.held` is a finger on it RIGHT NOW where :active can't be trusted
   to say so (controller/press.js — WebKit withholds it on touch), and
   `.is-pressed` is a control latched down by app state, with no finger on it
   at all. `.held` is only ever set on an ENABLED control, which is why the
   rules below do not repeat the `:disabled` guard their `:active` half carries. */
.btn {
  --btn-bg: var(--brand);
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.15rem;
  line-height: 1.1;
  color: #fff;
  text-align: center;
  border: 0;
  border-radius: var(--r);
  padding: 0.85em 1.25em;
  background: var(--btn-bg);
  cursor: pointer;
  /* ASYMMETRIC, and that is the whole point. Going DOWN is instant (see the
     pressed rule below): the finger is already there, so any duration at all is
     time the button spends not yet looking pressed — 30ms of ease measured as
     33-43ms from touch to settled, because the transition cannot start until
     the frame after the class lands. Coming BACK UP is where the movement goes,
     at 50ms, so the button springs rather than snapping: cutting BOTH ends to
     zero was tried once and read as harsh, which is what that was really about.
     Every pressed surface on the phone shares this pair; see the tiles in
     controller.css. */
  transition: transform var(--press-up) ease, filter var(--press-up) ease, background-color var(--press-up) ease;
}
/* Hover only where there IS a hover: a touch screen leaves :hover on the last
   thing touched, so a key pressed and slid off stayed tinted as if held. */
@media (hover: hover) { .btn:hover { filter: brightness(1.04); } }
.btn:active, .btn.held, .btn.is-pressed {
  transform: scale(0.96); filter: brightness(0.9);
  transition: transform 0s, filter 0s, background-color 0s;   /* down NOW */
}
.btn:focus-visible { outline: var(--focus-ring); outline-offset: 4px; }
.btn--brand  { --btn-bg: var(--brand); }
.btn--accent { --btn-bg: var(--accent); }   /* red now — white text stands */
.btn--danger { --btn-bg: var(--danger); }
.btn--item   { --btn-bg: var(--item); }
.btn--ghost  {                     /* a CHOICE: white, the faint edge */
  --btn-bg: var(--surface);
  color: var(--ink);
  box-shadow: inset 0 0 0 2px var(--edge);
}
@media (hover: hover) { .btn--ghost:hover { filter: none; } }
.btn--ghost:active, .btn--ghost.held, .btn--ghost.is-pressed { filter: none; background: var(--press); }
/* Disabled: flat grey, the out-of-reach look every choice shares. */
.btn:disabled, .btn[disabled] {       /* e.g. join button while connecting */
  background: #E2E5EC; color: var(--ink-3);
  box-shadow: none;
  cursor: default; filter: none;
}
.btn:disabled:active { transform: none; }

/* Icon button — a CHOICE: a round white key with the faint edge for one glyph
   (settings, pause, leave, sound), the same over paper, frost or the race.
   Pressed = a small squash and the press wash. */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 3rem; height: 3rem; padding: 0;
  color: var(--ink);
  background: var(--surface);
  border: 0;
  border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 2px var(--edge);
  cursor: pointer;
  transition: transform var(--press-up) ease, background-color var(--press-up) ease;
}
@media (hover: hover) {
  .icon-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--press) 50%, #fff); }
}
.icon-btn:active:not(:disabled), .icon-btn.held {
  transform: scale(0.94); background: var(--press);
  transition: transform 0s, background-color 0s;
}
.icon-btn:focus-visible { outline: var(--focus-ring); outline-offset: 3px; }
.icon-btn:disabled { opacity: 0.5; cursor: default; }

/* Key pill — the corner keys that sit together, as ONE white pill: each key is
   a segment of it, parted by a short rule. Only keys that show are parted,
   so a hidden one leaves no rule behind. A segment washes without squashing:
   the rule hangs off the segment after it, and a squash dragged it inward. */
.key-pill {
  display: inline-flex; align-items: center;
  background: var(--surface); border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 2px var(--edge);
}
.key-pill .icon-btn { position: relative; background: transparent; box-shadow: none; }
.key-pill .icon-btn:active:not(:disabled), .key-pill .icon-btn.held { transform: none; background: var(--press); }
.key-pill > .icon-btn:not(.hidden) ~ .icon-btn:not(.hidden)::before {
  content: ''; position: absolute; left: -1px; top: 28%; bottom: 28%;
  width: 2px; border-radius: 1px; background: var(--edge);
}

/* Pause glyph — two rounded bars, sized off the button's text colour. */
.pause-glyph { display: flex; gap: 4px; }
.pause-glyph::before, .pause-glyph::after {
  content: ''; width: 4px; height: 16px; border-radius: 2px; background: currentColor;
}

/* Fullscreen glyph — four warm-ink corner brackets, drawn in the same weight as
   the pause bars. The button carries both (enter/exit) and shows one, picked by
   aria-pressed so the visual and the a11y state can never drift apart. */
.fs-glyph {
  width: 20px; height: 20px;
  fill: none; stroke: currentColor; stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
}
[aria-pressed="false"] .fs-glyph--exit, [aria-pressed="true"] .fs-glyph--enter { display: none; }

/* Sound glyph — a speaker with waves while live, a cross while muted. Same
   two-glyph pattern as fullscreen: pressed = muted. Bigger than the fullscreen
   brackets and with a FILLED body: an outline-only speaker at bracket size
   read too small next to them. Stroke-width compensates for the upscale so
   the ink weight still matches (2.2 × 24/20 ≈ the brackets' 2.6). */
.snd-glyph {
  width: 24px; height: 24px;
  fill: none; stroke: currentColor; stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
}
.snd-glyph .snd-body { fill: currentColor; }
[aria-pressed="false"] .snd-glyph--off, [aria-pressed="true"] .snd-glyph--on { display: none; }

/* Chip — a small white paper token with a colour dot (player in a roster). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  background: var(--surface);
  border-radius: var(--r-pill);
  padding: 0.45rem 0.95rem 0.45rem 0.6rem;
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--ink);
  box-shadow: var(--shadow-pop);
}

/* Pill — a LABEL: uppercase caps in a colour on a pale tint of it. Never a
   solid fill, so it never reads as something to press. --c picks the colour. */
.pill {
  --c: var(--ink-2);
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--c) 78%, #1B1830);
  background: color-mix(in srgb, var(--c) 20%, #fff);
  border-radius: var(--r-pill);
  padding: 0.32em 0.95em 0.38em;
}

/* Field — an input/value box, set INTO the page rather than on it. */
.field {
  background: var(--surface);
  border: 2px solid var(--hairline);
  border-radius: var(--r-sm);
  padding: 0.85rem 1rem;
  color: var(--ink);
  font-weight: 700;
  box-shadow: inset 0 2px 0 rgba(20, 30, 50, 0.05);
}

/* Site footer — the one row of legal chrome ("Developed by Tim" + licenses /
   privacy / imprint), worn by the welcome board and the licenses page. It is
   the QUIETEST thing on any screen on purpose: no sticker, no shadow, no
   rotation. It has to be legible and reachable, and nothing more. Its position
   is the page's business; this is only the row. */
.site-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: center;
  gap: 0.4rem 0.9rem;
  font-weight: 700; font-size: 0.82rem;
  color: var(--ink-3);
}
.site-foot a {
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 2px;
  text-decoration-color: rgba(42, 39, 53, 0.3);
}
.site-foot a:hover { color: var(--ink); text-decoration-color: currentColor; }

/* Track schematic (built by schematicSvg in shared/trackPicker.js) — a field
   with the toy road ribbon. Shared by the phone's track picker and the
   display's lobby cup slot; the container sizes it. */
.track-map { width: 100%; aspect-ratio: 1 / 1; display: block; border-radius: var(--r-sm); background: color-mix(in srgb, var(--brand), #fff 78%); }
/* The marks are :root tokens (--track-map-*), because the two TV shells draw
   this same picture from their own toolkits and read them out of the token
   bake. They are in viewBox units; the schematic viewBox is 0 0 256 256 (see
   trackSchematic VIEW) so these are ~2.56× their old 0-100 values.
   `r` is a CSS geometry property here rather than an attribute so the dot's
   radius comes from the same one place as the strokes. */
.track-map__casing { fill: none; stroke: #fff; stroke-width: var(--track-map-casing); stroke-linejoin: round; stroke-linecap: round; }
.track-map__road { fill: none; stroke: var(--ink-2); stroke-width: var(--track-map-road); stroke-linejoin: round; stroke-linecap: round; }
.track-map__start { fill: var(--danger); stroke: #fff; stroke-width: var(--track-map-start-ring); r: var(--track-map-start-r); }

/* Star row — cup progression (built by starRow in shared/trackPicker.js): a row
   of chunky toy stars, filled = earned, the unearned ones a grey of the same
   shape. RED on purpose — amber/gold is vetoed in chrome, and celebration is
   red. Size by context via font-size. The stroke is the FILL's own colour with
   round joins, which is what rounds the points off: no outline on the web
   (Toy Box); tvOS still draws its die-cut pair from SF Symbols. */
.starrow { display: inline-flex; gap: 0.14em; line-height: 1; }
.starrow .star { width: 1em; height: 1em; }
.starcount { display: inline-flex; align-items: center; gap: 0.3em; white-space: nowrap; line-height: 1; }
.starrow .star path {
  fill: var(--red); stroke: var(--red); stroke-width: 3.2; stroke-linejoin: round;
}
/* An unearned star is HOLLOW, its outline in see-through ink: a faint fill
   sank into a picked tile's wash, and a line does not. */
.starrow .star--off path { fill: none; stroke: rgba(38, 40, 56, 0.42); stroke-width: 2.2; }
.starrow .star { overflow: visible; }

/* Drawn padlock (lockGlyph in shared/trackPicker.js) — platform lock emoji are
   coloured and clash with the chrome's ink, so the theme draws its own. */
.lock-glyph { width: 1em; height: 1em; }
.lock-glyph path { fill: none; stroke: var(--ink); stroke-width: 2.6; stroke-linecap: round; }
.lock-glyph rect { fill: var(--ink); }

/* Dot — a livery colour swatch; set its background inline from CAR_COLORS. */
.dot {
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  flex: none;
}

/* Car thumbnail (see /shared/carThumbs.js) — a box showing a pre-baked render.
   The container sizes it (width:100%); aspect-ratio reserves the box before
   images load so nothing below jumps. In spin mode an overlay sprites the
   turntable strip, stepped by a shared clock (so all cars stay in sync).
   5:4 MATCHES the baked frames (256×205, see scripts/capture-car-thumbs.js) —
   these cars are far longer than they are tall, so a square box was mostly air
   above the roof. The two must stay in step: the stills are object-fit:contain,
   so a box that disagrees with the asset letterboxes it and shrinks the car. */
.carthumb { position: relative; width: 100%; aspect-ratio: 5 / 4; }
/* No transition on the opacity: the turntable's frame 0 IS this still, so the
   handoff in carThumbs.js is a CUT. Fading between them looked like a safe
   dissolve and is not — both layers carry the car's soft ground shadow, and
   compositing two translucent copies of it darkens the shadow for the whole
   fade. Nothing else moves this opacity. */
.carthumb__still {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; display: block;
}
.carthumb__spin {
  position: absolute; inset: 0;
  background-repeat: no-repeat;
  /* background-size is written inline by carThumbs.js from SPIN_FRAMES */
  background-position: 0 0;
}

/* 5. Wordmark — ONE lockup (never a row of pills):
     <div class="wordmark"><span>TINY TRACK</span><span class="l2">PARTY!</span></div>
   Ink over a red PARTY! tipped against it, with a soft moulded drop. The TV
   shells still bake the die-cut version (scripts/bake-wordmark.mjs).
   Size by context via font-size. */
.wordmark {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 0.98;
  color: var(--ink);
  letter-spacing: 0.01em;
}
.wordmark span { display: block; }
.wordmark .l2 {
  color: var(--red); font-size: 1.24em;
  rotate: -3deg;
}

/* 6. Paper stage ----------------------------------------------------------
   Drop a <div class="scene"> with these children behind any full-screen board
   for the board backdrop: sky, a flat grass band, two sticker
   clouds and a few confetti rects. Decorative only — it never eats pointer
   events and sits at the bottom of the stacking order. NO SUN (it was yellow). */
.scene { position: absolute; inset: 0; overflow: hidden; z-index: 0; pointer-events: none; }

.scene__sky {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, #CFE7F7 0%, #EAF4FB 55%, #F3F8FC 100%);
}
/* rolling hills peeking over the grass band — two soft bumps for depth */
.scene__sky::before, .scene__sky::after {
  content: ''; position: absolute; border-radius: 50%;
}
.scene__sky::before { width: 74%; height: 30%; left: -16%; bottom: 11%; background: #BFE3A6; }
.scene__sky::after  { width: 82%; height: 36%; right: -20%; bottom: 9%; background: #A9D98C; }

.scene__sun { display: none; }   /* retired — the sun was yellow */

/* Cloud — a soft white pill with two humps, no outline: scenery, not chrome. */
.scene__cloud {
  position: absolute; background: #fff;
  border-radius: var(--r-pill);
}
.scene__cloud::before, .scene__cloud::after {
  content: ''; position: absolute; background: #fff;
  border-radius: 50%;
  clip-path: inset(0 0 40% 0);
}
/* positioned clear of the display lobby's rails (ticket left, cup slot right)
   and the centre tagline */
.scene__cloud--a { top: 9%; left: 34%; width: 150px; height: 42px; transform: rotate(-2deg); }
.scene__cloud--a::before { width: 62px; height: 62px; top: -26px; left: 24px; }
.scene__cloud--a::after  { width: 46px; height: 46px; top: -16px; left: 82px; }
.scene__cloud--b { top: 20%; right: 23%; width: 104px; height: 30px; transform: rotate(2deg); }
.scene__cloud--b::before { width: 44px; height: 44px; top: -18px; left: 18px; }
.scene__cloud--b::after  { width: 34px; height: 34px; top: -11px; left: 54px; }
.scene__cloud--c { top: 36%; right: 9%; width: 84px; height: 26px; transform: rotate(-2deg); }
.scene__cloud--c::before { width: 36px; height: 36px; top: -15px; left: 14px; }
.scene__cloud--c::after  { width: 28px; height: 28px; top: -9px; left: 44px; }

/* Flat grass band, gently rounded top — stage scenery. */
.scene__grass {
  position: absolute; left: -2%; right: -2%; bottom: 0; height: 24%;
  background: var(--grass);
  border-radius: 50% 50% 0 0 / 22% 22% 0 0;
}

/* 7. Motion — the SLAP-IN ------------------------------------------------
   Scale overshoot with a slight rotate wobble, ~240ms. Uses the independent
   scale/rotate properties so it composes with any static transform the
   element already carries (tilts, centring translates). Applied by page CSS
   on screen entry (display:none → shown restarts CSS animations for free);
   JS re-triggers it for same-element content swaps (countdown digits).
   Reduced motion: the animation simply never exists. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes sticker-slap {
    0%   { scale: 1.15; rotate: 2deg; opacity: 0; }
    60%  { scale: 0.96; rotate: -0.6deg; opacity: 1; }
    100% { scale: 1; rotate: 0deg; }
  }
  .slap { animation: sticker-slap 240ms cubic-bezier(0.2, 1.5, 0.35, 1) both; }
}
