/* ============================================================
   QUEST MOMENT — shared shell
   Every stage scene mounts inside this. The shell owns the camera, the
   impact, the title slam, the payoff plate, the traveller and the particle
   primitives; a scene only authors what makes it *that* place.

   Scene rules live further down, namespaced `.q-princess.q-sN` / `.q-racing.q-sN`.

   ---------- the beat clock ----------
   Four variables carry the whole timeline, so retiming a theme is four
   numbers rather than forty scattered animation-delays:

     --t-hit    the impact — flash, quake, colour drain
     --t-title  the name lands
     --t-plate  the payoff plate sets
     --t-cta    the way out appears

   The themes want genuinely different rhythms. Princess fires the hit and
   the title together: her world is drawn, bright and immediate, and a held
   pause before the name would read as a stall. The racing shorts follow the
   lookbook's own beat list — 0.0 wheel · 1.2 reflection · 2.1 the pass ·
   3.4 flag · 4.1 plaque — so the hit is *the pass*, and the title arrives a
   beat later, after the reflection has caught up. Slower, and far more
   expensive-looking. Everything after the plate is a held end frame: the
   scene never closes itself, it waits to be dismissed.

   ---------- the transform stack ----------
   Nothing animates `left`, `top`, `width` or `height`. A full-screen scene
   carrying forty elements cannot hold a frame budget on an iPad if the
   traveller's path relays out the scene sixty times a second. Every moving
   thing is a transform or an opacity, and the shell gives each its own
   element so two animations never contend for one property:

     .q-shake  the gentle whole-frame settle, type included — translate
     .q-cam    the camera push                              — transform
     .q-quake  the impact jolt                              — transform
     .q-scene  the colour drain                             — filter

   Separating the drain from the camera is what keeps the push cheap: a
   filter and a transform on one element re-rasterise the entire scene every
   frame, where two elements let the compositor move the finished raster.
   ============================================================ */

.q{
  --t-hit:1440ms; --t-title:1520ms; --t-plate:3050ms; --t-cta:3500ms;
  --stride:470ms;
  --drain-sat:.62; --drain-bright:1.34;
}
/* The racing beat list, restruck for the ten-second cut. The five-second
   version had to fire the pass at 2.1s because there was no time to do
   anything else first; with a real car in frame the short can afford the
   thing the GT3 board actually asks for — an approach you sit inside:

     0.0 – 2.4  the compression dolly, arriving into the world
     2.4 – 4.4  the run. Speed builds, the car closes, lamps compress
     4.4        the hit — the pass, the line, the lights out
     5.3        the name
     6.5        the plaque
     7.1        the way out appears
     7.1 – 10   aftermath. The car finishes what it was doing: pulls away,
                trails smoke, sits under the flag. Nothing new happens here
                and nothing needs to — it is the part a six-year-old watches
                twice, and cutting to black on the plate is what would make
                the reward feel like a dialog box.

   Everything above is four numbers. --t-run carries the tenth. */
.q-racing{
  --t-hit:4400ms; --t-title:5300ms; --t-plate:6500ms; --t-cta:7100ms;
  /* A night frame must not brighten. The lookbook's first rule is that
     asphalt near-black holds ~80% of the frame, and a bleach that lifts the
     blacks to grey reads as a gamma fault rather than as an impact — so the
     racing drain takes the saturation out and leaves the exposure almost
     alone. The princess scenes are the opposite case: her whole appeal is
     the pastel, so hers barely desaturates at all. */
  --drain-sat:.5; --drain-bright:1.1;
}

/* Styled by class rather than by #quest so more than one can exist at a time.
   The id is still set — it is the handle you reach for in devtools — but
   racing-shorts.html mounts all eight shorts into one document, and two
   elements sharing an id is a broken document however well it renders. */
.q{
  position:absolute;inset:0;z-index:60;overflow:hidden;
  background:#05070C;
  animation:q-in 260ms ease-out both;
  /* One nominal-frame unit. The shorts are composed against a 360-wide
     phone, and anything authored in those pixels — the corner sequence's
     circuit, and the cars riding it — multiplies by this to keep its shape
     on a wider screen instead of being re-tuned per device. --fw is the
     real frame width, published by quest.js at mount. */
  --u:calc(var(--fw,360) / 360);
}
@keyframes q-in{from{opacity:0}to{opacity:1}}

/* The gentle one: the whole frame, type included, settling after the hit.
   Kept small on purpose — shaking the title is what makes an impact read as
   arcade, and the racing register in particular cannot afford that. */
.q-shake{position:absolute;inset:0;animation:q-shake 520ms var(--t-hit) cubic-bezier(.22,.7,.3,1) both}
@keyframes q-shake{
  0%{translate:0 0} 8%{translate:-4px 3px} 22%{translate:3px -2px}
  40%{translate:-2px -1px} 62%{translate:1px 1px} 100%{translate:0 0}
}

/* The camera. Each scene picks a move by setting --cam-*. */
.q-cam{
  position:absolute;inset:0;
  transform-origin:var(--cam-origin,50% 60%);
  will-change:transform;
  animation:q-cam var(--t-run,5000ms) cubic-bezier(.22,.61,.36,1) both;
}
@keyframes q-cam{
  0%  {transform:scale(var(--cam-from,1.18)) translate(var(--cam-fx,0),var(--cam-fy,0))}
  62% {transform:scale(var(--cam-mid,1.02)) translate(0,0)}
  100%{transform:scale(var(--cam-to,1)) translate(var(--cam-tx,0),var(--cam-ty,0))}
}

/* The hard one: the world jolts, and only the world. An impulse rather than
   a rattle — one big displacement and a fast decay, the way a real camera
   mount recovers. A long even wobble reads as a video-game screen shake. */
.q-quake{position:absolute;inset:0;animation:q-quake 620ms var(--t-hit) cubic-bezier(.2,.8,.3,1) both}
@keyframes q-quake{
  0%  {transform:translate(0,0) rotate(0)}
  6%  {transform:translate(-13px,9px) rotate(-.5deg)}
  16% {transform:translate(10px,-7px) rotate(.35deg)}
  28% {transform:translate(-6px,-4px) rotate(-.2deg)}
  42% {transform:translate(4px,3px) rotate(.12deg)}
  58% {transform:translate(-2px,-2px) rotate(0)}
  100%{transform:translate(0,0) rotate(0)}
}

/* The held frame: at the peak the scene drains of colour for a third of a
   second while the title lands on top of it, then floods back. On .q-scene
   rather than an overlay, so the title — which lives outside the camera —
   stays vivid through it. */
.q-scene{position:absolute;inset:0;animation:q-drain 900ms calc(var(--t-hit) - 60ms) ease-out both}
@keyframes q-drain{
  0%  {filter:saturate(1) brightness(1)}
  12% {filter:saturate(var(--drain-sat)) brightness(var(--drain-bright))}
  46% {filter:saturate(calc(var(--drain-sat) + .18)) brightness(1.1)}
  100%{filter:saturate(1) brightness(1)}
}

/* Something for the title to land against. A radial bloom reads as a smudge
   on the light princess scenes, so this is a scrim band instead — soft top
   and bottom edges, so it works over dawn sky and night circuit alike. */
.q-hero{
  position:absolute;left:0;right:0;top:16%;height:42%;pointer-events:none;z-index:6;
  background:linear-gradient(180deg,transparent,rgba(6,10,18,.46) 26%,
             rgba(6,10,18,.5) 62%,transparent);
  opacity:0;animation:q-hero 1400ms calc(var(--t-title) - 80ms) ease-out both;
}
@keyframes q-hero{0%{opacity:0}14%{opacity:1}72%{opacity:.92}100%{opacity:.62}}

.q-vignette{
  position:absolute;inset:0;pointer-events:none;z-index:5;
  background:radial-gradient(130% 90% at 50% 46%,transparent 46%,rgba(0,0,0,.5) 100%);
}
.q-flash{
  position:absolute;inset:0;pointer-events:none;z-index:8;background:#fff;
  opacity:0;animation:q-flash 460ms calc(var(--t-hit) + 10ms) ease-out both;
}
@keyframes q-flash{0%{opacity:0}9%{opacity:.85}100%{opacity:0}}

/* Film grain over everything, same trick the racing theme uses on the app. */
.q-grain{
  position:absolute;inset:0;pointer-events:none;z-index:9;opacity:.26;
  mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E");
}

/* ---------- title slam ---------- */
.q-slam{
  position:absolute;left:0;right:0;top:34%;z-index:7;text-align:center;padding:0 22px;
  animation:q-slamin 700ms var(--t-title) cubic-bezier(.2,1.5,.36,1) both;
}
.q-kicker{
  display:block;font-size:10px;letter-spacing:.22em;text-transform:uppercase;
  font-weight:700;color:rgba(255,255,255,.72);margin-bottom:8px;
}
.q-slam h2{
  position:relative;display:inline-block;
  font-family:var(--font-display);font-size:clamp(34px,11vw,54px);line-height:1;
  font-weight:700;color:#fff;transform:skewX(var(--skew));
  text-shadow:0 6px 0 rgba(0,0,0,.35),0 14px 30px rgba(0,0,0,.6);
}
/* The glint is a clipped copy of the same text sweeping a highlight across. */
.q-glint{
  position:absolute;inset:0;color:transparent;
  background:linear-gradient(100deg,transparent 38%,rgba(255,255,255,.95) 50%,transparent 62%);
  -webkit-background-clip:text;background-clip:text;
  background-size:280% 100%;background-position-x:180%;
  animation:q-glint 1100ms calc(var(--t-title) + 630ms) ease-out both;
}
@keyframes q-glint{to{background-position-x:-90%}}
.q-blurb{
  margin-top:10px;font-size:14px;font-weight:600;color:rgba(255,255,255,.86);
  text-shadow:0 2px 10px rgba(0,0,0,.7);
}
@keyframes q-slamin{
  0%  {opacity:0;transform:skewX(var(--skew)) scale(2.6) translateY(-8px);filter:blur(6px)}
  55% {opacity:1;transform:skewX(var(--skew)) scale(.94)}
  100%{opacity:1;transform:skewX(var(--skew)) scale(1)}
}

/* ---------- payoff plate ---------- */
.q-plate{
  position:absolute;left:50%;bottom:104px;z-index:7;width:min(300px,84%);
  transform:translateX(-50%);
  animation:q-platein 620ms var(--t-plate) cubic-bezier(.2,.9,.25,1.25) both;
}
.q-in{
  padding:14px 16px;border-radius:14px;
  background:linear-gradient(180deg,rgba(255,255,255,.16),rgba(10,14,22,.72));
  border:1px solid rgba(255,255,255,.24);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 18px 34px rgba(0,0,0,.6);
  backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);
}
.q-row{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:4px 0}
.q-row span{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:rgba(255,255,255,.66);font-weight:700}
.q-row b{font-family:var(--font-display);font-size:17px;color:#fff;transform:skewX(var(--skew))}
.q-row.gold b{color:#FFD21E}
.q-div{height:1px;background:rgba(255,255,255,.18);margin:6px 0}
@keyframes q-platein{0%{opacity:0;transform:translateX(-50%) translateY(22px)}
  100%{opacity:1;transform:translateX(-50%) translateY(0)}}

.q-cta{
  position:absolute;left:50%;bottom:34px;z-index:7;transform:translateX(-50%);
  padding:13px 26px;border-radius:999px;cursor:pointer;
  font-family:var(--font-display);font-size:15px;font-weight:700;color:#0B0F16;
  background:linear-gradient(180deg,#fff,#D6DEE9);border:1px solid rgba(255,255,255,.7);
  box-shadow:0 10px 24px rgba(0,0,0,.55);
  animation:q-ctain 420ms var(--t-cta) ease-out both;
}
.q-cta span{transform:skewX(var(--skew));display:inline-block}
.q-cta:active{transform:translateX(-50%) translateY(2px)}
@keyframes q-ctain{0%{opacity:0;transform:translateX(-50%) translateY(14px)}
  100%{opacity:1;transform:translateX(-50%) translateY(0)}}

/* ---------- particle primitives ----------
   The factories in quest.js scatter bare <i> elements into these hosts and
   set --dx/--dy/--s/--spin per particle; the animation belongs here. */
.q-motes{position:absolute;inset:0;pointer-events:none}
.q-motes i{
  position:absolute;width:6px;height:6px;border-radius:50%;
  background:#fff;opacity:0;
  animation-name:q-mote;animation-timing-function:ease-out;animation-fill-mode:both;
}
@keyframes q-mote{
  0%  {opacity:0;transform:translate(0,0) scale(0)}
  25% {opacity:1;transform:translate(calc(var(--dx)*.3),calc(var(--dy)*.3)) scale(var(--s))}
  100%{opacity:0;transform:translate(var(--dx),var(--dy)) scale(calc(var(--s)*.5))}
}

.q-burst{position:absolute;inset:0;pointer-events:none}
.q-burst i{
  position:absolute;width:7px;height:7px;border-radius:2px;background:#fff;
  animation-name:q-brst;animation-timing-function:cubic-bezier(.15,.7,.4,1);
  animation-fill-mode:both;
}
@keyframes q-brst{
  0%  {opacity:1;transform:translate(0,0) rotate(0) scale(1.4)}
  100%{opacity:0;transform:translate(var(--dx),var(--dy)) rotate(var(--spin)) scale(0)}
}

.q-fall{position:absolute;inset:0;pointer-events:none;overflow:hidden}
.q-fall i{
  position:absolute;top:-6%;border-radius:2px;background:#fff;
  animation-name:q-drop;animation-timing-function:linear;animation-iteration-count:infinite;
}
/* Princess falls are petals, not confetti — the shape does the work. */
.q-princess .q-fall i{border-radius:60% 20% 60% 20%}
@keyframes q-drop{
  0%  {opacity:0;transform:translate(0,0) rotate(0)}
  6%  {opacity:1}
  90% {opacity:1}
  100%{opacity:0;transform:translate(var(--drift),110vh) rotate(var(--spin))}
}

/* ---------- the traveller ----------
   Three nested channels that never fight, because each owns exactly one
   transform and no keyframe has to restate another's:

     .q-rider   the path across the frame — scene-authored, slow, 2D
     .q-gait    the stride                — shared, fast, looping
     .q-body    squash, stretch and lean  — shared, fast, looping

   The path used to be animated on `left`, which lays the scene out again on
   every frame. `.q-rider` now spans the frame, so a percentage transform
   moves the traveller exactly one frame-width and the path costs nothing.

   The contact shadow deliberately sits *outside* `.q-gait`: it follows the
   path but not the bob. A shadow that leaves the ground with the character
   is the fastest way to make weight read as fake — this one stays put,
   tightening and darkening as she lands and spreading as she floats. */
.q-rider{
  position:absolute;inset:0;z-index:3;pointer-events:none;
  will-change:transform;
}
.q-gait,.q-shadow{
  position:absolute;left:var(--rx,0);margin-left:var(--rml,0);
  bottom:var(--ry,16%);width:var(--rw,96px);
}
.q-gait{will-change:transform;animation:q-gait var(--stride) linear infinite}
.q-body{display:block;transform-origin:50% 100%;animation:q-body var(--stride) linear infinite}
.q-body svg{width:100%;height:auto;display:block}
.q-shadow{
  height:11px;margin-bottom:-6px;border-radius:50%;z-index:-1;
  background:radial-gradient(50% 50% at 50% 50%,rgba(34,20,38,.6),transparent 72%);
  animation:q-shadow var(--stride) linear infinite;
}

/* A gallop, not a float: quick push off the ground, a held suspension, then
   gravity taking her down faster than she went up. The per-keyframe easings
   are what make it read as weight rather than a sine wave. */
@keyframes q-gait{
  0%  {transform:translateY(1px);   animation-timing-function:cubic-bezier(.16,.72,.3,1)}
  20% {transform:translateY(-12px); animation-timing-function:cubic-bezier(.4,0,.6,1)}
  50% {transform:translateY(-14px); animation-timing-function:cubic-bezier(.5,0,.78,.18)}
  100%{transform:translateY(1px)}
}
@keyframes q-body{
  0%  {transform:scale(1.035,.955) rotate(-1deg); animation-timing-function:cubic-bezier(.16,.72,.3,1)}
  20% {transform:scale(.98,1.04) rotate(-3deg)}
  50% {transform:scale(1,1) rotate(-1.8deg)}
  100%{transform:scale(1.035,.955) rotate(-1deg)}
}
@keyframes q-shadow{
  0%  {opacity:.52;transform:scale(1,1); animation-timing-function:cubic-bezier(.16,.72,.3,1)}
  50% {opacity:.15;transform:scale(.58,.68)}
  100%{opacity:.52;transform:scale(1,1)}
}

/* Standing still. Scenes where she arrives and stays put tag the rider
   `q-idle` — a gallop under a character who has stopped is worse than no
   animation at all. */
.q-idle .q-gait{animation:q-breath 2800ms ease-in-out infinite}
.q-idle .q-body{animation:q-settle 2800ms ease-in-out infinite}
.q-idle .q-shadow{animation:none;opacity:.42}
@keyframes q-breath{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes q-settle{0%,100%{transform:scale(1,1)}50%{transform:scale(.995,1.015)}}

@media (prefers-reduced-motion:reduce){
  .q *{animation:none!important;transition:none!important}
  .q-slam,.q-plate,.q-cta{opacity:1!important;transform:none!important}
  .q-slam{transform:translateY(0)!important}
  .q-plate{transform:translateX(-50%)!important}
  .q-cta{transform:translateX(-50%)!important}
  .q-flash{opacity:0!important}
  /* The scrim stays: it is the title's contrast against a bright dawn sky,
     not decoration, and zeroing it made the reduced-motion frame the least
     legible one in the app. */
  .q-hero{opacity:.62!important}
  /* Particle hosts have no still frame worth showing. With the animation
     stripped a burst is a stack of squares sitting on its origin and a fall
     is a row of petals parked above the top edge — artefacts, not scenery. */
  .q-motes,.q-burst,.q-fall{display:none!important}
}

/* ============================================================
   SCENE 1 · PRINCESS — The Meadow
   Dawn over rolling hills. Camera pushes in as she crosses.
   ============================================================ */
.q-princess.q-s1{--cam-from:1.24;--cam-mid:1.03;--cam-to:1;--cam-origin:46% 72%}
.q-princess.q-s1 .q-scene{background:linear-gradient(180deg,#2A1B3D 0%,#7C4A6B 38%,#E9899E 62%,#FBD86B 100%)}
.m-sky{position:absolute;inset:0;
  background:radial-gradient(70% 45% at 50% 78%,rgba(255,236,180,.9),transparent 70%);
  opacity:0;animation:m-dawn 2600ms 200ms ease-out both}
@keyframes m-dawn{to{opacity:1}}
.m-sun{
  position:absolute;left:50%;bottom:44%;width:132px;height:132px;margin-left:-66px;
  border-radius:50%;background:radial-gradient(circle,#FFF6D8,#FFD98A 55%,#FBB6CE);
  box-shadow:0 0 70px 26px rgba(255,214,138,.55);
  transform:translateY(90px) scale(.7);opacity:0;
  animation:m-rise 3000ms 260ms cubic-bezier(.25,.8,.3,1) both;
}
@keyframes m-rise{0%{transform:translateY(90px) scale(.7);opacity:0}
  40%{opacity:1}100%{transform:translateY(-6px) scale(1);opacity:1}}
.m-rays{position:absolute;left:50%;bottom:46%;width:0;height:0}
.m-ray{
  position:absolute;left:-2px;bottom:0;width:4px;height:300px;transform-origin:50% 100%;
  background:linear-gradient(0deg,rgba(255,240,200,.5),transparent 78%);
  transform:rotate(calc((var(--i) - 3) * 15deg)) scaleY(0);
  animation:m-ray 2200ms calc(900ms + var(--i)*70ms) ease-out both;
}
@keyframes m-ray{0%{transform:rotate(calc((var(--i) - 3) * 15deg)) scaleY(0);opacity:0}
  50%{opacity:.85}100%{transform:rotate(calc((var(--i) - 3) * 15deg)) scaleY(1);opacity:.35}}
.m-hill{position:absolute;left:-12%;right:-12%;border-radius:50% 50% 0 0}
.m-h3{bottom:22%;height:26%;background:#6B4A72;opacity:.75;animation:m-par 5000ms linear both}
.m-h2{bottom:14%;height:24%;background:#4E7C63;animation:m-par2 5000ms linear both}
.m-h1{bottom:0;height:26%;background:linear-gradient(180deg,#6FAE83,#3F7A5A)}
@keyframes m-par{from{transform:translateX(14px)}to{transform:translateX(-14px)}}
@keyframes m-par2{from{transform:translateX(26px)}to{transform:translateX(-26px)}}
.m-field{position:absolute;left:0;right:0;bottom:0;height:26%}
.m-flower{
  position:absolute;bottom:calc(4% + var(--m3) * 22%);
  left:calc(2% + var(--i) * 7%);
  width:13px;height:13px;border-radius:50%;
  background:radial-gradient(circle,#FFF6D8 26%,#FBB6CE 30%,#F284AC 62%,transparent 66%);
  transform:scale(0);
  animation:m-bloom 700ms calc(700ms + var(--i)*90ms) cubic-bezier(.34,1.8,.5,1) both;
}
.m-flower:nth-child(3n){background:radial-gradient(circle,#FFF6D8 26%,#C9AEEF 30%,#A784DE 62%,transparent 66%)}
.m-flower:nth-child(3n+1){background:radial-gradient(circle,#FFF6D8 26%,#FBD86B 30%,#EFC24C 62%,transparent 66%)}
@keyframes m-bloom{0%{transform:scale(0) rotate(-40deg)}100%{transform:scale(1) rotate(0)}}
/* The path only — the gallop, the squash and the contact shadow are the
   shared rig in the shell. The base transform is her mid-frame pose, which
   is also what a reduced-motion reader is left looking at. */
.q-princess.q-s1 .q-rider{--ry:19%;--rw:104px}
.m-uni{transform:translateX(42%);animation:m-path 5000ms cubic-bezier(.3,.5,.5,1) both}
@keyframes m-path{
  0%  {transform:translateX(-30%)}
  46% {transform:translateX(42%)}
  100%{transform:translateX(116%)}
}

/* ============================================================
   SCENE 2 · PRINCESS — Wildflower Path
   Petals lift off the path behind her and spiral into a column.
   ============================================================ */
.q-princess.q-s2{--cam-from:1.3;--cam-mid:1.04;--cam-to:1.08;--cam-origin:50% 64%}
.q-princess.q-s2 .q-scene{background:linear-gradient(180deg,#BFE1F2 0%,#E7F3F8 44%,#CDE8D4 100%)}
.w-sky{position:absolute;inset:0;
  background:radial-gradient(60% 40% at 24% 14%,rgba(255,255,255,.95),transparent 70%),
             radial-gradient(52% 34% at 82% 22%,rgba(255,228,238,.85),transparent 72%)}
.w-far{position:absolute;left:-10%;right:-10%;bottom:42%;height:16%;border-radius:50% 50% 0 0;
  background:#A8CBB4;opacity:.6}
.w-path{
  position:absolute;left:50%;bottom:-6%;width:60%;height:56%;margin-left:-30%;
  background:linear-gradient(180deg,#EED9C4,#D9BFA4);
  clip-path:polygon(38% 0,62% 0,100% 100%,0 100%);
}
.w-verge{position:absolute;bottom:0;height:52%;width:34%}
.w-left{left:0}.w-right{right:0}
.w-stem{
  position:absolute;bottom:calc(2% + var(--m4) * 12%);
  width:5px;height:calc(26px + var(--m3) * 14px);border-radius:3px;
  background:linear-gradient(180deg,#7FD3B2,#4E9B7A);transform-origin:50% 100%;
  animation:w-sway 2600ms calc(var(--i)*110ms) ease-in-out infinite alternate;
}
.w-left .w-stem{left:calc(4% + var(--i)*10%)}
.w-right .w-stem{right:calc(4% + var(--i)*10%)}
.w-stem::after{content:"";position:absolute;top:-9px;left:-5px;width:15px;height:15px;
  border-radius:50%;background:radial-gradient(circle,#FFF6D8 24%,#FBB6CE 28%,#F284AC 66%,transparent 70%)}
.w-right .w-stem:nth-child(2n)::after{background:radial-gradient(circle,#FFF6D8 24%,#C9AEEF 28%,#A784DE 66%,transparent 70%)}
@keyframes w-sway{from{transform:rotate(-7deg)}to{transform:rotate(7deg)}}
/* She has to clear the payoff plate by the end frame, so after the beat she
   carries on up the path — smaller, further away, out of its way. */
.q-princess.q-s2 .q-rider{--ry:16%;--rw:96px;--rx:50%;--rml:-48px;transform-origin:50% calc(100% - var(--ry))}
.w-uni{transform:translateY(-6%) scale(.86);animation:w-path 5000ms cubic-bezier(.3,.6,.4,1) both}
@keyframes w-path{
  0%  {transform:translateY(9%) scale(.42);opacity:0}
  18% {opacity:1}
  46% {transform:translateY(0) scale(1);opacity:1}
  70% {transform:translateY(-6%) scale(.86)}
  100%{transform:translateY(-25%) scale(.46);opacity:.85}
}
/* The column: petals climbing a helix, each one a step further round. */
.w-column{position:absolute;left:50%;bottom:26%;width:0;height:0;z-index:4}
.w-swirl{
  position:absolute;width:11px;height:11px;border-radius:60% 20% 60% 20%;
  left:0;bottom:0;opacity:0;
  animation:w-climb 2400ms ease-out both;
}
@keyframes w-climb{
  0%  {opacity:0;transform:translate(0,0) scale(.4) rotate(0)}
  22% {opacity:1}
  100%{opacity:0;transform:translate(var(--tx),calc(-14px - var(--r)*1.9)) scale(1.15) rotate(320deg)}
}


/* ============================================================
   RACING — art direction from design/art-direction/racing-shorts-lookbook.html

   The reference is Gran Turismo 1 promotional footage, not arcade racing.
   Everything below follows from four rules in that lookbook:

     1. Restraint. Near-black holds ~80% of the frame; one light source.
     2. The car is never a portrait — a tread arc, a rim highlight, a red
        smear, a reflection. Suggestion, not a die-cast.
     3. Slow camera. A locked-off wide and one push, or a low pan.
     4. Track orange appears as *light*, never as a fill.

   The type breaks from the app's Impact for the length of the short: thin
   tracked chrome caps bottom-left over a hairline rule, with a timecode
   opposite. Impact returns the instant the UI does.
   ============================================================ */

/* ---------- the quiet type register ----------
   Everything below is 46px lower than it was. The old block ran from the
   CTA up to bottom:342px, which was free real estate while the lower half
   of a racing short was empty road — and became a plaque sitting on the
   hero's number roundel the moment there was a car standing there. Compacted
   to the bottom ~30% of the frame, which is also what a broadcast lower
   third actually occupies. */
.q-racing .q-slam{
  top:auto;bottom:80px;left:30px;right:30px;text-align:left;padding:0;
  animation:q-fadeup 900ms var(--t-title) cubic-bezier(.2,.7,.3,1) both;
}
.q-racing .q-kicker{
  font-family:var(--font-ui);font-size:9px;letter-spacing:.34em;
  color:rgba(143,160,184,.78);margin-bottom:9px;
}
.q-racing .q-slam h2{
  font-family:var(--font-ui);font-weight:400;
  font-size:clamp(21px,6.4vw,30px);letter-spacing:.3em;text-transform:uppercase;
  transform:none;color:#EAF1FA;
  text-shadow:0 2px 18px rgba(0,0,0,.9);
}
/* No glint: a highlight sweep is showmanship, and this register doesn't. */
.q-racing .q-glint{display:none}
.q-racing .q-blurb{
  margin-top:9px;font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(143,160,184,.72);font-weight:400;
}
@keyframes q-fadeup{0%{opacity:0;transform:translateY(14px)}100%{opacity:1;transform:translateY(0)}}

.q-hair{
  position:absolute;left:30px;right:30px;bottom:158px;height:1px;z-index:7;
  background:linear-gradient(90deg,rgba(201,212,226,.36),rgba(201,212,226,0) 62%);
  transform-origin:left;transform:scaleX(0);
  animation:q-hairin 900ms calc(var(--t-title) - 60ms) cubic-bezier(.2,.7,.3,1) both;
}
@keyframes q-hairin{to{transform:scaleX(1)}}
.q-tc{
  position:absolute;right:30px;bottom:38px;z-index:7;
  font-family:var(--font-ui);font-size:9px;letter-spacing:.22em;
  color:rgba(201,212,226,.5);
  animation:q-fadeup 700ms calc(var(--t-title) + 180ms) ease-out both;
}
/* Princess keeps her centred display slam — the hairline and timecode are
   the racing short's grammar, not hers. */
.q-princess .q-hair,.q-princess .q-tc{display:none}

/* The plaque: the design system's own chrome bar, set like a plate bolted
   to the frame rather than a card floating over it. */
/* Left-aligned under the hairline, not centred. The shared keyframes carry
   translateX(-50%) for the princess plate, so racing needs its own or the
   plaque animates itself half its width off the frame. */
.q-racing .q-plate{
  bottom:186px;left:30px;right:auto;transform:none;width:auto;max-width:min(288px,72%);
  animation-name:q-platein-left;
}
@keyframes q-platein-left{
  0%  {opacity:0;transform:translateY(16px)}
  100%{opacity:1;transform:translateY(0)}
}
.q-racing .q-in{
  border-radius:3px;background:rgba(6,10,18,.62);
  border:1px solid rgba(201,212,226,.2);
  box-shadow:0 10px 30px rgba(0,0,0,.7);backdrop-filter:blur(3px);
}
.q-racing .q-in::before{
  content:"";display:block;height:5px;border-radius:2px;margin:-2px 0 10px;
  background:var(--chrome-bar,linear-gradient(180deg,#1C2433,#EEF4FB 33%,#5D6D84 51%,#F2F7FD 77%,#2A3547));
  box-shadow:0 1px 3px rgba(0,0,0,.7);
}
.q-racing .q-row b{font-family:var(--font-ui);font-weight:400;font-size:14px;
  letter-spacing:.1em;transform:none}
.q-racing .q-row span{font-size:9px;letter-spacing:.2em}

/* Nothing on screen invites skipping, so the way out stays quiet until the
   short has actually finished saying what it came to say. */
.q-racing .q-cta{
  bottom:26px;
  background:none;border:1px solid rgba(201,212,226,.34);color:rgba(234,241,250,.9);
  font-family:var(--font-ui);font-weight:400;font-size:10px;letter-spacing:.28em;
  text-transform:uppercase;padding:11px 22px;border-radius:2px;box-shadow:none;
}
.q-racing .q-cta span{transform:none}
.q-racing .q-vignette{
  background:radial-gradient(120% 90% at 50% 50%,transparent 38%,rgba(0,0,0,.76) 100%)}
/* The white flash is arcade. A night frame lifts instead of blowing out. */
.q-racing .q-flash{background:#FFF6DC;animation:q-flash-soft 700ms var(--t-hit) ease-out both}
@keyframes q-flash-soft{0%{opacity:0}10%{opacity:.2}100%{opacity:0}}
/* The scrim moves to the bottom for racing, because the type does. It was a
   band across the upper middle — correct while the lower third of a racing
   short was empty road, and wrong the moment a car was standing there. The
   name, the plaque and the way out all live in the bottom 45% of the frame,
   so that is where they need something to sit against; a lower third over
   the deck is what a broadcast graphic does anyway. */
.q-racing .q-hero{
  top:auto;bottom:0;height:48%;
  background:linear-gradient(180deg,transparent,rgba(3,6,11,.62) 30%,
             rgba(3,6,11,.86) 62%,rgba(3,6,11,.94) 100%)}
.q-racing .q-shake{animation-duration:600ms}

/* ---------- materials ----------
   Lifted from the lookbook's own stylesheet so the shorts are made of the
   same stuff the stills were. */
.r-lamp{
  position:absolute;width:9px;height:9px;border-radius:50%;background:#FFF6DC;
  box-shadow:0 0 10px 5px rgba(255,217,160,.95),0 0 46px 20px rgba(255,154,71,.5),
             0 0 140px 70px rgba(255,106,0,.2);
}
.r-refl{position:absolute;filter:blur(10px);
  background:linear-gradient(180deg,rgba(255,154,71,.6),rgba(255,106,0,.14) 52%,transparent)}
.r-ripple{
  position:absolute;filter:blur(2.5px);
  background:repeating-linear-gradient(180deg,rgba(4,7,13,.72) 0 2px,transparent 2px 6px);
  -webkit-mask-image:radial-gradient(70% 100% at 50% 0,#000 10%,transparent 80%);
  mask-image:radial-gradient(70% 100% at 50% 0,#000 10%,transparent 80%);
}
.r-wet{
  position:absolute;left:0;right:0;bottom:0;
  background:repeating-linear-gradient(180deg,rgba(201,212,226,.05) 0 1px,transparent 1px 9px);
  -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,.9),transparent 70%);
  mask-image:linear-gradient(180deg,rgba(0,0,0,.9),transparent 70%);
}
.r-horizon{position:absolute;left:0;right:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(201,212,226,.3) 30%,rgba(255,154,71,.5) 52%,
             rgba(201,212,226,.18) 74%,transparent)}
/* The pass: the car as six frames of horizontal blur and nothing else. */
.r-smear{
  position:absolute;height:5px;border-radius:99px;filter:blur(3px);
  background:linear-gradient(90deg,transparent,rgba(224,27,36,.15) 12%,rgba(255,60,50,.95) 62%,
             #FFDCD8 88%,transparent);
}
/* Rubber, not a spoked wheel: a near-black disc with a deep inner shadow, so
   the corner of the frame is filled by a mass rather than a graphic. */
.r-tread{
  position:absolute;border-radius:50%;
  background:radial-gradient(circle,#151B26 34%,#0A0E15 62%,#05080E 100%);
  box-shadow:inset 0 0 70px 20px rgba(0,0,0,.95),inset 0 0 0 1px rgba(201,212,226,.06);
}
/* The tread blocks belong on the shoulder of the tyre. Run across the whole
   disc — as they were — a conic gradient converges at the hub and the wheel
   reads as a starburst, which is the single thing it must not look like.
   Masked to the outer band it reads as rubber catching the light. */
.r-tread::before{
  content:"";position:absolute;inset:0;border-radius:50%;
  background:repeating-conic-gradient(from 0deg,rgba(255,255,255,.07) 0deg 2.4deg,transparent 2.4deg 6.4deg);
  -webkit-mask-image:radial-gradient(circle,transparent 64%,#000 77%);
  mask-image:radial-gradient(circle,transparent 64%,#000 77%);
}
.r-rim{position:absolute;height:2px;filter:blur(1.6px);
  background:linear-gradient(90deg,transparent,rgba(255,242,200,.9) 25%,#fff 50%,
             rgba(255,242,200,.85) 72%,transparent)}
.r-bokeh{position:absolute;inset:0;filter:blur(6px)}
/* "Sun crossing behind it so the flare pulses." The sun was static, which
   left the dawn frames feeling like a painting rather than a shot. */
.r-sun{position:absolute;border-radius:50%;background:#FFF8DC;filter:blur(26px);opacity:.95;
  animation:r-flare var(--t-run,5000ms) ease-in-out both}
@keyframes r-flare{
  0%  {opacity:.46;transform:scale(.88)}
  34% {opacity:.9;transform:scale(1)}
  44% {opacity:1;transform:scale(1.16)}    /* the car crosses it */
  56% {opacity:.68;transform:scale(.97)}
  100%{opacity:.9;transform:scale(1.05)}
}
.r-anam{position:absolute;left:-10%;right:-10%;height:3px;filter:blur(4px);
  background:linear-gradient(90deg,transparent,rgba(255,242,200,.75) 30%,#fff 50%,
             rgba(255,242,200,.75) 70%,transparent)}
.r-haze{position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(255,232,190,.05) 30%,rgba(255,214,150,.34) 58%,
             rgba(255,196,120,.12) 70%,transparent)}
.r-sil{position:absolute;background:#080C15}
.r-silo{position:absolute;left:0;right:0;bottom:0;
  background:linear-gradient(180deg,#141B2A,#080C15)}
.r-checker{
  position:absolute;background-size:var(--cs,52px) var(--cs,52px);
  background-image:conic-gradient(#EAF1FA 90deg,#05080E 90deg 180deg,#EAF1FA 180deg 270deg,#05080E 270deg);
}
/* Time Compression — the way into every short. */
.r-streaks{
  position:absolute;inset:0;filter:blur(1.6px);transform:scaleY(.58);
  background:repeating-conic-gradient(from 0deg at 50% 48%,rgba(201,212,226,0) 0deg 3.6deg,
             rgba(201,212,226,.2) 3.6deg 4.2deg,rgba(201,212,226,0) 4.2deg 10deg);
  -webkit-mask-image:radial-gradient(closest-side circle at 50% 48%,transparent 24%,#000 62%,rgba(0,0,0,.3) 94%);
  mask-image:radial-gradient(closest-side circle at 50% 48%,transparent 24%,#000 62%,rgba(0,0,0,.3) 94%);
}
.r-streaks.warm{
  filter:blur(3.4px);transform:scaleY(.44);
  background:repeating-conic-gradient(from 7deg at 50% 48%,rgba(255,154,71,0) 0deg 6deg,
             rgba(255,154,71,.26) 6deg 7.6deg,rgba(255,154,71,0) 7.6deg 19deg);
}
.r-lamps{
  position:absolute;inset:0;filter:blur(6px);
  background:repeating-linear-gradient(90deg,transparent 0 82px,rgba(255,246,220,.3) 82px 92px,transparent 92px 190px);
  -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,.8) 0 8%,transparent 22%);
  mask-image:linear-gradient(180deg,rgba(0,0,0,.8) 0 8%,transparent 22%);
}
.r-tube{position:absolute;border-radius:50%;border:1px solid rgba(201,212,226,.14);
  box-shadow:inset 0 0 60px rgba(27,107,255,.14)}
/* Road seams streaming past under the tunnel — the lookbook's own material,
   and the thing that gives the compression a floor to be travelling over
   instead of an abstract field of light. */
.r-seams{
  position:absolute;left:0;right:0;bottom:0;height:34%;filter:blur(2px);
  background:repeating-linear-gradient(90deg,rgba(201,212,226,.22) 0 26px,transparent 26px 120px);
  -webkit-mask-image:linear-gradient(180deg,transparent,#000);
  mask-image:linear-gradient(180deg,transparent,#000);
}
/* The car, when a frame wants a body rather than a fragment: a blurred
   silhouette with one hot line along the roof. Straight from the lookbook's
   dawn hero frame — still not a portrait, just a shape the light falls on. */
.r-car{
  position:absolute;filter:blur(1.4px);
  background:linear-gradient(180deg,#1A2130,#05080E 60%);
  clip-path:polygon(0 100%,4% 62%,14% 40%,30% 26%,52% 20%,70% 24%,86% 40%,96% 66%,100% 100%);
}

/* The entry: "one continuous forward dolly, accelerating, then a hard brake
   at the line — 0.4s of near-freeze while the streaks keep bleeding."

   The old entry just eased outward at a constant swell, which is a zoom, not
   a dolly. The brake is the whole character of the move: it accelerates for
   1.1s, stops almost dead, and hangs there for four tenths of a second
   before releasing. The streaks keep rotating straight through the freeze —
   that contrast between a stopped camera and a still-bleeding frame is what
   makes it read as speed rather than as a transition wipe. */
.r-entry{position:absolute;inset:0;z-index:4;animation:r-entry 2400ms both}
@keyframes r-entry{
  0%  {opacity:1;transform:scale(.5);   animation-timing-function:cubic-bezier(.6,0,.9,.28)}
  52% {opacity:1;transform:scale(1.98); animation-timing-function:cubic-bezier(.02,.86,.12,1)}
  70% {opacity:.94;transform:scale(2.13)}
  84% {opacity:.46;transform:scale(2.2); animation-timing-function:ease-in}
  100%{opacity:0;transform:scale(2.52)}
}
/* Kept linear and running past the brake — the bleed is the point. */
.r-entry .r-streaks{animation:r-spin 2400ms linear both}
@keyframes r-spin{to{transform:scaleY(.58) rotate(14deg)}}
.r-entry .r-seams{animation:r-seamrun 2400ms cubic-bezier(.6,0,.9,.28) both}
@keyframes r-seamrun{to{background-position-x:-1200px}}

/* ---------- shared racing grounds ---------- */
/* Night: asphalt near-black holds the frame, sodium orange is the only warm
   note, blue haze keeps the shadows from going muddy. */
.r-night{position:absolute;inset:0;
  background:radial-gradient(90% 60% at 26% 40%,rgba(255,106,0,.13),transparent 62%),
             radial-gradient(120% 70% at 62% 38%,rgba(27,107,255,.11),transparent 66%),
             linear-gradient(180deg,#03060B 0%,#070C16 34%,#0C1322 41%,#070C15 62%,#04070E 100%)}
.r-bok{background:radial-gradient(circle 22px at 74% 30%,rgba(255,154,71,.3),transparent 70%),
                  radial-gradient(circle 14px at 84% 34%,rgba(201,212,226,.18),transparent 70%),
                  radial-gradient(circle 30px at 12% 33%,rgba(27,107,255,.16),transparent 72%)}
/* Dawn: steel-blue shadow, sand and warm haze, flame-yellow sun. */
.r-dawn{position:absolute;inset:0;
  background:radial-gradient(70% 52% at 30% 62%,rgba(255,210,30,.5),transparent 62%),
             radial-gradient(120% 80% at 40% 66%,rgba(255,106,0,.42),transparent 70%),
             linear-gradient(180deg,#1B263C 0%,#3A4356 26%,#8A7A70 52%,#C9A177 62%,#6E6357 74%,#2B3345 100%)}
/* The pass itself: six frames of horizontal blur, and the reflection
   arriving late — the car is gone before the water catches up.

   "At the line, time drops to about a third speed." That is the lookbook's
   one explicit motion note and it was the thing most missing here: the smear
   used to cross at a constant clip and vanish. Now it covers 165% of its
   travel in the first 40% of the clock and the remaining 100% in the other
   60% — arithmetically a third speed, and the deceleration is carried by
   the easing rather than by a second animation, so there is nothing to keep
   in sync. The frame goes quiet exactly as the car reaches the middle. */
.r-pass{left:-40%;width:52%;opacity:0;
  animation:r-pass 1400ms var(--d,4400ms) both}
@keyframes r-pass{
  0%  {opacity:0;transform:translateX(0) scaleX(.45);
       animation-timing-function:cubic-bezier(.34,0,.72,1)}
  12% {opacity:1}
  40% {opacity:1;transform:translateX(165%) scaleX(1.45);
       animation-timing-function:cubic-bezier(0,.62,.2,1)}
  100%{opacity:.34;transform:translateX(268%) scaleX(1.06)}
}
.r-pass2{opacity:.55;height:3px;filter:blur(5px)}
.r-passrefl{filter:blur(20px);opacity:0;
  background:radial-gradient(58% 100% at 52% 0,rgba(224,27,36,.5),transparent 74%);
  animation:r-lag 1900ms var(--d,4720ms) ease-out both}
@keyframes r-lag{0%{opacity:0}22%{opacity:1}100%{opacity:.14}}

/* Camera: locked-off, then one slow push. No whip pans.
   The racing variant parks: it settles by the pass and then holds dead still
   for half a second while the smear crawls and the reflection blooms. A
   camera that keeps drifting through the money shot is what separates a
   screensaver from footage. */
.q-racing .q-cam{animation-name:q-cam-race}
@keyframes q-cam-race{
  0%  {transform:scale(var(--cam-from,1.14)) translate(var(--cam-fx,0),var(--cam-fy,0))}
  30% {transform:scale(calc(var(--cam-from,1.14) - .07)) translate(0,0)}
  42% {transform:scale(var(--cam-mid,1.01)) translate(0,0)}
  52% {transform:scale(var(--cam-mid,1.01)) translate(0,0)}
  100%{transform:scale(var(--cam-to,1.05)) translate(var(--cam-tx,0),var(--cam-ty,0))}
}
.q-racing.q-s1,.q-racing.q-s4,.q-racing.q-s7{--cam-from:1.12;--cam-mid:1.02;--cam-to:1.06;--cam-origin:38% 62%}
.q-racing.q-s2,.q-racing.q-s5,.q-racing.q-s6,.q-racing.q-s8{--cam-from:1.14;--cam-mid:1.01;--cam-to:1.05;--cam-origin:50% 48%}
.q-racing.q-s3{--cam-from:1.5;--cam-mid:1.04;--cam-to:1;--cam-origin:50% 48%}

/* ---------- world, second pass ----------
   The materials above were built for shorts with no car in them: a smear
   needs a gradient to cross, and that is all it needs. A car standing on
   the same gradient reads as a decal on a poster, so these are the pieces
   that give it somewhere to be — a wall it passes, a gantry it drives
   under, a stand that watches it, kerbing at the edge of the road. */

/* Pit wall and garage mouths, streaming past on the left. The garage light
   is the only interior light in the set and it wants to look fluorescent
   and slightly sick next to the sodium outside. */
.r-pitwall{position:absolute;left:0;top:26%;width:34%;bottom:24%;z-index:3;
  background:linear-gradient(90deg,#111A29,#0A1220 62%,#070D18);
  clip-path:polygon(0 0,100% 22%,100% 78%,0 100%)}
.r-garage{position:absolute;left:2%;width:9%;top:34%;height:26%;z-index:4;
  background:linear-gradient(180deg,rgba(214,238,255,.5),rgba(140,186,232,.16) 60%,transparent);
  box-shadow:0 0 30px 12px rgba(150,200,255,.16);
  transform:skewY(6deg);opacity:.8;
  animation:r-garagerun var(--t-run,10000ms) linear both}
@keyframes r-garagerun{from{transform:skewY(6deg) translateX(0)}
  to{transform:skewY(6deg) translateX(-260%)}}

/* The gantry: a banner on two legs, arriving overhead. This is where the
   board says the words land — the car drives *through* the verse rather
   than having it appear in front of the car. */
.r-gantry2{position:absolute;left:14%;right:14%;top:8%;height:9%;z-index:6;
  background:linear-gradient(180deg,#0C1524,#060B14);
  border-top:1px solid rgba(201,212,226,.24);
  box-shadow:0 8px 26px rgba(0,0,0,.75);
  transform-origin:50% 0;
  animation:r-gantrycome var(--t-run,10000ms) cubic-bezier(.5,0,.86,.4) both}
.r-gantry2::after{content:"";position:absolute;inset:0;opacity:.34;filter:blur(.5px);
  background-size:14px 14px;
  background-image:conic-gradient(#EAF1FA 90deg,#05080E 90deg 180deg,#EAF1FA 180deg 270deg,#05080E 270deg)}
.r-gleg{position:absolute;top:8%;width:5px;height:16%;z-index:6;
  background:linear-gradient(90deg,#18202F,#0A1120);
  animation:r-gantrycome var(--t-run,10000ms) cubic-bezier(.5,0,.86,.4) both}
@keyframes r-gantrycome{
  0%  {transform:translateY(0) scale(.34);opacity:0}
  10% {opacity:.9}
  44% {transform:translateY(4%) scale(1);opacity:1}
  /* Over the camera on the hit, and then actually gone. It used to hold a
     little opacity out to 3.6×, where a banner is wider than the frame and
     stops being a gantry passing overhead — it becomes a checkerboard wall
     hanging over the whole aftermath. */
  60% {transform:translateY(-24%) scale(2);opacity:.4}
  72% {transform:translateY(-36%) scale(2.5);opacity:0}
  100%{transform:translateY(-42%) scale(2.7);opacity:0}
}

/* Grandstand, dissolved. Never in focus — the moment a crowd resolves into
   people the frame stops being about the car. */
.r-stand2{position:absolute;left:0;right:0;top:18%;height:22%;z-index:2;filter:blur(5px);opacity:.7;
  background:repeating-linear-gradient(90deg,rgba(150,175,215,.13) 0 3px,transparent 3px 8px),
             repeating-linear-gradient(180deg,rgba(120,150,195,.15) 0 4px,transparent 4px 11px),
             linear-gradient(180deg,#0B1524,#101C30 70%,#0A121F);
  clip-path:polygon(0 34%,100% 8%,100% 100%,0 100%)}

/* Headlight bloom that grows with the approach: the flare has to gain on
   the car, or a head-on shot just looks like a car getting bigger. */
.r-hlbloom{position:absolute;left:50%;top:52%;width:70%;height:34%;z-index:37;
  transform:translate(-50%,-50%);pointer-events:none;filter:blur(26px);
  background:radial-gradient(circle,rgba(255,246,214,.6),rgba(255,206,132,.22) 46%,transparent 74%);
  animation:r-hlgrow var(--t-run,10000ms) ease-in both}
@keyframes r-hlgrow{
  0%  {opacity:.2;transform:translate(-50%,-50%) scale(.3)}
  44% {opacity:.8;transform:translate(-50%,-50%) scale(.86)}
  52% {opacity:1;transform:translate(-50%,-50%) scale(1.16)}
  70% {opacity:.72;transform:translate(-50%,-50%) scale(1.04)}
  100%{opacity:.6;transform:translate(-50%,-50%) scale(1.1)}
}

/* ============================================================
   THE CORNER SEQUENCE
   One stretch of circuit seen from a fixed tower position, for the pass.
   Everything here is a stroke on the board's own centreline path — road,
   kerbs, racing line and centre dashes are the same `d` at six widths — so
   the track cannot come apart the way four separately-placed rings could,
   and a change to the path on the board moves all of it at once.

   The SVG is rotated a quarter turn by its own <g> so the board's landscape
   corner runs down a portrait frame; passXY() in quest-scenes.js does the
   identical arithmetic for the cars, which are HTML and cannot ride inside
   the SVG. Those two have to agree exactly — if you touch one, touch both.

   Sized from --u rather than percentages: the viewBox has a fixed aspect,
   and a percentage height would letterbox it the moment the frame is a
   different shape, quietly moving every car off the road.
   ============================================================ */
/* .r-seq, not .r-pass: that name is already the taillight smear of a car
   up the road (see pass() in quest-scenes.js), and it carries a scaleX
   animation that silently squashed this whole circuit to 45% of its width. */
.r-seq{position:absolute;inset:0;z-index:2;overflow:hidden;pointer-events:none}
/* Late afternoon. High enough that nothing casts a long shadow of its own —
   see the note on .ov in cars.css for why the light has to be this way. */
.r-seq .sky{position:absolute;inset:0;
  background:radial-gradient(120% 80% at 78% 8%,rgba(255,198,120,.2),transparent 60%),
             linear-gradient(158deg,#2A2114 0%,#1A1710 42%,#0E0F10 100%)}
.r-seq svg{position:absolute;left:0;top:calc(var(--u) * 8px);
  width:calc(var(--u) * 360px);height:calc(var(--u) * 518px);
  /* Both ends of the path stop inside the frame, and both stop near the
     top, so one fade at the top dissolves the entry and the exit together.
     Without it the circuit reads as a road that simply ends. */
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 15%);
  mask-image:linear-gradient(180deg,transparent 0,#000 15%)}
.r-seq path{fill:none}
/* Gravel apron, wider than everything, under everything. */
.pt-runoff{stroke:#4E4430;stroke-width:250;stroke-linecap:round;stroke-linejoin:round;
  opacity:.9}
/* Kerbing: the road's own path stroked nine units wider on each side and
   dashed, so the segments stay square to the track all the way round. Two
   strokes at one period, offset by half of it, make the red and the white —
   a single gradient would shear them through every curve. */
.pt-kerbr{stroke:#8E2A26;stroke-width:168;stroke-dasharray:26 26;opacity:.42}
.pt-kerbw{stroke:#B4AC9E;stroke-width:168;stroke-dasharray:26 26;stroke-dashoffset:26;
  opacity:.42}
.pt-road{stroke:#32353B;stroke-width:150;stroke-linecap:round;stroke-linejoin:round}
/* The line worn dark into the tarmac where every car has been. It sits a
   little inside the road so there is dirty track outside it for the
   defender to get pushed onto. */
.pt-line{stroke:rgba(12,13,16,.28);stroke-width:44;filter:blur(3px)}
.pt-dash{stroke:rgba(236,244,255,.28);stroke-width:3.5;stroke-dasharray:24 46}
/* Trackside furniture, and the only shadows in the short that carry a
   direction — they are fixed in the world, so they can say where the sun is
   without the cars having to. */
.r-seq .rake{position:absolute;left:-10%;right:-10%;top:-10%;bottom:-10%;
  background:repeating-linear-gradient(114deg,rgba(8,7,6,.3) 0 26px,transparent 26px 96px);
  filter:blur(5px);opacity:.5}
.r-seq .vig{position:absolute;inset:0;
  background:radial-gradient(122% 86% at 46% 40%,transparent 36%,rgba(0,0,0,.64) 100%)}

/* Barrier and catch fence, hard in the foreground and hard out of focus.
   This is what says the camera is a person standing at the side of a track
   rather than a drone: you are looking *through* something. */
.r-barrier{position:absolute;left:-4%;right:-4%;bottom:6%;height:5%;z-index:42;
  filter:blur(4.5px);opacity:.5;
  background:linear-gradient(180deg,rgba(196,206,220,.5) 0 30%,rgba(90,100,116,.6) 40%,
             rgba(24,30,40,.85) 74%,rgba(10,14,20,.9))}
.r-fence{position:absolute;left:-4%;right:-4%;bottom:10%;height:15%;z-index:42;
  filter:blur(3.6px);opacity:.16;
  background:repeating-linear-gradient(74deg,rgba(214,226,242,.5) 0 1px,transparent 1px 15px),
             repeating-linear-gradient(-74deg,rgba(214,226,242,.5) 0 1px,transparent 1px 15px)}

/* The streaked world for the tracking profile — trackside furniture,
   barriers, marshal posts and lamps, all of it smeared into horizontal
   mush by a lens that is keeping pace with the car. Two layers at
   different periods, because one period reads as wallpaper. It shares the
   road's easing exactly: everything in that short decelerates together or
   the shot comes apart. */
.r-mush{position:absolute;left:-20%;right:-20%;z-index:3;pointer-events:none;
  filter:blur(3.4px);opacity:.62;
  background:repeating-linear-gradient(90deg,rgba(201,212,226,.18) 0 40px,transparent 40px 150px),
             repeating-linear-gradient(90deg,rgba(255,154,71,.22) 0 14px,transparent 14px 260px);
  animation:r-mushrun var(--t-run,10000ms) both}
@keyframes r-mushrun{
  0%  {background-position:0 0,0 0;            animation-timing-function:linear}
  30% {background-position:-2500px 0,-3600px 0;animation-timing-function:cubic-bezier(.18,.66,.3,1)}
  44% {background-position:-3380px 0,-4880px 0;animation-timing-function:cubic-bezier(.2,.5,.4,1)}
  100%{background-position:-3840px 0,-5550px 0}
}
/* The line arriving at the car rather than the car arriving at the line —
   which is what a tracking camera actually shows you. Same curve again. */
/* Upright, so its mask has to fade top and bottom rather than left and
   right — .r-linepaint's own mask is built for a band lying along the road,
   and reusing it here would fade the line out at exactly the height the car
   is sitting at. */
.r-lineswoop{opacity:.42;
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 18%,#000 84%,transparent);
  mask-image:linear-gradient(180deg,transparent,#000 18%,#000 84%,transparent);
  animation:r-lineswoop var(--t-run,10000ms) both}
/* The line has one job the rest of the world does not: it has to arrive on
   the hit, not before it and not after. So it rides the same shape but its
   keyframe at 44% is the nose, exactly. */
@keyframes r-lineswoop{
  0%  {transform:translateX(560%); animation-timing-function:linear}
  30% {transform:translateX(190%); animation-timing-function:cubic-bezier(.18,.66,.3,1)}
  44% {transform:translateX(0);    animation-timing-function:cubic-bezier(.2,.5,.4,1)}
  100%{transform:translateX(-44%)}
}

/* The finish line as paint on the road, not as a graphic over it. */
.r-linepaint{opacity:.26;filter:blur(2.2px);z-index:4;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent)}

/* A marshal's flag at the line — waved, on a pole, in the frame. The old
   full-width sweep was the lens wearing a checkerboard; this belongs to
   somebody standing there. */
.r-flagwave{z-index:43;opacity:0;transform-origin:0 0;filter:blur(1.6px) brightness(.72);
  /* Cloth, not a swatch: the trailing edge falls away and the whole thing
     is cut on a curve, so a skew reads as fabric moving rather than as a
     rectangle being sheared. */
  clip-path:polygon(0 0,100% 6%,96% 74%,74% 96%,40% 82%,0 100%);
  animation:r-wave var(--t-run,10000ms) both}
@keyframes r-wave{
  0%,36%{opacity:0;transform:rotate(-8deg) skewY(0)}
  40%   {opacity:.9;transform:rotate(-4deg) skewY(5deg)}
  46%   {opacity:1;transform:rotate(3deg) skewY(-6deg)}
  52%   {opacity:1;transform:rotate(-5deg) skewY(6deg)}
  58%   {opacity:1;transform:rotate(2deg) skewY(-4deg)}
  66%   {opacity:.9;transform:rotate(-3deg) skewY(4deg)}
  100%  {opacity:.8;transform:rotate(0deg) skewY(1deg)}
}
.r-flagpole{position:absolute;left:4%;top:28%;width:3px;height:22%;z-index:42;
  background:linear-gradient(90deg,#39424F,#0E131B);opacity:0;
  animation:r-fadein 400ms calc(var(--t-hit) - 800ms) ease-out both}
@keyframes r-fadein{to{opacity:.9}}

/* Heat haze off the deck, and the exhaust shimmer behind a car under load.
   Cheap, and it is most of what separates "rendered" from "photographed". */
.r-shimmer{position:absolute;z-index:41;pointer-events:none;filter:blur(7px);opacity:.5;
  background:repeating-linear-gradient(180deg,rgba(255,214,150,.14) 0 3px,transparent 3px 9px);
  animation:r-shim 1100ms ease-in-out infinite alternate}
@keyframes r-shim{from{transform:translateY(0) scaleY(1)}to{transform:translateY(-7px) scaleY(1.14)}}

/* ---- 1 · Pit Lane ---- */
/* The garage mouth: one soft rectangle of light behind everything, which is
   what makes the crew read as backlit rather than as grey figures on a grey
   floor. It sits under the deck deliberately — light spilling *out* of the
   garage lands on the lane, so the box floor in front of the mouth is the
   brightest part of the ground and everything nearer the camera falls off. */
.pitmouth{position:absolute;z-index:1;pointer-events:none;filter:blur(16px);
  background:radial-gradient(70% 100% at 50% 100%,rgba(226,240,255,.5),
    rgba(150,186,232,.16) 52%,transparent 78%)}
/* ...and the falloff toward camera. Without it the near crew sit in the
   same value as the far ones and the depth collapses. */
.pitfall{position:absolute;left:0;right:0;top:38%;bottom:0;z-index:43;pointer-events:none;
  background:linear-gradient(180deg,transparent 0 34%,rgba(4,6,12,.5) 82%,rgba(2,4,8,.72))}
.r-cold{opacity:.9;box-shadow:inset 0 0 30px rgba(0,0,0,.95),-16px 0 46px rgba(255,214,150,.3);
  animation:r-creep var(--t-run,5000ms) linear both}
@keyframes r-creep{from{transform:rotate(0)}to{transform:rotate(104deg)}}
.r-warm{opacity:0;animation:r-ignite 900ms 4360ms ease-out both}
@keyframes r-ignite{0%{opacity:0;transform:scaleX(.3)}40%{opacity:1}100%{opacity:.85;transform:scaleX(1)}}

/* ---- 2 · Practice Laps ---- */
.r-roll{opacity:.55;animation:r-creep var(--t-run,5000ms) linear both}

/* ---- 3 · Qualifying ---- */
/* Onboard. The world rolls, the cockpit does not — the camera is bolted to
   the car, so through a corner it is the horizon that tips and never the
   driver. Rolling the cockpit instead is the classic tell of a fake
   onboard, and it is the one thing this short cannot get wrong.

   No other short in the set moves the horizon at all, which is what makes
   this camera read as a different place rather than a different angle. */
.r-onb{position:absolute;inset:0;transform-origin:50% 64%;
  animation:r-onbroll var(--t-run,10000ms) both}
@keyframes r-onbroll{
  0%  {transform:rotate(0) translateX(0);
       animation-timing-function:cubic-bezier(.42,0,.5,1)}
  20% {transform:rotate(1.4deg) translateX(1.2%)}
  /* turn-in, then the apex on the hit: fully committed, kerb under the
     left-hand wheels, and it stays there for a beat before it unwinds */
  36% {transform:rotate(-6.4deg) translateX(-4%);
       animation-timing-function:cubic-bezier(.3,0,.4,1)}
  44% {transform:rotate(-8.2deg) translateX(-5.6%)}
  60% {transform:rotate(-3.2deg) translateX(-2%)}
  100%{transform:rotate(.5deg) translateX(0)}
}
/* The backdrop has to be bigger than the frame or the roll swings a corner
   of the world out and shows the bare stage behind it. */
.r-onb .r-night{inset:-18%}
/* The apex kerb, coming under the left-hand wheels and gone. Skewed because
   it is a strip of kerbing seen from a car that is not square to it. */
.r-apexkerb{position:absolute;left:-30%;width:74%;top:62%;height:9%;z-index:4;
  background:repeating-linear-gradient(90deg,rgba(206,42,42,.8) 0 26px,
    rgba(226,232,242,.8) 26px 52px);
  filter:blur(1.6px);opacity:0;
  animation:r-apexhit var(--t-run,10000ms) both}
@keyframes r-apexhit{
  0%,28%{opacity:0;transform:translate(66%,22%) skewY(-6deg) scale(.5)}
  38%   {opacity:.85}
  44%   {opacity:1;transform:translate(-4%,0) skewY(-6deg) scale(1.14)}
  58%   {opacity:.6;transform:translate(-42%,9%) skewY(-6deg) scale(1.42)}
  72%,100%{opacity:0;transform:translate(-74%,17%) skewY(-6deg) scale(1.7)}
}
/* Speed at the edges of the lens, and a floor under the type. */
.r-onbvig{position:absolute;inset:0;z-index:5;pointer-events:none;
  background:radial-gradient(56% 46% at 50% 44%,transparent 38%,rgba(4,7,13,.56) 100%)}
/* The cockpit. Silhouette with one lit edge and no interior detail — at
   phone size an instrument binnacle is four grey pixels and a lie. */
.r-cockpit{position:absolute;inset:0;z-index:46;pointer-events:none}
.r-cockpit .roofl{position:absolute;left:0;right:0;top:0;height:14%;
  background:linear-gradient(180deg,#04060B 0 60%,#080D15 84%,transparent);
  box-shadow:inset 0 -1px 0 rgba(214,232,255,.13)}
/* Narrow and steeply raked. Wide upright pillars stop reading as a
   windscreen surround and start reading as tower blocks either side of the
   road. */
.r-cockpit .pil{position:absolute;top:6%;width:11%;height:46%;background:#04060B;
  box-shadow:inset 0 0 0 1px rgba(214,232,255,.07)}
.r-cockpit .pil.l{left:0;clip-path:polygon(0 0,100% 0,34% 100%,0 100%)}
.r-cockpit .pil.r{right:0;clip-path:polygon(0 0,100% 0,100% 100%,66% 100%)}
.r-cockpit .mir{position:absolute;top:19%;width:7%;height:4.5%;border-radius:2px;
  background:linear-gradient(180deg,#121926,#04060B)}
/* The bonnet is seen almost edge-on, so it is mostly dark with the crown
   catching light — a filled red arc reads as a hill in front of the car.
   The one hint of hero red at the leading edge is all that is needed to say
   whose car you are sitting in. */
.r-cockpit .bonnet{position:absolute;left:-10%;right:-10%;bottom:0;height:17%;
  border-radius:46% 46% 0 0 / 46% 46% 0 0;
  background:linear-gradient(180deg,#4A080C 0 4%,#1A0407 15%,#0A0D14 54%,#04060B);
  box-shadow:inset 0 2px 0 rgba(255,150,120,.34)}
/* The purple sector: the one moment colour is allowed to mean something. */
.r-purple{
  position:absolute;left:50%;top:31%;transform:translate(-50%,-50%);z-index:5;
  display:flex;align-items:baseline;gap:12px;padding:9px 18px;border-radius:2px;
  background:rgba(76,29,149,.42);border:1px solid rgba(167,124,255,.6);
  box-shadow:0 0 30px rgba(140,80,255,.5);opacity:0;
  animation:r-sector 900ms var(--t-hit) cubic-bezier(.2,1.4,.4,1) both;
}
.r-purple span{font-size:9px;letter-spacing:.28em;color:rgba(220,200,255,.8)}
.r-purple b{font-family:var(--font-ui);font-size:19px;font-weight:400;letter-spacing:.1em;color:#E9DEFF}
@keyframes r-sector{0%{opacity:0;transform:translate(-50%,-50%) scale(1.6)}
  60%{opacity:1;transform:translate(-50%,-50%) scale(.96)}100%{opacity:1;transform:translate(-50%,-50%) scale(1)}}
.r-bloom{position:absolute;inset:0;background:radial-gradient(closest-side circle at 50% 48%,
  #FFFDF6 0%,rgba(255,210,30,.7) 18%,rgba(255,106,0,.3) 34%,transparent 72%);
  opacity:0;animation:r-bloomin 1300ms calc(var(--t-hit) - 20ms) ease-out both}
@keyframes r-bloomin{0%{opacity:0;transform:scale(.2)}22%{opacity:.9}100%{opacity:0;transform:scale(1.7)}}

/* ---- 4 · Club Round ---- */
.r-gantry{position:absolute;left:50%;top:16%;transform:translateX(-50%);display:flex;gap:16px;z-index:3}
.r-startlight{
  width:15px;height:15px;border-radius:50%;background:#1A0A0A;
  box-shadow:inset 0 0 6px rgba(0,0,0,.9);
  animation:r-lights 3600ms calc(1500ms + var(--i) * 220ms) ease-out both;
}
@keyframes r-lights{
  0%  {background:#1A0A0A;box-shadow:inset 0 0 6px rgba(0,0,0,.9)}
  18% {background:#FF2A20;box-shadow:0 0 16px 5px rgba(224,27,36,.8)}
  54% {background:#FF2A20;box-shadow:0 0 16px 5px rgba(224,27,36,.8)}
  56% {background:#1A0A0A;box-shadow:inset 0 0 6px rgba(0,0,0,.9)}
  100%{background:#1A0A0A;box-shadow:inset 0 0 6px rgba(0,0,0,.9)}
}
.r-launch{opacity:0;animation:r-ignite 700ms 4340ms ease-out both}

/* ---- 5 · Night Circuit ---- */
.r-rain{position:absolute;inset:0;z-index:4;pointer-events:none}
.r-drop{
  position:absolute;width:1.5px;height:22px;border-radius:2px;
  background:linear-gradient(180deg,transparent,rgba(201,212,226,.55));
  left:calc(3% + var(--i) * 7%);top:-10%;
  animation:r-rainfall 900ms calc(var(--i) * 90ms) linear infinite;
}
@keyframes r-rainfall{to{transform:translate(-18px,130vh)}}

/* ---- 6 · Regional Final ---- */
/* The rival goes a beat late and slightly quicker — he is losing the drag
   to the line, which is the only story two smears can tell. */
.r-rival{left:-40%;width:46%;opacity:0;
  background:linear-gradient(90deg,transparent,rgba(27,107,255,.2) 12%,rgba(120,180,255,.9) 62%,#EAF1FA 88%,transparent);
  animation:r-pass 1240ms 4580ms both}
.r-rivalrefl{filter:blur(18px);opacity:0;
  background:radial-gradient(58% 100% at 52% 0,rgba(27,107,255,.45),transparent 74%);
  animation:r-lag 1400ms 4980ms ease-out both}
.r-lunge{opacity:.6;animation:r-creep var(--t-run,5000ms) linear both}

/* ---- 7 · The Grand Prix ---- */
.r-gp{background:radial-gradient(80% 46% at 50% 68%,rgba(255,210,30,.42),transparent 64%),
      linear-gradient(180deg,#0A0F1A 0%,#1B263C 30%,#6E6357 58%,#C9A177 68%,#3A4356 100%)}
.r-stand{position:absolute;left:0;right:0;top:24%;height:26%;z-index:2;
  background:linear-gradient(180deg,#080C15,#0E1524);overflow:hidden}
.r-bulb{
  position:absolute;width:4px;height:4px;border-radius:50%;background:#FFF6DC;
  left:calc(2% + var(--i) * 3.8%);top:calc(8% + var(--m4) * 22%);
  opacity:0;box-shadow:0 0 8px 3px rgba(255,246,220,.9);
  animation:r-flashbulb 420ms calc(900ms + var(--i) * 95ms) ease-out infinite;
}
@keyframes r-flashbulb{0%{opacity:0}12%{opacity:1}100%{opacity:0}}

/* ---- 8 · Champion ---- */
/* Beat 04 — the flag, in blur, arriving after the pass and just under the
   title. It used to fire at 1.15s, which put the finish line before the car
   had even crossed the frame. */
.r-flagblur{filter:blur(9px);opacity:0;transform:skewY(-3deg);
  animation:r-flag 1500ms 4880ms ease-out both}
.r-flagsharp{filter:blur(2px);opacity:0;transform:skewY(-3deg);
  animation:r-flag 1500ms 5000ms ease-out both}
@keyframes r-flag{0%{opacity:0;transform:skewY(-3deg) translateX(60%)}
  30%{opacity:.42}70%{opacity:.34}100%{opacity:0;transform:skewY(-3deg) translateX(-40%)}}
.r-burnout{opacity:.7;animation:r-spinup var(--t-run,5000ms) cubic-bezier(.2,.8,.3,1) both}
@keyframes r-spinup{from{transform:rotate(0)}to{transform:rotate(2160deg)}}

/* ============================================================
   PRINCESS 3-8
   One light source per scene and a slow camera, borrowed from the racing
   lookbook; the drawn pastel vocabulary is hers and stays.
   ============================================================ */

/* ---- 3 · Whispering Woods ---- */
.q-princess.q-s3{--cam-from:1.26;--cam-mid:1.03;--cam-to:1;--cam-origin:50% 66%}
.f-night{position:absolute;inset:0;
  background:radial-gradient(60% 40% at 50% 12%,rgba(201,174,239,.5),transparent 70%),
             linear-gradient(180deg,#2B1F3D 0%,#3E2B52 46%,#26324A 100%)}
.f-moon{position:absolute;left:50%;top:8%;width:74px;height:74px;margin-left:-37px;border-radius:50%;
  background:radial-gradient(circle,#FFFDF0,#FBD86B 70%,transparent);
  box-shadow:0 0 60px 22px rgba(251,216,107,.35);
  animation:f-glow 3000ms 200ms ease-out both}
@keyframes f-glow{0%{opacity:.35;transform:scale(.9)}100%{opacity:1;transform:scale(1)}}
/* Two speeds of parallax under her: the canopy drifts, the floor slides
   faster because it is nearer. Scene 3 used to replay the Meadow's run
   keyframe verbatim — same traverse, same cadence, no depth — which made
   the second forest read as the first field with the lights off. */
.f-canopy{position:absolute;inset:0 -10%;animation:f-canopypar 5000ms cubic-bezier(.24,.5,.4,1) both}
@keyframes f-canopypar{from{transform:translateX(5%)}to{transform:translateX(-6%)}}
.f-tree{
  position:absolute;bottom:16%;width:calc(30px + var(--m3) * 16px);
  height:calc(46% + var(--m4) * 9%);
  left:calc(-4% + var(--i) * 12%);
  border-radius:44% 44% 6% 6%;
  background:linear-gradient(180deg,#241736,#150E22);
  box-shadow:0 0 0 3px rgba(110,74,87,.5);
  transform-origin:50% 100%;
  animation:f-part 1800ms calc(500ms + var(--i) * 80ms) cubic-bezier(.3,.9,.4,1) both;
}
/* The trees lean away as she comes through, deepest first. */
@keyframes f-part{
  0%  {transform:rotate(0) scaleY(1)}
  100%{transform:rotate(calc((var(--m2) * 2 - 1) * 7deg)) scaleY(1.04)}
}
.f-floor{position:absolute;left:-14%;right:-14%;bottom:0;height:20%;
  background:linear-gradient(180deg,#2E2140,#1A1228);
  animation:f-floorpar 5000ms cubic-bezier(.24,.5,.4,1) both}
@keyframes f-floorpar{from{transform:translateX(9%)}to{transform:translateX(-11%)}}
/* She grows a little as she crosses — passing nearer the camera than she
   did in the Meadow, so the same traverse reads as a different place. */
.q-princess.q-s3 .q-rider{--ry:15%;--rw:100px}
.f-uni{transform:translateX(44%) scale(1);animation:f-path 5000ms cubic-bezier(.24,.5,.4,1) both}
@keyframes f-path{
  0%  {transform:translateX(-28%) scale(.88)}
  44% {transform:translateX(44%) scale(1)}
  100%{transform:translateX(118%) scale(1.05)}
}

/* ---- 4 · The Rope Bridge ----
   One deck line at 58% governs the whole scene: the ledges rise to it, the
   planks sit on it, the handrail hangs above it and she walks along it.
   Everything below is chasm. */
.q-princess.q-s4{--cam-from:1.12;--cam-mid:1.18;--cam-to:1.06;--cam-origin:50% 56%}
.b-sky{position:absolute;inset:0;
  background:linear-gradient(180deg,#BFE1F2 0%,#DCE9F2 30%,#C6D5DE 52%,#93A6B4 100%)}
.b-far{position:absolute;left:-6%;right:-6%;top:24%;height:20%;border-radius:50% 50% 0 0;
  background:#9DAFBC;opacity:.5}
/* Chasm mist: it has to read as depth, not as a white wall, so it stays
   under 55% opacity and sits behind the bridge. */
.b-mist{position:absolute;left:0;right:0;top:58%;bottom:0;z-index:1;
  background:linear-gradient(180deg,rgba(255,255,255,0),rgba(232,240,246,.55) 34%,
             rgba(176,192,205,.7) 72%,rgba(120,138,154,.8));
  animation:b-roll 5000ms linear both}
@keyframes b-roll{from{transform:translateX(-3%)}to{transform:translateX(3%)}}
.b-ledge{position:absolute;top:58%;bottom:0;width:15%;z-index:3;
  background:linear-gradient(180deg,#7E6A57,#5A4A3C);
  box-shadow:inset 0 4px 0 rgba(255,255,255,.16)}
.b-l{left:0;border-radius:0 8px 0 0}
.b-r{right:0;border-radius:8px 0 0 0}
.b-rope{position:absolute;left:13%;right:13%;height:3px;border-radius:2px;background:#8B6B4F;z-index:3}
.b-top{top:42%}
.b-bottom{top:58%}
.b-planks{position:absolute;left:13%;right:13%;top:58%;height:13px;z-index:3}
.b-plank{
  position:absolute;top:0;width:6%;height:12px;border-radius:2px;
  left:calc(var(--i) * 8.2%);
  background:#A9784F;box-shadow:0 2px 0 rgba(110,74,87,.45);
  transform-origin:50% 0;opacity:.5;
  animation:b-light 520ms calc(480ms + var(--i) * 92ms) ease-out both;
}
/* Each plank warms and dips as her weight lands on it. */
@keyframes b-light{
  0%  {opacity:.5;transform:translateY(0);background:#A9784F}
  42% {opacity:1;transform:translateY(6px);background:#FBD86B}
  100%{opacity:1;transform:translateY(1px);background:#C99A63}
}
/* The handrail sags where she is, then recovers. */
.b-top{animation:b-sag 5000ms ease-in-out both}
@keyframes b-sag{0%{transform:translateY(0)}46%{transform:translateY(7px)}100%{transform:translateY(0)}}
.q-princess.q-s4 .q-rider{--ry:42%;--rw:92px;z-index:4}
.b-uni{transform:translateX(42%);animation:b-path 5000ms cubic-bezier(.35,.1,.4,1) both}
@keyframes b-path{
  0%  {transform:translateX(-24%) translateY(0) rotate(0)}
  36% {transform:translateX(30%) translateY(6px) rotate(-3deg)}
  48% {transform:translateX(42%) translateY(13px) rotate(4deg)}   /* the lurch */
  60% {transform:translateX(54%) translateY(4px) rotate(-2deg)}
  100%{transform:translateX(112%) translateY(0) rotate(0)}
}

/* ---- 5 · Crystal Falls ---- */
.q-princess.q-s5{--cam-from:1.3;--cam-mid:1.02;--cam-to:1.1;--cam-origin:52% 46%}
.c-rock{position:absolute;inset:0;
  background:linear-gradient(180deg,#3E5566 0%,#54707F 40%,#2E4450 100%)}
.c-pool{position:absolute;left:0;right:0;bottom:0;height:30%;
  background:linear-gradient(180deg,#7FD3B2,#3E8C86);opacity:.9}
.c-fall{position:absolute;left:26%;right:26%;top:0;bottom:24%}
.c-sheet{
  position:absolute;top:0;bottom:0;width:11%;left:calc(var(--i) * 10%);
  background:linear-gradient(180deg,rgba(255,255,255,.9),rgba(191,225,242,.6));
  animation:c-pour 700ms calc(var(--i) * 40ms) linear infinite;
}
@keyframes c-pour{to{background-position-y:60px}}
.q-princess.q-s5 .q-rider{--ry:50%;--rw:98px;z-index:4}
.c-uni{transform:translateX(42%);animation:c-path 5000ms cubic-bezier(.3,.2,.4,1) both}
@keyframes c-path{
  0%  {transform:translateX(-24%) translateY(4%) rotate(-6deg)}
  30% {transform:translateX(14%) translateY(-4%) rotate(4deg)}
  46% {transform:translateX(42%) translateY(-6%) rotate(0)}       /* through the sheet */
  70% {transform:translateX(72%) translateY(-1.4%) rotate(-3deg)}
  100%{transform:translateX(116%) translateY(3%) rotate(2deg)}
}
/* The rainbow the water throws once she's through it. */
.c-bow{position:absolute;left:50%;top:52%;width:0;height:0;z-index:3}
.c-band{
  position:absolute;left:0;top:0;border-radius:50% 50% 0 0;border-style:solid;
  border-width:9px 9px 0 9px;border-color:currentColor transparent transparent transparent;
  width:calc(200px + var(--i) * 54px);height:calc(100px + var(--i) * 27px);
  margin-left:calc((100px + var(--i) * 27px) * -1);
  opacity:0;animation:c-arc 1400ms calc(1500ms + var(--i) * 70ms) ease-out both;
}
.c-band:nth-child(1){color:#FBB6CE}.c-band:nth-child(2){color:#FBD86B}
.c-band:nth-child(3){color:#A7E8CE}.c-band:nth-child(4){color:#C9AEEF}
@keyframes c-arc{0%{opacity:0;transform:scale(.4)}40%{opacity:.95}100%{opacity:.8;transform:scale(1)}}

/* ---- 6 · The Cloud Stair ---- */
.q-princess.q-s6{--cam-from:1.04;--cam-mid:1.02;--cam-to:1.1;--cam-origin:50% 20%;--cam-ty:9%}
.s-sky{position:absolute;inset:0;
  background:linear-gradient(180deg,#1B2145 0%,#4A4A7E 38%,#9B7FA8 66%,#E9A9B8 88%,#FBD86B 100%);
  animation:s-dusk 4200ms ease-in both}
@keyframes s-dusk{0%{filter:brightness(1.35) saturate(1.1)}100%{filter:brightness(.72) saturate(.9)}}
.s-star{
  position:absolute;width:3px;height:3px;border-radius:50%;background:#FFF6D8;
  left:calc(2% + var(--i) * 4.4%);top:calc(4% + var(--m4) * 13%);
  opacity:0;animation:s-twinkle 2600ms calc(1200ms + var(--i) * 70ms) ease-out both;
}
@keyframes s-twinkle{0%{opacity:0;transform:scale(0)}40%{opacity:1;transform:scale(1.4)}100%{opacity:.85;transform:scale(1)}}
/* Both particle containers need to be the positioned box their children
   measure against. .s-stars worked without this by luck — an unpositioned
   container drops its absolutely-positioned children onto .q-scene, which
   happens to be the same rectangle — and would have silently mispositioned
   twenty-two stars the moment anything gave it a position of its own. */
.s-stars{position:absolute;inset:0}
.s-steps{position:absolute;inset:0}
.s-step{
  position:absolute;height:22px;border-radius:14px;background:#fff;
  width:calc(30% - var(--i) * 1.6%);
  left:calc(8% + var(--i) * 8%);
  bottom:calc(6% + var(--i) * 12%);
  box-shadow:0 6px 14px rgba(70,60,110,.3);
  opacity:0;transform:scale(.5);
  animation:s-form 620ms calc(400ms + var(--i) * 150ms) cubic-bezier(.34,1.6,.5,1) both;
}
@keyframes s-form{0%{opacity:0;transform:scale(.5) translateY(14px)}
  100%{opacity:1;transform:scale(1) translateY(0)}}
.q-princess.q-s6 .q-rider{--ry:6%;--rw:88px;--rx:8%}
.s-uni{transform:translate(54%,-80%);animation:s-path 5000ms cubic-bezier(.4,.1,.5,1) both}
@keyframes s-path{
  0%  {transform:translate(0,0)}
  46% {transform:translate(30%,-40%)}
  100%{transform:translate(54%,-80%)}
}

/* ---- 7 · The Castle Gates ---- */
/* A gentler push: at 1.34 the camera cropped her off the bottom edge. */
.q-princess.q-s7{--cam-from:1.06;--cam-mid:1.1;--cam-to:1.2;--cam-origin:50% 46%}
.g-sky{position:absolute;inset:0;
  background:linear-gradient(180deg,#2B2050 0%,#4B3568 48%,#6E4A6B 100%)}
.g-wall{position:absolute;left:0;right:0;bottom:0;height:64%;
  background:repeating-linear-gradient(90deg,#7A6A80 0 46px,#6B5C72 46px 92px);
  box-shadow:inset 0 6px 0 rgba(255,255,255,.12)}
.g-tower{position:absolute;bottom:0;width:22%;height:84%;
  background:repeating-linear-gradient(90deg,#8B7A91 0 40px,#7A6A80 40px 80px);
  border-radius:6px 6px 0 0}
.g-tl{left:2%}.g-tr{right:2%}
.g-torches{position:absolute;inset:0;z-index:3}
.g-torch{
  position:absolute;width:13px;height:13px;border-radius:50%;background:#FBD86B;
  left:calc(9% + var(--i) * 16.5%);bottom:calc(38% + var(--m2) * 16%);
  opacity:0;
  box-shadow:0 0 18px 7px rgba(251,216,107,.75),0 0 46px 20px rgba(242,132,172,.35);
  animation:g-ignite 500ms calc(420ms + var(--i) * 150ms) ease-out both;
}
@keyframes g-ignite{0%{opacity:0;transform:scale(.2)}60%{opacity:1;transform:scale(1.35)}100%{opacity:1;transform:scale(1)}}
.g-arch{position:absolute;left:30%;right:30%;bottom:0;height:46%;
  border-radius:50% 50% 0 0;background:#2A1E33}
.g-door{position:absolute;bottom:0;width:20%;height:44%;background:linear-gradient(180deg,#A9784F,#6E4A32);
  border:3px solid #6E4A57;transform-origin:var(--o,left);
  animation:g-open 1500ms 1300ms cubic-bezier(.4,.1,.3,1) both}
.g-dl{left:30%;--o:left}
.g-dr{right:30%;--o:right}
@keyframes g-open{0%{transform:perspective(600px) rotateY(0)}
  100%{transform:perspective(600px) rotateY(calc((var(--s,1)) * -78deg))}}
.g-dr{--s:-1}
.g-flood{position:absolute;left:30%;right:30%;bottom:0;height:46%;
  background:radial-gradient(60% 90% at 50% 100%,#FFF6D8,rgba(251,216,107,.6) 40%,transparent 78%);
  opacity:0;animation:g-flood 2400ms 1560ms ease-out both}
@keyframes g-flood{0%{opacity:0;transform:scale(.5)}40%{opacity:1}100%{opacity:.85;transform:scale(1.6)}}
/* A walk, not a gallop — she is going in, not running past. */
.q-princess.q-s7 .q-rider{--ry:40%;--rw:76px;--rx:50%;--rml:-38px;z-index:4;
  --stride:640ms;transform-origin:50% calc(100% - var(--ry))}
/* She ends up in the gateway — higher, smaller, and clear of the plate. */
.g-uni{transform:translateY(-2%) scale(.6);animation:g-path 5000ms ease-in-out both}
@keyframes g-path{0%{transform:translateY(9%) scale(.8);opacity:0}
  22%{opacity:1}100%{transform:translateY(-2%) scale(.48);opacity:.92}}

/* ---- 8 · The Throne Room ---- */
/* --ry lives on the scene root rather than the rider, so the crown can hang
   off the same number and stay over her head at any frame size. */
.q-princess.q-s8{--cam-from:1.42;--cam-mid:1.06;--cam-to:1;--cam-origin:50% 62%;
  --ry:38%;--rw:84px}
.t-room{position:absolute;inset:0;
  background:linear-gradient(180deg,#4A3358 0%,#6B4A6E 44%,#8A6478 74%,#3E2C46 100%)}
.t-windows{position:absolute;left:0;right:0;top:6%;height:34%}
.t-win{
  position:absolute;top:0;width:15%;height:100%;border-radius:50% 50% 4px 4px;
  left:calc(14% + var(--i) * 28%);
  background:linear-gradient(180deg,#FFF6D8,#FBD86B 50%,#F284AC);
  box-shadow:0 0 40px 14px rgba(251,216,107,.35);
  opacity:0;animation:f-glow 2200ms calc(400ms + var(--i) * 200ms) ease-out both;
}
.t-dais{position:absolute;left:22%;right:22%;bottom:30%;height:12%;
  background:linear-gradient(180deg,#C9AEEF,#A784DE);border-radius:5px}
.t-throne{position:absolute;left:38%;right:38%;bottom:42%;height:24%;
  background:linear-gradient(180deg,#FBD86B,#EFC24C);
  border:3px solid #6E4A57;border-radius:14px 14px 4px 4px;
  box-shadow:0 10px 26px rgba(50,30,60,.5)}
/* Unrolls on scaleY from the front edge rather than on height — the last
   layout-animating keyframe in the file, and the carpet is the widest thing
   on screen, so it was the most expensive one left. */
.t-carpet{position:absolute;left:34%;right:34%;bottom:0;height:32%;
  background:linear-gradient(180deg,#F284AC,#D8628F);
  clip-path:polygon(28% 0,72% 0,100% 100%,0 100%);
  transform-origin:50% 100%;
  animation:t-unroll 1500ms 300ms cubic-bezier(.3,.8,.4,1) both}
@keyframes t-unroll{from{transform:scaleY(0)}to{transform:scaleY(1)}}
/* The carpet runs from the dais down to the bottom edge, so the plate lands
   on it rather than on her. */
.t-carpet{height:46%}
/* She arrives and stays — tagged q-idle in the scene, so the shared gallop
   gives way to a breath. A stride under a character standing at a throne is
   worse than no animation at all. */
.q-princess.q-s8 .q-rider{--rx:50%;--rml:-42px;z-index:4;
  transform-origin:50% calc(100% - var(--ry))}
.t-uni{transform:translateY(-.5%) scale(1);animation:t-path 5000ms cubic-bezier(.3,.6,.4,1) both}
@keyframes t-path{0%{transform:translateY(10%) scale(.6);opacity:0}
  26%{opacity:1}54%{transform:translateY(0) scale(1);opacity:1}100%{transform:translateY(-.5%) scale(1)}}
.t-crown{position:absolute;left:50%;bottom:calc(var(--ry) + 7.4%);width:58px;margin-left:-29px;z-index:5;line-height:0;
  filter:drop-shadow(0 6px 10px rgba(50,30,60,.5));
  animation:t-descend 1400ms 1400ms cubic-bezier(.3,1.5,.4,1) both}
.t-crown svg{width:100%;height:auto}
@keyframes t-descend{0%{opacity:0;transform:translateY(-90px) scale(1.5) rotate(-12deg)}
  60%{opacity:1}100%{opacity:1;transform:translateY(0) scale(1) rotate(0)}}

/* ============================================================
   OPENING STINGERS
   The curtain each kid gets when they open their game. Scene markup is
   registered under 'intro' in quest-scenes.js; the shell is in quest.js.

   Everything here is namespaced `.q-sting`, and the one thing to keep in
   mind editing it is that this plays *every single launch*. Anything that
   is merely clever gets tiring by the fortieth viewing; what survives is
   whatever is quiet, short, and over before it has asked for attention.
   ============================================================ */

.q-sting{z-index:70}                 /* over the home screen it opens onto */
.q-sting.is-out{animation:q-stingout 200ms ease-in both}
@keyframes q-stingout{to{opacity:0}}
/* The camera does one slow settle and nothing else — there is no hit to
   punctuate, so a move with a brake in it would be punctuating nothing. */
.q-sting .q-cam{animation:q-cam var(--t-run,3000ms) cubic-bezier(.24,.7,.32,1) both}
.q-sting.q-racing .q-cam{--cam-from:1.2;--cam-mid:1.03;--cam-to:1.05;--cam-origin:50% 62%}
.q-sting.q-princess .q-cam{--cam-from:1.16;--cam-mid:1.02;--cam-to:1.06;--cam-origin:50% 30%}
/* No colour drain: the drain exists to hold a title, and there is no title. */
.q-sting .q-scene{animation:none}
.q-sting .q-vignette{background:radial-gradient(128% 92% at 50% 50%,transparent 44%,rgba(0,0,0,.6) 100%)}

/* The skip affordance. Late, small, and low — early enough that a kid who
   wants out knows the way, late enough that it isn't the first thing the
   frame says. The whole surface is the target; this is only the label. */
/* Above the visor, not inside it: at z-index 9 under the helmet band the
   label sat on the padding and read as printed on the helmet. */
.q-skip{
  position:absolute;left:0;right:0;bottom:22px;z-index:11;text-align:center;
  font-family:var(--font-ui);font-size:9px;letter-spacing:.28em;text-transform:uppercase;
  color:rgba(226,235,248,.4);pointer-events:none;
  animation:q-skipin 600ms 900ms ease-out both;
}
@keyframes q-skipin{from{opacity:0}to{opacity:1}}
.q-sting.q-princess .q-skip{color:rgba(255,240,246,.5)}

/* ---------- RACING · Grid ---------- */
/* Cold and blue above, because the only warm things in the frame should be
   the tree, the tail lights and the flag. */
.i-gridsky{position:absolute;inset:0;
  background:radial-gradient(80% 40% at 50% 24%,rgba(27,107,255,.16),transparent 68%),
             radial-gradient(50% 30% at 50% 52%,rgba(255,106,0,.1),transparent 70%)}
/* Grid boxes painted on the deck. Drawn flat and skewed rather than put on
   the perspective plane: they need to sit under specific cars, and chasing
   a shared vanishing point for three rectangles costs more than it returns. */
.i-box{position:absolute;z-index:3;border:2px solid rgba(236,244,255,.34);
  border-top:0;filter:blur(.6px);opacity:0;animation:q-skipin 500ms 500ms ease-out both}
.i-b1{left:34%;width:32%;top:74%;height:9%;transform:perspective(300px) rotateX(58deg)}
.i-b2{left:19%;width:18%;top:60%;height:5%;transform:perspective(300px) rotateX(58deg);opacity:.6}
.i-b3{left:64%;width:17%;top:59%;height:5%;transform:perspective(300px) rotateX(58deg);opacity:.6}
.i-heat{left:38%;width:24%;top:70%;height:9%}

/* The tree. Three lamps on a post, dead centre, well above the cars — a
   drag tree, not the five-light gantry stage 4 already owns. */
.i-tree{
  position:absolute;left:50%;top:15%;z-index:8;transform:translateX(-50%);
  display:flex;flex-direction:column;gap:9px;padding:9px 8px;border-radius:4px;
  background:rgba(6,10,18,.82);border:1px solid rgba(201,212,226,.16);
  opacity:0;animation:q-skipin 400ms 1350ms ease-out both;
}
/* The mast. Without it the tree hangs in the sky like a traffic light in a
   dream — a light that isn't mounted to anything reads as a UI element
   floating over the scene rather than as something on the circuit. */
.i-tree::after{
  content:"";position:absolute;left:50%;top:100%;width:5px;height:150px;
  margin-left:-2.5px;
  background:linear-gradient(180deg,#161D2A,#0A0F1A 60%,transparent);
}
.i-lamp{width:22px;height:22px;border-radius:50%;background:#12161F;
  box-shadow:inset 0 0 8px rgba(0,0,0,.95)}
/* Each lamp lights, holds, and dies as the next one takes over. Written as
   three delayed runs of one keyframe rather than as one long timeline, so
   retiming the sequence is three numbers instead of nine. */
.i-red{animation:i-lamp-r 900ms 1500ms ease-out both}
.i-amb{animation:i-lamp-a 900ms 2100ms ease-out both}
/* Green does not go out. It is the last thing the frame says. */
.i-grn{animation:i-lamp-g 700ms 2820ms ease-out both}
@keyframes i-lamp-r{
  0%{background:#12161F;box-shadow:inset 0 0 8px rgba(0,0,0,.95)}
  14%,66%{background:#FF2A20;box-shadow:0 0 18px 6px rgba(224,27,36,.85)}
  100%{background:#2A0E10;box-shadow:inset 0 0 8px rgba(0,0,0,.9)}
}
@keyframes i-lamp-a{
  0%{background:#12161F;box-shadow:inset 0 0 8px rgba(0,0,0,.95)}
  14%,66%{background:#FFC01E;box-shadow:0 0 18px 6px rgba(255,180,20,.85)}
  100%{background:#2A210A;box-shadow:inset 0 0 8px rgba(0,0,0,.9)}
}
@keyframes i-lamp-g{
  0%{background:#12161F;box-shadow:inset 0 0 8px rgba(0,0,0,.95)}
  18%,100%{background:#2AE05A;box-shadow:0 0 24px 9px rgba(42,224,90,.9),0 0 70px 26px rgba(42,224,90,.3)}
}

/* The helmet beat, shot from inside the helmet. First attempt was two flat
   bars closing to a letterbox, and it read as exactly that — letterboxing
   is a film convention and says "widescreen", not "helmet". What makes it a
   helmet is the *shape* of the hole: wide, corner-radiused, taller across
   the middle than at the sides.

   So it is built inside-out. `.i-vwin` is the aperture, and a 999px spread
   shadow paints everything outside it — one element, one shape, and the
   surround can never disagree with the hole. Dropping the visor is then a
   scaleY on the window: at 1.9 the aperture is taller than the frame and no
   helmet is visible at all, at 1 it sits where it lands. The shadow scales
   with it and stays oversized either way. */
.i-visor{position:absolute;inset:0;z-index:7;pointer-events:none;overflow:hidden}
/* Band depth and curvature are both deliberately shallow. At 21% deep with a
   32% vertical radius it looked right on a 340×600 phone and turned into a
   porthole on a 502×707 iPad — the taller the frame, the more an elliptical
   radius bows, so the same numbers crop far harder as the aspect changes.
   15/17% with a 20% radius is a gentle dome at every size the app runs at,
   and the asymmetry (deeper at the chin than the brow) is what stops it
   reading as a symmetrical letterbox. */
.i-vwin{
  position:absolute;left:-7%;right:-7%;top:15%;bottom:17%;
  border-radius:52% 52% 48% 48% / 20% 20% 18% 18%;
  box-shadow:0 0 0 999px #070A11,
             inset 0 0 0 2px rgba(150,188,238,.16),
             inset 0 7px 12px -6px rgba(0,0,0,.9);
  transform:scaleY(1.7);
  animation:i-visordrop 620ms 950ms cubic-bezier(.3,.9,.4,1) both;
}
@keyframes i-visordrop{to{transform:scaleY(1)}}
/* The gasket highlight, on the aperture's own edge rather than on a bar's —
   a helmet's padding catches light at the opening and nowhere else. */
.i-vgask{
  position:absolute;left:-7%;right:-7%;top:15%;bottom:17%;
  border-radius:52% 52% 48% 48% / 20% 20% 18% 18%;
  box-shadow:inset 0 3px 7px -2px rgba(180,212,255,.3),
             inset 0 -3px 7px -2px rgba(180,212,255,.16);
  transform:scaleY(1.7);
  animation:i-visordrop 620ms 950ms cubic-bezier(.3,.9,.4,1) both;
}

/* The flag. It opens the whole thing: fills the frame, waves once, and is
   whipped off to the left like a cloth being pulled away — which is what a
   curtain is, and the reason this beat exists at all. */
.i-flag{
  position:absolute;inset:-8% -14%;z-index:9;
  background-size:64px 64px;
  background-image:conic-gradient(#EAF1FA 90deg,#0A0D14 90deg 180deg,#EAF1FA 180deg 270deg,#0A0D14 270deg);
  transform-origin:0 50%;
  animation:i-flagoff 900ms cubic-bezier(.5,0,.78,.2) both;
}
/* The wave is a skew that reverses, not a wobble — cloth under tension
   snaps through flat rather than oscillating around it. */
@keyframes i-flagoff{
  0%  {transform:skewY(0) translateX(0) scaleX(1);opacity:1}
  22% {transform:skewY(-3.5deg) translateX(0) scaleX(1);opacity:1}
  40% {transform:skewY(2.5deg) translateX(-2%) scaleX(1);opacity:1}
  62% {transform:skewY(-6deg) translateX(-32%) scaleX(.86);opacity:.9}
  100%{transform:skewY(-11deg) translateX(-124%) scaleX(.5);opacity:0}
}

/* ---------- PRINCESS · The Long Way Up ---------- */
.q-sting.q-princess .q-scene{
  background:linear-gradient(180deg,#2A1B3D 0%,#5E3A63 26%,#A6607F 48%,#E9899E 68%,#FBD86B 100%)}
.i-dawnsky{animation:m-dawn 1400ms 100ms ease-out both}
/* Valley mist, sitting between the planes. Depth here is haze rather than
   scale, the same as the racing shorts — it is just warm instead of cold. */
.i-mist{position:absolute;left:0;right:0;top:44%;height:34%;z-index:2;filter:blur(12px);
  background:linear-gradient(180deg,transparent,rgba(255,226,238,.5) 46%,transparent);
  animation:i-drift 2800ms ease-in-out both}
@keyframes i-drift{from{transform:translateX(-4%)}to{transform:translateX(4%)}}

/* The mountain, in three planes. Each is a peak rather than the rounded
   hills of the meadow scenes: she is not crossing this, she is climbing it. */
.i-mtn{position:absolute;left:-20%;right:-20%;bottom:0;z-index:2}
.i-m3{height:74%;background:linear-gradient(96deg,#6B4E78 0 46%,#4C3357 46%);opacity:.6;
  clip-path:polygon(0 100%,26% 40%,42% 58%,58% 14%,76% 46%,100% 100%);
  animation:i-rise 1500ms 200ms cubic-bezier(.2,.8,.3,1) both}
.i-m2{height:60%;background:linear-gradient(96deg,#845C86 0 44%,#5E3F66 44%);opacity:.85;
  clip-path:polygon(0 100%,18% 52%,38% 30%,54% 48%,72% 26%,100% 100%);
  animation:i-rise 1500ms 340ms cubic-bezier(.2,.8,.3,1) both}
.i-m1{height:40%;z-index:4;background:linear-gradient(96deg,#5C8E71 0 42%,#3C6B52 42%);
  clip-path:polygon(0 100%,14% 44%,34% 62%,52% 34%,74% 56%,92% 40%,100% 100%);
  animation:i-rise 1500ms 460ms cubic-bezier(.2,.8,.3,1) both}
@keyframes i-rise{0%{opacity:0;transform:translateY(9%)}100%{opacity:1;transform:translateY(0)}}

/* The castle. Small, pale and high — see the scene note. It is roughly two
   per cent of the frame and that is the correct size for something you are
   setting out toward rather than standing in front of. */
/* Sat at top:19% it floated in open sky well above every peak, which reads
   as a castle in the clouds rather than one you climb to. The tallest ridge
   is .i-m3's peak — 74% tall, anchored bottom, apex at 14% of its own
   height, so y = 26% + .14*74% ≈ 36% of frame, at x ≈ 58%. The castle is
   planted there, and it is the mountain's summit that puts it out of reach
   rather than the sky. */
/* Centred with a margin rather than a translate: the entrance animation owns
   `transform`, and a translateX(-50%) here would be clobbered by its
   translateY the moment it ran — leaving the castle centred on a 250px
   preview and four per cent off the peak on a 520px iPad. */
.i-castle{
  position:absolute;left:58%;margin-left:-16px;top:calc(36% - 28px);
  width:32px;height:29px;z-index:3;
  opacity:0;animation:i-castlein 1100ms 750ms ease-out both;
}
@keyframes i-castlein{0%{opacity:0;transform:translateY(6px)}100%{opacity:1;transform:translateY(0)}}
/* Layout for every part; stone colour for the stone ones only. Setting the
   fill on `.i-castle i` out-specifies .i-cglow's own gradient (0,1,1 against
   0,1,0) and painted the glow as a solid pale block, which blurred into a
   rectangle hanging over the mountain — but narrowing the whole rule to the
   stone also took `position:absolute` away from the towers, and they laid
   themselves out as a row of inline dashes. Two rules, one job each. */
.i-castle i{position:absolute}
.i-castle .i-ct,.i-castle .i-cwall{background:#F3E4F2}
.i-cwall{left:4px;bottom:0;width:26px;height:15px;
  box-shadow:0 1px 0 rgba(120,86,124,.5)}
/* Three towers, the middle one tallest. Crenellations are one repeating
   gradient across the top rather than drawn teeth — at this size a tooth is
   a subpixel and the gradient is the only version that survives. */
.i-ct{bottom:11px;width:7px;background:#F3E4F2}
.i-ct1{left:1px;height:15px}
.i-ct2{left:13px;height:23px}
.i-ct3{left:25px;height:16px}
.i-ct::after{content:"";position:absolute;left:0;right:0;top:0;height:3px;
  background:repeating-linear-gradient(90deg,#F3E4F2 0 2px,transparent 2px 4px)}
/* The one warm light in the upper half of the frame — the reason to go. */
.i-castle .i-cglow{left:-22px;top:-20px;width:76px;height:70px;border-radius:50%;
  background:radial-gradient(circle,rgba(255,246,214,.5),rgba(255,214,138,.18) 44%,transparent 70%);
  filter:blur(6px);animation:i-pulse 2600ms ease-in-out infinite}
@keyframes i-pulse{0%,100%{opacity:.7}50%{opacity:1}}

/* The path, drawn all the way up before she takes a step on it. Each stone
   lights in sequence from the bottom, so the eye is walked to the castle
   once before she starts — the shot explains itself, then plays. */
.i-path{position:absolute;inset:0;z-index:5;pointer-events:none}
.i-step{
  position:absolute;width:calc(15px - var(--i) * 1.1px);height:calc(9px - var(--i) * .6px);
  border-radius:50%;background:rgba(255,246,214,.85);
  box-shadow:0 0 10px 4px rgba(255,226,160,.5);
  left:calc(11% + var(--i) * 5.2% + var(--m2) * 2.5%);
  bottom:calc(13% + var(--i) * 5.6%);
  opacity:0;transform:scale(.3);
  animation:i-stone 700ms calc(900ms + var(--i) * 105ms) cubic-bezier(.3,1.5,.5,1) both;
}
@keyframes i-stone{0%{opacity:0;transform:scale(.3)}
  55%{opacity:1;transform:scale(1.15)}100%{opacity:.72;transform:scale(1)}}
/* A near ridge she walks out from behind, so she enters the world rather
   than sliding in over the top of it. */
.i-ridge{position:absolute;left:-10%;right:-10%;bottom:0;height:15%;z-index:7;
  background:linear-gradient(180deg,#31624A,#1E4432);
  border-radius:50% 50% 0 0 / 26% 26% 0 0}

/* Her path: in from the left, up the first pitch, still walking on the cut. */
.q-sting.q-princess .q-rider{--ry:15%;--rw:74px;z-index:6}
/* The delay is a full second, and `both` holds the 0% frame through it — so
   the 0% frame has to be genuinely off-frame. At -24% she was clipped by
   the edge rather than outside it, which meant a second of a unicorn
   galloping on the spot with her nose in shot before she set off. */
.i-walk{animation:i-setout 2800ms 1150ms cubic-bezier(.32,.6,.5,1) both}
@keyframes i-setout{
  0%  {transform:translate(-34%,4%)}
  100%{transform:translate(30%,-7%)}
}

/* ---------- stingers · reduced motion ----------
   The shared rule above kills every animation in `.q`, which for a takeover
   leaves a perfectly good end frame. A stinger has no end frame — it is all
   animation — so without this it opens on its *first* frame and holds it:
   a full-screen checkerboard with the lights off, or a mountain with no
   mountain on it. Each of these is the last frame of its beat, stated
   directly. Same picture the animation arrives at; it just starts there. */
@media (prefers-reduced-motion:reduce){
  /* the curtain has already been pulled */
  .q-sting .i-flag{opacity:0!important}
  /* the visor is down */
  .q-sting .i-vwin,.q-sting .i-vgask{transform:scaleY(1)!important}
  /* green, and everything that had to fade in has */
  .q-sting .i-tree,.q-sting .i-box,.q-sting .q-skip,
  .q-sting .i-castle,.q-sting .i-mtn,.q-sting .i-step{opacity:1!important;transform:none!important}
  .q-sting .i-b2,.q-sting .i-b3{opacity:.6!important}
  .q-sting .i-grn{background:#2AE05A!important;
    box-shadow:0 0 24px 9px rgba(42,224,90,.9),0 0 70px 26px rgba(42,224,90,.3)!important}
  .q-sting .i-red{background:#2A0E10!important}
  .q-sting .i-amb{background:#2A210A!important}
  /* the grid boxes keep their perspective — `transform:none` above would
     stand them upright, and a grid box seen face-on is a rectangle */
  .q-sting .i-box{transform:perspective(300px) rotateX(58deg)!important}
  /* she is on her way */
  .q-sting .i-walk{transform:translate(6%,-1%)!important}
  .q-sting .i-step{opacity:.72!important}
}
