/* Motion primitives — shared by landing, admin and the future portal.
   Two rules govern everything here:

   1. Nothing is hidden unless script can show it again. Every hidden
      state sits behind `html.anim`, set by the inline head script before
      first paint and dropped by a watchdog if reveal.js never arrives.
      With scripting off the page simply renders in its finished state.
   2. prefers-reduced-motion removes the movement, never the content. */

/* --reveal-op is an element's own resting opacity, and revealing has to
   restore that rather than assume 1: `.anim .reveal.is-in` outranks a
   plain `.rule-brass { opacity: .7 }`, so a hard 1 would quietly brighten
   every decorative element the moment it was revealed. It is read with a
   fallback and never declared here — declaring it on `.reveal` would
   make the fix depend on which stylesheet the page loads last. */
.reveal { --reveal-i: 0; }

.anim .reveal {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity var(--t-slow) var(--ease-out),
    transform var(--t-slow) var(--ease-out);
  transition-delay: calc(var(--reveal-i) * 90ms);
}

.anim .reveal.is-in {
  opacity: var(--reveal-op, 1);
  transform: none;
}

/* A group staggers its own children, so a card grid arrives in sequence
   instead of as one block. The indices are CSS rather than JS, so the
   delay is already right on the first frame — no reflow, no flicker. */
.reveal-group > *:nth-child(2) { --reveal-i: 1; }
.reveal-group > *:nth-child(3) { --reveal-i: 2; }
.reveal-group > *:nth-child(4) { --reveal-i: 3; }
.reveal-group > *:nth-child(5) { --reveal-i: 4; }

/* Headline: reveal.js splits the text into .word spans and stamps each
   with its index. The heading is held blank until it has been split, or
   the unsplit line paints for a frame and then jumps as it re-flows. */
.anim [data-split] { opacity: 0; }
.anim [data-split].is-split { opacity: 1; }

.word {
  --reveal-i: 0;
  display: inline-block;
}

.anim .word {
  opacity: 0;
  transform: translateY(0.42em);
  transition:
    opacity var(--t-slow) var(--ease-out),
    transform var(--t-slow) var(--ease-out);
  transition-delay: calc(var(--reveal-i) * 60ms);
}

.anim .word.is-in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .anim .reveal,
  .anim .word,
  .anim [data-split] {
    opacity: var(--reveal-op, 1);
    transform: none;
    transition: none;
  }
}
