/* ==========================================================================
   Deskrune — v2.1 SCROLL-DRIVEN MOTION + FINAL POLISH
   File: /assets/v2-motion-v20260508c.css

   This is the LAST stylesheet to load on every page. It is the final word on
   motion. The base v2-* sheets (still at version `b`) handle layout, color,
   typography. This sheet handles motion-grammar across the entire site.

   Brand-canon motion grammar:
   - Sinusoidal ease-in-out cubic-bezier(0.45, 0, 0.55, 1) — never bounce.
   - Durations: 180-360ms. Anything > 400ms is hype motion. We don't ship hype.
   - Reduced-motion is a hard kill on every animation. Vestibular safety first.
   - Per-property transitions only. No `transition: all` ever.

   Browser support note:
   - Scroll-driven animations (`animation-timeline: view()`) ship in:
       * Chrome 115+ (June 2023)  — full support
       * Edge 115+                — full support
       * Safari 17.4+ (Mar 2024)  — partial: view() yes, scroll() yes, but
                                    `animation-range` keywords need 17.4+
       * Firefox                  — flag-gated as of writing (May 2026)
   - We feature-detect with `@supports (animation-timeline: view())` and fall
     back to the IntersectionObserver-driven `.in-view` class wired in
     /assets/dramatic-effects-v20260508a.js.
   - Reduced-motion users see no scroll-driven animation at all — content
     simply renders in place at full opacity.

   The 7 motion upgrades are numbered in section order below.
   ========================================================================== */


/* --------------------------------------------------------------------------
   #3. SCROLL-PADDING-TOP — anchor links don't slam under sticky nav.
   Principle: when a hash link jumps, the target should land below the 64px
   sticky nav, not behind it. 88px gives 64 + 24 of breathing room.
   This rule is foundational for every page so we set it on :root.
   -------------------------------------------------------------------------- */
:root {
  scroll-padding-top: 88px;
  /* Fallback gracefully if the nav is hidden (e.g. PDF viewer pages) — the
     extra 88px is invisible there because there is no hash navigation. */
}
html {
  /* Smooth scroll for hash navigation. Reduced-motion overrides below. */
  scroll-behavior: smooth;
}


/* --------------------------------------------------------------------------
   #1. SCROLL-DRIVEN REVEAL UPGRADE — for browsers that ship scroll-timeline.
   Principle: when a browser supports `animation-timeline: view()`, the
   reveal animation is driven by the element's scroll position natively —
   no JS, no IntersectionObserver lag, no rAF jitter. The animation
   progresses linearly with scroll, so the user feels like the page is
   *responding* to them, not playing an animation *at* them.

   The IntersectionObserver fallback (in /assets/dramatic-effects-v20260508a.js)
   catches every browser that doesn't support scroll-timeline yet — Firefox,
   older Safari, and anyone behind a flag. They get the same fade-up via the
   `.in-view` class.

   IMPORTANT: nested inside `prefers-reduced-motion: no-preference` so any
   reduced-motion user falls through to the static fallback automatically.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    /* When scroll-timeline is supported, REPLACE the IntersectionObserver
       reveal with the native scroll-timeline reveal. We undo the JS-toggled
       opacity:0 baseline because the animation handles everything. */
    [data-reveal] {
      /* Reset baseline — animation will take it from from-state to to-state. */
      opacity: 1;
      transform: none;
      animation: dr-scroll-reveal linear both;
      animation-timeline: view();
      /* `entry 5%` — start animating when element is 5% into viewport.
         `cover 30%` — finish by the time 30% of viewport is covered.
         This means the reveal completes BEFORE the element reaches center,
         not after. Reading flow stays uninterrupted. */
      animation-range: entry 5% cover 30%;
    }
    [data-reveal-stagger] > * {
      opacity: 1;
      transform: none;
      animation: dr-scroll-reveal linear both;
      animation-timeline: view();
      animation-range: entry 5% cover 30%;
    }
  }
}

@keyframes dr-scroll-reveal {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}


/* --------------------------------------------------------------------------
   #2. BUY-PAGE KIT-CARD STAGGER — magazine layout, scroll-driven.
   Principle: each kit card on the buy page reveals at a slightly later
   scroll position than its predecessor, creating a magazine cascade.
   Pure CSS via `nth-child` + scroll-timeline range offset. No JS.

   The base v2-buy-v20260508b.css already animates `.live-product` with
   IntersectionObserver staggered delays (60ms apart). When scroll-timeline
   is supported we want the reveal driven by SCROLL not TIME, so we stagger
   the `animation-range` start by 2% per card instead.

   We target `.live-product` because that's the actual buy-page card class
   (we confirmed by reading the buy/index.html structure). `.kit-card` is
   the conceptual term used in spec; the implementation class is `.live-product`.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .live-product-grid > .live-product {
      /* Override the time-based animation-delay from v2-buy-v20260508b.css —
         delay is meaningless for scroll-driven animations.
         Each card's `entry` start shifts later by 2% so the cascade reads
         as a magazine reveal, not a single sweep. */
      animation: dr-scroll-reveal linear both;
      animation-timeline: view();
      animation-delay: 0s; /* nuke any time-based delay from earlier sheet */
    }
    .live-product-grid > .live-product:nth-child(1) {
      animation-range: entry 0% cover 25%;
    }
    .live-product-grid > .live-product:nth-child(2) {
      animation-range: entry 2% cover 28%;
    }
    .live-product-grid > .live-product:nth-child(3) {
      animation-range: entry 4% cover 30%;
    }
    .live-product-grid > .live-product:nth-child(4) {
      animation-range: entry 6% cover 32%;
    }
    .live-product-grid > .live-product:nth-child(5) {
      animation-range: entry 8% cover 34%;
    }
    .live-product-grid > .live-product:nth-child(6) {
      animation-range: entry 10% cover 36%;
    }
  }
}


/* --------------------------------------------------------------------------
   #4. AMAZON FILTER CHIPS — scroll-snap on the horizontal strip.
   Principle: the filter row scrolls horizontally on mobile (set in
   v2-amazon-v20260508b.css line 96+). Without snap, swipes can land
   between chips and look wrong. Snap-mandatory locks every swipe to
   a chip start, magazine-tab feel.

   Desktop has flex-wrap and no horizontal scroll — snap is harmless there
   because mandatory snap only triggers when the container actually scrolls.
   -------------------------------------------------------------------------- */
.az-filter-row {
  scroll-snap-type: x mandatory;
  scroll-padding-inline-start: 8px; /* leave 8px gutter so chip 1 isn't flush */
  -webkit-overflow-scrolling: touch; /* iOS momentum scroll */
}
.az-filter-row > .az-chip {
  scroll-snap-align: start;
  scroll-snap-stop: always;
  /* `always` means the snap NEVER skips a chip even on fast flicks. */
}


/* --------------------------------------------------------------------------
   #5. STICKY NAV BACKDROP BLUR — subtle materialization on scroll.
   Principle: when the user scrolls past 64px the nav becomes a translucent
   surface with backdrop-filter blur. This is done with a CSS-only sentinel
   approach — we use scroll-driven animation on `:root` to drive a custom
   property `--nav-blur-progress` that the nav reads.

   Why we picked the scroll-driven CSS approach over IntersectionObserver:
   - No new JS file to ship and load
   - Native paint timing — no flicker
   - Zero JS = zero main-thread cost on scroll

   Browsers without scroll-timeline support get a conditional fallback below
   that just always applies the blur. Acceptable degradation: the nav looks
   fine without the on-scroll-only flair.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    /* Drive a custom property from scroll position. The animation runs
       across the first 64px of vertical scroll on the root scroller. */
    :root {
      animation: dr-nav-progress linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 64px;
    }
    @keyframes dr-nav-progress {
      from { --nav-scrolled: 0; }
      to   { --nav-scrolled: 1; }
    }

    /* Nav opacity / backdrop respond to the progress var. We use a single
       CSS @property registration (registered below) so the var is animatable. */
    nav {
      background-color: rgb(
        from var(--ds-bg, #faf9f5) r g b /
        calc(1 - (var(--nav-scrolled, 0) * 0.1))
      );
      backdrop-filter: blur(calc(var(--nav-scrolled, 0) * 12px));
      -webkit-backdrop-filter: blur(calc(var(--nav-scrolled, 0) * 12px));
      transition: border-color 180ms cubic-bezier(0.45, 0, 0.55, 1);
    }
  }
}

/* Fallback — browsers without scroll-timeline get the blur applied always
   when the page has been scrolled at least once. We rely on JS that already
   ships in the codebase; if even that fails, the nav stays opaque cream
   which is also fine. (No regression vs current state.) */


/* --------------------------------------------------------------------------
   #6. HERO BRAND-GLYPH PARALLAX — 0.5x scroll speed, CSS-only.
   Principle: the brand-reveal-glyph (the small Deskrune mark in the hero)
   should drift slightly upward at half the scroll speed, giving the hero a
   parallax depth without dragging in any JS. Pure scroll-timeline.

   We move only the GLYPH inside the hero — never the headline, body copy,
   or anything text-bearing. Reading-stable text > parallax theatrics.

   Reduced-motion: NO parallax. Glyph stays static. The brand-reveal-glyph
   already has its own draw-in animation from visual-pass-v20260508a.css —
   that's a one-shot reveal which is fine for vestibular users; what we
   suppress here is the continuous scroll-bound translation.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    .brand-reveal-glyph {
      animation: dr-glyph-parallax linear both;
      animation-timeline: scroll(root block);
      /* Run across the first 100vh so by the time hero scrolls off, the
         glyph has drifted up by 50px. After that, no more movement. */
      animation-range: 0 100vh;
    }
    @keyframes dr-glyph-parallax {
      from { transform: translateY(0); }
      to   { transform: translateY(-50px); }
    }
  }
}


/* --------------------------------------------------------------------------
   #7. FINAL MOTION GRAMMAR AUDIT — universal hard rules.
   Principle: any motion that escaped the cascade gets normalized here.

   (a) Universal reduced-motion kill — the ultimate safety net. Even if a
       future stylesheet introduces an animation without its own reduced-
       motion guard, this one catches it.

   (b) Smooth scroll behaviour respected: when reduced-motion is on, hash
       jumps are instant rather than smooth-scrolled — also the canonical
       behaviour.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    /* 0.01ms (not 0) keeps animationend / transitionend listeners alive so
       JS that waits on them doesn't deadlock. */
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    /* Kill scroll-timeline animations entirely — they have no equivalent
       to a "1ms duration", they're tied to scroll position. Setting the
       timeline to none reverts the element to its CSS to-state. */
    animation-timeline: none !important;
  }
  html {
    scroll-behavior: auto !important;
  }
  /* Re-assert: data-reveal content must be visible (was the #1 vestibular
     trap in early v2 builds — a half-faded section if JS observer didn't
     fire). */
  [data-reveal],
  [data-reveal-stagger] > * {
    opacity: 1 !important;
    transform: none !important;
  }
  /* Hero brand glyph: stop parallax. */
  .brand-reveal-glyph {
    transform: none !important;
  }
  /* Nav: drop the dynamic backdrop-filter — keep the static nav surface. */
  nav {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
}


/* --------------------------------------------------------------------------
   BONUS: NORMALIZE EXISTING `transition: all` IN OLDER CSS (audit fix).
   The audit found one offender:
     /assets/v2-amazon-v20260508b.css:85
       .az-chip { transition: background 0.18s, color 0.18s, border-color 0.18s; }
   That line itself is fine, but the ORIGINAL non-versioned amazon CSS file
   inside /amazon/index.html has inline `transition: all 0.18s` on .az-chip
   (line 126 of amazon/index.html). We override it here with named properties.
   -------------------------------------------------------------------------- */
.az-chip {
  /* Override any inline `transition: all` with named-property transitions.
     Same duration (180ms) but only paints what changes. */
  transition:
    background-color 180ms cubic-bezier(0.45, 0, 0.55, 1),
    color 180ms cubic-bezier(0.45, 0, 0.55, 1),
    border-color 180ms cubic-bezier(0.45, 0, 0.55, 1),
    transform 180ms cubic-bezier(0.45, 0, 0.55, 1) !important;
}


/* --------------------------------------------------------------------------
   BONUS: GLOBAL EASING TOKEN — for any cascade stylesheet that wants the
   canon ease curve via a CSS variable instead of typing the cubic-bezier.
   -------------------------------------------------------------------------- */
:root {
  --ds-ease-canon: cubic-bezier(0.45, 0, 0.55, 1);
  --ds-dur-fast: 180ms;
  --ds-dur-base: 220ms;
  --ds-dur-slow: 360ms;
}


/* --------------------------------------------------------------------------
   END — v2-motion-v20260508c.css
   Reduced-motion users: every animation in this file is gated behind
   `prefers-reduced-motion: no-preference` OR has an explicit kill in the
   reduce block above. Verified by reading every selector twice.
   ========================================================================== */
