/* ============================================
   Mario Salvador — Portfolio
   Architectural minimalism: exposed structure,
   strict grid, hairline rules, no ornamentation.
   ============================================ */

/* ============================================
   HELVETICA — self-hosted from this folder.

   Four faces cover everything the site uses: regular and bold, each
   with its italic. Light is mapped to weight 300 so it's available if
   a rule ever calls for it. `font-display: swap` means text paints in
   the fallback immediately rather than waiting on the file — worth it,
   because these are .ttf/.otf originals and heavier than a .woff2.

   To shrink them: convert each to .woff2 (roughly a quarter the size)
   and swap the src url plus format('woff2').
   ============================================ */
/* The weight ranges matter: the stylesheet asks for 500 in a lot of
   places (headings, labels, buttons) and Helvetica has no medium cut.
   Claiming 400–500 for the regular face and 600–700 for the bold makes
   that resolution explicit and identical in every browser, instead of
   leaving it to each engine's fallback rules or letting one of them
   fake a medium by smearing the outlines. */
@font-face {
  font-family: "Helvetica Local";
  src: url("Helvetica.ttf") format("truetype");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Helvetica Local";
  src: url("Helvetica-Oblique.ttf") format("truetype");
  font-weight: 400 500;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Helvetica Local";
  src: url("Helvetica-Bold.ttf") format("truetype");
  font-weight: 600 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Helvetica Local";
  src: url("Helvetica-BoldOblique.ttf") format("truetype");
  font-weight: 600 700;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Helvetica Local";
  src: url("helvetica-light-587ebe5a59211.ttf") format("truetype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

/* ============================================
   SAVED PALETTE — "Sage" (approved)
   To restore, paste these over the values in :root below.

     --bg:        #f7f6f4;
     --fg:        #121212;
     --fg-dim:    #55534f;
     --fg-faint:  #6f6c66;
     --rule:      #dedcd7;
     --rule-soft: #eae8e4;
     --accent:    #545E54;
     --surface:   #eeece8;

   Also switch back, further down:
     .hero-figure::after  → opacity 0.22, mix-blend-mode multiply
     --on-image           → #f7f6f4
     --on-image-scrim     → rgba(14,14,14,0.6)
     .yt-play border      → rgba(247,246,244,0.5)
   ============================================ */

/* ============================================
   SAVED TYPE — previous options
   To restore, paste over --font / --font-mono below and swap the
   Google Fonts <link> in index.html's <head> to match.

   Helvetica, self-hosted from this folder (see @font-face above):
     --font:      "Helvetica Local", "Helvetica Neue", Helvetica, Arial, sans-serif;

   Helvetica Neue (system, no web font needed):
     --font:      "Helvetica Neue", Helvetica, Arial, sans-serif;
     --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

   Schibsted Grotesk (Google Fonts):
     --font:      "Schibsted Grotesk", "Helvetica Neue", Helvetica, Arial, sans-serif;

   Geist:
     --font:      "Geist", "Helvetica Neue", Helvetica, Arial, sans-serif;
     --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

   Jost (Google Fonts) — geometric sans, Manifold stand-in:
     --font:      "Jost", sans-serif;
     Swap the Google Fonts <link> to:
     family=Jost:wght@300;400;500;600&family=DM+Mono:wght@400;500

   Bricolage Grotesque (Google Fonts) — modern grotesque, more
   character in its curves than Jost/Helvetica:
     --font:      "Bricolage Grotesque", "Helvetica Neue", Helvetica, Arial, sans-serif;
     Swap the Google Fonts <link> to:
     family=Bricolage+Grotesque:opsz,wght@12..96,300..700&family=DM+Mono:wght@400;500

   Courier Prime (Google Fonts) — typewriter trial, paired with a
   forced-lowercase override (`* { text-transform: lowercase !important; }`,
   added as the very last rule in this file):
     --font:      "Courier Prime", "Courier New", monospace;
     --font-mono: "Courier Prime", "Courier New", monospace;
     Swap the Google Fonts <link> to:
     family=Courier+Prime:ital,wght@0,400;0,700;1,400&family=DM+Mono:wght@400;500
   ============================================ */

/* ============================================
   INVERTED — trial. The Sage light palette is saved above; paste it
   back over the values below to return. Alongside the swap:
     .hero-figure::after  → opacity 0.22, mix-blend-mode soft-light
     .yt-play border      → rgba(18,18,18,0.5)
   ============================================ */

:root {
  --bg: #0e0e0d;            /* near-black — ink, not pure black */
  --fg: #f2f1ed;            /* warm off-white */
  --fg-dim: #a8a59e;        /* secondary text */
  --fg-faint: #86837c;      /* tertiary / metadata — still AA at 10px */
  --rule: #2a2a28;          /* hairline */
  --rule-soft: #1d1d1b;
  --accent: #8fa08c;        /* the sage lifted so it reads on black */
  --surface: #171716;       /* inset / media well */

  /* Per-section identity colours — one named variable per section, used
     both for that section's heading (see #music/#film/#services/#contact
     below) and for its own link in the hero index, so the homepage
     previews each section's colour before you ever scroll to it. Bio
     keeps the site's base --accent (sage); the rest get their own hue. */
  --accent-bio: var(--accent);
  --accent-music: #c17a3d;
  --accent-film: #8a4a52;
  --accent-services: #5b7c99;
  --accent-contact: #7d6b9e;

  /* Fixed values for controls that sit on top of photography, where the
     page palette doesn't apply. */
  --on-image: #f2f1ed;
  --on-image-scrim: rgba(8,8,8,0.62);

  /* Archivo — a bold, blocky grotesque built for heavy display weights
     without warping (Black/900 keeps the same skeleton as Regular).
     Matches the assertive, all-caps masthead style being referenced. */
  --font: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Expo-out: decelerates hard at the end, so things settle rather than
     stop. Smooth without overshoot or bounce. */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  --gutter: 40px;
  --max-width: 1440px;
}

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: -0.005em;
}

img { max-width: 100%; display: block; }
a { color: inherit; }

.wrap {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

h1, h2, h3, h4 {
  font-family: var(--font);
  font-weight: 500;
  line-height: 1.02;
  margin: 0 0 0.4em;
  letter-spacing: -0.028em;
}

p {
  margin: 0 0 1.1em;
  color: var(--fg-dim);
}

em { font-style: italic; }

/* ---------- Metadata label ---------- */
/* ---------- Metadata label ---------- */
.eyebrow {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 10px;
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
}

/* ---------- Scroll progress ----------
   A hairline, not a bar — the same 1px vocabulary as every rule on the
   page, just filled in by how far down you are. */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  z-index: 200;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .scroll-progress { display: none; }
}

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--bg);
  border-bottom: 1px solid var(--rule);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  height: 76px;
}

/* A hairline frame around the whole set, so the four items read as one
   control rather than four stray words in the corner. */
.site-header nav {
  border: 1px solid var(--rule);
  background: var(--bg);
}

.logo {
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fg);
}

.nav-links {
  display: flex;
  gap: 4px;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Once the hero is out of view this bar is the only way across the
   site, so each item is a real target: a padded cell with a hairline
   between it and the next, not free-floating micro-type. */
.nav-links li + li { border-left: 1px solid var(--rule); }

.nav-links a {
  display: inline-block;
  padding: 10px 22px;
  text-decoration: none;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-dim);
  transition: color 0.2s linear, background 0.2s linear, transform 0.4s var(--ease);
}

.nav-links a:hover { color: var(--fg); background: var(--surface); }

/* The section you're in is filled solid — visible at a glance, and it
   makes the unfilled items read as the places you can go next. */
.nav-links a.active {
  color: var(--bg);
  background: var(--fg);
}

.nav-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
}

.nav-toggle span {
  display: block;
  width: 20px;
  height: 1px;
  background: var(--fg);
  margin: 5px 0;
}

/* ---------- Hero ----------
   Two facing blocks rather than text-over-photo: type holds the left of
   the grid, the portrait is a hard-edged column on the right. Because
   nothing sits on top of the image, it runs at full strength and needs
   no scrim — which is what kept the earlier full-bleed versions looking
   washed out. */
/* The band is full-bleed so its bottom rule matches every section rule
   below it, but the content inside sits in exactly the same capped,
   centred column as .wrap — so the portrait's right edge lines up with
   the right edge of the page, not the window. */
.hero {
  position: relative;
  background: var(--bg);
  border-bottom: 1px solid var(--rule);
}

.hero-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--gutter);
  min-height: calc(100vh - 76px);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: auto 1fr;
  align-items: stretch;
  overflow: hidden;
}

/* ---------- Hero index ----------
   The four sections as one stacked column of large type, held on the
   page's left edge. This is the whole navigation: no eyebrow, no
   numbering, no rules between items — just the names at a size that
   makes them the first thing read. */
/* Left column: the intro sits directly under the masthead rule, the
   four section names below it. */
.hero-left {
  grid-column: 1 / 8;
  grid-row: 2;
  display: flex;
  flex-direction: column;
  padding: 0 40px 0 0;
}

/* Sits high in the column rather than bottom-aligned, with the names
   spread apart so the set reads as an index of destinations instead of
   a tight block of type. */
/* Sentence case, tight, set as a quiet stack rather than a wall of
   display caps — the restraint is the point. */
.hero-index {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: start;
  justify-content: center;
  text-align: left;
  gap: 0.12em;
  padding: 34px 0 48px;
  margin-top: 0;
}

.hero-index a {
  font-size: clamp(30px, 3.6vw, 52px);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fg);
  transition: color 0.25s linear, opacity 0.25s linear;
}

/* Each name previews the colour its own section uses for its heading,
   so the identity colours show up here on the homepage too, not just
   once you've scrolled down. Order matches the links in the markup:
   Bio, Music, Film, Services, Contact. */
.hero-index a:nth-child(1) { color: var(--accent-bio); }
.hero-index a:nth-child(2) { color: var(--accent-music); }
.hero-index a:nth-child(3) { color: var(--accent-film); }
.hero-index a:nth-child(4) { color: var(--accent-services); }
.hero-index a:nth-child(5) { color: var(--accent-contact); }

/* Hovering one name recedes the others, so the column reads as a set
   with a current choice rather than four equal blocks of type. */
.hero-index:hover a { opacity: 0.34; }
.hero-index a:hover { opacity: 1; }

.hero-index a:focus-visible { outline: 1px solid var(--accent); outline-offset: 4px; }

/* ---------- Hero right: the portrait column ----------
   No left rule: the photograph now fades out on every side, so a
   hairline beside it would re-draw the very boundary the mask removes. */
.hero-right {
  grid-column: 8 / -1;
  grid-row: 2;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Two masks composited together — one per axis — dissolve every edge of
   the photograph into the page ground, so it reads as an image bleeding
   into the paper rather than a rectangle sitting on it. The vertical
   pass holds longer at the top (the crown of the head is up there) and
   fades further at the foot. */
.hero-figure {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  -webkit-mask-image:
    linear-gradient(to bottom, rgba(0,0,0,0) 0%, #000 5%, #000 88%, rgba(0,0,0,0) 100%),
    linear-gradient(to right,  rgba(0,0,0,0) 0%, #000 6%, #000 94%, rgba(0,0,0,0) 100%);
  mask-image:
    linear-gradient(to bottom, rgba(0,0,0,0) 0%, #000 5%, #000 88%, rgba(0,0,0,0) 100%),
    linear-gradient(to right,  rgba(0,0,0,0) 0%, #000 6%, #000 94%, rgba(0,0,0,0) 100%);
  /* Intersect rather than overlay: a pixel survives only where BOTH
     gradients are opaque, which is what rounds the fade into the
     corners instead of leaving them hard. */
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* Oversized vertically so the parallax drift never exposes an edge.
   Source is 2592px square, so it holds up at this scale without help.
   Framed slightly high to keep the face and hands in the crop. */
.hero-figure img {
  position: absolute;
  inset: -10% 0;
  width: 100%;
  height: 120%;
  object-fit: cover;
  object-position: 50% 44%;
  filter: grayscale(1) brightness(1.08) contrast(1.02);
  transform: translate3d(0, var(--py, 0px), 0);
  will-change: transform;
}

/* A whisper of grain — enough to bind the photograph to the paper
   ground, not enough to read as an effect. */
.hero-figure::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  /* Coarser, softer noise than before — closer to photographic silver
     grain than to digital speckle. Overlay rather than multiply, so it
     lifts highlights as well as sinking shadows instead of only
     darkening the frame. */
  opacity: 0.26;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.42' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)' opacity='0.55'/%3E%3C/svg%3E");
}

/* ---------- Masthead: the name across the top ----------
   Full width of the page column, above both the index and the portrait.
   Sized to fill the measure rather than to a fixed scale, so it always
   reads as the masthead and never as a heading that happens to be big. */
.hero-content {
  grid-column: 1 / -1;
  grid-row: 1;
  position: relative;
  z-index: 2;
  padding: 30px 0 26px;
  border-bottom: 1px solid var(--rule);
}

/* Hero entrance — each line rises just after the one above it. The
   parent gets .is-loaded from js/script.js once the DOM is ready, so
   nothing animates before the page can paint. */
.hero-tagline,
.hero-index a {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 1s var(--ease), transform 1s var(--ease);
}

.is-loaded .hero-tagline { transition-delay: 0.46s; }

/* The index arrives name by name, top to bottom. */
.is-loaded .hero-index a:nth-child(1) { transition-delay: 0.14s; }
.is-loaded .hero-index a:nth-child(2) { transition-delay: 0.22s; }
.is-loaded .hero-index a:nth-child(3) { transition-delay: 0.30s; }
.is-loaded .hero-index a:nth-child(4) { transition-delay: 0.38s; }

.is-loaded .hero-tagline,
.is-loaded .hero-index a {
  opacity: 1;
  transform: none;
}

/* The hover-recede effect must not fight the entrance transition. */
.is-loaded .hero-index:hover a { opacity: 0.34; }
.is-loaded .hero-index a:hover { opacity: 1; }

/* The name arrives letter by letter (split in js/script.js) — everything
   else in the hero still arrives as a block, so this is the one moment
   that reads as "built" rather than faded in. */
.hero-content h1 .split-char {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.55em);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  transition-delay: calc(0.05s + var(--i, 0) * 32ms);
}

.is-loaded .hero-content h1 .split-char {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .hero-tagline,
  .hero-index a,
  .hero-content h1 .split-char {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

.hero-content h1 {
  /* 10.2vw ≈ the full page measure for a 14-character name; the clamp
     stops it exploding on ultrawide or crushing on small screens. */
  font-size: clamp(44px, 10.2vw, 152px);
  font-weight: 800;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 0.88;
  margin: 0;
  color: var(--fg);
}

/* The standfirst: under the name, above the index. Larger than body
   copy so it carries as an opening statement rather than a caption. */
.hero-tagline {
  flex: 0 0 auto;
  font-size: clamp(16px, 1.35vw, 20px);
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: var(--fg-dim);
  margin: 0;
  padding: 30px 0 40px;
  max-width: 46ch;
}

.hero-cta {
  display: flex;
  gap: 28px;
  flex-wrap: wrap;
}

/* ---------- Buttons: rules, not pills ---------- */
.btn {
  display: inline-block;
  padding: 0 0 6px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  background: none;
  font-family: var(--font);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fg);
  cursor: pointer;
  transition: border-color 0.2s linear, color 0.2s linear, transform 0.4s var(--ease);
}

.btn:hover { border-bottom-color: var(--accent); color: var(--accent); }

.btn-primary,
.btn-ghost,
.btn-outline {
  background: none;
  box-shadow: none;
  color: var(--fg);
  border: 0;
  border-bottom: 1px solid var(--rule);
}

.btn-primary { border-bottom-color: var(--accent); color: var(--accent); }
.btn-primary:hover { border-bottom-color: var(--fg); color: var(--fg); }

/* ---------- Sections ---------- */
.section {
  border-bottom: 1px solid var(--rule);
  /* The sticky header sits in the flow, so an anchor jump would land
     with the header covering the section's first line. This reserves
     its height plus a little air. */
  scroll-margin-top: 92px;
}

/* ---------- Per-section accent ----------
   Background and body text stay constant site-wide (tried a full
   black/white swap per section — too much). Instead each section gets
   its own accent hue, so only the things already coloured with
   --accent (hover states, active nav, focus rings, section heading)
   shift as you move down the page — a quieter way to give each
   section its own identity. */
#music { --accent: var(--accent-music); }
#film { --accent: var(--accent-film); }
#services { --accent: var(--accent-services); }
#contact { --accent: var(--accent-contact); }

.section-head h2 { color: var(--accent); }

.section-toggle { display: block; margin: 0; padding: 0; }

/* Each section repeats its own name from the nav as a heading at its
   own top — a landmark for a visitor who scrolled or jumped straight
   in, not a control. The <summary> stays because a <details> requires
   one, but every section ships `open` and it's no longer clickable to
   collapse (see pointer-events below) — it's a label, not a toggle. */
.section-toggle summary {
  display: block;
  padding: 0;
  margin: 0;
  border: 0;
  list-style: none;
  background: none;
  cursor: default;
  pointer-events: none;
}

.section-toggle summary::-webkit-details-marker { display: none; }
.section-toggle summary::marker { content: ""; }

.section-head {
  max-width: none;
  margin: 0;
  position: relative;
}

.section-toggle summary .section-head {
  display: grid;
  grid-template-columns: 80px 1fr auto;
  align-items: baseline;
  gap: 24px;
  padding: 34px 0;
  border-top: 0;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 0;
}

.section-head h2 {
  grid-column: 1 / 3;
  padding-left: 56px;
  font-size: clamp(26px, 3.4vw, 42px);
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1;
  margin: 0;
  text-transform: uppercase;
}


.section-head .lede {
  grid-column: 1 / 3;
  padding-left: 56px;
  font-size: 14px;
  color: var(--fg-dim);
  max-width: 46ch;
  margin: 12px 0 0;
}

.section-toggle .section-body {
  padding: 0 0 140px;
  border-top: 0;
  margin-top: 0;
  animation: none;
}

.section-body > .wrap:first-child { padding-top: 96px; }

/* ---------- Subsection heads ---------- */
.subhead {
  display: grid;
  grid-template-columns: 80px 1fr;
  gap: 24px;
  align-items: baseline;
  margin: 120px 0 32px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--rule);
}

.subhead:first-of-type { margin-top: 0; }

.subhead h3 {
  grid-column: 2;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg);
  margin: 0;
}

.subhead .count { font-size: 11px; color: var(--fg-faint); }

/* ---------- About ---------- */
.about-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 24px;
  align-items: start;
}

.bio-text { grid-column: 1 / 7; }

.bio-text p {
  font-size: 16px;
  line-height: 1.6;
  color: var(--fg-dim);
  max-width: 58ch;
}

.about-grid > .reveal:last-child { grid-column: 8 / 13; }

.about-portrait {
  aspect-ratio: 4/5;
  overflow: hidden;
  border-radius: 0;
  background: var(--surface);
}

.about-portrait img {
  width: 100%; height: 100%;
  object-fit: cover;
}

.facts {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin-top: 48px;
  padding-top: 0;
  border-top: 1px solid var(--rule);
}

.fact {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 24px;
  padding: 16px 0;
  border-bottom: 1px solid var(--rule-soft);
}

.fact dt {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--fg-faint);
  font-weight: 500;
  margin: 0;
}

.fact dd {
  margin: 0;
  font-size: 14px;
  color: var(--fg-dim);
  line-height: 1.5;
}

.awards-strip {
  margin-top: 40px;
  display: block;
}

.award-item {
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: 20px;
  align-items: baseline;
  font-size: 13px;
  color: var(--fg-dim);
  padding: 14px 0;
  border-top: 1px solid var(--rule-soft);
}

.award-item:first-child { border-top: 1px solid var(--rule); }

.award-item .yr {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--fg-faint);
  min-width: 0;
}

/* The description must be a single element. Loose text and inline tags
   would each become their own grid item and wrap onto a new row. */
.award-item .what { min-width: 0; }

/* ---------- Media grid: hard-edged blocks, hairline separated ---------- */
.grid { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }

/* Fills to content rather than a fixed column count — avoids leaving an
   empty cell, which would otherwise show as a bare strip of the hairline
   background behind the grid. */
.grid-auto { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

.media-card {
  background: var(--bg);
  border: 0;
  border-radius: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: background 0.2s linear;
  box-shadow: none;
}

.media-card:hover {
  background: var(--surface);
  transform: none;
  box-shadow: none;
  border-color: transparent;
}

.media-card .embed-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16/9;
  background: var(--surface);
  overflow: hidden;
}

.media-card .embed-wrap iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.media-card .embed-wrap video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}

/* ---------- Video cover frame ----------
   A still from the video itself, held as a structural block until played. */
.yt-facade {
  cursor: pointer;
  display: block;
  text-decoration: none;
  color: inherit;
}

.yt-thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;   /* crops hqdefault's 4:3 letterbox bars back to 16:9 */
  transform: scale(1.001);   /* promotes to its own layer, avoids scale jitter */
  transition: transform 0.85s var(--ease);
}

/* A slow push-in on hover — enough motion to signal "this opens" without
   a hard state flip. */
.media-card:hover .yt-thumb {
  transform: scale(1.045);
}

/* Rectangular, hairline, labelled — a control, not a badge. Reads as
   "Play" at rest so the cover is obviously interactive, and inverts to
   solid on hover.

   This sits on top of a video still, not on the page background, so it
   uses the fixed on-image values: a light label over a dark scrim stays
   legible whatever the frame underneath happens to be. */
.yt-play {
  position: absolute;
  left: 20px;
  bottom: 20px;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 34px;
  padding: 0 14px;
  border: 1px solid rgba(242,241,237,0.45);
  border-radius: 0;
  background: var(--on-image-scrim);
  color: var(--on-image);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition:
    background 0.35s var(--ease),
    border-color 0.35s var(--ease),
    color 0.35s var(--ease),
    transform 0.45s var(--ease);
}

/* The outbound arrow travels up-right on hover — a small, literal cue
   that the link leaves the site. */
.yt-out {
  transform: translate(0, 0);
  transition: transform 0.45s var(--ease);
}

.media-card:hover .yt-out { transform: translate(2px, -2px); }

.yt-play-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1;
}

.media-card:hover .yt-play {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.yt-facade:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}

@media (prefers-reduced-motion: reduce) {
  .yt-thumb,
  .yt-out { transition: none; }
  .media-card:hover .yt-thumb { transform: none; }
  .media-card:hover .yt-out { transform: none; }
}

.media-card-body { padding: 20px 22px 26px; flex-grow: 1; }

.media-card-body h4 {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: 0 0 5px;
}

.media-card-body p {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--fg-faint);
  margin: 0;
}

.badge {
  display: inline-block;
  background: none;
  border: 0;
  border-left: 1px solid var(--accent);
  color: var(--accent);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 2px 0 2px 12px;
  border-radius: 0;
  margin-top: 14px;
}

.eih-note { margin-top: 16px; font-size: 12px; color: var(--fg-faint); }
.eih-note a { color: var(--fg); text-decoration: underline; }

/* ---------- Audio embeds ---------- */
.audio-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 32px 24px;
}

.audio-card { display: flex; flex-direction: column; }

.audio-card iframe {
  width: 100%;
  height: 152px;
  border: 0;
  border-radius: 0;
  display: block;
  background: var(--surface);
}

.bandcamp-card iframe { height: 168px; }

.audio-label {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--fg);
}

.audio-label span { color: var(--fg-faint); font-weight: 400; }

/* ---------- Discography: album art, played in place ----------
   The cover is the resting state — real square album art rather than
   the compact iframe bar. A click swaps in the actual Spotify player
   and starts it; only one plays at a time, matching the exclusive-
   playback rule used everywhere else on the site. */
.discography-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 32px 24px;
}

.discography-card { display: flex; flex-direction: column; }

.discography-cover {
  position: relative;
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--rule);
  border-radius: 0;
  background: var(--surface);
  overflow: hidden;
  cursor: pointer;
  font: inherit;
  color: inherit;
  -webkit-appearance: none;
  appearance: none;
}

.discography-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.85s var(--ease);
}

.discography-cover:hover img { transform: scale(1.045); }

.discography-cover:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}

/* Reuses the same rectangular hairline label as the video covers, so
   "this is playable" reads identically everywhere on the site. */
.discography-play { position: absolute; left: 16px; bottom: 16px; }

.discography-cover:hover .discography-play {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.discography-player { display: none; background: var(--surface); }

.discography-player iframe {
  width: 100%;
  height: 352px;
  border: 0;
  display: block;
}

.discography-card.is-playing .discography-cover { display: none; }
.discography-card.is-playing .discography-player { display: block; }

.discography-card .audio-label { margin-top: 12px; }

@media (prefers-reduced-motion: reduce) {
  .discography-cover img { transition: none; }
  .discography-cover:hover img { transform: none; }
}

/* ---------- Collaborations ----------
   Three labelled groups under one heading, so recordings, singles and
   live footage read as distinct sets rather than one long run of
   players. The label sits in the same 80px metadata column the section
   and subsection heads use, keeping the left edge consistent. */
.collab-group { margin-top: 44px; }
.collab-group:first-of-type { margin-top: 0; }

.minor-head {
  display: grid;
  grid-template-columns: 80px 1fr;
  gap: 24px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0 0 18px;
}

.minor-head::before {
  content: "";
  border-top: 1px solid var(--rule);
  transform: translateY(0.62em);
}

/* ---------- Collaborations grid ----------
   One uniform set. Each player sits in a fixed-height well, so Spotify
   (natural 152px) and Bandcamp (natural ~200px) occupy exactly the same
   footprint and rows never come out ragged. The well clips any overflow,
   and each card carries the same hairline frame and label block. */
.collab-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}

.collab-card {
  background: var(--bg);
  padding: 16px;
  display: flex;
  flex-direction: column;
  transition: background 0.2s linear;
}

.collab-card:hover { background: var(--surface); }

.collab-player {
  position: relative;
  height: 200px;
  overflow: hidden;
  background: var(--surface);
}

.collab-player iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.collab-card .audio-label {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
  font-size: 11px;
  line-height: 1.4;
}

/* The credit sits under the title rather than trailing it, so titles of
   different lengths still line up. */
.collab-card .audio-label span {
  display: block;
  margin-top: 3px;
  color: var(--fg-faint);
}

@media (max-width: 560px) {
  .collab-grid { grid-template-columns: 1fr; }
}

.audio-grid--narrow {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 32px 24px;
}

.audio-grid--narrow .audio-card {
  border: 0;
  border-radius: 0;
  padding: 0;
  background: none;
}

.audio-grid--narrow .audio-card iframe { height: 152px; border-radius: 0; }
.audio-grid--narrow .bandcamp-card iframe { height: 200px; }
.audio-grid--narrow .audio-label { margin-top: 12px; margin-bottom: 0; font-size: 11px; }

/* ---------- Film credits ----------
   The hairlines are drawn by the cards themselves rather than by a gap
   over a coloured container. With an odd number of credits the last row
   never fills, and a container background would render that leftover as
   a bordered empty box; this way it's simply page ground, so the block
   ends wherever the cards end. */
.film-credits {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 0;
  background: none;
  border: 0;
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  margin-bottom: 0;
}

.credit-card {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border: 0;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  overflow: hidden;
  transition: background 0.2s linear;
  box-shadow: none;
}

.credit-card:hover {
  background: var(--surface);
  transform: none;
  box-shadow: none;
}

/* Scoped to .frame-video, not to every <video> in a credit card: the
   MoMA card uses a <video> as a still inside .embed-wrap, where the
   absolute positioning of .yt-thumb has to win. */
.credit-card .frame-video {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
  background: var(--surface);
}

/* A still, not a player — clicks belong to the link wrapping it. */
.yt-facade video.yt-thumb { pointer-events: none; }

/* A credit whose film lives off-site uses the same cover frame as the
   video grid, so both kinds of card read identically in the row. */
.credit-card .embed-wrap {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--surface);
  overflow: hidden;
}

.credit-card:hover .yt-thumb { transform: scale(1.045); }
.credit-card:hover .yt-out { transform: translate(2px, -2px); }

.credit-card:hover .yt-play {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

@media (prefers-reduced-motion: reduce) {
  .credit-card:hover .yt-thumb,
  .credit-card:hover .yt-out { transform: none; }
}

/* Grows to the row height so every card in a row shares one baseline
   for its bottom edge, whatever the length of its description. */
.credit-card-body {
  flex: 1 1 auto;
  padding: 24px 24px 30px;
}

.credit-card-body h4 {
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.025em;
  margin-bottom: 6px;
}

.credit-card-body p { font-size: 12.5px; color: var(--fg-faint); }

/* Production credit for work that belongs to someone else — set apart
   from the description so it's clearly attribution, not a claim. */
.credit-attrib {
  margin-top: -4px;
  padding-top: 10px;
  border-top: 1px solid var(--rule-soft);
  font-size: 11px;
  line-height: 1.45;
  color: var(--fg-faint);
}

/* ---------- Score reader ----------
   A spread of two pages held in a hairline frame, with the title tabs
   above and the page controls below. Same vocabulary as the rest of the
   site: no rounded corners, no shadows, one accent. */
.score-reader { display: block; }

.score-tabs {
  display: flex;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-bottom: 0;
}

.score-tab {
  flex: 1 1 0;
  min-width: 0;
  display: block;
  padding: 16px 20px;
  border: 0;
  border-radius: 0;
  background: var(--bg);
  font-family: var(--font);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.015em;
  text-align: left;
  color: var(--fg);
  cursor: pointer;
  transition: background 0.2s linear, color 0.2s linear;
  -webkit-appearance: none;
  appearance: none;
}

.score-tab span {
  display: block;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  transition: color 0.2s linear;
}

.score-tab:hover { background: var(--surface); }

.score-tab.is-active { background: var(--fg); color: var(--bg); }
.score-tab.is-active span { color: var(--bg); opacity: 0.7; }

.score-tab:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }

/* The frame is the fixed dimension; pages are scaled to fill its height,
   which is what keeps every spread the same size whatever the paper
   ratio of the source PDF. */
.score-spread {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1px;
  height: min(85vh, 920px);
  padding: 24px;
  background: var(--surface);
  border: 1px solid var(--rule);
}

.score-page {
  display: block;
  max-height: 100%;
  background: #fff;   /* engraved scores are black on white, always */
}

.score-status {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0 24px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  pointer-events: none;
}

.score-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 14px 0;
  border: 1px solid var(--rule);
  border-top: 0;
}

.score-nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 0;
  background: none;
  color: var(--fg);
  cursor: pointer;
  transition: background 0.2s linear, color 0.2s linear, border-color 0.2s linear, opacity 0.2s linear;
}

.score-nav:hover:not(:disabled) {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.score-nav:disabled { opacity: 0.28; cursor: default; }
.score-nav:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }

.score-count {
  min-width: 92px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
}

/* Below 860px the reader drops to one page at a time — two would be
   unreadable. That switch lives in js/scores.js, so the page count and
   the arrows step by one to match. */
@media (max-width: 860px) {
  .score-spread { height: min(75vh, 700px); }
}

@media (max-width: 560px) {
  .score-tabs { flex-direction: column; }
  .score-spread { height: min(65vh, 560px); padding: 12px; }
}

/* Full screen: the pages get the whole viewport height instead of a
   fraction of the page, which is what actually makes a dense
   orchestral score legible rather than just "bigger." */
.score-reader:fullscreen,
.score-reader:-webkit-full-screen {
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: var(--bg);
  padding: 24px;
}
.score-reader:fullscreen .score-spread,
.score-reader:-webkit-full-screen .score-spread {
  height: calc(100vh - 140px);
  padding: 32px;
}
.score-nav-zoom { margin-left: 8px; }

/* ---------- Film: scoring reel ----------
   Rows, not players. Five cues cost about the height of one video card,
   which is the whole point — this is a range demo sitting under the
   film work, not a second discography. Same hairline row vocabulary as
   the press list and award strip. */
.reel-list {
  display: block;
  margin-top: 4px;
  border-top: 1px solid var(--rule);
}

.reel-track {
  position: relative;
  display: grid;
  grid-template-columns: 80px 34px 1fr auto;
  align-items: center;
  gap: 18px;
  width: 100%;
  padding: 22px 0;
  margin: 0;
  border: 0;
  border-bottom: 1px solid var(--rule-soft);
  border-radius: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: background 0.2s linear;
}

.reel-track:last-child { border-bottom: 1px solid var(--rule); }
.reel-track:hover { background: var(--surface); }

.reel-track:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}

.reel-index {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.03em;
  color: var(--fg-faint);
  padding-left: 0;
}

/* A real target, not a glyph — 34px square with a hairline frame, so
   "this plays" is legible at a glance the way the video covers are. */
.reel-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--rule);
  color: var(--fg);
  transition: color 0.2s linear, background 0.2s linear, border-color 0.2s linear;
}

.reel-track:hover .reel-icon {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.reel-track.is-playing .reel-icon {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}

/* One icon slot, two glyphs — the pause bars only surface on the row
   that's actually running. */
.reel-icon-pause { display: none; }
.reel-track.is-playing .reel-icon-play { display: none; }
.reel-track.is-playing .reel-icon-pause { display: inline; }

.reel-title {
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.022em;
  color: var(--fg);
  min-width: 0;
}

.reel-title span {
  display: block;
  margin-top: 4px;
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--fg-faint);
}

.reel-time {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
  min-width: 0;
}

/* Progress rides the row's own bottom rule rather than adding a bar —
   the hairline simply turns accent-coloured from left to right. */
.reel-progress {
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 2px;
  width: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
}

@media (max-width: 640px) {
  .reel-track { grid-template-columns: 30px 34px 1fr auto; gap: 14px; padding: 18px 0; }
  .reel-title { font-size: 17px; }
}

/* ---------- Services: pure grid of hairline cells ---------- */
.services-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: 0;
  overflow: hidden;
}

.service-card {
  background: var(--bg);
  padding: 34px 30px 40px;
  position: relative;
  transition: background 0.2s linear;
}

.service-card:hover { background: var(--surface); transform: none; }

.service-index {
  display: block;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  color: var(--fg-faint);
  margin-bottom: 26px;
  transition: color 0.2s linear;
}

.service-card:hover .service-index { color: var(--accent); }

.service-card h4 {
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.025em;
  margin-bottom: 10px;
}

.service-card p {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--fg-faint);
  margin: 0;
  max-width: 34ch;
}

/* ---------- Press ---------- */
.press-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 24px;
  align-items: start;
}

.press-photo {
  grid-column: 1 / 5;
  aspect-ratio: 4/5;
  border-radius: 0;
  overflow: hidden;
  position: sticky;
  top: 96px;
}

/* A landscape source cropped to a 4:5 column — held left of centre so
   the hands and the neck of the tres stay in frame. */
.press-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 38% 50%;
}

.press-list { grid-column: 6 / 13; display: block; }

.press-item {
  display: grid;
  grid-template-columns: 160px 1fr 24px;
  align-items: baseline;
  gap: 24px;
  padding: 22px 0;
  border-top: 1px solid var(--rule);
  text-decoration: none;
  transition: opacity 0.2s linear;
}

.press-item:hover { opacity: 0.6; }
.press-item:last-child { border-bottom: 1px solid var(--rule); }

.press-item .outlet {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--fg-faint);
  font-weight: 500;
  min-width: 0;
}

.press-item .headline {
  font-family: var(--font);
  font-size: 17px;
  font-weight: 400;
  letter-spacing: -0.02em;
  color: var(--fg);
}

.press-item .go { font-size: 14px; color: var(--fg-faint); transition: none; }
.press-item:hover .go { transform: none; color: var(--fg-faint); }

/* ---------- Gallery: structural image blocks ---------- */
.gallery-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  margin-top: 0;
  overflow: hidden;
}

/* Each cell is a download link; the label only surfaces on hover so the
   grid still reads as photographs rather than as a row of buttons. */
.gallery-item {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--bg);
  text-decoration: none;
  color: inherit;
}

.gallery-strip img {
  width: 100%;
  height: 240px;
  object-fit: cover;
  border-radius: 0;
  display: block;
  transition: opacity 0.25s linear;
}

.gallery-item:hover img { opacity: 0.55; }

.gallery-dl {
  position: absolute;
  left: 12px;
  bottom: 12px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 11px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--on-image);
  background: var(--on-image-scrim);
  border: 1px solid rgba(242,241,237,0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
  pointer-events: none;
}

.gallery-item:hover .gallery-dl,
.gallery-item:focus-visible .gallery-dl {
  opacity: 1;
  transform: none;
}

.gallery-item:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }

@media (prefers-reduced-motion: reduce) {
  .gallery-strip img,
  .gallery-dl { transition: none; }
  .gallery-dl { transform: none; }
}

.gallery-strip--dark { margin-top: 64px; gap: 1px; }
.gallery-strip--dark img { border-radius: 0; }

/* ---------- Contact ---------- */
.contact-section { background: var(--bg); border-bottom: none; }
.contact-section .eyebrow { color: var(--accent); }

/* No max-width override here — it must stay inside .wrap's capped,
   centred column, or the contact block drifts left of every other
   section once the viewport passes --max-width. */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 24px;
}

/* Two columns: the address block on the left, the form on the right,
   with the socials pushed to the foot of the left column so both sides
   finish on the same line. */
.contact-intro {
  grid-column: 1 / 6;
  display: flex;
  flex-direction: column;
}

.contact-intro h3 {
  font-size: clamp(24px, 2.6vw, 36px);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.12;
  color: var(--fg);
  margin: 0;
}

/* margin, not padding — padding here would sit inside the grid's
   own background and paint as a solid block above the cells. */
/* Fixed rather than margin-top: auto — auto only pushes when the flex
   column has slack to give, and it no longer reliably does now that
   the column beside it (just an email link) is short. */
.contact-intro .socials { margin-top: 48px; }

/* ---------- Contact: email ---------- */
.contact-email {
  grid-column: 7 / -1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  justify-content: center;
}

.field-label {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--fg);
}

.contact-email-link {
  font-size: clamp(22px, 2.4vw, 32px);
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 8px;
  transition: color 0.2s linear, border-color 0.2s linear;
  word-break: break-word;
}

.contact-email-link:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* Each platform is a hairline cell holding its mark above its name —
   a real target rather than a small underlined word. Monochrome by
   design: the marks inherit --fg, so they invert with the palette and
   never introduce brand colour into a two-colour page. */
/* A vertical stack of hairline rows — mark on the left, name beside it.
   Monochrome by design: the marks inherit --fg, so they invert with the
   palette and never introduce brand colour into a two-colour page. */
.socials {
  display: flex;
  flex-direction: column;
  margin-top: 48px;
  border-top: 1px solid var(--rule);
}

.socials a {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 16px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 16px 14px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: left;
  text-decoration: none;
  color: var(--fg);
  background: none;
  transition: background 0.2s linear, color 0.2s linear;
}

.socials a svg {
  display: block;
  flex: 0 0 auto;
  transition: transform 0.4s var(--ease);
}

/* Inverts to solid on hover, the same gesture the nav and submit
   button use. */
.socials a:hover {
  background: var(--fg);
  color: var(--bg);
}

.socials a:hover svg { transform: translateX(2px); }

.socials a:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }

@media (prefers-reduced-motion: reduce) {
  .socials a svg { transition: none; }
  .socials a:hover svg { transform: none; }
}

.contact-photo { aspect-ratio: 3/4; border-radius: 0; overflow: hidden; }
.contact-photo img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- Footer ---------- */
.site-footer {
  padding: 28px 0;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  background: var(--bg);
  border-top: 1px solid var(--rule);
  text-align: left;
}

.site-footer .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

/* ---------- Scroll motion ----------
   Blocks rise and settle as they enter. Long duration + expo-out easing
   keeps it fluid rather than snappy; no scale, rotation or bounce, so
   the grid never appears to distort. */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
  will-change: opacity, transform;
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* Children of a grid animate in sequence rather than as one slab. The
   index is assigned in js/script.js; the delay is capped there so a
   fifteen-item grid doesn't take a full second to finish. */
.stagger-item {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 0.75s var(--ease),
    transform 0.75s var(--ease);
  transition-delay: calc(var(--i, 0) * 55ms);
  will-change: opacity, transform;
}

.stagger-item.is-visible {
  opacity: 1;
  transform: none;
}

/* Section headings get a slightly longer, later travel than their
   contents, so the title leads and the body follows. */
.section-toggle summary .section-head {
  transition: opacity 0.2s linear;
}

/* Once an element has finished, drop the compositor hint. */
.reveal.is-settled,
.stagger-item.is-settled { will-change: auto; }

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .stagger-item {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0ms;
  }
}

/* ---------- Responsive ---------- */
@media (max-width: 1040px) {
  :root { --gutter: 28px; }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .services-grid { grid-template-columns: repeat(2, 1fr); }
  .gallery-strip { grid-template-columns: repeat(3, 1fr); }
  .bio-text { grid-column: 1 / 8; }
  .about-grid > .reveal:last-child { grid-column: 8 / 13; }
}

@media (max-width: 860px) {
  .about-grid,
  .contact-grid,
  .press-grid { grid-template-columns: 1fr; }
  .bio-text,
  .about-grid > .reveal:last-child,
  .press-photo,
  .press-list,
  .contact-intro,
  .contact-email { grid-column: 1 / -1; }
  .contact-intro .socials { margin-top: 32px; margin-bottom: 48px; }
  .press-photo { position: static; aspect-ratio: 16/9; }
  .film-credits,
  .grid-2,
  .grid-3 { grid-template-columns: 1fr; }
  .services-grid { grid-template-columns: 1fr; }
  .gallery-strip { grid-template-columns: repeat(2, 1fr); }
  /* Stack: portrait becomes a banded block above the type. */
  /* Stack: name + portrait as one banded block, then the index below. */
  .hero-inner {
    grid-template-columns: 1fr;
    min-height: 0;
    padding: 0;
  }
  .hero-content {
    grid-column: 1;
    grid-row: 1;
    padding: 26px var(--gutter) 22px;
  }
  .hero-content h1 { font-size: clamp(38px, 13vw, 74px); }
  .hero-right {
    grid-column: 1;
    grid-row: 2;
  }
  .hero-figure { aspect-ratio: 4 / 3; }
  .hero-figure img { inset: 0; height: 100%; }
  .hero-left {
    grid-column: 1;
    grid-row: 3;
    padding: 0 var(--gutter);
  }
  .hero-tagline { padding: 26px 0 30px; }
  .hero-index { padding: 0 0 40px; margin-top: 0; }
  .hero-index a { font-size: clamp(28px, 8vw, 44px); }
  .section-toggle summary .section-head { grid-template-columns: 44px 1fr; gap: 16px; padding: 24px 0; }
  .section-head h2,
  .section-head .lede { padding-left: 28px; }
  .subhead { grid-template-columns: 44px 1fr; gap: 16px; margin-top: 64px; }
  .minor-head { grid-template-columns: 44px 1fr; gap: 16px; }
  .grid-auto { grid-template-columns: 1fr; }
  .fact { grid-template-columns: 1fr; gap: 6px; }
  .press-item { grid-template-columns: 1fr; gap: 8px; }
  /* The framed desktop bar becomes a plain drop-down panel on mobile. */
  .site-header nav { border: 0; background: none; }
  .nav-links {
    position: fixed;
    top: 76px;
    left: 0; right: 0;
    background: var(--bg);
    flex-direction: column;
    padding: 12px var(--gutter) 24px;
    gap: 0;
    border-bottom: 1px solid var(--rule);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s linear;
  }
  .nav-links li + li { border-left: 0; border-top: 1px solid var(--rule-soft); }
  .nav-links a { display: block; padding: 16px 0; font-size: 14px; }
  .nav-links a.active { background: none; color: var(--accent); }
  .nav-links.open { opacity: 1; pointer-events: auto; }
  .nav-toggle { display: block; }
}

@media (max-width: 560px) {
  :root { --gutter: 20px; }
  .gallery-strip { grid-template-columns: repeat(2, 1fr); }
  .gallery-strip img { height: 170px; }
}
