/* =========================================================================
   Abyss Motion Pictures v4 — "The Descent"
   Palette: midnight-amber adapted → brand icy-blue swap, cool near-black.
   Rhythm/type from design-system/scale.json (no ad-hoc px in layout).
   ========================================================================= */

:root {
  /* --- Palette (icy-blue, cool near-black; sanctioned, NOT pure #000) --- */
  --bg: #06080C;
  --surface: #0C1118;
  --surface-2: #131C28;
  --text: #EAF2F8;
  --text-muted: #7E8EA1;        /* AA on --bg */
  --border: #1A2532;
  --accent: #38BDF8;
  --accent-hover: #7DD3FC;
  --on-accent: #06080C;

  /* Elevation shadows — tinted to the deep-blue bg hue, never pure black. */
  --shadow-sm: 0 1px 2px rgba(6, 8, 20, 0.45), 0 1px 1px rgba(6, 8, 20, 0.35);
  --shadow-md: 0 6px 18px rgba(6, 8, 20, 0.55), 0 2px 6px rgba(6, 8, 20, 0.4);
  --shadow-lg: 0 18px 48px rgba(6, 8, 20, 0.6), 0 6px 14px rgba(6, 8, 20, 0.45);

  /* --- Spacing (scale.json, 4px base) --- */
  --sp-2xs: 4px;
  --sp-xs: 8px;
  --sp-sm: 12px;
  --sp-md: 16px;
  --sp-lg: 24px;
  --sp-xl: 32px;
  --sp-2xl: 48px;
  --sp-3xl: 64px;
  --sp-4xl: 96px;
  --sp-5xl: 128px;

  /* --- Type scale (scale.json clamps) --- */
  --fs-caption: 0.75rem;   /* 12 */
  --fs-small: 0.875rem;    /* 14 */
  --fs-body: 1rem;         /* 16 */
  --fs-lead: 1.25rem;      /* 20 */
  --fs-h4: clamp(1.563rem, 1.4rem + 0.8vw, 1.953rem);
  --fs-h3: clamp(1.953rem, 1.6rem + 1.6vw, 2.441rem);
  --fs-h2: clamp(2.441rem, 1.9rem + 2.5vw, 3.052rem);
  --fs-h1: clamp(3.052rem, 2.2rem + 3.8vw, 3.815rem);
  --fs-display: clamp(3.815rem, 2.4rem + 6.2vw, 4.768rem);

  --font-sans: "IBM Plex Sans", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;

  /* Chrome sits above content; player/intro above chrome. */
  --z-shade: 20;
  --z-chrome: 40;
  --z-drawer: 60;
  --z-player: 80;
  --z-intro: 100;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* Scroll progress 0→1, published by the depth-gauge rAF handler (app.js).
     Drives the descent shade — the page literally loses light as you dive. */
  --descent: 0;

  /* Shared film-grain tile (SVG turbulence) — used by the page and the intro. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

*,
*::before,
*::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

a { color: inherit; }

img,
video { display: block; max-width: 100%; }

/* =========================================================================
   DESCENT ATMOSPHERE — two fixed, non-interactive layers over the content
   but UNDER all chrome/dialogs (z 20 < chrome 40): light falloff + grain.
   ========================================================================= */
/* Shade: darkens with scroll depth (driven by --descent), heavier toward the
   bottom of the viewport — sunlight dying overhead as the dive continues. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--z-shade);
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(2, 4, 8, 0.25),
    rgba(2, 4, 8, 0.85)
  );
  opacity: calc(var(--descent) * 0.4);
}
/* Film grain: static SVG turbulence tile at whisper opacity — cinema texture,
   not a neon/glow effect (kept off the fixed chrome and dialogs). */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-shade) + 1);
  pointer-events: none;
  background-image: var(--grain);
  background-size: 160px 160px;
  opacity: 0.045;
}

/* Square-cornered site — icon buttons keep a hairline 2px radius only. */

/* =========================================================================
   Shared: icon buttons (≥44px hit area, tinted press)
   ========================================================================= */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: none;
  border-radius: 2px;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: color 0.18s var(--ease-out), background-color 0.18s var(--ease-out),
    transform 0.12s var(--ease-out);
}
.icon-btn svg { width: 22px; height: 22px; }
.icon-btn:hover { color: var(--accent-hover); background: rgba(56, 189, 248, 0.08); }
.icon-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.icon-btn:active { transform: scale(0.96); }

@media (prefers-reduced-motion: reduce) {
  .icon-btn:active { transform: none; }
}

/* =========================================================================
   INTRO OVERLAY
   Displayed ONLY under `.intro-live` (set by the sync head script when JS is
   present and the intro hasn't been seen) — no-JS renders the full page.
   ========================================================================= */
.intro { display: none; }

/* Displayed while .intro-live is on <html> AND kept displayed through its own
   .is-leaving exit — keying display off .intro-seen made the overlay vanish the
   same frame dismiss() ran, cutting to the page with no transition. */
.intro-live .intro,
.js .intro.is-leaving {
  display: flex;
  position: fixed;
  inset: 0;
  z-index: var(--z-intro);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Darkest of the palette for the "surface before the dive" — still not pure #000. */
  background: #03050A;
  transition: opacity 1.4s var(--ease-out);
}
/* Same film grain as the page (body::after) so the intro isn't a flat slab. */
.intro::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--grain);
  background-size: 160px 160px;
  opacity: 0.05;
}
/* JS sets this to run the auto-dismiss exit: the dark surface slowly clears… */
.intro.is-leaving {
  opacity: 0;
  pointer-events: none;
}
/* …while the page beneath sharpens — surfacing out of the water. The blur is
   present from first paint (intro-live set by the sync head script) and
   transitions away when dismiss() removes the class. */
@media (prefers-reduced-motion: no-preference) {
  .intro-live main { filter: blur(18px); }
  /* Explicit blur(0px) target: `filter: none` can't be interpolated, so
     transitioning to it snaps instantly — 18px→0px animates. The class (and
     the filter with it) is dropped once the exit completes. */
  .intro-leaving main {
    filter: blur(0px);
    transition: filter 1.6s var(--ease-out);
  }
}

.intro__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-md);
  text-align: center;
}

/* Real brand lockup. screen blend drops the PNG's pure-black bg into the dark
   overlay, leaving only the glowing chevron + wordmark. Rises + fades in. */
.intro__logo {
  width: min(70vw, 460px);
  height: auto;
  mix-blend-mode: screen;
  opacity: 0;
  transform: translateY(24px) scale(0.98);
  animation: intro-rise 1.1s var(--ease-out) 0.2s forwards;
}

.intro__hook {
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  letter-spacing: 0.18em;
  color: var(--accent);
  opacity: 0;
  animation: intro-fade 0.7s var(--ease-out) 1.2s forwards;
}

@keyframes intro-rise {
  to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes intro-fade {
  to { opacity: 1; }
}

/* Reduced motion: collapse the intro to its final composed state instantly. */
@media (prefers-reduced-motion: reduce) {
  .intro__logo,
  .intro__hook {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .intro-live .intro,
  .js .intro.is-leaving { transition: opacity 0.3s linear; }
}

/* =========================================================================
   FIXED CHROME
   ========================================================================= */
.chrome-nav {
  position: fixed;
  top: var(--sp-lg);
  left: var(--sp-lg);
  z-index: var(--z-chrome);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs, 2px);
  padding: var(--sp-xs);
  border-radius: 2px;
  /* Transparent over the hero; JS adds .is-scrolled past it. */
  background: transparent;
  transition: background-color 0.28s var(--ease-out),
    backdrop-filter 0.28s var(--ease-out), box-shadow 0.28s var(--ease-out);
}
.chrome-nav.is-scrolled {
  background: rgba(12, 17, 24, 0.72);
  backdrop-filter: blur(10px);
  box-shadow: var(--shadow-sm);
}
.chrome-nav__link {
  min-height: 44px;
  /* Floats over moving video — crisp edge + soft haze keeps it legible. */
  text-shadow: 0 1px 2px rgba(2, 4, 8, 0.75), 0 2px 10px rgba(2, 4, 8, 0.45);
  display: inline-flex;
  align-items: center;
  padding: 0 var(--sp-xs);
  border: none;
  background: transparent;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.06em;
  text-decoration: none;
  cursor: pointer;
  transition: color 0.16s var(--ease-out);
}
.chrome-nav__link:hover { color: var(--accent-hover); }
.chrome-nav__link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.chrome-wordmark {
  position: fixed;
  left: var(--sp-lg);
  bottom: var(--sp-lg);
  z-index: var(--z-chrome);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  min-height: 44px;
  text-decoration: none;
  color: var(--text);
  /* Floats over video tiles once surfaced. */
  text-shadow: 0 1px 2px rgba(2, 4, 8, 0.75), 0 2px 10px rgba(2, 4, 8, 0.45);
}
/* Real chevron; screen blend drops its black bg, leaving the glow. Slightly
   larger than the old SVG to seat the glow padding. */
.chrome-wordmark__mark { width: 26px; height: auto; mix-blend-mode: screen; }
/* Real ABYSS glyphs (mask) filled with text colour — matches the logo font. */
.chrome-wordmark__text {
  display: block;
  width: 92px;
  aspect-ratio: 4072 / 334;
  background-color: var(--text);
  -webkit-mask-image: url(media/mask-abyss.png);
  mask-image: url(media/mask-abyss.png);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  filter: drop-shadow(0 1px 2px rgba(2, 4, 8, 0.85));
}
.chrome-wordmark:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* The dive instruments (side ruler, depth gauge) and the wordmark only surface
   once the dive begins — hidden at the top of the page, back out when you
   return to the surface. JS toggles html.is-scrolled off the hero observer.
   Scoped under .js so a no-JS load still shows them statically. */
.js .chrome-wordmark,
.js .depth-readout,
.js .depth-ruler {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.45s var(--ease-out);
}
.js.is-scrolled .chrome-wordmark,
.js.is-scrolled .depth-readout,
.js.is-scrolled .depth-ruler {
  opacity: 1;
}
.js.is-scrolled .chrome-wordmark { pointer-events: auto; }

.depth-readout {
  position: fixed;
  right: var(--sp-lg);
  bottom: var(--sp-lg);
  z-index: var(--z-chrome);
  display: inline-flex;
  align-items: baseline;
  gap: 0.35ch;
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.08em;
  color: var(--text-muted);
  /* Floats over video tiles once surfaced. */
  text-shadow: 0 1px 2px rgba(2, 4, 8, 0.75), 0 2px 10px rgba(2, 4, 8, 0.45);
}
.depth-readout__value {
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  font-weight: 500;
}

.depth-ruler {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: 12px;
  z-index: var(--z-chrome);
  /* Repeating hairline ticks — a diver's depth ruler down the left edge. */
  background-image: repeating-linear-gradient(
    to bottom,
    var(--border) 0,
    var(--border) 1px,
    transparent 1px,
    transparent 12px
  );
  pointer-events: none;
}
.depth-ruler__playhead {
  position: absolute;
  left: 0;
  top: 0;
  width: 12px;
  height: 2px;
  background: var(--accent);
  /* JS drives translateY with scroll progress. */
  will-change: transform;
}

/* =========================================================================
   HERO
   ========================================================================= */
.hero {
  /* Pinned while the title band scrolls up OVER it. The fade is anchored to
     the CONTENT, not to scroll progress: fully opaque above the title band's
     top edge (--hero-fade-start, viewport px set each frame by the scroll
     handler), easing to fully transparent at the categories row
     (--hero-fade-end) — one continuous gradient, video always visible behind
     the title, never a moving cut line. Defaults keep it visible without JS. */
  position: sticky;
  top: 0;
  z-index: 0;
  width: 100vw;
  height: 100svh;
  min-height: 34rem;
  overflow: hidden;
  /* GOTCHA: pinned = it covers the viewport for the WHOLE page. Without this,
     the invisible hero swallows clicks on static content below (footer links).
     The scroll cue re-enables itself. */
  pointer-events: none;
  -webkit-mask-image: linear-gradient(
    to bottom,
    #000 calc(var(--hero-fade-start, 9999) * 1px),
    transparent calc(var(--hero-fade-end, 99999) * 1px)
  );
  mask-image: linear-gradient(
    to bottom,
    #000 calc(var(--hero-fade-start, 9999) * 1px),
    transparent calc(var(--hero-fade-end, 99999) * 1px)
  );
}
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Same unifying cool grade as the mosaic tiles. */
  filter: saturate(0.85) contrast(1.05);
}
/* Portrait phone: the landscape reel cover-crops to a middle zoom. Show the
   whole frame instead (letterboxed). Landscape phones + desktop keep cover. */
@media (max-width: 768px) and (orientation: portrait) {
  .hero__video { object-fit: contain; }
}
.hero__scrim {
  position: absolute;
  inset: 0;
  /* Lockup moved below the video — a lighter scrim remains for nav/cue legibility. */
  background: linear-gradient(to top, rgba(6, 8, 12, 0.55), transparent 40%),
    linear-gradient(to bottom, rgba(6, 8, 12, 0.35), transparent 30%);
}
/* =========================================================================
   TITLE BAND — lockup below the hero (the video runs clean, no text overlap)
   ========================================================================= */
.title-band {
  /* Transparent + above the pinned hero: the lockup slides up over the video
     while the mask fades the footage out beneath it. */
  position: relative;
  z-index: 1;
  padding: clamp(3rem, 7vw, 5rem) var(--sp-2xl) clamp(1.5rem, 4vw, 3rem);
  text-align: center;
  background: transparent;
  perspective: 900px; /* stage for the lockup's pointer tilt */
}
.title-band__lockup {
  margin: 0;
  /* Pointer-driven 3D tilt (JS sets --tilt-x/--tilt-y; 0 when idle/no-hover). */
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transition: transform 0.18s var(--ease-out);
}
/* Subtle depth for the lockup — legitimate again now that the title sits over
   the fading hero VIDEO (over the earlier flat near-black band it rendered as
   a smudged halo). drop-shadow, not text-shadow: text-shadow paints through
   the transparent background-clip glyphs. */
.title-band__word,
.title-band__sub {
  filter: drop-shadow(0 1px 2px rgba(2, 4, 8, 0.65))
    drop-shadow(0 6px 18px rgba(2, 4, 8, 0.45));
}
/* The wordmarks are now the REAL logo glyphs (alpha masks) filled with the
   crystal gradient — exact brand font, and every effect (gradient / --sheen /
   3D tilt / hover flow) still works because they operate on a real background,
   not background-clip:text. mask-size 100% 100% is exact: each element's
   aspect-ratio equals its mask's, so the glyphs never distort. */
.title-band__word {
  display: block;
  width: min(90vw, 680px);
  aspect-ratio: 4072 / 334; /* mask-abyss.png */
  margin-inline: auto;
  /* Crystal glass: icy vertical gradient; --sheen (pointer-driven) slides the
     light across the glass as the lockup tilts. Static without JS/pointer. */
  background-image: linear-gradient(
    180deg,
    #F7FBFF 0%,
    #E4F0F8 38%,
    #AFCBDD 62%,
    #7FA3BD 88%,
    #6E93AF 100%
  );
  background-size: 100% 170%;
  background-position: 50% calc(32% + var(--sheen, 0%));
  -webkit-mask-image: url(media/mask-abyss.png);
  mask-image: url(media/mask-abyss.png);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.title-band__sub {
  display: block;
  width: min(85.4vw, 645px); /* 0.949 × word, preserves the logo's line scale */
  aspect-ratio: 3868 / 112; /* mask-sub.png */
  margin: calc(var(--sp-sm) + 0.3em) auto 0;
  background-color: var(--text);
  -webkit-mask-image: url(media/mask-sub.png);
  mask-image: url(media/mask-sub.png);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
/* Same icy current as the category tags flows through the lockup on hover. */
@media (prefers-reduced-motion: no-preference) {
  .title-band__lockup:hover .title-band__word,
  .title-band__lockup:hover .title-band__sub {
    background-image: linear-gradient(
      100deg,
      var(--text) 20%,
      var(--accent-hover) 50%,
      var(--text) 80%
    );
    background-size: 220% 100%;
    animation: zone-filter-flow 1.6s linear infinite;
  }
}
@media (prefers-reduced-motion: reduce) {
  .title-band__lockup:hover .title-band__word,
  .title-band__lockup:hover .title-band__sub {
    background-image: none;
    background-color: var(--accent-hover);
  }
}
.title-band__tagline {
  margin: var(--sp-lg) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.12em;
  color: var(--text-muted);
  /* Same over-video defense as the lockup (plain text — text-shadow is fine). */
  text-shadow: 0 1px 2px rgba(2, 4, 8, 0.75), 0 2px 10px rgba(2, 4, 8, 0.45);
}
.title-band__hook {
  margin: var(--sp-sm) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  letter-spacing: 0.22em;
  color: var(--accent);
  text-shadow: 0 1px 2px rgba(2, 4, 8, 0.75), 0 2px 10px rgba(2, 4, 8, 0.45);
}
.hero__cue {
  position: absolute;
  left: 50%;
  bottom: var(--sp-lg);
  transform: translateX(-50%);
  pointer-events: auto; /* the one interactive element inside the inert hero */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  color: var(--text-muted);
  transition: color 0.18s var(--ease-out);
  animation: cue-bob 2.4s ease-in-out infinite;
}
.hero__cue svg { width: 24px; height: 24px; }
.hero__cue:hover { color: var(--accent-hover); }
.hero__cue:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

@keyframes cue-bob {
  0%, 100% { transform: translate(-50%, 0); }
  50% { transform: translate(-50%, 6px); }
}
@media (prefers-reduced-motion: reduce) {
  .hero__cue { animation: none; }
}

/* =========================================================================
   WORK — mosaic
   ========================================================================= */
.work {
  /* Top rhythm shares the space with .title-band above it. Transparent on
     purpose: the hero's own mask gradient owns the fade — an opaque edge here
     would re-introduce the hard cut. */
  position: relative;
  z-index: 1;
  padding: clamp(2rem, 5vw, 4.5rem) var(--sp-2xl) var(--sp-2xl);
}

/* Category filter — dives to a stratum's anchor; text-only, generously spaced. */
.zone-filter {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(var(--sp-md), 4vw, var(--sp-2xl));
  max-width: 1400px;
  margin: 0 auto clamp(3rem, 7vw, 6rem);
}
.zone-filter[hidden] { display: none; } /* flex would defeat [hidden] otherwise */
.zone-filter__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* hit-area gate */
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--text-muted);
  transition: color 0.2s var(--ease-out);
}
/* Hairline pipe between items (not on the first). */
.zone-filter__link + .zone-filter__link::before {
  content: "";
  position: absolute;
  left: calc(-0.5 * clamp(var(--sp-md), 4vw, var(--sp-2xl)));
  height: 1em;
  width: 1px;
  background: var(--border);
}
/* Underline grows from center on hover/focus. */
.zone-filter__link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 6px;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transition: transform 0.3s var(--ease-out);
}
.zone-filter__link:hover::after,
.zone-filter__link:focus-visible::after { transform: scaleX(1); }
.zone-filter__link:hover,
.zone-filter__link:focus-visible { color: var(--accent-hover); }
/* Scrollspy: the stratum currently in view keeps its underline + accent. */
.zone-filter__link.is-active { color: var(--accent-hover); }
.zone-filter__link.is-active::after { transform: scaleX(1); }
.zone-filter__link:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* "Flow" hover: an icy current sweeps through the letters (motion-safe only —
   reduced-motion users get the plain color/underline change above). */
@media (prefers-reduced-motion: no-preference) {
  .zone-filter__link:hover {
    color: transparent;
    background-image: linear-gradient(
      100deg,
      var(--text-muted) 20%,
      var(--accent-hover) 50%,
      var(--text-muted) 80%
    );
    background-size: 220% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    animation: zone-filter-flow 1.6s linear infinite;
  }
}
@keyframes zone-filter-flow {
  from { background-position: 110% 0; }
  to { background-position: -110% 0; }
}
.mosaic {
  max-width: 1400px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--sp-md);
}

/* Zone label row — spans full width, hairline rule, mono. */
.zone-label {
  grid-column: 1 / -1;
  scroll-margin-top: var(--sp-2xl); /* anchor target clears the fixed chrome */
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  margin-top: var(--sp-2xl);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.1em;
  color: var(--text-muted);
  min-width: 0;
}
.zone-label:first-child { margin-top: 0; }
.zone-label__index { color: var(--accent); font-weight: 500; }
.zone-label__name { color: var(--text); white-space: nowrap; }
.zone-label__rule {
  flex: 1 1 auto;
  height: 1px;
  min-width: var(--sp-lg);
  background: var(--border);
}
.zone-label__depth {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Tile — the whole poster/video is a button that opens the player. */
.tile {
  position: relative;
  /* Width comes from the data (buildTile sets --tile-span: 12/8/6/4). */
  grid-column: span var(--tile-span, 6);
  min-width: 0;            /* prevent long-label overflow in the grid */
  aspect-ratio: 16 / 9;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 0;
  background: var(--surface);
  color: var(--text);
  overflow: hidden;
  cursor: pointer;
  text-align: left;
  transition: box-shadow 0.24s var(--ease-out), transform 0.14s var(--ease-out);
}
/* Full-width tiles get a wider cinematic band (16:9 slab is too tall at span 12). */
.tile--full { grid-column: span 12; aspect-ratio: 21 / 9; }
.tile:hover,
.tile:focus-visible { box-shadow: var(--shadow-lg); }
.tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tile:active { transform: scale(0.996); }

.tile__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.tile__poster,
.tile__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Unifying cool grade so mixed stock footage sits in one palette. */
  filter: saturate(0.85) contrast(1.05);
}
.tile__video {
  opacity: 0;
  transition: opacity 0.4s var(--ease-out), transform 0.5s var(--ease-out);
}
.tile.is-playing .tile__video { opacity: 1; }
/* Motion-safe media nudge on hover/focus. */
.tile:hover .tile__media,
.tile:focus-visible .tile__media {
  transform: scale(0.985);
  transition: transform 0.5s var(--ease-out);
}

/* Per-tile scrim + credit block. */
.tile__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(6, 8, 12, 0.82), transparent 46%);
  pointer-events: none;
}
.tile__credit {
  position: absolute;
  left: var(--sp-md);
  right: var(--sp-md);
  bottom: var(--sp-md);
  min-width: 0;
  text-wrap: pretty;
  transform: translateY(6px);
  opacity: 0.92;
  transition: transform 0.28s var(--ease-out), opacity 0.28s var(--ease-out);
}
.tile:hover .tile__credit,
.tile:focus-visible .tile__credit { transform: translateY(0); opacity: 1; }
.tile__title {
  display: block;         /* span by markup — force block so title/meta stack */
  margin: 0;
  font-weight: 500;
  font-size: var(--fs-lead);
  line-height: 1.25;
  text-shadow:
    0 1px 2px rgba(2, 4, 8, 0.7),
    0 1px 12px rgba(6, 8, 20, 0.55);
}
.tile__title span { color: var(--text-muted); font-weight: 400; }
.tile__meta {
  display: block;         /* span by markup — force block so it sits below the title */
  margin: var(--sp-2xs) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  letter-spacing: 0.08em;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* The single accent detail — a tiny icy tick in the corner. */
.tile__tick {
  position: absolute;
  top: var(--sp-sm);
  right: var(--sp-sm);
  width: 14px;
  height: 2px;
  background: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .tile:active { transform: none; }
  .tile:hover .tile__media,
  .tile:focus-visible .tile__media { transform: none; }
}

/* =========================================================================
   MANIFESTO (−440 m)
   ========================================================================= */
.manifesto {
  /* Deeper zone reads darker. */
  position: relative; /* anchors the ghost watermark */
  background: #04060A;
  padding: clamp(5rem, 14vw, 10rem) var(--sp-2xl);
}
/* Ghost chevron: the real glowing brand mark, screen-blended so its black bg
   vanishes into the floor, held faint so it's relief behind the text — not a
   competing element. Decorative (aria-hidden). */
.manifesto__watermark {
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(280px, 46vmin, 520px);
  height: auto;
  transform: translate(-50%, -46%);
  mix-blend-mode: screen;
  opacity: 0.32;
  pointer-events: none;
}
.manifesto__text {
  position: relative; /* stays above the watermark */
  max-width: 22ch;
  margin: 0 auto;
  font-weight: 300;
  font-size: var(--fs-h3);
  line-height: 1.3;
  letter-spacing: -0.01em;
  text-align: center;
  text-wrap: balance;
}

/* =========================================================================
   CONTACT / FLOOR (−480 m — darkest)
   ========================================================================= */
.floor {
  background: #03050A;
  padding: clamp(4rem, 12vw, 8rem) var(--sp-2xl);
  text-align: center;
}
.floor__eyebrow {
  margin: 0;
  font-weight: 300;
  font-size: var(--fs-h4);
  color: var(--text);
}
.floor__lead {
  margin: var(--sp-sm) 0 var(--sp-2xl);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.1em;
  color: var(--text-muted);
}
.floor__email {
  display: inline-block;
  min-height: 44px;
  padding: var(--sp-2xs) var(--sp-2xs);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-h4);
  letter-spacing: 0.01em;
  color: var(--accent);
  text-decoration: none;
  word-break: break-word;
  transition: color 0.16s var(--ease-out);
}
.floor__email:hover { color: var(--accent-hover); text-decoration: underline; text-underline-offset: 6px; }
.floor__email:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.floor__meta {
  margin: var(--sp-2xl) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.floor__ig {
  display: inline-flex;
  align-items: center;
  gap: 0.2ch;
  min-height: 44px;
  color: var(--text);
  text-decoration: none;
  transition: color 0.16s var(--ease-out);
}
.floor__ig svg { width: 15px; height: 15px; }
.floor__ig:hover { color: var(--accent-hover); }
.floor__ig:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.site-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-md);
  padding: var(--sp-2xl) var(--sp-2xl) calc(var(--sp-2xl) + 40px);
  background: #03050A;
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  letter-spacing: 0.08em;
  color: var(--text-muted);
  text-align: center;
}
/* Real horizontal lockup; screen blend removes the PNG's black bg on #03050A. */
.site-footer__logo {
  width: min(78vw, 340px);
  height: auto;
  mix-blend-mode: screen;
}
.site-footer__line { letter-spacing: 0.1em; }

/* =========================================================================
   ABOUT DRAWER
   ========================================================================= */
.drawer-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  background: rgba(3, 5, 10, 0.6);
  opacity: 0;
  transition: opacity 0.32s var(--ease-out);
}
.drawer-scrim.is-open { opacity: 1; }

.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: calc(var(--z-drawer) + 1);
  width: min(420px, 92vw);
  padding: var(--sp-xl);
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform 0.36s var(--ease-out);
}
.drawer.is-open { transform: translateX(0); will-change: transform; }

.drawer__head {
  display: flex;
  align-items: center;
  justify-content: flex-end; /* close button stays right (chevron removed) */
  margin-bottom: var(--sp-xl);
}
.drawer__title {
  margin: 0 0 var(--sp-md);
  font-weight: 300;
  font-size: var(--fs-h3);
  letter-spacing: 0.04em;
}
.drawer__blurb {
  margin: 0 0 var(--sp-xl);
  color: var(--text);
  text-wrap: pretty;
}
.drawer__meta { margin: 0 0 var(--sp-xl); }
.drawer__row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-md);
  padding: var(--sp-sm) 0;
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  min-width: 0;
}
.drawer__row dt { color: var(--text-muted); flex: none; }
.drawer__row dd {
  margin: 0;
  text-align: right;
  min-width: 0;
  word-break: break-word;
}
.drawer__row a { color: var(--accent); text-decoration: none; }
.drawer__row a:hover { color: var(--accent-hover); text-decoration: underline; }
.drawer__legal {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  color: var(--text-muted);
}
.drawer__legal a { color: var(--text-muted); }
.drawer__legal a:hover { color: var(--accent-hover); }

@media (prefers-reduced-motion: reduce) {
  .drawer { transition: opacity 0.2s linear; opacity: 0; transform: none; }
  .drawer.is-open { opacity: 1; }
}

/* =========================================================================
   FULLSCREEN PLAYER
   ========================================================================= */
.player {
  position: fixed;
  inset: 0;
  z-index: var(--z-player);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* GOTCHA: `display: flex` above beats the [hidden] attribute's UA display:none,
   which left an invisible full-viewport pointer-trap over the page when closed. */
.player[hidden] { display: none; }

/* Failure state when a hotlinked video errors (dead CDN link, network drop). */
.player.has-error .player__stage::after {
  content: "This film couldn\2019t be loaded.";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono, monospace);
  font-size: 0.875rem;
  color: var(--text-muted);
  background: rgba(6, 8, 12, 0.6);
}
.player__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 8, 12, 0.92);
  opacity: 0;
  transition: opacity 0.3s var(--ease-out);
}
.player.is-open .player__backdrop { opacity: 1; }

.player__stage {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: scale(0.985);
  transition: opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out);
}
.player.is-open .player__stage { opacity: 1; transform: scale(1); }

.player__video {
  /* Edge-to-edge: fill the viewport regardless of display size (an intrinsic
     1920px file would otherwise sit with margins on higher-res screens).
     cover trades a slight crop on non-16:9 screens for a full-bleed stage. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #03050A;
}
/* Touch devices: cover on a portrait phone crops a landscape film to a thin
   center slice — show the WHOLE frame instead (letterboxed); rotating the
   phone to landscape then fills the screen naturally. Desktop keeps cover. */
@media (hover: none) and (pointer: coarse) {
  .player__video { object-fit: contain; }
}
/* Portrait films always show their full frame (pillarboxed), never cover-cropped. */
.player.is-portrait .player__video { object-fit: contain; }

.player__info {
  position: absolute;
  left: var(--sp-lg);
  bottom: calc(64px + var(--sp-lg));
  max-width: min(360px, 80vw);
  padding: var(--sp-md);
  background: rgba(12, 17, 24, 0.92);
  border: 1px solid var(--border);
  border-radius: 2px;
  box-shadow: var(--shadow-md);
}
.player__info-title {
  margin: 0;
  font-weight: 500;
  font-size: var(--fs-lead);
  text-wrap: balance;
}
.player__info-artist { margin: var(--sp-2xs) 0 var(--sp-sm); color: var(--text-muted); }
.player__info-type,
.player__info-dur {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.player__bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
  transition: opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out);
}
/* Auto-hide: the chrome sleeps unless the pointer is near the bottom of the
   screen, the video is paused, or a control holds keyboard focus (JS toggles
   .chrome-visible; focus-within is the CSS safety net). Touch devices never
   auto-hide (no hover to summon it back). */
@media (hover: hover) and (pointer: fine) {
  .player:not(.chrome-visible) .player__bar:not(:focus-within) {
    opacity: 0;
    transform: translateY(10px);
    pointer-events: none;
  }
}
.player__bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-md);
  padding: var(--sp-xs) var(--sp-md);
  min-height: 64px;
}
.player__bar-left { display: flex; align-items: center; gap: var(--sp-xs); min-width: 0; }
.player__mark { width: 22px; height: auto; flex: none; mix-blend-mode: screen; }
/* Real ABYSS glyphs (mask), matches the logo font. */
.player__wordmark {
  display: block;
  width: 88px;
  aspect-ratio: 4072 / 334;
  flex: none;
  background-color: var(--text);
  -webkit-mask-image: url(media/mask-abyss.png);
  mask-image: url(media/mask-abyss.png);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.player__bar-center {
  display: flex;
  align-items: center;
  gap: var(--sp-2xs);
  min-width: 0;
}
.player__bar-right { display: flex; align-items: center; gap: var(--sp-2xs); }

/* Volume: mute button + a slider that expands on hover/focus (keeps the bar
   compact until you reach for it). */
.player__volume { display: inline-flex; align-items: center; }
.player__volume-slider {
  width: 0;
  opacity: 0;
  height: 3px;
  margin-left: 0;
  cursor: pointer;
  accent-color: var(--accent); /* modern browsers tint track+thumb icy-blue */
  background: transparent;
  transition: width 0.22s var(--ease-out), opacity 0.22s var(--ease-out),
    margin-left 0.22s var(--ease-out);
}
.player__volume:hover .player__volume-slider,
.player__volume:focus-within .player__volume-slider,
.player__volume-slider:focus-visible {
  width: 72px;
  opacity: 1;
  margin-left: var(--sp-2xs);
}
.player__volume-slider:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* Touch (no hover): keep it visible + compact so it's usable without hover. */
@media (hover: none) and (pointer: coarse) {
  .player__volume-slider { width: 60px; opacity: 1; margin-left: var(--sp-2xs); }
}

.player__time {
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  letter-spacing: 0.04em;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  padding-left: var(--sp-2xs);
}

/* Play/pause + mute glyph swaps — driven by state classes on the button. */
.ic-pause,
.ic-mute { display: none; }
#player-playpause.is-playing .ic-play { display: none; }
#player-playpause.is-playing .ic-pause { display: inline; }
/* Mute icon turns accent when MUTED (aria-pressed reflects state). */
#player-mute[aria-pressed="true"] { color: var(--accent); }
#player-mute[aria-pressed="true"] .ic-volume { display: none; }
#player-mute[aria-pressed="true"] .ic-mute { display: inline; }

/* Film-ruler scrubber along the bar's top edge. */
.player__scrub {
  position: absolute;
  left: 0;
  right: 0;
  top: -7px;
  height: 14px;
  cursor: pointer;
  touch-action: none;   /* pointer drag owns the gesture, no scroll hijack */
}
.player__scrub:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.player__scrub-track {
  position: absolute;
  left: 0;
  right: 0;
  top: 6px;
  height: 3px;
  /* Repeating tick marks — the "film ruler". */
  background-image: repeating-linear-gradient(
    to right,
    var(--border) 0,
    var(--border) 1px,
    transparent 1px,
    transparent 10px
  );
  background-color: rgba(126, 142, 161, 0.18);
}
.player__scrub-fill {
  position: absolute;
  left: 0;
  top: 6px;
  height: 3px;
  width: 0;
  background: var(--accent);
}
.player__scrub-head {
  position: absolute;
  top: 3px;
  left: 0;
  width: 3px;
  height: 9px;
  background: var(--accent-hover);
  transform: translateX(-50%);
}

@media (prefers-reduced-motion: reduce) {
  .player__stage,
  .player__backdrop { transition: opacity 0.2s linear; }
  .player__stage { transform: none; }
  .player.is-open .player__stage { transform: none; }
}

/* Lock body scroll while an overlay owns the screen. */
body.is-locked { overflow: hidden; }

/* =========================================================================
   RESPONSIVE
   ========================================================================= */
@media (max-width: 768px) {
  .work { padding-left: var(--sp-md); padding-right: var(--sp-md); }
  /* Below 768: every tile spans full width. */
  .tile,
  .tile--full { grid-column: 1 / -1; }
  .mosaic { gap: var(--sp-sm); }

  /* Stacked nav floats mid-line over centered text on small screens; a
     horizontal top strip reads as a header and keeps words legible under it. */
  .chrome-nav {
    top: 0;
    left: 0;
    right: 0;
    flex-direction: row;
    gap: var(--sp-sm);
    padding: var(--sp-2xs) var(--sp-md);
    border-radius: 0;
  }
  .chrome-wordmark { left: var(--sp-md); bottom: var(--sp-md); }
  .depth-readout { right: var(--sp-md); bottom: var(--sp-md); }

  .title-band { padding-left: var(--sp-lg); padding-right: var(--sp-lg); }

  .player__bar-inner { flex-wrap: wrap; gap: var(--sp-xs); }
  /* Drop the wordmark group entirely on small screens so fullscreen/close never
     wrap to a second row bottom-left — transport stays one row, close top-right. */
  .player__bar-left { display: none; }
  .player__time { font-size: var(--fs-caption); }
  .player__info { left: var(--sp-md); right: var(--sp-md); max-width: none; }
}

/* Wide screens get a roomier mosaic gap. */
@media (min-width: 1440px) {
  .mosaic { gap: var(--sp-lg); }
}
