/* ==========================================================================
   MOTION.CSS — Tier-0 motion system (0 KB JS). Shared by every site.
   Source of truth: TIER-GRADING.md. Values from the premium-web-design skill
   (motion-system.md) and Website Business/docs/research/DESIGN.md.

   Laws encoded here:
   - Base state is VISIBLE. If nothing applies (Firefox, reduced motion, no
     support) the content simply sits there. Never hide in CSS.
   - Only transform / opacity / clip-path animate. Compositor-only.
   - One easing family, four durations. Restraint is the signal.
   - Reduced motion REPLACES movement with fades; it does not delete feedback.
   ========================================================================== */

:root {
  --ease-out-expo:   cubic-bezier(0.19, 1, 0.22, 1);   /* the house curve */
  --ease-out-expo2:  cubic-bezier(0.16, 1, 0.30, 1);   /* large media transforms */
  --ease-inout-expo: cubic-bezier(0.87, 0, 0.13, 1);   /* symmetric moves */
  --ease-ui:         cubic-bezier(0.40, 0, 0.20, 1);   /* hover / focus / colour only */

  --duration-fast:  180ms;   /* hover, focus, colour */
  --duration-base:  360ms;   /* small movement */
  --duration-slow:  800ms;   /* reveals */
  --duration-media: 1200ms;  /* large media transforms */

  --reveal-distance: 40px;
  --stagger: 6%;             /* per-child offset inside a reveal group (view-timeline %) */
  --parallax: 10%;           /* max differential; never above 15% */
}

/* Halve the travel on phones. The motion table calls for half the distance and
   a 20-30% shorter duration on mobile, and 40px of rise on a 390px screen is a
   long way to move a card. */
@media (max-width: 599px) {
  :root { --reveal-distance: 20px; }
}

/* --------------------------------------------------------------------------
   DISPLAY TYPE HELPERS — the two highest-leverage moves, both free
   -------------------------------------------------------------------------- */
.display      { line-height: 0.85; letter-spacing: -0.045em; text-wrap: balance; }
.display-md   { line-height: 0.95; letter-spacing: -0.03em;  text-wrap: balance; }
.label-caps   { letter-spacing: 0.08em; text-transform: uppercase; }
p, li         { text-wrap: pretty; }

/* --------------------------------------------------------------------------
   SCROLL-DRIVEN REVEALS — the backbone. Off the main thread.
   <el data-reveal>            rise (default)
   <el data-reveal="fade">     opacity only
   <el data-reveal="left|right|scale|clip">
   <ul data-reveal-group>      children stagger via --i (set inline or by nth-child)
   -------------------------------------------------------------------------- */
[data-reveal], [data-reveal-group] > * { opacity: 1; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    [data-reveal] {
      animation: mo-rise linear both;
      animation-duration: 1ms;               /* Firefox needs non-zero; declare BEFORE timeline */
      animation-timeline: view();
      animation-range: entry 15% cover 40%;
    }
    [data-reveal="fade"]  { animation-name: mo-fade; }
    [data-reveal="left"]  { animation-name: mo-left; }
    [data-reveal="right"] { animation-name: mo-right; }
    [data-reveal="scale"] { animation-name: mo-scale; }
    [data-reveal="clip"]  { animation-name: mo-clip; animation-range: entry 10% cover 55%; }

    /* Staggered groups: each child offsets its range by --i * --stagger */
    [data-reveal-group] > * {
      animation: mo-rise linear both;
      animation-duration: 1ms;
      animation-timeline: view();
      animation-range-start: entry calc(10% + var(--i, 0) * var(--stagger));
      animation-range-end:   cover calc(35% + var(--i, 0) * var(--stagger));
    }
    [data-reveal-group="fade"] > * { animation-name: mo-fade; }
    [data-reveal-group="left"] > * { animation-name: mo-left; }
    [data-reveal-group] > :nth-child(1) { --i: 0; }
    [data-reveal-group] > :nth-child(2) { --i: 1; }
    [data-reveal-group] > :nth-child(3) { --i: 2; }
    [data-reveal-group] > :nth-child(4) { --i: 3; }
    [data-reveal-group] > :nth-child(5) { --i: 4; }
    [data-reveal-group] > :nth-child(6) { --i: 5; }
    [data-reveal-group] > :nth-child(n+7) { --i: 6; }

    /* Parallax: one decorative layer per section, ≤15% differential, never body text */
    [data-parallax] {
      animation: mo-parallax linear both;
      animation-duration: 1ms;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }

    /* Scroll-linked 3D rise (Tier 3): flat, crisp perspective — never isometric */
    [data-rise-3d] {
      animation: mo-rise-3d linear both;
      animation-duration: 1ms;
      animation-timeline: view();
      animation-range: entry 5% cover 45%;
    }

    /* PHONES. MOBILE-UI-SPEC 13 allows one reveal per viewport height. Measured
       at 390px, up to FIVE were mid-animation at once: a phone viewport is tall
       relative to the content, and every grouped child carries its own
       staggered range. On a phone a group now arrives as one move rather than
       six, and each reveal completes in a shorter range, so two rarely overlap. */
    @media (max-width: 599px) {
      [data-reveal] { animation-range: entry 12% cover 28%; }
      /* Children run off the GROUP's timeline, not their own, so a group arrives
         as one synchronised move instead of six that each wait their turn.
         Without the named timeline each child animates on its own position and
         a six-item list becomes six separate reveals on one phone screen. */
      [data-reveal-group] { view-timeline-name: --mo-group; }
      [data-reveal-group] > * {
        animation-timeline: --mo-group;
        animation-range-start: entry 12%;
        animation-range-end: cover 30%;
      }
      [data-rise-3d] { animation-range: entry 8% cover 32%; }
    }
  }
}

@keyframes mo-rise  { from { opacity: 0; transform: translateY(var(--reveal-distance)); } to { opacity: 1; transform: none; } }
@keyframes mo-fade  { from { opacity: 0; } to { opacity: 1; } }
@keyframes mo-left  { from { opacity: 0; transform: translateX(calc(var(--reveal-distance) * -1)); } to { opacity: 1; transform: none; } }
@keyframes mo-right { from { opacity: 0; transform: translateX(var(--reveal-distance)); } to { opacity: 1; transform: none; } }
@keyframes mo-scale { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: none; } }
@keyframes mo-clip  { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0% 0); } }
@keyframes mo-parallax { from { transform: translateY(var(--parallax)); } to { transform: translateY(calc(var(--parallax) * -1)); } }
@keyframes mo-rise-3d { from { opacity: 0; transform: perspective(1200px) rotateX(10deg) translateY(48px); } to { opacity: 1; transform: perspective(1200px) rotateX(0) translateY(0); } }

/* Camera-pull pairing for clip reveals: the inner image counter-scales */
[data-reveal="clip"] > img, [data-reveal="clip"] > picture > img { transform-origin: center; }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    [data-reveal="clip"] > img, [data-reveal="clip"] > picture > img {
      animation: mo-unscale linear both;
      animation-duration: 1ms;
      animation-timeline: view();
      animation-range: entry 10% cover 55%;
    }
  }
}
@keyframes mo-unscale { from { transform: scale(1.18); } to { transform: scale(1); } }

/* --------------------------------------------------------------------------
   STICKY PINNED PANELS — highest impression-per-byte effect, pure CSS.
   <section class="pin-stage" style="--panels:3">
     <div class="pin-stage__sticky"> …panels with [data-panel] … </div>
   </section>
   Panel visibility keyed to the stage's own view timeline.
   -------------------------------------------------------------------------- */
.pin-stage { position: relative; height: calc(var(--panels, 3) * 100svh); view-timeline-name: --stage; view-timeline-axis: block; }
.pin-stage__sticky { position: sticky; top: 0; height: 100svh; overflow: clip; display: grid; }
.pin-stage__sticky > [data-panel] { grid-area: 1 / 1; opacity: 1; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Each panel declares --from/--to as percentages of the stage's contain range */
    .pin-stage__sticky > [data-panel] {
      animation: mo-panel linear both;
      animation-duration: 1ms;
      animation-timeline: --stage;
      animation-range-start: contain var(--from, 0%);
      animation-range-end:   contain var(--to, 100%);
    }
  }
}
/* Panel is visible between 15%–85% of its own slice; crossfades at the edges */
@keyframes mo-panel {
  0%   { opacity: 0; transform: translateY(24px); }
  15%  { opacity: 1; transform: none; }
  85%  { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-24px); }
}
/* Without scroll-driven support the panels stack vertically instead of pinning */
@supports not (animation-timeline: view()) {
  .pin-stage { height: auto; }
  .pin-stage__sticky { position: static; height: auto; display: block; }
  .pin-stage__sticky > [data-panel] { padding-block: var(--sp-12, 3rem); }
}

/* --------------------------------------------------------------------------
   MARQUEE — with a real, visible pause control (WCAG 2.2.2 Level A).
   <div class="marquee" data-marquee>
     <div class="marquee__track"> …items… …items duplicated… </div>
     <button class="marquee__toggle" data-marquee-toggle aria-pressed="false">Pause</button>
   </div>
   -------------------------------------------------------------------------- */
.marquee { position: relative; overflow: hidden; mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marquee__track { display: flex; gap: var(--marquee-gap, 3rem); width: max-content; animation: mo-marquee var(--marquee-dur, 40s) linear infinite; }
.marquee[data-paused] .marquee__track { animation-play-state: paused; }
@media (hover: hover) and (pointer: fine) { .marquee:hover .marquee__track { animation-play-state: paused; } }
.marquee:focus-within .marquee__track { animation-play-state: paused; }
.marquee__toggle {
  position: absolute; right: 0.5rem; bottom: 0.5rem;
  min-width: 44px; min-height: 44px;
  padding: 0 0.75rem; font: inherit; font-size: 0.75rem;
  border: 1px solid currentColor; border-radius: 999px; background: transparent; color: inherit;
  cursor: pointer;
}
@keyframes mo-marquee { to { transform: translateX(-50%); } }

/* --------------------------------------------------------------------------
   SURFACE CRAFT — grain, blend, hairlines, media hover
   -------------------------------------------------------------------------- */
.grain { position: relative; isolation: isolate; }
.grain::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  opacity: 0.055; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.grain > * { position: relative; z-index: 2; }

.blend-diff { mix-blend-mode: difference; color: #fff; }
.isolate { isolation: isolate; }

.hairline { border-color: color-mix(in oklab, currentColor 12%, transparent); }

.media-hover { overflow: clip; }
.media-hover img, .media-hover video { transition: transform var(--duration-media) var(--ease-out-expo2); transform-origin: center; }
@media (hover: hover) and (pointer: fine) {
  .media-hover:hover img, .media-hover:hover video { transform: scale(1.04); }
}

/* Tier 3: flat 3D tilt on pointer-fine only; JS writes --rx/--ry */
.perspective { perspective: 1200px; }
.tilt { transform: perspective(1200px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); transform-style: preserve-3d; transition: transform var(--duration-base) var(--ease-ui); will-change: auto; }

/* Tier 3: multi-rate gradient field. Three layers drifting at different rates.
   Low chroma, hue-matched to the site — never a purple/blue AI mesh. */
.aurora { position: absolute; inset: -25%; z-index: 0; pointer-events: none; overflow: hidden; opacity: var(--aurora-opacity, 0.55); }
.aurora > i { position: absolute; inset: 0; display: block; will-change: transform; background: radial-gradient(closest-side at 50% 50%, var(--aurora-c, currentColor) 0%, transparent 70%); }
.aurora > i:nth-child(1) { --aurora-c: var(--aurora-1); animation: mo-drift-a 18s var(--ease-inout-expo) infinite alternate; transform-origin: 30% 40%; }
.aurora > i:nth-child(2) { --aurora-c: var(--aurora-2); animation: mo-drift-b 26s var(--ease-inout-expo) infinite alternate; transform-origin: 70% 60%; }
.aurora > i:nth-child(3) { --aurora-c: var(--aurora-3); animation: mo-drift-c 34s var(--ease-inout-expo) infinite alternate; transform-origin: 50% 80%; }
@keyframes mo-drift-a { from { transform: translate(-12%, -8%) scale(0.9); } to { transform: translate(14%, 10%) scale(1.15); } }
@keyframes mo-drift-b { from { transform: translate(10%, 12%) scale(1.1) rotate(0deg); } to { transform: translate(-14%, -6%) scale(0.85) rotate(12deg); } }
@keyframes mo-drift-c { from { transform: translate(0%, 14%) scale(1.0); } to { transform: translate(8%, -12%) scale(1.25); } }

/* Hero video (Tier 3): poster is the LCP element; video fades in on `playing` */
.hero-video { position: absolute; inset: 0; z-index: 0; overflow: clip; }
.hero-video > img, .hero-video > video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero-video > video { opacity: 0; transition: opacity var(--duration-media) var(--ease-ui); }
.hero-video[data-playing] > video { opacity: 1; }

/* --------------------------------------------------------------------------
   REDUCED MOTION — replace, don't remove.
   base.css already collapses durations; here we force resting states and
   keep opacity-only feedback (opacity is exempt from WCAG 2.3.3's definition).
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  [data-reveal], [data-reveal-group] > *, [data-parallax], [data-rise-3d], .pin-stage__sticky > [data-panel] {
    animation: none !important; opacity: 1 !important; transform: none !important; clip-path: none !important;
  }
  [data-reveal="clip"] > img, [data-reveal="clip"] > picture > img { animation: none !important; transform: none !important; }
  .pin-stage { height: auto; }
  .pin-stage__sticky { position: static; height: auto; display: block; }
  .pin-stage__sticky > [data-panel] { padding-block: var(--sp-12, 3rem); }
  .marquee__track { animation-play-state: paused !important; }
  .aurora > i { animation: none !important; }
  .media-hover img, .media-hover video, .tilt { transition: none !important; transform: none !important; }
  .hero-video > video { display: none; }   /* poster stands in; JS also pauses */
}
