/* ==========================================================================
   text-reveal.css  —  per-letter staggered text reveal
   --------------------------------------------------------------------------
   Runtime lives in js/text-reveal.js. Contract mirrors the js-appear pattern
   in css/base.css:

   - An element opts in with  data-split-text.
   - js/text-reveal.js splits it into `.sr-w` word spans / `.sr-l` letter
     spans and adds `js-split` to <html>. ONLY then are the letters hidden,
     so with JS off (or reduced motion) the page is fully visible static
     text and nothing here applies.
   - The splitter sets  --li  (0-based letter index) inline on each letter;
     the delay is  --li * --sr-stagger.
   - The reveal flips `.sr-in` on the element; it plays once.

   ONLY opacity is animated — no transform, no display change on letters we
   did not already inherit — so measured heights and line breaking are
   identical with and without the effect. (Word spans get `white-space:
   nowrap` for the same reason base.css does it on `.split > span`: it keeps
   a word from ever breaking between its own letter spans.)

   Measured on the live reference: ~30ms stagger, ~120ms per-letter fade.
   ========================================================================== */

[data-split-text] {
  --sr-stagger: 0.03s;   /* per-letter delay step */
  --sr-dur:     0.12s;   /* per-letter fade duration */
}

/* Structural: applied whether or not JS armed the animation, but these only
   exist on spans the splitter itself created, so no-JS is unaffected. */
[data-split-text] .sr-w { white-space: nowrap; }

/* Word mode: data-split-text="words" fades whole words, not letters.
   (The splitter tags the word spans themselves as units in this mode.)
   Measured on the live reference: mono captions (hero note, hero stats,
   the nav "book a call" label) fade word-by-word, ~85-150ms apart. */
[data-split-text="words"] {
  --sr-stagger: 0.12s;
  --sr-dur:     0.15s;
}

/* --- armed state ------------------------------------------------------- */
/* The transition MUST live on the .sr-in state, not the armed state.
   If the armed rule carried it, adding `js-split` would itself be
   transitioned (visible -> hidden, staggered over ~1.2s) whenever the
   browser had already computed style for the letters; elements inside the
   first viewport are then flipped to .sr-in ~2 frames later, the fade-out
   reverses before it is visible, and the reveal silently no-ops. (This is
   exactly what happened to every above-the-fold heading.) With the
   transition on .sr-in only, arming snaps to hidden instantly and the
   reveal is the only transitioned change. */
html.js-split [data-split-text] .sr-l {
  opacity: 0;
}

html.js-split [data-split-text].sr-in .sr-l {
  opacity: 1;
  transition-property: opacity;
  transition-duration: var(--sr-dur);
  transition-timing-function: linear;
  transition-delay: calc(var(--li, 0) * var(--sr-stagger));
}

/* --- reduced motion ---------------------------------------------------- */
/* text-reveal.js already bails out before adding `js-split` here; this is a
   belt-and-braces rule so a cached/armed document can never hide text. */
@media (prefers-reduced-motion: reduce) {
  html.js-split [data-split-text] .sr-l,
  html.js-split [data-split-text].sr-in .sr-l {
    opacity: 1 !important;
    transition: none !important;
  }
}
