/* scroll-scene.css — baseline (Tier 0) for the BRG/ATTOH scroll-scene engine.
 *
 * Pairs with scroll-scene.js. This stylesheet alone gives scroll-driven reveals and
 * parallax on modern browsers via native CSS scroll-timeline — no JavaScript required.
 * The JS engine layers pin/scrub/horizontal/3D on top and, on older browsers, drives the
 * reveals with IntersectionObserver instead.
 *
 * HARD RULE: content is visible by default. Motion is an enhancement applied only when the
 * browser supports no-preference motion. If CSS/JS never runs, the page reads normally.
 */

/* ---- reveal: enhance-an-already-visible-default -------------------------- */
/* Baseline = fully visible. The enhanced (hidden→shown) state is only armed once JS has
   confirmed motion is wanted (.scene-js on <html>, not .scene-reduced). This guarantees a
   headless renderer or a stalled script never ships a blank section. */
/* `:not(.scene-split)` — a split-text host animates its own units, so fading the whole host
   as well would double the motion on the same words. */
@media (prefers-reduced-motion: no-preference) {
  html.scene-js:not(.scene-reduced) [data-scene~="reveal"]:not(.scene-split),
  html.scene-js:not(.scene-reduced) [data-scene-reveal]:not(.scene-split) {
    opacity: 0;
    transform: translate3d(0, 18px, 0);
    will-change: opacity, transform;
    transition:
      opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1) var(--scene-delay, 0ms),
      transform 0.7s cubic-bezier(0.16, 1, 0.3, 1) var(--scene-delay, 0ms);
  }
  /* The un-hide rule carries the SAME prefix as the hide rule above, plus .is-inview, so it is
     unambiguously more specific. Do not shorten it: the hidden state gained a :not() qualifier
     once, which silently out-specified a shorter un-hide rule and left revealed sections
     invisible for good. Specificity, not source order, has to decide this one. */
  html.scene-js:not(.scene-reduced) [data-scene~="reveal"].is-inview,
  html.scene-js:not(.scene-reduced) [data-scene-reveal].is-inview {
    opacity: 1;
    transform: none;
  }
}

/* ---- parallax: native CSS scroll-timeline where supported ---------------- */
/* On browsers with view-timeline the depth is driven entirely by CSS (no scroll handler).
   The JS rAF fallback sets an inline transform on browsers that lack it. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    html.scene-js:not(.scene-reduced) [data-scene-parallax],
    html.scene-js:not(.scene-reduced) [data-depth] {
      animation: scene-parallax linear both;
      animation-timeline: view();
      animation-range: cover;
    }
  }
}
@keyframes scene-parallax {
  from { transform: translate3d(0, calc(var(--scene-depth, 0.15) * 60px), 0); }
  to   { transform: translate3d(0, calc(var(--scene-depth, 0.15) * -60px), 0); }
}

/* ---- pinned + horizontal scaffolding ------------------------------------ */
/* Both of these REWRITE layout, so they are gated on .scene-tier1 — the class the engine adds
   only once GSAP/ScrollTrigger are actually running. Gating them on .scene-js (JS present) was
   wrong: when the library import was blocked, pinned steps stayed absolutely stacked on top of
   each other and horizontal tracks stayed overflow:hidden, so real content was unreadable or
   clipped away with no error anywhere. Default = ordinary readable flow. */
[data-scene~="pin"] { position: relative; }
[data-scene~="pin"] [data-scene-step] { display: block; }
html.scene-tier1:not(.scene-reduced) [data-scene~="pin"] [data-scene-step] {
  position: absolute; inset: 0; display: grid; place-items: center;
}

[data-scene~="horizontal"] { overflow-x: auto; -webkit-overflow-scrolling: touch; }
html.scene-tier1:not(.scene-reduced) [data-scene~="horizontal"] { overflow: hidden; }
[data-scene~="horizontal"] [data-scene-track] {
  display: flex; flex-wrap: nowrap; will-change: transform;
}

/* progress var (0..1) written by the engine; usable by any child via var(--progress) */
[data-scene-scrub] { --progress: 0; }

/* ---- 3D host + poster fallback ------------------------------------------ */
[data-scene~="3d"] { position: relative; overflow: hidden; }
[data-scene~="3d"] .scene-3d-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  opacity: 0; transition: opacity 0.8s ease;
}
[data-scene~="3d"].scene-3d-ready .scene-3d-canvas { opacity: 1; }
/* Poster image (data-scene-poster) sits under the canvas and shows until WebGL is ready,
   or forever under reduced-motion / low-power / context-loss. */
[data-scene~="3d"] [data-scene-poster] {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
[data-scene~="3d"].scene-3d-lost .scene-3d-canvas { opacity: 0; }

/* ---- split-text masked reveal ------------------------------------------- */
/* .scene-u is the clip window, .scene-ui the sliding unit. The padding/negative-margin pair
   keeps descenders (g, y, p) from being sheared off by the clip. */
.scene-split .scene-u { display: inline-block; vertical-align: top; }
/* one word of a chars-split: keeps its characters together so the line cannot break inside it */
.scene-split .scene-w { display: inline-block; white-space: nowrap; }
@media (prefers-reduced-motion: no-preference) {
  html.scene-js:not(.scene-reduced) .scene-split .scene-u {
    overflow: hidden; padding-bottom: 0.08em; margin-bottom: -0.08em;
  }
  html.scene-js:not(.scene-reduced) .scene-split .scene-ui {
    display: inline-block; transform: translate3d(0, 110%, 0);
    transition: transform 0.95s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: calc(var(--i, 0) * 42ms);
  }
  /* same-prefix + .is-inview, for the same specificity reason as the reveal pair above */
  html.scene-js:not(.scene-reduced) .scene-split.is-inview .scene-ui { transform: none; }
}

/* ---- sticky card stack --------------------------------------------------- */
/* The stacking itself is native position:sticky, so it works with JS off. Tier 1 only adds
   the scale/dim scrub as the next card lands. */
[data-scene~="stack"] { display: grid; gap: clamp(20px, 4vw, 44px); }
[data-scene~="stack"] [data-scene-card] {
  position: sticky;
  top: calc(var(--scene-stack-top, 92px) + var(--i, 0) * 12px);
  transform-origin: center top;
  transition: box-shadow 0.4s ease;
}
@media (prefers-reduced-motion: reduce) {
  [data-scene~="stack"] [data-scene-card] { position: relative; top: auto; transform: none !important; }
}

/* ---- scroll-velocity marquee -------------------------------------------- */
[data-scene-marquee] { display: flex; overflow: hidden; --mq-skew: 0deg; }
[data-scene-marquee] [data-scene-track] {
  display: flex; flex: 0 0 auto; flex-wrap: nowrap; align-items: center;
  gap: clamp(24px, 4vw, 64px); white-space: nowrap; will-change: transform;
}
[data-scene-marquee].scene-marquee-live { transform: skewY(var(--mq-skew)); }
/* No JS: a plain, readable, horizontally scrollable row. */
html:not(.scene-js) [data-scene-marquee] { overflow-x: auto; -webkit-overflow-scrolling: touch; }
@media (prefers-reduced-motion: reduce) {
  [data-scene-marquee] { overflow-x: auto; transform: none !important; }
  [data-scene-marquee] [data-scene-track] { transform: none !important; }
  [data-scene-marquee] [data-scene-cloned="1"] + [data-scene-cloned="1"] { display: none; }
}

/* ---- scrubbed video ----------------------------------------------------- */
[data-scene~="video"] { position: relative; }
[data-scene~="video"] video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- SVG draw-on-scroll ------------------------------------------------- */
.scene-draw-path { transition: stroke-dashoffset 1.5s cubic-bezier(0.16, 1, 0.3, 1); }
@media (prefers-reduced-motion: reduce) {
  .scene-draw-path { transition: none !important; stroke-dashoffset: 0 !important; }
}

/* ---- pointer tilt ------------------------------------------------------- */
[data-scene-tilt] { transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
@media (prefers-reduced-motion: reduce) {
  [data-scene-tilt] { transition: none; transform: none !important; }
}

/* ---- magnetic cursor ---------------------------------------------------- */
/* difference blending keeps the ring legible over any background without a colour per theme.
   The native cursor is only suppressed when the page opts into data-scene-cursor="hide". */
.scene-cursor {
  position: fixed; top: 0; left: 0; z-index: 60;
  width: 32px; height: 32px; border-radius: 999px;
  border: 1.5px solid #fff; mix-blend-mode: difference;
  pointer-events: none; opacity: 0.55;
  transition: width 0.25s ease, height 0.25s ease, opacity 0.25s ease, background-color 0.25s ease;
}
.scene-cursor.is-hot { width: 56px; height: 56px; opacity: 0.95; background: rgba(255, 255, 255, 0.16); }
html.scene-cursor-hide, html.scene-cursor-hide * { cursor: none; }
[data-cursor-magnetic] { transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); }
@media (pointer: coarse) { .scene-cursor { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .scene-cursor { display: none; }
  html.scene-cursor-hide, html.scene-cursor-hide * { cursor: auto; }
  [data-cursor-magnetic] { transition: none; transform: none !important; }
}

/* ---- clip-path mask reveal ---------------------------------------------- */
/* The element is always painted; only its clip changes. Nothing here can hide content for good,
   which is why this uses clip-path rather than opacity. */
@media (prefers-reduced-motion: no-preference) {
  html.scene-js:not(.scene-reduced) [data-scene-mask] {
    transition: clip-path 1.1s cubic-bezier(0.16, 1, 0.3, 1);
  }
  html.scene-js:not(.scene-reduced) [data-scene-mask="up"]    { clip-path: inset(100% 0 0 0); }
  html.scene-js:not(.scene-reduced) [data-scene-mask="down"]  { clip-path: inset(0 0 100% 0); }
  html.scene-js:not(.scene-reduced) [data-scene-mask="left"]  { clip-path: inset(0 100% 0 0); }
  html.scene-js:not(.scene-reduced) [data-scene-mask="right"] { clip-path: inset(0 0 0 100%); }
  html.scene-js:not(.scene-reduced) [data-scene-mask="iris"]  { clip-path: circle(0% at 50% 50%); }
  /* same-prefix + .is-inview, so a specificity change cannot leave content clipped away */
  html.scene-js:not(.scene-reduced) [data-scene-mask].is-inview { clip-path: inset(0 0 0 0); }
  html.scene-js:not(.scene-reduced) [data-scene-mask="iris"].is-inview { clip-path: circle(75% at 50% 50%); }
}
@media (prefers-reduced-motion: reduce) {
  [data-scene-mask] { clip-path: none !important; transition: none !important; }
}

/* ---- physics pile ------------------------------------------------------- */
/* Default (and reduced-motion, and no-JS) is an ordinary wrapped row. The solver only takes over
   once it has measured that row, so the bodies start exactly where they already were. */
[data-scene~="physics"] { position: relative; display: flex; flex-wrap: wrap; gap: clamp(8px, 1.4vw, 16px); }
[data-scene~="physics"].scene-physics-live { display: block; touch-action: pan-y; }
[data-scene~="physics"].scene-physics-live [data-scene-body] {
  position: absolute; left: 0; top: 0; margin: 0; will-change: transform; cursor: grab; user-select: none;
}
[data-scene~="physics"].scene-physics-live [data-scene-body]:active { cursor: grabbing; }
@media (prefers-reduced-motion: reduce) {
  [data-scene~="physics"] { display: flex !important; height: auto !important; }
  [data-scene~="physics"] [data-scene-body] { position: static !important; transform: none !important; cursor: auto; }
}

/* ---- text scramble ------------------------------------------------------ */
/* Tabular figures stop the glyph churn from changing the line's width mid-decode. */
[data-scene-scramble] { font-variant-numeric: tabular-nums; }

/* ---- cross-document page transitions ------------------------------------ */
/* Same-origin navigations cross-fade instead of flashing white. Browsers without the API
   ignore the whole block; reduced-motion users get the instant swap back. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: scene-vt-out 0.26s cubic-bezier(0.4, 0, 1, 1) both; }
::view-transition-new(root) { animation: scene-vt-in 0.34s cubic-bezier(0, 0, 0.2, 1) both; }
@keyframes scene-vt-out { to { opacity: 0; transform: translate3d(0, -8px, 0); } }
@keyframes scene-vt-in { from { opacity: 0; transform: translate3d(0, 12px, 0); } }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* ---- reduced-motion / no-JS: everything visible, no motion, no canvas ---- */
html.scene-reduced [data-scene~="3d"] .scene-3d-canvas { display: none; }
@media (prefers-reduced-motion: reduce) {
  [data-scene~="reveal"], [data-scene-reveal], [data-scene-text],
  [data-scene-parallax], [data-depth] { opacity: 1 !important; transform: none !important; animation: none !important; }
  .scene-split .scene-u { overflow: visible !important; }
  .scene-split .scene-ui { transform: none !important; transition: none !important; }
  [data-scene~="3d"] .scene-3d-canvas { display: none !important; }
}
