/* motion.css — the motion vocabulary. Every effect is opt-in through a data attribute, the content is fully
   visible without JavaScript, and everything turns static under prefers-reduced-motion (and in print).
   Needs in <head>, before this file (the timeout shows everything if motion.js never runs):
   <script>document.documentElement.classList.add("js");setTimeout(function(){if(!window.__motion)document.documentElement.classList.remove("js")},4000)</script> */
:root {
  --ease-out: cubic-bezier(.16, 1, .3, 1);       /* entrances: quick start, long soft landing */
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);   /* scroll-linked moves, morphs */
  --ease-back: cubic-bezier(.34, 1.56, .64, 1);  /* a hint of overshoot: buttons, chips — never on text */
  --t-fast: .25s; --t-mid: .7s; --t-slow: 1.1s;
  --stagger: 70ms;
}
html { scroll-behavior: auto; }   /* never "smooth" globally: it fights scroll-linked code and test scripts */

/* 1 · Reveal on scroll ------------------------------------------------------------------------------------
   [data-reveal] = "up" (default) | "fade" | "left" | "right" | "scale" | "clip" | "blur"
   [data-stagger] on a parent staggers its [data-reveal] children (motion.js sets --i); --delay adds a pause.
   End states are "none": a finished reveal never leaves a transform, clip or filter that would crop a shadow. */
html.js [data-reveal] {
  opacity: 0; transform: translate3d(0, 28px, 0);
  transition: opacity var(--t-mid) var(--ease-out), transform var(--t-slow) var(--ease-out),
              clip-path var(--t-slow) var(--ease-in-out), filter var(--t-slow) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger) + var(--delay, 0ms));
}
html.js [data-reveal="fade"] { transform: none; }
html.js [data-reveal="left"] { transform: translate3d(-40px, 0, 0); }
html.js [data-reveal="right"] { transform: translate3d(40px, 0, 0); }
html.js [data-reveal="scale"] { transform: scale(.94); }
html.js [data-reveal="blur"] { transform: translate3d(0, 14px, 0); filter: blur(14px); }
html.js [data-reveal="clip"] { opacity: 1; transform: none; clip-path: inset(0 0 100% 0); }
html.js [data-reveal].is-in { opacity: 1; transform: none; filter: none; }
html.js [data-reveal="clip"].is-in { clip-path: inset(-30% -30% -30% -30%); }   /* open past the box: shadows stay whole */

/* 2 · Split text: [data-split] wraps each word in a mask (motion.js); words rise in one after the other.
   The mask is padded so accents (É, À) and descenders are never cut. Spaces stay real spaces (SEO, a11y).
   Until it is split, the line stays invisible (no flash of the plain line). */
html.js [data-split]:not(.is-split) { opacity: 0; }
.sw { display: inline-block; overflow: hidden; vertical-align: top; padding: .1em .05em .22em; margin: -.1em -.05em -.22em; }
.sw > span { display: inline-block; }
html.js [data-split] .sw > span { transform: translate3d(0, 135%, 0) rotate(4deg); transform-origin: 0 100%;
  transition: transform var(--t-slow) var(--ease-out); transition-delay: calc(var(--w, 0) * 38ms + var(--delay, 0ms)); }
html.js [data-split].is-in .sw > span { transform: none; }

/* 3 · Scroll progress: motion.js writes --p (0 → 1, linear) and --pe (eased) on [data-progress] near the screen.
   "pin" (default): 0 when the block's top reaches the top of the screen, 1 when its bottom reaches the bottom —
   for tall blocks with a sticky child. "view": 0 when the top enters at the bottom, 1 when the bottom leaves at the top. */

/* 3a · Immersive chapter: a window in the page opens to the whole screen, then a line rises over the photo.
   <section class="chapter" data-progress><div class="chapter__stick"><div class="chapter__frame">
     <img class="chapter__img" …><div class="chapter__text"><p class="chapter__kicker">…</p><p class="chapter__line">…</p></div>
   </div></div></section> */
.chapter { position: relative; }
.chapter__stick { position: relative; height: 82vh; min-height: 480px; overflow: hidden; }
.chapter__frame { position: absolute; inset: 0; overflow: hidden; isolation: isolate; background: #05101f; }
.chapter__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.chapter__frame::after { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg, rgba(2, 8, 20, .1), rgba(2, 8, 20, 0) 40%, rgba(2, 8, 20, .78)); }
.chapter__text { position: absolute; z-index: 2; left: clamp(20px, 6vw, 96px); right: 20px; bottom: clamp(40px, 9vh, 110px); color: #fff; }
.chapter__kicker { font-size: 12.5px; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; opacity: .85; }
.chapter__line { margin-top: 14px; max-width: 15ch; font-size: clamp(34px, 5.6vw, 92px); line-height: 1.02; letter-spacing: -.03em; text-wrap: balance; }
html.js .chapter { height: 230vh; }
html.js .chapter__stick { position: sticky; top: 0; height: 100vh; height: 100svh; min-height: 0; }
html.js .chapter__frame { --o: calc(1 - min(1, var(--pe, 0) * 1.9));   /* the window is fully open at ~half the travel */
  clip-path: inset(calc(var(--o) * 16%) calc(var(--o) * var(--side, 14%)) round calc(var(--o) * 32px)); }
html.js .chapter__img { transform: scale(calc(1.02 + var(--o) * .2)); }
html.js .chapter__text { opacity: clamp(0, calc((var(--p, 0) - .38) * 4), 1); transform: translate3d(0, calc((1 - clamp(0, calc((var(--p, 0) - .38) * 4), 1)) * 40px), 0); }
@media (max-width: 700px) { html.js .chapter { height: 190vh; } html.js .chapter__frame { --side: 5%; } }

/* 3b · Horizontal track: a row that slides sideways while the page scrolls down (motion.js sizes the block).
   <section class="hscroll" data-hscroll><div class="hscroll__stick"><div class="hscroll__track" data-track>…cards…</div></div></section> */
.hscroll__stick { overflow-x: auto; }
.hscroll__track { display: flex; gap: clamp(16px, 2vw, 28px); width: max-content; padding: 0 clamp(20px, 6vw, 96px); }
html.js .hscroll[data-hscroll] .hscroll__stick { position: sticky; top: 0; height: 100vh; height: 100svh; overflow: hidden; display: flex; align-items: center; }
html.js .hscroll[data-hscroll] .hscroll__track { transform: translate3d(calc(var(--p, 0) * var(--dx, 0px) * -1), 0, 0); will-change: transform; }

/* 3c · Words that light up as you read: <p data-split data-progress="view" class="lightup">…</p> */
html.js .lightup .sw > span { transform: none; transition: none; opacity: clamp(.16, calc(var(--p, 0) * 1.6 * var(--n, 1) - var(--w, 0)), 1); }

/* 3d · A line that draws itself as you scroll: <svg data-progress="view"><path class="draw" pathLength="1" d="…"/></svg> */
.draw { fill: none; stroke-dasharray: 1; stroke-dashoffset: 0; }
html.js [data-progress] .draw { stroke-dashoffset: calc(1 - min(1, var(--pe, 0) * 1.6)); }

/* 4 · Parallax: [data-parallax="0.15"] moves itself, measured on its parent (no feedback loop). Uses the
   individual "translate" property, so it never fights a reveal's "transform".
   For a photo that drifts inside its frame: <figure class="pframe"><img data-parallax="-0.12" …></figure> */
[data-parallax] { translate: 0 var(--py, 0px); will-change: translate; }
.pframe { position: relative; overflow: hidden; }
.pframe > img { width: 100%; height: 124%; margin-top: -12%; object-fit: cover; display: block; }   /* room to drift, no gap */

/* 5 · Magnetic buttons, spotlight cards and 3D tilt (fine pointers only, set up by motion.js) */
[data-tilt] { transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); transition: transform .6s var(--ease-out); transform-style: preserve-3d; }
[data-magnetic] { translate: var(--mx, 0px) var(--my, 0px); transition: translate .5s var(--ease-out); }
[data-spotlight] { position: relative; isolation: isolate; }
[data-spotlight]::before { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none; opacity: 0;
  background: radial-gradient(380px circle at var(--sx, 50%) var(--sy, 50%), rgba(255, 255, 255, .12), transparent 62%); transition: opacity .35s; }
[data-spotlight]:hover::before { opacity: 1; }

/* 6 · Marquee: <div class="marquee"><div class="marquee__track">…items… <span aria-hidden="true">…same items…</span></div></div> */
.marquee { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marquee__track { display: flex; width: max-content; animation: marquee var(--marquee-speed, 40s) linear infinite; }
.marquee:hover .marquee__track { animation-play-state: paused; }
@keyframes marquee { to { transform: translate3d(-50%, 0, 0); } }

/* 6b · Stacked cards: <div class="stack"><article style="--i:0">…</article><article style="--i:1">…</article></div>
   each card stays put while the next one slides over it (CSS only; give the cards an opaque background) */
.stack { display: grid; gap: 24px; }
.stack > * { position: sticky; top: calc(var(--stack-top, 110px) + var(--i, 0) * 20px); }

/* 7 · Navigation: [data-nav] gets .is-scrolled past the top and .is-hidden while scrolling down (motion.js).
   Don't put a transform on the [data-nav] element yourself (wrap it): the hide uses transform. */
[data-nav] { transition: transform .5s var(--ease-out), background-color .3s, box-shadow .3s; }
[data-nav].is-hidden { transform: translate3d(0, calc(-100% - 20px), 0); }

/* 8 · Page-to-page transitions without JavaScript (browsers that lack it simply load the page) */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: vt-out .32s var(--ease-in-out) both; }
::view-transition-new(root) { animation: vt-in .6s var(--ease-out) both; }
@keyframes vt-out { to { opacity: 0; transform: translate3d(0, -10px, 0); } }
@keyframes vt-in { from { opacity: 0; transform: translate3d(0, 14px, 0); } }

/* Lenis (optional smooth wheel scrolling) — the rules of its lenis.css */
html.lenis, html.lenis body { height: auto; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* Calm mode: reduced motion and print show every final state at once */
@media (prefers-reduced-motion: reduce) {
  html.js [data-reveal], html.js [data-split] .sw > span { opacity: 1 !important; transform: none !important; clip-path: none !important; filter: none !important; transition: none !important; }
  html.js .chapter { height: auto; } html.js .chapter__stick { position: relative; height: 82vh; }
  html.js .chapter__frame { clip-path: none; } html.js .chapter__img { transform: none; } html.js .chapter__text { opacity: 1; transform: none; }
  html.js .hscroll[data-hscroll] { height: auto !important; } html.js .hscroll[data-hscroll] .hscroll__stick { position: relative; height: auto; overflow-x: auto; padding: 40px 0; }
  html.js .hscroll[data-hscroll] .hscroll__track { transform: none; }
  html.js .lightup .sw > span { opacity: 1; }
  html.js [data-progress] .draw { stroke-dashoffset: 0; }
  [data-parallax], [data-magnetic] { translate: none !important; }
  [data-tilt] { transform: none !important; }
  .marquee__track { animation: none; }
  @view-transition { navigation: none; }
}
@media print {
  html.js [data-reveal], html.js [data-split] .sw > span { opacity: 1 !important; transform: none !important; clip-path: none !important; filter: none !important; }
}
