/* ============================================================
   IALF CPI™ — Motion
   ------------------------------------------------------------
   Three-state strategy:
     1. No JS            → everything renders in its FINAL state.
     2. JS + scroll-timeline support (html.sdt)    → native CSS
        scroll-driven animations, compositor-only, zero main thread.
     3. JS, no support   (html.no-sdt)             → IntersectionObserver
        adds .is-visible; identical visual result.
   Nothing is ever left at opacity:0 without a path to visible.
   ============================================================ */

@layer motion {

/* ------------------------------------------------------------
   HOUSE REVEAL GRAMMAR
   opacity + 24px rise + 6px blur · 1100ms · editorial ease
   ------------------------------------------------------------ */
/* No blur. A 6px blur on display type looks like a rendering fault
   while it is mid-scroll, and on a scroll-driven timeline the reader
   controls how long they sit inside that state. Rise and fade only. */
@keyframes reveal {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes reveal-x {
  from { opacity: 0; transform: translateX(32px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes reveal-scale {
  from { opacity: 0; transform: scale(.955); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes curtain {
  from { clip-path: inset(0 0 100% 0 round var(--radius-images)); }
  to   { clip-path: inset(0 0 0 0 round var(--radius-images)); }
}
@keyframes counter-scale {
  from { transform: scale(1.14); }
  to   { transform: scale(1); }
}
@keyframes wipe-x {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
@keyframes draw-x   { from { --draw-x: 0; }   to { --draw-x: 1; } }
@keyframes progress { from { --progress: 0; } to { --progress: 1; } }

/* ---------- State 2: native scroll-driven ---------- */
@supports (animation-timeline: view()) {
  html.sdt .reveal,
  html.sdt .reveal-x,
  html.sdt .reveal-scale {
    animation-timeline: view();
    animation-range: entry 4% cover 32%;
    animation-fill-mode: both;
    animation-duration: 1ms; /* progress is driven by scroll, not time */
    animation-timing-function: var(--ease-editorial);
    animation-delay: 0s;
  }
  html.sdt .reveal       { animation-name: reveal; }
  html.sdt .reveal-x     { animation-name: reveal-x; }
  html.sdt .reveal-scale { animation-name: reveal-scale; }

  /* Stagger: shift each child's range rather than using a delay,
     because a delay has no meaning on a scroll timeline. */
  html.sdt .stagger > * {
    animation: reveal 1ms var(--ease-editorial) both;
    animation-timeline: view();
    animation-range: entry calc(2% + var(--i, 0) * 5%) cover calc(28% + var(--i, 0) * 5%);
  }

  /* Scroll-linked progress drivers */
  html.sdt [data-progress] {
    animation: progress 1ms linear both;
    animation-timeline: view();
    animation-range: entry 24% cover 68%;
  }
  html.sdt [data-draw] {
    animation: draw-x 1ms linear both;
    animation-timeline: view();
    animation-range: entry 18% cover 52%;
  }

  /* Media plates: curtain-lift with a counter-scaling interior */
  html.sdt .plate--curtain {
    animation: curtain 1ms var(--ease-editorial) both;
    animation-timeline: view();
    animation-range: entry 6% cover 40%;
  }
  html.sdt .plate--curtain > img,
  html.sdt .plate--curtain > video,
  html.sdt .plate--curtain > .plate__art {
    animation: counter-scale 1ms var(--ease-editorial) both;
    animation-timeline: view();
    animation-range: entry 6% cover 46%;
  }

  /* Hero planes — independent depth rates against the root scroller */
  html.sdt .hero__ghost,
  html.sdt .hero__copy,
  html.sdt .cluster,
  html.sdt .showcase,
  html.sdt .showcase__portrait,
  html.sdt .frag--chip { animation-timeline: scroll(root); animation-range: 0 92vh; animation-fill-mode: both; animation-duration: 1ms; animation-timing-function: linear; }
  /* IMPACT carries two animations at once: the scroll-linked drift,
     and its own glow on a time clock. Declaring only plane-ghost here
     silently replaced the bloom, so the word never lit. Per-animation
     timelines keep both alive. */
  html.sdt .hero__ghost {
    animation-name: plane-ghost, ghost-sweep, ghost-bloom;
    animation-timeline: scroll(root), auto, auto;
    animation-range: 0 92vh, normal, normal;
    animation-duration: 1ms, 7.5s, 11s;
    animation-timing-function: linear, var(--ease-inout), var(--ease-inout);
    animation-iteration-count: 1, infinite, infinite;
    animation-fill-mode: both, none, none;
  }
  html.sdt .hero__copy  { animation-name: plane-copy; }
  html.sdt .cluster     { animation-name: plane-cluster; }
  html.sdt .showcase    { animation-name: plane-showcase; }
  html.sdt .showcase__portrait { animation-name: plane-chip; }
  html.sdt .frag--chip  { animation-name: plane-chip; }

  /* Top scroll-progress hairline */
  html.sdt .scrollbar-progress {
    animation: bar 1ms linear both;
    animation-timeline: scroll(root);
  }
}

@keyframes plane-ghost   { to { transform: translateY(-16%); } }
@keyframes plane-copy    { to { transform: translateY(-5%); opacity: .34; filter: blur(3px); } }
@keyframes plane-cluster { from { transform: translateY(0) rotate(-1.1deg); } to { transform: translateY(-18%) rotate(.4deg); } }
@keyframes plane-showcase { to { transform: translateY(-12%); } }
@keyframes plane-chip    { to { transform: translateY(-46%); } }
@keyframes bar           { to { transform: scaleX(1); } }

/* ---------- State 3: IntersectionObserver fallback ---------- */
html.no-sdt .reveal,
html.no-sdt .reveal-x,
html.no-sdt .reveal-scale,
html.no-sdt .stagger > *,
html.no-sdt .plate--curtain {
  opacity: 0;
  transition: opacity var(--dur-reveal) var(--ease-editorial),
              transform var(--dur-reveal) var(--ease-editorial),
              filter var(--dur-reveal) var(--ease-editorial),
              clip-path var(--dur-reveal) var(--ease-editorial);
}
html.no-sdt .reveal,
html.no-sdt .stagger > * { transform: translateY(20px); }
html.no-sdt .reveal-x     { transform: translateX(32px); }
html.no-sdt .reveal-scale { transform: scale(.955); }
html.no-sdt .plate--curtain { opacity: 1; clip-path: inset(0 0 100% 0 round var(--radius-images)); }

html.no-sdt .stagger > * { transition-delay: calc(var(--i, 0) * 70ms); }

html.no-sdt .is-visible,
html.no-sdt .is-visible > * {
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
  clip-path: inset(0 0 0 0 round var(--radius-images)) !important;
}

/* ------------------------------------------------------------
   HERO ENTRY CHOREOGRAPHY (time-based, runs once on load)
   ------------------------------------------------------------ */
@keyframes line-up   { from { transform: translateY(105%); } to { transform: translateY(0); } }
/* The one place a blur is still right: the italic word settles once,
   on load, at a size and duration where it reads as ink drying rather
   than as an out-of-focus screen. Softened from 10px to 5px. */
@keyframes ink-bleed {
  from { opacity: 0; filter: blur(5px); transform: scaleX(1.03); }
  to   { opacity: 1; filter: blur(0);   transform: scaleX(1); }
}
@keyframes fade-up   { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
@keyframes pop-in    { from { opacity: 0; transform: translateY(26px) scale(.955); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes rule-draw { from { --draw-x: 0; } to { --draw-x: 1; } }
@keyframes arc-draw  { from { stroke-dashoffset: 620; } to { stroke-dashoffset: 0; } }
@keyframes widget-in { from { opacity: 0; transform: translateX(-50%) translateY(140%); } to { opacity: 1; transform: translateX(-50%) translateY(0); } }

html.js .hero__eyebrow > span { display: inline-block; opacity: 0; animation: fade-up 460ms var(--ease-editorial) both; animation-delay: calc(120ms + var(--i) * 26ms); }
html.js .hero__line > span    { animation: line-up var(--dur-reveal) var(--ease-editorial) both; animation-delay: calc(380ms + var(--i) * 90ms); }
html.js .hero__copy .accent   { animation: ink-bleed 900ms var(--ease-editorial) both; animation-delay: 640ms; display: inline-block; }
html.js .hero__lede           { animation: fade-up var(--dur-slow) var(--ease-editorial) both; animation-delay: 820ms; }
html.js .hero__triad li       { animation: fade-up 620ms var(--ease-editorial) both; animation-delay: calc(960ms + var(--i) * 140ms); }
html.js .hero__triad li::after{ animation: rule-draw 460ms var(--ease-editorial) both; animation-delay: calc(1120ms + var(--i) * 140ms); }
html.js .hero__cta            { animation: pop-in 620ms var(--ease-spring) both; animation-delay: 1180ms; }
html.js .frag                 { animation: pop-in 700ms var(--ease-spring) both; animation-delay: calc(1300ms + var(--i) * 90ms); }
html.js .rcard,
html.js .showcase__portrait,
html.js .showcase__chip       { animation: pop-in 720ms var(--ease-spring) both; animation-delay: calc(1240ms + var(--i) * 110ms); }
html.js .cluster__arc         { stroke-dasharray: 620; animation: arc-draw 760ms var(--ease-editorial) both; animation-delay: 1480ms; }
html.js .hero__scroll-cue     { animation: fade-up 600ms var(--ease-editorial) both; animation-delay: 1560ms; }
html.js .companion            { animation: widget-in 900ms var(--ease-spring) both; animation-delay: 1600ms; }

/* Safety net. The hero entry starts from opacity:0 / translateY, and a
   page that loads in a background tab can have those animations
   deferred until it is first shown. If the sequence has not finished
   within a few seconds, JS adds .hero-done and every element snaps to
   its final state — the hero is never left invisible. */
html.hero-done .hero__eyebrow > span,
html.hero-done .hero__line > span,
html.hero-done .hero__copy .accent,
html.hero-done .hero__lede,
html.hero-done .hero__triad li,
html.hero-done .hero__cta,
html.hero-done .frag,
html.hero-done .disc,
html.hero-done .stat,
html.hero-done .rcard,
html.hero-done .showcase__portrait,
html.hero-done .showcase__chip,
html.hero-done .hero__scroll-cue,
html.hero-done .companion {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
}
@media (max-width: 959px) {
  html.hero-done .companion { transform: translateX(-50%) !important; }
}
@media (min-width: 960px) {
  html.hero-done .companion { transform: none !important; }
}
html.hero-done .hero__triad li::after { animation: none !important; --draw-x: 1; }
html.hero-done .cluster__arc { animation: none !important; stroke-dashoffset: 0 !important; }

/* ------------------------------------------------------------
   CLOSING WORDS — left-to-right wipe
   ------------------------------------------------------------ */
/* `--i` lives on .closer__row and inherits down to the word. */
@supports (animation-timeline: view()) {
  html.sdt .closing__word {
    animation: wipe-x 1ms var(--ease-editorial) both;
    animation-timeline: view();
    animation-range: entry calc(4% + var(--i, 0) * 7%) cover calc(30% + var(--i, 0) * 7%);
  }
  html.sdt .closer__meaning,
  html.sdt .closer__tag {
    animation: reveal 1ms var(--ease-editorial) both;
    animation-timeline: view();
    animation-range: entry calc(8% + var(--i, 0) * 7%) cover calc(34% + var(--i, 0) * 7%);
  }
}
html.no-sdt .closing__word {
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--dur-slow) var(--ease-editorial);
  transition-delay: calc(var(--i, 0) * 180ms);
}
html.no-sdt .closer.is-visible .closing__word,
html.no-sdt .closing__words.is-visible .closing__word { clip-path: inset(0 0 0 0); }

/* ------------------------------------------------------------
   MOTION PREFERENCE — a designed static alternative
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html:not(.motion-forced) *,
  html:not(.motion-forced) *::before,
  html:not(.motion-forced) *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}

/* Explicit user opt-out via the in-page toggle. Everything resolves
   to its final state; nothing is hidden, nothing moves. */
html.motion-off *,
html.motion-off *::before,
html.motion-off *::after {
  animation: none !important;
  transition: none !important;
  scroll-behavior: auto !important;
}
html.motion-off .reveal,
html.motion-off .reveal-x,
html.motion-off .reveal-scale,
html.motion-off .stagger > *,
html.motion-off .plate--curtain,
html.motion-off .closer__meaning,
html.motion-off .closer__tag,
html.motion-off .closing__word {
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
  clip-path: none !important;
}
html.motion-off .loop__node,
html.motion-off .spotlight li { opacity: 1 !important; }
html.motion-off .spotlight li { color: var(--color-paper-white) !important; }
html.motion-off .spotlight li::before { color: var(--color-electric-cobalt) !important; }
html.motion-off .ledger__item { opacity: 1 !important; position: static !important; }
html.motion-off .rail__stack > li { position: static !important; }
html.motion-off [data-progress],
html.motion-off [data-draw] { --progress: 1; --draw-x: 1; }
html.motion-off .marquee__track { animation: none !important; flex-wrap: wrap; width: 100%; }
html.motion-off .blob,
html.motion-off .closer__loop,
html.motion-off .proof__item--loop svg,
html.motion-off .plate--drift > img { animation: none !important; }
html.motion-off .hero__scroll-cue svg { animation: none !important; }
html.motion-off .scrollbar-progress { display: none; }

/* Same collapse for the OS-level preference */
@media (prefers-reduced-motion: reduce) {
  html:not(.motion-forced) .reveal,
  html:not(.motion-forced) .reveal-x,
  html:not(.motion-forced) .reveal-scale,
  html:not(.motion-forced) .stagger > *,
  html:not(.motion-forced) .plate--curtain,
  html:not(.motion-forced) .closer__meaning,
  html:not(.motion-forced) .closer__tag,
  html:not(.motion-forced) .closing__word {
    opacity: 1 !important; transform: none !important;
    filter: none !important; clip-path: none !important;
  }
  html:not(.motion-forced) .closer__loop { animation: none !important; }
  html:not(.motion-forced) .loop__node,
  html:not(.motion-forced) .spotlight li { opacity: 1 !important; }
  html:not(.motion-forced) .spotlight li { color: var(--color-paper-white) !important; }
  html:not(.motion-forced) .ledger__item,
  html:not(.motion-forced) .rail__stack > li { position: static !important; }
  html:not(.motion-forced) [data-progress],
  html:not(.motion-forced) [data-draw] { --progress: 1; --draw-x: 1; }
  html:not(.motion-forced) .marquee__track { flex-wrap: wrap; width: 100%; }
  html:not(.motion-forced) .blob,
  html:not(.motion-forced) .closer__loop,
  html:not(.motion-forced) .proof__item--loop svg,
  html:not(.motion-forced) .plate--drift > img { animation: none !important; }
}

}
