/* ════════════════════════════════════════════════════════════════════════
   FX ENGINE  ·  reusable motion & effects foundation
   ─────────────────────────────────────────────────────────────────────────
   Built to carry LOTS of designs & effects without jank.

   PRINCIPLES
   • GPU-only: every effect animates transform / opacity / filter — the three
     properties the browser can move on the compositor thread (no layout, no
     repaint → smooth 60fps even when the main thread is busy).
   • Content-safe: nothing is hidden unless JavaScript is running (html.js).
     If JS is blocked or fails, the page is fully readable.
   • Respectful: honours prefers-reduced-motion AND prefers-reduced-data /
     Save-Data. Motion is a bonus, never a barrier.
   • Declarative: add one attribute, get an effect. Scales to any number of
     elements through a SINGLE IntersectionObserver (see assets/js/main.js).

   USAGE  (full guide in FX-SYSTEM.md)
     <div data-fx="up">…</div>                 reveal: rise + fade
     <div data-fx="zoom-in" data-fx-delay="150">…</div>
     <ul  data-fx-stagger data-fx-step="90">…</ul>   children cascade in
     <img data-fx-scroll="parallax-sm">           continuous scroll parallax
   ════════════════════════════════════════════════════════════════════════ */

/* ── Motion tokens — every effect pulls from these, so a hundred animations
      still feel like ONE design language. Re-tune the whole site from here. ── */
:root {
  --fx-dur:        720ms;
  --fx-dur-slow:  1100ms;
  --fx-ease:        cubic-bezier(.22, .61, .36, 1);   /* confident ease-out   */
  --fx-ease-spring: cubic-bezier(.34, 1.56, .64, 1);  /* slight overshoot     */
  --fx-distance:   28px;   /* travel for directional reveals      */
  --fx-distance-lg: 60px;
  --fx-blur:       12px;   /* blur-in start                       */
  --fx-scale:      .92;    /* zoom-in start                       */
  --fx-step:       80ms;   /* default stagger interval            */
  /* progress bar fill — falls back to gold if the site sets nothing */
  --fx-progress-grad: var(--grad-gold, linear-gradient(90deg, #e8b94a, #f6d98a));
}

/* ═══════════════ 1 · REVEAL ON SCROLL  (data-fx) ════════════════════════
   Hidden state is applied ONLY when html has the `js` class (set inline in
   <head> before paint). No JS → no hidden state → content always shows. */
html.js [data-fx] {
  opacity: 0;
  transition:
    opacity   var(--fx-dur) var(--fx-ease),
    transform var(--fx-dur) var(--fx-ease),
    filter    var(--fx-dur) var(--fx-ease),
    clip-path var(--fx-dur) var(--fx-ease);
}

/* Starting transforms per variant (GPU compositor only) */
html.js [data-fx="fade"]      { transform: none; }
html.js [data-fx="up"]        { transform: translate3d(0, var(--fx-distance), 0); }
html.js [data-fx="down"]      { transform: translate3d(0, calc(var(--fx-distance) * -1), 0); }
html.js [data-fx="left"]      { transform: translate3d(var(--fx-distance), 0, 0); }
html.js [data-fx="right"]     { transform: translate3d(calc(var(--fx-distance) * -1), 0, 0); }
html.js [data-fx="up-lg"]     { transform: translate3d(0, var(--fx-distance-lg), 0); }
html.js [data-fx="zoom-in"]   { transform: scale(var(--fx-scale)); }
html.js [data-fx="zoom-out"]  { transform: scale(1.06); }
html.js [data-fx="rotate"]    { transform: translate3d(0, var(--fx-distance), 0) rotate(-3deg); transform-origin: left bottom; }
html.js [data-fx="flip"]      { transform: perspective(900px) rotateX(14deg); transform-origin: center bottom; }
html.js [data-fx="blur"]      { filter: blur(var(--fx-blur)); }
html.js [data-fx="mask"]      { clip-path: inset(0 0 100% 0); transform: translate3d(0, 8px, 0); }
html.js [data-fx="spring"]    { transform: translate3d(0, var(--fx-distance), 0) scale(.96);
                                transition-timing-function: var(--fx-ease-spring); }

/* Revealed — JS adds .is-in when the element scrolls into view */
html.js [data-fx].is-in {
  opacity: 1;
  transform: none;
  filter: none;
}
html.js [data-fx="mask"].is-in { clip-path: inset(0 0 -20% 0); }

/* ═══════════════ 2 · STAGGER  (data-fx-stagger on a parent) ══════════════
   Direct children cascade in. JS sets --fx-i (index) per child; optional
   data-fx-step overrides the interval. Great for grids, lists, nav rows. */
html.js [data-fx-stagger] > * {
  opacity: 0;
  transform: translate3d(0, var(--fx-distance), 0);
  transition:
    opacity   var(--fx-dur) var(--fx-ease),
    transform var(--fx-dur) var(--fx-ease);
  transition-delay: calc(var(--fx-i, 0) * var(--fx-step));
}
html.js [data-fx-stagger].is-in > * { opacity: 1; transform: none; }

/* ═══════════════ 3 · SCROLL-DRIVEN LAYER  (data-fx-scroll) ═══════════════
   Continuous, scrubbed-by-scroll effects that run 100% on the compositor
   thread via the modern scroll-timeline API — zero main-thread JavaScript.
   Progressive enhancement: only activates where supported (Chrome/Edge 115+,
   Safari 18+); everywhere else the element simply sits still. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    [data-fx-scroll="parallax"] {
      animation: fx-parallax linear both;
      animation-timeline: view();
      animation-range: cover;
    }
    [data-fx-scroll="parallax-sm"] {
      animation: fx-parallax-sm linear both;
      animation-timeline: view();
      animation-range: cover;
    }
    [data-fx-scroll="rise"] {
      animation: fx-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 65%;
    }
    [data-fx-scroll="zoom"] {
      animation: fx-scrollzoom linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }
  }
}
/* keyframes use scale on the parallax ones so the moved image never shows an
   edge inside an overflow:hidden frame */
@keyframes fx-parallax    { from { transform: translate3d(0, -8%, 0) scale(1.12); }
                            to   { transform: translate3d(0,  8%, 0) scale(1.12); } }
@keyframes fx-parallax-sm { from { transform: translate3d(0, -5%, 0) scale(1.08); }
                            to   { transform: translate3d(0,  5%, 0) scale(1.08); } }
@keyframes fx-rise        { from { opacity: 0; transform: translate3d(0, 42px, 0); }
                            to   { opacity: 1; transform: none; } }
@keyframes fx-scrollzoom  { from { transform: scale(1.14); }
                            to   { transform: scale(1); } }

/* ═══════════════ 4 · SCROLL PROGRESS BAR ════════════════════════════════
   compositor-only (transform: scaleX). JS drives it everywhere; this is the
   only element we promote with will-change because it animates constantly. */
.fx-progress {
  position: fixed; inset: 0 0 auto 0; height: 3px; z-index: 300;
  transform: scaleX(0); transform-origin: 0 50%;
  background: var(--fx-progress-grad);
  box-shadow: 0 0 12px rgba(232, 185, 74, .4);
  will-change: transform; pointer-events: none;
}

/* ═══════════════ 5 · RENDER-PERF UTILITY ════════════════════════════════
   .fx-cv lets the browser skip layout/paint for far-offscreen blocks on long
   pages. contain-intrinsic-size reserves height so the scrollbar stays put.
   Opt-in — add to tall sections that are NOT in-page #anchor targets. */
.fx-cv { content-visibility: auto; contain-intrinsic-size: auto 700px; }

/* ═══════════════ 6 · SAFETY RAILS ═══════════════════════════════════════ */

/* Fallback: if the observer ever misses an element, force everything visible
   after a short grace period (JS adds .fx-fallback). Content can never stick. */
html.fx-fallback [data-fx],
html.fx-fallback [data-fx-stagger] > * {
  opacity: 1 !important; transform: none !important;
  filter: none !important; clip-path: none !important;
}

/* Data-saver: drop ambient/continuous motion, keep the content + reveals */
@media (prefers-reduced-data: reduce) {
  [data-fx-scroll] { animation: none !important; }
}

/* Reduced motion: no movement at all — instant, accessible, nausea-free */
@media (prefers-reduced-motion: reduce) {
  html.js [data-fx],
  html.js [data-fx-stagger] > * {
    opacity: 1 !important; transform: none !important;
    filter: none !important; clip-path: none !important;
    transition: none !important;
  }
  [data-fx-scroll] { animation: none !important; }
  .fx-progress { display: none; }
}
