/* ============================================================================
 * animations.css — THE animation library (Mission Control v2).
 * Single source of truth for every @keyframes in the app. Components reference
 * these by name (SetPieces.tsx is the matching component library); Tailwind's
 * animate-* utilities point here too (tailwind.config.js declares only the
 * `animation` shorthands, never keyframes — no duplication).
 *
 * COMPAT RULES (learned from real-device testing, 2026-07-22):
 *  - NEVER use var() inside @keyframes — older iOS WebKit (pre-iPhone-15-era
 *    Safari AND iOS Chrome, same engine) discards such animations entirely.
 *    That is why rxRush became rxRushL/rxRushR with literal % transforms.
 *  - Percentage translate scales with the element's own size — prefer it over
 *    per-size px variants.
 *  - Keep every animated property on transform/opacity/height/background only.
 *  - Every looping piece must be clipped by its own overflow-hidden box
 *    (owner rule: animations never flow outside their block).
 *  - prefers-reduced-motion collapses everything to a static end frame (the
 *    ONE global accessibility rule, at the bottom).
 * ========================================================================== */

/* ── Entrances ── */
@keyframes fadeUp { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:none; } }
@keyframes viewIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } }
@keyframes eshaIn { from { opacity:0; transform:translateX(28px); } to { opacity:1; transform:none; } }

/* ── Ambient signals ── */
@keyframes sweep   { to { transform:rotate(360deg); } }
@keyframes orbSpin { to { transform:rotate(360deg); } }
@keyframes orbSpinR{ to { transform:rotate(-360deg); } }
@keyframes pulseS  { 0%,100% { transform:scale(1); opacity:1; } 50% { transform:scale(1.3); opacity:.65; } }
@keyframes floaty  { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-5px); } }
@keyframes flick   { 0%,100% { transform:rotate(-5deg) scale(1); } 50% { transform:rotate(6deg) scale(1.12); } } /* streak flame */
@keyframes ringPulse { 0% { transform:scale(.7); opacity:.55; } 100% { transform:scale(1.5); opacity:0; } }
@keyframes typid   { 0%,60%,100% { opacity:.25; transform:translateY(0); } 30% { opacity:1; transform:translateY(-3px); } } /* Esha thinking dots */
@keyframes waveX   { to { transform:translateX(-12px); } }   /* liquid surface drift (shield + rx water) */

/* ── Esha ACTIVE state — two lights drift and shift colour ── */
@keyframes eshaLightA { 0%,100% { transform:translate(-18%,-10%); background:radial-gradient(circle,#B9B2FF,transparent 70%); } 50% { transform:translate(22%,20%); background:radial-gradient(circle,#7FE7C4,transparent 70%); } }
@keyframes eshaLightB { 0%,100% { transform:translate(22%,18%); background:radial-gradient(circle,#7FB4FF,transparent 70%); } 50% { transform:translate(-20%,-16%); background:radial-gradient(circle,#C9A7FF,transparent 70%); } }

/* ── Line-trace set pieces (Bio, formulae) — one figure at a time ── */
@keyframes draw     { 0% { stroke-dashoffset:60; } 42%,58% { stroke-dashoffset:0; } 100% { stroke-dashoffset:-60; } }
/* bioTrace plates run at cycle/N offsets (25% for 4 plates) — the visible
   window (draw + hold + fade) must END before the next plate's 3% fade-in, or
   two drawings overlay (owner bug, 2026-07-23). Visible span here: 3%–24%. */
@keyframes bioTrace { 0% { stroke-dashoffset:360; opacity:0; } 3% { opacity:1; } 18% { stroke-dashoffset:0; } 22% { stroke-dashoffset:0; opacity:1; } 24% { stroke-dashoffset:0; opacity:0; } 100% { stroke-dashoffset:0; opacity:0; } }

/* ── Reaction synthesis (2H₂ + O₂ → poof → H₂O, vessel fills) ──
   rxRushL/R: literal percentage transforms (older-iOS-safe; % scales with the
   H-pair's own width so one pair of keyframes fits every size). */
@keyframes rxRushL  { 0% { transform:translateX(-160%); opacity:0; } 9% { opacity:1; } 30% { transform:translateX(0); } 37% { transform:translateX(0); opacity:1; } 43% { opacity:0; } 100% { opacity:0; transform:translateX(0); } }
@keyframes rxRushR  { 0% { transform:translateX(160%); opacity:0; } 9% { opacity:1; } 30% { transform:translateX(0); } 37% { transform:translateX(0); opacity:1; } 43% { opacity:0; } 100% { opacity:0; transform:translateX(0); } }
@keyframes rxCore   { 0% { opacity:0; transform:scale(.5); } 12% { opacity:1; transform:scale(1); } 37% { opacity:1; transform:scale(1); } 45% { opacity:0; transform:scale(.4); } 100% { opacity:0; } }
@keyframes rxPoof   { 0%,35% { opacity:0; transform:scale(.2); } 43% { opacity:.9; transform:scale(1.15); } 54% { opacity:0; transform:scale(1.8); } 100% { opacity:0; } }
@keyframes rxWater  { 0%,45% { height:0; } 72% { height:74%; } 90% { height:74%; } 100% { height:0; } }
@keyframes rxProduct{ 0%,45% { opacity:0; transform:translate(-50%,-46%) scale(.6); } 58% { opacity:1; transform:translate(-50%,-50%) scale(1); } 88% { opacity:1; transform:translate(-50%,-50%) scale(1); } 100% { opacity:0; transform:translate(-50%,-50%) scale(1); } }

/* ── Chemistry set-piece: add-then-heat ── */
@keyframes chemFill    { 0%,22% { background:#5AA7F0; height:52%; } 44% { background:#5AA7F0; height:56%; } 56% { background:#9B6FD0; } 68% { background:#F06D96; } 80% { background:#34D3A0; height:58%; } 100% { background:#34D3A0; height:58%; } }
@keyframes chemDropper { 0%,40% { opacity:1; transform:translateY(0); } 52% { opacity:0; transform:translateY(-9px); } 100% { opacity:0; transform:translateY(-9px); } }
@keyframes chemDrop    { 0%,6% { opacity:0; transform:translateY(-10px); } 12% { opacity:1; transform:translateY(-8px); } 30% { opacity:1; transform:translateY(28px); } 34%,100% { opacity:0; transform:translateY(30px); } }
@keyframes chemFlame   { 0%,46% { opacity:0; transform:scaleY(.3); } 54% { opacity:1; transform:scaleY(1); } 72% { opacity:1; transform:scaleY(1.12); } 80%,100% { opacity:0; transform:scaleY(.3); } }
@keyframes chemBub     { 0%,48% { opacity:0; transform:translateY(0) scale(.5); } 56% { opacity:1; } 72% { opacity:1; transform:translateY(-13px) scale(1); } 80%,100% { opacity:0; transform:translateY(-15px) scale(1); } }

/* ── Physics set-piece: rocket launch (v2 — starts ON the pad, ignition plume,
   then lift). Plume puffs drift out left/right with literal transforms. ── */
@keyframes rocketGo  { 0%,22% { transform:translateY(0); } 30% { transform:translateY(-2px); } 62% { transform:translateY(-34px); } 68% { transform:translateY(-48px); opacity:0; } 76% { transform:translateY(0); opacity:0; } 84%,100% { transform:translateY(0); opacity:1; } }
@keyframes flameGrow { 0%,18% { transform:scaleY(.25); opacity:.4; } 26% { transform:scaleY(.9); opacity:1; } 60% { transform:scaleY(1.15); opacity:1; } 68%,100% { opacity:0; } }
/* Layered exhaust plume: an inner hot core, mid rollers that billow outward,
   and slow outer smoke that lingers — three depths so it reads as volume, not
   three dots. All literal transforms (older-iOS rule). */
@keyframes plumeL    { 0%,20% { opacity:0; transform:translate(0,0) scale(.28); } 27% { opacity:.9; transform:translate(-6px,0) scale(.95); } 42% { opacity:.45; transform:translate(-12px,-2px) scale(1.5); } 56% { opacity:.12; transform:translate(-17px,-4px) scale(2); } 66%,100% { opacity:0; transform:translate(-19px,-5px) scale(2.2); } }
@keyframes plumeR    { 0%,20% { opacity:0; transform:translate(0,0) scale(.28); } 27% { opacity:.9; transform:translate(6px,0) scale(.95); } 42% { opacity:.45; transform:translate(12px,-2px) scale(1.5); } 56% { opacity:.12; transform:translate(17px,-4px) scale(2); } 66%,100% { opacity:0; transform:translate(19px,-5px) scale(2.2); } }
@keyframes plumeL2   { 0%,23% { opacity:0; transform:translate(0,0) scale(.3); } 31% { opacity:.55; transform:translate(-9px,1px) scale(1.2); } 50% { opacity:.28; transform:translate(-16px,0) scale(1.9); } 64% { opacity:.08; transform:translate(-22px,-2px) scale(2.4); } 74%,100% { opacity:0; transform:translate(-24px,-3px) scale(2.5); } }
@keyframes plumeR2   { 0%,23% { opacity:0; transform:translate(0,0) scale(.3); } 31% { opacity:.55; transform:translate(9px,1px) scale(1.2); } 50% { opacity:.28; transform:translate(16px,0) scale(1.9); } 64% { opacity:.08; transform:translate(22px,-2px) scale(2.4); } 74%,100% { opacity:0; transform:translate(24px,-3px) scale(2.5); } }
@keyframes plumeC    { 0%,22% { opacity:0; transform:translateY(0) scale(.35); } 29% { opacity:.85; transform:translateY(-1px) scale(1.05); } 46% { opacity:.3; transform:translateY(-4px) scale(1.7); } 60%,100% { opacity:0; transform:translateY(-6px) scale(2); } }
@keyframes plumeCore { 0%,21% { opacity:0; transform:scale(.3); } 27% { opacity:.95; transform:scale(1); } 40% { opacity:.5; transform:scale(1.35); } 52%,100% { opacity:0; transform:scale(1.5); } }

/* ── Reward / reference set-pieces ── */
@keyframes barRise { from { transform:scaleY(0); } to { transform:scaleY(1); } }  /* leaderboard podium */
@keyframes cyc     { 0% { opacity:0; transform:translateY(4px); } 6%,28% { opacity:1; transform:translateY(0); } 34%,100% { opacity:0; transform:translateY(-4px); } } /* 3-slot cycling values */
/* 11-slot cycling label (PYQ year stack '16→'26) — windows tiled edge-to-edge
   so exactly one year is on screen (each slot owns 1/11 ≈ 9.09% of the cycle) */
@keyframes cyc11   { 0% { opacity:0; transform:translateY(4px); } 2.2% { opacity:1; transform:translateY(0); } 9.09% { opacity:1; transform:translateY(0); } 11.3%,100% { opacity:0; transform:translateY(-4px); } }

/* ── Mock score: hand-marked marker circle. The stroke draws on fast (like a
   real marker), holds, then the whole mark fades before the next colour. ── */
@keyframes markerDraw { 0% { stroke-dashoffset:300; } 20% { stroke-dashoffset:0; } 82% { stroke-dashoffset:0; opacity:1; } 100% { stroke-dashoffset:0; opacity:0; } }
@keyframes markerInk  { 0% { opacity:0; } 12% { opacity:1; } 82% { opacity:1; } 100% { opacity:0; } }
@keyframes blinkc  { 0%,100% { opacity:.15; } 50% { opacity:1; } }   /* periodic grid ripple */
@keyframes shuf    { 0%,100% { transform:rotate(0deg); } 50% { transform:rotate(-2.4deg); } } /* PYQ top paper shuffle */

/* ── Accessibility: reduced motion collapses everything to a static frame.
   NOTE for device QA: if a phone has iOS/Android "Reduce Motion" enabled,
   this rule (correctly) freezes ALL animations in every browser on it.
   The html[data-still] variant is the SAME collapse, stamped at boot by
   stillMode.ts for devices the media query can't see: save-data / 2G
   connections and low-core/low-memory hardware. Set-pieces additionally swap
   to designed still frames (SetPieces.useStillMode) — this rule is the global
   backstop for every other animation (entrances, ambience, Esha bars). ── */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration:.01s !important; animation-iteration-count:1 !important; }
}
html[data-still] * { animation-duration:.01s !important; animation-iteration-count:1 !important; }
