/* ==========================================================================
   cinema.css
   Purpose: the cinema layer stylesheet. Additive only — it loads LAST, after
   tokens.css, base.css, components.css, chrome.css, motion.css and the world
   theme, and it never edits or overrides an existing rule by name.

   Contents
     1.  Motion tokens (the contract's easing / duration / stagger language)
     2.  Scroll bridge hooks
     3.  #cinema-canvas mount
     4.  Letterbox bars (2.35:1)
     5.  Split-text masks + headline / parallax / kinetic type
     6.  Image mask reveal
     7.  Link underline draw
     8.  Card tilt (specular sheen)
     9.  Sound toggle
     10. Ring motif
     11. prefers-reduced-motion — the complete alternate cut

   THE NATIVE CURSOR IS NEVER HIDDEN
   This file once carried a custom-cursor section whose first act was
   `cursor: none`. It is gone, and nothing here may set `cursor: none` again:
   the operating system pointer is drawn by the compositor and is always ahead
   of anything the page can tween toward it.

   Rules this file obeys
     - Specificity stays at one class / one attribute wherever possible so
       components.css always wins a genuine conflict.
     - Everything is gated behind a state hook the JS adds (`cine-motion`,
       `cine-on`, `cine-smooth`). With JS off, or the CDN down, not one rule in
       this file changes the page.
     - Colour comes from the per-world --w-* semantic tokens only. No world
       palette is hard-coded here.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Motion tokens
   Namespaced --cine-* so tokens.css keeps sole ownership of its own names.
   -------------------------------------------------------------------------- */

:root {
  /* Camera and large moves. */
  --ease-cinematic: cubic-bezier(0.65, 0, 0.15, 1);
  /* UI reveals. */
  --ease-silk: cubic-bezier(0.22, 1, 0.36, 1);

  --cine-dur-micro: 220ms;   /* 150–250ms  hovers, presses, cursor morphs   */
  --cine-dur-ui: 560ms;      /* 400–700ms  UI reveals                       */
  --cine-dur-scene: 1200ms;  /* 900–1600ms scene beats                      */
  --cine-dur-world: 2200ms;  /* 1800–2600ms world transition                */

  --cine-stagger-sibling: 60ms; /* 40–80ms  */
  --cine-stagger-line: 60ms;

  /* Written by cinema/scroll.js. Defaults keep every consumer valid pre-boot. */
  --cine-scroll-progress: 0;
  --cine-scroll-velocity: 0;
  --cine-scroll-direction: 0;

  /* Written by the timeline/engine. 0 = bars fully retracted. */
  --cine-letterbox: 0;
  /* Written by the preloader / engine as compile+asset progress, 0–1. */
  --cine-load-progress: 0;
}

/* --------------------------------------------------------------------------
   2. Scroll bridge
   base.css sets `html { scroll-behavior: smooth }` under no-preference. Native
   smooth scrolling and a JS scroll animator fight each other, so while Lenis is
   driving we hand scroll-behavior back to `auto` — cinema/scroll.js then
   re-implements eased anchor jumps itself, including hash and focus.
   The class is only ever present when Lenis actually started.
   -------------------------------------------------------------------------- */

html.cine-smooth {
  scroll-behavior: auto;
}

/* Scroll locking stays entirely with core.js lockScroll(): it sets body overflow
   and adds `is-scroll-locked`, which cinema/scroll.js watches to stop Lenis.
   We deliberately add no overflow rule of our own on <html> — base.css chose
   `overflow-x: clip` there on purpose and must not be disturbed. */

/* --------------------------------------------------------------------------
   3. The single persistent WebGL canvas
   z-index:-1 puts it behind every DOM element but in front of the root
   background, which is why <body> hands its background colour up to <html>
   while the cinema layer is live. The DOM story stays fully interactive.
   -------------------------------------------------------------------------- */

#cinema-canvas {
  position: fixed;
  inset: 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Never a pop-in: the engine fades this up once the first frame is ready. */
  opacity: 0;
  transition: opacity var(--cine-dur-scene) var(--ease-silk);
  contain: strict;
}

#cinema-canvas[data-ready="true"] {
  opacity: 1;
}

html.cine-on {
  /* Holds the world's base colour so the canvas has something behind it. */
  background-color: var(--w-base);
}

html.cine-on body {
  /* Only the colour is released; any theme background-image is untouched. */
  background-color: transparent;
}

/* --------------------------------------------------------------------------
   4. Letterbox — 2.35:1, easing in on key beats and releasing after
   Bars sit above the page content but below the header, dialogs and the skip
   link, so nothing interactive is ever covered.
   -------------------------------------------------------------------------- */

.cine-letterbox {
  position: fixed;
  inset: 0;
  z-index: 150;
  pointer-events: none;
}

.cine-letterbox::before,
.cine-letterbox::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  /* The exact bar height that crops a full viewport to 2.35:1, never negative. */
  height: max(0px, calc((100svh - (100vw / 2.35)) / 2));
  background-color: #000;
  transform: scaleY(var(--cine-letterbox, 0));
  transition: transform var(--cine-dur-scene) var(--ease-cinematic);
  will-change: transform;
}

.cine-letterbox::before {
  top: 0;
  transform-origin: 50% 0%;
}

.cine-letterbox::after {
  bottom: 0;
  transform-origin: 50% 100%;
}

/* --------------------------------------------------------------------------
   5. Split text
   The mask is the only thing this file guarantees; the rise itself is animated
   by GSAP so the two systems never disagree about the resting transform.
   Nothing is hidden by CSS — if the JS never runs, the text renders normally.
   -------------------------------------------------------------------------- */

.cine-split {
  /* Masks are block-level rows; words are inline. */
  display: block;
}

.cine-line {
  display: block;
  overflow: hidden;
  /* Descenders and accents must not be clipped by the mask. */
  padding-block-end: 0.14em;
  margin-block-end: -0.14em;
}

.cine-line__inner {
  display: block;
  transform-origin: 0% 100%;
  will-change: transform, opacity;
}

.cine-split--words .cine-word {
  display: inline-block;
  transform-origin: 0% 100%;
  will-change: transform, opacity;
}

.cine-split[data-cine-split-state="in"] .cine-line__inner,
.cine-split[data-cine-split-state="in"] .cine-word {
  will-change: auto;
}

/* Headline letter-spacing opening on enter. */
.cine-headline {
  letter-spacing: -0.02em;
  transition: letter-spacing var(--cine-dur-scene) var(--ease-cinematic);
}

.cine-headline[data-cine-headline-state="in"] {
  letter-spacing: normal;
}

/* Parallax display type — the offset is written as a variable by the JS so we
   never overwrite a transform another stylesheet already owns. */
.cine-parallax {
  transform: translate3d(0, var(--cine-parallax-y, 0px), 0);
  will-change: transform;
}

/* Kinetic type. Chapter titles ONLY — one signature move, not a page effect. */
.cine-kinetic {
  transform: translate3d(var(--cine-kinetic-shift, 0px), 0, 0)
             skewY(var(--cine-kinetic-skew, 0deg));
  transition: transform var(--cine-dur-micro) linear;
  will-change: transform;
}

/* --------------------------------------------------------------------------
   6. Image mask reveal — 1.05 -> 1.0 inside the mask
   -------------------------------------------------------------------------- */

.cine-image {
  position: relative;
  overflow: hidden;
  /* Nothing here changes layout, so this can never cause a shift. */
  isolation: isolate;
}

.cine-image__media {
  display: block;
  width: 100%;
  transform: scale(1.05);
  transform-origin: 50% 50%;
  transition: transform var(--cine-dur-world) var(--ease-cinematic);
  will-change: transform;
}

.cine-image[data-cine-image-state="in"] .cine-image__media {
  transform: scale(1);
  will-change: auto;
}

/* --------------------------------------------------------------------------
   7. Link underline draw
   -------------------------------------------------------------------------- */

.cine-underline {
  position: relative;
  /* The drawn rule replaces the default one only while this class is live. */
  text-decoration: none;
}

.cine-underline::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: -0.12em;
  height: 1px;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: 0% 50%;
  transition: transform var(--cine-dur-ui) var(--ease-silk);
}

.cine-underline:hover::after,
.cine-underline:focus-visible::after {
  transform: scaleX(1);
}

/* Leaving reverses from the far edge, so the line withdraws rather than snaps. */
.cine-underline:not(:hover):not(:focus-visible)::after {
  transform-origin: 100% 50%;
}

/* --------------------------------------------------------------------------
   8. Card tilt
   The magnetic-pull effect that used to share this section was removed: a
   button that walks away from the pointer is a target that has to be chased.
   -------------------------------------------------------------------------- */

.cine-tilt {
  position: relative;
  transform-style: preserve-3d;
  will-change: transform;
}

/* A moving specular sheen that tracks the pointer across the card face. */
.cine-tilt::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--cine-dur-ui) var(--ease-silk);
  background: radial-gradient(
    120% 90% at var(--cine-sheen-x, 50%) var(--cine-sheen-y, 50%),
    color-mix(in srgb, var(--w-glow, #fff) 26%, transparent) 0%,
    transparent 62%
  );
}

.cine-tilt[data-cine-tilt-state="on"]::after {
  opacity: 1;
}

/* Touch replaces hover with a press spring. */
@media (hover: none) {
  .cine-tilt:active {
    transform: scale(0.985);
    transition: transform var(--cine-dur-micro) var(--ease-silk);
  }
}

/* --------------------------------------------------------------------------
   9. Sound toggle — opt-in, muted by default, never autoplaying
   -------------------------------------------------------------------------- */

.cine-sound {
  position: fixed;
  inset-block-end: var(--space-5);
  inset-inline-end: var(--space-5);
  z-index: var(--z-sticky);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  font-family: var(--font-utility);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--w-text-dim);
  background-color: color-mix(in srgb, var(--w-surface) 82%, transparent);
  border: 1px solid var(--w-line);
  border-radius: var(--radius-pill);
  cursor: pointer;
  backdrop-filter: blur(10px);
  transition:
    color var(--cine-dur-micro) var(--ease-silk),
    border-color var(--cine-dur-micro) var(--ease-silk);
}

.cine-sound:hover,
.cine-sound:focus-visible {
  color: var(--w-text);
  border-color: var(--w-accent);
}

.cine-sound:focus-visible {
  outline: 2px solid var(--w-focus);
  outline-offset: 3px;
}

/* Four bars that stand still when muted and breathe when sound is on. */
.cine-sound__bars {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 10px;
}

.cine-sound__bars i {
  display: block;
  width: 2px;
  height: 3px;
  background-color: currentColor;
  transition: height var(--cine-dur-micro) var(--ease-silk);
}

.cine-sound[aria-pressed="true"] .cine-sound__bars i {
  animation: cine-sound-bar 900ms var(--ease-silk) infinite alternate;
}

.cine-sound[aria-pressed="true"] .cine-sound__bars i:nth-child(2) { animation-delay: 120ms; }
.cine-sound[aria-pressed="true"] .cine-sound__bars i:nth-child(3) { animation-delay: 240ms; }
.cine-sound[aria-pressed="true"] .cine-sound__bars i:nth-child(4) { animation-delay: 360ms; }

@keyframes cine-sound-bar {
  from { height: 3px; }
  to   { height: 10px; }
}

/* --------------------------------------------------------------------------
   10. Ring motif
   Shared by the preloader (drawn against real compile/asset progress) and by
   the in-page orbital accents. --cine-load-progress / --cine-ring-progress are
   written by JS; the ring simply draws itself to that value.
   -------------------------------------------------------------------------- */

.cine-rings {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.cine-ring {
  display: block;
  overflow: visible;
  fill: none;
  stroke: var(--w-accent);
  stroke-width: 1;
  stroke-linecap: round;
  opacity: 0.55;
}

/* pathLength="1" on the <circle> makes the progress maths unit-free. */
.cine-ring circle {
  fill: none;
  stroke: inherit;
  stroke-width: inherit;
  stroke-linecap: inherit;
  stroke-dasharray: 1;
  stroke-dashoffset: calc(1 - var(--cine-ring-progress, var(--cine-load-progress, 0)));
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  transition: stroke-dashoffset var(--cine-dur-ui) var(--ease-silk);
}

.cine-ring--track circle {
  stroke-dashoffset: 0;
  opacity: 0.18;
  transition: none;
}

/* Orbital accent rings drift with scroll velocity rather than on a timer. */
.cine-ring--orbit {
  rotate: calc(var(--cine-scroll-progress, 0) * 180deg);
  transition: rotate var(--cine-dur-ui) linear;
}

/* --------------------------------------------------------------------------
   11. prefers-reduced-motion — the complete alternate cut
   Not a degradation. Every mask opens, every image sits at its true scale,
   every underline is present, the bars are retracted and the canvas never fades
   in. The cursor needs no rule here: it is the system cursor at all times, for
   every visitor, in every mode. Everything conveyed by motion above
   is conveyed statically here, and the JS side never installs in the first
   place, so these rules only ever have to catch markup already in the page.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  :root {
    --cine-dur-micro: 1ms;
    --cine-dur-ui: 1ms;
    --cine-dur-scene: 1ms;
    --cine-dur-world: 1ms;
    --cine-stagger-sibling: 0ms;
    --cine-stagger-line: 0ms;
    --cine-letterbox: 0;
  }

  /* The canvas is never started under reduced motion; if one exists it stays
     out of the way rather than fading anything in. */
  #cinema-canvas {
    opacity: 0;
    transition: none;
  }

  .cine-letterbox {
    display: none;
  }

  /* Masks open. Text is plain text again, in normal flow, fully selectable. */
  .cine-line {
    overflow: visible;
    padding-block-end: 0;
    margin-block-end: 0;
  }

  .cine-line__inner,
  .cine-word {
    transform: none !important;
    opacity: 1 !important;
    will-change: auto;
  }

  .cine-headline {
    letter-spacing: normal;
    transition: none;
  }

  .cine-parallax,
  .cine-kinetic {
    transform: none;
    transition: none;
    will-change: auto;
  }

  .cine-image__media {
    transform: none;
    transition: none;
    will-change: auto;
  }

  /* The underline is a static rule, always present — the information the draw
     conveyed (this is a link) is conveyed by simply being there. */
  .cine-underline::after {
    transform: scaleX(1);
    transition: none;
  }

  .cine-tilt {
    transform: none;
    transition: none;
    will-change: auto;
  }

  .cine-tilt::after {
    display: none;
  }

  .cine-sound {
    transition: none;
  }

  .cine-sound[aria-pressed="true"] .cine-sound__bars i {
    animation: none;
    height: 10px;
  }

  .cine-ring circle,
  .cine-ring--orbit {
    transition: none;
    rotate: none;
  }
}
