/* ==========================================================================
   omarigruzzman.com — site.css
   Phase 1: fonts, tokens, nav, hero, footer, legal/prose pages.
   Palette values are the sampled ones from CLAUDE.md §3.2.
   ========================================================================== */

/* ---------------------------------------------------------------- fonts -- */

@font-face {
  font-family: "Gruzzman";
  src: url("../fonts/Gruzzman-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* Montserrat ships as a variable font: one file covers 400–600. */
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-latin-ext.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-latin-italic.woff2") format("woff2");
  font-weight: 400 700;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-latin-ext-italic.woff2") format("woff2");
  font-weight: 400 700;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --------------------------------------------------------------- tokens -- */

:root {
  --bg: #121010;
  --text: #C5B6AF;
  --text-muted: #918379;
  --heading: #C5B6AF;
  --accent: #D9CBB9;
  --accent-strong: #F0E8DC;
  --active: #00FF25;

  --line: rgba(217, 203, 185, 0.25);
  --field: rgba(217, 203, 185, 0.55);
  --btn-fill: rgba(217, 203, 185, 0.06);
  /* --btn-fill composited over --bg, as an opaque colour. Only the autofill
     override needs this: masking Chrome's yellow takes an inset box-shadow,
     and a translucent colour would let the yellow through. */
  --field-fill-solid: #1E1B1A;
  --btn-border: rgba(217, 203, 185, 0.40);

  /* One glass treatment, used by the playlist panel, the mobile control block
     and the desktop transport strip, so they cannot drift apart. */
  --glass-bg: rgba(24, 20, 19, 0.34);
  --glass-blur: blur(24px) saturate(120%);
  --glass-border: rgba(240, 232, 220, 0.10);
  /* The desktop player strip is the only glass surface that sits on artwork.
     Over the brightest blurred patch of the Collapse cover (#9DA8A8) the
     0.34 tint leaves the 12px times at 3.82:1 and the transport glyphs at
     2.89:1 — both below §10. 0.45 is the first value that clears 4.5:1 for
     the text and 3:1 for the glyphs (4.83 and 3.50). The panels that sit on
     the page background keep --glass-bg. */
  --glass-bg-strip: rgba(24, 20, 19, 0.45);

  /* The mini bar is the one surface that has to read as floating above the
     page rather than sitting in it. Same tint as the strip, carried further so
     the bar depends less on whatever is behind it: over the page background
     that is barely lighter (rgb(21,18,17) to rgb(22,19,18)), but over bright
     blurred content it takes the muted label from 1.60:1 to 2.95:1. The lift
     itself is the border and the shadow, not the fill. */
  --minibar-fill: rgba(24, 20, 19, 0.72);
  /* Cast upward onto the page. Black on a near-black page is a small step, but
     it is the only direction that reads as depth here — lightening the halo
     would look like a glow. */
  --shadow-lift: 0 -14px 34px rgba(0, 0, 0, 0.55);

  /* The artwork stage's aspect ratio, from reference/2_Listen.png (1586x671).
     Not 16:9 — the mockup's stage is wider, and the admin crops artwork to it. */
  --stage-ratio: 2.3636;

  --font-display: "Gruzzman", "Arial Narrow", sans-serif;
  --font-body: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  --maxw: 1600px;
  --gutter: 68px;
  --nav-h: 72px;
  --rhythm: 80px;

  --radius-card: 20px;
  --radius-control: 6px;
  --control-h: 48px;

  --t-fast: 150ms;
}

@media (max-width: 699px) {
  :root {
    --gutter: 20px;
    --rhythm: 48px;
    --control-h: 52px;   /* taller touch targets on phones */
  }
}

/* ---------------------------------------------------------------- reset -- */

*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

/* Safari honours scroll-behavior only on the element that actually scrolls.
   `overflow-x: hidden` on body promotes body to a scroll container, which is
   why anchor scrolling jumped there. `clip` prevents the horizontal overflow
   without creating one. Declared on both elements so whichever ends up
   scrolling is covered. */
html, body { scroll-behavior: smooth; }

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

/* Grain (CLAUDE.md §3.2). A fixed overlay rather than a scrolling background:
   pinned to the viewport it is rasterised once and composited, so scrolling
   never repaints it. z-index -1 keeps it behind every section but in front of
   body's flat --bg, which the tile is calibrated against. The tile carries the
   base colour itself, so it needs no opacity — see grain.svg for the
   measurements. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: url("../img/grain.svg");
  background-repeat: repeat;
  background-size: 512px 512px;
  pointer-events: none;
}

img, svg { max-width: 100%; display: block; }

/* Author styles above outrank the UA stylesheet's [hidden] rule, which is how
   an SVG with the hidden attribute was still being painted. Restore it. */
[hidden] { display: none !important; }

a { color: inherit; }

h1, h2, h3, h4, p, figure { margin: 0; }

button { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--accent-strong);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--bg);
  color: var(--text);
  padding: 12px 20px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-control);
}
.skip-link:focus { left: var(--gutter); top: 12px; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------- layout -- */

.wrap {
  width: 100%;
  /* --maxw is the CONTENT width (§3.3), so the box is content + both gutters.
     At a 1723px viewport this yields content starting at exactly 68px, which is
     what reference/1_Hero.png measures. */
  max-width: calc(var(--maxw) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--rhythm);
  scroll-margin-top: var(--nav-h);
}

/* ------------------------------------------------------------ typography -- */

.h-display,
.h-section {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--heading);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.05;
}

/* Section headings — LISTEN, SELECTED WORK, THE COMPOSER … */
.h-section { font-size: clamp(28px, 2.55vw, 44px); }

/* Muted subline directly under a heading */
.subline {
  color: var(--text-muted);
  font-size: clamp(16px, 1.15vw, 20px);
  margin-top: 14px;
  max-width: 62ch;
}

/* ------------------------------------------------------------------ nav -- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--nav-h);
  z-index: 100;
  background: transparent;
}

/* Transparent over the hero, but once the page moves the bar needs its own
   ground or content scrolls through it. A short gradient keeps the edge soft. */
.nav::before {
  content: "";
  position: absolute;
  inset: 0 0 -18px 0;
  z-index: -1;
  background: linear-gradient(to bottom,
              var(--bg) 0%, var(--bg) 72%, rgba(18, 16, 16, 0) 100%);
  opacity: 0;
  transition: opacity 220ms ease;
  pointer-events: none;
}
.nav.is-scrolled::before { opacity: 1; }

.nav__inner {
  height: 100%;
  max-width: calc(var(--maxw) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.nav__wordmark { display: block; flex: none; }
.nav__wordmark img { width: 224px; height: auto; }

.nav__links { display: flex; align-items: center; gap: 34px; }

.nav__link {
  font-weight: 500;
  font-size: 18px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--text);
  transition: color var(--t-fast) ease;
}
.nav__link:hover { color: var(--accent-strong); }

.nav__burger { display: none; }

/* Mobile nav */
@media (max-width: 899px) {
  .nav__links { display: none; }

  .nav__wordmark img { width: 168px; }

  .nav__burger {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
  }
  .nav__burger span {
    display: block;
    width: 26px;
    height: 2px;
    margin-inline: auto;
    background: var(--text);
    transition: transform var(--t-fast) ease, opacity var(--t-fast) ease;
  }
  .nav__burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav__burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
  .nav__burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
}

.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--bg);
  background-image: url("../img/grain.svg");
  background-size: 512px 512px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mobile-menu[hidden] { display: none; }

.mobile-menu__links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
}
.mobile-menu__link {
  font-family: var(--font-display);
  font-size: 30px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--heading);
}

body.is-menu-open { overflow: hidden; }

/* -------------------------------------------------------------- buttons -- */
/* Dark translucent fill + hairline border. Primary and secondary differ by
   border weight, not by fill (CLAUDE.md §3.2). */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-h);
  padding: 0 26px;
  border-radius: var(--radius-control);
  background: var(--btn-fill);
  color: var(--text);
  font-weight: 500;
  font-size: 18px;
  line-height: 1.2;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  transition: border-color var(--t-fast) ease, color var(--t-fast) ease,
              background-color var(--t-fast) ease;
}

.btn--primary { border: 1px solid var(--accent); }
.btn--secondary { border: 1px solid var(--btn-border); }

.btn:hover {
  color: var(--accent-strong);
  border-color: var(--accent-strong);
  background: rgba(217, 203, 185, 0.10);
}

@media (max-width: 699px) {
  .btn { width: 100%; min-height: 52px; }
  /* The button fills the column here, so the group must too — otherwise its
     fit-content width and the button's 100% chase each other. */
  .hero__cta { width: 100%; }
}

/* ----------------------------------------------------------------- hero -- */

.hero {
  /* The hero is two columns that must never intersect:
       [ text .............. ][ gap ][ portrait ]
     --portrait-w reserves the portrait column; the text column is whatever
     is left. Type inside the text column is sized in cqw, so it can never
     outgrow the space it has. */
  /* Desktop hero (reference/1_Hero.png). The hero now sits in the same centred
     container as the nav and every section, and all its measures are fractions
     of that CONTAINER width via cqw — so the hero's left edge aligns with the
     wordmark and the section headings, and it stops scaling at 1600px exactly
     where everything else does.

     Ratios are the reference's: name 3.0, subtitle 1.6, quote 4.3, portrait
     38.8, with tops at 9.6 / 14 / 23 and the button at 46. */
  --hero-gap: clamp(24px, 2.4vw, 40px);

  /* Advance width of the quote's longest line, "doesn't describe a moment.",
     in em at 700 italic. Measured, not guessed — it decides the clearance. */
  --quote-em: 14.42;
  --quote-floor: 44px;

  --hero-name-top: 9.6cqw;
  --hero-subtitle-top: 14cqw;
  --hero-quote-top: 23cqw;
  --hero-button-top: 46cqw;
  --hero-name-size: 3cqw;
  --hero-subtitle-size: 1.6cqw;

  /* Height follows the content when 55cqw would leave dead space under the
     button — which is what happened between 900 and 1100px. */
  --hero-h: max(55cqw, calc(var(--hero-button-top) + 128px));

  /* Portrait: bottom-aligned, its top edge 40px above the name's, exactly as
     the reference draws it. Height is what that alignment implies; width
     follows from the aspect ratio. */
  --portrait-h-ideal: calc(var(--hero-h) - var(--hero-name-top) + 40px);
  --portrait-w-ideal: calc(var(--portrait-h-ideal) * 0.8306);

  /* Clearance, in the order asked for. The quote gives way first — down to
     --quote-floor — and only then does the portrait. */
  --quote-fit: calc((100cqw - var(--portrait-w-ideal) - var(--hero-gap)) / var(--quote-em));
  /* min() on the outside: the floor stops the quote being *reduced* past 44px
     to resolve an overlap, but must never push it above its natural 4.3cqw —
     which below ~1250px would have inflated the quote and squeezed the
     portrait for no reason. */
  --hero-quote-size: min(4.3cqw, max(var(--quote-floor), var(--quote-fit)));

  --portrait-w: min(var(--portrait-w-ideal),
                    calc(100cqw - var(--hero-gap) - var(--hero-quote-size) * var(--quote-em)));
  --portrait-h: calc(var(--portrait-w) / 0.8306);

  position: relative;
  min-height: 100vh;      /* fallback for browsers without svh */
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-top: var(--nav-h);
  overflow: hidden;
}

.hero__inner {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: calc(var(--maxw) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: 60px;
}



.hero__text {
  container-type: inline-size;
  max-width: calc(100% - var(--portrait-w) - var(--hero-gap));
}

.hero__name {
  font-size: clamp(26px, 6.85cqw, 49px);
  margin-bottom: 12px;
}

.hero__subtitle {
  font-size: clamp(17px, 4.1cqw, 36px);
  font-weight: 400;
  color: var(--text);
  line-height: 1.3;
}

/* The quote is Montserrat 700 italic at every viewport (reference/1_Hero.png).
   Declared here and nowhere else — no breakpoint sets font-weight on it, so
   this rule is the single source. */
.hero__quote {
  font-style: italic;
  font-weight: 700;
  font-size: clamp(24px, 7cqw, 78px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  margin-top: clamp(40px, 7vw, 128px);
  margin-bottom: clamp(40px, 8vw, 148px);
}

/* The CTA group shrink-wraps to the button, which is what lets the meta line
   below centre on the button's axis instead of on the text column. */
.hero__cta { width: fit-content; }

/* Meta line under the CTA, set like the nav (§3.4): uppercase, tracked, muted.
   Written in sentence case and uppercased here rather than in the markup, so a
   screen reader does not spell it out letter by letter. */
.hero__meta {
  margin-top: 18px;
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  /* Centred within the group, i.e. on the button above it. The last letter's
     trailing letter-space would otherwise push the line half a space left. */
  text-align: center;
  text-indent: 0.08em;
}

.hero__portrait {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(32px, 11%, 116px);
  z-index: 1;
  width: var(--portrait-w);
  height: auto;
  /* On short viewports the height caps first; object-fit keeps the aspect
     ratio and pins the image to the right of its reserved column. */
  max-height: calc(100svh - var(--nav-h) - 40px);
  object-fit: contain;
  object-position: bottom right;
  pointer-events: none;
  user-select: none;
}

/* Desktop hero. Declared after the base .hero__portrait rule — a media query
   adds no specificity, so an earlier block would simply lose. */
@media (min-width: 900px) {
  /* The height lives on .hero__inner, not here: cqw only resolves inside the
     query container, and .hero is outside it — used here it silently fell back
     to the viewport, which is the whole bug this rebuild set out to fix. */
  /* The hero always fills at least the viewport, so the next section is never
     visible before scrolling — on a 3440x1440 display the 880px stage left
     560px of LISTEN showing. The stage keeps its own height (--hero-h) and all
     the proportional geometry inside it; flex centring only distributes the
     leftover space around it, so nothing about the composition changes on
     viewports where the content already fills the height.
     No padding-top: the nav is fixed and overlays, so there is nothing to
     subtract. */
  .hero {
    display: flex;
    align-items: center;
    height: auto;
    min-height: 100vh;    /* fallback for browsers without svh */
    min-height: 100svh;
    padding-top: 0;
  }

  /* The same box as .wrap: centred, capped at --maxw, gutters either side.
     Being the query container makes 100cqw its content width. */
  /* An element never queries itself, so cqw on .hero__inner would resolve
     against the viewport. The height therefore comes from .hero__text, which
     IS inside the container, and .hero__inner takes it from there. */
  .hero__inner {
    position: relative;
    inset: auto;
    height: auto;
    width: 100%;
    max-width: calc(var(--maxw) + 2 * var(--gutter));
    margin-inline: auto;
    padding-inline: var(--gutter);
    padding-block: 0;
    container-type: inline-size;
  }

  .hero__text {
    position: relative;
    height: var(--hero-h);
    max-width: none;
  }

  .hero__name,
  .hero__subtitle,
  .hero__quote {
    position: absolute;
    left: 0;
    right: 0;
    margin: 0;
  }

  .hero__name {
    top: calc(var(--hero-name-top) - 0.13em);
    font-size: var(--hero-name-size);
  }
  .hero__subtitle {
    top: calc(var(--hero-subtitle-top) - 0.035em);
    font-size: var(--hero-subtitle-size);
  }
  .hero__quote {
    /* No leading subtracted: at line-height 1.2 Montserrat's ascender slightly
       overflows the line box, so the glyphs already start at the box top. */
    top: var(--hero-quote-top);
    font-size: var(--hero-quote-size);
  }
  /* The group is placed, and the button and meta line sit inside it in flow —
     so the meta line stays centred on the button without either needing to
     know the other's width. max-width applies the hero's clearance rule: the
     group stops where the portrait column begins. The name, subtitle and quote
     clear the portrait by being sized in cqw; this group is fixed-size type,
     so the boundary has to be drawn for it. */
  .hero__cta {
    position: absolute;
    top: var(--hero-button-top);
    left: 0;
    max-width: calc(100cqw - var(--portrait-w) - var(--hero-gap));
  }

  .hero__meta { margin-top: 20px; }

  .hero__portrait {
    right: var(--gutter);
    bottom: 0;
    top: auto;
    width: var(--portrait-w);
    height: var(--portrait-h);
    max-height: none;
    object-fit: contain;
    object-position: bottom right;
  }
}

@media (max-width: 899px) {
  /* Block layout, so <picture> stops being a flex item of .hero and the
     portrait can take its natural width above the text. */
  .hero {
    display: block;
    min-height: 100vh;    /* fallback for browsers without svh */
    min-height: 100svh;
    padding-bottom: 40px;
  }
  .hero > picture {
    display: block;
    width: 100%;
    max-width: 420px;
    margin: 0 auto 28px;
    padding-inline: var(--gutter);
  }
  .hero__portrait {
    position: static;
    width: 100%;
    height: auto;
  }
  .hero__inner { padding-block: 0 0; }
  .hero__text { max-width: none; }
  /* Same 700 italic as desktop; 26px so the first sentence sets on two lines
     and "moment." is not orphaned at this weight. */
  .hero__quote {
    max-width: none;
    font-size: 26px;
  }
  .hero__name { font-size: 32px; }
  .hero__subtitle { font-size: 21px; }
}

/* .br-desktop applies only to the desktop layout; .br-always on every viewport.
   Together the quote reads "The right sound / doesn't describe a moment. / It
   creates one." on desktop and drops to two lines on mobile, breaking at the
   same sentence boundary. The text is correct with the breaks removed. */
.br-desktop { display: none; }
.br-always  { display: inline; }
@media (min-width: 900px) { .br-desktop { display: inline; } }

/* --------------------------------------------------------------- player -- */
/* One shared audio engine drives this player and (phase 3) the work list.
   Desktop: the artwork is full-bleed across the container and the glass
   playlist floats on top of it at the right — that overlay is what the
   backdrop blur reads against. Mobile: artwork on top, one glass block below. */

.listen .subline { margin-bottom: 34px; }

/* The player must never be taller than the space under the heading, or a nav
   click to #listen lands with its lower half below the fold.

   Two levers, in that order. First the SPACING above the player gives way: on
   a short viewport the section's top padding and the subline gap shrink, which
   buys ~70px without touching the player. Only if that is still not enough
   does the WIDTH cap engage — it caps the height too, because the stage is
   sized by its aspect ratio, but it also makes the player narrower than the
   nav and the Selected Work cards, so it is the last resort rather than the
   first. The min() below is what sequences them: while the compressed spacing
   leaves room, the cap exceeds 100% and nothing changes.

   --listen-head-h is an estimate of the heading block (heading + subline +
   margins) at desktop sizes; it only has to be close, since a few px of error
   move the cap by a few px of width. */
@media (min-width: 900px) {
  .listen {
    --listen-pad-top: var(--rhythm);
    --listen-head-h: 130px;
    --player-avail: calc(100svh - var(--nav-h) - var(--listen-pad-top) - var(--listen-head-h));
    padding-top: var(--listen-pad-top);
  }
  .player {
    max-width: min(100%, calc(var(--player-avail) * var(--stage-ratio)));
  }
}

/* Short desktop viewports (a 1512x800 laptop is the case this is cut for):
   compress the spacing so the player keeps the full container width.

   The threshold is where full width stops fitting with the roomy spacing:
   nav 72 + padding 80 + heading block 130 + a 1512-wide stage 582 = 864. It is
   set just above that, so the compressed and uncompressed sides overlap and
   neither leaves a band of heights where the cap trims a few px off the width. */
@media (min-width: 900px) and (max-height: 879px) {
  .listen {
    --listen-pad-top: 40px;
    --listen-head-h: 96px;      /* the same block with the tightened gaps */
  }
  .listen .subline { margin-top: 10px; margin-bottom: 14px; }
}

.player {
  position: relative;
}

.player__stage {
  position: relative;
  aspect-ratio: var(--stage-ratio);
  overflow: hidden;
  background: #0d0b0b;
}

.player__art {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 200ms ease;
}
.player.is-swapping .player__art { opacity: 0; }

.player__mark {
  position: absolute;
  top: 3.2%;
  left: 2.3%;
  width: 5.6%;
  min-width: 44px;
  height: auto;
  opacity: 0.1;
  pointer-events: none;
}

.player__share {
  position: absolute;
  top: 2.4%;
  right: 27.6%;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: 0;
  color: var(--accent);
  cursor: pointer;
  transition: color var(--t-fast) ease;
  z-index: 3;
}
.player__share:hover { color: var(--accent-strong); }
.player__share svg { width: 21px; height: 21px; }

.player__copied {
  position: absolute;
  top: 3.4%;
  right: calc(27.6% + 52px);
  z-index: 3;
  font-size: 13px;
  color: var(--accent-strong);
  opacity: 0;
  transition: opacity 200ms ease;
  pointer-events: none;
  white-space: nowrap;
}
.player__copied.is-shown { opacity: 1; }

/* Title sits bottom-left inside the artwork, above the controls. The control
   heights are pixel values (they do not scale with the artwork) so the title
   never collides with them at narrow widths. */
.player {
  /* Two separate glass surfaces at the foot of the artwork: a slim strip
     carrying the times and the seek bar, and a compact pill around the
     transport. Splitting them keeps far more of the cover visible than one
     tall band, and the transport reads as a control rather than a toolbar. */
  --progress-h: 32px;
  --pill-h: 48px;
  --controls-gap: 10px;       /* between the progress strip and the pill */
  --controls-h: calc(var(--progress-h) + var(--controls-gap) + var(--pill-h));
  --strip-bottom: 2%;
  --strip-gap: 14px;          /* between the controls and the title above them */
  /* Left-aligned, never centred: the page is left-aligned throughout, so a
     centred player reads as a mistake once the height cap scales it down. */
  margin-inline: 0 auto;
}

.player__controls {
  position: absolute;
  left: 1.1%;
  right: 27.5%;               /* stops short of the playlist panel */
  bottom: var(--strip-bottom);
  z-index: 2;
}

.player__title {
  position: absolute;
  left: 2.3%;
  bottom: calc(var(--strip-bottom) + var(--controls-h) + var(--strip-gap));
  font-size: clamp(18px, 2.1vw, 36px);
  color: var(--accent-strong);
  transition: opacity 200ms ease;
  z-index: 2;
}
.player.is-swapping .player__title { opacity: 0; }

.player__progress {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 6px;
}

.player__time {
  flex: none;
  font-size: 12px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--accent-strong);
}

.player__bar {
  position: relative;
  flex: 1 1 auto;
  height: 22px;              /* generous hit area; the line itself is 2px */
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}
.player__bar::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 2px;
  background: rgba(240, 232, 220, 0.35);
}
.player__bar-fill {
  position: absolute;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--accent-strong);
}
.player__bar-knob {
  position: absolute;
  left: 0;
  width: 11px;
  height: 11px;
  margin-left: -5.5px;
  border-radius: 50%;
  background: var(--accent-strong);
}

.player__transport {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 30px;
}

.player__btn {
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: 0;
  color: var(--accent-strong);
  cursor: pointer;
  opacity: 0.75;
  transition: opacity var(--t-fast) ease;
}
.player__btn:hover { opacity: 1; }
.player__btn svg { width: 20px; height: 20px; }
.player__btn--play svg { width: 34px; height: 34px; }

/* Exactly one of the two glyphs is ever displayed. */
.player__btn--play .player__icon-pause { display: none; }
.player.is-playing .player__btn--play .player__icon-play  { display: none; }
.player.is-playing .player__btn--play .player__icon-pause { display: block; }

/* Glass playlist panel */
.player__panel {
  position: absolute;
  top: 2%;
  right: 0.6%;
  bottom: 2%;
  width: 26%;
  z-index: 2;
  padding: 22px 22px 10px;
  border-radius: var(--radius-card);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.player__panel-title {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.12em;
  color: var(--accent-strong);
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(240, 232, 220, 0.28);
  margin-bottom: 6px;
}

.player__list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1 1 auto;
}

.track {
  width: 100%;
  min-height: 48px;                 /* §5.2 hard number / touch target */
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: var(--accent-strong);
  transition: color var(--t-fast) ease;
}
.player__list li + li .track { margin-top: 0; }

/* Title over an optional description line. The duration and chevron stay put
   and centre themselves on the pair, because .track is align-items: center. */
.track__body  { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.track__title { font-size: 16px; font-weight: 500; }
/* Its own colour, so it stays muted under the active row's green and the
   hover white, both of which reach the title by inheritance. */
.track__desc  { font-size: 13px; font-weight: 400; color: var(--text-muted); }
.track__dur   { flex: none; font-size: 13px; font-variant-numeric: tabular-nums; }
.track__chev  { flex: none; font-size: 15px; opacity: 0.8; }

.track:hover { color: #fff; }
.track[aria-current="true"] { color: var(--active); }

.listen__platform { margin-top: 32px; }

/* Launch state 1 fills the platform button's slot with a plain line, so the
   spacing under the player is the same in both states. Left edge is the wrap
   gutter, which is where the player starts too. */
.listen__note {
  margin-top: 32px;
  margin-bottom: 0;
  color: var(--text-muted);
  font-size: 16px;
}

@media (min-width: 900px) {
  /* Reference spacing: four rows on a ~99px pitch inside the panel. */
  .player__list li { display: flex; }
  .track { min-height: clamp(48px, 6.2vw, 99px); }

  /* The controls block is only a positioning frame — the glass lives on its
     two children, so the artwork shows through between them. */
  .player__controls {
    display: flex;
    flex-direction: column;
    align-items: center;      /* centres the pill; the strip stretches past it */
    gap: var(--controls-gap);
    height: var(--controls-h);
  }

  /* (a) The progress strip: full width of the controls column, tall enough for
     the 11px knob and no taller. */
  .player__progress {
    align-self: stretch;
    height: var(--progress-h);
    margin-bottom: 0;
    padding-inline: 18px;
    gap: 14px;
  }

  /* (b) The transport pill: auto width, sized by the three buttons. */
  .player__transport {
    height: var(--pill-h);
    gap: 14px;
    padding-inline: 10px;
  }

  /* Both are glass, and both sit on artwork rather than on the page
     background — hence --glass-bg-strip rather than --glass-bg. See the
     contrast note at the token. */
  .player__progress,
  .player__transport {
    border-radius: 999px;
    background: var(--glass-bg-strip);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border: 1px solid var(--glass-border);
  }

  .player__bar { height: 20px; }
}

/* ---- player, mobile ---- */
@media (max-width: 899px) {
  .player__stage {
    aspect-ratio: 4 / 3;
    border-radius: 4px 4px 0 0;
  }
  .player__mark { width: 12%; min-width: 40px; top: 3%; left: 4%; }
  .player__share { top: 2%; right: 2%; }
  .player__copied { top: 3%; right: 54px; }

  /* Title stays over the artwork; the controls drop into the glass block,
     as in reference/mobile_player.png. The controls and the panel share one
     glass surface, so they read as a single element. */
  .player__title {
    left: 5%;
    bottom: 5%;
    font-size: 20px;
  }

  .player__controls,
  .player__panel {
    background: var(--glass-bg);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border-inline: 1px solid var(--glass-border);
  }

  .player__controls {
    position: static;
    padding: 18px 18px 6px;
    border-radius: 0;
    border-top: 0;
  }

  .player__panel {
    position: static;
    width: auto;
    padding: 4px 18px 14px;
    border-radius: 0 0 12px 12px;
    border-top: 0;
    border-bottom: 1px solid rgba(240, 232, 220, 0.10);
  }

  .player__transport { gap: 26px; }
  .player__btn--play svg { width: 30px; height: 30px; }

  .player__panel-title { font-size: 14px; padding-bottom: 10px; }
  .track { min-height: 44px; }
  .track__title { font-size: 15px; }
  .track__desc { font-size: 12px; }
  .track__dur { font-size: 12px; }
}

/* ----------------------------------------------------------- mini player -- */

/* Same glass as the player's transport strip, because it is the same kind of
   surface: a control layer floating over the page. Tokens only — --glass-bg-strip,
   --glass-border, --glass-blur, --accent-strong, --text-muted, --line,
   --accent, --control-h, --t-fast. */
.minibar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 80;                 /* under the nav (90) and the mobile menu */
  background: var(--minibar-fill);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  /* --line rather than --glass-border: at 0.25 against 0.10 the hairline is
     the main thing separating the bar from the page. */
  border-top: 1px solid var(--line);
  box-shadow: var(--shadow-lift);
  /* Clears the iOS home indicator without padding the bar on anything else. */
  padding-bottom: env(safe-area-inset-bottom, 0px);

  /* Fades rather than slides, so nothing moves. Driven by a class between two
     applied states, not by @starting-style off display:none — that only fires
     on the first render and was measured leaving the bar laid out at opacity 0
     in both Chrome headless and the preview, i.e. permanently invisible.
     visibility rather than display keeps it out of the tab order and the
     accessibility tree while hidden, and still transitions. */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--t-fast) ease, visibility 0s linear var(--t-fast);
}

.minibar.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--t-fast) ease, visibility 0s;
}

/* Without JS the bar never appears at all. */
.minibar[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .minibar, .minibar.is-visible { transition: none; }
}

/* The bar overlays fixed, so the page needs room under it or the footer's last
   line sits behind the glass. */
body.has-minibar { padding-bottom: 76px; }

.minibar__inner {
  max-width: calc(var(--maxw) + 2 * var(--gutter));
  margin-inline: auto;
  padding: 10px var(--gutter);
  display: flex;
  align-items: center;
  gap: 16px;
}

.minibar__toggle {
  flex: none;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: 1px solid var(--btn-border);
  border-radius: 50%;
  color: var(--accent-strong);
  cursor: pointer;
  transition: border-color var(--t-fast) ease, color var(--t-fast) ease;
}
.minibar__toggle:hover { border-color: var(--accent-strong); }
.minibar__toggle svg { width: 16px; height: 16px; }
.minibar__icon-pause { display: none; }
.minibar.is-playing .minibar__icon-play  { display: none; }
.minibar.is-playing .minibar__icon-pause { display: block; }

/* Title doubles as the way back to the player that owns the track. */
.minibar__id {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.minibar__title {
  font-size: 14px;
  font-weight: 600;
  color: var(--accent-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--t-fast) ease;
}

/* Green while playing and back to the bright tone when paused — the same rule
   and the same token as .workrow.is-playing .workrow__title, so the bar says
   what the Selected Work rows say. The section label underneath stays muted;
   two green lines would spend the cue on nothing.

   The LISTEN playlist marks its row green through aria-current instead, which
   means selected rather than playing, so it stays green through a pause. The
   two lists already disagree; the bar follows the one whose green means
   playback, which is what the bar's own play button is reporting. */
.minibar.is-playing .minibar__title { color: var(--active); }
.minibar__id:hover .minibar__title { text-decoration: underline; text-underline-offset: 3px; }
.minibar__section {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.minibar__time {
  flex: none;
  font-size: 12px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--accent-strong);
}

/* Same seek behaviour and the same hit area as the players' bars. */
.minibar__bar {
  position: relative;
  flex: 1 1 auto;
  min-width: 80px;
  height: 20px;
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}
.minibar__bar::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 2px;
  background: var(--line);
}
.minibar__fill {
  position: absolute;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--accent-strong);
}

.minibar__volume {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-muted);
}
.minibar__volume[hidden] { display: none; }
.minibar__volume svg { width: 17px; height: 17px; }
.minibar__vol {
  width: 84px;
  accent-color: var(--accent);
  cursor: pointer;
}

.minibar :focus-visible {
  outline: 2px solid var(--accent-strong);
  outline-offset: 2px;
}

@media (max-width: 699px) {
  .minibar__inner { padding-inline: var(--gutter); gap: 12px; }
  .minibar__section { display: none; }   /* the title alone carries it here */
  body.has-minibar { padding-bottom: 68px; }
}

/* --------------------------------------------------------- selected work -- */

/* Sub-heading sits one step below the section heading. The mockup measures a
   cap-height ratio of 0.633 between them (19px vs 30px). */
.h-sub { font-size: clamp(20px, 1.61vw, 28px); }

.workcards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  margin-top: 40px;
}

.workcard {
  display: flex;
  flex-direction: column;
  padding: 32px;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
}

.workcard__title {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: clamp(17px, 1.28vw, 22px);
  letter-spacing: 0.01em;
  color: var(--text);
  margin-bottom: 18px;
}

.workcard__meta {
  font-size: 15px;
  color: var(--text-muted);
  margin-bottom: 14px;
}

.workcard__role {
  font-style: italic;
  font-size: 17px;
  color: var(--text);
  margin-bottom: 26px;
}

/* Free space collects here, so the divider and credit stay bottom-aligned
   across both cards (§5.3). */
.workcard__body {
  flex: 1 1 auto;
  font-size: 16px;
  line-height: 1.62;
  color: var(--text);
}

.workcard__foot { margin-top: 28px; }

.workcard__divider {
  height: 0;
  margin: 0 0 24px;
  border: 0;
  border-top: 1px solid var(--line);
}

.workcard__credit {
  font-size: 15px;
  color: var(--text-muted);
}

/* Fixed-height slot: card 2 has no link, and an inline-block's line box would
   not match the anchor's exactly. A block of known height makes the divider
   and credit land on the same line in both cards. */
.workcard__linkslot {
  margin-top: 22px;
  font-size: 16px;
  line-height: 1.65;
  height: 1.65em;
}

.workcard__link {
  display: inline-block;
  font: inherit;
  color: var(--text);
  text-decoration: none;
  transition: color var(--t-fast) ease;
}
.workcard__link:hover { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 4px; }

/* ---- the work audio list ---- */

.work__subhead { margin-top: 64px; }
.work .subline { margin-bottom: 44px; }

.worklist { display: flex; flex-direction: column; gap: 36px; }

.workrow {
  display: flex;
  align-items: flex-start;
  gap: 32px;
}

.workrow__btn {
  flex: none;
  width: 40px;
  height: 40px;
  margin-top: 2px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: 1px solid var(--btn-border);
  border-radius: 50%;
  color: var(--text);
  cursor: pointer;
  transition: color var(--t-fast) ease, border-color var(--t-fast) ease;
}
.workrow__btn:hover { color: var(--accent-strong); border-color: var(--accent-strong); }
.workrow__btn svg { width: 15px; height: 15px; }

/* Exactly one glyph, driven by the row's state class. */
.workrow__icon-pause { display: none; }
.workrow.is-playing .workrow__icon-play  { display: none; }
.workrow.is-playing .workrow__icon-pause { display: block; }

.workrow__body { flex: 1 1 auto; min-width: 0; }

/* Compact rows: the page's 1.65 body leading would make these ~40% taller
   than the mockup, which sets the three lines tightly. */
.workrow__title {
  font-weight: 600;
  font-size: 15px;
  line-height: 1.25;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
  transition: color var(--t-fast) ease;
}
.workrow.is-playing .workrow__title { color: var(--active); }

.workrow__context {
  font-size: 12px;
  line-height: 1.25;
  color: var(--text-muted);
  margin-top: 2px;
}

.workrow__progress {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 6px;
}

.workrow__time {
  flex: none;
  font-size: 12px;
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.workrow__bar {
  position: relative;
  flex: 1 1 auto;
  height: 1px;                  /* the rule itself, per §5.3 */
  background: var(--line);
  cursor: pointer;
  touch-action: none;
}
/* Pointer target extended well beyond the 1px rule without adding any layout
   height, so the row keeps the mockup's spacing and is still easy to hit. */
.workrow__bar::after {
  content: "";
  position: absolute;
  inset: -12px 0;
}
.workrow__fill {
  position: absolute;
  left: 0;
  top: 0;
  height: 1px;
  width: 0;
  background: var(--accent-strong);
}

@media (max-width: 899px) {
  .workcards { grid-template-columns: 1fr; gap: 24px; }
  .workcard { padding: 24px; }
  .workcard__linkslot { height: auto; }   /* nothing to align to when stacked */
  .work__subhead { margin-top: 48px; }
  .worklist { gap: 28px; }
  .workrow { gap: 18px; }
}

/* ---------------------------------------------------------- the composer -- */

.about__grid { margin-top: 34px; }

.about__text {
  /* A readable measure rather than the full container: the old 1560px cap put
     around 113 characters on a line at 1512px, where 800px puts 66 (measured).
     Left-aligned in the wrap, so it still starts on the heading's gutter. */
  max-width: 800px;
}

/* Facts beside the prose. Quiet by construction: muted, 15px, no bullets and
   no heading, so it reads as metadata rather than a second column of copy. */
.about__facts {
  margin: 40px 0 0;
  padding: 0;
  list-style: none;
  font-size: 15px;
  line-height: 2.1;
  color: var(--text-muted);
  max-width: 800px;
}

/* Two columns from 1150px up; below that the grid collapses and the facts fall
   in after the prose. align-items: start puts the first fact level with the top
   of the first paragraph.

   The prose track is minmax rather than a flat 800px, and the facts track has a
   floor rather than sizing to whatever is left. The longest fact — "Earlier
   works credited as Omari Mshvenieradze" — sets on 351px, so the pair needs
   600 + 48 + 351 = 999px of content, which is 1144px of viewport once the
   gutters are paid for; 1150 is that with a little slack. Below it the prose
   would have to drop under a 50-character measure to keep the facts on one
   line each, so the grid stacks instead. Above it the prose takes its full
   800px everywhere there is room. */
@media (min-width: 1150px) {
  .about__grid {
    display: grid;
    grid-template-columns: minmax(600px, 800px) minmax(360px, 1fr);
    column-gap: clamp(48px, 4vw, 110px);
    align-items: start;
  }
  .about__facts { margin-top: 0; }
}

.about__text p {
  font-size: clamp(17px, 1.45vw, 25px);
  line-height: 1.92;
  color: var(--text);
}
.about__text p + p { margin-top: 1.35em; }

/* ------------------------------------------------------- clearance checklist -- */

/* Framed like the Selected Work cards — same border, radius and padding, and
   the same transparent fill, so the grain shows through as it does there.
   Width stays the §5.5 centred 910px measure; the frame is the only change. */
/* Full container width, so the card's outer edges land on the same two lines
   as the pair of Selected Work cards above it. At 910px it read as a square
   and wrapped the heading; landscape is the section's own proportion.
   Padding scales with the width rather than stepping, so the wider card gets
   a wider inset and the mobile card keeps the cards' 24px. */
.checklist__block {
  padding: clamp(24px, 2.6vw, 44px);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
}

/* "THE CLEARANCE CHECKLIST" is long and this block is narrow, so the standard
   section size would wrap it. The mockup steps it down to keep it on one
   line (cap 30 against 37 elsewhere) — same ratio here. */
.checklist .h-section { font-size: clamp(24px, 2.2vw, 38px); }

.checklist__body {
  margin-top: 20px;
  font-size: clamp(16px, 1.11vw, 19px);
  line-height: 1.72;
  color: var(--text);
}

.checklist__form { margin-top: 30px; }

.checklist__row {
  display: flex;
  gap: 36px;
  align-items: stretch;
}

.field {
  height: var(--control-h);
  padding: 0 16px;
  border: 1px solid var(--field);
  border-radius: var(--radius-control);
  background: var(--btn-fill);
  color: var(--text);
  font: inherit;
  font-size: 16px;
  transition: border-color var(--t-fast) ease;
}
.field::placeholder { color: var(--text-muted); opacity: 1; }

/* Chrome paints autofilled fields on its own opaque yellow, which beats
   `background`, and recolours the text past `color`. Masking it takes an inset
   shadow — hence --field-fill-solid, since the translucent --btn-fill would
   let the yellow through — and -webkit-text-fill-color for the text, which is
   the one property that outranks the UA style here. All four states, because
   Chrome keeps the pseudo-class through hover, focus and active.

   On .field, so the checklist sign-up and the commission form are covered by
   one rule. The commission controls restate it further down with their own
   brighter value; equal specificity, later wins. */
.field:-webkit-autofill,
.field:-webkit-autofill:hover,
.field:-webkit-autofill:focus,
.field:-webkit-autofill:active {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--field-fill-solid) inset;
  box-shadow: 0 0 0 1000px var(--field-fill-solid) inset;
  caret-color: var(--text);
}

.field:focus { outline: none; border-color: var(--accent-strong); }
.field:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }

/* The 50/50 split was sized for a 910px card; across the full container it
   would draw a ~640px button. The field takes the room, the button stays at
   its own width, and the pair keeps to a comfortable measure rather than
   running the whole card. */
.checklist__row    { max-width: 760px; }
.checklist__input  { flex: 1 1 auto; min-width: 0; }
.checklist__submit { flex: 0 0 auto; }

/* Honeypot — off-canvas rather than display:none, which some bots skip. */
.checklist__hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.checklist__error {
  margin-top: 14px;
  font-size: 15px;
  color: #E4736B;
}
.checklist__error:empty { display: none; }

.checklist__note {
  margin-top: 22px;
  font-size: clamp(14px, 1vw, 17px);
  line-height: 1.88;
  color: var(--text-muted);
}
.checklist__note a { color: var(--text-muted); text-decoration: underline; text-underline-offset: 3px; }
.checklist__note a:hover { color: var(--accent-strong); }

.checklist__success .checklist__body { margin-top: 20px; }

@media (max-width: 699px) {
  .checklist__row { flex-direction: column; gap: 14px; }
  /* The main axis is now vertical, so the 50% flex-basis above would apply to
     HEIGHT and collapse the field. Reset it and set the height outright. */
  .checklist__input,
  .checklist__submit { flex: 0 0 auto; width: 100%; }
}

/* -------------------------------------------------------- commission form -- */

.contact__intro {
  margin-top: 14px;
  margin-bottom: 44px;
  font-size: clamp(15px, 1.05vw, 17px);
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 70ch;
}

/* Two equal columns on the same gap the paired fields in block 1 use, so every
   field edge in the form lines up on one of two axes. */
.cform {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: clamp(32px, 8vw, 128px);
  row-gap: 52px;
  align-items: start;
}

.cform__block {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
/* Layout: [1 spans both] / [2][3] / [4 spans both]. Blocks 2 and 3 carry four
   fields each, so the two project columns end level. */
.cform__block:nth-of-type(1) { grid-column: 1 / span 2; }
.cform__block:nth-of-type(2) { grid-column: 1; }
.cform__block:nth-of-type(3) { grid-column: 2; }
.cform__block:nth-of-type(4) { grid-column: 1 / span 2; }

/* Block 1 pairs its four fields two-up across the columns it spans. */
.cform__fields--pairs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(32px, 8vw, 128px);
}

.cform__legend {
  padding: 0;
  margin-bottom: 18px;
  font-weight: 600;
  font-size: clamp(15px, 1.05vw, 17px);
  color: var(--text);
}

.cform__required-note {
  margin-top: -12px;
  margin-bottom: 16px;
  font-size: 13px;
  color: var(--text-muted);
}

.cfield {
  position: relative;
  margin-bottom: 16px;
}

/* Visible label above each field. The placeholder used to carry the name and
   vanished on the first keystroke, which left a filled form with no way to
   tell what any answer was. */
.cfield__label {
  display: block;
  margin-bottom: 6px;
  font-size: 13px;
  line-height: 1.4;
  letter-spacing: 0.01em;
  color: var(--text-muted);
}
.cfield__req { color: var(--accent); }

.cfield__control { width: 100%; display: block; }

/* Filled-in answers read at the site's brightest body tone, placeholders stay
   at --text-muted. The two used to be --text against --text-muted, 19 points
   of L* apart, which was too close to tell a done field from an empty one at a
   glance; this doubles the gap to 37 (L* 92.3 against 55.7).

   The step is made by lifting the value, not by dimming the placeholder:
   --text-muted already measures 4.67:1 on this field's background, so §10's
   4.5:1 leaves it nowhere to go. Weight is untouched, so nothing reflows as
   the field fills. */
.cfield__control { color: var(--accent-strong); }

select.cfield__control {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 44px;
  cursor: pointer;
}

/* A select showing its empty option is a placeholder, not an answer. :invalid
   cannot say that here — it needs the `required` attribute, and the markup
   carries aria-required instead, so the old rule never matched anything. Nor
   would required have covered "Your role on the project", which is optional
   but still starts empty. :has() asks the question directly: is the selected
   option the empty one. Where :has() is unsupported the select simply keeps
   the value colour, which is what it did before this rule. */
select.cfield__control:has(option[value=""]:checked) { color: var(--text-muted); }

/* The open list is drawn by the OS on most platforms and ignores this; it is
   kept for the ones that honour it. */
select.cfield__control option[value=""] { color: var(--text-muted); }

.cfield__textarea {
  height: auto;
  min-height: 200px;
  padding: 14px 16px;
  line-height: 1.6;
  resize: vertical;
}

/* Chrome paints autofilled fields on its own yellow, which is opaque and wins
   over `background`. The only way past it is to cover the field with an inset
   shadow — hence --field-fill-solid, since a translucent fill would let the
   yellow through — and to set the text through -webkit-text-fill-color, which
   outranks `color` in that state. Every interaction state needs it: Chrome
   keeps :-webkit-autofill on the field through hover, focus and active. */
.cfield__control:-webkit-autofill,
.cfield__control:-webkit-autofill:hover,
.cfield__control:-webkit-autofill:focus,
.cfield__control:-webkit-autofill:active {
  -webkit-text-fill-color: var(--accent-strong);
  -webkit-box-shadow: 0 0 0 1000px var(--field-fill-solid) inset;
  box-shadow: 0 0 0 1000px var(--field-fill-solid) inset;
  caret-color: var(--accent-strong);
}

/* The control's own box. It is exactly as tall as the control, so the icons
   below centre on it with a plain 50% and never have to know how tall the
   label above them is — including when a label wraps. */
.cfield__box { position: relative; }

/* Chevron on selects at every viewport (§5.6); icons on the date and url. */
.cfield__chevron,
.cfield__icon {
  position: absolute;
  right: 15px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--text-muted);
  pointer-events: none;
}
.cfield--icon .cfield__control { padding-right: 44px; }

/* The native date control sizes itself differently on every platform — taller
   on iOS. Strip its chrome and pin the box to the same height as every other
   control. The field is type="date" at all times; the empty state is handled
   by colour, below, not by swapping the type. */
input[type="date"].cfield__control,
input[type="text"].cfield__control {
  height: var(--control-h);
  min-height: var(--control-h);
  line-height: normal;
}

input[type="date"].cfield__control {
  position: relative;
  appearance: none;
  -webkit-appearance: none;
}

/* iOS and Chrome each lay the value out differently inside the box. Giving the
   shadow content the field's inner height and matching line-height centres the
   chosen date exactly like a text input's value. */
input[type="date"].cfield__control::-webkit-date-and-time-value,
input[type="date"].cfield__control::-webkit-datetime-edit {
  display: block;
  text-align: left;
  margin: 0;
  padding: 0;
  height: calc(var(--control-h) - 2px);
  line-height: calc(var(--control-h) - 2px);
}

/* An empty date reads as a placeholder like every other empty field: Chrome's
   own dd.mm.yyyy hint, in the placeholder tone. A chosen date inherits the
   value colour from .cfield__control.

   Keyed on a class because no selector can ask whether a date input is empty —
   :invalid needs `required` (this field is optional), :placeholder-shown does
   not apply to dates, and an attribute or :not() placed after a
   ::-webkit-datetime-edit-* pseudo-element makes Chrome drop the rule outright
   (checked against the CSSOM). A class on the element does work, and site.js
   keeps it in step with the value. Without JS the hint renders in the value
   colour, which is the harmless direction to fail in. */
input[type="date"].cfield__control.is-empty::-webkit-datetime-edit { color: var(--text-muted); }
input[type="date"].cfield__control::-webkit-datetime-edit-fields-wrapper {
  height: calc(var(--control-h) - 2px);
  line-height: calc(var(--control-h) - 2px);
}

/* Our own calendar glyph is the affordance; the native one covers the field
   so a tap anywhere opens the picker. */
input[type="date"].cfield__control::-webkit-calendar-picker-indicator {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
}

.cfield__error {
  margin-top: 6px;
  font-size: 13px;
  color: #E4736B;
}
.cfield__error:empty { display: none; }

.field[aria-invalid="true"] { border-color: #E4736B; }

.cform__hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.cform__foot { grid-column: 1 / -1; }

.cform__consent {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 4px;
  max-width: 76ch;
}
.cform__consent label {
  font-size: 15px;
  line-height: 1.55;
  color: var(--text);
}
.cform__consent a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
.cform__consent a:hover { color: var(--accent-strong); }

.cform__checkbox {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.cform__consent-error { margin-top: 2px; }

.cform__actions {
  margin-top: 26px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

.cform__submit { min-width: 354px; }

.cform__contact {
  display: flex;
  align-items: center;
  gap: 14px;
}

.cform__pill {
  display: inline-flex;
  align-items: center;
  height: var(--control-h);
  padding: 0 26px;
  border: 1px solid var(--btn-border);
  border-radius: 999px;
  color: var(--text);
  font-size: 16px;
  text-decoration: none;
  transition: color var(--t-fast) ease, border-color var(--t-fast) ease;
}
.cform__pill:hover { color: var(--accent-strong); border-color: var(--accent-strong); }

.cform__icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--btn-border);
  border-radius: 50%;
  color: var(--text);
  transition: color var(--t-fast) ease, border-color var(--t-fast) ease;
}
.cform__icon:hover { color: var(--accent-strong); border-color: var(--accent-strong); }
.cform__icon svg { width: 19px; height: 19px; }

.cform__status {
  margin-top: 18px;
  font-size: 15px;
  color: #E4736B;
}
.cform__status:empty { display: none; }

.contact__success-text {
  font-size: clamp(17px, 1.3vw, 21px);
  line-height: 1.6;
  color: var(--text);
  max-width: 52ch;
}

/* One column, full-width fields (§5.6). */
@media (max-width: 899px) {
  .cform { grid-template-columns: 1fr; row-gap: 40px; }
  .cform__block:nth-of-type(1),
  .cform__block:nth-of-type(2),
  .cform__block:nth-of-type(3),
  .cform__block:nth-of-type(4) { grid-column: 1; }
  .cform__fields--pairs { grid-template-columns: 1fr; }
  .cform__actions { flex-direction: column; align-items: stretch; }
  .cform__submit { min-width: 0; width: 100%; }
  .cform__contact { justify-content: center; flex-wrap: wrap; }
  .cform__pill { flex: 1 1 100%; justify-content: center; }
}

/* --------------------------------------------------------------- footer -- */

.footer {
  border-top: 1px solid var(--line);
  padding-block: 28px;
  margin-top: var(--rhythm);
}

.footer__inner {
  max-width: calc(var(--maxw) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px 32px;
  flex-wrap: wrap;
  font-size: 15px;
}

.footer__copy { color: var(--text); }

.footer__links { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.footer__links a { text-decoration: none; transition: color var(--t-fast) ease; }
.footer__links a:hover { color: var(--accent-strong); }
.footer__mail { text-decoration: underline; text-underline-offset: 3px; }
.footer__sep { color: var(--text-muted); }

@media (max-width: 699px) {
  .footer__inner { flex-direction: column; align-items: flex-start; }
}

/* ----------------------------------------------- prose / legal / status -- */

.prose-page {
  padding-top: calc(var(--nav-h) + 64px);
  padding-bottom: 40px;
}

.prose {
  max-width: 760px;
  margin-inline: auto;
}

.prose__title { margin-bottom: 40px; }

.prose h2 {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 19px;
  margin-top: 40px;
  margin-bottom: 10px;
  color: var(--text);
}

.prose h3 {
  font-family: var(--font-body);
  font-weight: 600;
  font-style: italic;
  font-size: 17px;
  margin-top: 28px;
  margin-bottom: 6px;
  color: var(--text);
}

.prose p { margin-bottom: 16px; }
.prose ul { margin: 0 0 16px; padding-left: 22px; }
.prose li { margin-bottom: 8px; }

.prose a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
.prose a:hover { color: var(--accent-strong); }

.prose__meta { color: var(--text-muted); font-style: italic; margin-top: 36px; }

/* Unresolved placeholders from the legal source (§6) — visible until supplied */
.todo { color: #A51C1C; font-style: normal; font-weight: 500; }

.status-page {
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-top: var(--nav-h);
}
.status-page__inner { max-width: 620px; }
.status-page__title { margin-bottom: 20px; }
.status-page__text { margin-bottom: 34px; }

/* ------------------------------------------------------ reduced motion -- */

@media (prefers-reduced-motion: reduce) {
  html, body { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
