/* SOUND FRAME — Patch 13: motion layer (css/motion.css + js/motion.js, vendor: GSAP 3.12.5 + ScrollTrigger, Lenis 1.1.13)
   Everything here is scoped to html.sf-motion, which the inline head script sets only when the visitor has not asked for reduced motion.
   If js/motion.js never runs, the head script's failsafe removes .sf-intro after 2.6 s and every rule below that could hide content stops applying.
   Signal Blue stays a UX colour: it appears only as the 2px wipe edge, the curtain progress rule and the existing markers. No large blue fills. */

/* ---------------- Lenis (smooth wheel scrolling; touch stays native) ---------------- */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }

/* ---------------- page curtain: covers on load / before an internal page change ---------------- */
html.sf-intro body::after { content: ""; position: fixed; inset: 0; z-index: 9990; background: var(--ink); }
.sf-curtain { position: fixed; inset: 0; z-index: 9990; background: var(--ink); pointer-events: none; will-change: transform; }
.sf-curtain i { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--sf-signal); transform: scaleX(0); transform-origin: left; }
.sf-curtain.is-idle { visibility: hidden; }

/* ---------------- hero frames ---------------- */
html.sf-motion .hero__pic { will-change: clip-path; }
html.sf-motion .hero__pic img { will-change: transform; transform-origin: 50% 50%; }

/* ---------------- image wipe: clip + 2px Signal Blue leading edge (--p 0 → 1, driven by motion.js) ---------------- */
/* the clip sits on the media inside, so the frame keeps its box (IntersectionObserver ignores zero-area targets) */
html.sf-motion .sf-wipe { --p: 1; --l: 0; }
html.sf-motion .sf-wipe img, html.sf-motion .sf-wipe figcaption { clip-path: inset(0 calc((1 - var(--p)) * 100%) 0 0); }
html.sf-motion .sf-wipe:not(.pf):not(.dev) { position: relative; }
html.sf-motion .sf-wipe::after { content: ""; position: absolute; top: 0; bottom: 0; left: calc(var(--p) * 100% - 2px); width: 2px; z-index: 4; background: var(--sf-signal); opacity: var(--l); pointer-events: none; }
/* frames that already use ::after for a shade keep it; the edge moves to ::before there */
html.sf-motion .hero-shade.sf-wipe::after { left: 0; width: auto; opacity: 1; background: linear-gradient(to top, rgba(8, 8, 8, .46) 0, rgba(8, 8, 8, .2) 48%, rgba(8, 8, 8, .3) 100%); }
html.sf-motion .hero-shade.sf-wipe::before { content: ""; position: absolute; top: 0; bottom: 0; left: calc(var(--p) * 100% - 2px); width: 2px; z-index: 4; background: var(--sf-signal); opacity: var(--l); pointer-events: none; }

/* ---------------- statements: word-by-word scrub (the line spans stay; words inside them carry the opacity) ---------------- */
html.sf-motion [data-reveal].sf-scrub > span { opacity: 1 !important; transform: none !important; transition: none !important; }
html.sf-motion .sf-w { display: inline !important; color: inherit !important; transition: none !important; transform: none !important; }

/* ---------------- stacking: the next section slides over a pinned statement ---------------- */
html.sf-motion .sf-cover { position: relative; z-index: 2; }
html.sf-motion .sf-stack { z-index: 1; }

/* ---------------- closing frames: open from an inset frame as they enter, then drift ---------------- */
html.sf-motion .sf-frame { overflow: hidden; will-change: clip-path; }
html.sf-motion .sf-frame img { will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  html.sf-intro body::after, .sf-curtain { display: none; }
  html.sf-motion .sf-wipe img, html.sf-motion .sf-wipe figcaption { clip-path: none; }
  html.sf-motion .sf-wipe::after, html.sf-motion .hero-shade.sf-wipe::before { display: none; }
}
