/* alive/motion.css - Nova motion tokens + reveal utilities (v1, 18 Jul 2026)
   Dependency-free. Compositor-safe (transform/opacity only). Reduced-motion first-class.
   Usage: include this file, add `document.documentElement.className+=' js-alive'` INLINE in
   <head> (before paint), load alive.js deferred. No-JS = everything visible. */

:root {
  /* Duration palette - pick three per project, these are the defaults (Corporate) */
  --dur-quick: 160ms;      /* hovers, presses, toggles */
  --dur-standard: 300ms;   /* cards, panels, state changes */
  --dur-slow: 700ms;       /* hero reveals, dramatic moments */

  /* Named easing tokens (defaults `ease`/`linear` are banned for spatial motion) */
  --ease-expo-out: cubic-bezier(0.16, 1, 0.3, 1);      /* signature entrances */
  --ease-quart-out: cubic-bezier(0.25, 1, 0.5, 1);     /* general entrances */
  --ease-expo-inout: cubic-bezier(0.87, 0, 0.13, 1);   /* large transitions */
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);         /* default on-screen UI */
  --ease-emphasized: cubic-bezier(0.05, 0.7, 0.1, 1);  /* attention entrances */
  --ease-accelerate: cubic-bezier(0.3, 0, 1, 1);       /* exits only */
  --ease-float: cubic-bezier(0.4, 0, 0.2, 1);          /* ambient */
  --ease-bounce: cubic-bezier(0.175, 0.885, 0.32, 1.275); /* playful overshoot */

  /* Stagger step (JS multiplies by --alive-i) */
  --stagger: 80ms;
  /* Reveal travel distance - keep <= 1/3 viewport */
  --reveal-y: 28px;
}

/* ---- Scroll-triggered reveals ----------------------------------------------------
   Markup: <div data-alive="reveal">  (optional: data-alive="reveal-left|reveal-scale")
   Stagger a group: parent gets data-alive-group; children auto-indexed by alive.js.
   Hidden state ONLY applies once JS is confirmed (html.js-alive) - no-JS stays visible. */
.js-alive [data-alive] {
  opacity: 0;
  transform: translateY(var(--reveal-y));
  transition:
    opacity var(--dur-slow) var(--ease-quart-out),
    transform var(--dur-slow) var(--ease-expo-out);
  transition-delay: calc(var(--alive-i, 0) * var(--stagger));
  will-change: opacity, transform;
  /* FAILSAFE: if alive.js never runs (404, crash, blocked), content force-reveals at 2.5s.
     CSS-only, so the hidden state can never strand the page (the GSAP-from() trap). */
  animation: alive-failsafe 0.5s var(--ease-quart-out) 2.5s forwards;
}
@keyframes alive-failsafe { to { opacity: 1; transform: none; } }
.js-alive [data-alive].is-in { animation: none; }
.js-alive [data-alive="reveal-left"]  { transform: translateX(-36px); }
.js-alive [data-alive="reveal-scale"] { transform: scale(0.94) translateY(12px); }
.js-alive [data-alive].is-in {
  opacity: 1;
  transform: none;
}
/* Free will-change after arrival (perf) */
.js-alive [data-alive].is-done { will-change: auto; }

/* ---- Page-load choreography -------------------------------------------------------
   Markup: data-load="1".."6" - slots on the standard timeline:
   structure 0-200 / hero words 200-600 / subtitle 400-800 / nav 600-900 / support 800-1200 */
.js-alive [data-load] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 600ms var(--ease-quart-out), transform 600ms var(--ease-expo-out);
  animation: alive-failsafe 0.5s var(--ease-quart-out) 2.5s forwards; /* same failsafe */
}
.js-alive.is-loaded [data-load] { animation: none; }
.js-alive [data-load="1"] { transition-delay: 0ms; }
.js-alive [data-load="2"] { transition-delay: 200ms; }
.js-alive [data-load="3"] { transition-delay: 400ms; }
.js-alive [data-load="4"] { transition-delay: 600ms; }
.js-alive [data-load="5"] { transition-delay: 800ms; }
.js-alive [data-load="6"] { transition-delay: 1000ms; }
.js-alive.is-loaded [data-load] { opacity: 1; transform: none; }

/* ---- Hero word-buildup (alive.js splitWords) --------------------------------------
   Words slide up individually - never fade the block. Original text kept in aria-label. */
.js-alive .alive-word {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.6em);
  transition: opacity 500ms var(--ease-quart-out), transform 500ms var(--ease-expo-out);
  transition-delay: calc(var(--alive-i, 0) * var(--stagger) + 200ms);
}
.js-alive.is-loaded .alive-word { opacity: 1; transform: none; }

/* ---- Micro-interactions ---------------------------------------------------------- */
.alive-press { transition: transform var(--dur-quick) var(--ease-standard); }
.alive-press:active { transform: scale(0.97); }
.alive-lift { transition: transform var(--dur-quick) var(--ease-standard),
              box-shadow var(--dur-quick) var(--ease-standard); }
.alive-lift:hover { transform: translateY(-3px); }

/* ---- Ambient layer (use ONCE, low amplitude) ------------------------------------- */
@keyframes alive-drift {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(0, -14px, 0); }
}
.alive-ambient { animation: alive-drift 9s var(--ease-float) infinite; }

/* ---- Reduced motion: real kill switch -------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .js-alive [data-alive], .js-alive [data-load], .js-alive .alive-word {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .alive-ambient { animation: none !important; }
  .alive-press, .alive-lift { transition: none; }
}
