/* =========================================================
   FLORENCE — R3 (2026-07-22)
   Rebuilt to the designer's Figma "Florence Redesign" R2C1
   frames: desktop HOME/WORK/DIRECTORS/CONTACT/SHOP (1920)
   + mobile (402). All state changes are ~300ms dissolves.
   Splash (logo-only landing) retained from R2 by request.
   ========================================================= */
/* =========================================================
   FLORENCE — Production Company
   Built to the Florence Identity Rebrand Style Guide.
   Brand tokens in :root. Avantt / Dazzed / Noe Text are the
   licensed brand fonts — when their web files are added to
   /assets/fonts they take over automatically; until then the
   closest free fallbacks (Hanken Grotesk / Fraunces) are used.
   ========================================================= */

@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,500;1,9..144,300;1,9..144,400&display=swap');

/* ============================================================
   LICENSED BRAND FONTS  (Avantt · Dazzed · Noe Text)
   Drop the licensed web files into /assets/fonts using the
   filenames below and they take over automatically. Until then
   each face falls back to Hanken Grotesk / Fraunces. See
   /assets/fonts/README.txt for the full file list.
   ============================================================ */

/* --- Avantt (display / headlines / UI) --- */
@font-face { font-family:"Avantt"; src:local("Avantt Regular"),local("Avantt"),
  url("../fonts/Avantt-Regular.woff2") format("woff2"),url("../fonts/Avantt-Regular.woff") format("woff");
  font-weight:400; font-style:normal; font-display:swap; }
@font-face { font-family:"Avantt"; src:local("Avantt Regular Italic"),
  url("../fonts/Avantt-RegularItalic.woff2") format("woff2"),url("../fonts/Avantt-RegularItalic.woff") format("woff");
  font-weight:400; font-style:italic; font-display:swap; }
@font-face { font-family:"Avantt"; src:local("Avantt Medium"),
  url("../fonts/Avantt-Medium.woff2") format("woff2"),url("../fonts/Avantt-Medium.woff") format("woff");
  font-weight:500; font-style:normal; font-display:swap; }
@font-face { font-family:"Avantt"; src:local("Avantt SemiBold"),
  url("../fonts/Avantt-SemiBold.woff2") format("woff2"),url("../fonts/Avantt-SemiBold.woff") format("woff");
  font-weight:600; font-style:normal; font-display:swap; }

/* --- Dazzed (subheads / labels) --- */
@font-face { font-family:"Dazzed"; src:local("Dazzed Medium"),
  url("../fonts/Dazzed-Medium.woff2") format("woff2"),url("../fonts/Dazzed-Medium.woff") format("woff");
  font-weight:500; font-style:normal; font-display:swap; }
@font-face { font-family:"Dazzed"; src:local("Dazzed SemiBold"),
  url("../fonts/Dazzed-SemiBold.woff2") format("woff2"),url("../fonts/Dazzed-SemiBold.woff") format("woff");
  font-weight:600; font-style:normal; font-display:swap; }
@font-face { font-family:"Dazzed"; src:local("Dazzed Bold"),
  url("../fonts/Dazzed-Bold.woff2") format("woff2"),url("../fonts/Dazzed-Bold.woff") format("woff");
  font-weight:700; font-style:normal; font-display:swap; }

/* --- Noe Text (editorial serif / body) --- */
/* Thin cut (Figma "Noe Text Thin Italic" — manifesto/statement). The licensed
   Thin file takes over automatically when dropped in /assets/fonts; until then
   the Light Italic file answers this slot so the weight is the lightest we own. */
@font-face { font-family:"Noe Text"; src:local("Noe Text Thin Italic"),
  url("../fonts/NoeText-ThinItalic.woff2") format("woff2"),
  url("../fonts/NoeText-LightItalic.woff2") format("woff2");
  font-weight:100 200; font-style:italic; font-display:swap; }
@font-face { font-family:"Noe Text"; src:local("Noe Text Light"),
  url("../fonts/NoeText-Light.woff2") format("woff2"),url("../fonts/NoeText-Light.woff") format("woff");
  font-weight:300; font-style:normal; font-display:swap; }
@font-face { font-family:"Noe Text"; src:local("Noe Text Light Italic"),
  url("../fonts/NoeText-LightItalic.woff2") format("woff2"),url("../fonts/NoeText-LightItalic.woff") format("woff");
  font-weight:300; font-style:italic; font-display:swap; }
@font-face { font-family:"Noe Text"; src:local("Noe Text Regular"),local("Noe Text"),
  url("../fonts/NoeText-Regular.woff2") format("woff2"),url("../fonts/NoeText-Regular.woff") format("woff");
  font-weight:400; font-style:normal; font-display:swap; }
@font-face { font-family:"Noe Text"; src:local("Noe Text Regular Italic"),
  url("../fonts/NoeText-RegularItalic.woff2") format("woff2"),url("../fonts/NoeText-RegularItalic.woff") format("woff");
  font-weight:400; font-style:italic; font-display:swap; }
@font-face { font-family:"Noe Text"; src:local("Noe Text Bold"),
  url("../fonts/NoeText-Bold.woff2") format("woff2"),url("../fonts/NoeText-Bold.woff") format("woff");
  font-weight:700; font-style:normal; font-display:swap; }

:root {
  /* Figma color styles */
  --bg:      #E7E7D8;   /* 40% SAND — page ground */
  --ink:     #333333;   /* 80% BLACK */
  --mut:     #666666;   /* 60% BLACK — idle nav/labels */
  --marine:  #445193;
  --slate:   #b0c1c6;
  --sand:    #c3c4a1;
  --white:   #ffffff;
  --dark:    #212227;   /* overlay / shop ground */
  --paper:   #E7E7D8;   /* legacy alias (splash, admin bits) */

  --sans:  "Avantt", "Hanken Grotesk", "Helvetica Neue", Arial, sans-serif;
  --cond:  "Dazzed", "Hanken Grotesk", "Helvetica Neue", Arial, sans-serif;
  --serif: "Noe Text", "Fraunces", Georgia, "Times New Roman", serif;

  /* Figma 1920 metrics, fluid below that */
  --mx:     clamp(20px, 4.688vw, 90px);     /* side margin (90) */
  --headh:  clamp(96px, 9.22vw, 177px);     /* header incl. rule (176+1) */
  --railgap:clamp(16px, 1.51vw, 29px);      /* rule → content (205-176) */
  --gap:    clamp(12px, 1.042vw, 20px);     /* grid gap (20) */
  --ease:   cubic-bezier(0.4, 0, 0.2, 1);
  --fade:   .3s;                            /* the dissolve */

  /* THE VIEWPORT WE CAN ACTUALLY SEE.
     On a phone `100vh` is a lie by design: it is the height the page would
     have with the browser's own chrome hidden, and it does not change when
     the address bar slides back in. So anything sized in vh keeps its tall
     value while the visible band gets shorter, and the bottom of it — the
     transport, the caption, the ✕ — ends up underneath the address bar.
     Turned sideways there is so little height to begin with that the whole
     player goes out of reach, and with the page scroll locked behind the
     lightbox there is no way to scroll the bar away again: stuck.
     `dvh` is the same measurement, honest — it tracks the chrome as it comes
     and goes. Everything that used to say 100vh says var(--vvh) now. The
     px fallback below is written by app.js for browsers without dvh. */
  --vvh: 100vh;
}
@supports (height: 100dvh) { :root { --vvh: 100dvh; } }

* { box-sizing: border-box; margin: 0; padding: 0; }
/* the scrollbar gutter is ALWAYS reserved — page-height changes (short
   director grids vs long bios) can never nudge the content sideways */
/* overflow-x lives on <html>, NEVER on <body>. On body it silently turns body
   into a scroll container, which (a) kills `position: sticky` on the rail in
   every browser and (b) demotes `position: fixed` on the header to absolute in
   iOS Safari — the header "comes unglued" on a real phone while emulators look
   fine. On <html> the overflow propagates to the viewport instead, so the
   sideways clamp survives and fixed/sticky keep working. */
html { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  overflow-y: scroll; overflow-x: hidden; scrollbar-gutter: stable; }
body { font-family: var(--serif); background: var(--bg); color: var(--ink);
  line-height: 1.45;
  transition: background-color .5s var(--ease), color .5s var(--ease); }
/* the shop's dither layer lives permanently at opacity 0 so entering /
   leaving the dark theme DISSOLVES instead of snapping */
body::before { content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image: url("../img/texture/dither20.png"); background-size: 640px;
  mix-blend-mode: screen; opacity: 0; transition: opacity .5s var(--ease); }
a { color: inherit; text-decoration: none; }
img, video, svg { display: block; max-width: 100%; }
button { font: inherit; background: none; border: 0; cursor: pointer; color: inherit; }
::selection { background: var(--marine); color: var(--bg); }

/* type utilities */
.caps  { font-family: var(--cond); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.serif-italic { font-family: var(--serif); font-style: italic; font-weight: 300; }

/* page dissolve-in — content only; the header stays put across pages.
   The post-splash fade is intentionally a touch slower/softer than the
   between-pages fade so the site eases in after the logo clears. */
main { animation: pageIn .65s var(--ease) both; }
@keyframes pageIn { from { opacity: 0; } to { opacity: 1; } }
/* grid pages: the THUMBNAILS never fade between sections — they land
   instantly and go straight into the ring-rotation transition; only the
   rail copy animates (each block unfolds on its own) */
main.layout { animation: none; }
/* while the splash holds the screen, the page waits unrendered — dismissing
   the splash releases these animations so the site truly fades IN (slower) */
body.splash-lock main { animation: none; opacity: 0; }
body.splash-lock .rail > * { animation: none; opacity: 0; }
body.splash-dismissed main { animation: pageIn 1.35s var(--ease) both; }
body.splash-dismissed .rail > * { animation-duration: 1.15s; }
/* home: hold the manifesto hidden until the mosaic ring finishes rotating */
body.hold-rail .rail > * { animation: none !important; opacity: 0 !important; }
@media (prefers-reduced-motion: reduce) { main, body.splash-lock main { animation: none; opacity: 1; } }

/* =========================================================
   HEADER — wordmark left, nav baseline-aligned, dark rule
   ========================================================= */
.site-head { position: fixed; top: 0; left: 0; right: 0; z-index: 90;
  height: var(--headh); background: var(--bg);
  transition: background-color .5s var(--ease);
  display: flex; align-items: flex-end; gap: clamp(40px, 7.8vw, 150px);
  padding: 0 var(--mx) var(--railgap); }
/* Figma: 2px 80%-black rule, x=90 → 1830 (inset to the margins) */
.site-head::after { content: ""; position: absolute; left: var(--mx); right: var(--mx);
  bottom: 0; height: 2px; background: var(--ink); transition: background-color .5s var(--ease); }
.site-head__logo { display: block; position: relative; }
.site-head__logo img { width: clamp(180px, 17.07vw, 328px); height: auto; }
/* the marine wordmark sits hidden behind the dark one. It no longer traces
   on hover (removed by request) — it is driven only by the contact page's
   landing sweep, which scripts clip-path directly. */
.site-head__logo-trace { position: absolute; inset: 0; pointer-events: none;
  clip-path: inset(0 100% 0 0);
  transition: clip-path .42s cubic-bezier(.5,0,.2,1); }
@media (prefers-reduced-motion: reduce) { .site-head__logo-trace { transition: none; } }
body.shop-dark .site-head__logo-trace { filter: none; }
.nav-links { display: flex; align-items: flex-end; gap: clamp(28px, 3.23vw, 62px); }
/* Figma nav bar: 358×11 at x=478 — starts on the GRID column (rail 18.84% +
   3.58% gutter of the content width) and bottom-aligns with the logo, so the
   item baselines sit on the same line as the wordmark, 29px above the rule. */
/* Only from 1200 up. The 20.57% column is a percentage of a 1920 frame: the
   four items need 197px of type, so the column stops fitting them somewhere
   around 1100 and the words butt together. Below 1200 the menu drops onto its
   own line under the wordmark instead (see the stacked block further down).
   min-width: max-content is the belt to that braces — between 1200 and ~1350
   the column is still narrower than the type wants, so it grows to fit and the
   18px floor keeps a readable gap. Above ~1350 the column is wider than
   max-content, min-width stops mattering, and space-between spreads the items
   exactly as Figma draws them (52.6px gaps at 1920). */
@media (min-width: 1200px) {
  .nav-links { position: absolute; bottom: var(--railgap);
    left: calc(var(--mx) + (100% - 2 * var(--mx)) * .2242);
    width: calc((100% - 2 * var(--mx)) * .2057);
    min-width: max-content;
    justify-content: space-between; gap: 18px; }
}
.nav-links a { font-family: var(--cond); font-weight: 600; font-size: 10px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--mut);
  line-height: 1.1; transition: color var(--fade) var(--ease); }
.nav-links a:hover { color: var(--marine); }        /* hover highlights blue */
.nav-links a.active { color: var(--marine); }        /* current page stays blue */
.nav-toggle { display: none; }

/* =========================================================
   LAYOUT — fixed left rail (18.84%), scrolling grid right
   ========================================================= */
.layout { display: grid; grid-template-columns: 18.84% 1fr;
  column-gap: 3.58%; align-items: start;
  padding: calc(var(--headh) + var(--railgap)) var(--mx) clamp(40px, 4vw, 90px); }
.rail { position: sticky; top: calc(var(--headh) + var(--railgap)); }

/* rail: home manifesto — Figma Callout: Noe Text THIN Italic 40/50 */
.rail__manifesto { font-family: var(--serif); font-style: italic; font-weight: 200;
  font-size: clamp(21px, 2.084vw, 40px); line-height: 1.25; color: var(--ink); }

/* rail: caps lists (work categories · director roster) — 15px rows, 27px pitch */
.rail-list { list-style: none; }
.rail-list li { display: block; }
.rail-list a, .rail-list button.chip { display: inline-block; font-family: var(--cond); font-weight: 600;
  font-size: clamp(13px, .782vw, 15px); letter-spacing: .04em; text-transform: uppercase;
  color: var(--mut); line-height: 1; padding: 0; margin: 0 0 clamp(8px, .53vw, 10px);
  text-align: left; transition: color var(--fade) var(--ease); }
.rail-list a:hover, .rail-list button.chip:hover { color: var(--marine); }
.rail-list a.active, .rail-list button.chip.active { color: var(--marine); }

/* rail: selected state — H1 20 marine + Body 2 15/21.7 */
.rail h1, .rail__title { font-family: var(--cond); font-weight: 600;
  font-size: clamp(16px, 1.042vw, 20px); letter-spacing: .04em; text-transform: uppercase;
  color: var(--marine); line-height: 1; margin: 0 0 clamp(14px, 1.15vw, 22px); }
/* WORK rail: the three categories stay uniform — active is simply marine */
#workSelTitle { display: none; }
#workCats { margin-bottom: clamp(14px, 1.15vw, 22px); }
.rail__body { font-family: var(--serif); font-weight: 400;
  font-size: clamp(13.5px, .782vw, 15px); line-height: 1.447; color: var(--ink);
  max-width: 42ch; }
.rail__body + .rail__body { margin-top: .9em; }
.rail__socials { display: flex; gap: clamp(14px, 1.05vw, 20px); margin-top: clamp(14px, 1.15vw, 22px); }
.rail__socials a { color: var(--marine); }
.rail__socials svg { width: clamp(19px, 1.18vw, 22.5px); height: auto; }
.rail .back { display:inline-block; font-family: var(--cond); font-weight:600; font-size:11px;
  letter-spacing:.06em; text-transform:uppercase; color: var(--mut); margin-bottom: clamp(10px,.8vw,16px); }
.rail .back:hover { color: var(--ink); }

/* rail content unfolds top-to-bottom — a soft reveal sweeps DOWN the text
   so the first line resolves first and each line follows in succession
   (a quick overall fade starts it; the mask does the unfolding) */
@keyframes railIn { from { opacity: 0; transform: translateY(-7px); } to { opacity: 1; transform: none; } }
@keyframes unfoldIn {
  0%   { opacity: 0; -webkit-mask-position: 0 100%; mask-position: 0 100%; }
  22%  { opacity: 1; }
  100% { opacity: 1; -webkit-mask-position: 0 0; mask-position: 0 0; }
}
.rail > * {
  -webkit-mask-image: linear-gradient(180deg, #000 45%, transparent 62%);
          mask-image: linear-gradient(180deg, #000 45%, transparent 62%);
  -webkit-mask-size: 100% 250%; mask-size: 100% 250%;
  animation: unfoldIn .9s var(--ease) both; }
.rail > *:nth-child(1) { animation-delay: .02s; }
.rail > *:nth-child(2) { animation-delay: .14s; }
.rail > *:nth-child(3) { animation-delay: .26s; }
.rail > *:nth-child(4) { animation-delay: .38s; }
.rail > *:nth-child(5) { animation-delay: .5s; }
.rail > *:nth-child(6) { animation-delay: .62s; }
.rail > *:nth-child(7) { animation-delay: .74s; }
.rail > *:nth-child(n+8) { animation-delay: .86s; }
/* landing manifesto: slower — the copy unfolds elegantly, line by line */
.rail > .rail__manifesto { animation-duration: 1.6s; }
body.splash-dismissed .rail__manifesto { animation-duration: 1.9s; }
/* the staggered roster does its own per-row fade — no block mask on the list */
.rail > .rail-list--stagger { -webkit-mask-image: none; mask-image: none; animation: none; }
@media (prefers-reduced-motion: reduce) { .rail > * { animation: none; -webkit-mask-image: none; mask-image: none; } }
/* director roster: each name fades in, in sequence, top → bottom
   (delays are set per-row by JS) */
.rail-list--stagger li { opacity: 0; animation: railIn .55s var(--ease) both; }
@media (prefers-reduced-motion: reduce) { .rail-list--stagger li { animation: none; opacity: 1; } }

/* =========================================================
   GRID — 3-up 16:9 tiles, marine duotone at rest
   ========================================================= */
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap);
  position: relative; align-content: start; overflow: hidden; }
.grid .tile { grid-column: auto; }         /* legacy span classes neutralised */
.tile { position: relative; overflow: hidden; aspect-ratio: 16 / 9;
  cursor: pointer; background: #3a4478; }
.tile.is-hidden { display: none; }
.tile__poster { position: absolute; inset: 0; background-size: cover; background-position: center;
  filter: grayscale(1) contrast(1.05) brightness(1.05);
  transition: filter var(--fade) var(--ease); }
.tile__reel { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.tile__reel iframe { border: 0; pointer-events: none; }
.tile__video, .duo__media, .duo__media iframe { pointer-events: none; }
.tile__video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1;
  filter: grayscale(1) contrast(1.05) brightness(1.05); transition: filter var(--fade) var(--ease); }
/* duotone: marine screen + color + paper darken over the grayscale still */
.tile__tint { position: absolute; inset: 0; z-index: 2; background: var(--marine);
  mix-blend-mode: screen; opacity: 1; transition: opacity var(--fade) var(--ease); pointer-events: none; }
.tile__tint::before { content: ""; position: absolute; inset: 0; background: var(--marine); mix-blend-mode: color; }
.tile__tint::after  { content: ""; position: absolute; inset: 0; background: #9AA3BB; mix-blend-mode: darken; }
.tile__tex { position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background-image: url("../img/texture/dither10.png"); background-size: 420px;
  mix-blend-mode: multiply; opacity: .28; transition: opacity var(--fade) var(--ease); }
.tile__shade { display: none; }
/* hover → full colour + caption */
.tile:hover .tile__poster, .tile:hover .tile__video { filter: none; }
.tile:hover .tile__tint, .tile:hover .tile__tex { opacity: 0; }
.tile__info { position: absolute; inset: 0; z-index: 3; display: flex;
  align-items: center; justify-content: center; text-align: center;
  opacity: 0; transition: opacity var(--fade) var(--ease); pointer-events: none; padding: 8px; }
.tile:hover .tile__info { opacity: 1; }
.tile__cap { font-family: var(--cond); font-weight: 600; font-size: clamp(9px, .58vw, 11px);
  letter-spacing: .08em; text-transform: uppercase; color: var(--white);
  text-shadow: 0 0 14px rgba(20,22,30,.35); }
/* director page: captions behave exactly as they do in the work section —
   hidden at rest, centred on hover. (The old always-on bottom captions with
   the left→right mask sweep are gone.) */
.tile:focus-visible { outline: 2px solid var(--marine); outline-offset: 3px; }

/* =========================================================
   CONTACT — statement, team + reps columns, image right
   ========================================================= */
.contact { display: grid; grid-template-columns: 18.84% 22.4% 1fr; column-gap: 3.58%;
  padding: calc(var(--headh) + var(--railgap)) var(--mx) clamp(40px, 4vw, 90px); }
.contact__statement { grid-column: 1 / 3; grid-row: 1; font-family: var(--serif); font-style: italic;
  font-weight: 200; font-size: clamp(21px, 2.084vw, 40px); line-height: 1.25;
  margin-bottom: clamp(26px, 2.9vw, 56px); min-width: 0; }
.contact__col--team { grid-column: 1; grid-row: 2; }
.contact__col--reps { grid-column: 2; grid-row: 2; }
/* contact: the copy under the header unfolds first (top → bottom, slower);
   THEN the contact columns unfold the same way, after it settles */
.contact > * {
  -webkit-mask-image: linear-gradient(180deg, #000 45%, transparent 62%);
          mask-image: linear-gradient(180deg, #000 45%, transparent 62%);
  -webkit-mask-size: 100% 250%; mask-size: 100% 250%;
  animation: unfoldIn .9s var(--ease) both; }
.contact__statement { animation-duration: 1.6s; animation-delay: .1s; }
/* the image lands immediately — no unfold, only the copy animates */
.contact__img { animation: none; -webkit-mask-image: none; mask-image: none; }
/* the columns begin while the statement is resolving its last lines, so the
   roll-out reads as ONE continuous top-to-bottom motion — no pause between */
.contact__col--team, .contact__col--reps { animation-duration: 1.4s; animation-delay: 1.05s; }
@media (prefers-reduced-motion: reduce) { .contact > * { animation: none; -webkit-mask-image: none; mask-image: none; } }
.contact__img { grid-column: 3; grid-row: 1 / 3; }
.contact .grp { margin-bottom: clamp(12px, 1.15vw, 22px); }
.contact .grp h4 { font-family: var(--cond); font-weight: 600; font-size: clamp(9px, .53vw, 10px);
  letter-spacing: .06em; text-transform: uppercase; color: var(--marine); margin-bottom: 2px; }
.contact .grp .nm { font-family: var(--cond); font-weight: 600; font-size: clamp(10px, .63vw, 12px);
  letter-spacing: .05em; text-transform: uppercase; color: var(--ink); }
.contact .grp .ln { font-family: var(--cond); font-weight: 500; font-size: clamp(9px, .58vw, 11px);
  letter-spacing: .03em; color: var(--ink); }
.contact .grp .ln a:hover { color: var(--marine); }
.contact__img .duo { position: relative; overflow: hidden; }
.contact__img img, .contact__img .duo__img { width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(1) contrast(1.05) brightness(1.05); }
.contact__img .duo__tint { position: absolute; inset: 0; background: var(--marine);
  mix-blend-mode: screen; }
.contact__img .duo__tint::before { content:""; position:absolute; inset:0; background: var(--marine); mix-blend-mode: color; }

/* =========================================================
   SHOP — dark dither ground, product cards, hover = colour
   ========================================================= */
body.shop-dark { background: var(--dark); color: #ddd; }
body.shop-dark::before { opacity: .18; }
/* Parked at the top of the page the header can be see-through — what shows
   through is the shop's own dithered ground, so the bar reads as part of it.
   The moment it starts FLOATING — scrolled, or with the menu dropped open —
   transparent stops working: it is a fixed bar with product photography
   running underneath it, so the nav words land on a book cover and the menu
   looks clipped. Give it solid ground in exactly those two states. */
body.shop-dark .site-head { background: transparent; }
body.shop-dark .site-head.scrolled,
body.shop-dark .site-head.menu-open { background: var(--dark); }
body.shop-dark .site-head::after { background: rgba(231,231,216,.9); }
/* the burger and the menu's closing hairline are inked for the cream theme;
   on the dark ground they need the light rule instead */
body.shop-dark .nav-toggle { color: rgba(231,231,216,.9); }
body.shop-dark .nav-links.open { border-bottom-color: rgba(231,231,216,.9); }
.site-head__logo img { transition: filter .5s var(--ease); }
body.shop-dark .site-head__logo img { filter: invert(1) brightness(1.6); }
body.shop-dark .nav-links a { color: #55565c; }
body.shop-dark .nav-links a:hover { color: #9a9ba1; }
body.shop-dark .nav-links a.active { color: var(--white);
  font-size: clamp(15px, 1.25vw, 24px); letter-spacing: .04em; }
.shoprow { position: relative; z-index: 1; display: grid;
  grid-template-columns: repeat(4, 1fr); gap: var(--gap);
  padding: calc(var(--headh) + var(--railgap)) var(--mx) clamp(28px, 2.6vw, 50px); }
.prod { cursor: pointer; }
.prod__thumb { position: relative; aspect-ratio: 413 / 600; background: #E9E9DC;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  transition: background var(--fade) var(--ease); }
.prod__thumb img { width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0) contrast(1.02); transition: filter var(--fade) var(--ease); }
.prod__thumb svg { width: 78%; height: auto;
  filter: saturate(0) contrast(1.02); transition: filter var(--fade) var(--ease); }
.prod:hover .prod__thumb { background: var(--white); }
.prod:hover .prod__thumb img, .prod:hover .prod__thumb svg { filter: none; }
.prod__meta { display: block; text-align: center; margin-top: clamp(10px, 1vw, 18px); }
.prod__name { font-family: var(--cond); font-weight: 600; font-size: clamp(11px, .782vw, 15px);
  letter-spacing: .08em; text-transform: uppercase; color: #8e8f95;
  transition: color var(--fade) var(--ease); }
.prod:hover .prod__name { color: var(--white); }
.prod__var, .prod__soon { display: none; }

/* =========================================================
   VIDEO OVERLAY — charcoal scrim inset from the page edge
   ========================================================= */
/* height, not inset-bottom: a fixed element is laid out against the tall
   viewport, so `inset: 0` alone hangs its bottom edge behind the address bar
   the moment that bar comes back. (top+height+bottom is over-constrained, so
   the bottom is the one the browser drops — which is what we want.) */
.lightbox { position: fixed; inset: 0; height: var(--vvh); z-index: 200; display: none; }
.lightbox.open { display: block; animation: pageIn var(--fade) var(--ease) both; }
.lightbox__overlay { position: absolute; inset: clamp(12px, 1.4vw, 27px);
  background: rgba(30, 31, 35, .97); cursor: pointer; }
.lightbox__panel { position: absolute; inset: clamp(12px, 1.4vw, 27px);
  display: flex; align-items: center; justify-content: center; pointer-events: none; }
.lightbox__panel > * { pointer-events: auto; }
.lightbox__frame { position: relative; width: min(70.4vw, 1350px);
  max-height: calc(var(--vvh) * .78);
  aspect-ratio: 16 / 9; background: #000; }
.lightbox__frame iframe, .lightbox__frame video { position: absolute; inset: 0;
  width: 100%; height: 100%; border: 0; }
/* shop: enlarged product view */
.lightbox__frame.is-img { width: auto; height: min(calc(var(--vvh) * .82), 900px); aspect-ratio: 413 / 600;
  max-height: none; background: transparent; }
.lightbox__frame.is-img img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; }
.lightbox__still { position: absolute; inset: 0; background-size: cover; background-position: center;
  transition: opacity .4s var(--ease); pointer-events: none; z-index: 2; }
.lightbox__still.gone { opacity: 0; }
/* the browser muted us — a standing invitation to tap, which does not fade
   out with the transport. pointer-events off so the tap lands on the hit
   layer that is listening for it, wherever on the film it falls. */
.lightbox__frame.sound-off::after { content: "TAP FOR SOUND"; position: absolute;
  left: 50%; bottom: clamp(64px, 8vh, 96px); transform: translateX(-50%);
  z-index: 5; pointer-events: none; white-space: nowrap;
  font-family: var(--cond); font-weight: 600; font-size: 10px;
  letter-spacing: .12em; color: var(--white);
  padding: 7px 13px 6px; border-radius: 999px;
  background: rgba(18,19,24,.62); backdrop-filter: blur(6px);
  animation: pageIn var(--fade) var(--ease) both; }
@media (max-width: 760px) { .lightbox__frame.sound-off::after { font-size: 11px; } }
/* transparent hit layer over the (chromeless) player — click = play/pause,
   mouse movement wakes the custom controls */
.lightbox__hit { position: absolute; inset: 0; z-index: 1; cursor: pointer; }
/* --lbui scales every piece of player furniture from one number. Desktop
   runs at 1; a phone runs at 1.1, because a thumb is not a cursor. */
.lightbox { --lbui: 1; }
/* z-index, because the ✕ is a SIBLING of the frame and comes BEFORE it in
   the markup — so without one the film paints straight over it. On a desktop
   the frame is inset and they never overlap; sideways on a phone the film is
   full-bleed and the ✕ sits on the picture, which is where Jerad found it
   buried. 7 puts it with the full-screen ✕, above the transport's 4. */
.lightbox__close { position: absolute; z-index: 7;
  top: clamp(16px, 2vw, 40px); right: clamp(16px, 2.2vw, 44px);
  display: flex; align-items: center; gap: .85em;
  font-family: var(--cond); font-weight: 600; font-size: calc(10px * var(--lbui));
  letter-spacing: .1em; text-transform: uppercase; color: var(--white); }
.lightbox__x { line-height: 1; }
.lightbox__cap { position: absolute; left: clamp(16px, 2.2vw, 44px); bottom: clamp(16px, 2vw, 40px);
  color: var(--white); max-width: 60%; }
/* name + title share one line to protect the border space */
.lightbox__cap > div { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.lightbox__title { display: block; font-family: var(--cond); font-weight: 600; font-size: 11px;
  letter-spacing: .1em; text-transform: uppercase; white-space: nowrap; }
.lightbox__sub { display: block; font-family: var(--serif); font-style: italic; font-weight: 300;
  font-size: 15px; margin-top: 0; text-transform: none; letter-spacing: 0; }
.lightbox__desc { display: none; }
.lightbox__fs { position: absolute; right: clamp(16px, 2.2vw, 44px); bottom: clamp(16px, 2vw, 40px);
  font-family: var(--cond); font-weight: 600; font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--white); display: flex; gap: 8px; align-items: center; }
.lightbox__fs svg { width: 12px; height: 12px; }
/* =========================================================
   CUSTOM PLAYER CONTROLS — clean overlays, no platform chrome.
   The bar fades in on mouse movement (.ui-on), holds ~1.6s,
   then fades out until the mouse moves again.
   ========================================================= */
.lightbox__ctrl { position: absolute; left: 0; right: 0; bottom: 0; z-index: 4;
  display: flex; align-items: center; gap: calc(clamp(10px, .9vw, 16px) * var(--lbui));
  padding: calc(30px * var(--lbui)) calc(clamp(14px, 1.1vw, 20px) * var(--lbui)) calc(13px * var(--lbui));
  background: linear-gradient(to top, rgba(18,19,24,.62), rgba(18,19,24,0));
  color: var(--white); opacity: 0; pointer-events: auto;
  transition: opacity .35s var(--ease); }
.lightbox__frame.ui-on .lightbox__ctrl { opacity: 1; }
/* the bar answers the cursor the instant it arrives, whether or not the
   idle timer had already faded it out — otherwise a click aimed at the
   timeline falls through and pauses the film instead of seeking */
.lightbox__ctrl:hover { opacity: 1; }
.lightbox__play { display: flex; align-items: center; justify-content: center;
  width: calc(24px * var(--lbui)); height: calc(24px * var(--lbui)); color: var(--white); flex: none; }
.lightbox__play svg { width: calc(13px * var(--lbui)); height: calc(13px * var(--lbui)); fill: currentColor; }
/* back / next through the films — quieter than play, so the eye still
   lands on the middle button first */
.lightbox__step { display: flex; align-items: center; justify-content: center;
  width: calc(22px * var(--lbui)); height: calc(24px * var(--lbui)); color: var(--white); flex: none; opacity: .7;
  transition: opacity var(--fade) var(--ease); }
.lightbox__step svg { width: calc(12px * var(--lbui)); height: calc(12px * var(--lbui)); fill: currentColor; }
.lightbox__step:hover { opacity: 1; }
.lightbox__step.is-off { opacity: .25; pointer-events: none; }
.lightbox__time { font-family: var(--cond); font-weight: 600; font-size: calc(10px * var(--lbui));
  letter-spacing: .08em; opacity: .85; min-width: calc(34px * var(--lbui)); text-align: center;
  font-variant-numeric: tabular-nums; flex: none; }
.lightbox__track { position: relative; flex: 1; height: calc(26px * var(--lbui));
  display: flex; align-items: center; cursor: pointer;
  /* the browser must not steal the drag for a scroll gesture */
  touch-action: none; }
/* the fill and the knob are decoration — every pointer event belongs to
   the track itself, so a click never lands on a child that ignores it */
.lightbox__bar, .lightbox__knob { pointer-events: none; }
.lightbox__track::before { content: ""; position: absolute; left: 0; right: 0;
  height: 2px; background: rgba(255,255,255,.28); }
.lightbox__bar { position: absolute; left: 0; height: 2px; width: 0; background: var(--white); }
.lightbox__knob { position: absolute; width: calc(9px * var(--lbui)); height: calc(9px * var(--lbui)); border-radius: 50%;
  background: var(--white); transform: translateX(-50%); opacity: 0;
  transition: opacity .2s var(--ease); }
.lightbox__track:hover .lightbox__knob, .lightbox__track.scrubbing .lightbox__knob { opacity: 1; }
.lightbox__volwrap { display: flex; align-items: center; gap: calc(8px * var(--lbui)); flex: none; }
.lightbox__mute { color: var(--white); display: flex; }
.lightbox__mute svg { width: calc(17px * var(--lbui)); height: calc(17px * var(--lbui)); }
.lightbox__vol { -webkit-appearance: none; appearance: none; width: calc(clamp(48px, 3.6vw, 70px) * var(--lbui));
  height: 2px; background: rgba(255,255,255,.28); outline: none; cursor: pointer; }
.lightbox__vol::-webkit-slider-thumb { -webkit-appearance: none; appearance: none;
  width: calc(9px * var(--lbui)); height: calc(9px * var(--lbui)); border-radius: 50%; background: var(--white); }
.lightbox__vol::-moz-range-thumb { width: calc(9px * var(--lbui)); height: calc(9px * var(--lbui)); border: 0;
  border-radius: 50%; background: var(--white); }
.lightbox__volwrap:not(.is-muted) .lightbox__slash { display: none; }
.lightbox__volwrap.is-muted .lightbox__waves { display: none; }
/* FULL SCREEN: ✕ exit at top right, same fade-with-the-mouse behavior */
.lightbox__fsx { display: none; position: absolute; top: 18px; right: 22px; z-index: 6;
  font-family: var(--cond); font-weight: 600; font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--white); padding: 10px 12px;
  opacity: 0; pointer-events: none; transition: opacity .4s var(--ease); }
.lightbox__frame:fullscreen .lightbox__fsx { display: flex; align-items: center; gap: 8px; }
/* .is-fs is app.js's own record of being full screen, kept by the
   fullscreenchange listener — one class we set, instead of a :fullscreen /
   :-webkit-full-screen selector pair that engines spell differently */
.lightbox__frame.is-fs .lightbox__fsx { display: flex; align-items: center; gap: 8px; }
.lightbox__frame.ui-on .lightbox__fsx { opacity: 1; pointer-events: auto; }
.lightbox__frame:fullscreen:not(.ui-on) { cursor: none; }

/* =========================================================
   SPLASH — R2 logo-only landing, kept verbatim by request
   ========================================================= */
.splash { position: fixed; inset: 0; z-index: 300; background: var(--bg); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: opacity .32s var(--ease), visibility .32s var(--ease); }
.splash.gone { opacity: 0; visibility: hidden; pointer-events: none; }
.splash__tex { position: absolute; inset: 0; pointer-events: none;
  background-image: url("../img/texture/dither10.png"); background-size: 420px;
  mix-blend-mode: multiply; opacity: .35; }
.splash__logo { position: relative; width: min(72vw, 880px); animation: soloIn 1.6s var(--ease) both; }
@keyframes soloIn { from { opacity: 0; } to { opacity: 1; } }
body.splash-lock { overflow: hidden; }
@media (prefers-reduced-motion: reduce) { .splash__logo { animation: none; } }

/* =========================================================
   LEGACY SHIMS — old classes that may linger render harmless
   ========================================================= */
.footer2, .dir-preview, .nav-drop, .workcats, .viewtog, .page-head, .shop__note, .shop__more { display: none; }
.wrap { width: 100%; }

/* =========================================================
   TABLET (761–1024) — desktop-style header, 3-col 270px grid
   ========================================================= */
/* =========================================================
   NARROW DESKTOP + TABLET (761–1199) — menu under the wordmark
   ---------------------------------------------------------
   Two different failures, one fix. Below 1025 the wordmark is pinned at 328px
   while the frame keeps narrowing, so the nav column runs straight through it.
   Between 1025 and 1199 there is no overlap but the column is too narrow for
   the four items and they cram into one word. Stacking the menu on its own
   line under the wordmark solves both, and bottom-aligning the header keeps
   the rule where it was. The stack fits inside the fluid --headh above 1024
   (at 1199: 37px logo + 22 gap + 11 nav + 18 railgap = 88 of 110), so main's
   padding still clears the fixed header — no override needed up here.
   ========================================================= */
@media (max-width: 1199px) and (min-width: 761px) {
  .site-head { flex-direction: column; align-items: flex-start;
    justify-content: flex-end; gap: clamp(12px, 2vw, 22px); }
  .nav-links { position: static; width: auto; justify-content: flex-start;
    gap: clamp(28px, 3.23vw, 62px); }
}

@media (max-width: 1024px) and (min-width: 761px) {
  :root { --headh: 155px; --mx: 83px; }
  .site-head__logo img { width: 328px; }
  .layout, .contact { grid-template-columns: 1fr; }
  .rail { position: static; margin-bottom: 30px; }
  .rail__manifesto { font-size: 40px; }
  .contact__statement { white-space: normal; }
  .contact__img { grid-column: 1; grid-row: auto; margin-top: 24px; }
  .shoprow { grid-template-columns: repeat(2, 1fr); }
}

/* =========================================================
   MOBILE (≤760) — Figma 402 frames (160:942 …): inline menus,
   1px hairline rule, 1-col 362-wide grid
   ========================================================= */
@media (max-width: 760px) {
  :root { --mx: 20px; --gap: 20px; }
  .site-head { position: sticky; height: auto; flex-wrap: wrap; align-items: center;
    gap: 0; padding: 20px var(--mx) 5px; }
  /* mobile rule is a 1px hairline (desktop keeps 2px) */
  .site-head::after { height: 1px; }
  .site-head__logo img { width: 160px; }
  /* hamburger: three 22×2 bars at 0/7/14 (Figma) ↔ ✕ */
  .nav-toggle { display: block; margin-left: auto; width: 22px; height: 16px;
    position: relative; font-size: 0; color: var(--ink); }
  .nav-toggle::before, .nav-toggle::after { content: ""; position: absolute; left: 0; right: 0;
    height: 2px; background: currentColor; transition: transform var(--fade) var(--ease), top var(--fade) var(--ease); }
  .nav-toggle::before { top: 0; box-shadow: 0 7px 0 currentColor; }
  .nav-toggle::after  { top: 14px; }
  .site-head.menu-open .nav-toggle::before { box-shadow: none; }
  .nav .nav-toggle span, .nav-toggle .bar { display: none; }
  .site-head.menu-open .nav-toggle::before { top: 7px; transform: rotate(45deg); }
  .site-head.menu-open .nav-toggle::after  { top: 7px; transform: rotate(-45deg); }
  /* menu: right-aligned rows (12px, 27px pitch) between the header rule
     and a closing hairline; it pushes the page down (inline, not overlay) */
  .nav-links { display: none; }
  .nav-links.open { display: flex; flex-direction: column; align-items: flex-end;
    width: 100%; gap: 0; padding: 8px 0 10px; border-bottom: 1px solid var(--ink);
    animation: pageIn var(--fade) var(--ease) both; }
  .nav-links.open a { font-size: 12px; letter-spacing: .04em; line-height: 27px; }
  /* the manifesto steps back while the menu is open */
  .site-head.menu-open + main .rail__manifesto { opacity: 0; }
  .rail__manifesto { transition: font-size .3s var(--ease), opacity var(--fade) var(--ease); }
  .layout, .contact { grid-template-columns: 1fr; display: block;
    padding: 5px var(--mx) 60px; }
  .rail { position: static; padding: 5px 0 0; }
  /* Callout 40/50 full → Mobile Callout Scroll 20/auto once scrolled */
  .rail__manifesto { font-size: clamp(14px, 4.98vw, 20px); line-height: 1.35;
    padding-bottom: 16px; }
  body.condensed .rail__manifesto { font-size: 14px; line-height: 1.4; }
  /* inline list rows: Body 2 Mobile 12px, 27px pitch */
  .rail-list a, .rail-list button.chip { font-size: 12px; line-height: 27px; margin-bottom: 0; }
  .rail h1, .rail__title { font-size: 15px; }
  .rail__body { font-size: 12px; line-height: 1.4; max-width: 310px; }
  .rail__divider { height: 1px; background: var(--ink); margin: 12px 0 5px; }
  .grid { grid-template-columns: 1fr; padding: 20px 0; border-top: 0; }
  .contact__statement { white-space: normal; font-size: 26px; margin-bottom: 24px; }
  .contact__img { margin-top: 26px; }
  .shoprow { grid-template-columns: 1fr; padding-top: 20px; }
  body.shop-dark .nav-links a { color: #9a9ba1; }
  /* R4n: the player sits NATURALLY in portrait — 16:9, full width,
     centred — no forced CSS rotation. Landscape view comes from the
     user actually rotating the phone (see orientation rule below). */
  /* On a phone the viewer is the whole screen — no cream border showing
     through at the edges or under the safe areas. One dark field, so the
     film is the only thing in the room. */
  /* One black field, edge to edge. The overlay used to be a 97% charcoal
     over black, which lands around #1D1E22 — close enough to read as a
     mistake next to the player's own pure-black letterbox bars. Same #000
     on the field, the frame and the bars, so the surround disappears. */
  .lightbox { background: #000; --lbui: 1.1; }
  .lightbox__overlay { inset: 0; background: #000; }
  .lightbox__panel { inset: 0; }
  .lightbox__frame { width: 100vw; max-height: none; transform: none; }
  .lightbox__frame.is-img { width: auto; height: min(calc(var(--vvh) * .82), 900px); }
  /* Upper right, and nothing but the ✕. On a phone the word was doing no
     work the glyph wasn't already doing, and the corner is where a thumb
     goes looking for it. Sized off --lbui so it grows with the transport. */
  .lightbox__close { left: auto; bottom: auto;
    right: max(14px, calc(env(safe-area-inset-right, 0px) + 6px));
    top: max(14px, calc(env(safe-area-inset-top, 0px) + 6px));
    justify-content: center; gap: 0;
    width: calc(44px * var(--lbui)); height: calc(44px * var(--lbui));
    font-size: calc(20px * var(--lbui)); letter-spacing: 0;
    writing-mode: horizontal-tb; transform: none; padding: 0; margin: 0; }
  .lightbox__closeword { display: none; }
  .lightbox__cap { left: 18px; bottom: 14px; }
  .lightbox__fs { display: none; }

}
/* =========================================================
   PHONE ROTATED — the SITE, not the player.
   ---------------------------------------------------------
   A rotated phone is ~852×393, which lands in the 761–1024 tablet block:
   a 328px wordmark and a 155px header on a 393px-tall screen, with the
   whole page stacked into one column. Sideways there is plenty of width
   and almost no height, so the answer is the desktop shape, shrunk: a
   short single-row header, the rail back beside the grid, and type sized
   off the height rather than the width.
   ========================================================= */
@media (max-width: 960px) and (orientation: landscape) and (pointer: coarse) {
  /* viewport-fit=cover lets the page finally reach the glass — on Jerad's
     16 Pro Max that is 122 CSS pixels of landscape width the site was
     simply never being given. The bill for it is that the notch and the
     home indicator are now OURS to clear, so the one horizontal margin
     every landscape shell already reads from takes the larger of its own
     24px and whatever the hardware is standing in. Both sides get the same
     number so the grid stays centred rather than shunted. */
  :root { --headh: 62px; --gap: 14px; --railgap: 10px;
    --mx: max(24px, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)); }
  .site-head { position: fixed; flex-direction: row; flex-wrap: nowrap;
    align-items: flex-end; justify-content: space-between;
    height: var(--headh); gap: 24px; padding: 0 var(--mx) 9px; }
  .site-head::after { height: 1px; }
  .site-head__logo img { width: clamp(112px, 15vw, 150px); }
  /* sideways there is room for the four words on the same line as the
     wordmark — the hamburger is a portrait solution to a portrait problem */
  .nav-toggle { display: none; }
  .nav-links, .nav-links.open { display: flex; position: static; flex-direction: row;
    width: auto; align-items: flex-end; justify-content: flex-end;
    gap: clamp(20px, 3.4vw, 44px); padding: 0; border-bottom: 0; animation: none; }
  .nav-links a, .nav-links.open a { font-size: 10px; letter-spacing: .1em; line-height: 1.1; }
  .layout { display: grid; grid-template-columns: 24% 1fr; column-gap: 3.2%;
    padding: calc(var(--headh) + var(--railgap)) var(--mx) 40px; }
  /* a director bio is taller than a rotated phone, so a plain sticky rail would
     stick with its TOP already off-screen and the first lines unreachable.
     Clamp it to the space under the header and let it scroll inside itself. */
  .rail { position: sticky; top: calc(var(--headh) + var(--railgap));
    padding: 0; margin-bottom: 0; align-self: start;
    max-height: calc(var(--vvh) - var(--headh) - var(--railgap) - 16px);
    overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none; -ms-overflow-style: none; }
  .rail::-webkit-scrollbar { width: 0; height: 0; }
  /* A long bio scrolls inside the rail, but with the scrollbar hidden there
     is nothing to say so. These are scroll shadows: the two `local` layers
     ride with the text and sit over the two `scroll` layers, so a shadow is
     only visible on an edge that actually has more text past it. Nothing
     shows on a rail short enough to fit — no false promise of more. */
  .rail { background:
      linear-gradient(var(--bg) 34%, rgba(231,231,216,0)) top    / 100% 26px no-repeat local,
      linear-gradient(rgba(231,231,216,0), var(--bg) 66%) bottom / 100% 26px no-repeat local,
      radial-gradient(farthest-side at 50% 0,   rgba(18,19,24,.20), rgba(18,19,24,0)) top    / 100% 11px no-repeat scroll,
      radial-gradient(farthest-side at 50% 100%, rgba(18,19,24,.20), rgba(18,19,24,0)) bottom / 100% 11px no-repeat scroll; }
  /* the callout is sized off the short edge here — 40px would eat the screen */
  .rail__manifesto { font-size: clamp(14px, 5.2vh, 22px); line-height: 1.3;
    padding-bottom: 0; }
  body.condensed .rail__manifesto { font-size: clamp(13px, 4vh, 17px); }
  .rail-list a, .rail-list button.chip { font-size: 12px; line-height: 1;
    margin-bottom: 9px; }
  .rail h1, .rail__title { font-size: 15px; margin-bottom: 12px; }
  .rail__body { font-size: 12px; line-height: 1.45; max-width: 46ch; }
  .grid { grid-template-columns: repeat(3, 1fr); padding: 0 0 20px; border-top: 0; }
  .contact { display: grid; grid-template-columns: 24% 26% 1fr; column-gap: 3.2%;
    padding: calc(var(--headh) + var(--railgap)) var(--mx) 40px; }
  .contact__statement { grid-column: 1 / 3; grid-row: 1; white-space: normal;
    font-size: clamp(15px, 5.6vh, 24px); margin-bottom: clamp(14px, 4vh, 28px); }
  .contact__col--team { grid-column: 1; grid-row: 2; }
  .contact__col--reps { grid-column: 2; grid-row: 2; }
  .contact__img { grid-column: 3; grid-row: 1 / 3; margin-top: 0; }
  .contact__img .duo { min-height: 0; }
  .shoprow { grid-template-columns: repeat(3, 1fr); padding-top: 14px; }

  /* ---- and the player, filling the landscape screen ---- */
  .lightbox { background: #000; --lbui: 1.1; }
  .lightbox__overlay { inset: 0; background: #000; }
  .lightbox__panel { inset: 0; }
  .lightbox__frame { width: min(100vw, calc(var(--vvh) * 1.707));
    max-height: var(--vvh); transform: none; }
  /* Turned sideways with a film open, the frame stops being a 16:9 box laid
     on the page and becomes the page. app.js sets `is-imm` on rotation (and
     on opening while already sideways) and asks for real full screen on top,
     which Android and iPad grant and an iPhone does not — there WebKit only
     ever hands full screen to a <video> it owns, never to a cross-origin
     iframe, so this layout IS the full screen there.
     `aspect-ratio` needs the !important: setLbAspect writes the film's true
     ratio inline, per film, and inline beats a class on specificity alone.
     The picture does not stretch — the embed letterboxes inside whatever box
     it is given, exactly as it does in full screen. */
  .lightbox.is-imm .lightbox__panel { inset: 0; padding: 0; }
  .lightbox.is-imm .lightbox__frame { width: 100vw; height: var(--vvh);
    max-width: none; max-height: none; aspect-ratio: auto !important; }
  /* The frame's bottom edge moves down to the screen's when it goes
     full-bleed, and the transport rides it down — but the caption is
     anchored to the screen and does not move, so the play row lands on the
     director's name again, which is the exact thing Jerad asked us to fix
     twice. The bar takes back the eight pixels the frame gained. */
  .lightbox.is-imm .lightbox__ctrl {
    padding-bottom: calc(36px * var(--lbui) + env(safe-area-inset-bottom, 0px)); }
  /* Sent properly full screen — Android and iPad, never an iPhone — the
     frame is promoted and the ✕ beside it is not painted at all, so THIS is
     the way out and it has to be the same thumb-sized target the other one
     is, in the same corner, rather than a desktop-sized label. */
  .lightbox__fsx { top: max(14px, calc(env(safe-area-inset-top, 0px) + 6px));
    right: max(14px, calc(env(safe-area-inset-right, 0px) + 6px));
    width: calc(44px * var(--lbui)); height: calc(44px * var(--lbui));
    justify-content: center; padding: 0; letter-spacing: 0;
    font-size: calc(20px * var(--lbui)); }
  /* upper right, bare ✕ — same as portrait, clear of the notch when the
     phone is turned either way */
  .lightbox__close { left: auto; bottom: auto;
    right: max(14px, calc(env(safe-area-inset-right, 0px) + 6px));
    top: max(14px, calc(env(safe-area-inset-top, 0px) + 6px));
    justify-content: center; gap: 0;
    width: calc(44px * var(--lbui)); height: calc(44px * var(--lbui));
    font-size: calc(20px * var(--lbui)); letter-spacing: 0;
    writing-mode: horizontal-tb; transform: none; padding: 0; margin: 0; }
  .lightbox__closeword { display: none; }
  /* Sideways the ✕ stops being furniture parked in the black beside the film
     and becomes furniture sitting ON it — so it belongs to the transport
     now, and keeps the transport's hours: up when the film opens, gone a
     couple of seconds later, back on the next touch.
     `ui-on` has to be read off .lightbox rather than .lightbox__frame,
     because the ✕ is the frame's older sibling and no selector starting at
     the frame can reach backwards to it. app.js sets the class on both.
     `has-ui` is the guard: a still or a product panel has no player, so
     nothing would ever wake the ✕ back up, and hiding it there would be a
     locked door. Only a real player, with a real wake(), gets to fade it. */
  .lightbox.has-ui .lightbox__close { opacity: 0; pointer-events: none;
    transition: opacity .35s var(--ease); }
  .lightbox.has-ui.ui-on .lightbox__close { opacity: 1; pointer-events: auto; }
  /* (A film sent properly full-screen promotes the FRAME, so this ✕ is not
     painted at all there — .lightbox__fsx is the way out, and it keeps the
     same hours off the same class.) */
  .lightbox__cap { left: max(18px, env(safe-area-inset-left, 0px)); bottom: 14px; }
  /* Sideways there is no room to spare, so the transport steps up off the
     caption instead of sitting on the director's name. It grows its own
     bottom padding rather than lifting off the edge, so the scrim still
     runs all the way down and the caption sits inside it.
     R30: 46px read as floated — halfway back down to the 13px it sat at
     before the lift, which still leaves the bar clear of the caption. */
  .lightbox__ctrl { padding-top: calc(26px * var(--lbui));
    padding-bottom: calc(29px * var(--lbui) + env(safe-area-inset-bottom, 0px));
    padding-left: max(14px, env(safe-area-inset-left, 0px));
    padding-right: max(14px, env(safe-area-inset-right, 0px)); }
  .lightbox__fs { display: none; }

}

/* contact image fills its column */
.contact__img .duo { height: 100%; min-height: 52vh; }
@media (max-width: 760px) { .contact__img .duo { min-height: 46vw; } }
/* …but a small phone turned sideways is under 760 too, and there the image
   is a column beside the copy, not a band under it — it takes its height
   from the row. (This has to sit after the rule above to out-order it.) */
@media (max-width: 960px) and (orientation: landscape) and (pointer: coarse) {
  .contact__img .duo { min-height: 0; }
}

/* Names branded with deliberate inner capitals — OjO — opt out of the
   uppercase treatment the captions and rails apply to everything else.
   The class is doubled so it out-ranks rules like `.rail h1` on its own,
   without reaching for !important. */
.keepcase.keepcase { text-transform: none; }
