/* ============================================================
   THE STAGE — the world a verse is recited into
   A racing short you drive with your voice. Same materials as the eight
   reward shorts (quest.css, cars.css) and the same rules, with one
   difference that changes the engineering: a short runs for ten seconds
   against a fixed clock, and this runs for as long as a child takes.

   So nothing here is keyed to a timeline. The world loops, and everything
   that moves is either an infinite animation whose *playback rate* the app
   turns up and down, or a transform driven off a custom property. There is
   no --run, no hit, no beat count.

   THE THREE RULES INHERITED FROM quest.css, none of them negotiable:
     · Nothing animates left, top, width or height.
     · A filter and a transform never share an element — one re-rasterises
       the whole scene every frame, two let the compositor move a finished
       raster. Every blurred plane here is therefore two elements — and the
       ORDER of the two is the whole point, which the first build got exactly
       backwards and paid for at thirty frames a second.

       The band moves; the strip inside it carries the blur. That way the
       strip's content never changes, its blurred raster is computed once,
       and the parent's transform slides the finished picture. Put the blur
       on the parent instead and the child moves *inside* it, so the parent's
       content changes every frame and the blur is recomputed every frame
       across a strip twice the window's width. Ten planes of that is the
       difference between sixty and thirty.
     · Two animations never share a transform: last one wins, silently.
   ============================================================ */

/* The host the app mounts a scene into. `.screen > *` puts every direct child
   of a screen on z-index 1; the world has to sit under that, because the one
   thing on this screen that may never be competed with is the scripture.

   `fixed`, not `absolute`, and that is the whole difference between a game
   and a strip of wallpaper. The app is a 520px column centred in the window
   — right for a phone, and on a laptop it leaves two thirds of the screen
   as dead margin. Fixed positioning takes the world out to the full viewport
   while the card and the controls stay in the column, so the child is
   reciting *inside* the race rather than next to a picture of one.

   It escapes `.screen{overflow:hidden}` because nothing between here and the
   viewport establishes a containing block — no transform, no filter, no
   will-change. If one is ever added to #app or .screen, this silently
   becomes absolute again and the world snaps back into the column. */
.rq-host{ position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden }

/* ---------- the eight worlds ----------
   Hudson's theme already defines eight grounds, one per journey stage, and
   states the rule they follow: "Every one keeps a single light source above
   the horizon, which is the camera grammar of this theme; what changes is
   what that light is." Cool shop fluorescents, warm daylight haze, a neon
   pool in the wet, cold storm, then flame, then gold.

   The race takes them rather than inventing its own. The sky IS `--bg` — the
   same value the app paints behind every other screen at that stage — so the
   world a child recites into is the world they just came from, and a future
   edit to the theme carries here for free. What this file adds is only what a
   race needs and a settings screen does not: how the light above the horizon
   is coloured, and whether the sodium lamps are lit at all.

   Two of the eight are daylight. Sodium lamps burning at noon is the kind of
   detail that stops the other six being worth anything, so at Practice and
   Qualifying they go out. */
.rq-racing .rq-sky{ position:absolute;inset:0;background:var(--bg) }
.rq-racing{
  --lamp-op:1;
  --glow-a:rgba(255,178,96,.22);
  --glow-b:rgba(27,107,255,.12);
  --tar:transparent;
}
/* Pit Lane — shop fluorescents, teal and cheap. */
:root[data-stage="0"] .rq-racing{ --glow-a:rgba(126,206,214,.20);--glow-b:rgba(40,90,120,.16) }
/* Practice — the one round that isn't lit, just bright. */
:root[data-stage="1"] .rq-racing{ --lamp-op:0;--glow-a:rgba(255,238,198,.30);--glow-b:rgba(150,148,128,.14);
  --tar:rgba(180,168,140,.16) }
/* Qualifying — low sun straight down the main straight. */
:root[data-stage="2"] .rq-racing{ --lamp-op:0;--glow-a:rgba(255,150,60,.32);--glow-b:rgba(120,80,52,.16);
  --tar:rgba(190,120,60,.12) }
/* Club Round — first real points, and the blue flame to mark them. */
:root[data-stage="3"] .rq-racing{ --glow-a:rgba(74,134,255,.24);--glow-b:rgba(30,60,140,.16) }
/* Night Circuit — a pool rather than a wash; everything outside it is gone. */
:root[data-stage="4"] .rq-racing{ --glow-a:rgba(255,178,96,.22);--glow-b:rgba(122,86,180,.18) }
/* Regional Final — storm light, the coldest round of the season. */
:root[data-stage="5"] .rq-racing{ --glow-a:rgba(168,178,214,.22);--glow-b:rgba(60,70,110,.18) }
/* The Grand Prix — the flame finally allowed onto the ground itself. */
:root[data-stage="6"] .rq-racing{ --glow-a:rgba(255,106,0,.32);--glow-b:rgba(196,63,0,.18);
  --tar:rgba(255,106,0,.07) }
/* Champion — gold over chrome, the brightest of the eight. */
:root[data-stage="7"] .rq-racing{ --glow-a:rgba(255,210,30,.30);--glow-b:rgba(239,194,76,.16);
  --tar:rgba(255,210,30,.08) }

.rq-lamps{ opacity:var(--lamp-op,1) }
/* A wash over the asphalt, because the deck's own colours are night ones and
   two of these rounds are not. Over the road, under everything on it. */
.rq-tar{
  position:absolute;left:0;right:0;top:var(--hz);bottom:0;
  background:var(--tar,transparent);pointer-events:none;
}

/* ---------- the screen, once there is a world behind it ----------
   The recite screen was laid out for a quiet card: `.learn-area` is flex:1,
   so the card grows to fill everything between the header and the controls
   and there is nothing left for a scene to be seen in. Staged, the card
   stops filling the space it is offered and sits up against the top of it.

   The progress strip goes away entirely, and that is a deletion rather than
   a hide: the scene IS the strip. Both read the same latched `reach`, and
   putting two pictures of one number on a screen only invites a child to
   check whether they agree. */
#s-recite.is-staged .learn-area{ align-items:flex-start;padding-top:4px }
#s-recite.is-staged .learn-card{ max-height:56% }
#s-recite.is-staged #recite-strip{ display:none }
#s-recite.is-staged .learn-foot{ gap:7px }

/* The HUD is positioned against the top of its host, and in the app the top
   of the host is underneath the title bar.

   And in the app it is cut down to one number. The full readout — stage
   name, gap, hairline rules — has room on a bare 360x780 harness frame and
   none at all on the real screen, which already carries a title bar, a
   strand of dots, a card and three controls before the world gets a say.
   What survives is the position, because it is the only part that changes
   on its own and the only part that is a reward. */
.rq-hud{ top:var(--hud-top,0) }
.rq-hud.is-compact b,
.rq-hud.is-compact u,
.rq-hud.is-compact .rq-gapnum{ display:none }
.rq-hud.is-compact{ top:auto;bottom:0;height:0 }
/* Pinned to the app's column, not the window. The world is full-bleed now,
   so `right:14px` put the one number a child is meant to watch a laptop's
   width away from the verse they are reciting. */
.rq-hud.is-compact .rq-pos{
  left:auto;top:auto;
  right:calc(50% - var(--road,520px) / 2 + 16px);
  bottom:var(--pos-bottom,120px);
  font-size:21px;letter-spacing:.06em;
  text-shadow:0 2px 10px rgba(0,0,0,.8);
}

/* ---------- the shell ---------- */
/* Full-bleed behind the screen it belongs to. `contain` is the one addition
   the shorts do not need: this runs for a minute rather than ten seconds,
   and telling the browser the scene cannot affect anything outside itself
   is most of what keeps a sixty-second paint honest on a tablet. */
.rq{
  position:absolute;inset:0;overflow:hidden;pointer-events:none;
  contain:strict;z-index:0;
  /* The scale everything on the road is measured against, in px, set by
     mount(). It is the *column* width, never the viewport's: cars.css sizes
     a car as `--k * --fw`, so letting this follow a laptop window would put
     a nine-hundred-pixel GT3 on screen. The world is as wide as the window;
     the things on it are as big as the app. */
  --fw:520;
  --road:520px;
  /* WHERE THE HORIZON IS, and the only number that decides it. Everything
     that sits on, above or below the road is expressed against this.

     46%, not the 34% this was first drawn at, because the app's real
     viewport is a laptop: short and wide, with a verse card occupying the
     top ~42%. A horizon at 34% is a horizon behind the card — the vanishing
     point, the far distance and the rival at his furthest all hidden by the
     scripture, which leaves a child watching a road with no beginning and
     no one to catch. Dropping it below the card gives the whole road back. */
  --hz:46%;
  --speed:.35;          /* 0 = coasting, 1 = flat out. Drives everything. */
  --gap:1;              /* 1 = the rival is up the road, 0 = alongside */
  --push:0;             /* camera push, 0..1 */
}

/* The camera. Transform only. It pushes in as the verse goes on and
   tightens at the finish; the app moves --push and this reads it. */
.rq-cam{
  position:absolute;inset:0;transform-origin:52% 62%;
  transform:scale(calc(1 + var(--push) * .07)) translateY(calc(var(--push) * -1.2%));
  transition:transform 900ms cubic-bezier(.16,.6,.2,1);
  will-change:transform;
}
/* The lens. Filter only, on its own element, for the reason above. */
.rq-scene{
  position:absolute;inset:0;
  filter:saturate(calc(1 + var(--speed) * .14));
  transition:filter 600ms linear;
}

/* ---------- parallax ----------
   A plane is two elements. The band is placed and blurred; the strip inside
   is twice the frame wide, carries the repeating material, and translates by
   exactly one pattern period before looping.

   One period, not half its own width — that is the difference between a
   seamless loop and a visible seam every few seconds, because the strip's
   width is a share of the viewport and the pattern's is a pixel count, and
   the two do not divide. `.r-deck` solves it the same way.

   All of them run one keyframe and differ only in duration and period,
   which is what lets the app move the whole world's speed with a single
   sweep of playback rates rather than eight separate sums. */
/* A band is static unless it says otherwise. Nothing on the racing side
   drifts sideways any more — the far planes hold and everything near the
   road rides .rq-verge — so the animation is opt-in, and a plane that does
   not move costs nothing at all. Princess still uses it: hers is a flat
   cartoon sky where drifting cloud reads as weather rather than as a camera
   pointing the wrong way. */
.rq-band{ position:absolute;left:0;width:200%;pointer-events:none }
.rq-band.is-drift{ animation:rq-run linear infinite;will-change:transform }
.rq-band > i{ position:absolute;inset:0;display:block }
@keyframes rq-run{ to{ transform:translate3d(calc(var(--per,150px) * -1),0,0) } }

/* CAMERA GRAMMAR, and the thing this scene got wrong first time.

   This is a chase camera looking down the road. Trackside things converge at
   the vanishing point and stream *toward* the lens; they do not slide across
   it. Sideways travel is the grammar of a camera standing beside the track —
   which is what racing short 8 is, and where these bands were borrowed from.
   Reading it here made the stands appear to move at right angles to the car,
   which is exactly the error quest-scenes.js warns about: "which way the road
   runs is part of the camera, not decoration."

   So the far planes hold still — at that distance a grandstand barely moves
   anyway — and everything near the road rides the road's own perspective
   plane in .rq-verge below, converging with it for free. */

/* Far silhouette — grandstand and pylons. Static: distant, and this camera
   is pointed straight down the straight. */
.rq-stands{ top:calc(var(--hz) - 14%);height:14% }
.rq-stands > i{
  opacity:.85;
  filter:blur(.8px);
  background:
    repeating-linear-gradient(90deg,#0A101C 0 46px,transparent 46px 52px,
      #0A101C 52px 122px,transparent 122px 132px),
    linear-gradient(180deg,transparent 0 14%,#0C1524 14% 30%,#080E1A 30% 84%,#05090F 84%);
}
/* Crowd stipple in the stands. Two thousand people as one texture, which is
   the honest budget for something nine percent of frame high and blurred. */
.rq-crowd{ top:calc(var(--hz) - 12%);height:7.5% }
.rq-crowd > i{
  opacity:.22;
  filter:blur(1.4px);
  background:repeating-conic-gradient(from 0deg at 50% 50%,rgba(143,163,189,.5) 0deg 90deg,
    transparent 90deg 180deg,rgba(90,101,119,.4) 180deg 270deg,transparent 270deg);
  background-size:7px 7px;
}
/* Catch fencing over the stands. */
.rq-fence{ top:calc(var(--hz) - 12%);height:11% }
.rq-fence > i{
  opacity:.09;
  filter:blur(1.6px);
  background:
    repeating-linear-gradient(74deg,rgba(214,226,242,.6) 0 1px,transparent 1px 16px),
    repeating-linear-gradient(-74deg,rgba(214,226,242,.6) 0 1px,transparent 1px 16px);
}
/* Armco, the last thing between the road and the world. */
/* Trackside furniture, already mush at this speed. Two periods layered,
   because one period reads as wallpaper — the shorts learned that. */
/* Marshal posts, close enough to still be objects. */

/* ---------- trackside, on the road's own plane ----------
   Barriers running down both verges and marshal posts spaced along them,
   built as layers of a perspective plane with the same rotateX the deck
   uses. They converge with the road because they ARE the road's plane, and
   the posts stream toward the camera because the plane's texture scrolls in
   Y — which is what driving forward looks like. `.r-deck.kerb` earns its
   kerbing the same way, and for the same reason. */
.rq-verge{
  position:absolute;left:-60%;right:-60%;bottom:0;height:calc(100% - var(--hz));
  perspective:var(--persp,260px);perspective-origin:50% 0%;
  overflow:hidden;pointer-events:none;
}
.rq-verge > i{
  position:absolute;left:0;right:0;bottom:0;height:340%;display:block;
  transform-origin:50% 100%;transform:rotateX(76deg);
  background:
    /* marshal posts, one file down each verge */
    repeating-linear-gradient(180deg,rgba(255,210,30,.6) 0 20px,transparent 20px 400px),
    repeating-linear-gradient(180deg,rgba(255,210,30,.6) 0 20px,transparent 20px 400px),
    /* the barrier itself, continuous */
    linear-gradient(180deg,rgba(206,216,230,.5),rgba(70,80,96,.55)),
    linear-gradient(180deg,rgba(206,216,230,.5),rgba(70,80,96,.55));
  background-size:2.4% 400px,2.4% 400px,4.6% 100%,4.6% 100%;
  background-position:22.6% 0,75% 0,21% 0,74.4% 0;
  background-repeat:repeat-y,repeat-y,no-repeat,no-repeat;
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 24%,#000 100%);
  mask-image:linear-gradient(180deg,transparent 0,#000 24%,#000 100%);
  animation:rq-vergerun var(--verge-t,560ms) linear infinite;
}
/* One post period per cycle, so the loop is seamless at any speed. */
@keyframes rq-vergerun{
  to{ background-position:22.6% 400px,75% 400px,21% 0,74.4% 0 }
}

/* ---------- sodium lamps ----------
   A lamp is a source above the verge and the smear it lays on wet asphalt,
   because light landing twice is the whole reason a wet night frame looks
   expensive. Each one approaches on its own loop — growing, drifting
   outward, and dying as it leaves the frame — rather than riding a band
   sideways, for the camera reason at the top of the parallax section.

   The reflection is a child of the lamp so it grows with it: a reflection
   that stayed put while its source came at you would read as two objects. */
.rq-lamps{ position:absolute;inset:0;pointer-events:none }
.rq-lamps > span{
  position:absolute;left:50%;top:var(--hz);
  transform-origin:50% 0;
  animation:rq-lampin var(--lamp-t,5600ms) linear infinite;
  animation-delay:var(--d,0ms);
  will-change:transform,opacity;
}
/* Perspective is not linear: a lamp a long way up the road barely moves for
   most of its life and then leaves the frame in a moment. The easing on the
   first keyframe carries the whole run, which is what buys that. */
@keyframes rq-lampin{
  0%  {transform:translate3d(var(--x0,0px),0,0) scale(.14);opacity:0;
       animation-timing-function:cubic-bezier(.55,.02,.86,.38)}
  10% {opacity:.9}
  78% {opacity:1}
  100%{transform:translate3d(var(--x1,-52vw),30vh,0) scale(3.1);opacity:0}
}

/* ---------- depth ----------
   Every layer states its own z-index, and none of them may be left to DOM
   order. The reason is one line in cars.css: `.r-deck` carries `z-index:2`,
   and a positioned element with a z-index paints in a later step than every
   `z-index:auto` element in the same stacking context — so the road painted
   over the parallax planes, the wet sheen, the trackside mush and the rival,
   all of which come *after* it in the markup. It cost an invisible rival and
   an hour to find, because everything was present, positioned, opaque and
   reported visible by the DOM. It was simply underneath the road.

   Back to front. The gap between 9 and 20 is deliberate: it is where the
   ground stops and the road starts, and the reflections that belong ON the
   road have to land above it. */
.rq-sky      { z-index:1 }
.rq-tar      { z-index:21 }
.rq-sky-glow{ z-index:2 }
.rq-stands  { z-index:3 }
.rq-crowd   { z-index:4 }
.rq-fence   { z-index:5 }
.rq .r-bok  { z-index:6 }
.rq .r-horizon{ z-index:7 }
.rq .r-deck { z-index:20 }
.rq-verge   { z-index:22 }   /* barriers and posts, on the road's own plane */
.rq-sheen   { z-index:23 }
.rq-lamps   { z-index:24 }   /* the reflections lie on the road, not under it */
.rq .r-wet  { z-index:24 }
.rq-slot    { z-index:30 }   /* the rival; the hero's own parts are 38–40 */
.rq-gantry  { z-index:50 }
.rq-line    { z-index:25 }   /* paint on the road: under both cars, over the asphalt */
.rq-flagpole{ z-index:52 }
.rq-flag    { z-index:53 }
.rq-bloom   { z-index:55 }
/* quest.css puts these at 5 and 9, which was fine inside a takeover whose
   scene was everything below them. Here they have to cap the whole frame. */
.rq .q-vignette{ z-index:60 }
.rq .q-grain   { z-index:61 }
.rq-hud     { z-index:70 }

/* ---------- the road ----------
   C.deck() does the perspective. It is sized here so its top edge lands on
   the horizon this scene puts at 34%. */
.rq .r-deck{ height:calc(100% - var(--hz)) }

/* Floodlight spill above the stands. Nothing in the upper third was lit, so
   a third of every frame was doing no work at all — and a night circuit that
   is black above the grandstand is a circuit with no floodlights on it. */
.rq-sky-glow{
  position:absolute;left:0;right:0;top:0;height:var(--hz);pointer-events:none;
  background:
    radial-gradient(42% 60% at 22% 96%,var(--glow-a),transparent 70%),
    radial-gradient(38% 54% at 62% 92%,var(--glow-a),transparent 70%),
    radial-gradient(50% 50% at 88% 98%,var(--glow-b),transparent 72%);
}

/* The wet sheen sitting on the asphalt. */
.rq-sheen{
  position:absolute;left:0;right:0;top:var(--hz);bottom:0;pointer-events:none;
  background:radial-gradient(72% 46% at 50% 8%,rgba(27,107,255,.1),transparent 70%);
}

/* ---------- the actors ----------
   Four wrappers, and each one owns exactly one thing, which is the same
   argument cars.css makes for having two:

     .rq-slot   the race position   — transform
     .rq-dim    beaten-ness         — filter and opacity
     .r-cw      placement and depth — transform (scale off --k)
     .r-rig     the idle float      — transform (animation)

   Collapsing any pair of them puts two animations on one transform, or a
   filter and a transform on one element. Both are silent failures. */
.rq-slot{
  position:absolute;inset:0;pointer-events:none;
  transform:translate3d(var(--sx,0),var(--sy,0),0);
  will-change:transform;
}
/* Fast enough to feel attached to the voice, slow enough that a recogniser
   revising itself twice in one frame does not read as a twitch. */
.rq-slot.is-smooth{ transition:transform 220ms cubic-bezier(.3,.7,.3,1) }

/* What losing looks like. rig-drop's note in cars.css is the rule here too:
   a rival who only falls back reads as parked; one who also loses the light
   and the focus reads as beaten. */
.rq-dim{
  position:absolute;inset:0;
  opacity:var(--rq-o,1);
  filter:blur(var(--rq-blur,0px)) saturate(var(--rq-sat,1)) brightness(var(--rq-bri,1));
  transition:opacity 260ms linear,filter 260ms linear;
}
/* Depth is scale, and scale is a transform, so it can be transitioned. */
.rq-rivalcar{ transition:transform 220ms cubic-bezier(.3,.7,.3,1) }

/* The rival's taillights, and the column they drop onto wet asphalt.
   These are not decoration — up the road at night they ARE the rival, and
   the bodywork behind them only starts doing the work in the last couple of
   lengths. Both are sized in JS off the car's own scale so they stay bolted
   to it, and both sit on the nominal contact point the car does. */
.rq-tails{
  position:absolute;left:50%;top:56%;z-index:31;pointer-events:none;
  width:var(--tw,60px);height:calc(var(--tw,60px) * .07 + 3px);
  transform:translate(-50%,-260%);
  opacity:var(--to,1);
  transition:opacity 260ms linear,width 220ms cubic-bezier(.3,.7,.3,1);
}
/* Two lamps, and nothing between them. The first attempt drew them as a pair
   of gradients inside one box, which fills the space between and reads as a
   lit girder across the road — indistinguishable, at that size, from the
   finish gantry that arrives later. */
.rq-tails > i{
  position:absolute;top:0;bottom:0;width:26%;border-radius:99px;
  background:linear-gradient(180deg,#FF9A85,#E01B24 60%,#8E0F16);
  box-shadow:0 0 8px 2px rgba(255,60,50,.9),0 0 26px 8px rgba(226,30,40,.45);
}
.rq-tails > i:first-child{ left:0 }
/* Opacity only — no transform, no filter — so this can be its own cheap
   channel without contending with the three wrappers around it. */
.rq-body{ position:absolute;inset:0;opacity:var(--rq-body,1);
  transition:opacity 300ms linear }
.rq-tails > i:last-child{ right:0 }
.rq-tailwet{
  position:absolute;left:50%;top:56%;z-index:30;pointer-events:none;
  width:var(--tw,40px);height:9%;
  transform:translate(-50%,-2%);
  opacity:var(--to,1);filter:blur(9px);
  background:linear-gradient(180deg,rgba(226,30,40,.55),transparent 76%);
  transition:opacity 260ms linear,width 220ms cubic-bezier(.3,.7,.3,1);
}

/* ---------- the finish ----------
   A chase camera, so the line does not sweep across the frame — it comes AT
   you, lying flat on the road, and passes underneath. Both it and the gantry
   therefore start as slivers at the horizon and grow from a transform-origin
   pinned there, which is a cheap and convincing stand-in for perspective.

   The first attempt reused the shorts' `.r-linepaint` / `.r-lineswoop`, and
   they are built for a camera standing *beside* the track: an upright band
   crossing the frame. Dropped into a chase cam and stretched with scaleY it
   became a barcode wall across the middle of the picture — checks are only
   checks while the scale stays uniform.

   Translations are in percentages of each element's own height, and that
   height is a percentage of the frame, so the whole sequence scales with the
   screen instead of being pinned to a pixel count. */

/* The line, flat on the road, arriving. */
.rq-line{
  position:absolute;left:50%;top:var(--hz);width:46%;height:1.5%;
  --cs:9px;opacity:0;
  transform-origin:50% 0;
  transform:translate3d(-50%,0,0) scale(.12);
}
.rq.is-finishing .rq-line{ animation:rq-linein 1250ms cubic-bezier(.42,0,.72,1) both }
@keyframes rq-linein{
  0%  {opacity:0;transform:translate3d(-50%,0,0) scale(.12)}
  12% {opacity:1}
  100%{opacity:1;transform:translate3d(-50%,4200%,0) scale(2.6)}
}

/* The gantry, arriving overhead and passing over. Beam and legs are one
   element's children so they grow together — a gantry whose legs scale at a
   different rate is a gantry made of rubber. */
.rq-gantry{
  position:absolute;left:50%;top:calc(var(--hz) - 1.5%);width:76%;height:1.8%;
  opacity:0;transform-origin:50% 100%;
  transform:translate3d(-50%,0,0) scale(.22);
  background:linear-gradient(180deg,#28344A,#0A101C);
  box-shadow:0 2px 10px rgba(0,0,0,.7),inset 0 1px 0 rgba(201,212,226,.35);
}
.rq-gantry > i{
  position:absolute;top:100%;width:5%;height:900%;
  background:linear-gradient(180deg,#28344A,#0C1420);
}
.rq-gantry > i:first-child{ left:1% }
.rq-gantry > i:last-child{ right:1% }
.rq.is-finishing .rq-gantry{ animation:rq-gantryin 1250ms cubic-bezier(.42,0,.72,1) both }
@keyframes rq-gantryin{
  0%  {opacity:0;transform:translate3d(-50%,0,0) scale(.22)}
  14% {opacity:.9}
  100%{opacity:.9;transform:translate3d(-50%,-680%,0) scale(4.6)}
}

/* Bloom at the line. It swells and falls away — never a cut to white. */
.rq-bloom{
  position:absolute;inset:0;opacity:0;pointer-events:none;
  background:radial-gradient(60% 40% at 50% 44%,rgba(255,214,150,.85),
    rgba(255,154,71,.28) 46%,transparent 74%);
}
.rq.is-finishing .rq-bloom{ animation:rq-bloom 1500ms 480ms ease-in-out both }
@keyframes rq-bloom{ 0%{opacity:0} 34%{opacity:.62} 100%{opacity:0} }

/* The marshal's flag: trackside, small, and on a pole. It belongs to a
   person standing at the line, not to the lens — it was previously a
   quarter of the frame, which reads as a test card. */
.rq-flag{
  position:absolute;left:7%;top:calc(var(--hz) - 7%);width:10%;height:5%;
  --cs:6px;opacity:0;transform-origin:0 50%;filter:blur(.4px);
}
.rq.is-finishing .rq-flag{ animation:rq-flagin 2200ms 600ms both }
@keyframes rq-flagin{
  0%  {opacity:0;transform:translateX(14px) rotate(9deg) skewY(0)}
  14% {opacity:1}
  38% {transform:translateX(0) rotate(-3deg) skewY(6deg)}
  60% {transform:translateX(0) rotate(2deg)  skewY(-7deg)}
  82% {transform:translateX(0) rotate(-2deg) skewY(5deg)}
  100%{opacity:1;transform:translateX(0) rotate(0) skewY(0)}
}
.rq-flagpole{
  position:absolute;left:6.4%;top:calc(var(--hz) - 7%);width:2px;height:8%;opacity:0;
  background:linear-gradient(180deg,rgba(201,212,226,.7),rgba(90,100,116,.15));
}
.rq.is-finishing .rq-flagpole{ animation:rq-fade 500ms 600ms both }
@keyframes rq-fade{ from{opacity:0} to{opacity:1} }

/* ---------- the HUD ----------
   Racing only, and in the theme's own voice: Andale Mono on hairline rules,
   the register the win-moment handoff specifies. It sits outside .rq-cam so
   the camera push never drags the numbers with it — a HUD that scales is a
   sticker on the lens.

   Princess gets no HUD at all. Her progress is the road behind her and the
   landmark ahead, which is most of the difference between a race and a walk. */
.rq-hud{
  position:absolute;left:0;right:0;bottom:0;pointer-events:none;
  font-family:"Andale Mono","Courier New",monospace;
  opacity:0;animation:rq-fade 700ms 300ms both;
}
.rq-hud b{
  position:absolute;font-weight:400;font-size:9px;letter-spacing:.3em;
  text-transform:uppercase;color:rgba(143,163,189,.9);
}
.rq-hud b.l{ left:16px;top:14px }
.rq-hud b.r{ right:16px;top:14px;text-align:right }
.rq-hud u{
  position:absolute;top:30px;height:1px;text-decoration:none;
  background:linear-gradient(90deg,rgba(237,243,250,.28),transparent);
}
.rq-hud u.l{ left:16px;width:84px }
.rq-hud u.r{ right:16px;width:84px;
  background:linear-gradient(270deg,rgba(237,243,250,.28),transparent) }
/* The position card flips from P2 to P1 exactly once, and it is the only
   number on screen that ever changes on its own. */
.rq-pos{
  position:absolute;left:16px;top:38px;font-size:26px;letter-spacing:.02em;
  color:#EDF3FA;transition:color 400ms linear;
}
.rq.is-led .rq-pos{ color:#FFD21E }
.rq-gapnum{
  position:absolute;right:16px;top:38px;font-size:15px;letter-spacing:.06em;
  color:rgba(237,243,250,.8);font-variant-numeric:tabular-nums;
}

/* ---------- princess ----------
   Her scene lives entirely in styles/stage-princess.css, loaded alongside
   this file — see that file's header for why. Nothing princess-specific
   remains here; only the shell above (.rq, .rq-cam, .rq-scene, .rq-band,
   .rq-slot, .rq-dim) and the racing material below, both shared or
   racing-only. */

/* ---------- reduced motion ----------
   Snap, never suppress — the rule quest.js sets for the stage moments. A
   child who cannot take the movement still gets the world, the road, the
   rival and the finish; what they do not get is the travelling.

   Princess's own reduced-motion rules live in stage-princess.css next to
   the elements they freeze; only the shared/racing ones stay here. */
@media (prefers-reduced-motion:reduce){
  .rq-band.is-drift,.rq .r-deck i,.rq-verge > i,
  .rq-lamps > span{ animation:none !important }
  .rq-cam,.rq-slot,.rq-dim,.rq-rivalcar{ transition:none !important }
  /* A lamp frozen at the start of its approach is an invisible lamp — the
     rule for the stage moments is snap to a still frame, never suppress. */
  .rq-lamps > span{
    opacity:.9;transform:translate3d(var(--x1),16vh,0) scale(1.4);
  }
  .rq.is-finishing .rq-line,.rq.is-finishing .rq-gantry,
  .rq.is-finishing .rq-flag,.rq.is-finishing .rq-flagpole,
  .rq.is-finishing .rq-bloom{
    animation-duration:1ms !important;animation-delay:0ms !important;
  }
}
