/* ============================================================
   ZENITH LABS — Motion System

   Two engines, deliberately split:

   1. DISCRETE reveals  -> IntersectionObserver adds .is-in
      Universal support, no scroll-thread work, no jank.
   2. CONTINUOUS scrub  -> native `animation-timeline: view()/scroll()`
      Runs off-main-thread. Guarded by @supports; degrades to a
      static end-state (never a broken one) where unsupported.

   Register is "engineered", not playful: things settle like an
   instrument needle. No overshoot, no bounce, no elastic.
   ============================================================ */

/* ---------- 1. Discrete reveals (IO-driven) ---------- */

[data-reveal] {
  will-change: opacity, transform;
}

/* Base: fade + rise */
[data-reveal="up"] {
  opacity: 0;
  transform: translate3d(0, 28px, 0);
  transition:
    opacity var(--t-reveal) var(--e-out),
    transform var(--t-reveal) var(--e-out);
}

[data-reveal="fade"] {
  opacity: 0;
  transition: opacity var(--t-reveal) var(--e-out);
}

/* Wipe from the left — for rules and dividers */
[data-reveal="wipe"] {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--t-slow) var(--e-snap);
}

/* Scale-in for cards / media */
[data-reveal="scale"] {
  opacity: 0;
  transform: translate3d(0, 20px, 0) scale(0.985);
  transition:
    opacity var(--t-reveal) var(--e-out),
    transform var(--t-reveal) var(--e-out);
}

/* Clip reveal — the premium one. Content slides up inside a mask. */
[data-reveal="mask"] {
  clip-path: inset(0 0 100% 0);
  transition: clip-path var(--t-reveal) var(--e-out);
}

[data-reveal].is-in {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
  clip-path: inset(0 0 0 0);
  will-change: auto;
}

/* Stagger — set --i on children; index * step */
[data-stagger] > * {
  --step: 70ms;
  transition-delay: calc(var(--i, 0) * var(--step));
}
[data-stagger="fast"]  > * { --step: 45ms; }
[data-stagger="slow"]  > * { --step: 110ms; }

/* ---------- 2. Per-line masked text reveal ----------
   JS wraps each line in .line > .line__i. The mask is the .line
   overflow; the inner element translates up into view.
   This is the single most expensive-looking cheap effect. */

.line {
  display: block;
  overflow: hidden;
  /* Descenders get clipped by overflow:hidden — pad and pull back */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}

.line__i {
  display: block;
  transform: translate3d(0, 105%, 0);
  transition: transform var(--t-reveal) var(--e-out);
  transition-delay: calc(var(--l, 0) * 90ms);
  will-change: transform;
}

.is-in .line__i,
.line.is-in .line__i {
  transform: translate3d(0, 0, 0);
  will-change: auto;
}

/* ---------- 3. Continuous scrub (native scroll timelines) ---------- */

@supports (animation-timeline: view()) {

  /* Parallax drift — subtle. 
     Range tuned so movement happens while the element is on screen. */
  .scrub-parallax {
    animation: drift linear both;
    animation-timeline: view();
    animation-range: entry 0% exit 100%;
  }
  @keyframes drift {
    from { transform: translate3d(0, var(--drift-from, 6%), 0); }
    to   { transform: translate3d(0, var(--drift-to, -6%), 0); }
  }

  /* Section progress rail — fills as the section passes */
  .scrub-progress {
    animation: fill-x linear both;
    animation-timeline: view();
    animation-range: entry 100% exit 0%;
    transform-origin: left center;
  }
  @keyframes fill-x {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }

  /* Reading progress on the document scroll */
  .scrub-page-progress {
    animation: fill-x linear both;
    animation-timeline: scroll(root block);
    transform-origin: left center;
  }

  /* Slow zoom on media as it crosses the viewport */
  .scrub-zoom {
    animation: creep-zoom linear both;
    animation-timeline: view();
    animation-range: entry 0% exit 100%;
  }
  @keyframes creep-zoom {
    from { transform: scale(1.12); }
    to   { transform: scale(1.0); }
  }

  /* Pinned-track scrub: the inner element animates across the
     duration of its tall parent track. Used by the hero vial. */
  .scrub-pin > .pin-stage {
    animation: pin-seq linear both;
    animation-timeline: view(block);
    animation-range: contain 0% contain 100%;
  }
}

/* ---------- 4. Sticky pin track ----------
   Pure CSS pin: a tall track with a 100vh sticky stage inside.
   --pin-vh sets how many viewports of scroll the pin lasts. */

.pin-track {
  position: relative;
  height: calc(var(--pin-vh, 3) * 100vh);
}

.pin-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  place-items: center;
  overflow: hidden;
}

/* ---------- 5. Interaction primitives ---------- */

.hover-lift {
  transition:
    transform var(--t-base) var(--e-swiss),
    box-shadow var(--t-base) var(--e-swiss),
    border-color var(--t-base) var(--e-swiss);
}
.hover-lift:hover { transform: translate3d(0, -4px, 0); }

/* Underline that draws from the left */
.link-draw {
  position: relative;
  display: inline-block;
}
.link-draw::after {
  content: "";
  position: absolute;
  left: 0; bottom: -2px;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform var(--t-base) var(--e-out);
}
.link-draw:hover::after,
.link-draw:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left center;
}

/* ---------- 6. Reduced motion ----------
   We do NOT simply kill animation. We remove *travel* and keep
   opacity, so the page still reads as composed rather than static.
   Nothing is left invisible. */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    transition: opacity 200ms linear !important;
  }

  .line__i { transform: none !important; }

  .scrub-parallax,
  .scrub-zoom { animation: none !important; transform: none !important; }

  /* Pin tracks collapse to a single screen — no forced long scroll */
  .pin-track { height: auto !important; }
  .pin-stage { position: static !important; height: auto !important; }
}

/* No-JS safety: if the observer never runs, show everything. */
.no-js [data-reveal] {
  opacity: 1;
  transform: none;
  clip-path: none;
}
.no-js .line__i { transform: none; }
