/* VBK-1932 — Light builder motion primitives, CSS half.
   Pairs with motion.js (GSAP + ScrollTrigger + SplitText + Lenis).

   DIVISION OF LABOUR, and it matters:
   - CSS owns STATE-DRIVEN micro-interaction — :hover, :focus-visible, :active.
     These are interruptible by nature, need no frame loop, and a transition
     expresses them better and cheaper than JS ever will.
   - JS owns everything with a TIMELINE or a SIMULATION — reveals, scrubbed
     scroll, springs, masked text, pinned sequences.

   Nothing below transitions `transform` on an element motion.js drives
   (magnetic, tilt, parallax, mask, pin, horizontal, marquee). A CSS transition
   on those would re-interpolate every inline transform GSAP writes each frame,
   which reads as lag and fights the spring — that conflict is the single
   easiest way to make a good engine feel broken. Hover scale for magnetic
   buttons is therefore applied to an INNER element, never the tracked one. */

:root {
  /* --- tokens consumed by CSS --- */
  --motion-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --motion-ease-overshoot: cubic-bezier(0.34, 1.56, 0.64, 1);
  --motion-duration-fast: 200ms;
  --motion-duration-base: 550ms;
  --motion-scale-hover: 1.03;

  /* --- tokens consumed by motion.js (seconds + GSAP ease names) ---
     Kept in CSS so ONE attribute on <html> retunes both halves at once. */
  --motion-duration-s: 0.9;
  --motion-distance: 28;
  --motion-stagger-step-s: 0.08;
  --motion-ease-js: power3.out;
  --motion-scroll-duration-s: 1.05;
}

/* ---------------------------------------------------------------------------
   MOTION CHARACTER — one attribute on <html>, set from the art direction.
   --------------------------------------------------------------------------- */

[data-motion-character="mechanical"] {
  --motion-ease: cubic-bezier(0.4, 0, 0.6, 1);
  --motion-duration-fast: 120ms; --motion-duration-base: 220ms;
  --motion-scale-hover: 1;
  --motion-duration-s: 0.34; --motion-distance: 10; --motion-stagger-step-s: 0.04;
  --motion-ease-js: power1.inOut; --motion-scroll-duration-s: 0.8;
}

[data-motion-character="organic"] {
  --motion-ease: cubic-bezier(0.16, 0.84, 0.44, 1);
  --motion-duration-fast: 320ms; --motion-duration-base: 800ms;
  --motion-scale-hover: 1.05;
  --motion-duration-s: 1.1; --motion-distance: 32; --motion-stagger-step-s: 0.1;
  --motion-ease-js: power2.out; --motion-scroll-duration-s: 1.25;
}

[data-motion-character="precise"] {
  --motion-ease: cubic-bezier(0.2, 0, 0.2, 1);
  --motion-duration-fast: 140ms; --motion-duration-base: 320ms;
  --motion-scale-hover: 1.01;
  --motion-duration-s: 0.5; --motion-distance: 14; --motion-stagger-step-s: 0.05;
  --motion-ease-js: power3.out; --motion-scroll-duration-s: 0.9;
}

[data-motion-character="cinematic"] {
  --motion-ease: cubic-bezier(0.19, 1, 0.22, 1);
  --motion-duration-fast: 400ms; --motion-duration-base: 900ms;
  --motion-scale-hover: 1.04;
  --motion-duration-s: 1.4; --motion-distance: 40; --motion-stagger-step-s: 0.12;
  --motion-ease-js: expo.out; --motion-scroll-duration-s: 1.4;
}

[data-motion-character="bouncy"] {
  --motion-ease: var(--motion-ease-overshoot);
  --motion-duration-fast: 220ms; --motion-duration-base: 480ms;
  --motion-scale-hover: 1.08;
  --motion-duration-s: 0.8; --motion-distance: 30; --motion-stagger-step-s: 0.07;
  --motion-ease-js: back.out(1.7); --motion-scroll-duration-s: 1;
}

[data-motion-character="editorial"] {
  --motion-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --motion-duration-base: 650ms;
  --motion-duration-s: 1; --motion-distance: 26; --motion-stagger-step-s: 0.09;
  --motion-ease-js: power3.out; --motion-scroll-duration-s: 1.1;
}

[data-motion-character="quiet"] {
  --motion-duration-fast: 140ms; --motion-duration-base: 260ms;
  --motion-scale-hover: 1.01;
  --motion-duration-s: 0.45; --motion-distance: 8; --motion-stagger-step-s: 0.03;
  --motion-ease-js: power1.out; --motion-scroll-duration-s: 0.7;
}

/* ---------------------------------------------------------------------------
   ANTI-FOUC GATE. `js` is added by motion.js ONLY once GSAP is confirmed
   loaded — so if the engine 404s, none of this applies and the page renders
   as ordinary visible content. motion.js then immediately takes ownership of
   the start state with gsap.set(), which is why these rules must never also
   specify a transform: two sources for one property is how reveals end up
   animating 0 -> 0.
   --------------------------------------------------------------------------- */

.js .reveal,
.js [data-motion~="reveal"],
.js .stagger-children > * {
  opacity: 0;
}

.js [data-motion~="hero"] [data-hero-step],
.js [data-motion~="hero-media"] {
  opacity: 0;
}

.is-visible { opacity: 1; }

/* ---------------------------------------------------------------------------
   MASKED TEXT. SplitText builds the wrapper elements; these rules give the
   mask something to clip against.
   --------------------------------------------------------------------------- */

[data-split] .motion-line,
[data-split] .motion-word,
[data-split] .motion-char {
  display: inline-block;
  will-change: transform;
}

/* SplitText's own mask wrappers. overflow:clip beats hidden here — hidden
   establishes a scroll container, which on a heading can trap focus scrolling. */
[data-split] .split-line,
[data-split] .split-word,
[data-split] .split-char {
  overflow: clip;
  display: inline-block;
}

/* ---------------------------------------------------------------------------
   STATE-DRIVEN MICRO-INTERACTION — CSS's half of the job.
   --------------------------------------------------------------------------- */

[data-motion~="magnetic"] {
  /* No transition: motion.js writes x/y here every frame. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  will-change: transform;
}

/* The scale lives on an inner span so it cannot collide with the spring. */
[data-motion~="magnetic"] > * {
  transition: transform var(--motion-duration-fast) var(--motion-ease);
}
[data-motion~="magnetic"]:hover > * {
  transform: scale(var(--motion-scale-hover));
}

[data-motion~="tilt"] {
  will-change: transform;
}

[data-motion~="parallax"],
[data-motion~="mask"] img,
[data-motion~="mask"] video {
  will-change: transform;
}

[data-motion~="mask"] {
  overflow: clip;
}

/* Underline wipe — pure CSS, no JS needed, and a staple of the look. */
[data-motion~="underline"] {
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size var(--motion-duration-base) var(--motion-ease);
  text-decoration: none;
}
[data-motion~="underline"]:hover,
[data-motion~="underline"]:focus-visible {
  background-size: 100% 1px;
}

/* ---------------------------------------------------------------------------
   STRUCTURAL PRIMITIVES.
   --------------------------------------------------------------------------- */

[data-motion~="horizontal"] { overflow: clip; }
[data-motion~="horizontal"] [data-horizontal-track] {
  display: flex;
  flex-wrap: nowrap;
  will-change: transform;
}

[data-motion~="marquee"] { overflow: clip; }
[data-motion~="marquee"] [data-marquee-track] {
  display: flex;
  flex-wrap: nowrap;
  width: max-content;
  will-change: transform;
}

[data-motion~="pin"] [data-pin-step] { will-change: transform, opacity; }

/* ---------------------------------------------------------------------------
   REDUCED MOTION. motion.js returns early and marks everything visible; this
   block is the belt to that braces, covering the CSS-only interactions too.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .js .reveal,
  .js [data-motion~="reveal"],
  .js .stagger-children > *,
  .js [data-motion~="hero"] [data-hero-step],
  .js [data-motion~="hero-media"] {
    opacity: 1 !important;
    transform: none !important;
  }
}
