/* ==========================================================================
   enhance.css — additive polish on top of the Act-One scroll page:
     1. Word-by-word headline reveals  ([data-reveal="words"])
     2. Interstitial robot "connector" cartoons between sections (.interlude)
     3. Scroll-scrubbed courier (the interlude bot rides the wheel 1:1)
     4. Kicker chapter ritual (rule draws in, tracking exhales)

   Loads AFTER base.css so the [data-reveal="words"] container override wins by
   source order. Everything degrades to a clean static frame under no-JS / print
   / reduced-motion.
   ========================================================================== */

/* ==========================================================================
   1 · WORD-BY-WORD HEADLINE REVEAL
   The container stays visible (so its words show their own motion); the inner
   spans rise from a clipped baseline. text-anim.js stamps one computed delay
   per word (--wd: spoken-cadence envelope + <br> caesura + <em> breath);
   reveal.js still flips .is-visible on the container and promotes the spans
   to compositor layers only while the cascade is in flight.
   ========================================================================== */
html.js [data-reveal="words"] {
  opacity: 1;
  transform: none;
  filter: none;
}
.word {
  /* clip-box for the rising inner span; padding+negative margin give italics
     and descenders room so the clip never shaves a glyph. */
  display: inline-block;
  overflow: hidden;
  padding: 0.14em 0.14em;
  margin: -0.14em -0.14em;
  vertical-align: top;
}
.word__inner {
  display: inline-block;
  /* baseline-left origin: the ~2% --ease-settle overshoot (≈0.025em, inside
     the 0.14em mask padding) tips from where type is set, not from center.
     will-change is applied by reveal.js only while the cascade runs. */
  transform-origin: 0% 100%;
}
html.js [data-reveal="words"] .word__inner {
  transform: translateY(115%) rotate(2.4deg);
  opacity: 0;
  transition:
    transform 0.72s var(--ease-settle),
    opacity 0.52s var(--ease-out);
  transition-delay: calc(var(--wd, 0ms) + var(--reveal-delay, 0ms));
}
html.js [data-reveal="words"].is-visible .word__inner {
  transform: none;
  opacity: 1;
  filter: none;
}

/* Emphasized words — and the cover line, which IS the opening shot — settle
   on a longer, heavier arc (0.95s transform, 0.7s opacity): weight = emphasis. */
html.js [data-reveal="words"] .word--em .word__inner,
html.js #act-one .hero__title .word__inner {
  transition-duration: 0.95s, 0.7s;
}

/* [data-em-beat] (the payoff line): em words arrive AFTER the rest of the
   line (text-anim.js holds them past the envelope) and clear from a soft
   blur — the thesis is an event, not a word in the flow. The visible-state
   filter reset below MUST match this rule's specificity: the generic
   `.is-visible .word__inner { filter: none }` is one class short and loses
   the cascade, which would leave the thesis permanently soft-focus. */
html.js [data-reveal="words"][data-em-beat] .word--em .word__inner {
  filter: blur(0.08em);
  transition:
    transform 0.95s var(--ease-settle),
    opacity 0.7s var(--ease-out),
    filter 0.8s var(--ease-out);
  transition-delay: calc(var(--wd, 0ms) + var(--reveal-delay, 0ms));
}
html.js [data-reveal="words"][data-em-beat].is-visible .word--em .word__inner {
  filter: none;
}

/* ==========================================================================
   2 · INTERLUDE — a slim band that bridges two sections. A little robot travels
   down a flowing dashed guide line, carrying the story (and sometimes the
   glowing "time" orb) to the next moment.
   ========================================================================== */
.interlude {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: clamp(0.5rem, 3vw, 2.4rem) 0;
  pointer-events: none;
}
.interlude__art {
  inline-size: clamp(72px, 9vw, 116px);
  block-size: auto;
  /* the scene paint classes read these brand-anchored vars (mirrors .spot) */
  --k-teal: var(--accent);
  --k-teal-lt: #2fa18d;
  --k-teal-dk: #0c5346;
  --k-gold: var(--accent-2);
  --k-gold-lt: #e2ad3c;
  --k-coral: #d76b4a;
  --k-coral-dk: #b14e30;
  --k-skin: #f0d3a4;
  --k-cream: #fffaf2;
  --k-ink: #2a201a;
  --k-shadow: rgba(42, 32, 26, 0.15);
}
.interlude__art svg { display: block; inline-size: 100%; block-size: auto; overflow: visible; }

/* ==========================================================================
   3 · INTERLUDE MOTION — only when motion is welcome.
   Time-looped travel is the FALLBACK for engines without scroll-driven
   animations; section 4 below upgrades it to a scroll-scrubbed ride.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  /* the robot floats down the guide, fading in at the top and out at the base */
  html.js .interlude.is-visible .k-travel {
    animation: k-travel 3.8s var(--ease-breathe) infinite;
    transform-box: view-box;
  }
  /* dashes flow downward to imply travel */
  html.js .interlude.is-visible .k-dashflow {
    animation: k-dashflow 1.1s linear infinite;
  }
  /* a soft pulse on the carried orb / antenna light */
  html.js .interlude .k-orbpulse {
    animation: k-orbpulse 2.4s var(--ease-breathe) infinite;
    transform-box: fill-box;
    transform-origin: center;
  }
}
@keyframes k-travel {
  0%   { transform: translateY(-12px); opacity: 0; }
  14%  { opacity: 1; }
  84%  { opacity: 1; }
  100% { transform: translateY(96px); opacity: 0; }
}
@keyframes k-dashflow { to { stroke-dashoffset: -36; } }
@keyframes k-orbpulse {
  0%, 100% { transform: scale(1); opacity: 0.9; }
  50%      { transform: scale(1.18); opacity: 1; }
}

/* ==========================================================================
   4 · SCROLL-SCRUBBED COURIER — where supported, the courier rides its guide
   line bound 1:1 to the reader's scroll: pause and it pauses, reverse and it
   returns. No .is-visible gate (a first approach must never snap mid-line);
   the longhand timeline is declared AFTER the shorthand (which resets it).
   The doubled selector out-ranks the time-looped fallback by source order.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .interlude { view-timeline: --interlude block; }
    html.js .interlude .k-travel,
    html.js .interlude.is-visible .k-travel {
      animation: k-travel-scrub linear both;
      animation-timeline: --interlude;
      transform-box: view-box;
    }
  }
}
@keyframes k-travel-scrub {
  0%   { transform: translateY(-12px); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translateY(96px); opacity: 0; }
}

/* ==========================================================================
   5 · KICKER CHAPTER RITUAL — every chapter label opens the same way: the
   accent rule draws in from the left while the tracking exhales into place.
   Letter-spacing animates layout, but only on a short single-line label.
   The full transition shorthand is redeclared (append is impossible).
   ========================================================================== */
html.js .kicker[data-reveal] {
  letter-spacing: 0.3em;
  transition:
    opacity calc(var(--dur-2) * 0.55) var(--ease-soft),
    transform var(--dur-2) var(--ease-out),
    letter-spacing 1.1s var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
html.js .kicker[data-reveal].is-visible { letter-spacing: 0.18em; }
html.js .kicker[data-reveal]::before {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.8s var(--ease-out);
  transition-delay: calc(var(--reveal-delay, 0ms) + 120ms);
}
html.js .kicker[data-reveal].is-visible::before { transform: scaleX(1); }

/* ==========================================================================
   REDUCED MOTION — every enhancement above resolves to its settled frame.
   (base.css's force-reveal covers opacity/transform on [data-reveal]; the
   kicker's tracking + rule and the em-beat blur need explicit resets.)
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html.js [data-reveal="words"] .word__inner {
    transform: none !important;
    opacity: 1 !important;
    filter: none !important;
  }
  .interlude .k-travel, .interlude .k-dashflow, .interlude .k-orbpulse {
    animation: none !important;
  }
  html.js .kicker[data-reveal] { letter-spacing: 0.18em !important; }
  html.js .kicker[data-reveal]::before { transform: none !important; }
}
