/* ============================================================
   STUDIO — dark editorial / motion-design homepage
   Generic placeholder build. Vanilla CSS, no framework.
   ============================================================ */

/* Sligoil Micro — Velvetyne / Ariel Martín Pérez, monospace (self-hosted, OFL) */
@font-face {
  font-family: "Sligoil Micro";
  src: url("fonts/Sligoil-Micro.woff2") format("woff2"),
       url("fonts/Sligoil-Micro.woff") format("woff");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  /* clean grotesque for headings (KOTO-style: one sans, small + light) — no serif */
  --font-display: "Geist", system-ui, -apple-system, sans-serif;
  --bg: #0a0a0a;
  --bg-2: #111;
  --fg: #ffffff;
  --muted: #8a8a8a;
  --muted-2: #5a5a5a;
  --accent: #ededed; /* monochrome — off-white "accent"; imagery is the only colour */
  --radius: 16px;
  --gutter: clamp(18px, 2.2vw, 34px); /* edge space — reduced, used everywhere */
  --maxw: 1680px;
  --ease: cubic-bezier(0.76, 0, 0.24, 1);
  --ease-out: cubic-bezier(.22, 1, .36, 1); /* entrance lift — the wipe/curtain family */
  /* duration scale for the scattered UI transitions (hovers, fades, draws).
     Choreographed timings — nav collapse, wipe cover/lift, grain/kenburns/drift
     keyframes, the video control bar — keep their own tuned values. */
  --t-fast: .2s;
  --t-med: .35s;
  --t-slow: .5s;

  /* minimal rounded corners — two sizes */
  --radius-lg: 6px;
  --radius-sm: 4px;

  /* frosted glass (Kevin Powell style): translucent bg + blur + a gradient rim
     (lighter top → fainter bottom) drawn via a mask-composite border on ::before */
  --glass-bg: hsl(240 6% 10% / 0.42);
  --glass-blur: 12px;
  --glass-edge-top: hsl(0 0% 100% / 0.30);
  --glass-edge-bottom: hsl(0 0% 100% / 0.05);

  /* ============================================================
     LAYOUT GRID — single source of truth (see CLAUDE.md "Layout grid").
     12 columns between the page margins; every region snaps to a column span,
     so the rail (nav · home work text · Work-page title) always lines up.
     ============================================================ */
  --columns: 12;
  --gap: clamp(12px, 1.4vw, 22px);                                  /* gutter BETWEEN columns */
  /* content caps at --maxw on huge screens; --edge grows the side inset past --maxw
     so the grid centers (hero media stays full-bleed). Below --maxw both are no-ops:
     --content-w = 100vw and --edge = --gutter, so normal screens are byte-identical. */
  --content-w: min(100vw, var(--maxw));
  --edge: max(var(--gutter), calc((100vw - var(--maxw)) / 2 + var(--gutter)));
  --col: calc((var(--content-w) - 2 * var(--gutter) - 11 * var(--gap)) / 12);  /* one of 12 columns */
  --rail: calc(3 * var(--col) + 2 * var(--gap));                    /* the standard rail = cols 1–3 (~1/4) */
  --measure: 34rem;                                                  /* max reading width for body copy */
  /* Selected-work videos span 8 columns (cols 5–12, right edge); sticky-centre padding from it */
  --workmedia: calc(8 * var(--col) + 7 * var(--gap));
  --panel-pad: max(4svh, calc(50svh - 0.3125 * var(--workmedia)));   /* 0.3125 = ½ · (10/16) for 16:10 panels */
}

/* shared frosted-glass surface + gradient rim. Apply `is-glass` to any element that
   should read as glass; it provides the background, blur and the rim border. */
.is-glass { position: relative; background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); }
/* (rim light removed — glass is just plain blur.)
   LIQUID-GLASS DISPLACEMENT REMOVED 2026-06-13. The url(#liquid-glass) SVG filter in
   backdrop-filter re-samples AND displaces the backdrop every frame, so on a fast
   scroll Chromium can't keep up → it flashes the glass, and the media behind it, on/off
   (whole-page flash on fast bottom→top scroll). This is the same root cause already
   found + patched locally at .pdetail__navname (~line 1048); this removes it globally.
   Plain blur from .is-glass stays; the nav pill keeps its frost via .nav__shell::before.
   The <filter id="liquid-glass"> in index.html is now unused (safe to delete). */
/* Mobile Safari PASSES the @supports test above but can't actually render an SVG
   url() filter in backdrop-filter, so the WHOLE declaration (blur included) is
   dropped → no glass blur on phones. The liquid displacement is a desktop-only
   flourish anyway, so on touch devices force plain blur back (incl. the nav pill,
   whose blur lives on ::before). */
@media (pointer: coarse) {
  .is-glass,
  .nav.is-scrolled .nav__shell,
  .nav__shell::before {
    backdrop-filter: blur(var(--glass-blur)) !important;
    -webkit-backdrop-filter: blur(var(--glass-blur)) !important;
  }
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  -webkit-font-smoothing: antialiased;
  background: var(--bg);
  color-scheme: dark;
  /* no rubber-band overscroll: you can't drag past the top to reveal the page bg */
  overscroll-behavior-y: none;
  /* always reserve the scrollbar gutter so opening a route (body→overflow:hidden)
     doesn't widen the page by the scrollbar width → no horizontal jump on the
     fixed nav / full-bleed hero */
  scrollbar-gutter: stable;
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: "Geist", system-ui, -apple-system, sans-serif;
  line-height: 1.2;
  overflow-x: clip; /* clip h-overflow WITHOUT making body a scroll container */
  overscroll-behavior-y: none;
}

a { color: inherit; }
.mono { font-family: "Sligoil Micro", ui-monospace, monospace; }
.muted { color: var(--muted); }

::selection { background: var(--accent); color: #000; }

/* dark scrollbars — a quiet thumb on a transparent track, on the document AND the
   #route overlay (a fixed inset:0 container that scrolls internally, so it paints
   its own bar). When the standard properties are honoured the ::-webkit rules are
   ignored; they remain for engines that only know the pseudo-element form. */
html, .route { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.22) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
/* transparent border + padding-box clip insets the thumb off the page edge */
::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,.22); border-radius: 5px;
  border: 3px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background-color: rgba(255,255,255,.34); }

.link-arrow { text-decoration: none; transition: opacity var(--t-fast) ease; }
.link-arrow:hover { opacity: .6; }

/* masked line reveal (one-shot): inner block slides up from below an overflow
   clip — the `translate-y-full → 0` pattern. Initial transform set here so the
   line is hidden before JS runs (no flash); GSAP animates it to 0 on view. */
.mask { overflow: hidden; }
.mask__i { display: block; }
/* hide the line before GSAP takes over (no flash); once `.gsap-ready` is set,
   GSAP owns the transform exclusively so the two never stack */
html:not(.gsap-ready) .mask__i { transform: translateY(110%); }
a.mask, .mask--inline { display: inline-block; }

/* ---------- animated gradient "media" placeholders ----------
   Motion lives on a ::before layer driven by `transform` only, so it is
   GPU-composited and never triggers a repaint while scrolling. */
/* NB: container-type:inline-size + isolation:isolate were grain scaffolding (cqw sizing
   + containing the overlay blend). Grain is off, so they're removed — both are GPU
   re-rasterization triggers that fed the flashing/lag (compositor overload). */
.media { position: absolute; inset: 0; overflow: hidden; }
.media::before {
  content: ""; position: absolute; inset: -28%;
  background-size: 100% 100%;
  animation: drift 22s ease-in-out infinite;
  transform: translateZ(0);
}
.media--hero::before   { background-image: linear-gradient(120deg,#1a0a2e,#2d1b69,#0f3460,#b6005a,#ff7a18); }
.media--peach::before  { background-image: linear-gradient(135deg,#ff9966,#ff5e62,#ffcc70,#ff7a18); }
.media--lime::before   { background-image: linear-gradient(135deg,#0f3d2e,#1b998b,#d8f000,#3ddc84); }
.media--violet::before { background-image: linear-gradient(135deg,#0d0d0d,#2d1b69,#7028e4,#e5b2ca); }
.media--ocean::before  { background-image: linear-gradient(135deg,#0f2027,#203a43,#2c5364,#27c4c4); }
.media[data-paused="true"]::before { animation-play-state: paused; }
/* photo placeholder: a real image replaces the animated gradient on this surface */
.media--photo { background-size: cover; background-position: center; background-repeat: no-repeat; }
.media--photo::before { display: none; }
/* slow Ken-Burns drift keeps the still images alive (reads less "template", more
   film). Scale stays >1 so no edge ever shows while it pans. Paused off-screen
   via the existing data-paused flag. Different durations desync the surfaces. */
/* No permanent will-change: it forever-promotes EVERY photo (and the wrapper of every
   video tile) to its own GPU layer → Chromium re-rasterizes them on fast scroll = the
   flashing + lag. The kenburns transform auto-composites only while it actually runs. */
.media--photo { animation: kenburns 28s ease-in-out infinite; }
.hero__media.media--photo { animation-duration: 34s; }
.card__media .media--photo { animation-duration: 22s; }
/* Ken-Burns is for STILLS only — when a <video> is present the drift slowly zooms
   the clip, which we don't want. Cancel the animation on any media holding a video. */
.media--photo:has(.media__video) { animation: none; transform: none; }
.media[data-paused="true"] { animation-play-state: paused; }
@keyframes kenburns {
  0%   { transform: scale(1.06) translate(0, 0); }
  50%  { transform: scale(1.12) translate(-1.4%, -1%); }
  100% { transform: scale(1.06) translate(0, 0); }
}
/* DROP-IN VIDEO: put a <video class="media__video" autoplay muted loop playsinline
   poster="<the photo url>" src="media/xxx.mp4"></video> inside any .media and it
   covers the photo automatically (photo stays as poster/fallback). */
.media video, .media__video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; z-index: 1;
  /* NO live filter / translateZ here. A `filter` on a decoding <video> forces a
     per-frame re-rasterization of its GPU layer, which Chromium flashes on fast
     scroll (confirmed 2026-06-13). The old `filter: contrast(1.06)` gamma dip lived
     here — removed for that reason. The transcode already ships color-correct (sRGB
     tag); if the extra depth is wanted back, bake it into transcode.sh, not as a
     live CSS filter. */
}

@keyframes drift {
  0%   { transform: translate3d(-2%,-1%,0) scale(1.06) rotate(0deg); }
  50%  { transform: translate3d(2%,1%,0)  scale(1.14) rotate(3deg); }
  100% { transform: translate3d(-2%,-1%,0) scale(1.06) rotate(0deg); }
}

/* ============================================================
   NAV
   ============================================================ */
/* symmetric edge: distance from top == distance from left == --gutter (mirrored right) */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  display: flex; align-items: flex-start; /* so the collapsed shell can grow DOWN as one piece */
  gap: clamp(20px, 2.4vw, 40px);
  padding: var(--gutter) var(--edge);   /* horizontal inset follows the content edge so the wordmark aligns with the rail on ultrawide */
}
/* keep the over-hero row visually centred even though the nav is top-aligned */
.nav__links { min-height: 34px; display: flex; align-items: center; }
/* Over the hero VIDEO, difference-blend is unreliable: white text over a mid-tone
   frame renders ~mid-grey (invisible), and the moving film makes it flicker. So the
   over-hero wordmark + links are solid white, and a top-fade scrim (.nav::before, below)
   carries the contrast — same approach as the hero copy's bottom scrim, no text-shadow.
   Scrolled state is untouched — the frosted pill carries its own contrast and the scrim
   fades out (the pill's scale-in motion is preserved; this never touches .nav__shell::before). */
.nav:not(.is-scrolled) .nav__word,
.nav:not(.is-scrolled) .nav__links a {
  color: #fff;
}
/* Top-fade scrim behind the nav: full-width band of shade dropping from the top edge,
   fading to transparent — no side/bottom edges in frame. Fades out on scroll as the
   frosted pill takes over. */
.nav::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: 0; left: 0; width: 100vw; height: 150px;
  background: linear-gradient(to bottom,
    rgba(0,0,0,.45), rgba(0,0,0,.22) 40%, transparent 100%);
  opacity: 1; transition: opacity var(--t-med) ease;
}
.nav.is-scrolled::before { opacity: 0; }
/* shell HUGS its content (so over the hero the wordmark + links sit close together —
   no rail-wide gap). Collapsed, it's a compact frosted pill: the ::before grows in
   from the left and it expands DOWNWARD into the menu. */
.nav__shell { position: relative; display: flex; flex-direction: column; align-self: flex-start;
  width: max-content; border-radius: var(--radius-sm); overflow: hidden; }
.nav__shell::before {
  content: ""; position: absolute; inset: 0; z-index: 0; border-radius: var(--radius-sm);
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur));
  transform: scaleX(0); transform-origin: left center; opacity: 0;
  transition: transform .5s var(--ease), opacity .35s ease;
}
.nav.is-scrolled .nav__shell::before { transform: scaleX(1); opacity: 1; }
/* keep the bar + menu above the frosted pill */
.nav__bar, .nav__menu { position: relative; z-index: 1; }

/* bar = the top row (wordmark over the hero · page + toggle when collapsed).
   Height is CONSTANT (34px) in both states → no vertical jump on collapse (which
   also keeps the pill compact); only horizontal padding animates. gap:0 — spacing
   between the collapsed bits comes from their own margins so the 0-width page/home
   add nothing over the hero (keeps the wordmark + links tight). */
.nav__bar {
  display: inline-flex; align-items: center; gap: 0; flex: none;
  height: 34px; padding: 0; border: 0; background: transparent;
  color: var(--accent); cursor: pointer; font: inherit; text-align: left;
  transition: padding .45s var(--ease);
}
.nav.is-scrolled .nav__bar { padding: 0 13px; }
/* flex + align-items:center so "Johan" and "Wallmén" share one vertical line
   (an inline-block with overflow:hidden takes its baseline from the bottom edge,
   which knocked "Johan" out of alignment — flex/center sidesteps that quirk) */
.nav__word { display: inline-flex; align-items: center; font-family: "Geist", system-ui, -apple-system, sans-serif; font-weight: 500; font-size: 19px; letter-spacing: -.01em; line-height: 1; white-space: nowrap; transition: font-size .45s var(--ease); }
.nav__family { display: block; }
/* "Johan " collapses away on scroll → leaves "Wallmén" */
.nav__given { display: block; overflow: hidden; max-width: 6em; opacity: 1;
  transition: max-width .5s var(--ease), opacity .4s ease; }
.nav.is-scrolled .nav__given { max-width: 0; opacity: 0; }
/* current-page label — expands/fades in on collapse (animated, not a display pop) */
.nav__page { display: inline-flex; align-items: center; max-width: 0; opacity: 0; overflow: hidden; white-space: nowrap;
  color: var(--muted); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; line-height: 1;
  transition: max-width .3s var(--ease), opacity .22s ease; }
.nav__page::before { content: "/"; margin: 0 9px; color: var(--muted-2); }
.nav.is-scrolled .nav__page { max-width: 12em; opacity: 1; transition: max-width .45s var(--ease) .08s, opacity .35s ease .12s; }
/* menu toggle = a drawn "+" that rotates into an "×" on open.
   OUT (expanding) is quick so it never lingers; IN (collapsing) is delayed so it
   appears AFTER the name settles — mirroring the nicer expand direction. */
.nav__home {
  position: relative; width: 10px; height: 10px; flex: none;
  max-width: 0; overflow: hidden; opacity: 0; margin-left: 0;
  transition: max-width .2s var(--ease), opacity .14s ease, margin .2s var(--ease);
}
.nav__home::before, .nav__home::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; margin-top: -0.625px;
  height: 1.25px; background: currentColor; border-radius: 1px;
  transition: transform .4s var(--ease);
}
.nav__home::after { transform: rotate(90deg); }
.nav.nav--open .nav__home::before { transform: rotate(45deg); }
.nav.nav--open .nav__home::after  { transform: rotate(-45deg); }
.nav.is-scrolled .nav__word { font-size: 16px; }
.nav.is-scrolled .nav__home { max-width: 10px; opacity: .6; margin-left: 9px;
  transition: max-width .35s var(--ease) .2s, opacity .3s ease .24s, margin .35s var(--ease) .2s; }

/* menu: lives INSIDE the shell, below the bar. Smooth height via grid-rows 0fr→1fr
   (no max-height guesswork → no stutter); items stagger-fade in on open. */
.nav__menu {
  display: grid; grid-template-rows: minmax(0, 0fr);   /* minmax(0,…) kills the auto content-min so the closed pill is truly bar-height */
  transition: grid-template-rows .42s var(--ease);
}
.nav:not(.is-scrolled) .nav__menu { display: none; }
.nav.is-scrolled.nav--open .nav__menu { grid-template-rows: minmax(0, 1fr); }
.nav__menuinner { overflow: hidden; min-height: 0; display: flex; flex-direction: column; padding-bottom: 6px; }
.nav__menu a {
  padding: 9px 14px; color: var(--muted); text-decoration: none;
  font-size: 12px; letter-spacing: .12em; text-transform: uppercase;
  opacity: 0; transform: translateY(-6px);
  transition: color .12s ease, opacity .35s ease, transform .4s var(--ease);
}
/* staggered reveal Home → Work → Thoughts → Contact (quick but readable).
   delays apply only on open; closing has no delay so it collapses snappily. */
.nav.nav--open .nav__menu a { opacity: 1; transform: none; }
.nav.nav--open .nav__menu a:nth-child(1) { transition-delay: 0s, .04s, .04s; }
.nav.nav--open .nav__menu a:nth-child(2) { transition-delay: 0s, .10s, .10s; }
.nav.nav--open .nav__menu a:nth-child(3) { transition-delay: 0s, .16s, .16s; }
.nav.nav--open .nav__menu a:nth-child(4) { transition-delay: 0s, .22s, .22s; }
.nav__menu a:hover { color: var(--fg); background: rgba(255,255,255,.07); }
.nav__menu a.is-current { color: var(--fg); }

/* horizontal links — over the hero. On scroll they RETRACT toward the pill (fade +
   slide + collapse width) instead of blinking out via display:none. */
.nav__links {
  display: flex; gap: 24px; align-items: center; margin-left: 0;
  max-width: 360px; overflow: hidden;
  transition: opacity .35s ease, max-width .5s var(--ease), margin .45s var(--ease), transform .45s var(--ease);
}
.nav__links a {
  color: var(--muted); text-decoration: none; font-size: 12px; white-space: nowrap;
  letter-spacing: .12em; text-transform: uppercase;
  transition: color var(--t-fast) ease;
}
.nav__links a:hover { color: var(--fg); }
.nav:not(.is-scrolled) .nav__links a { color: #fff; }
.nav.is-scrolled .nav__links { opacity: 0; max-width: 0; margin-left: 0; transform: translateX(-16px); pointer-events: none; }

/* ============================================================
   HERO
   ============================================================ */
/* hero is pinned (sticky) at the top; the black .content scrolls up over it */
.hero {
  position: sticky; top: 0; z-index: 0;
  height: 100svh; overflow: hidden;
  isolation: isolate; /* contain the copy's blend mode to the hero contents */
}
/* the rest of the page rides above the hero. The top "rest" zone is transparent
   so the hero stays visible (while its headline exits); the panel below is solid
   and covers the hero as it scrolls up. */
.content { position: relative; z-index: 1; }
.hero-rest { height: 40svh; }
/* Dark section rises over the pinned hero. Its top edge is CUT UPWARD: the two
   outer corners are the high points and the edge slopes down to a flat middle,
   so as the panel scrolls up the hero shows through two angled notches — the
   first thing visible is a triangle/angle peeking in on the left and the right. */
.content__panel {
  background: var(--bg);
  /* minimal rounded top corners on the black section; revealed over the hero as it rises */
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.hero__bg { position: absolute; inset: 0; z-index: 0; will-change: transform; }
.hero__media { inset: 0; width: 100%; height: 100%; z-index: 0; }
/* Bottom-fade scrim (not a full vignette): a full-width band of shade rising from the
   hero's bottom edge keeps white text legible over any video frame while leaving the
   upper film clear. Full-width + vertical-only fade means it has NO visible side/top
   edges in frame (the earlier corner-anchored pool was only as wide as the copy, so its
   falloff read as a blob edge over bright frames). Replaces difference-blend, which goes
   invisible over mid-tone frames and flickers on moving video. */
.hero__copy {
  position: absolute; left: var(--edge); bottom: var(--gutter); z-index: 2;
}
.hero__copy::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: calc(-1 * var(--edge)); width: 100vw;
  bottom: calc(-1 * var(--gutter)); height: 48vh;
  background: linear-gradient(to top,
    rgba(0,0,0,.55), rgba(0,0,0,.30) 24%, rgba(0,0,0,.10) 46%, transparent 74%);
}
.hero__title { display: flex; flex-direction: column; font-weight: 300; gap: 2px; }
/* hero = a restrained greeting — light, modestly sized, tight (KOTO-style) */
.hero__display {
  font-family: "Geist", system-ui, -apple-system, sans-serif;
  font-weight: 300; font-size: clamp(32px, 3.8vw, 58px); line-height: 1.02;
  letter-spacing: -.02em; color: #fff;
}
/* hero PLAY control — frosted glass; follows the cursor over the hero,
   opens the fullscreen player on click */
.hero__play {
  position: absolute; left: 50%; top: 52%; transform: translate(-50%,-50%); z-index: 4;
  display: inline-flex; align-items: center; gap: 6px; line-height: 1;
  border: 0;
  color: #fff; border-radius: var(--radius-sm); cursor: pointer;
  font-size: 11px; letter-spacing: .14em; padding: 7px 12px;
  transition: background var(--t-fast) ease; /* no opacity fade — instant show/hide */
}
.hero__play:hover { background: hsl(240 6% 14% / 0.55); }
/* SVG icon — 1em square so it matches the font size exactly and stays 1:1 */
.hero__playicon { width: 1em; height: 1em; display: block; flex: none; }

/* follow-cursor mode (fine pointers): native cursor hidden, button trails the mouse */
.hero--cursor,
.hero--cursor * { cursor: none; }
/* follow mode: position:fixed + viewport coords (JS) so the pill stays under the
   pointer while scrolling — no scroll-linked repositioning (which janks) needed */
.hero--cursor .hero__play { position: fixed; left: 0; top: 0; opacity: 0; }
.hero--cursor .hero__play.is-follow { opacity: 1; }

/* fullscreen video player — fills the screen, no chrome; Escape exits */
.player {
  position: fixed; inset: 0; z-index: 200;
  background: #000;
  opacity: 0; pointer-events: none; transition: opacity var(--t-med) ease;
  /* When closed the overlay stayed fully composited — including a viewport-sized
     ::before Ken-Burns layer that kept ANIMATING behind it on every page (confirmed
     via CDP compositing reasons). content-visibility:hidden drops the whole subtree's
     layers + halts that animation while closed. The fade-IN still plays (opacity 0→1
     once .is-open matches); only the close-fade becomes instant — a fair trade. */
  content-visibility: hidden;
}
.player.is-open { opacity: 1; pointer-events: auto; content-visibility: visible; }
.player__media { position: absolute; inset: 0; width: 100%; height: 100%; }
.player video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.player__note {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); z-index: 1;
  color: rgba(255,255,255,.7); font-size: 12px; letter-spacing: .15em;
}
/* close ✕ — same frosted-glass style as the PLAY button */
.player__close {
  position: absolute; top: var(--gutter); right: var(--gutter); z-index: 3;
  border: 0; color: #fff; cursor: pointer;
  border-radius: var(--radius-sm); padding: 8px 12px; font-size: 12px; line-height: 1;
  transition: background var(--t-fast) ease;
}
.player__close:hover { background: rgba(28,28,28,.38); }

/* ============================================================
   FILM GRAIN — fine 24fps boil, ONLY on the media surfaces (images + videos).
   Per-.media ::after, clipped to each image/video box (.media overflow:hidden) so
   it never touches UI chrome or type. (A real grain-plate <video> overlay was tried
   and reverted — this CSS grain read nicer.)
   ============================================================ */
.media::after {
  content: none;   /* GRAIN OFF (was "") — flip back to "" to re-enable the boil overlay below */
  position: absolute; z-index: 2; pointer-events: none;
  inset: -50%;   /* oversized + clipped so the boil-translate never reveals an edge */
  /* overlay + TRUE mid-grey (128) noise = identity at 0.5 → zero-mean perturbation,
     no colour/contrast/black shift. The SVG filter MUST be color-interpolation-filters
     ='sRGB' (else feTurbulence's noise gamma-encodes to mean ~187 = a bright grey that
     LIGHTENS/washes via overlay). feColorMatrix desaturates (luma) + flattens alpha. */
  opacity: .6; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0.33 0.33 0.33 0 0 0.33 0.33 0.33 0 0 0.33 0.33 0.33 0 0 0 0 0 0 1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  /* grain DOT SIZE = baseFrequency (lower → bigger, rendered crisp). Do NOT size via
     background-size (scaling the tile just upscales/blurs it). Size in cqw keeps the
     grain the same relative to the footage on every screen. */
  background-size: 22cqw 22cqw;
  animation: grain 0.5s steps(12) infinite;   /* 12 frames / 0.5s = 24fps boil */
  will-change: transform;
}
.media[data-paused="true"]::after { animation-play-state: paused; }
@keyframes grain {
  0%   { transform: translate(0, 0); }
  8%   { transform: translate(-12%, 7%); }
  17%  { transform: translate(10%, -13%); }
  25%  { transform: translate(-8%, 15%); }
  33%  { transform: translate(14%, -6%); }
  42%  { transform: translate(-15%, -10%); }
  50%  { transform: translate(6%, 14%); }
  58%  { transform: translate(-11%, -15%); }
  67%  { transform: translate(15%, 8%); }
  75%  { transform: translate(-7%, -12%); }
  83%  { transform: translate(12%, 13%); }
  92%  { transform: translate(-14%, -7%); }
  100% { transform: translate(0, 0); }
}
@media (prefers-reduced-motion: reduce) { .media::after { animation: none; } }


/* ============================================================
   INTRO (word-by-word reveal)
   ============================================================ */
.intro {
  min-height: 52svh; display: flex; align-items: flex-start;
  /* top inset matches the left/right inset (--gutter) so the text sits high in
     the section with an even gap from the top and the edge */
  padding: var(--gutter) var(--edge) 4svh;
}
/* intro = first-person manifesto — clean Geist, same family as the headlines */
.intro p {
  font-family: "Geist", system-ui, -apple-system, sans-serif; font-weight: 400;
  font-size: clamp(18px, 1.9vw, 30px); line-height: 1.4; letter-spacing: -.01em;
  max-width: 58ch;
}
/* line-by-line reveal: each rendered line in its own overflow clip; the inner
   block rises from below as you scroll (padding/negative-margin pair keeps
   descenders from being clipped at rest) */
.rl { display: block; overflow: hidden; padding-bottom: .12em; margin-bottom: -.12em; }
.rl__i { display: block; will-change: transform; }

/* ============================================================
   OUR WORK (sticky two-column)
   ============================================================ */
.work { padding-top: 0; }
/* full-bleed: text hugs the left edge, panels ~60% of the viewport on the right */
.work__inner {
  display: grid;
  grid-template-columns: repeat(12, 1fr); /* text cols 1–3 · videos cols 5–12 (8 cols, right edge) */
  column-gap: var(--gap);
  padding: 0 var(--edge);
}
.work__rail { grid-column: 1 / 4; }
.work__panels { grid-column: 5 / 13; }

/* rail distributed into 3 zones: label+title ~⅓ down, description mid, meta bottom */
/* rail content sits at the vertical middle of the viewport → aligns with the
   centred video; reserved meta height keeps it from shifting between projects */
.work__rail {
  position: sticky; top: 0; height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
}
.work__label { color: var(--muted); margin-bottom: 1.8rem; font-size: 12px; letter-spacing: .12em; text-transform: uppercase; }
.work__meta { position: relative; min-height: 520px; --title-h: calc(2.5 * clamp(24px, 2.3vw, 40px)); }
/* tight, vertically-centered group; desc min-height reserves space so the block
   is the same height for every project → title/foot never shift on switch.
   Items are stacked; the title rolls behind a mask and desc/foot fade (GSAP). */
.work__item {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: center;
  pointer-events: none;
}
.work__item.is-active { pointer-events: auto; }
/* title wraps at the same width as the body so it reads as a contained section
   (never extends past the body's right edge); reserve max title lines so every
   project's title/body/foot lands in the same place on cross-fade */
.work__title { overflow: hidden; padding-bottom: .06em; max-width: var(--measure); min-height: var(--title-h); }
/* inner title fills the mask height so the masked roll (yPercent ±110) always
   clears a short title fully — otherwise a 2-line title peeks in a 3-line mask
   and overlaps the outgoing title mid-transition */
.work__title-i { display: block; min-height: var(--title-h); font-size: clamp(24px, 2.3vw, 40px); line-height: 1.08; font-weight: 300; letter-spacing: -.02em; color: rgba(255,255,255,.92); }
/* pre-hide non-active parts until GSAP takes over (no flash) */
html:not(.gsap-ready) .work__item:not(.is-active) .work__title-i { transform: translateY(110%); }
html:not(.gsap-ready) .work__item:not(.is-active) .work__desc,
html:not(.gsap-ready) .work__item:not(.is-active) .work__foot { opacity: 0; }
.work__desc { color: var(--muted); font-size: 15px; line-height: 1.65; max-width: var(--measure); margin-top: 1.4rem; min-height: 11.5em; }
.work__foot { display: flex; align-items: center; gap: 14px; margin-top: 1.5rem; }
.work__logo { position: relative; width: 36px; height: 36px; flex: none; border-radius: var(--radius-sm); overflow: hidden; }
.work__footinfo { display: flex; flex-direction: column; gap: 3px; }
.work__client { font-size: 13px; color: var(--fg); }
.work__tag { font-size: 11px; color: var(--muted-2); letter-spacing: .1em; }

/* near-touching stack. Top/bottom padding = 50svh − halfPanelHeight so the rail
   text tracks the active video into the centre with ZERO offset, pins centred,
   and rides out with the LAST video the same way (panel = 60vw, 16/10 →
   height 37.5vw → half 18.75vw). */
.work__panels {
  position: relative; /* positioning context for the cursor-follow VIEW disc */
  display: flex; flex-direction: column; gap: 8px;
  padding: var(--panel-pad) 0; /* centre the active panel; derived from --workmedia height */
}
/* fine pointers: hide the native cursor over the media; the frosted VIEW disc trails
   the mouse instead (same control as the hero PLAY + Thoughts READ buttons). The left
   description column keeps its normal cursor and is NOT a click target. */
.work--cursor .panel, .work--cursor .panel * { cursor: none; }
/* same frosted pill as the hero PLAY + Thoughts READ buttons — just a new symbol + text */
.work__cursor {
  position: fixed; left: 0; top: 0; z-index: 6;
  display: inline-flex; align-items: center; gap: 6px; line-height: 1;
  border: 0; color: #fff; border-radius: var(--radius-sm);
  font-size: 11px; letter-spacing: .14em; padding: 6px 12px;
  opacity: 0; pointer-events: none; /* no opacity fade — instant show/hide */
}
.work__cursor.is-follow { opacity: 1; }
.work__cursoricon { width: 1em; height: 1em; display: block; flex: none; }
.panel {
  display: block;
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-2);
  text-decoration: none;
}
/* darken every panel except the centred (current) one */
.panel::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: #000; opacity: 0; transition: opacity var(--t-med) ease; pointer-events: none;
}
.work--reel .panel:not(.is-current)::after { opacity: .82; }
/* media lives in a wrapper so a mobile caption can flow below it; on desktop the
   wrapper just fills the panel exactly as the bare .media did */
.panel__media { position: absolute; inset: 0; display: block; }
.panel__cap { display: none; }

/* pull the CTA up into the (empty) exit-padding zone so it rests just below the
   last video, without shortening the rail's pin range. position+z-index required:
   the negative margin parks the link inside .work__panels' padding box, and the
   panels are position:relative (VIEW-cursor anchor) — a positioned box hit-tests
   above static content, so without this layer the link is unclickable. */
.work__cta { position: relative; z-index: 2; padding: 0 var(--edge) 6svh; text-align: right; margin-top: calc(4svh - var(--panel-pad)); }
.work__cta a { color: var(--fg); text-decoration: none; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; }

/* ============================================================
   STUDIO NEWS
   ============================================================ */
.news { padding: 8svh var(--edge) 12svh; }
.news__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 3rem;
}
.news__label { color: var(--muted); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; max-width: 60ch; }
.news__head a { font-size: 12px; letter-spacing: .08em; text-decoration: none; }

/* fixed-height grid with equal rows so the two right cards align as a block
   with the feature card; gap matches the work-video gap (8px) */
.news__grid {
  position: relative; /* anchor for the cursor-follow READ button */
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 8px;
  min-height: 64svh;
}
.card {
  text-decoration: none; display: flex; flex-direction: column;
  border-radius: var(--radius-lg); overflow: hidden; background: var(--bg-2);
}
/* fine pointers: hide the cursor over the cards; the frosted READ button trails
   the mouse instead (same control as the hero PLAY button) */
.news--cursor .card,
.news--cursor .card * { cursor: none; }
.news__cursor {
  position: fixed; left: 0; top: 0; z-index: 5;
  display: inline-flex; align-items: center; gap: 6px; line-height: 1;
  border: 0; color: #fff; border-radius: var(--radius-sm);
  font-size: 11px; letter-spacing: .14em; padding: 6px 12px;
  opacity: 0; pointer-events: none; /* no opacity fade — instant show/hide */
}
.news__cursor.is-follow { opacity: 1; }
.news__cursoricon { width: 1em; height: 1em; display: block; flex: none; }
.card--feature { grid-row: 1 / span 2; }
.card__media { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; } /* fills card height → bottoms align; clips the Ken-Burns scale */
/* equal body height across cards so the feature image bottom lines up with the
   bottom-right card's image bottom */
.card__body { padding: 22px 22px 26px; display: flex; flex-direction: column; gap: 12px; flex: none; min-height: 150px; }
.card__tag { font-size: 11px; letter-spacing: .14em; color: var(--accent); }
.card__title { font-weight: 300; font-size: clamp(18px, 1.6vw, 26px); line-height: 1.25; }
.card--feature .card__title { font-size: clamp(17px, 1.6vw, 24px); }
.card__date { font-size: 11px; letter-spacing: .1em; color: var(--muted-2); margin-top: auto; }

/* ============================================================
   CONTACT / FOOTER
   ============================================================ */
.footer { position: relative; padding: 14svh var(--edge) 40px; overflow: hidden; }

/* two columns: Contact heading LEFT · everything else (big email + rows) RIGHT */
.footer__top { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gutter); align-items: start; margin-bottom: 18svh; }
.footer__right { display: flex; flex-direction: column; align-items: flex-start; gap: clamp(28px, 4svh, 44px); }
.footer__heading { font-weight: 300; font-size: clamp(22px, 2.4vw, 36px); letter-spacing: -.02em; }
.footer__hello { display: flex; flex-direction: column; gap: 14px; }
/* the one allowed inflation on this page — display-size, still LIGHT (wt300) */
.footer__email {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(28px, 3.4vw, 44px); letter-spacing: -.02em; line-height: 1.1;
  color: #fff; text-decoration: none; position: relative;
}
/* same underline-draw as the nav links */
.footer__email::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-med) var(--ease);
}
.footer__email:hover::after { transform: scaleX(1); }
.footer__rows { display: flex; flex-direction: column; gap: 18px; width: 100%; }
.footer__row { display: flex; gap: 18px; align-items: baseline; border-top: 1px solid rgba(255,255,255,.12); padding-top: 14px; }
.footer__rowlabel { font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); flex: none; width: 168px; }
.footer__rowval { text-decoration: none; font-size: 16px; }
.footer__channels { display: flex; flex-wrap: wrap; gap: 16px; }
.footer__channels a { text-decoration: none; opacity: .8; transition: opacity var(--t-fast); }
.footer__channels a:hover { opacity: 1; }

.footer__bottom {
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  font-size: 11px; color: var(--muted); letter-spacing: .04em;
  border-top: 1px solid rgba(255,255,255,.12); padding-top: 20px;
}

/* ============================================================
   POLISH — intro reveal, hover micro-interactions
   ============================================================ */
/* page-load curtain: covers on first paint, then LIFTS away — same scaleY/origin-top/
   easeOutQuint motion as the route wipe (.wipe.is-lift), so first paint and route
   changes share one language. Pure CSS = robust (no JS dependency). */
.intro-overlay {
  position: fixed; inset: 0; z-index: 300; pointer-events: none;
  background: var(--bg);
  transform-origin: top; will-change: transform;
  animation: introCurtain .9s var(--ease-out) forwards;
}
@keyframes introCurtain {
  0%, 22% { transform: scaleY(1); visibility: visible; } /* ~.2s hold, then ~.7s lift */
  100%    { transform: scaleY(0); visibility: hidden; }
}
/* hero greeting rises in as the curtain clears (transform only — opacity is
   owned by the scroll-scrub in initHeroExit, so they never fight) */
.hero__copy { animation: introRise 1.15s var(--ease) .15s both; }
@keyframes introRise {
  from { transform: translateY(34px); }
  to   { transform: translateY(0); }
}
/* nav settles in behind the curtain — wordmark first, links just after (the lift,
   origin top, reveals the nav LAST, so delays start near the curtain's end and the
   nav lands slightly after the hero copy). Animates the CHILDREN (opacity/transform
   only): the difference blend lives on .nav itself and the bar height stays 34px.
   `backwards` fill only — once the entrance ends, the collapse choreography's
   transitions (max-width/translateX on these same elements) own the props again. */
.nav__word  { animation: navIn .5s var(--ease-out) .7s backwards; }
.nav__links { animation: navIn .5s var(--ease-out) .85s backwards; }
@keyframes navIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* nav links: amber underline draws in on hover */
.nav__links a { position: relative; }
.nav__links a::after {
  content: ""; position: absolute; left: 0; bottom: 1px; height: 1px; width: 100%;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-med) var(--ease);
}
.nav__links a:hover::after { transform: scaleX(1); }


/* keyboard focus — visible on the near-black bg; :focus-visible only (no mouse rings) */
:focus-visible { outline: 1.5px solid var(--accent); outline-offset: 3px; }
.nav__menu a:focus-visible { outline-offset: -2px; }

/* skip link — off-screen until keyboard-focused */
.skiplink { position: fixed; top: 10px; left: 10px; z-index: 400;
  padding: 8px 14px; background: var(--bg-2); color: var(--fg);
  border-radius: var(--radius-sm); font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; text-decoration: none;
  transform: translateY(-200%); transition: transform var(--t-fast) ease; }
.skiplink:focus-visible { transform: none; }

@media (max-width: 900px) {
  .footer__top { grid-template-columns: 1fr; gap: 28px; }
  .news__grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 768px) {
  .nav__links { display: none; }
  /* the collapsed shell widens on small screens so the page label + toggle fit */
  .nav.is-scrolled .nav__shell { width: 60vw; min-width: 200px; }

  .work__inner { grid-template-columns: 1fr; }
  /* single column: rail + panels stack in document order (don't keep the desktop col spans,
     which collapse the rail to a 0-width track and overlap the panels) */
  .work__rail, .work__panels { grid-column: 1 / -1; }
  .work__rail { position: static; height: auto; padding: 8svh 0 0; }
  .work__meta { min-height: 0; }
  .work__item { position: static; opacity: 1; transform: none; pointer-events: auto; margin-bottom: 1.5rem; }
  /* on mobile, show each title above its panel instead of cross-fade */
  .work__item:not(:first-child) { display: none; }
  .work__item .work__title, .work__item .work__desc, .work__item .work__foot { display: none; }
  .work__panels { padding: 4svh 0; gap: 5svh; }
  /* the desktop sticky-centring pull (margin-top: calc(4svh - --panel-pad), ~-300px
     at phone widths) doesn't belong here — the work section isn't pinned on mobile,
     so the negative margin yanked the CTA + last panel down into the footer. */
  .work__cta { margin-top: 4svh; }
  /* panel becomes a stacked tile: media keeps the frame, caption flows below */
  .panel { aspect-ratio: auto; overflow: visible; background: none; border-radius: 0; text-decoration: none; }
  .panel__media { position: relative; inset: auto; aspect-ratio: 16 / 12;
    border-radius: var(--radius-lg); overflow: hidden; background: var(--bg-2); }
  .panel__cap { display: flex; flex-direction: column; gap: 4px; padding: 10px 2px 0; }
  .panel__captitle { font-family: var(--font-display); font-weight: 300; font-size: 20px;
    line-height: 1.15; letter-spacing: -.01em; color: rgba(255,255,255,.92); }
  .panel__capmeta { font-size: 11px; letter-spacing: .1em; color: var(--muted); text-transform: uppercase; }

  .news__grid { grid-template-columns: 1fr; }
  .card--feature { grid-row: auto; }
  .card--feature .card__media { aspect-ratio: 4/3; }

  .footer__row { flex-direction: column; gap: 6px; }
  .footer__rowlabel { width: auto; }
}

@media (max-width: 480px) {
  .hero__display { font-size: clamp(40px, 14vw, 80px); }
}

/* ============================================================
   HEADLINE TYPEFACE — Geist (light grotesque)
   ============================================================ */
.work__title,
.footer__heading,
.card__title {
  font-family: var(--font-display);
  font-weight: 300; /* light grotesque headings */
  letter-spacing: -0.02em;
}

/* ============================================================
   REDUCED MOTION
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
  .intro-overlay { display: none !important; } /* no fade animation → must not linger */
  .hero__copy { transform: none !important; }
  .media::before { transform: none !important; }
  .reveal__w { opacity: 1 !important; }
  .mask__i { transform: none !important; }
}

/* ============================================================
   JSON-driven content — route view, index grid, detail, blocks, cover-wipe
   (added with content.js; tokens reused: --accent, --radius-lg/sm, Geist + Sligoil mono)
   ============================================================ */
.route[hidden]{ display:none; }
/* routes are a fixed full-viewport overlay that scrolls INTERNALLY (show() sets
   el.scrollTop=0). The body is locked behind it so the homepage doesn't bleed
   through or double-scroll; sticky rails pin against this container. */
.route{ position:fixed; inset:0; z-index:3; background:var(--bg); overflow-y:auto; overflow-x:clip; -webkit-overflow-scrolling:touch; }
body.is-routed{ overflow:hidden; }            /* lock homepage scroll behind a route */
body.is-routed main{ display:none; }

/* cover-wipe: a panel rises to cover (brisk ease-in-out), then lifts away with a
   soft easeOutQuint so the new page is revealed gently (the content then eases in) */
.wipe{ position:fixed; inset:0; z-index:60; background:var(--bg); transform:scaleY(0);
  transform-origin:bottom; pointer-events:none; will-change:transform; }
.wipe.is-cover{ transform:scaleY(1); transform-origin:bottom; transition:transform .4s cubic-bezier(.65,0,.35,1); }
.wipe.is-lift{ transform:scaleY(0); transform-origin:top; transition:transform .58s var(--ease-out); }
@media (prefers-reduced-motion: reduce){ .wipe{ display:none; } }

/* shared route chrome */
.route__inner{ max-width:1200px; margin:0 auto; padding:140px 6vw 12svh; }
.route__back{ display:inline-block; margin-bottom:3rem; }
.route__head{ display:flex; justify-content:space-between; align-items:baseline; margin-bottom:3rem; }

/* --- All Projects: same column setup as the homepage work section --------------
   Blank left column (1fr) holds a sticky "Work" title; the asymmetric tile grid is
   pushed into the right 60vw column. Same --gutter + 8px inter-image spacing as the
   homepage panels; bigger tiles. */
.windex{ display:grid; grid-template-columns:var(--rail) 1fr; gap:var(--gap);
  padding:110px var(--edge) 10svh; } /* rail (cols 1–4) + projects (cols 5–12) */
/* Work index only: 2-col rail → the tile grid starts one column further left (cols 3–12) */
.windex--work{ grid-template-columns:calc(2*var(--col) + var(--gap)) 1fr; }
/* title sits at the TOP of the rail and sticks there as the content scrolls */
.windex__rail{ position:sticky; top:120px; align-self:start; display:flex; flex-direction:column; gap:1.8rem; }
.windex__title{ font-family:var(--font-display); font-weight:300; color:var(--fg);
  font-size:clamp(28px,3.4vw,52px); letter-spacing:-.02em; }

/* filter sub-nav (vertical list in the rail) */
.filters{ display:flex; flex-direction:column; align-items:flex-start; gap:.3rem; }
.filter{ background:none; border:0; padding:3px 0; color:var(--muted); font:inherit;
  font-size:12px; letter-spacing:.12em; text-transform:uppercase; cursor:pointer;
  transition:color var(--t-fast) ease; }
.filter:hover{ color:var(--fg); }
.filter.is-active{ color:var(--fg); }
/* wireFilters() toggles [hidden] to filter the grid. .ptile/.tcard set their own
   display, which overrides the UA [hidden]{display:none} — so force it here. */
.ptile[hidden], .tcard[hidden]{ display:none !important; }

/* not-found: message column beside the "Lost?" rail title (body type + mono links) */
.missing{ max-width:var(--measure); padding-top:.6em; }
.missing__msg{ font-size:clamp(16px,1.4vw,20px); line-height:1.6; color:rgba(255,255,255,.86); }
.missing__links{ display:flex; gap:2.2rem; margin-top:2.4rem;
  font-size:12px; letter-spacing:.12em; text-transform:uppercase; }
.missing__links a{ text-decoration:none; }

/* Thoughts index — distinct from the projects masonry: latest = wide lead card,
   the rest a 3-up grid (same --gap spacing) */
.tgrid{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px; align-content:start; }
.tcard{ position:relative; display:flex; flex-direction:column; text-decoration:none;
  border-radius:var(--radius-lg); overflow:hidden; background:var(--bg-2); }
.tcard__media{ position:relative; aspect-ratio:16/10; overflow:hidden; }
.tcard__body{ padding:16px 18px 20px; display:flex; flex-direction:column; gap:9px; }
.tcard__tag{ color:var(--accent); font-size:11px; letter-spacing:.12em; }
.tcard__title{ font-family:var(--font-display); font-weight:400; font-size:clamp(15px,1.3vw,19px); color:#fff; line-height:1.2; }
.tcard__date{ color:var(--muted-2); font-size:11px; letter-spacing:.08em; }
.tcard--lead{ grid-column:1 / -1; flex-direction:row; min-height:clamp(380px, 46vw, 640px); }
.tcard--lead .tcard__media{ flex:1 1 60%; aspect-ratio:auto; }
.tcard--lead .tcard__body{ flex:1 1 40%; justify-content:center; gap:16px; padding:clamp(28px,4vw,64px); }
.tcard--lead .tcard__title{ font-weight:300; font-size:clamp(26px,2.8vw,44px); }

/* Contact page — a lead line, then THREE columns: each a mono label, a big value,
   and a preview image (Humblestorm = live screenshot; email/LinkedIn = accent tiles) */
.contactp__lead{ font-family:var(--font-display); font-weight:300; line-height:1.2;
  font-size:clamp(28px,3.4vw,52px); color:#fff; margin-bottom:clamp(3rem,7svh,6rem); max-width:24ch; }
.contactp__cols{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px; }   /* match the work/thoughts tile gap */
.contactp__col{ display:flex; flex-direction:column; align-items:flex-start; gap:1rem; }
.contactp__label{ color:var(--muted); font-size:11px; letter-spacing:.12em; text-transform:uppercase; }
.contactp__val{ font-family:var(--font-display); font-weight:400; color:#fff;
  font-size:clamp(18px,1.5vw,24px); text-decoration:none; transition:opacity var(--t-fast) ease; }
.contactp__val:hover{ opacity:.65; }
.contactp__media{ display:block; position:relative; width:100%; aspect-ratio:16/10; margin-top:.4rem;
  border-radius:var(--radius-lg); overflow:hidden; background:var(--bg-2); }
.contactp__media .media{ transition:transform var(--t-slow) var(--ease); }
.contactp__media .media--photo{ animation:none; transform:none; }   /* no Ken-Burns on contact previews */
.contactp__media:hover .media{ transform:scale(1.04); }

/* two EQUAL-WIDTH columns, 8px gap (matches the homepage panel gap). Tile HEIGHTS
   alternate (3 vs 2 rows) so the columns stay equal width but the seams stagger —
   a clean masonry, no aligned rows. grid-auto-flow:dense keeps it gapless. */
/* ratio-driven masonry: tiny row unit + JS sets each tile's row span from its ratio
   (content.js layoutWorkGrid), so a tile renders at its true shape and cover stops
   cropping content. Every tile is SINGLE-COLUMN — a full-width (2-col) tile below
   ragged columns punches a hole, so we don't use one; cinema is just the widest/
   shortest 1-col shape (2.35 scope). Single-column everywhere ⇒ dense packing can
   never leave an interior hole. */
.pgrid{ display:grid; grid-template-columns:repeat(2,1fr); gap:8px; grid-auto-flow:dense;
  grid-auto-rows:8px; }
.ptile{ position:relative; overflow:hidden; border-radius:var(--radius-lg); display:block;
  grid-column:span 1; }
/* media shrinks from the bottom on hover by animating HEIGHT (not a clip), so
   object-fit:cover keeps the content CENTRED in the shrinking slice — the video/
   poster re-centres as the card lifts, same idea as the hero re-centre. Rounded
   corners are preserved (border-radius on the media), and it's snappy/fast. */
.ptile .media{ position:absolute; top:0; left:0; right:0; bottom:auto; height:100%;
  border-radius:var(--radius-lg); overflow:hidden; animation:none; transform:scale(1.08);
  transition:height var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), filter var(--t-fast) var(--ease); }
/* caption = one row: project name left (bold), client right; both body-size Geist */
/* top padding = bottom + the 8px grid gap, so the text is visually centred between
   the image above and the next card below (which is 8px away) */
.ptile__cap{ position:absolute; left:0; right:0; bottom:0; padding:12px 0 4px; line-height:1.1;
  display:flex; flex-direction:row; align-items:baseline; justify-content:space-between; gap:14px; z-index:3;
  transform:translateY(100%); transition:transform var(--t-fast) var(--ease); }
/* single line, ellipsis if long; same body size as the homepage work copy (15px),
   title in bold, client regular weight (same size) */
.ptile__title{ font-family:var(--font-display); font-weight:600; font-size:15px; color:#fff;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; flex:1 1 auto; }
.ptile__client{ font-family:var(--font-display); font-weight:400; font-size:15px; color:rgba(255,255,255,.62);
  white-space:nowrap; flex:none; text-align:right; }
@media (pointer:fine){
  /* hover state is JS-driven (.is-hover/.has-hover, content.js syncTileHover) —
     NOT :hover, which browsers don't re-evaluate during scroll, so a tile scrolled
     under a stationary pointer would keep the old state until the scroll settles */
  /* hovering one tile darkens the others (like the homepage non-current panels) */
  .pgrid.has-hover .ptile:not(.is-hover) .media{ filter:brightness(.4); }
  /* hovered tile: caption reveals + the imagery zooms OUT (pulls back to neutral) */
  .ptile.is-hover .media{ height:calc(100% - 32px); transform:scale(1); }
  .ptile.is-hover .ptile__cap{ transform:translateY(0); }
  /* frosted VIEW pill (same control as the homepage selected-work panels) replaces
     the native cursor over the tiles; show/hide + position ride syncTileHover */
  .windex--work .ptile, .windex--work .ptile *{ cursor:none; }
}
@media (pointer:coarse){           /* no hover → captions always shown */
  .ptile .media{ height:calc(100% - 32px); }
  .ptile__cap{ transform:translateY(0); }
}
@media (max-width:768px){
  .windex{ grid-template-columns:1fr; padding:90px var(--gutter) 8svh; gap:3svh; }
  .windex__rail{ position:static; top:auto; align-self:auto; gap:1.2rem; }
  .filters{ flex-direction:row; flex-wrap:wrap; gap:.4rem 1.1rem; }
  .pgrid{ grid-template-columns:repeat(2,1fr); grid-auto-rows:8px; }
  .ptile,[class*="ptile--"]{ grid-column:span 2 !important; }  /* full-width; row span from JS (mobile ratio) */
  .tgrid{ grid-template-columns:1fr; }
  .tcard--lead{ flex-direction:column; min-height:0; }
  .tcard--lead .tcard__media{ aspect-ratio:16/10; }
  .contactp__cols{ grid-template-columns:1fr; gap:2.5rem; }
  .contactp__media{ max-width:420px; }
}

/* --- Thought detail (.tdetail): a centred editorial article, our style —
   kicker/meta · big light headline · contained hero · prose · prev/next at the foot --- */
.tdetail{ max-width:760px; margin:0 auto; padding:104px var(--edge) 88px; }
.tdetail .route__back{ display:inline-block; margin-bottom:clamp(2rem,4svh,3rem); }
.tdetail__meta{ color:var(--muted); font-size:12px; letter-spacing:.1em; text-transform:uppercase; margin:0 0 1.2rem; }
.tdetail__tag{ color:var(--accent); }
.tdetail__title{ font-family:var(--font-display); font-weight:300; font-size:clamp(30px,4vw,58px);
  line-height:1.08; letter-spacing:-.02em; color:#fff; margin:0 0 clamp(2rem,4.5svh,3.2rem); }
.tdetail__hero{ position:relative; aspect-ratio:16/9; border-radius:var(--radius-lg); overflow:hidden;
  background:var(--bg-2); margin-bottom:clamp(2.5rem,5svh,4rem); }
/* body blocks keep their default centred reading layout (max-width 760, margin auto) */
.tdetail__body .blk:first-child{ margin-top:0; }
/* prev / next at the foot */
.tdetail__nav{ display:grid; grid-template-columns:1fr 1fr; gap:1.5rem; margin-top:clamp(4rem,8svh,7rem);
  padding-top:2rem; border-top:1px solid rgba(255,255,255,.12); }
.tdetail__navlink{ display:flex; flex-direction:column; gap:.6rem; text-decoration:none; min-width:0; }
.tdetail__navlink--next{ text-align:right; align-items:flex-end; }
.tdetail__navlabel{ color:var(--muted); font-size:11px; letter-spacing:.12em; text-transform:uppercase; }
.tdetail__navtitle{ font-family:var(--font-display); font-weight:400; font-size:clamp(15px,1.4vw,19px);
  color:#fff; line-height:1.25; transition:opacity var(--t-fast) ease; }
.tdetail__navlink:hover .tdetail__navtitle{ opacity:.6; }

/* --- Project detail (.pdetail): sticky meta rail (cols 1–3) + wide cinematic
   media column (cols 5–12, the same 8-col span the homepage selected-work uses).
   The rail pins through the whole scroll while the video + body ride past. --- */
.pdetail{ padding:104px var(--edge) 40px; }   /* small bottom gap → no empty scroll-past */
.pdetail__grid{ display:grid; grid-template-columns:repeat(12,1fr); column-gap:var(--gap); }
/* rail is a viewport-tall flex column so the Next / ← back nav sits at the lower-left
   (margin-top:auto) while the credits + contents stay up top. Sticky → pinned for the
   whole scroll; the GRID (bounded by main content) caps the page height so you never
   scroll past the content on the left. */
.pdetail__rail{ grid-column:1 / 4; position:sticky; top:104px; align-self:start;
  display:flex; flex-direction:column; min-height:calc(100svh - 104px - 40px); }
.pdetail__main{ grid-column:5 / 13; min-width:0; }

/* client (with square logo box) sits ABOVE the title */
.pdetail__client{ display:flex; align-items:center; gap:.7rem; color:var(--fg); font-size:14px; margin:0 0 1.4rem; }
/* clickable client → their site; glow on hover, no underline */
.pdetail__client--link{ width:max-content; text-decoration:none; cursor:pointer; }
.pdetail__client--link .pdetail__clientlogo,
.pdetail__client--link .pdetail__clientname{ transition:filter var(--t-fast) ease, color var(--t-fast) ease; }
.pdetail__client--link:hover .pdetail__clientlogo{ filter:drop-shadow(0 0 9px rgba(255,255,255,.55)); }
.pdetail__client--link:hover .pdetail__clientname{ color:#fff; }
.pdetail__client--link:focus-visible{ outline:2px solid rgba(255,255,255,.5); outline-offset:4px; border-radius:6px; }
.pdetail__clientlogo{ position:relative; width:36px; height:36px; flex:none;
  border-radius:var(--radius-sm); overflow:hidden; }
.pdetail__title{ font-family:var(--font-display); font-weight:300; font-size:clamp(26px,2.3vw,40px);
  line-height:1.08; letter-spacing:-.02em; color:rgba(255,255,255,.92); margin:0 0 1.6rem; }
/* editorial headline atop the content column (the big line; rail keeps the short name) */
.pdetail__headline{ font-family:var(--font-display); font-weight:300; font-size:clamp(30px,3.4vw,56px);
  line-height:1.03; letter-spacing:-.025em; color:#fff; margin:0 0 clamp(1.4rem,2.4vw,2.4rem); padding-bottom:.04em; }
.pdetail__spec{ display:flex; flex-direction:column; border-top:1px solid rgba(255,255,255,.12); }
.pdetail__specrow{ display:flex; justify-content:space-between; align-items:baseline; gap:1rem;
  padding:.85rem 0; border-bottom:1px solid rgba(255,255,255,.12); }
.pdetail__specrow span{ flex:none; color:var(--muted); font-size:11px; letter-spacing:.1em; text-transform:uppercase; }
.pdetail__specrow b{ font-weight:400; color:#fff; font-size:13px; text-align:right; }
/* the "Project" row is mobile-only — on desktop the rail title already shows the name */
.pdetail__specrow--project{ display:none; }

/* contents menu — clickable chapter jump links */
.pdetail__contents{ margin-top:2rem; }
.pdetail__contentshead{ color:var(--muted); font-size:11px; letter-spacing:.12em; text-transform:uppercase; margin-bottom:1rem; }
.pdetail__clist{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:.7rem; }
.pdetail__clink{ display:flex; align-items:baseline; gap:.7rem; background:none; border:0; padding:0; width:100%;
  text-align:left; cursor:pointer; color:var(--muted); font-family:var(--font-display); font-weight:400;
  font-size:15px; line-height:1.25; transition:color var(--t-fast) ease; }
.pdetail__clink:hover,
.pdetail__clink.is-active{ color:#fff; } /* .is-active = scroll-spy's current chapter (wireContents) */
.pdetail__cnum{ flex:none; color:var(--accent); font-size:11px; }

/* prev/next as small looping-video tiles at the lower-left of the pinned rail:
   a rounded square playing the project's card loop under a frosted name plate
   that clears (reveals the video) on hover. */
.pdetail__nav{ margin-top:auto; padding-top:2.5rem; display:flex; gap:.8rem; align-items:flex-start; }
.pdetail__navtile{ flex:1 1 0; min-width:0; display:flex; flex-direction:column; gap:.55rem; text-decoration:none; }
.pdetail__navtile--next{ align-items:flex-end; text-align:right; }
.pdetail__navlabel{ color:var(--muted); font-size:11px; letter-spacing:.12em; text-transform:uppercase; transition:color var(--t-fast) ease; }
.pdetail__navtile:hover .pdetail__navlabel{ color:var(--fg); }
.pdetail__navthumb{ position:relative; width:100%; max-width:200px; aspect-ratio:2 / 1; border-radius:var(--radius-sm);
  overflow:hidden; background:var(--bg-2); }
.pdetail__navthumb .media{ position:absolute; inset:0; width:100%; height:100%; border-radius:0; }
.pdetail__navname{ position:absolute; inset:0; z-index:4; display:flex; align-items:flex-end;
  padding:.6rem .65rem; font-size:11px; letter-spacing:.05em; line-height:1.2; text-transform:uppercase;
  color:#fff; text-align:left; transition:opacity .4s ease; }
.pdetail__navtile:hover .pdetail__navname{ opacity:0; }
/* Fix the "blur flashes away" glitch: backdrop-filter over the autoplaying thumb
   video re-samples every frame, and the SVG url(#liquid-glass) displacement makes it
   drop/recompute on Chromium → visible flicker. Keep the frosted blur but drop the
   displacement here (imperceptible at this size), and pin the plate + video to their
   own GPU layers so the blur stays stable across scroll and the route fade-in. */
.pdetail__navthumb{ isolation:isolate; }
.pdetail__navthumb .media__video{ transform:translateZ(0); }
.pdetail__navname.is-glass{
  backdrop-filter:blur(var(--glass-blur)); -webkit-backdrop-filter:blur(var(--glass-blur));
  transform:translateZ(0); backface-visibility:hidden;
}

/* hero shape follows the project's card.ratio (--ar set inline); 16/10 is the fallback */
.pdetail__hero{ position:relative; aspect-ratio:var(--ar, 16/10); border-radius:var(--radius-lg);
  overflow:hidden; background:var(--bg-2); }
/* parallax layer (initRouteMotion drives it) — the transform lives here, never on the
   .media child: Ken-Burns owns the media's transform. Must be a positioned full-bleed
   box so the absolute .media fills it; no CSS transform — GSAP owns it whole. */
.pdetail__herobg{ position:absolute; inset:0; }
.pdetail__body{ margin-top:clamp(2rem,4svh,3.5rem); }
.pdetail__body > :first-child{ margin-top:0; }
.pdetail__body .blk-chapter{ scroll-margin-top:96px; }   /* clear the fixed nav on jump */
/* blocks flow left-aligned in the media column (kill their centring auto-margins);
   everything fills the full column width — text spans the column like the videos */
.pdetail__body .blk,.pdetail__body .blk-chapter,.pdetail__body .blk-media,
.pdetail__body .blk-embed,.pdetail__body .blk-gallery,.pdetail__body .blk-stats,
.pdetail__body .blk-quote{ margin-inline:0; max-width:100%; }

/* Project detail collapses to one column at ≤960 (above the 768 mobile breakpoint)
   so the rail — 3/12 cols ≈ 180px, which crams the spec values — never shows.
   The desktop rail order (client·title·specs·contents·prev/next) ABOVE the film
   reads oddly stacked, so flatten rail + main (display:contents) into one flex
   column and re-sequence: identity → headline → film → its details → contents →
   body → prev/next last. (Homepage work + work-index rails stay 2-col; they hold
   only a short title or filters, so they're fine narrow.) */
@media (max-width:960px){
  .pdetail__grid{ display:flex; flex-direction:column; gap:clamp(1.6rem,3.4svh,2.6rem); }
  .pdetail__rail, .pdetail__main{ display:contents; }
  /* the bare project name above the headline reads oddly on mobile; hide it and let
     the name appear as the first "Project" row in the details below the film instead */
  .pdetail__title    { display:none; }
  .pdetail__specrow--project{ display:flex; }
  .pdetail__client   { order:1; margin:0; }
  .pdetail__headline { order:3; margin:0; }
  .pdetail__hero     { order:4; }
  .pdetail__spec     { order:5; }
  .pdetail__contents { order:6; margin:0; }
  .pdetail__body     { order:7; margin:0; }
  .pdetail__nav      { order:8; margin:0; padding-top:2svh; }
}
@media (max-width:768px){
  .pdetail{ padding:84px var(--gutter) 10svh; }
  .pdetail__hero{ aspect-ratio:var(--ar-m, var(--ar, 16/10)); }  /* mobile ratio if the project sets one */
  .pdetail__rail .route__back{ margin-top:2rem; }
  .tdetail{ padding:84px var(--gutter) 12svh; }
  .tdetail__nav{ grid-template-columns:1fr; gap:2rem; }
  .tdetail__navlink--next{ text-align:left; align-items:flex-start; }
}

/* --- blocks --- */
.blk{ max-width:760px; margin:0 auto 2rem; }
.blk-p{ font-size:clamp(16px,1.4vw,20px); line-height:1.6; color:rgba(255,255,255,.86); }
.blk-lead{ font-size:clamp(20px,1.9vw,28px); font-weight:300; line-height:1.45;
  color:rgba(255,255,255,.95); margin-bottom:2.5rem; }
.blk-h{ font-family:var(--font-display); font-weight:400; font-size:clamp(20px,1.8vw,28px); margin:2.5rem auto 1rem; }
.blk-chapter{ max-width:760px; margin:5rem auto 1.5rem; }
.blk-chapter__num{ color:var(--accent); font-size:12px; }
.blk-chapter__kicker{ color:var(--accent); font-size:12px; margin-left:1rem; }
.blk-chapter__title{ font-family:var(--font-display); font-weight:300; font-size:clamp(22px,2.2vw,36px); overflow:hidden; }

/* ===== project-detail chapter sections: heading label left / body right, hairline-bounded.
   Heading is dropped to body font size so it reads as a quiet label like the meta rail.
   (Thoughts pages keep the old single-column .blk-chapter above.) ===== */
.pdetail__body .chsec{
  display:grid; grid-template-columns:1fr 3fr; column-gap:var(--gap);
  align-items:start; padding:clamp(2rem,3.5svh,3rem) 0;
  border-top:1px solid rgba(255,255,255,.12); scroll-margin-top:96px;
}
.pdetail__body .chsec:last-child{ border-bottom:1px solid rgba(255,255,255,.12); }
.chsec__head{ grid-column:1; display:flex; align-items:baseline; gap:.55em; flex-wrap:wrap; }
.chsec__num{ color:var(--accent); font-size:clamp(16px,1.4vw,20px); line-height:1.3; }
.chsec__title{ margin:0; font-family:var(--font-display); font-weight:400;
  font-size:clamp(16px,1.4vw,20px); line-height:1.3; overflow:hidden; }
.chsec__kicker{ color:var(--accent); font-size:12px; }
.chsec__body{ grid-column:2; }
.chsec__body > :first-child{ margin-top:0; }
/* media/gallery/stats/etc inside a chapter break full-width below the text row */
.pdetail__body .chsec > .blk-media,
.pdetail__body .chsec > .blk-embed,
.pdetail__body .chsec > .blk-gallery,
.pdetail__body .chsec > .blk-stats,
.pdetail__body .chsec > .blk-audio,
.pdetail__body .chsec > .blk-quote,
.pdetail__body .chsec > .blk-logo{ grid-column:1 / -1; max-width:100%; margin-top:clamp(1.4rem,2.6svh,2.2rem); }
@media (max-width:768px){
  .pdetail__body .chsec{ grid-template-columns:1fr; }
  .chsec__head{ grid-column:1; margin-bottom:1rem; }
  .chsec__body{ grid-column:1; }
}

.blk-media{ max-width:1000px; }
/* frame is the fixed-size clipper; the .media inside scales (Ken-Burns) within it → no bleed */
.blk-media__frame{ position:relative; aspect-ratio:16/9; border-radius:var(--radius-lg); overflow:hidden; }
/* ===== custom branded film player (.blk-media--player) — no native controls:
   our mono control bar (ambilight glow removed 2026-06-13). ===== */
.blk-media--player{ position:relative; isolation:isolate; }
.blk-media--player .blk-media__frame{ aspect-ratio:auto; background:#000; position:relative; z-index:1; }
.blk-video{ display:block; width:100%; height:auto; }

/* ===== watch affordance: a persistent WATCH FILM CTA + the PLAY follow-cursor =====
   Two frosted pills (.is-glass = plain backdrop-blur). The .vplay-core CTA sits centred & VISIBLE
   on the poster (base .hero__play centres it) — the click target; it stays put. On fine pointers
   the native cursor is hidden over the film and the .vplay PLAY pill trails the pointer on top.
   Follow rules are scoped to .vplay so they never reposition the centred CTA. */

/* persistent centred WATCH FILM CTA — always visible; fades out while playing (control bar governs) */
.blk-media__frame .vplay-core{ z-index:5; transition:background var(--t-fast) ease, transform var(--t-med) var(--ease-out); }
/* hover (the follow pill hides here — see initBlockVideos): the native cursor is hidden too, and
   the button REACTS — grows + brightens — so only the button shows, clearly armed to play. */
.blk-media__frame.vplayer--cursor .vplay-core{ cursor:none; }
.blk-media__frame .vplay-core:hover{ transform:translate(-50%,-50%) scale(1.1); background:hsl(240 6% 14% / 0.62); }
.blk-media--player.is-playing .vplay-core{ opacity:0; pointer-events:none; }
.vplay-core:focus-visible{ outline:2px solid #fff; outline-offset:4px; }

/* PLAY follow-cursor (fine pointers): native cursor hidden over the film; the pill trails it. */
.blk-media__frame.vplayer--cursor,
.blk-media__frame.vplayer--cursor .blk-video{ cursor:none; }
.blk-media__frame .vplay{ pointer-events:none; opacity:0; }
.blk-media__frame.vplayer--cursor .vplay{ position:fixed; left:0; top:0; z-index:30; }
.blk-media__frame.vplayer--cursor .vplay.is-follow{ opacity:1; }
.blk-media--player.is-playing .vplay{ opacity:0; }
.vplay .vplay__pause{ display:none; }
.vplay.is-playing .vplay__play{ display:none; }
.vplay.is-playing .vplay__pause{ display:block; }

/* control bar — mono, branded; fades in on activity / while paused */
.vp__ctrl{ position:absolute; left:0; right:0; bottom:0; z-index:7;
  display:flex; align-items:center; gap:12px; padding:18px 16px 12px;
  background:linear-gradient(to top, rgba(0,0,0,.62), rgba(0,0,0,0));
  font-size:11px; letter-spacing:.08em; color:#fff;
  opacity:0; transform:translateY(8px); transition:opacity .25s ease, transform .25s ease; }
.blk-media--player:not(.is-playing) .vp__ctrl,
.blk-media--player.vp--active .vp__ctrl{ opacity:1; transform:none; }
.vp__b{ background:none; border:0; color:#fff; padding:0; cursor:pointer; display:inline-flex; flex:none; opacity:.85; transition:opacity .15s ease; }
.vp__b:hover{ opacity:1; }
.vp__i{ width:15px; height:15px; display:block; }
.vp__i-pause{ display:none; }
.blk-media--player.is-playing .vp__i-play{ display:none; }
.blk-media--player.is-playing .vp__i-pause{ display:block; }
.vp__i-muted{ display:none; }
.blk-media--player.is-muted .vp__i-vol{ display:none; }
.blk-media--player.is-muted .vp__i-muted{ display:block; }
.vp__time{ flex:none; font-variant-numeric:tabular-nums; color:rgba(255,255,255,.8); }
.vp__scrub{ flex:1 1 auto; padding:8px 0; cursor:pointer; touch-action:none; }
.vp__track{ position:relative; height:3px; background:rgba(255,255,255,.22); border-radius:2px; }
.vp__buf{ position:absolute; inset:0 auto 0 0; width:0; background:rgba(255,255,255,.32); border-radius:2px; }
.vp__fill{ position:absolute; inset:0 auto 0 0; width:0; background:#fff; border-radius:2px; }
.vp__knob{ position:absolute; top:50%; left:0; width:11px; height:11px; margin-left:-5.5px; border-radius:50%;
  background:#fff; transform:translateY(-50%) scale(0); transition:transform .15s ease; }
.vp__scrub:hover .vp__knob{ transform:translateY(-50%) scale(1); }
.vp__vol{ flex:none; width:62px; padding:8px 0; cursor:pointer; touch-action:none; }
.vp__voltrack{ position:relative; height:3px; background:rgba(255,255,255,.22); border-radius:2px; }
.vp__volfill{ position:absolute; inset:0 auto 0 0; width:100%; background:#fff; border-radius:2px; }
/* keep a usable cursor over the bar even though the film body hides it */
.blk-media__frame.vplayer--cursor .vp__ctrl,
.blk-media__frame.vplayer--cursor .vp__ctrl *{ cursor:pointer; }
.blk-media__frame.vplayer--cursor .vp__time{ cursor:default; }

/* fullscreen: figure fills the screen, film centred */
.blk-media--player:fullscreen{ display:flex; align-items:center; justify-content:center;
  width:100vw; height:100svh; max-width:none; margin:0; background:#000; }
.blk-media--player:fullscreen .blk-media__frame{ width:100%; height:100%; display:flex; align-items:center; }
.blk-media--player:fullscreen .blk-video{ width:100%; height:100%; object-fit:contain; }
.blk-embed{ max-width:1000px; }
.blk-embed__frame{ position:relative; aspect-ratio:16/9; border-radius:var(--radius-lg); overflow:hidden; }
.blk-embed__frame iframe{ position:absolute; inset:0; width:100%; height:100%; border:0; }
/* Spotify is a player, not a video — compact fixed height, not 16:9 */
.blk-embed--spotify{ max-width:400px; }
.blk-embed--spotify .blk-embed__frame{ aspect-ratio:auto; height:152px; border-radius:12px; background:#121212; }
.blk-embed--spotify .blk-embed__frame iframe{ border-radius:12px; }
/* hairline-framed pull-quote: thin rules above + below, large light display text
   capped to a punchy measure, mono uppercase attribution in accent. Reads as an
   editorial moment and rhymes with the chapter dividers. */
.blk-quote{ max-width:100%; margin:clamp(1.6rem,3svh,2.6rem) 0; padding:clamp(1.9rem,4.2svh,3rem) 0;
  border-top:1px solid rgba(255,255,255,.12); border-bottom:1px solid rgba(255,255,255,.12); }
.blk-quote p{ font-family:var(--font-display); font-weight:300; font-size:clamp(24px,3vw,42px);
  line-height:1.22; letter-spacing:-.01em; max-width:22ch; color:rgba(255,255,255,.92); }
.blk-quote cite{ display:block; margin-top:1.3rem; font-style:normal;
  color:var(--accent); font-size:11px; letter-spacing:.14em; text-transform:uppercase; }
/* justified-rows gallery: JS (initGallery/layoutGallery) sets each item's width+height
   from its measured aspect ratio; CSS only flows the rows + clips each tile. */
.blk-gallery{ max-width:100%; display:flex; flex-wrap:wrap; gap:8px; }
.blk-gallery__item{ flex:0 0 auto; width:260px; height:173px; position:relative;
  overflow:hidden; border-radius:var(--radius-sm); }
.blk-gallery__item .media{ position:absolute; inset:0; width:100%; height:100%; border-radius:0; }
.blk-gallery__item img,.blk-gallery__item .media__video{ width:100%; height:100%; object-fit:cover; display:block; }
/* gallery items open in the lightbox (content.js initLightbox) → clickable + focusable */
.blk-gallery__item{ cursor:zoom-in; }
.blk-gallery__item:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
@media (hover:hover) and (pointer:fine){
  .blk-gallery__item::after{ content:"\2922"; position:absolute; right:8px; bottom:6px; z-index:4;
    font-size:13px; line-height:1; color:#fff; opacity:0; transform:translateY(4px);
    text-shadow:0 1px 4px rgba(0,0,0,.6); transition:opacity var(--t-fast) ease, transform var(--t-fast) ease; pointer-events:none; }
  .blk-gallery__item:hover::after{ opacity:.92; transform:translateY(0); }
}

/* ---- lightbox (enlarge gallery images / videos) -------------------------------
   Fixed overlay above the route (z 350 sits over route=3, nav=100, wipe=60). */
.lightbox{ position:fixed; inset:0; z-index:350; display:flex; align-items:center; justify-content:center;
  padding:clamp(16px,4vw,56px); background:rgba(8,8,8,.92); -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  opacity:0; visibility:hidden; transition:opacity var(--t-fast) ease, visibility 0s linear var(--t-fast); }
.lightbox.is-open{ opacity:1; visibility:visible; transition:opacity var(--t-fast) ease; }
body.is-lightboxed .route{ overflow:hidden; }
.lightbox__stage{ display:flex; align-items:center; justify-content:center; max-width:100%; max-height:100%; }
.lightbox__media{ display:block; max-width:min(1400px,92vw); max-height:88svh; width:auto; height:auto;
  object-fit:contain; border-radius:var(--radius-lg); box-shadow:0 30px 80px rgba(0,0,0,.6); background:#000;
  transform:scale(.985); transition:transform var(--t-fast) var(--ease-out); }
.lightbox.is-open .lightbox__media{ transform:scale(1); }
.lightbox__close{ position:absolute; top:clamp(12px,2vw,22px); right:clamp(12px,2vw,22px); z-index:2;
  background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.16); color:var(--fg);
  border-radius:999px; padding:8px 15px; font-size:11px; letter-spacing:.08em; cursor:pointer;
  transition:background var(--t-fast) ease; }
.lightbox__close:hover{ background:rgba(255,255,255,.18); }
.lightbox__nav{ position:absolute; top:50%; transform:translateY(-50%); z-index:2;
  width:48px; height:48px; display:flex; align-items:center; justify-content:center;
  background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.14); color:var(--fg);
  border-radius:999px; font-size:24px; line-height:1; cursor:pointer; transition:background var(--t-fast) ease; }
.lightbox__nav:hover{ background:rgba(255,255,255,.18); }
.lightbox__nav--prev{ left:clamp(10px,2vw,26px); }
.lightbox__nav--next{ right:clamp(10px,2vw,26px); }
.lightbox__nav[hidden]{ display:none; }
.lightbox__close:focus-visible,.lightbox__nav:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
@media (prefers-reduced-motion: reduce){
  .lightbox{ transition:opacity .001s; }
  .lightbox__media{ transition:none; transform:none; }
}
@media (max-width:768px){
  .lightbox__nav{ width:40px; height:40px; font-size:20px; }
  .lightbox__media{ max-width:94vw; max-height:78svh; }
}
.blk-stats{ max-width:1000px; display:flex; flex-wrap:wrap; gap:3rem; margin:3rem auto; }
.blk-stats__item{ display:flex; flex-direction:column; }
.blk-stats__v{ font-family:"Sligoil Micro", ui-monospace, monospace; font-weight:400; font-size:clamp(40px,6vw,84px); line-height:1; color:#fff; }
.blk-stats__l{ font-size:11px; letter-spacing:.06em; color:var(--accent); margin-top:.5rem; text-transform:uppercase; }
.blk-logo img{ max-height:48px; width:auto; opacity:.85; }
/* z-index:3 lifts the logo above the .media gradient(::before) + grain(::after) overlays */
.work__logoimg{ position:relative; z-index:3; width:100%; height:100%; object-fit:cover; }
/* client-logo tiles drop the .media gradient/grain, so a transparent favicon
   (e.g. Apollo) sits directly on the page background instead of showing the gradient */
.work__logo, .pdetail__clientlogo{ background:transparent; }
.work__logo::before, .work__logo::after,
.pdetail__clientlogo::before, .pdetail__clientlogo::after{ content:none; }

/* ---- audio block: scrub-line player ---- */
.blk-audio{position:relative;height:108px;margin:2.4rem 0}
.blk-audio__meta{position:absolute;top:0;left:0;display:flex;align-items:center;gap:11px;z-index:2}
.blk-audio__cover{width:44px;height:44px;border-radius:var(--radius-lg,6px);object-fit:cover;background:#222;display:block}
.blk-audio__txt{display:flex;flex-direction:column;line-height:1.25;min-width:0}
.blk-audio__artist{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:#7a7a7a}
.blk-audio__title{font-size:17px;font-weight:500;color:var(--accent,#ededed)}
.blk-audio__time{position:absolute;top:13px;right:0;z-index:2;font-size:12px;color:#8a8a8a;letter-spacing:.03em}
.blk-audio__play{position:absolute;left:0;bottom:10px;width:44px;height:44px;border-radius:var(--radius-lg,6px);border:none;background:var(--accent,#ededed);display:grid;place-items:center;cursor:pointer;z-index:2;transition:transform var(--t-fast)}
.blk-audio__play:hover{transform:scale(1.05)}
.blk-audio__cv{position:absolute;inset:0;width:100%;height:100%;cursor:ew-resize;z-index:1;touch-action:none}
.blk-audio__el{display:none}
@media (max-width:768px){ .blk-audio__title{font-size:15px} }
