/* @spine-kit motion/motion.css · v1
   Canonical source: spine/kits/motion/motion.css
   Paste-verbatim. Fix the spine, then re-vendor — do not edit this copy in place. */

/* ═══════════════════════════════════════════════════════════════════
 * TPMotion CSS — the half of the kit that JS reads and gates against.
 * Pairs with motion.js (window.TPMotion). Reads no colors: everything
 * here is layout/timing. Colors, radii, and gradients stay in the
 * tokens kit — this file must never define a --tp-* or Night Shift
 * custom property (spine/README.md authoring rule 6).
 * ═══════════════════════════════════════════════════════════════════ */

:root {
  /* Tunable feel — override these in a stack's own :root (after this
     file loads) to retune motion for the whole site in one place.
     These are layout variables this kit owns, not design tokens. */
  --motion-dur:        640ms;   /* reveal fade/rise duration */
  --motion-dur-fast:   220ms;   /* hover / micro-interaction duration */
  --motion-dur-flip:   380ms;   /* FLIP re-layout duration */
  --motion-ease:       cubic-bezier(.22, .61, .36, 1);   /* reveal easing */
  --motion-ease-flip:  cubic-bezier(.22, .9, .3, 1);     /* FLIP easing */
  --motion-reveal-distance:   22px;   /* reveal's initial translateY */
  --motion-reveal-delay-step: 80ms;   /* per-item stagger, via --d on the element */
}

/* ── reveal-on-scroll ─────────────────────────────────────────────
   Gated behind html.js: this IS the JS-off legibility rule, made
   physical. A stack is responsible for adding the .js class to
   <html> — ideally with a tiny synchronous inline snippet in <head>,
   before motion.js itself even loads (see the kit README) — so that
   if that never happens (JS disabled, blocked, or the script fails
   to load), this selector never matches at all and .reveal content
   simply renders at its default opacity: 1. A JS-off visitor gets a
   complete page, never a field of blank blocks waiting on a script
   that isn't coming.

   The :not(.no-motion) guard means an element with motion off never
   enters the hidden state in the first place — there is nothing to
   snap back from later, and so nothing that can flash or get stuck. */
html.js:not(.no-motion) {
  scroll-behavior: smooth;
}
html.js:not(.no-motion) .reveal {
  opacity: 0;
  transform: translateY(var(--motion-reveal-distance));
  transition: opacity var(--motion-dur) var(--motion-ease),
              transform var(--motion-dur) var(--motion-ease);
  transition-delay: calc(var(--d, 0) * 1ms);
}
html.js:not(.no-motion) .reveal.in {
  opacity: 1;
  transform: none;
}

/* ── governor classes ─────────────────────────────────────────────
   no-motion    -> motion is OFF, for any reason: the OS default
                   (prefers-reduced-motion: reduce, no override set
                   yet) or an explicit user opt-out via the governor.
                   This block is independent of the media query below
                   on purpose — it is what makes an explicit opt-out
                   work even when the OS itself has no preference set,
                   a case the media query alone can never see.
   force-motion -> motion is explicitly ON despite an OS-level
                   prefers-reduced-motion: reduce. Used only inside
                   the media query, as the one escape hatch for a user
                   who opted back in on this site specifically. */
html.no-motion,
html.no-motion body {
  scroll-behavior: auto !important;
}
html.no-motion *,
html.no-motion *::before,
html.no-motion *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}
/* belt-and-suspenders: even if some other rule ever gives .reveal a
   hidden or mid-transition inline style, this wins and content stays
   visible. Never let "motion is off" mean "content is invisible". */
html.no-motion .reveal,
html.no-motion .reveal.in {
  opacity: 1 !important;
  transform: none !important;
}

/* Failsafe — content visibility must NEVER depend on an animation completing.
   A hidden document suspends DocumentTimeline, so a reveal transition started
   in a background tab freezes at opacity 0 and the page renders blank. The
   inline head snippet sets .motion-failed when it detects that case, or when
   motion.js has not booted in time. This renders the finished state outright. */
html.motion-failed .reveal,
html.motion-failed .reveal.in {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}

/* ── OS-level reduced motion ──────────────────────────────────────
   Honored by default, with no opt-in required — this fires purely
   off the visitor's OS/browser setting, independent of any class JS
   has had a chance to add yet. :not(.force-motion) is the only
   override. Crucially, this must never leave .reveal content stuck
   at opacity 0 — a reduced-motion visitor gets the finished, visible
   page, not a half-appeared one. */
@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion),
  html:not(.force-motion) body {
    scroll-behavior: auto !important;
  }
  html:not(.force-motion) *,
  html:not(.force-motion) *::before,
  html:not(.force-motion) *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* Defensive duplicate of the .no-motion rule above, scoped to the
     media query itself: covers the gap between the OS reporting
     "reduce" and a governor script having run long enough to add
     .no-motion — .reveal must never be the thing a reduced-motion
     visitor is left waiting on. !important is load-bearing here, not
     decoration: html.js:not(.no-motion) .reveal (the base hiding
     rule) carries an extra .js class, so it is MORE specific than a
     plain html:not(.force-motion) .reveal would be — without
     !important this "defensive" rule loses the cascade outright and
     the gap it exists to cover reopens silently. */
  html:not(.force-motion) .reveal {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ── decorative motion (pulses, glows, spills) ────────────────────
   A stack's own pulsing/glowing decorative elements (a "still
   building" seam-light, a hover spill, an eyebrow spark — anything
   whose CSS animation exists purely for atmosphere) should follow
   this same pattern: read var(--o, <default>) for their resting
   opacity, and go to `animation: none !important` under both
   html.no-motion and the reduced-motion media query above, landing on
   that resting opacity rather than disappearing. This file does not
   hardcode those selectors — they belong to the stack that defines
   them — but the rule is the same wherever they live:

     html.no-motion .my-pulse,
     .my-pulse (inside the reduced-motion media query) {
       animation: none !important;
       opacity: var(--o, .8);
     }
*/
