/* ---------------------------------------------------------------------------
   "How it works" animated explainer — the four-beat loop that fills the slot
   the 30-second video was deferred from.

   Ported verbatim from the canonical reference implementation (rmp-story-final.html)
   per CLAUDE_CODE_BRIEF_story_slide_2.md. Demo scaffolding from that file (its page
   header, nav, .how wrapper, .proto-note) is deliberately NOT carried over.

   Loaded only by how-it-works.html. It lives here rather than in style.css because
   style.css is shared by all seven pages, and ~9KB of single-page animation CSS
   should not be paid for by the six pages that never render it.

   16s master timeline, four beats at 0-25 / 25-50 / 50-75 / 75-100%.
   No JavaScript: a plain infinite CSS loop. The only conditional behaviour is
   prefers-reduced-motion, at the bottom of this file.
   --------------------------------------------------------------------------- */

/* Tokens are scoped to the card rather than :root — --rmp-* already come from
   assets/design-system/tokens/colors.css via style.css, and these four are needed
   only in here. Poppins/Inter/Patrick Hand are already loaded by the page's own
   font <link>, so this file adds no network request. */
.anim-card {
  --loop: 16s;                  /* master timeline — infinite */
  --font-heading: 'Poppins', system-ui, sans-serif;
  --font-tagline: 'Patrick Hand', cursive;
  --shadow-card: 0 20px 40px -18px rgba(15,110,105,.25), 0 1px 2px rgba(0,0,0,.05);
}

/* ---- the card + stage ---- */
/* width:100% is a porting addition, not in the reference. There the card sat in a plain
   block container, where `margin:0 auto` + max-width fills the line box. Here it is a GRID
   item, and an auto margin suppresses the default `justify-self:stretch`, leaving the card
   shrink-to-fit — it collapsed to 48x40, its padding alone, and .stage's width:100%
   resolved against nothing. */
/* White, not the reference's --rmp-light-grey. The reference page was white, so a
   light-grey panel read as a distinct card there. This slot sits inside .section-grey,
   which is the SAME colour — the card dissolved into its own section, leaving only the
   shadow to suggest an edge. Panel colour is therefore stated per context rather than
   inherited from the reference: it must contrast with whatever section hosts it. */
.anim-card { background: var(--rmp-white); border-radius: 24px; box-shadow: var(--shadow-card); padding: 24px 24px 16px; width: 100%; max-width: 640px; margin: 0 auto; overflow: hidden; }
.stage { position: relative; width: 100%; aspect-ratio: 480/360; }
.stage svg.scenebox { position: absolute; inset: 0; width: 100%; height: 82%; overflow: visible; }

/* The explainer needs width to read, and stacking hands it the full container instead.
   Stacked it takes its own 640px cap; in two columns it gets 524px at 1200 and above and
   falls away to 514 at 1100, 476 at 1024 and 424 at 920, so the band is worth keeping only
   while the columns are wide. Raised from 900 to 1100 (the site-wide stack is 600).
   The second reason is the alignment band in style.css: stretching the card to the copy
   column leaves it taller than the artwork whenever the copy is the taller of the two, and
   that surplus runs 8px at 1200 and above, 14px at 1100, then 68px at 1024, 112px at 960
   and 129px at 901. Below 1100 the card was mostly empty white. Stacking there ends both
   problems with one number, which is why the two are tied.
   Moves in lockstep with the offset-zeroing query in style.css -- if they ever disagree,
   the card carries an unexplained 34.5px above it while stacked. Scoped to this one grid
   so no other .grid-2 is touched. */
@media (max-width: 1100px) {
  .grid-2-explainer { grid-template-columns: 1fr; }
}

/* ---- line-art primitives ---- */
.ln { stroke: var(--rmp-dark-teal); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; fill: none; }
.fw { fill: #fff; } .ft { fill: var(--rmp-teal); } .fy { fill: var(--rmp-yellow); } .fd { fill: var(--rmp-dark-teal); }
svg .anim { transform-box: fill-box; transform-origin: center; animation-duration: var(--loop); animation-timing-function: ease-in-out; animation-iteration-count: infinite; }
.float { animation: float 3.4s ease-in-out infinite alternate; }
@keyframes float { from { transform: translateY(0); } to { transform: translateY(-4px); } }

/* ---- beat overlays (activate / found / reunited) ---- */
.scene { opacity: 0; animation: var(--loop) linear infinite; }
.s2 { animation-name: vis2; } .s3 { animation-name: vis3; } .s4 { animation-name: vis4; }
@keyframes vis2 { 0%,22% { opacity: 0; } 25% { opacity: 1; } 47% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 0; } }
@keyframes vis3 { 0%,47% { opacity: 0; } 50% { opacity: 1; } 72% { opacity: 1; } 75% { opacity: 0; } 100% { opacity: 0; } }
@keyframes vis4 { 0%,72% { opacity: 0; } 75% { opacity: 1; } 95% { opacity: 1; } 98.5% { opacity: 0; } 100% { opacity: 0; } }

/* ---- beat 1: ensemble assembly (staggered pops), then corners dim to context ---- */
.ens-keys { animation-name: popKeys; }
.ens-glasses { animation-name: popGlasses; }
.ens-lunch { animation-name: lunchMove; }
.ens-pack { animation-name: popPack; }
.ens-case { animation-name: popCase; }
@keyframes popKeys {
  0%,1% { opacity: 0; transform: scale(.3); } 2.5% { opacity: 1; transform: scale(1.07); } 3.5% { transform: scale(1); }
  25% { opacity: 1; transform: scale(1); } 27% { opacity: .22; transform: scale(.88); }
  96% { opacity: .22; transform: scale(.88); } 99.5%,100% { opacity: 0; transform: scale(.88); } }
@keyframes popGlasses {
  0%,3% { opacity: 0; transform: scale(.3); } 4.5% { opacity: 1; transform: scale(1.07); } 5.5% { transform: scale(1); }
  25% { opacity: 1; transform: scale(1); } 27% { opacity: .22; transform: scale(.88); }
  96% { opacity: .22; transform: scale(.88); } 99.5%,100% { opacity: 0; transform: scale(.88); } }
@keyframes popPack {
  0%,8% { opacity: 0; transform: scale(.3); } 9.5% { opacity: 1; transform: scale(1.07); } 10.5% { transform: scale(1); }
  25% { opacity: 1; transform: scale(1); } 27% { opacity: .22; transform: scale(.88); }
  96% { opacity: .22; transform: scale(.88); } 99.5%,100% { opacity: 0; transform: scale(.88); } }
@keyframes popCase {
  0%,10.5% { opacity: 0; transform: scale(.3); } 12% { opacity: 1; transform: scale(1.07); } 13% { transform: scale(1); }
  25% { opacity: 1; transform: scale(1); } 27% { opacity: .22; transform: scale(.88); }
  96% { opacity: .22; transform: scale(.88); } 99.5%,100% { opacity: 0; transform: scale(.88); } }
/* lunchbox: pops third, then steps forward as protagonist for beats 2-4 */
@keyframes lunchMove {
  0%,5.5% { opacity: 0; transform: translate(0,0) scale(.3); }
  7% { opacity: 1; transform: translate(0,0) scale(1.07); }
  8% { transform: translate(0,0) scale(1); }
  25% { transform: translate(0,0) scale(1); }
  28% { transform: translate(-46px,18px) scale(1.25); }
  75.5% { transform: translate(-46px,18px) scale(1.25); }
  77.5% { transform: translate(-46px,18px) scale(1.34); }
  79.5% { transform: translate(-46px,18px) scale(1.27); }
  96% { opacity: 1; transform: translate(-46px,18px) scale(1.27); }
  99.5%,100% { opacity: 0; transform: translate(-46px,18px) scale(1.27); } }

/* assembly sparkles */
.spark { animation-name: sparkE; opacity: 0; }
.spark.d2 { animation-name: sparkE2; }
@keyframes sparkE { 0%,12.5% { opacity: 0; transform: scale(.2); } 14% { opacity: 1; transform: scale(1.15); } 17% { opacity: 0; transform: scale(1.3); } 100% { opacity: 0; } }
@keyframes sparkE2 { 0%,13.5% { opacity: 0; transform: scale(.2); } 15% { opacity: 1; transform: scale(1.1); } 18% { opacity: 0; transform: scale(1.35); } 100% { opacity: 0; } }

/* beat 2: activate */
.knob { animation-name: knob; }
@keyframes knob { 0%,30% { transform: translateX(0); } 33%,100% { transform: translateX(26px); } }
.track-on { animation-name: trackOn; opacity: 0; }
@keyframes trackOn { 0%,30% { opacity: 0; } 33%,100% { opacity: 1; } }
.ring-draw { stroke-dasharray: 100; stroke-dashoffset: 100; animation-name: ringDraw; }
@keyframes ringDraw { 0%,35% { stroke-dashoffset: 100; } 43%,100% { stroke-dashoffset: 0; } }
.tick-draw { stroke-dasharray: 100; stroke-dashoffset: 100; animation-name: tickDraw; }
@keyframes tickDraw { 0%,41% { stroke-dashoffset: 100; } 46%,100% { stroke-dashoffset: 0; } }

/* beat 3: finder scans */
.phone-in { animation-name: phoneIn; }
@keyframes phoneIn { 0%,50% { transform: translateX(150px) rotate(6deg); opacity: 0; } 52% { opacity: 1; } 56%,100% { transform: translateX(0) rotate(0); opacity: 1; } }
.pulse { animation-name: pulse; opacity: 0; transform-origin: center; }
.pulse.p2 { animation-name: pulse2; } .pulse.p3 { animation-name: pulse3; }
@keyframes pulse { 0%,56% { opacity: 0; transform: scale(.3); } 58% { opacity: .85; } 64% { opacity: 0; transform: scale(2.1); } 100% { opacity: 0; } }
@keyframes pulse2 { 0%,59% { opacity: 0; transform: scale(.3); } 61% { opacity: .85; } 67% { opacity: 0; transform: scale(2.1); } 100% { opacity: 0; } }
@keyframes pulse3 { 0%,62% { opacity: 0; transform: scale(.3); } 64% { opacity: .85; } 70% { opacity: 0; transform: scale(2.1); } 100% { opacity: 0; } }
.bubble-pop { animation-name: bubblePop; opacity: 0; transform-origin: bottom left; }
@keyframes bubblePop { 0%,63% { opacity: 0; transform: scale(.4); } 65.5% { opacity: 1; transform: scale(1.08); } 67%,100% { opacity: 1; transform: scale(1); } }
.dot-t { animation-name: dotT; opacity: 0; }
.dot-t.d2 { animation-name: dotT2; } .dot-t.d3 { animation-name: dotT3; }
@keyframes dotT { 0%,66% { opacity: 0; } 67.5%,100% { opacity: 1; } }
@keyframes dotT2 { 0%,67.5% { opacity: 0; } 69%,100% { opacity: 1; } }
@keyframes dotT3 { 0%,69% { opacity: 0; } 70.5%,100% { opacity: 1; } }

/* beat 4: reunited */
.conf { animation-name: conf; opacity: 0; }
@keyframes conf { 0%,75.5% { opacity: 0; transform: translate(0,0) rotate(0) scale(.6); } 77% { opacity: 1; } 86% { opacity: 0; transform: translate(var(--tx),var(--ty)) rotate(var(--rot)) scale(1); } 100% { opacity: 0; } }
.heart-pop { animation-name: heartPop; opacity: 0; transform-origin: center; }
@keyframes heartPop { 0%,77% { opacity: 0; transform: scale(.3); } 79.5% { opacity: 1; transform: scale(1.15); } 81% { transform: scale(1); } 94% { opacity: 1; transform: translateY(-6px); } 97.5%,100% { opacity: 0; transform: translateY(-7px); } }

/* captions + step dots. Locked Brand Pack V3 step copy — do not reword.
   aria-hidden in the markup: the SVG's role="img" label is the single description
   exposed to assistive tech, so these four crossfading strings are not read out. */
.captions { position: absolute; left: 0; right: 0; bottom: 34px; height: 54px; text-align: center; }
.cap { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; gap: 3px; opacity: 0; animation: var(--loop) linear infinite; }
.cap .step { font-size: 11.5px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--rmp-teal); }
.cap .txt { font-family: var(--font-heading); font-weight: 600; font-size: 18px; color: var(--rmp-dark-teal); }
.cap.c4 .txt { font-family: var(--font-tagline); font-weight: 400; font-size: 26px; color: var(--rmp-teal); transform: skewX(-9deg); }
.c1 { animation-name: vis1; } .c2 { animation-name: vis2; } .c3 { animation-name: vis3; } .c4 { animation-name: vis4; }
@keyframes vis1 { 0% { opacity: 1; } 22% { opacity: 1; } 25% { opacity: 0; } 97% { opacity: 0; } 100% { opacity: 1; } }
.sdots { position: absolute; left: 0; right: 0; bottom: 8px; display: flex; gap: 7px; justify-content: center; }
.sdots span { width: 7px; height: 7px; border-radius: 9999px; background: rgba(15,110,105,.16); animation: var(--loop) linear infinite; }
.sdots .p1 { animation-name: sdot1; } .sdots .p2 { animation-name: sdot2; } .sdots .p3 { animation-name: sdot3; } .sdots .p4 { animation-name: sdot4; }
@keyframes sdot1 { 0% { background: var(--rmp-teal); width: 20px; } 24% { background: var(--rmp-teal); width: 20px; } 26% { background: rgba(15,110,105,.16); width: 7px; } 98% { background: rgba(15,110,105,.16); width: 7px; } 100% { background: var(--rmp-teal); width: 20px; } }
@keyframes sdot2 { 0%,24% { background: rgba(15,110,105,.16); width: 7px; } 26% { background: var(--rmp-teal); width: 20px; } 49% { background: var(--rmp-teal); width: 20px; } 51%,100% { background: rgba(15,110,105,.16); width: 7px; } }
@keyframes sdot3 { 0%,49% { background: rgba(15,110,105,.16); width: 7px; } 51% { background: var(--rmp-teal); width: 20px; } 74% { background: var(--rmp-teal); width: 20px; } 76%,100% { background: rgba(15,110,105,.16); width: 7px; } }
@keyframes sdot4 { 0%,74% { background: rgba(15,110,105,.16); width: 7px; } 76% { background: var(--rmp-teal); width: 20px; } 97% { background: var(--rmp-teal); width: 20px; } 99%,100% { background: rgba(15,110,105,.16); width: 7px; } }

/* ---------------------------------------------------------------------------
   Narrow widths: captions reflow BELOW the stage instead of overlaying it.

   Above 560px the authored reference design applies unchanged — .captions is an
   absolute overlay sitting in the empty lower band of the 480x300 viewBox. That
   works because the caption band is a fixed 88px (54px block + 34px offset) against
   a stage that is tall enough to spare it.

   It stops working as the card narrows, and measurement showed why. Clearance
   between the beat-3 caption and the finder phone (beat 3 carries the longest
   locked string, "If found, the finder scans and contacts you"):

       viewport   card inner   caption lines   clearance
         1440         476            1            +8
        1100/768      592            1           +31
         600          504            1           +14
         560          464            1            +6
         520          424            1            -2   <- crossover
         480          384            2           -25
         390          294            2           -42
         320          224            2           -56

   Two causes compound. The caption band is fixed while the stage scales, so it
   claims 24% of the stage at design size but 40% at 390px. Then below ~520px the
   beat-3 string wraps to two lines and overflows .captions' fixed 54px height
   UPWARD into the artwork — which is why the curve steepens instead of degrading
   gently.

   Retuning the box ratio (taller stage, smaller svg height %) was measured and
   rejected: it shrinks the artwork exactly where it is already smallest, and it is
   a tuned constant that any future caption length would defeat. Reflow makes the
   overlap structurally impossible instead of narrowly avoided — the captions are
   grid-stacked in one cell, so the band is as tall as its tallest caption, whatever
   the copy says. Locked copy, type sizes and crossfade timing are untouched. */
@media (max-width: 560px) {
  /* .stage stops being a fixed-ratio overlay frame and becomes a normal column */
  .stage { aspect-ratio: auto; }
  .stage svg.scenebox { position: static; width: 100%; height: auto; aspect-ratio: 480/300; }
  /* one grid cell for all four captions: they still crossfade on opacity, but the
     band's height now comes from the tallest of them rather than a constant */
  .captions { position: static; height: auto; display: grid; margin-top: 14px; }
  .cap { position: static; grid-area: 1 / 1; }
  .sdots { position: static; margin-top: 12px; }
}

/* Reduced motion: the static composed frame — full bright ensemble, step-1 caption,
   first progress dot active.
   Scoped to .anim-card rather than the reference's page-wide `*`: that universal
   selector was demo scaffolding in spirit, and on a real page it would silently
   disable any other animation the site ever adds. */
@media (prefers-reduced-motion: reduce) {
  .anim-card, .anim-card * { animation: none !important; }
  .anim-card .scene, .anim-card .cap { opacity: 0; }
  .anim-card .c1 { opacity: 1 !important; }
  .anim-card .sdots .p1 { background: var(--rmp-teal); width: 20px; }
}
