/* ============================================================================
   CLAUDE DESIGN LANGUAGE — motion-patterns.css
   The LIFE layer. Implementable, perf-safe versions of techniques captured from
   live award sites (see research/living-*.md). EXTENDS base.css — it reuses the
   --ease-* / --dur-* tokens and the .reveal/.stagger primitives; it does not
   duplicate them.

   THE CALM-BUT-ALIVE CONTRACT (from living-editorial.md):
     • One technique per element. Headlines reveal; images wipe; nav morphs.
     • Easing is always ~expo.out (--ease-out-expo). Never ease-in-out, never bounce.
     • ≤ 3 simultaneous animations per viewport. The eye tracks 3; more is noise.
     • Reveals 0.6–1.0s · page transitions 1.2–1.8s · micro-interactions < 0.3s.
     • EVERYTHING here is gated. The STATIC END-STATE is the default; motion is the
       progressive enhancement that only switches on inside
       @media (prefers-reduced-motion: no-preference). Author content motion-free.

   HOW TO READ A PATTERN: each block has a one-line "WHEN / DIAL" comment —
   when to reach for it, and where it sits on stoic → editorial → interactive.
   ============================================================================ */

/* Animatable custom properties (where a raw value can't be keyframed).
   Guarded — @property is ignored by engines that don't support it; the fallback
   is simply the static end-state, which is exactly what we want. */
@supports (background: paint(id)) { /* no-op guard; @property below is self-safe */ }
@property --mp-reveal { syntax: "<percentage>"; initial-value: 100%; inherits: false; }
@property --mp-angle  { syntax: "<angle>";      initial-value: 0deg;  inherits: false; }
@property --mp-shine  { syntax: "<angle>";      initial-value: 0deg;  inherits: false; }

/* ============================================================================
   BASELINE: every pattern's resting state lives OUTSIDE the media query, so a
   reduced-motion user (or an unsupported engine) sees the finished design.
   ============================================================================ */
.mp-line-mask,
.mp-line-mask .mp-line-inner { display: block; }
.mp-line-mask { overflow: hidden; }            /* the mask; inner sits at rest */

.mp-blur-up { opacity: 1; filter: none; transform: none; }
.mp-clip-wipe { clip-path: inset(0 0 0 0); }
.mp-wght-shift { font-variation-settings: "wght" var(--mp-wght-to, 600); }

/* ============================================================================
   LIVE LAYER — only switches on when motion is welcome.
   ============================================================================ */
@media (prefers-reduced-motion: no-preference) {

  /* --------------------------------------------------------------------------
     1 · MASKED LINE / WORD REVEAL                         [Joffrey Spitzer]
     WHEN: headlines, pull-quotes, lede paragraphs. The signature editorial move
           — text pushes up THROUGH a clipped baseline, never floats in.
     DIAL: editorial → interactive.
     Pair with motion-patterns.js splitLines()/splitWords(), or hand-author the
     <span class="mp-line-mask"><span class="mp-line-inner">…</span></span> nesting.
     -------------------------------------------------------------------------- */
  .mp-line-mask .mp-line-inner {
    transform: translateY(110%);
    transition: transform var(--dur-slower) var(--ease-out-expo);
    transition-delay: calc(var(--mp-i, 0) * 60ms);   /* per-line stagger via index */
    will-change: transform;
  }
  .mp-line-mask.is-in .mp-line-inner,
  .is-in > .mp-line-mask .mp-line-inner { transform: translateY(0); }
  /* words: tighter stagger, same mechanic */
  .mp-words .mp-line-mask .mp-line-inner { transition-delay: calc(var(--mp-i, 0) * 28ms); }

  /* --------------------------------------------------------------------------
     2 · SCROLL-DRIVEN REVEAL — native, zero-JS                 [Cyd Stumpel]
     WHEN: any non-hero block (section intros, captions, cards). Runs on the
           compositor; no IntersectionObserver bundle. Falls back to the JS-driven
           .reveal (base.css) where animation-timeline is unsupported — see @supports.
     DIAL: stoic → editorial.
     -------------------------------------------------------------------------- */
  @supports (animation-timeline: view()) {
    .mp-scroll-reveal {
      opacity: 1; transform: none;                  /* static default if JS-less + old engine */
      animation: mp-reveal-up linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 45%;
    }
    /* Let this pattern OWN scroll-reveal so we don't double-animate base's .reveal */
    .mp-scroll-reveal.reveal { opacity: 1; transform: none; transition: none; }
  }
  @keyframes mp-reveal-up {
    from { opacity: 0; transform: translateY(18px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  /* Sibling stagger for grids using scroll-timeline (delay via --mp-i) */
  .mp-scroll-stagger > * { animation-delay: calc(var(--mp-i, 0) * 80ms); }

  /* --------------------------------------------------------------------------
     3 · CLIP-PATH IMAGE WIPE-IN                       [Exo Ape · Balans]
     WHEN: feature/hero imagery. Replaces the tired fade-up — the image is
           uncovered like a print being placed. Direction set with --mp-from.
     DIAL: editorial.
     -------------------------------------------------------------------------- */
  .mp-clip-wipe {
    --mp-reveal: 100%;
    clip-path: inset(0 var(--mp-reveal) 0 0);       /* default: wipe from left */
  }
  .mp-clip-wipe.from-right { clip-path: inset(0 0 0 var(--mp-reveal)); }
  .mp-clip-wipe.from-bottom { clip-path: inset(var(--mp-reveal) 0 0 0); }
  @supports (animation-timeline: view()) {
    .mp-clip-wipe {
      animation: mp-wipe linear both;
      animation-timeline: view();
      animation-range: entry 8% entry 60%;
    }
  }
  /* JS-triggered variant (.is-in) for browsers without scroll-timeline */
  .mp-clip-wipe.is-trigger { transition: --mp-reveal var(--dur-page) var(--ease-out-expo); }
  .mp-clip-wipe.is-trigger.is-in { --mp-reveal: 0%; }
  @keyframes mp-wipe { to { --mp-reveal: 0%; } }

  /* --------------------------------------------------------------------------
     4 · BLUR-UP REVEAL                                  [Linear · Retool]
     WHEN: hero words, premium headings, key stats. Each unit clears from blurred
           + dropped to crisp. Highest signal-to-noise reveal in the product cluster.
     DIAL: editorial → interactive.  (Per-word stagger via --mp-i.)
     -------------------------------------------------------------------------- */
  .mp-blur-up {
    opacity: 0;
    filter: blur(8px);
    transform: translateY(20%);
    transition:
      opacity   var(--dur-slow) var(--ease-out),
      filter    var(--dur-slow) var(--ease-out),
      transform var(--dur-slower) var(--ease-out-expo);
    transition-delay: calc(var(--mp-i, 0) * 80ms);
    will-change: opacity, filter, transform;
  }
  .mp-blur-up.is-in,
  .is-in > .mp-blur-up,
  .is-in .mp-blur-up { opacity: 1; filter: blur(0); transform: none; }

  /* --------------------------------------------------------------------------
     5 · VARIABLE-FONT WEIGHT SHIFT                       [ABC Dinamo]
     WHEN: a single variable-font headline or number. The motion lives in the
           CONTENT, not a decoration layer. Hover OR scroll variant below.
     DIAL: stoic → editorial.  Requires a variable font (most theme display faces).
     -------------------------------------------------------------------------- */
  .mp-wght-shift {
    font-variation-settings: "wght" var(--mp-wght-from, 320);
    transition: font-variation-settings var(--dur-slow) var(--ease-out-expo);
  }
  .mp-wght-shift:hover,
  .mp-wght-shift:focus-visible { font-variation-settings: "wght" var(--mp-wght-to, 680); }
  @supports (animation-timeline: view()) {
    .mp-wght-scroll {
      animation: mp-weight linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 50%;
    }
    @keyframes mp-weight {
      from { font-variation-settings: "wght" var(--mp-wght-from, 320); }
      to   { font-variation-settings: "wght" var(--mp-wght-to, 680); }
    }
  }

  /* --------------------------------------------------------------------------
     6 · MARQUEE BAND                                     [Cosmos · Graza]
     WHEN: one ambient belt per page — credentials, client names, a tagline.
           Continuous life with no scroll/hover dependency. Duplicate the track's
           children so -50% is a seamless loop. Pauses on hover.
     DIAL: editorial → interactive.  One instance per page for editorial clients.
     -------------------------------------------------------------------------- */
  .mp-marquee { overflow: hidden; width: 100%; -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
  .mp-marquee-track {
    display: inline-flex; flex-wrap: nowrap; white-space: nowrap;
    gap: var(--mp-marquee-gap, var(--space-l));
    will-change: transform;
    animation: mp-marquee var(--mp-marquee-dur, 36s) linear infinite;
  }
  .mp-marquee:hover .mp-marquee-track { animation-play-state: paused; }
  .mp-marquee--reverse .mp-marquee-track { animation-direction: reverse; }
  @keyframes mp-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

  /* --------------------------------------------------------------------------
     7 · MAGNETIC / HOVER-LIFT MICRO-INTERACTIONS         [Exo Ape · Vercel]
     WHEN: buttons, cards, thumbnails, links. Sub-0.3s feedback that says
           "this is touchable." Magnetic offset (--mp-mx/--mp-my) is set by JS;
           the lift + scale are pure CSS.
     DIAL: any.  These are the floor — safe even on stoic projects.
     -------------------------------------------------------------------------- */
  .mp-lift { transition: transform var(--dur-base) var(--ease-out-expo), box-shadow var(--dur-base) var(--ease-out); will-change: transform; }
  .mp-lift:hover { transform: translateY(-3px); }
  .mp-lift:active { transform: translateY(-1px) scale(0.99); }

  .mp-thumb { overflow: hidden; }
  .mp-thumb > img, .mp-thumb > video { transition: transform var(--dur-slow) var(--ease-out-expo); will-change: transform; }
  .mp-thumb:hover > img, .mp-thumb:hover > video { transform: scale(1.04); }

  .mp-magnetic {
    transition: transform var(--dur-base) var(--ease-out-expo);
    transform: translate(calc(var(--mp-mx, 0) * 1px), calc(var(--mp-my, 0) * 1px));
    will-change: transform;
  }

  /* Shine-orbit border — slow rotating highlight on a card edge   [Retool]
     WHEN: one feature card on a dark surface. Near-invisible at rest. */
  .mp-shine { position: relative; isolation: isolate; }
  .mp-shine::before {
    content: ""; position: absolute; inset: -1px; border-radius: inherit; padding: 1px; pointer-events: none;
    background: conic-gradient(from var(--mp-shine), transparent 62%, color-mix(in oklab, var(--accent) 70%, white) 72%, transparent 82%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor; mask-composite: exclude;
    animation: mp-shine-spin 9s linear infinite;
  }
  @keyframes mp-shine-spin { to { --mp-shine: 360deg; } }

  /* --------------------------------------------------------------------------
     8 · STICKY-SCROLL SCENE HELPERS              [Scout · Immersive Garden]
     WHEN: a chapter you want to hold while content advances (pinned narrative).
           CSS-native pinning via position:sticky; pair with scroll-timeline or
           the IO helper in motion-patterns.js for step state. No GSAP needed
           for simple cases — see JS notes for the heavy/pinned upgrade path.
     DIAL: interactive.
     -------------------------------------------------------------------------- */
  .mp-scene { position: relative; }
  .mp-scene-sticky { position: sticky; top: 0; min-height: 100vh; display: grid; align-content: center; overflow: clip; }
  .mp-scene-steps > * { min-height: 90vh; }                  /* each scroll "beat" */
  /* Parallax-lite: JS sets --mp-scroll on :root; layers translate at <0.15 speed */
  .mp-parallax { transform: translateY(calc(var(--mp-scroll, 0) * var(--mp-depth, 0.04) * 1px)); will-change: transform; }

  /* --------------------------------------------------------------------------
     9 · ROLLING-NUMBER COUNTER (CSS side)                       [Stripe]
     WHEN: a single live-feeling stat. JS (rollNumber) updates the digits; this
           just locks tabular width + eases the vertical slide. DIAL: editorial.
     -------------------------------------------------------------------------- */
  .mp-counter { font-variant-numeric: tabular-nums lining-nums; font-feature-settings: "tnum" 1; }
  .mp-counter .mp-digit { display: inline-block; transition: transform var(--dur-slow) var(--ease-out-expo); }

} /* end @media no-preference */

/* ============================================================================
   10 · VIEW TRANSITIONS — native cross-page morphing            [Cyd Stumpel]
   WHEN: multi-page sites. A thumbnail morphs into the next page's hero; the page
         turns like a magazine. Zero JS for same-origin MPA navigation.
   DIAL: editorial → interactive.
   This lives OUTSIDE the media query because the API itself must be declared
   unconditionally; the timing/eased part is reduced-motion-gated below, and the
   browser additionally honors reduced-motion by shortening transitions.
   Give shared elements a unique view-transition-name (e.g. .mp-vt-hero).
   ============================================================================ */
/* @view-transition disabled — pages render client-side, so cross-doc nav faded into an empty #app. Re-enable once routes are prerendered. */
.mp-vt-hero  { view-transition-name: mp-hero; }
.mp-vt-title { view-transition-name: mp-title; }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(*) { animation-duration: var(--dur-page); animation-timing-function: var(--ease-out-expo); }
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: var(--dur-page); animation-timing-function: var(--ease-out-expo); }
}
@media (prefers-reduced-motion: reduce) {
  /* Belt-and-braces: collapse any cross-fade to an instant cut. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ============================================================================
   REDUCED-MOTION — explicit hard-stop for everything above.
   base.css already neutralizes .reveal/.stagger + zeroes durations globally;
   this guarantees these new patterns also resolve to their finished state.
   ============================================================================ */
@media (prefers-reduced-motion: reduce) {
  .mp-line-mask .mp-line-inner { transform: none !important; }
  .mp-blur-up { opacity: 1 !important; filter: none !important; transform: none !important; }
  .mp-scroll-reveal { opacity: 1 !important; transform: none !important; animation: none !important; }
  .mp-clip-wipe { clip-path: inset(0 0 0 0) !important; animation: none !important; }
  .mp-wght-shift, .mp-wght-scroll { animation: none !important; }
  .mp-marquee-track { animation: none !important; transform: none !important; }
  .mp-shine::before { animation: none !important; }
  .mp-parallax { transform: none !important; }
  .mp-lift, .mp-magnetic, .mp-thumb > img, .mp-thumb > video { transition: none !important; transform: none !important; }
}
