/* ============================================================
   Undullify Media — Spark Page
   motion.css
   The four-layer animation model (template v1.1).

     Layer 1 — Ambient .......... always running, 10–40s loops
     Layer 2 — Section reveals .. on IntersectionObserver entry, once
     Layer 3 — Moments .......... one-off cinematic, 800–1500ms
     Layer 4 — Micro-interactions ... user input (see styles.css :hover)

   Reusable keyframes + per-band animation blocks are appended here as
   each band is built in Stage 2, under labelled comment headers.

   Universal rule: animate transform/opacity only. Never width/height/top/left.
   ============================================================ */


/* ============================================================
   LAYER 2 — SECTION REVEAL BASELINE (every band)
   Default threshold 0.15 (set in motion.js). Opacity 0→1 + translateY 12px→0,
   500ms ease-out, once per session. JS adds .is-revealed when the section
   enters the viewport.
   ============================================================ */
.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
  will-change: opacity, transform;
}
.reveal.is-revealed {
  opacity: 1;
  transform: none;
}

/* Staggered children inside a revealed section (80–120ms apart).
   Apply .reveal-stagger to a parent; children animate when parent gets
   .is-revealed. Per-child delay is set inline or via :nth-child blocks
   added per band. */
.reveal-stagger > * {
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}
.reveal-stagger.is-revealed > * {
  opacity: 1;
  transform: none;
}


/* ============================================================
   LAYER 1 — AMBIENT (shared keyframes)
   Per-band ambient blocks (hero shimmer, paint breath, portrait
   rotation, etc.) are appended during Stage 2. Shared primitives live
   here so multiple bands can reuse them.
   ============================================================ */


@keyframes soft-pulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 0.6; }
}

@keyframes gentle-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}


/* ============================================================
   LAYER 3 — MOMENTS (shared keyframes)
   Band-specific moment sequences (polaroid arrival, conveyor swap,
   equation build, spark split, aeroplane flight) are appended during
   Stage 2. Shared primitives live here.
   ============================================================ */

@keyframes fade-rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

@keyframes stamp-in {
  0%   { opacity: 0; transform: scale(0); }
  70%  { opacity: 1; transform: scale(1.15); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }


/* ============================================================
   LAYER 4 — MICRO-INTERACTIONS
   Hover/focus transitions live with their components in styles.css
   (.btn--primary, .link-cta). They are retained under reduced motion.
   ============================================================ */


/* ============================================================
   PER-BAND ANIMATION BLOCKS (appended in Stage 2)
   ------------------------------------------------------------
   /* >>> Band 1 Hero — appended Stage 2 */
   /* >>> Band 2 Diagnostic — appended Stage 2 */
   /* ... through Band 13 ...                                  */
   ============================================================ */


/* ============================================================
   UNIVERSAL REDUCED-MOTION OVERRIDE
   Layer 1 stops. Layer 2 + Layer 3 jump to end state. Layer 4 retained.
   Per-band blocks add their own reduced-motion resets above this rule
   as needed; this is the catch-all baseline.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {

  /* Layer 2 — show end state immediately, no transition */
  .reveal,
  .reveal-stagger > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  /* Layer 1 + Layer 3 — kill all looping/keyframe animation */
  *, *::before, *::after {
    animation: none !important;
  }
}


/* ============================================================
   >>> BAND 1 — HERO  (Layer 3 load reveal + Layer 1 ambient)
   Pure-CSS, runs on page load. Keyframes hold the hidden "from"
   state during their start delay (fill: both); resting CSS is the
   visible end state, so reduced-motion shows the final composition.
   ============================================================ */
/* Pyramid, beam and cosmic load STATIC (no cheesy scale/extend reveal).
   All life comes from the continuous ambient layers below. */
/* beam: gentle global pulse + a slow left→right transparency shimmer */
.hero__beam    { animation: beam-pulse 13s ease-in-out infinite, beam-shimmer 8s linear infinite; }
.hero__headline { animation: fade-rise-16 600ms ease-out 300ms both; }
/* badge entrance waits for the web font (fonts-ready added by motion.js): the
   sticker sizes to its text, so playing it before the Cabinet Grotesk swap made
   it shrink mid-animation. Hidden until then. */
.hero__badge    { opacity: 0; }
html.fonts-ready .hero__badge { animation: badge-in 500ms ease-out 120ms both; }
/* warm gold glow behind the pyramid — very subtle slow breath, offset cadence
   so it doesn't throb in lockstep with the apex glow */
.hero__back-glow { opacity: 0; animation: back-glow-pulse 11s ease-in-out infinite; }
/* blooms from 0 → 15% opacity while expanding outward, then fades as it spreads */
@keyframes back-glow-pulse {
  0%   { opacity: 0;    transform: translate(-50%,-50%) scale(0.8); }
  45%  { opacity: 0.65; transform: translate(-50%,-50%) scale(1.05); }
  100% { opacity: 0;    transform: translate(-50%,-50%) scale(1.34); }
}
/* Ken Burns parallax: the cosmic starfield drifts + zooms VERY slowly behind
   the STATIC pyramid/beam — real depth from the separate layers. Kept long
   (44s) and gentle so it composes with the beam shimmer + twinkles. The min
   scale (1.04) always covers the stage, so panning never reveals an edge. */
.hero__cosmic {
  transform-origin: 50% 42%;
  will-change: transform;
  animation: hero-kenburns 54s ease-in-out infinite;
}
@keyframes hero-kenburns {
  0%   { transform: scale(1.04) translate(0, 0); }
  50%  { transform: scale(1.10) translate(-1.4%, -1.1%); }
  100% { transform: scale(1.04) translate(0, 0); }
}

@keyframes fade-rise-16 { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes badge-in {
  from { opacity: 0; transform: translateX(-50%) rotate(-6deg); }
  to   { opacity: 1; transform: translateX(-50%) rotate(-3deg); }
}

/* beam transparency shimmer sweeps L→R; moves exactly one mask tile (200%) so
   the loop is seamless (no phase jump), with a natural opaque gap between passes */
@keyframes beam-shimmer {
  from { -webkit-mask-position: 100% 0; mask-position: 100% 0; }
  to   { -webkit-mask-position: -100% 0; mask-position: -100% 0; }
}
/* beam breathes — a slow, gentle pulse (not a flicker) */
@keyframes beam-pulse {
  0%,100% { opacity: 0.7; }
  50%     { opacity: 1; }
}
@keyframes star-twinkle-kf { 0%,100% { opacity: 0.55; transform: scale(1); } 50% { opacity: 1; transform: scale(1.15); } }
/* apex spark: absent on load, then pops in (scaling up from centre) the moment
   the user starts scrolling — motion.js adds .is-scrolled to [data-hero] */
.star-twinkle--1 { opacity: 0; transform: scale(0); }
/* pops in scaling from centre, then HOLDS at full 100% opacity (no twinkle) */
[data-hero].is-scrolled .star-twinkle--1 {
  animation: spark-pop 420ms cubic-bezier(0.2, 1.35, 0.35, 1) both;
}
@keyframes spark-pop {
  0%   { opacity: 0; transform: scale(0); }
  60%  { opacity: 1; transform: scale(1.28); }
  100% { opacity: 1; transform: scale(1); }
}
.star-twinkle--2 { animation: star-twinkle-kf 6.5s ease-in-out 1.2s infinite; }
.star-twinkle--3 { animation: star-twinkle-kf 4.5s ease-in-out 0.6s infinite; }


/* ============================================================
   >>> BAND 2 — DIAGNOSIS  (Layer 3 two-phase)
   Phase 1 (threshold 0.4): polaroids arrive staggered, then names,
   then notes. Phase 2 (threshold 0.7): fade overlays + closer.
   ============================================================ */
[data-diagnosis] .polaroid { opacity: 0; }
[data-diagnosis].is-in .polaroid {
  animation: polaroid-arrive 500ms ease-out both;
}
[data-diagnosis].is-in .polaroid--james  { animation-delay: 0ms; }
[data-diagnosis].is-in .polaroid--maya    { animation-delay: 150ms; }
[data-diagnosis].is-in .polaroid--andrew  { animation-delay: 300ms; }
[data-diagnosis].is-in .polaroid--priya   { animation-delay: 450ms; }
@keyframes polaroid-arrive {
  from { opacity: 0; transform: translateY(16px) scale(0.96) rotate(var(--rot, 0deg)); }
  to   { opacity: 1; }
}
/* preserve each polaroid's resting rotation through the keyframe */
[data-diagnosis].is-in .polaroid--james  { --rot: -4deg; }
[data-diagnosis].is-in .polaroid--maya    { --rot: 3deg; }
[data-diagnosis].is-in .polaroid--andrew  { --rot: -2deg; }
[data-diagnosis].is-in .polaroid--priya   { --rot: 4deg; }

/* names baked into the artwork; the typed name is hidden (handled in styles.css).
   The handwritten NOTES stay hidden until their polaroid swaps (below). */
[data-diagnosis] .polaroid__note { opacity: 0; transform: translateY(6px); transition: opacity 500ms ease-out, transform 500ms ease-out; }

/* idle micro-motion (after arrival) */
[data-diagnosis].is-in .polaroid--james  { animation: polaroid-arrive 500ms ease-out both, idle-1 6s ease-in-out 2s infinite; }
[data-diagnosis].is-in .polaroid--maya    { animation: polaroid-arrive 500ms ease-out 150ms both, idle-2 7s ease-in-out 2.4s infinite; }
[data-diagnosis].is-in .polaroid--andrew  { animation: polaroid-arrive 500ms ease-out 300ms both, idle-1 5.5s ease-in-out 2.8s infinite; }
[data-diagnosis].is-in .polaroid--priya   { animation: polaroid-arrive 500ms ease-out 450ms both, idle-2 6.5s ease-in-out 2.2s infinite; }
@keyframes idle-1 { 0%,100% { translate: 0 0; } 50% { translate: 1px -2px; } }
@keyframes idle-2 { 0%,100% { translate: 0 0; } 50% { translate: -1px 2px; } }

/* SCROLL-DRIVEN swap: motion.js adds .is-swap-1 … .is-swap-4 cumulatively as
   the reader scrolls through the band. Each step fades that polaroid to its
   dulled version AND brings in its handwritten note at the same moment. */
[data-diagnosis].is-swap-1 .polaroid--james  .polaroid__fade,
[data-diagnosis].is-swap-2 .polaroid--maya    .polaroid__fade,
[data-diagnosis].is-swap-3 .polaroid--andrew  .polaroid__fade,
[data-diagnosis].is-swap-4 .polaroid--priya   .polaroid__fade { opacity: 1; }
[data-diagnosis].is-swap-1 .polaroid--james  .polaroid__note,
[data-diagnosis].is-swap-2 .polaroid--maya    .polaroid__note,
[data-diagnosis].is-swap-3 .polaroid--andrew  .polaroid__note,
[data-diagnosis].is-swap-4 .polaroid--priya   .polaroid__note { opacity: 1; transform: none; }

.band-closer[data-diagnosis-closer] { opacity: 0; transform: translateY(12px); transition: opacity 600ms ease-out, transform 600ms ease-out; }
[data-diagnosis].is-swap-5 .band-closer[data-diagnosis-closer] { opacity: 1; transform: none; }


/* ============================================================
   >>> BAND 3 — CREDIBILITY  (Layer 3 stagger + Layer 1 rotation)
   ============================================================ */
/* logos are static (no animate-in); only the founder block reveals */
[data-credibility] .founder__portrait,
[data-credibility] .founder__signature,
[data-credibility] .founder__bio { opacity: 0; }
[data-credibility].is-in .founder__portrait  { animation: fade-scale 600ms ease-out both; }
[data-credibility].is-in .founder__signature { animation: fade-rise-6 500ms ease-out 200ms both; }
[data-credibility].is-in .founder__bio       { animation: fade-rise-8 500ms ease-out 400ms both; }
@keyframes fade-rise-8 { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes fade-rise-6 { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes fade-scale  { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: none; } }
/* portrait circles + overlay rotation is scroll-driven (see initScrollDriven in
   motion.js → --founder-rot); no continuous spin */

/* Paint ambient (credibility, answers, cta) */
.paint-bg__halftones { animation: halftone-breath 14s ease-in-out infinite; }
.paint-bg__splatters { animation: splatter-drift 28s ease-in-out infinite; }
@keyframes halftone-breath { 0%,100% { opacity: 0.6; } 50% { opacity: 0.95; } }
@keyframes splatter-drift { 0%,100% { transform: translate(0,0); } 50% { transform: translate(10px,-8px); } }


/* ============================================================
   >>> BAND 4 — REFRAME / CONVEYOR  (Layer 3)
   ============================================================ */
/* Impact graphic punches in. Animate the WRAP (always present) — not the
   inlined SVG, which is swapped in asynchronously and would miss the trigger. */
[data-conveyor] .conveyor__impact-wrap { opacity: 0; }
[data-conveyor].is-in .conveyor__impact-wrap { animation: punch-in 700ms var(--ease-overshoot) both; }
@keyframes punch-in { 0% { opacity: 0; transform: scale(0.95); } 60% { opacity: 1; transform: scale(1.02); } 100% { opacity: 1; transform: scale(1); } }

/* robot-arm-1 → robot-arm-2 crossfade is handled via transitions in styles.css */

[data-antilist] .anti-list__item { opacity: 0; }
[data-antilist].is-in .anti-list__item { animation: fade-in 400ms ease-out 1200ms both; }
[data-antilist].is-in .anti-list__x { animation: stamp-in 350ms var(--ease-overshoot) both; }
[data-antilist].is-in .anti-list__item:nth-child(1) .anti-list__x { animation-delay: 1700ms; }
[data-antilist].is-in .anti-list__item:nth-child(2) .anti-list__x { animation-delay: 1900ms; }
[data-antilist].is-in .anti-list__item:nth-child(3) .anti-list__x { animation-delay: 2100ms; }
[data-antilist].is-in .anti-list__item:nth-child(4) .anti-list__x { animation-delay: 2300ms; }


/* ============================================================
   >>> BAND 5 — VISUAL PROOF  (Layer 3 + Layer 1)
   ============================================================ */
[data-proof] .proof__hand { opacity: 0; }
[data-proof].is-in .proof__hand { animation: fade-scale-9 700ms ease-out both; }
@keyframes fade-scale-9 { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: none; } }
/* spark fades/scales in and holds at full (100%) opacity — no pulse */
[data-proof].is-in .proof__spark { animation: spark-in 600ms var(--ease-out) 900ms both; }
@keyframes spark-in { from { opacity: 0; transform: scale(0.6); } to { opacity: 1; transform: scale(1); } }
/* Layer 1: halftone rotation 60s */
/* halftone rotation is scroll-driven (motion.js sets --halftone-rot) */
.proof__halftone { transform: rotate(var(--halftone-rot, 0deg)); transform-origin: 50% 50%; will-change: transform; }


/* ============================================================
   >>> BAND 6 — ORIGIN  (Layer 2 baseline; bold beat delayed)
   ============================================================ */
[data-band6] {} /* baseline .reveal handled globally */
.origin__beat--bold { transition-delay: 200ms; }


/* ============================================================
   >>> BAND 7 — METHOD  (Layer 3 + Layer 1 float)
   ============================================================ */
[data-method] .method__bubble { opacity: 0; }
[data-method].is-in .method__bubble { animation: fade-scale-9 600ms var(--ease-out) both, gentle-float 5s ease-in-out 800ms infinite; }


/* ============================================================
   >>> BAND 8 — PYRAMID  (Layer 3 + Layer 1 pulse)
   ============================================================ */
/* fades/scales in once and holds at full (100%) opacity — no pulse/fade-out */
[data-pyramid].is-in .pyramid__spark { animation: pyramid-spark-in 600ms var(--ease-out) both; }
/* keep the apex-centring translate through the scale/fade (fade-scale-9 would
   reset transform to none and knock the spark off the apex) */
@keyframes pyramid-spark-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}


/* ============================================================
   >>> BAND 9 — OFFER  (Layer 3 equation build)
   ============================================================ */
/* The £999 equation + scarcity line are STATIC (no build-in animation).
   Only the four deliverable icons (and their text) animate in, on scroll. */
[data-deliverables].is-in .deliverable { animation: fade-scale-9 500ms ease-out both; }
[data-deliverables].is-in .deliverable:nth-child(1) { animation-delay: 0ms; }
[data-deliverables].is-in .deliverable:nth-child(2) { animation-delay: 120ms; }
[data-deliverables].is-in .deliverable:nth-child(3) { animation-delay: 240ms; }
[data-deliverables].is-in .deliverable:nth-child(4) { animation-delay: 360ms; }


/* ============================================================
   >>> BAND 10 — NOT FOR YOU  (Layer 3 sun rise + Layer 1 pulse)
   ============================================================ */
/* sun fades in slowly (no movement) when the reader is ~half-way down the
   section, and fades back out on scroll-up (motion.js toggles .is-sun). Uses a
   transition (see .not-for__sun) so it's reversible. */
[data-notfor].is-sun .not-for__sun { opacity: 1; }


/* ============================================================
   >>> BAND 11 — TWO ANSWERS  (Layer 2 .reveal stagger via delays)
   ============================================================ */
[data-answers].is-in .answers__col:nth-child(1) .answers__q { transition-delay: 300ms; }
[data-answers].is-in .answers__col:nth-child(1) .answers__a { transition-delay: 500ms; }
[data-answers].is-in .answers__col:nth-child(2) .answers__q { transition-delay: 800ms; }
[data-answers].is-in .answers__col:nth-child(2) .answers__a { transition-delay: 1000ms; }


/* ============================================================
   >>> BAND 12 — CLOSE  (Layer 3 three-beat)
   ============================================================ */
[data-close] .close-line--top    { opacity: 0; }
[data-close] .close-line--bottom  { opacity: 0; }
[data-close] .close-spark         { opacity: 0; }
[data-close].is-in .close-line--top    { animation: slide-in-left 700ms var(--ease-out) both; }
[data-close].is-in .close-spark        { animation: fade-in 500ms ease-out 400ms both; }
[data-close].is-in .close-line--bottom { animation: slide-in-right 700ms var(--ease-out) 900ms both; }
/* the "spark" split (upper half peeling open) is scroll-driven — see
   .is-split in styles.css, triggered by motion.js at the viewport middle */
@keyframes slide-in-left  { from { opacity: 0; transform: translateX(-40px); } to { opacity: 1; transform: none; } }
@keyframes slide-in-right { from { opacity: 0; transform: translateX(40px); } to { opacity: 1; transform: none; } }


/* ============================================================
   >>> BAND 13 — CTA  (Layer 3 reveal + aeroplane flight)
   ============================================================ */
[data-cta] .cta__prompt { /* uses .reveal baseline */ }
/* signature pen-draw is JS-driven (initSignatureDraw in motion.js): each mask
   stroke uses its REAL length so the pen moves at a constant speed, drawn in
   order. Default (no JS / reduced motion) shows it fully drawn. */
.cta__signature .sig-stroke { stroke-dashoffset: 0; }
[data-cta] .cta__button { opacity: 0; }
[data-cta].is-in .cta__button { animation: fade-scale-9 500ms var(--ease-overshoot) 400ms both; }
[data-cta] .cta__aeroplane { opacity: 0; }
[data-cta].is-in .cta__aeroplane { opacity: 1; }
/* the dotted trail draws on (from its far end up toward the button) while the
   plane flies along it, leading the line, and stops at the end near the button */
[data-cta].is-in .cta__aeroplane #trail-reveal-path {
  animation: trail-draw 1500ms var(--ease-out) 600ms both;
}
[data-cta].is-in .cta__aeroplane #aeroplane {
  animation: plane-fly 1500ms var(--ease-out) 600ms both;
  offset-path: path("M107.5,52.6c-7.2,3-46.3,15.8-35.8,41.8s46.3,12,39.5-2.6S60.5,59.4,1,106.5");
  offset-rotate: 0deg;
}
@keyframes trail-draw { from { stroke-dashoffset: -100; } to { stroke-dashoffset: 0; } }
@keyframes plane-fly  { from { offset-distance: 100%; opacity: 0; } to { offset-distance: 0%; opacity: 1; } }
/* aeroplane is an <img>; #trail/#aeroplane animation requires inline SVG —
   see motion.js note. The <img> simply fades in (acceptable fallback). */


/* ============================================================
   REDUCED-MOTION RESETS for band-specific opacity:0 resting states
   (the universal block kills animations; these force end-state).
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .hero__layer, .hero__headline, .hero__badge,
  [data-diagnosis] .polaroid,
  [data-diagnosis] .polaroid__name,
  [data-diagnosis] .polaroid__note,
  .band-closer[data-diagnosis-closer],
  [data-credibility] .logo-row li,
  [data-credibility] .founder__portrait,
  [data-credibility] .founder__signature,
  [data-credibility] .founder__bio,
  [data-conveyor] .conveyor__impact-wrap,
  [data-antilist] .anti-list__item,
  [data-antilist] .anti-list__x,
  [data-proof] .proof__hand,
  [data-proof] .proof__spark,
  [data-method] .method__bubble,
  [data-pyramid] .pyramid__spark,
  .equation__part, .equation__op,
  .scarcity,
  [data-deliverables] .deliverable,
  [data-notfor] .not-for__sun,
  [data-close] .close-line--top,
  [data-close] .close-line--bottom,
  [data-close] .close-spark,
  [data-cta] .cta__button,
  [data-cta] .cta__signature,
  [data-cta] .cta__aeroplane {
    opacity: 1 !important;
    transform: none !important;
  }
  /* signature + plane rest in their end states, no animation */
  [data-cta] .cta__signature .sig-stroke { stroke-dashoffset: 0 !important; }
  [data-cta] .cta__aeroplane #aeroplane { offset-distance: 0% !important; }
  [data-cta] .cta__aeroplane #trail-reveal-path { stroke-dashoffset: 0 !important; }
  /* Conveyor: show the revealed state (arm-2) per spec */
  [data-conveyor] .conveyor__arm--1 { opacity: 0 !important; }
  [data-conveyor] .conveyor__arm--2 { opacity: 1 !important; }
  /* Close: spark rests in its split end state (upper half peeled open) */
  [data-close] .close-spark__half--upper { transform: translateX(-50%) rotate(-13deg) !important; }
  [data-close] .close-spark__half--lower { transform: translateX(-50%) rotate(0deg) !important; }
  /* Re-assert positioning transforms the blanket reset above would otherwise wipe */
  .hero__badge { transform: translateX(-50%) rotate(-4deg) !important; }
  [data-pyramid] .pyramid__spark { transform: translate(-50%, -50%) !important; }
  /* apex spark simply present (no pop) for reduced motion */
  .star-twinkle--1 { opacity: 1 !important; transform: none !important; }
  /* Make scroll-triggered transitions instant (jump to end state) */
  .polaroid__fade,
  .conveyor__arm--1, .conveyor__arm--2,
  .band-closer[data-diagnosis-closer] { transition: none !important; }
}
