/* motion.css — site-motion kit v2 (2026-07-11, post adversarial review)
   Vanilla implementation of design-lab/MOTION.md §2 (tokens) + §3 (reveal
   system) for STATIC sites. React/Next sites use design-lab's RevealObserver
   component instead — never both.

   Contract:
   - Elements are VISIBLE BY DEFAULT. Only html.mjs (set by reveal.js when
     IntersectionObserver exists AND prefers-reduced-motion is off AND the
     viewport is real) opts [data-reveal] elements into hidden-until-revealed.
     `.mjs` is deliberately NOT the head-contract `js` class — that swap runs
     before reveal.js and must never be able to hide content on its own.
   - The hidden state lives on :not(.revealed) and the .revealed rule pins NO
     property values — after the entrance, reveal.js strips data-reveal so
     every kit selector stops matching and the element's natural styles,
     hovers, and transitions fully return (review 2026-07-11: a value-pinning
     .revealed rule killed hover effects on revealed elements).
   - Tokens are namespaced --m-* : --dur/--ease are OWNED by the site token
     contract (design-lab MODULES.md); redeclaring them re-times the whole
     site depending on stylesheet order.
   - transform/opacity/filter only. Never animate top/left/width/height.
   - NEVER put data-reveal on the h1 or the LCP element (MOTION.md §3;
     hidden reveals are a proven FCP regression on this estate). */

:root {
  --m-dur-fast: 150ms;
  --m-dur: 250ms;
  --m-dur-slow: 600ms;
  --m-ease: cubic-bezier(.4, 0, .2, 1);
  --m-ease-out: cubic-bezier(.16, 1, .3, 1); /* THE reveal curve (MOTION.md §2) */
  --m-ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  --m-stagger: 60ms;
  --m-reveal-y: 24px;
}

/* ---- reveal system (design-lab MOTION.md §3 semantics) ------------------ */
html.mjs [data-reveal]:not(.revealed) {
  opacity: 0;
  transform: translateY(var(--m-reveal-y));
}
html.mjs [data-reveal="left"]:not(.revealed)  { transform: translateX(calc(-1 * var(--m-reveal-y))); }
html.mjs [data-reveal="right"]:not(.revealed) { transform: translateX(var(--m-reveal-y)); }
html.mjs [data-reveal="scale"]:not(.revealed) { transform: scale(.96); }
html.mjs [data-reveal="blur"]:not(.revealed)  { transform: none; filter: blur(8px); }

/* Pins NOTHING — only supplies the entrance transition. reveal.js removes
   data-reveal (+ .revealed + --i) after the entrance completes, restoring the
   element's own transition list for hovers etc. */
html.mjs [data-reveal].revealed {
  transition:
    opacity var(--m-dur-slow) var(--m-ease-out),
    transform var(--m-dur-slow) var(--m-ease-out),
    filter var(--m-dur-slow) var(--m-ease-out);
  transition-delay: calc(var(--i, 0) * var(--m-stagger));
}

/* ---- micro-interactions (T1 tier) ---------------------------------------
   Hover effects are pointer-gated (MOTION.md §7). m-squish is :active press
   feedback and deliberately NOT gated — it must work on touch. */
@media (hover: hover) and (pointer: fine) {
  .m-lift {
    transition: transform var(--m-dur-fast) var(--m-ease-out),
                box-shadow var(--m-dur-fast) var(--m-ease-out);
  }
  .m-lift:hover { transform: translateY(-3px); }

  .m-underline {
    background-image: linear-gradient(currentColor, currentColor);
    background-size: 0% 1.5px;
    background-position: 0 100%;
    background-repeat: no-repeat;
    transition: background-size var(--m-dur) var(--m-ease-out);
  }
  .m-underline:hover { background-size: 100% 1.5px; }

  .m-icon-nudge svg { transition: transform var(--m-dur-fast) var(--m-ease-out); }
  .m-icon-nudge:hover svg { transform: translateX(3px); }
}

.m-squish { transition: transform var(--m-dur-fast) var(--m-ease); }
.m-squish:active { transform: scale(.97); }

/* ---- entrance keyframes (sparingly; first-paint choreography ≤600ms total,
        LCP element never delayed >100ms — MOTION.md §9) -------------------- */
@keyframes m-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes m-rise {
  from { opacity: 0; transform: translateY(var(--m-reveal-y)); }
  to   { opacity: 1; transform: none; }
}

/* ---- SVG line-draw (MOTION.md §10) ---------------------------------------
   Put pathLength="1" on the path; add class="m-draw" data-reveal="".
   Hidden state is GATED on html.mjs (never baked into markup) so no-JS /
   reduced-motion / crawlers get the stroke fully drawn. */
.m-draw { stroke-dasharray: 1; }
html.mjs .m-draw:not(.revealed) { stroke-dashoffset: 1; }
html.mjs .m-draw.revealed {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.2s var(--m-ease-out);
}

/* Infinite/looping animations MUST carry data-pause-offscreen; reveal.js
   toggles .m-offscreen (class, never inline style — composes with
   hover-pause rules and other scripts). MOTION.md §10 hard rule. */
[data-pause-offscreen].m-offscreen,
[data-pause-offscreen].m-offscreen * {
  animation-play-state: paused !important;
}

/* ---- reduced motion: kill everything (MODULES.md §2 verbatim pattern) --- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  html.mjs [data-reveal]:not(.revealed) { opacity: 1; transform: none; filter: none; }
  html.mjs .m-draw:not(.revealed) { stroke-dashoffset: 0; }
}

/* ---- cross-document View Transitions (static-export superpower,
        MOTION.md §4: 90ms out / 260ms in, both fill) ----------------------- */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}
::view-transition-old(root) { animation: m-vt-out 90ms var(--m-ease) both; }
::view-transition-new(root) { animation: m-vt-in 260ms var(--m-ease-out) both; }
@keyframes m-vt-out { to { opacity: 0; } }
@keyframes m-vt-in  { from { opacity: 0; transform: translateY(8px); } }
