/* =====================================================================
 * Landing motion layer — the v2 features the founder approved, applied
 * to the MAIN landing page: the scroll-reveal system and the
 * self-drawing step SVGs (with their stat count-ups). Deliberately NOT
 * here: the serif display accents (rejected), v2's marquee/aurora/nav
 * effects (v2-only experiments). Lives in its own file so the frozen
 * ui/static/landing.css never changes.
 *
 * Safety model (mirrors landing_v2.css; see landing_motion.js):
 *  1. JS adds .js-anim to <html> ONLY when IntersectionObserver exists
 *     and prefers-reduced-motion is not "reduce". Every hidden initial
 *     state is scoped under .js-anim → no JS = fully visible page.
 *  2. Every `animation:` lives inside
 *     @media (prefers-reduced-motion: no-preference).
 *  3. landing.css's global reduce kill still applies; extended at the
 *     bottom so the SVG loops can't spin at 0.01ms.
 * ===================================================================== */

/* JetBrains Mono, self-hosted (shipped with the v2 deploy). glass.css's
   --mono token already names it first — these @font-faces light it up
   (the Google Fonts link is CSP-blocked, so without this the landing
   silently falls back to system mono). */
@font-face {
  font-family: "JetBrains Mono";
  src: url("/static/fonts/JetBrainsMono-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/static/fonts/JetBrainsMono-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/static/fonts/JetBrainsMono-SemiBold.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}

/* ---- Motion tokens ---------------------------------------------------- */
:root {
  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);    /* expo-out — entrances */
  --ease-inout:  cubic-bezier(0.65, 0, 0.35, 1);   /* line draws, sweeps  */
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);/* pops                */
  --dur-med: 0.7s;
  --dur-slow: 1.1s;
}

/* =====================================================================
 * Scroll-reveal framework
 *   data-reveal="up" (default) | "fade" | "scale" | "mask"
 *   optional inline --reveal-delay; parents with [data-stagger] get --si
 *   written on children by JS. One-shot: .is-in never re-hides.
 * ===================================================================== */
.js-anim [data-reveal] {
  opacity: 0;
  transition:
    opacity var(--dur-med) var(--ease-out),
    transform var(--dur-med) var(--ease-out);
  transition-delay: calc(var(--si, 0) * 70ms + var(--reveal-delay, 0s));
}
.js-anim [data-reveal="up"]    { transform: translateY(26px); }
.js-anim [data-reveal="scale"] { transform: translateY(18px) scale(0.96); }
/* Masked headline reveal: the clip animates on an INNER wrapper, never on
   the observed element itself — Chromium's IntersectionObserver treats a
   fully clip-pathed box as zero-area and would never fire. Negative insets
   leave room for ascenders/descenders. */
.js-anim [data-reveal="mask"] { opacity: 1; transform: none; }
.js-anim [data-reveal="mask"] .mask-inner {
  display: block;
  transform: translateY(12px);
  clip-path: inset(-8% -3% 100% -3%);
  transition:
    transform var(--dur-slow) var(--ease-out),
    clip-path var(--dur-slow) var(--ease-out);
  transition-delay: inherit;
}
.js-anim [data-reveal="mask"].is-in .mask-inner {
  transform: none;
  clip-path: inset(-8% -3% -14% -3%);
}
.js-anim [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* =====================================================================
 * Step SVG primitives — used by the four inline illustrations.
 *   .draw    paths with pathLength="1": stroke draws itself
 *   .pop     scale-in at center  |  .pop-up  rise-in
 *   .fadein  opacity in          |  .type    clip-path typing
 * Hidden states ONLY under .js-anim; entrance fires when IO marks the
 * parent .stage.is-in. Loops are separate selectors, delayed past the
 * entrance. Without .js-anim the final frame renders statically.
 * ===================================================================== */
.step-svg .lbl  { font-family: var(--mono); font-size: 11px; fill: var(--muted); letter-spacing: 0.08em; }
.step-svg .lbl-sm { font-family: var(--mono); font-size: 9px;  fill: var(--muted); letter-spacing: 0.10em; }
.step-svg .val  { font-family: var(--mono); font-size: 13px; font-weight: 500; fill: currentColor; }
.step-svg .acc  { stroke: var(--accent); }
.step-svg .acc-fill { fill: var(--accent); }
.step-svg .tint { fill: var(--brand-50); }

.js-anim .step-svg .draw   { stroke-dasharray: 1; stroke-dashoffset: 1.02; }
.js-anim .step-svg .pop    { opacity: 0; transform: scale(0.6); transform-box: fill-box; transform-origin: center; }
.js-anim .step-svg .pop-up { opacity: 0; transform: translateY(14px); }
.js-anim .step-svg .fadein { opacity: 0; }
.js-anim .step-svg .type   { clip-path: inset(0 100% 0 0); }
/* Loop-element setup states — the per-step loop rules below animate them in. */
.js-anim .step-svg .beam,
.js-anim .step-svg .caret,
.js-anim .step-svg .sparkle-ring,
.js-anim .step-svg .live-dot,
.js-anim .step-svg .halo,
.js-anim .step-svg .recv-dot,
.js-anim .step-svg .click-ripple,
.js-anim .step-svg .live-pulse { opacity: 0; }
.js-anim .step-svg .chip-bob,
.js-anim .step-svg .cursor {
  opacity: 0;
  transform: scale(0.6);
  transform-box: fill-box;
  transform-origin: center;
}

@media (prefers-reduced-motion: no-preference) {
  @keyframes lm-fade    { from { opacity: 0; } to { opacity: 1; } }
  @keyframes lm-draw    { to { stroke-dashoffset: 0; } }
  @keyframes lm-pop     { to { opacity: 1; transform: scale(1); } }
  @keyframes lm-pop-up  { to { opacity: 1; transform: translateY(0); } }
  @keyframes lm-type    { to { clip-path: inset(0 -2% 0 0); } }

  .js-anim .stage.is-in .step-svg .draw   { animation: lm-draw 1.1s var(--ease-inout) var(--d, 0s) forwards; }
  .js-anim .stage.is-in .step-svg .draw-long { animation-duration: 1.4s; }
  .js-anim .stage.is-in .step-svg .pop    { animation: lm-pop 0.5s var(--ease-spring) var(--d, 0s) forwards; }
  .js-anim .stage.is-in .step-svg .pop-up { animation: lm-pop-up 0.6s var(--ease-out) var(--d, 0s) forwards; }
  .js-anim .stage.is-in .step-svg .fadein { animation: lm-fade 0.6s ease var(--d, 0s) forwards; }
  .js-anim .stage.is-in .step-svg .type   { animation: lm-type 0.5s steps(14, end) var(--d, 0s) forwards; }

  /* ---- per-step loops (start after the entrance settles) ---- */
  /* 01 SCAN: beam sweeps the card; middle file chip bobs; receive dot pulses */
  @keyframes lm-beam { to { transform: translateY(236px); } }
  .js-anim .stage.is-in #step-svg-scan .beam {
    animation: lm-fade 0.6s ease 1.3s forwards,
               lm-beam 2.6s var(--ease-inout) 1.6s infinite alternate;
  }
  @keyframes lm-bob { to { transform: translateY(-4px); } }
  .js-anim .stage.is-in #step-svg-scan .chip-bob {
    animation: lm-pop 0.5s var(--ease-spring) 0.95s forwards,
               lm-bob 3.2s ease-in-out 2s infinite alternate;
  }
  @keyframes lm-ping { 0% { opacity: 0.9; transform: scale(0.4); } 70% { opacity: 0; transform: scale(1.6); } 100% { opacity: 0; transform: scale(1.6); } }
  .js-anim .stage.is-in #step-svg-scan .recv-dot {
    transform-box: fill-box; transform-origin: center;
    animation: lm-ping 2.6s ease-out 2s infinite;
  }
  /* 02 IDENTIFY: caret blinks; sparkle ring rotates; callout rings pulse */
  @keyframes lm-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
  .js-anim .stage.is-in #step-svg-identify .caret {
    animation: lm-fade 0.3s ease 2.1s forwards, lm-blink 1.1s steps(1) 2.2s infinite;
  }
  @keyframes lm-rotate { to { transform: rotate(360deg); } }
  .js-anim .stage.is-in #step-svg-identify .sparkle-ring {
    transform-box: fill-box; transform-origin: center;
    animation: lm-fade 0.6s ease 0.6s forwards, lm-rotate 10s linear 1s infinite;
  }
  @keyframes lm-ring-pulse { from { opacity: 0.45; } to { opacity: 1; } }
  .js-anim .stage.is-in #step-svg-identify .ring-pulse {
    animation: lm-draw 1.1s var(--ease-inout) var(--d, 0s) forwards,
               lm-ring-pulse 4s ease-in-out 2.4s infinite alternate;
  }
  /* 03 PRICE: live dot blinks; end-point halo pings */
  .js-anim .stage.is-in #step-svg-price .live-dot {
    animation: lm-fade 0.4s ease 0.5s forwards, lm-blink 1.6s steps(1) 2.4s infinite;
  }
  .js-anim .stage.is-in #step-svg-price .halo {
    transform-box: fill-box; transform-origin: center;
    animation: lm-ping 2.2s ease-out 2.4s infinite;
  }
  /* 04 PUBLISH: cursor clicks once then floats; LIVE dots pulse */
  @keyframes lm-click { 0% { transform: scale(1); } 40% { transform: scale(0.82); } 100% { transform: scale(1); } }
  .js-anim .stage.is-in #step-svg-publish .cursor {
    transform-box: fill-box; transform-origin: center;
    animation: lm-pop 0.5s var(--ease-spring) 0.45s forwards,
               lm-click 0.35s var(--ease-inout) 0.7s 1,
               lm-bob 3.4s ease-in-out 2s infinite alternate;
  }
  .js-anim .stage.is-in #step-svg-publish .click-ripple {
    transform-box: fill-box; transform-origin: center;
    animation: lm-ping 0.75s ease-out 0.72s 1 forwards;
  }
  @keyframes lm-live-pulse { from { opacity: 0.35; } to { opacity: 1; } }
  .js-anim .stage.is-in #step-svg-publish .live-pulse {
    animation: lm-fade 0.4s ease var(--d, 1.4s) forwards,
               lm-live-pulse 2.8s ease-in-out calc(var(--d, 1.4s) + 1s) infinite alternate;
  }
}

/* =====================================================================
 * Reduced-motion extension — landing.css squashes durations to 0.01ms;
 * cap iteration counts too and re-assert full visibility for every
 * hidden state this layer introduces.
 * ===================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-iteration-count: 1 !important; }
  .js-anim [data-reveal] { opacity: 1 !important; transform: none !important; clip-path: none !important; }
  .js-anim [data-reveal="mask"] .mask-inner { transform: none !important; clip-path: none !important; }
  .js-anim .step-svg .draw { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
  .js-anim .step-svg .pop,
  .js-anim .step-svg .pop-up,
  .js-anim .step-svg .fadein,
  .js-anim .step-svg .beam,
  .js-anim .step-svg .caret,
  .js-anim .step-svg .sparkle-ring,
  .js-anim .step-svg .live-dot,
  .js-anim .step-svg .live-pulse,
  .js-anim .step-svg .chip-bob,
  .js-anim .step-svg .cursor { opacity: 1 !important; transform: none !important; }
  /* Ping-style halos read as stray rings in a static frame — keep them off. */
  .js-anim .step-svg .halo,
  .js-anim .step-svg .recv-dot,
  .js-anim .step-svg .click-ripple { opacity: 0 !important; }
  .js-anim .step-svg .type { clip-path: none !important; }
}
