/**
 * motion.css — the Nexus motion KIT (XR-4086, DESIGN-RULING-2026-07-12-001).
 *
 * ONE definition of every motion primitive we ship (Rule 37). Plain CSS, no bundler, no dependency —
 * sites has no build step, and this must work inside a generated single-file client site as-is.
 *
 * A generated page opts in with MARKUP ONLY: add data-reveal / data-scrub / data-count / … and it
 * works. That is the whole point — build #1 took a session, builds #2–#10 should take markup.
 *
 * THE JOB TEST (binding): every primitive below names its job in one clause. Anything that cannot is
 * decoration, and decoration comes out.
 *
 * BANNED, and absent by construction: smooth-scroll hijacking (Lenis et al), scroll-jacked horizontal
 * galleries, WebGL-as-decoration, preloaders, bounce/elastic easing, and — the counterintuitive one —
 * ANY entrance animation on above-the-fold content, because that IS the LCP element. `motion.js`
 * enforces the last one at runtime: anything already in the viewport at load renders FINAL, instantly.
 *
 * THE FLOOR IS HARD, NOT ASPIRATIONAL: mobile LCP ≤2.0s · INP ≤200ms · CLS ≤0.05 · perf ≥90 · a11y ≥95.
 * Our buyer is a roofer on a 3-year-old Android on one bar of LTE — not a jury on a 16-inch MacBook.
 * Every transform below is compositor-only (transform/opacity). Nothing animates layout, so nothing
 * here can leak CLS.
 */

/* Motion is OPT-IN and only ever enhances. With JS off, .m-ready is never set, and every element
   below sits at its final, fully-legible state. All copy is in the DOM at load. */

/* ── 1. REVEAL — "bring this into view as the reader arrives at it" ───────────────────────────── */
/* `.reveal` is the LEGACY alias. Every generated client site is already covered in `class="reveal"`
   and `reveal-delay-N` markup — and it was DEAD: the generator applied the classes and ran an
   IntersectionObserver that added `.visible`, but no CSS rule for it ever existed. So the "reveal
   system" on every site we have ever shipped did precisely nothing. Aliasing it here lights all of
   that existing markup up through the ONE kit, with zero template churn and no second system
   (Rule 37 — a fork is how you get three definitions of one idea). */
[data-reveal],
.reveal {
  opacity: 1;
  transform: none;
}
.m-ready [data-reveal]:not(.m-in):not(.m-above),
.m-ready .reveal:not(.m-in):not(.m-above) {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
}
.m-ready [data-reveal],
.m-ready .reveal {
  transition: opacity .55s cubic-bezier(.22, .61, .36, 1), transform .55s cubic-bezier(.22, .61, .36, 1);
  will-change: opacity, transform;
}
.m-ready [data-reveal].m-in,   .m-ready [data-reveal].m-above,
.m-ready .reveal.m-in,         .m-ready .reveal.m-above {
  opacity: 1;
  transform: none;
  will-change: auto;
}
/* stagger: data-reveal-delay="1|2|3" (or the legacy .reveal-delay-N) — a rhythm, not a queue.
   Capped at 3: past that the reader is waiting on us, which is the opposite of the job. */
.m-ready [data-reveal-delay="1"], .m-ready .reveal-delay-1 { transition-delay: .07s; }
.m-ready [data-reveal-delay="2"], .m-ready .reveal-delay-2 { transition-delay: .14s; }
.m-ready [data-reveal-delay="3"], .m-ready .reveal-delay-3 { transition-delay: .21s; }
.m-ready .reveal-delay-4 { transition-delay: .21s; }
.m-ready .reveal-delay-5 { transition-delay: .21s; }

/* ── 2. SCRUB — "tie this element's own progress to the reader's scroll" ──────────────────────── */
/* motion.js sets --p (0→1) on the element as it travels the viewport. Consume it in your own CSS:
   e.g. transform: scaleX(var(--p)); or opacity: var(--p). No layout properties. */
[data-scrub] { --p: 0; }

/* A progress rail is the honest use of scrub: it SHOWS the reader where they are. */
[data-scrub-rail] {
  transform-origin: 0 50%;
  transform: scaleX(var(--p, 0));
}

/* ── 3. PIN — "hold ONE thing still while its explanation scrolls past" ───────────────────────── */
/* position:sticky, NOT scroll-jacking. The reader's scroll always means what they expect. ONE per
   page: a second pin is a competing focal point, and motion.js refuses it. */
[data-pin] {
  position: sticky;
  top: var(--m-pin-top, 12vh);
}

/* ── 4. MACHINE — "show the product actually doing the thing we sell" ─────────────────────────── */
/* The signature moment. motion.js advances data-state (0,1,2,…) from scrub progress or a timer; you
   style each state. This is a DOM/CSS state machine, not a video scrub: crisp at any DPR, ~2KB rather
   than 3–8MB of frames, cannot break the perf floor, and stays TRUE when the product UI changes. */
[data-machine] [data-step] {
  opacity: .28;
  transition: opacity .4s ease, transform .4s ease;
}
[data-machine] [data-step].m-active {
  opacity: 1;
}
[data-machine] [data-step].m-done {
  opacity: .5;
}

/* ── 5. COUNT — "make a real number land" ─────────────────────────────────────────────────────── */
/* Only ever point this at a number we can substantiate (Rule 43). tabular-nums so the width is
   stable while it ticks — a counter that reflows is a CLS bug wearing a nice suit. */
[data-count] { font-variant-numeric: tabular-nums; }

/* ── 6. MARQUEE — "imply an endless roster without asking for attention" ──────────────────────── */
/* Pure CSS. JS only clones the track so the loop is seamless. Pauses on hover so it is readable. */
[data-marquee] {
  display: flex;
  overflow: hidden;
  gap: var(--m-marquee-gap, 3rem);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
[data-marquee] > * {
  display: flex;
  gap: var(--m-marquee-gap, 3rem);
  flex: 0 0 auto;
  animation: m-marquee var(--m-marquee-speed, 38s) linear infinite;
}
[data-marquee]:hover > * { animation-play-state: paused; }
@keyframes m-marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(-100% - var(--m-marquee-gap, 3rem)), 0, 0); }
}

/* ── 7. TILT — "confirm this card is a thing you can act on" ──────────────────────────────────── */
/* Pointer micro-interaction. Fine pointers only: on a phone there IS no hover, and faking one costs
   INP for nothing. Deliberately small — 6°, not a toy. */
@media (hover: hover) and (pointer: fine) {
  [data-tilt] {
    transition: transform .18s ease-out;
    transform: perspective(700px) rotateX(calc(var(--ty, 0) * -6deg)) rotateY(calc(var(--tx, 0) * 6deg));
    will-change: transform;
  }
}

/* ── THE FLOOR: prefers-reduced-motion ───────────────────────────────────────────────────────────
   Not "un-fade and carry on" — the ruling is explicit that reduced-motion DISABLES pins. A pinned
   section for someone with vestibular sensitivity is the exact thing they asked us not to do. So the
   pin becomes static flow, the marquee stops, the counter jumps to its final value, and every reveal
   is simply already there. The page still says everything it said. */
@media (prefers-reduced-motion: reduce) {
  .m-ready [data-reveal],
  .m-ready .reveal,
  .m-ready [data-reveal]:not(.m-in):not(.m-above),
  .m-ready .reveal:not(.m-in):not(.m-above) {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  [data-pin] {
    position: static !important;
  }
  [data-marquee] > * {
    animation: none !important;
  }
  [data-tilt] {
    transform: none !important;
    transition: none !important;
  }
  [data-machine] [data-step] {
    opacity: 1 !important;
    transition: none !important;
  }
}
