/* ============================================================================
   Seceda Studios stylesheet

   Organisation
     1. Brand tokens       — edit these to re-skin the site
     2. Semantic tokens    — derived from brand; generally leave alone
     3. @font-face         — Neue Montreal + Playfair Display accent
     4. Reset + elements   — body, headings, links, buttons, inputs, dialog
     5. Utilities          — .reveal, .visually-hidden
     6. Layout chrome      — site header, site footer
     7. Home sections      — section.hero, section.work, section.cta
     8. Contact            — section.contact (page) and dialog#contact-modal
     9. Dialogs            — dialog#lightbox and dialog#contact-modal entrance
    10. Media + motion     — responsive, reduced-motion
   ---------------------------------------------------------------------------- */

/* 1. Brand tokens ─────────────────────────────────────────────────────────── */

:root {
  /* --- Brand colours, named from the brand guide -----------------------
     Deep      — canvas / page background, always with texture
     Moloko    — primary foreground (text, logo, UI chrome)
     Clockwork — pop highlight: CTAs, focus, "needs attention" states
     Electric  — RARE pop; reserve for genuinely important moments only
     These are the *source* palette. Everything else in the stylesheet
     derives from the semantic aliases below, so a rebrand is a 4-line edit.
     --- */
  --brand-deep:      #191919;
  --brand-moloko:    #c3c3c3;
  --brand-clockwork: #e78453;
  --brand-electric:  #7bd473;

  /* --- Brand typography. 2 faces + 1 accent. --- */
  --brand-font-display: "Neue Montreal", system-ui, -apple-system, "Segoe UI", sans-serif;
  --brand-font-body:    "Neue Montreal", system-ui, -apple-system, "Segoe UI", sans-serif;
  --brand-font-accent:  "Playfair Display", Georgia, "Times New Roman", serif;

  /* --- Background texture. Drop in any tileable image + adjust size/blend.
     The packaged `texture.png` is pre-baked to `--color-canvas` #191919
     with a ±10 brightness film-grain variation (pixel range 23–46,
     mean 25.4). It's designed to RENDER DIRECTLY as the brand canvas
     — there's no lighter mid-tone pixels for a blend-mode like
     `overlay` to lift, so `overlay` on a #191919 base crushes the
     grain to ~#040408 and the surface reads as a flat near-black
     slab with no texture visible (what the client flagged on the
     hero). `normal` just paints the PNG on top of the
     colour-as-fallback, letting the native grain through. If you
     swap in a texture with a different tonal range (e.g. a light
     paper grain for a pale canvas), pick a blend-mode that fits
     that PNG's histogram. */
  --brand-texture:       url("/static/images/texture.png");
  --brand-texture-size:  960px auto;
  --brand-texture-blend: normal;
  --brand-texture-opacity: 1;   /* set to 0 to disable the texture */
}

/* 2. Semantic tokens ──────────────────────────────────────────────────────── */

:root {
  /* Semantic aliases. Stylesheet code references these roles, never the
     brand names directly — so if the brand colour of, say, the canvas
     changes, every rule in the stylesheet repaints without editing a
     single selector. CSS custom properties are resolved lazily, so
     overrides at a narrower scope (e.g. a `[data-theme]` class) cascade
     through every dependent variable automatically. */
  --color-canvas:   var(--brand-deep);
  --color-ink:      var(--brand-moloko);
  --color-accent:   var(--brand-clockwork);  /* primary CTAs + needs-attention */
  --color-emphasis: var(--brand-electric);   /* reserved — rare, deliberate pop */

  /* Derived neutral tints — key off `--color-ink` so changing Moloko
     also recolours every border, hint, and surface. */
  --color-muted:    color-mix(in oklch, var(--color-ink) 65%, transparent);
  --color-faint:    color-mix(in oklch, var(--color-ink) 20%, transparent);
  --color-surface:  color-mix(in oklch, var(--color-ink)  8%, transparent);
  --color-scrim:    color-mix(in oklch, var(--color-canvas) 80%, transparent);
  --color-backdrop: color-mix(in oklch, var(--color-canvas) 82%, transparent);

  /* Fonts via semantic slots */
  --font-display: var(--brand-font-display);
  --font-body:    var(--brand-font-body);
  --font-accent:  var(--brand-font-accent);

  /* Type scale (fluid) — hero cap matches the live site; on a 1920px
     display 12vw ≈ 230px, which the old 10.5rem cap (168px) was cutting
     off. Raising it to 15rem (240px) lets the display type breathe at
     the full widths the brand kit illustrates without breaking mobile. */
  --fs-hero:    clamp(3rem, 12vw, 15rem);
  --fs-display: clamp(2.5rem, 7vw, 6rem);
  --fs-h1:      clamp(2rem, 5vw, 3.75rem);
  --fs-h2:      clamp(1.75rem, 3.5vw, 3rem);
  --fs-h3:      clamp(1.25rem, 2vw, 2rem);
  --fs-lede:    clamp(0.95rem, 1.1vw, 1.25rem);
  --fs-body:    1rem;
  --fs-small:   0.875rem;
  --fs-micro:   0.6875rem;

  /* Rhythm */
  --leading-tight: 0.92;
  --leading-snug:  1.12;
  --leading-body:  1.55;
  --track-wide:    0.14em;
  --track-loose:   0.18em;

  /* Spacing scale */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;
  --space-9: 6rem;
  --space-10: 8rem;
  --gutter:   clamp(1rem, 1.7vw, 2rem);

  /* Radii */
  --radius-xs: 4px;
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-pill: 999px;

  /* Motion */
  --ease-out:    cubic-bezier(.22, 1, .36, 1);
  --ease-in-out: cubic-bezier(.4, 0, .2, 1);
  --dur-fast:    150ms;
  --dur-med:     300ms;
  --dur-slow:    600ms;
  --dur-reveal:  900ms;

  /* Chrome */
  --nav-h:       110px;
  --content-max: 1800px;
  --reading-max: 1040px;

  color-scheme: dark;
}

/* 3. @font-face ───────────────────────────────────────────────────────────── */

@font-face {
  font-family: "Neue Montreal";
  src: url("/static/fonts/neue-montreal-thin.woff2") format("woff2");
  font-weight: 200; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("/static/fonts/neue-montreal-book.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("/static/fonts/neue-montreal-italic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("/static/fonts/neue-montreal-medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("/static/fonts/neue-montreal-bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("/static/fonts/neue-montreal-semibolditalic.woff2") format("woff2");
  font-weight: 600; font-style: italic; font-display: swap;
}

/* Playfair Display — self-hosted latin subset (GDPR: no Google Fonts CDN) */
@font-face {
  font-family: "Playfair Display";
  src: url("/static/fonts/playfair-display.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Playfair Display";
  src: url("/static/fonts/playfair-display-italic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}

/* 4. Reset + element defaults ────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
img, picture, video, canvas, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
:where(ul, ol) { padding: 0; list-style: none; }
button { background: none; border: 0; padding: 0; cursor: pointer; color: inherit; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 3px; border-radius: 3px; }

/* A "canvas surface" = brand-canvas colour + brand texture, blended.
   Every element that wants the brand's film-grain backdrop opts into
   this rule so a single source defines the texture PNG, its tile
   size, and the blend-mode.

   `section.hero` and `section.hero > .words > header` opt in as
   full peers of `body` / `dialog`: no property overrides, no pure
   black, no alternate blend-mode. The whole hero — header band,
   reveal zone, and the non-text area of the "HAS A STORY" band — now
   resolves to the SAME brand canvas value, giving a single
   continuous surface across the entire section.
   The "seam" problem between the header and the multiply-knockout
   band (which would otherwise force pure #000 here — see
   `section.hero`'s docblock for the multiply math) is handled one
   level deeper, on `.blend::after` with `mix-blend-mode: lighten`.
   That layer repaints the same canvas surface on top of the multiply
   output, lifting its flat 0 pixels up to `--color-canvas` so the
   blend band matches the rest of the hero pixel-for-pixel. */
body,
section.hero,
section.hero > .words > header,
dialog {
  background-color: var(--color-canvas);
  background-image: var(--brand-texture);
  background-repeat: repeat;
  background-size: var(--brand-texture-size);
  background-blend-mode: var(--brand-texture-blend);
}

body {
  min-height: 100dvh;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--leading-body);
  color: var(--color-muted);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  display: flex;
  flex-direction: column;
}
body > main { flex: 1; }

/* Headings — uppercase display face, unified rhythm */
h1, h2, h3, h4, h5 {
  color: var(--color-ink);
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--leading-snug);
  text-transform: uppercase;
  letter-spacing: -0.005em;
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }

/* Accent italics — Playfair in any element that uses <em> */
em, i {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--color-ink);
}

strong { color: var(--color-ink); font-weight: 700; }
small  { font-size: var(--fs-small); color: var(--color-muted); }

/* Inline paragraph rhythm */
p { color: var(--color-muted); }
p strong, p em { color: var(--color-ink); }

/* Generic links inside running text */
main a[href]:not([data-cta]):not([data-lightbox]):not([data-open]) {
  transition: border-color var(--dur-fast) var(--ease-out);
}

/* Shared button / CTA primitive, driven by a single attribute.
   Works on <a>, <button>, anywhere. Variants via data-cta value.
   Primary = Clockwork pop (the brand-guide'd button colour). */
[data-cta] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-6);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-body);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-canvas);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out),
              background var(--dur-med) var(--ease-out),
              color var(--dur-med) var(--ease-out),
              filter var(--dur-med) var(--ease-out);
}
[data-cta]:hover   { transform: translateY(-2px); filter: brightness(1.06); }
[data-cta="ghost"] {
  background: transparent;
  color: var(--color-ink);
  border-color: var(--color-faint);
}
[data-cta="ghost"]:hover { background: var(--color-surface); filter: none; }

/* 5. Utilities ──────────────────────────────────────────────────────────── */

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.reveal {
  /* Fallback: visible content when scroll timelines are unsupported. */
  opacity: 1;
  transform: none;
}

/* JS-only fallback path for browsers without `view()` timelines. */
html.no-scroll-timeline .reveal {
  opacity: 0;
  transform: translate3d(0, var(--reveal-shift, 2.5rem), 0)
             scale(var(--reveal-scale-from, 1));
  transition: opacity var(--reveal-fallback-duration, 620ms) linear,
              transform var(--reveal-fallback-duration, 620ms) linear;
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
html.no-scroll-timeline .reveal.is-in {
  opacity: 1;
  transform: none;
}
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(1)  { --reveal-delay:  40ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(2)  { --reveal-delay:  80ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(3)  { --reveal-delay: 120ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(4)  { --reveal-delay: 160ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(5)  { --reveal-delay: 200ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(6)  { --reveal-delay: 240ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(7)  { --reveal-delay: 280ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(8)  { --reveal-delay: 320ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(9)  { --reveal-delay: 360ms; }
html.no-scroll-timeline .reveal-stagger > .reveal:nth-child(10) { --reveal-delay: 400ms; }

@keyframes reveal-in {
  from {
    opacity: 0;
    transform: translate3d(0, var(--reveal-shift, 2.5rem), 0)
               scale(var(--reveal-scale-from, 1));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@supports (animation-timeline: view()) {
  .reveal {
    will-change: opacity, transform;
    animation-name: reveal-in;
    animation-duration: 1ms;
    /* Scroll timelines feel more natural with linear progress. */
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: view(block);
    animation-range: entry calc(var(--reveal-start, 12%) + var(--reveal-offset, 0%))
                     cover calc(var(--reveal-end, 52%) + var(--reveal-offset, 0%));
  }
}

/* Lightweight reusable stagger helper for lists/grids. */
.reveal-stagger > .reveal:nth-child(1)  { --reveal-offset: 0%; }
.reveal-stagger > .reveal:nth-child(2)  { --reveal-offset: 1%; }
.reveal-stagger > .reveal:nth-child(3)  { --reveal-offset: 2%; }
.reveal-stagger > .reveal:nth-child(4)  { --reveal-offset: 3%; }
.reveal-stagger > .reveal:nth-child(5)  { --reveal-offset: 4%; }
.reveal-stagger > .reveal:nth-child(6)  { --reveal-offset: 5%; }
.reveal-stagger > .reveal:nth-child(7)  { --reveal-offset: 6%; }
.reveal-stagger > .reveal:nth-child(8)  { --reveal-offset: 7%; }
.reveal-stagger > .reveal:nth-child(9)  { --reveal-offset: 8%; }
.reveal-stagger > .reveal:nth-child(10) { --reveal-offset: 9%; }

/* CTA beats should read stronger than ambient section reveals. */
.reveal-cta {
  --reveal-shift: 5.2rem;
  --reveal-scale-from: 0.985;
  --reveal-start: 2%;
  --reveal-end: 48%;
  --reveal-fallback-duration: 760ms;
}

/* Make bottom CTAs feel intentionally stepped, not a single fade block. */
section.cta > h2 > .reveal-cta,
section.services > .services-cta > h2 > .reveal-cta {
  --reveal-shift: 4.6rem;
  --reveal-start: 0%;
  --reveal-end: 42%;
}
section.cta > h2 > em.reveal-cta,
section.services > .services-cta > h2 > em.reveal-cta {
  --reveal-offset: 6%;
}
section.cta > h2 > .reveal-cta-line-1 { --reveal-offset: 0%; }
section.cta > h2 > .reveal-cta-line-2 { --reveal-offset: 6%; }

/* Hero sections get a dedicated load-in treatment so above-the-fold
   headers still feel animated even before scroll-driven timelines kick in. */
@keyframes hero-intro {
  from {
    opacity: 0;
    transform: translate3d(0, var(--hero-shift, 1.8rem), 0);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.hero-anim > * {
  opacity: 0;
  transform: translate3d(0, var(--hero-shift, 1.8rem), 0);
  animation: hero-intro var(--hero-duration, 820ms) var(--ease-out) both;
}
.hero-anim > *:nth-child(1) { animation-delay: 40ms; }
.hero-anim > *:nth-child(2) { animation-delay: 120ms; }
.hero-anim > *:nth-child(3) { animation-delay: 200ms; }
.hero-anim > *:nth-child(4) { animation-delay: 280ms; }

/* Intensity presets by page type. */
.hero-anim--bold   { --hero-shift: 3.3rem; --hero-duration: 1060ms; }
.hero-anim--medium { --hero-shift: 2.3rem; --hero-duration: 900ms; }
.hero-anim--soft   { --hero-shift: 1.35rem; --hero-duration: 760ms; }

/* 6. Site header + footer ───────────────────────────────────────────────── */

header[data-site] {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 1000;
  padding: var(--space-4) var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  pointer-events: none; /* children re-enable — lets scroll work on edges */
}
header[data-site] > * { pointer-events: auto; }

header[data-site] > a {
  display: inline-flex;
  padding: var(--space-2) var(--space-1);
}
header[data-site] > a > img {
  height: 80px;
  width: auto;
  filter: drop-shadow(0 1px 3px rgb(0 0 0 / 55%));
}

header[data-site] > nav[aria-label="Primary"] {
  display: flex;
  gap: var(--space-3);
  align-items: center;
}
header[data-site] nav > a {
  padding: var(--space-4) var(--space-4);
  color: var(--color-ink);
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 500;
  line-height: 1.1;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  text-shadow: 0 1px 3px rgb(0 0 0 / 55%);
  transition: opacity var(--dur-fast) var(--ease-out);
}
header[data-site] nav > a:hover { opacity: 0.7; }

header[data-site] nav > a[data-cta] {
  padding: 10px 18px;
  background: transparent;
  color: var(--color-ink);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-pill);
}
header[data-site] nav > a[data-cta]:hover {
  background: var(--color-ink);
  color: var(--color-canvas);
  opacity: 1;
  transform: none;
}

/* Social icon row (header + footer share the pattern) */
[data-social] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  list-style: none;
  padding: 0;
  margin: 0;
}
[data-social] > li { display: inline-flex; }
[data-social] > li > a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  color: var(--color-ink);
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
[data-social] > li > a:hover {
  background: var(--color-ink);
  color: var(--color-canvas);
  transform: translateY(-1px);
}
[data-social] > li > a > svg {
  width: 18px;
  height: 18px;
  fill: currentColor;
  stroke: currentColor;
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / 40%));
}
[data-social] > li > a:hover > svg { filter: none; }

/* Hover label — pulled from the existing `aria-label` so the screen-
   reader text and the visible tooltip stay in lockstep automatically.
   Pure CSS: no markup change, no JS. The `position: relative` on the
   anchor anchors the absolutely-positioned label above the icon. */
[data-social] > li > a {
  position: relative;
}
[data-social] > li > a::after {
  content: attr(aria-label);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translate(-50%, 4px);
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  color: var(--color-canvas);
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
  z-index: 5;
}
[data-social] > li > a:hover::after,
[data-social] > li > a:focus-visible::after {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Sprite definitions stay in the DOM but must not paint anywhere */
svg[data-sprite] { position: absolute; width: 0; height: 0; overflow: hidden; }

header[data-site] > button[data-menu-toggle] {
  display: none;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
}
header[data-site] > button[data-menu-toggle]:hover { background: var(--color-surface); }

header[data-site] > nav#mobile-menu {
  display: none;
}
header[data-site] > nav#mobile-menu[hidden] { display: none; }

footer[data-site] {
  border-top: 1px solid var(--color-faint);
  padding: var(--space-6) var(--gutter);
  color: var(--color-muted);
  font-size: var(--fs-small);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  grid-template-rows: auto auto;
  align-items: center;
  gap: var(--space-6) var(--space-5);
}
footer[data-site] > section     { justify-self: start; grid-row: 2; }
footer[data-site] > nav         { justify-self: center; grid-row: 2; display: flex; gap: var(--space-4); flex-wrap: wrap; justify-content: center; }
footer[data-site] > nav > a     {
  color: var(--color-ink);
  font-family: var(--font-display);
  font-size: var(--fs-small);
  letter-spacing: 0.04em;
  transition: opacity var(--dur-fast) var(--ease-out);
}
footer[data-site] > nav > a > em { color: var(--color-muted); }
footer[data-site] > nav > a:hover { opacity: 0.7; }
footer[data-site] > [data-social] { justify-self: end; grid-row: 2; }

/* Newsletter sign-up spans the full width on a row above the rest. */
footer[data-site] > .footer-newsletter {
  grid-column: 1 / -1;
  grid-row: 1;
  justify-self: center;
  width: min(520px, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-faint);
  text-align: center;
}
footer[data-site] > .footer-newsletter > label {
  font-family: var(--font-display);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--color-ink-muted);
}
footer[data-site] > .footer-newsletter > .row {
  display: flex;
  gap: var(--space-2);
}
footer[data-site] > .footer-newsletter > .row > input[type="email"] {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  color: var(--color-ink);
  font: inherit;
}
footer[data-site] > .footer-newsletter > .row > input[type="email"]:focus {
  outline: 2px solid var(--color-moloko);
  outline-offset: 1px;
}
footer[data-site] > .footer-newsletter > .row > button {
  background: var(--color-ink);
  color: var(--color-canvas);
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-5);
  font: inherit;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out), filter var(--dur-fast) var(--ease-out);
}
footer[data-site] > .footer-newsletter > .row > button:hover {
  transform: translateY(-1px);
  filter: brightness(1.06);
}
footer[data-site] > .footer-newsletter > small {
  color: var(--color-ink-muted);
  font-size: 0.78rem;
}

/* Thank-you page after subscribe. */
.newsletter-thanks {
  max-width: 48rem;
  margin: 0 auto;
  padding: calc(var(--nav-h) + var(--space-9)) var(--space-6) var(--space-9);
  text-align: center;
}
.newsletter-thanks > h1 {
  font-size: clamp(2rem, 5vw, 3.6rem);
  line-height: 1.05;
  margin: var(--space-3) 0;
}
.newsletter-thanks > h1 > em {
  font-family: var(--font-display-italic);
  color: var(--color-moloko);
  font-style: italic;
  display: block;
}
.newsletter-thanks > p { color: var(--color-ink-muted); max-width: 42ch; margin: var(--space-3) auto; }
.newsletter-thanks > .cta { margin-top: var(--space-6); }

@media (max-width: 760px) {
  footer[data-site] {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto auto;
    justify-items: center;
    text-align: center;
    gap: var(--space-4);
  }
  footer[data-site] > .footer-newsletter,
  footer[data-site] > section,
  footer[data-site] > nav,
  footer[data-site] > [data-social] {
    justify-self: center;
    grid-row: auto;
    grid-column: 1;
  }
  footer[data-site] > .footer-newsletter > .row { flex-direction: column; }
}

/* --- Services ----------------------------------------------------------- */
section.services {
  max-width: var(--content-max);
  margin-inline: auto;
  padding: calc(var(--nav-h) + var(--space-9)) var(--gutter) var(--space-9);
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}

/* Hero band */
/* Canonical page-header pattern.
 *
 * The services hero defined this look first; every public marketing
 * page now opts into the same rules via the comma-list. Pages just
 * mark their `<header>` with the appropriate class (`services-hero`,
 * `projects-head`, etc.) and inherit:
 *   - Centered flex column with a 76ch reading cap
 *   - Uppercase tracked kicker label
 *   - Big fluid `<h1>` with `<span>` + Playfair-italic `<em>` accent
 *   - 56ch lede underneath
 *
 * Long headings wrap naturally on narrow screens (no `nowrap` here);
 * services' "What we make." stays on a line because the content is
 * short, not because the rule forces it. The legal pages and the
 * home hero are intentionally NOT in this list — they use a different
 * structural pattern.
 */
section.services > header.services-hero,
.projects-head,
.filmmakers-head,
.bts-head,
.tools-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-3);
  max-width: 76ch;
  margin-inline: auto;
}
section.services > header.services-hero > .kicker,
.projects-head > .kicker,
.filmmakers-head > .kicker,
.bts-head > .kicker,
.tools-head > .kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
}
section.services > header.services-hero > h1,
.projects-head > h1,
.filmmakers-head > h1,
.bts-head > h1,
.tools-head > h1 {
  font-size: clamp(1.7rem, 7.8vw, 8rem);
  line-height: 0.94;
  color: var(--color-ink);
  margin-top: var(--space-2);
  letter-spacing: -0.01em;
  text-wrap: balance;
}
section.services > header.services-hero > h1 > span,
.projects-head > h1 > span,
.filmmakers-head > h1 > span,
.bts-head > h1 > span,
.tools-head > h1 > span {
  display: inline;
  text-transform: uppercase;
}
section.services > header.services-hero > h1 > em,
.projects-head > h1 > em,
.filmmakers-head > h1 > em,
.bts-head > h1 > em,
.tools-head > h1 > em {
  display: inline;
  margin: 0;
  padding-left: 0.22ch;
}
section.services > header.services-hero > .lede,
.projects-head > .lede,
.filmmakers-head > .lede,
.bts-head > .lede,
.tools-head > .lede {
  font-size: var(--fs-lede);
  color: var(--color-ink);
  margin-top: var(--space-4);
  max-width: 56ch;
  line-height: var(--leading-body);
  text-wrap: pretty;
}
/* Non-services heads need their own bottom margin because their parent
   page wrappers (.projects-page, .filmmakers-page, .tools-page, .bts)
   don't supply a flex gap the way `<section class="services">` does. */
.projects-head,
.filmmakers-head,
.bts-head,
.tools-head {
  margin-block-end: var(--space-8);
}

/* Operators / positioning band — bridge from the Process section into
   the Open-source section. Has its own kicker so the rhythm matches
   the other sibling sections (Process, Technology, Q&A). */
section.services > .services-intro {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-5) var(--space-9);
  align-items: start;
  padding: var(--space-7) 0;
  border-top: 1px solid var(--color-faint);
  border-bottom: 1px solid var(--color-faint);
}
section.services > .services-intro > p.kicker {
  grid-column: 1 / -1;
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: calc(-1 * var(--space-3));
}
section.services > .services-intro > h2 {
  font-size: var(--fs-display);
  line-height: 0.95;
  color: var(--color-ink);
}
section.services > .services-intro > p:not(.kicker) {
  font-size: clamp(1.08rem, 1.04vw, 1.18rem);
  max-width: 52ch;
  line-height: 1.68;
  color: color-mix(in srgb, var(--color-ink) 90%, #000 10%);
}
section.services > .services-intro > p:not(.kicker) + p:not(.kicker) {
  grid-column: 2;
  font-size: clamp(1.16rem, 1.16vw, 1.32rem);
  line-height: 1.58;
  font-weight: 520;
  color: var(--color-ink);
  border-left: 1px solid color-mix(in srgb, var(--color-accent) 55%, transparent);
  padding-left: var(--space-4);
  max-width: 46ch;
}

/* Service tiles — alternating image/text rhythm */
section.services > .services-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}
section.services > .services-grid > article.service-tile {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-8);
  align-items: center;
}
/* Flip every second tile so images alternate side */
section.services > .services-grid > article.service-tile:nth-child(even) > figure {
  grid-column: 2;
  grid-row: 1;
}
section.services > .services-grid > article.service-tile:nth-child(even) > .tile-body {
  grid-column: 1;
  grid-row: 1;
}
section.services article.service-tile > figure {
  margin: 0;
  position: relative;
  width: 104%;
  max-width: none;
  justify-self: start;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
  isolation: isolate;
}
section.services > .services-grid > article.service-tile:nth-child(even) > figure {
  justify-self: end;
}
section.services article.service-tile > figure > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  aspect-ratio: 16 / 9;
  transition: transform var(--dur-slow) var(--ease-out),
              filter var(--dur-med) var(--ease-out);
  filter: saturate(0.95);
}
section.services article.service-tile:hover > figure > img {
  transform: scale(1.02);
  filter: saturate(1.05);
}
section.services article.service-tile > .tile-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
section.services article.service-tile .tile-kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-accent);
}
section.services article.service-tile .tile-body > h3 {
  font-size: clamp(1.75rem, 4vw, 3rem);
  line-height: 1;
  color: var(--color-ink);
  margin-bottom: var(--space-2);
}
section.services article.service-tile .tile-and {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  text-transform: none;
  color: var(--color-muted);
  font-size: 0.75em;
  padding: 0 0.2ch;
}
section.services article.service-tile .tile-sub {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
  font-size: 1.2rem;
  margin-bottom: var(--space-2);
}
section.services article.service-tile .tile-body > p:not(.tile-kicker):not(.tile-sub):not(.tile-cta) {
  font-size: clamp(1.08rem, 1.08vw, 1.22rem);
  line-height: 1.7;
  color: color-mix(in srgb, var(--color-ink) 94%, #000 6%);
}
section.services article.service-tile .tile-capabilities {
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-1) var(--space-5);
  margin-top: var(--space-3);
  font-size: clamp(0.95rem, 0.95vw, 1.05rem);
  font-weight: 600;
  letter-spacing: 0.002em;
  line-height: 1.45;
  color: var(--color-ink);
}
section.services article.service-tile .tile-capabilities > li {
  position: relative;
  padding-left: 1.15em;
  padding-top: 6px;
  padding-bottom: 6px;
  border-top: 1px solid var(--color-faint);
}
section.services article.service-tile .tile-capabilities > li::before {
  content: "·";
  position: absolute;
  left: 0;
  color: var(--color-accent);
  font-weight: 800;
  font-size: 1.05em;
}
section.services article.service-tile .tile-cta {
  margin-top: var(--space-4);
}
section.services article.service-tile .tile-cta > a {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-small);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--color-accent);
  border-bottom: 1px solid var(--color-accent);
  padding-bottom: 2px;
  transition: filter var(--dur-fast) var(--ease-out);
}
section.services article.service-tile .tile-cta > a:hover {
  filter: brightness(1.15);
}

/* Process */
section.services > .services-process > header {
  max-width: 48ch;
  margin-bottom: var(--space-7);
}
section.services > .services-process > header > .kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: var(--space-4);
}
section.services > .services-process > header > h2 {
  font-size: var(--fs-display);
  line-height: 0.95;
  color: var(--color-ink);
  margin-bottom: var(--space-3);
}
section.services > .services-process > header > p.lede {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
  font-size: 1.2rem;
}
section.services ol.process-steps {
  counter-reset: step;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-6);
  padding: 0;
  list-style: none;
}
section.services ol.process-steps > li {
  padding-top: var(--space-4);
  border-top: 2px solid var(--color-ink);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
section.services ol.process-steps .step-no {
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  color: var(--color-accent);
}
section.services ol.process-steps > li > h3 {
  font-size: 1.5rem;
  color: var(--color-ink);
}
section.services ol.process-steps > li > p {
  font-size: clamp(1rem, 0.92vw, 1.08rem);
  line-height: 1.65;
  color: color-mix(in srgb, var(--color-ink) 90%, #000 10%);
}

/* OSS / technology */
section.services > .services-oss {
  padding: var(--space-8);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-faint);
}
section.services > .services-oss > header { max-width: 68ch; margin-bottom: var(--space-6); }
section.services > .services-oss > header > .kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: var(--space-4);
}
section.services > .services-oss > header > h2 {
  font-size: var(--fs-h2);
  line-height: var(--leading-tight);
  color: var(--color-ink);
  margin-bottom: var(--space-3);
}
section.services > .services-oss > header > p.lede {
  font-size: 1.05rem;
  max-width: 56ch;
}
section.services ul.oss-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-6);
  padding: 0;
  list-style: none;
}
section.services ul.oss-list > li {
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-faint);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
section.services ul.oss-list > li > h3 {
  font-size: 1.5rem;
  color: var(--color-ink);
}
section.services ul.oss-list > li > p.oss-tag {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
  font-size: 1rem;
  margin-top: calc(-1 * var(--space-1));
}
section.services ul.oss-list > li > p.oss-link {
  margin-top: auto;
  padding-top: var(--space-2);
  line-height: 1.4;
}
section.services ul.oss-list > li > p.oss-link > small {
  color: var(--color-muted);
  font-family: var(--font-display);
  letter-spacing: 0.02em;
  font-size: var(--fs-small);
}
section.services ul.oss-list > li > p.oss-link a {
  color: var(--color-ink);
  border-bottom: 1px solid var(--color-faint);
  transition: border-color var(--dur-fast) var(--ease-out);
}
section.services ul.oss-list > li > p.oss-link a:hover {
  border-bottom-color: var(--color-accent);
}
section.services > .services-oss > p.oss-bottom {
  margin-top: var(--space-6);
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-ink);
  font-size: 1.1rem;
  max-width: 48ch;
}

/* FAQ (native <details>) */
section.services > .services-faq > header {
  max-width: 48ch;
  margin-bottom: var(--space-6);
}
section.services > .services-faq > header > .kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: var(--space-4);
}
section.services > .services-faq > header > h2 {
  font-size: var(--fs-display);
  line-height: 0.95;
  color: var(--color-ink);
}
section.services div.faq-list > details {
  border-top: 1px solid var(--color-faint);
  padding: var(--space-5) 0;
}
section.services div.faq-list > details:last-child {
  border-bottom: 1px solid var(--color-faint);
}
section.services div.faq-list > details > summary {
  list-style: none;
  cursor: pointer;
  position: relative;
  padding-right: 3rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.1rem, 1.8vw, 1.5rem);
  color: var(--color-ink);
  line-height: 1.3;
}
section.services div.faq-list > details > summary::-webkit-details-marker { display: none; }
section.services div.faq-list > details > summary::after {
  content: "+";
  position: absolute;
  right: 0;
  top: -4px;
  font-size: 1.75rem;
  font-weight: 200;
  color: var(--color-accent);
  transition: transform var(--dur-fast) var(--ease-out);
  transform-origin: center;
  line-height: 1;
}
section.services div.faq-list > details[open] > summary::after {
  content: "×";
  transform: rotate(0deg);
}
section.services div.faq-list > details > p {
  margin-top: var(--space-4);
  max-width: 68ch;
  line-height: var(--leading-body);
  font-size: 1rem;
  color: var(--color-ink);
}

/* Page CTA */
section.services > .services-cta {
  text-align: center;
  padding: var(--space-9) 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  border-top: 1px solid var(--color-faint);
}
section.services > .services-cta > h2 {
  font-size: var(--fs-display);
  line-height: 0.95;
  color: var(--color-ink);
}
section.services > .services-cta > h2 > em {
  display: inline-block;
  margin-top: 0.1em;
}
section.services > .services-cta > p {
  max-width: 42ch;
  font-size: 1rem;
}
section.services > .services-cta > p > .cta-from {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
}
section.services > .services-cta > [data-cta] { margin-top: var(--space-4); }

/* Services — responsive */
@media (max-width: 900px) {
  section.services > .services-intro {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }
  section.services > .services-intro > h2 {
    font-size: clamp(2rem, 7.8vw, 3.2rem);
    max-width: 18ch;
    line-height: 0.98;
  }
  section.services > .services-intro > p:not(.kicker) {
    max-width: none;
    font-size: clamp(1.04rem, 2.15vw, 1.14rem);
  }
  section.services > .services-intro > p:not(.kicker) + p:not(.kicker) {
    grid-column: 1;
    border-left: 0;
    padding-left: 0;
    max-width: none;
    margin-top: var(--space-1);
    padding-top: var(--space-3);
    border-top: 1px solid color-mix(in srgb, var(--color-accent) 45%, transparent);
    font-size: clamp(1.08rem, 2.4vw, 1.2rem);
    line-height: 1.6;
  }
  section.services > .services-grid > article.service-tile,
  section.services > .services-grid > article.service-tile:nth-child(even) {
    grid-template-columns: 1fr;
  }
  section.services > .services-grid > article.service-tile:nth-child(even) > figure,
  section.services > .services-grid > article.service-tile:nth-child(even) > .tile-body {
    grid-column: 1;
    grid-row: auto;
  }
  section.services article.service-tile .tile-capabilities {
    grid-template-columns: 1fr;
  }
  section.services ol.process-steps { grid-template-columns: 1fr 1fr; }
  section.services ul.oss-list { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
  section.services > .services-intro > h2 {
    font-size: clamp(1.7rem, 9.2vw, 2.35rem);
    max-width: 15ch;
  }
  section.services ol.process-steps { grid-template-columns: 1fr; }
  section.services > .services-oss { padding: var(--space-5); }
}

/* Primary nav "Services" text link sits to the left of social icons */
header[data-site] nav[aria-label="Primary"] > a:not([data-cta]):first-of-type {
  margin-right: var(--space-2);
}

/* --- Admin --------------------------------------------------------------- */
section.admin {
  max-width: var(--reading-max);
  margin-inline: auto;
  padding: calc(var(--nav-h) + var(--space-8)) var(--gutter) var(--space-10);
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}
.admin-head > .kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: var(--space-4);
}
.admin-head > h1 {
  font-size: clamp(2.5rem, 6vw, 5rem);
  line-height: var(--leading-tight);
  color: var(--color-ink);
  margin-bottom: var(--space-6);
}
.admin-identity {
  display: grid;
  grid-template-columns: 10rem 1fr;
  gap: var(--space-3) var(--space-5);
  padding: var(--space-5) 0;
  border-top: 1px solid var(--color-faint);
  border-bottom: 1px solid var(--color-faint);
  margin-bottom: var(--space-5);
}
.admin-identity > dt {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--color-muted);
}
.admin-identity > dd { color: var(--color-ink); }

.admin-nav > h2 {
  font-size: var(--fs-h3);
  color: var(--color-ink);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: var(--space-5);
}
.admin-nav > ul {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-4);
}
.admin-nav > ul > li > a {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-md);
  color: var(--color-ink);
  background: var(--color-surface);
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.admin-nav > ul > li > a:hover {
  border-color: var(--color-ink);
  background: color-mix(in oklch, var(--color-ink) 4%, transparent);
}
.admin-nav > ul > li > a > strong {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: 1rem;
}
.admin-nav > ul > li > a > em {
  font-family: var(--font-accent);
  font-size: var(--fs-small);
  color: var(--color-muted);
}

/* --- Admin · contacts list ------------------------------------------- */
.admin-head .admin-back {
  margin-top: var(--space-4);
}
.admin-head .admin-back > a {
  font-family: var(--font-display);
  font-size: var(--fs-small);
  letter-spacing: 0.02em;
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-faint);
  padding-bottom: 1px;
  transition: color var(--dur-fast) var(--ease-out);
}
.admin-head .admin-back > a:hover { color: var(--color-ink); }

section.admin-contacts { margin-top: var(--space-6); }
section.admin-contacts .admin-empty {
  padding: var(--space-7) 0;
  color: var(--color-muted);
  font-size: 1.05rem;
}
section.admin-contacts .admin-count {
  font-family: var(--font-display);
  font-size: var(--fs-small);
  letter-spacing: 0.02em;
  color: var(--color-muted);
  margin-bottom: var(--space-5);
}
section.admin-contacts > ul {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;
  list-style: none;
}
section.admin-contacts > ul > li {
  padding: var(--space-6) 0;
  border-top: 1px solid var(--color-faint);
}
section.admin-contacts > ul > li:last-child {
  border-bottom: 1px solid var(--color-faint);
}
section.admin-contacts > ul > li > header {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}
section.admin-contacts > ul > li > header > h3 {
  font-size: 1.25rem;
  color: var(--color-ink);
  letter-spacing: 0.01em;
}
section.admin-contacts .contact-meta {
  font-family: var(--font-display);
  font-size: var(--fs-small);
  color: var(--color-muted);
  letter-spacing: 0.02em;
}
section.admin-contacts .contact-meta > a {
  color: var(--color-ink);
  border-bottom: 1px solid var(--color-faint);
  transition: border-color var(--dur-fast) var(--ease-out);
}
section.admin-contacts .contact-meta > a:hover {
  border-bottom-color: var(--color-accent);
}
section.admin-contacts .contact-meta > time {
  font-variant-numeric: tabular-nums;
}
section.admin-contacts .contact-msg {
  color: var(--color-ink);
  white-space: pre-wrap;
  line-height: var(--leading-body);
  max-width: 72ch;
}
section.admin-contacts .contact-ua {
  margin-top: var(--space-3);
  color: var(--color-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  word-break: break-all;
}

/* --- Legal pages (Impressum, Datenschutz) ------------------------------ */
section.legal {
  max-width: var(--reading-max);
  margin-inline: auto;
  padding: calc(var(--nav-h) + var(--space-8)) var(--gutter) var(--space-10);
  color: var(--color-muted);
}
section.legal > header {
  margin-bottom: var(--space-8);
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--color-faint);
}
section.legal > header > .kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: var(--space-4);
}
section.legal > header > h1 {
  font-size: clamp(2.75rem, 6vw, 5rem);
  line-height: var(--leading-tight);
  color: var(--color-ink);
  margin-bottom: var(--space-4);
}
section.legal > header > h1 > span {
  display: inline-block;
  text-transform: uppercase;
}
section.legal > header > h1 > em {
  display: inline-block;
  margin-left: 0.4ch;
}
section.legal > header > p.tagline {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
  font-size: 1.05rem;
  max-width: 52ch;
}

section.legal > article {
  padding: var(--space-7) 0;
}
section.legal > article + article {
  border-top: 1px solid var(--color-faint);
}
section.legal > article > h2 {
  font-size: var(--fs-h3);
  color: var(--color-ink);
  margin-bottom: var(--space-4);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
section.legal > article > h3 {
  font-size: 1rem;
  color: var(--color-ink);
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  margin: var(--space-6) 0 var(--space-3);
}
section.legal > article > p,
section.legal > article > ul {
  margin-bottom: var(--space-4);
  max-width: 68ch;
  line-height: var(--leading-body);
}
section.legal > article > ul {
  padding-left: 0;
  list-style: none;
}
section.legal > article > ul > li {
  position: relative;
  padding-left: 1.5em;
}
section.legal > article > ul > li::before {
  content: "–";
  position: absolute;
  left: 0;
  color: var(--color-muted);
}
section.legal > article .note {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--color-faint);
  margin-bottom: var(--space-6);
}
section.legal code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9em;
  padding: 1px 5px;
  background: var(--color-surface);
  border-radius: var(--radius-xs);
}

/* 7. Home sections ─────────────────────────────────────────────────────── */

/* -- Hero --------------------------------------------------------------- */
/* The hero is deliberately 200vh tall with 100vh of bottom padding.
   These two numbers aren't cosmetic — they drive the whole parallax
   + interaction model:

     • `min-height: 200vh` gives the sticky <video> (which is 100vh
       tall inside <a.feature>, see below) room to stay pinned for a
       full extra viewport after the user scrolls past the banner.
     • `padding-bottom: 100vh` is that extra viewport. This is the
       "reveal zone" — the 100vh window where the text banner has
       scrolled away but the sticky video is still on-screen. It's
       also the ONLY part of the hero that is click-through to
       <a.feature> (the banner band on top has `pointer-events: auto`
       on .words to absorb clicks — see `section.hero > .words`).

   If you shrink the hero below 200vh, or trim the bottom padding,
   the reveal zone shrinks with it and the featured-project link
   becomes progressively harder (or impossible) to click. Keyboard /
   focus-visible access to <a.feature> is unaffected either way. */
section.hero {
  position: relative;
  min-height: 200vh;
  padding: 0 var(--gutter) 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* No `background-color` override — inherit the full canvas surface
     (`--color-canvas` + `--brand-texture` + `--brand-texture-blend`)
     from the shared rule above. The hero is now a first-class brand
     canvas like `body` and `dialog`, so the "EVERY BRAND" band sits
     on the same dark-grey + faint-grain backdrop as the rest of the
     site instead of a dead pure-black slab.
     The "strip" problem between "EVERY BRAND" and "HAS A STORY" used
     to force pure #000 here — `.blend-inner`'s multiply-knockout
     collapses every non-text pixel to 0 regardless of input, and any
     shade brighter than 0 on the surrounding canvas would read as a
     visible seam across the blend band. That's now resolved by
     `.blend::after` (see below), which stacks a `mix-blend-mode:
     lighten` layer carrying the same brand canvas on top of the
     multiply output, lifting the flat 0 pixels back up to
     `--color-canvas` so the blend band colour-matches the surrounding
     hero exactly. The multiply text cutouts stay as video because
     their dimmed-video values land above the brand canvas value and
     win the `max()` lighten. */
}

/* Sticky video layer. The <figure> holds <video> + <figcaption>. */
section.hero > a.feature {
  position: absolute;
  inset: 0 var(--gutter);
}
section.hero > a.feature > video,
section.hero > a.feature > figcaption {
  position: absolute;
}
section.hero > a.feature > video {
  inset: 0;
  width: 100%;
  height: 100vh;
  object-fit: cover;
  border-radius: var(--radius-md);
  background: var(--color-surface);
  top: 0;
  position: sticky;
}
section.hero > a.feature > figcaption {
  inset: auto 0 0 0;
  bottom: 0;
  padding: var(--space-8) var(--space-6) var(--space-6);
  color: var(--color-ink);
  background: linear-gradient(to top, rgb(0 0 0 / 55%), transparent);
  pointer-events: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  opacity: 0;
  transition: opacity var(--dur-med) var(--ease-out);
}
section.hero > a.feature:hover > figcaption,
section.hero > a.feature:focus-visible > figcaption {
  opacity: 1;
}
/* Hero-card label trapdoor reveal.
   The `.title` / `.category` boxes are now per-line clip bands — the
   transform-and-opacity dance moved INSIDE to `.line__inner`. The
   wrapper translates 100% of its own height (one full text block) so
   it sits below its own band and is clipped to nothing in the resting
   state, then rises to `translateY(0)` on hover/focus. The figcaption
   gradient still fades in on hover, which carries the visibility
   change for the whole label group; the magician slide is now a
   pure motion event without a competing per-label fade. */
section.hero > a.feature > figcaption > .title {
  display: block;
  overflow: clip;
  font-family: var(--font-accent);
  font-style: italic;
  font-size: var(--fs-h3);
  line-height: 1.1;
  text-shadow: 0 2px 12px rgb(0 0 0 / 45%), 0 1px 3px rgb(0 0 0 / 35%);
}
section.hero > a.feature > figcaption > .category {
  display: block;
  overflow: clip;
  margin-top: var(--space-2);
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--color-muted);
  text-shadow: 0 2px 10px rgb(0 0 0 / 40%), 0 1px 2px rgb(0 0 0 / 30%);
}
section.hero > a.feature > figcaption > .title    > .line__inner,
section.hero > a.feature > figcaption > .category > .line__inner {
  display: block;
  transform: translate3d(0, 100%, 0);
  will-change: transform;
}
section.hero > a.feature > figcaption > .title    > .line__inner {
  transition: transform 900ms cubic-bezier(0.16, 0.84, 0.3, 1) 80ms;
}
section.hero > a.feature > figcaption > .category > .line__inner {
  transition: transform 1000ms cubic-bezier(0.16, 0.84, 0.3, 1) 360ms;
}
section.hero > a.feature:hover         > figcaption > .title    > .line__inner,
section.hero > a.feature:hover         > figcaption > .category > .line__inner,
section.hero > a.feature:focus-visible > figcaption > .title    > .line__inner,
section.hero > a.feature:focus-visible > figcaption > .category > .line__inner {
  transform: translate3d(0, 0, 0);
}

/* `.words` is the single continuous canvas for every hero-copy pixel:
   it contains both the `header` band ("every brand" + lede) and the
   `.blend` band ("has a story"), and it is what the unified streak
   layer below paints across.

   CRITICAL — do NOT give `.words` a stacking-context trigger (no
   `isolation`, no `z-index`, no `transform`, no `filter`, etc.).
   `.blend-inner` uses `mix-blend-mode: multiply` and its backdrop is
   resolved against its nearest ancestor stacking context. The sticky
   `.feature` video that we want to show through the "has a story"
   letters is a SIBLING of `.words` (both live under `section.hero`),
   so the backdrop must be the ROOT stacking context. If we turn
   `.words` into a stacking context, the multiply starts blending
   against the black inside `.words` instead of against the video,
   and the "window into the footage" effect dies. `position: relative`
   alone is fine — it gives us a containing block for `::before`
   without creating a stacking context. */
section.hero > .words {
  position: relative;
  flex: 1;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  pointer-events: auto;
  user-select: text;
}

/* The unified streak layer. ONE pseudo spans the full `.words` box,
   replacing the previous two-pseudo setup (`header::before` and
   `.blend::before`) which rendered as two disconnected motifs with
   a visible seam at the header/blend boundary — exactly the "SVG
   getting cut off behind 'has a story'" the client was seeing in
   Pesticide.

   Stacking:
     - `.words` is NOT a stacking context (see rule above), so this
       `::before` paints in the ROOT stacking context.
     - `z-index: 2` puts it ABOVE everything else in root at that
       point of the tree: the sticky video (positioned, z:auto),
       `header` (non-positioned block, painted in flow as part of
       `.words`' content), and the `.blend-inner` multiply layer
       (atomic stacking context created by mix-blend-mode, z:auto).
     - Paint order in root is therefore: section.hero bg → header
       block + text (in-flow content of `.words`) → video (sticky,
       positioned z:auto) → `.blend-inner` multiply layer (atomic
       SC at z:auto) → streaks (this pseudo, z:2) on top of all of it.
     - The multiply on `.blend-inner` still sees the sticky video
       as its backdrop because the video is painted earlier in the
       same (root) stacking context, within `.blend-inner`'s box
       area — so the "window into the video" effect survives.
     - The mask does the visual heavy lifting: the streak motif is
       only opaque in the upper-right quadrant and fades to fully
       transparent before the gradient reaches either the "EVERY
       BRAND" zone (lower-left of the header area) or the "has a
       story" zone (lower-center of the blend area), so even though
       the pseudo is technically painted on top of the text, no
       streak pixel ever lands on a text pixel.

   Positioning:
     - `inset: 0` keeps the pseudo strictly inside `.words` —
       NO negative insets here. Any horizontal overflow would spill
       past the viewport edge (the `--gutter` on `section.hero` is
       only ~1.7vw, narrower than a -4% bleed) and bring back the
       horizontal scrollbar we just fixed. Bleed is achieved via
       `background-position` values > 100% / < 0% instead, which
       anchors the image off the box without extending the pseudo.

   No `overflow: clip` is required on `.words` because every pixel
   of the image sits inside the pseudo's box. */
section.hero > .words::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: url("/static/images/streaks.svg");
  background-repeat: no-repeat;
  /* Large single instance sized off the viewport width so the motif
     reads at the same scale on every breakpoint. The SVG's natural
     aspect ratio (~1060×1827, tall) means `auto` height will make
     the image taller than `.words` itself — that's the point: the
     streak columns should run unbroken from the top of the hero
     through the bottom of the "has a story" zone. */
  background-size: clamp(820px, 92vw, 1480px) auto;
  /* Anchor off the upper-right edge so the streaks enter the frame
     from outside the viewport (an off-camera light source) and
     travel into the composition. Slight vertical offset lifts the
     brightest part of the motif up toward the nav. */
  background-position: 108% -10%;
  /* 225° runs from upper-right (0%) to lower-left (100%). We keep
     the streaks fully opaque at the source corner and fade them
     out WELL BEFORE the gradient reaches either text zone:
       - "EVERY BRAND" sits around ~65-70% along this gradient.
       - "has a story" sits around ~70-75% along this gradient.
     Transparent stop at 62% guarantees the streaks are gone by the
     time the gradient reaches either headline. The text zones
     stay clean, and we still get a generous opaque upper-right
     quadrant of streaks. */
  -webkit-mask-image: linear-gradient(225deg, #000 0%, #000 22%, transparent 62%);
          mask-image: linear-gradient(225deg, #000 0%, #000 22%, transparent 62%);
  /* Slightly stronger than the old header pseudo because this is
     now the ONLY streak layer in the hero — it has to carry the
     whole cinematic-light-trail signal on its own. */
  opacity: 0.58;
}

section.hero > .words > header {
  flex: 1;
  /* No `background-color` override. The header is a canvas surface
     (see the shared rule above), so it paints `--color-canvas`
     (#191919) + `--brand-texture` blended with `--brand-texture-blend`
     (overlay) — the same surface as `body` and `dialog`. That's what
     hides the sticky video behind "EVERY BRAND": `section.hero`'s
     own bg is covered by the video, so the header still needs its
     OWN opaque layer, but that layer is now the brand canvas rather
     than pure black.
     Colour-matching the blend band below: `.blend::after` paints the
     identical canvas surface with `mix-blend-mode: lighten`, which
     lifts `.blend-inner`'s multiply-knockout (flat 0) up to the same
     brand canvas value. So the header and the non-text area of the
     "HAS A STORY" band both resolve to `--color-canvas` + grain, and
     read as one continuous brand backdrop with no visible seam. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: var(--space-2);
  /* Horizontal padding stacks on top of the hero-section gutter so
     text sits two gutters in from the viewport edge — same visual
     rhythm as the work grid, prevents the headline from kissing the
     banner edge. */
  padding: calc(var(--nav-h) + var(--space-6)) var(--gutter) var(--space-3);
}

section.hero > .words > header > .row {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  gap: 1.5vw;
  max-width: 100%;
}
section.hero > .words > header > .row > p.lede {
  align-self: flex-start;
  padding-top: clamp(var(--space-2), 0.8vw, var(--space-4));
  max-width: 20ch;
  color: var(--color-ink);
  font-size: var(--fs-lede);
  font-weight: 500;
  line-height: var(--leading-body);
}
/* The "has a story" block.
   `.blend` is a flex sibling of `header` that gives the "has a
   story" band a slot at the bottom of `.words`. It carries THREE
   responsibilities, working in concert with `.blend-inner` (the
   multiply layer) and `.blend::after` (the canvas lifter):

     1. POSITIONING REFERENCE for both children and for its own
        background "readability floor".

     2. READABILITY FLOOR for the multiply-knockout text.
        `mix-blend-mode: multiply` on `.blend-inner` has a fatal
        property for a "window into the video" effect: when the
        backdrop is dark (a near-black video frame), the multiply
        collapses every text pixel to black too, because
          multiply(anything, 0) = 0
        so the headline vanishes into `.blend-inner`'s own black
        bg and becomes completely unreadable. No choice of text
        colour fixes this — multiply cannot produce output
        brighter than the darker of its two inputs, so darkening
        the video drags the text down with it.

        We fix the legibility problem by lifting the BACKDROP's
        minimum brightness, not the text's. `.blend`'s own
        background paints a ~30%-opaque ink-grey layer ON TOP of
        the sticky video (inside `.blend`'s box only). That grey
        layer becomes part of the backdrop `.blend-inner`'s
        multiply samples from, so the math transforms from
          multiply(textColor, videoFrame)
                                   ↓
          multiply(textColor, 0.3·ink + 0.7·videoFrame)
        which has a hard floor of ~0.229 brightness even when the
        video frame is pure #000 — enough brightness to keep the
        glyphs legible inside the cutout AND comfortably above
        `.blend::after`'s `--color-canvas` (#191919 ≈ 0.098) so the
        lighten lifter doesn't accidentally fill the text back in.

     3. HOST for `.blend::after`, the `mix-blend-mode: lighten`
        layer that repaints the brand canvas on top of the
        multiply output. Without it, the multiply-knockout would
        read as a pure-black rectangle against the hero's
        `--color-canvas` (#191919) surroundings — a visible seam
        across the band. The lifter turns that flat 0 into the
        same `--color-canvas` + grain as the header above, while
        leaving the letter-shape video cutouts intact (their
        values are always brighter than the canvas colour and win
        the `max()` lighten). See `.blend::after` below for full
        rationale.

   Why the grey floor never bleeds out sideways: `.blend-inner` is
   block-level with no width override, so it fills `.blend`
   edge-to-edge. Every pixel outside a letter shape is covered by
   its opaque black bg, which multiply turns to black regardless of
   backdrop, and the lighter on `::after` then lifts that black to
   canvas — never showing the floor directly. The grey floor only
   reaches the viewport through the letter-shape "window" the
   multiply punches open.

   The streaks motif that used to have its own `::before` here now
   lives on `.words::before` (one level up) so a single continuous
   SVG layer flows across BOTH the header and the blend zones
   without the visible seam the per-box setup produced. */
section.hero > .words > .blend {
  position: relative;
  /* Readability floor. Derived from `--color-ink` (brand Moloko
     #c3c3c3) via `color-mix` so the floor tint stays coupled to
     the brand palette — if ink ever changes, this tracks it
     automatically. 30% is the smallest ratio that keeps the
     headline readable on an all-black video frame while still
     leaving bright frames dominated by the video cutout rather
     than by the tint. Below ~25% starts failing the dark-frame
     legibility test; above ~40% starts washing the bright-frame
     cutout into a flat grey-veiled look. */
  background: color-mix(in srgb, var(--color-ink) 30%, transparent);
  /* NO padding on `.blend` itself. Any padding here creates
     transparent gaps around `.blend-inner` through which the sticky
     video leaks out (that's the brown strip the client was seeing
     behind "has a story") AND exposes the grey floor as a literal
     grey rectangle around the headline. All spacing lives inside
     `.blend-inner` instead, so its black multiply-knockout can
     stretch to the full width of `.blend` and completely cover
     both the video and the floor. */
}

/* Brand-canvas LIFTER. This is the piece that lets the hero use
   `--color-canvas` + grain everywhere (matching body / dialog / the
   rest of the site) instead of being forced to pure black.

   Problem it solves: `.blend-inner`'s `mix-blend-mode: multiply`
   collapses every non-text pixel to exactly 0 (flat pure black),
   regardless of backdrop. That would clash hard against a
   `--color-canvas` (#191919) header — a visible rectangular seam
   across the "HAS A STORY" band.

   How it fixes it: this pseudo paints the SAME canvas surface
   (`--color-canvas` + `--brand-texture` + overlay) as `body` / the
   header / `section.hero`, then composites with `mix-blend-mode:
   lighten`. Per-channel, lighten returns `max(layer, backdrop)`,
   so what each pixel becomes depends on what `.blend-inner`'s
   multiply produced below:
     - Non-text pixels (multiply = 0):
         max(canvas ≈ 0.098, 0) = canvas.
       The flat black is lifted to the brand canvas value exactly,
       grain specs and all — pixel-for-pixel match with the header
       band. Seam gone.
     - "STORY" text cutout (multiply = video × readability floor,
       typically ≥ 0.23 even on pure-black frames):
         max(canvas ≈ 0.098, cutout) = cutout.
       The video-through-text reveal wins the `max()` and shows
       untouched. The readability floor on `.blend` guarantees the
       multiply result never drops below ~0.229, which is always
       brighter than the brand canvas, so the cutout never gets
       "swallowed" by the lifter.
     - "has a" fragment (multiply ≈ 0.76 × floor-composited-video,
       ≥ 0.175 on pure-black frames): same as STORY, stays visible.

   Paint-order plumbing:
     - `position: absolute; inset: 0` covers `.blend` edge-to-edge
       (same extent as `.blend-inner`).
     - `.blend-inner` is a NON-positioned flow child, so this
       positioned pseudo naturally paints AFTER it — lighten
       composites with the multiply result below, which is what we
       want. No z-index juggling needed.
     - `pointer-events: none` so the lifter can't intercept the
       click on the sticky video's `<a.feature>` below the hero
       headline zone. */
section.hero > .words > .blend::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-color: var(--color-canvas);
  background-image: var(--brand-texture);
  background-repeat: repeat;
  background-size: var(--brand-texture-size);
  background-blend-mode: var(--brand-texture-blend);
  mix-blend-mode: lighten;
}
/* Multiply-knockout: this is where the video-through-text effect
   actually happens, and where the video gets hidden everywhere else.

   Two-stage pipeline:
     1. THIS element (`.blend-inner`) runs `mix-blend-mode: multiply`
        against (video + readability floor). Non-text pixels collapse
        to flat 0, text pixels become a dimmed video cutout.
     2. `.blend::after` (see its own docblock above) runs
        `mix-blend-mode: lighten` against the multiply output with a
        `--color-canvas` + grain fill. That lifts the flat 0 back up
        to the brand canvas value so the blend band matches the
        header and `section.hero` pixel-for-pixel. Text cutouts
        survive because their dimmed-video values land above the
        canvas value and win the lighten `max()`.

   Why stage 1 stays at pure #000000 (not `--color-canvas` #191919):
   multiplying #000000 with *any* backdrop yields #000000, so the
   non-text area is perfectly flat — no tint-by-frame flicker. Stage
   2 then repaints that flat area with the real brand canvas. If we
   tried to use `--color-canvas` here, the multiply would produce a
   colour varying by video frame (0.098 × frame), and stage 2's
   lighten would leak the brightest video frames through in non-text
   areas as a dim video ghost. Pure black is the only backdrop that
   guarantees a clean seam-free lift.

   CRUCIAL: stage 1's backdrop is NOT just the raw video. It's the
   sticky `.feature` video composited with `.blend`'s own 30%
   ink-grey floor (see the `.blend` rule above for the rationale
   and math). A plain `multiply(text, video)` would drop the
   headline to invisible on dark frames; the grey floor guarantees
   a minimum backdrop brightness of ~0.229 inside every letter
   shape, so the headline stays legible regardless of what the
   video is doing — AND keeps the multiply output above the lighten
   threshold (~0.098) so stage 2 doesn't accidentally fill the
   cutouts back in.

   Critical sizing rules:
     - `display: block` + no width override → `.blend-inner` fills
       the full width of `.blend` (= full width of `.words` = exactly
       the horizontal extent of the sticky video). No horizontal
       sliver of video escapes to either side, and no sliver of the
       `.blend` grey floor leaks out either.
     - Padding lives inside `.blend-inner` (NOT on `.blend`) so the
       black multiply bg paints through the padding. Any padding on
       the parent `.blend` would be transparent, exposing both the
       video and the grey floor around the edges.
     - `.blend-inner` sits flush against the bottom of `header`
       (flex-column siblings, `header` is `flex: 1`) — so between
       the two of them they paint a solid canvas from the top of
       the hero to the bottom of the first viewport, completely
       hiding the sticky video except through the "has a story"
       letter shapes. */
section.hero > .words > .blend > .blend-inner {
  padding: 0 var(--gutter) clamp(var(--space-4), 2.5vw, var(--space-6));
  background: #000000;
  mix-blend-mode: multiply;
}
/* The headline itself — with the `<span.story-word>` portion ("STORY")
   intentionally scaled taller for extra emphasis.
   (The italic `<em>has a </em>` fragment is DELIBERATELY coloured
   differently; see the rule immediately after this one.)

   Pure white is the identity element for multiply: the parent's
   `mix-blend-mode: multiply` samples `.blend`'s compound backdrop
   (30% ink floor + 70% video) inside each letter shape, and
     multiply(#fff, backdrop) = backdrop
   so "STORY" reads as a straight window into that backdrop — the
   cinematic video cutout on bright frames, a soft ~#3a3a3a grey
   on pure-black frames (the floor). This is the "reveal" half of
   the two-tone effect. */
section.hero > .words > .blend > .blend-inner > p.line {
  color: #ffffff;
  font-size: clamp(2.25rem, 9vw, 11rem);
  text-align: center;
}
section.hero > .words > .blend > .blend-inner > p.line .story-word {
  display: inline-block;
  transform: scaleY(1.16);
  transform-origin: 50% 58%;
}

/* The "has a" fragment. Wrapped in <em> in the template so we have
   a targetable selector for the alternate treatment.

   Why a different colour from the rest of the line:
     The original design (branch base 218c1aa, before any of this
     branch's commits) had a two-tone knockout — "has a" reading
     as a soft grey veneer laid OVER the footage, "STORY" reading
     as a clean video cutout. That asymmetry is what the client
     has repeatedly asked for:
       - "has a"  → opaque-looking grey glyphs; the video is barely
                    visible through the serifs; the italic reads as
                    a poetic pause painted onto the film.
       - "STORY"  → full video-through cutout; the blocky sans reads
                    as the cinematic reveal cut INTO the film.

   How the colour produces that look (math inside the multiply):
     `.blend-inner` has `mix-blend-mode: multiply`. Each pixel of
     its contents is composited with `.blend`'s compound backdrop
     (30% ink floor + 70% video — see the `.blend` rule above) as
     `result = (textColor / 255) × backdrop`:
       - white (#fff, 255/255 = 1.00) × backdrop = backdrop
       - ink  (#c3c3c3, 195/255 = 0.76) × backdrop = 0.76 × backdrop
     So "has a" always paints ~24% darker than "STORY" — the
     greyish translucent overlay look, preserved on both bright
     and dark video frames. On a pure-black frame, "has a"
     bottoms out around `0.76 × 0.3 × #c3c3c3 ≈ #2c2c2c`, which
     is dimmer than "STORY"'s `#3a3a3a` floor but still clearly
     visible against the flat black of the knockout.

   Why explicit here, not inherited:
     The old implementation depended on the generic
     `p strong, p em { color: var(--color-ink); }` rule in §4.
     That's fragile — any refactor of the default inline-emphasis
     colour would silently break the hero treatment. Locking the
     value in with a selector that's specific to the blend
     context removes that coupling entirely. Specificity (0,0,5,3)
     easily beats the generic (0,0,0,2), and sits next to the rule
     it pairs with so future readers see both halves of the
     two-tone story in one place. */
section.hero > .words > .blend > .blend-inner > p.line em {
  color: var(--color-ink);
  font-size: 0.78em;
}

section.hero p.line {
  font-family: var(--font-display);
  font-size: var(--fs-hero);
  font-weight: 700;
  line-height: var(--leading-tight);
  color: var(--color-ink);
  text-transform: uppercase;
  letter-spacing: -0.01em;
}
section.hero > .words > header p.line {
  font-weight: 600;
}
section.hero p.line em {
  font-family: var(--font-accent);
}

/* -- Work grid --------------------------------------------------------- */
section.work {
  max-width: var(--content-max);
  margin-inline: auto;
  padding: var(--space-8) var(--gutter) var(--space-9);
}
section.work > ul {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  /* Slightly cap row height on ultra-wide screens so prose tiles don't
     drift too far apart vertically. */
  grid-auto-rows: clamp(300px, 24vw, 430px);
  gap: 1.2vw;
}
section.work > ul > li { min-width: 0; }
section.work > ul > li.large   { grid-row: span 2; }
section.work > ul > li.heading { grid-column: 1 / -1; align-self: center; padding: var(--space-6) 0; }
section.work > ul > li.prose   {
  align-self: start;
  padding: var(--space-4) 0;
  /* Anchor pseudo-element kicker (see ::before below). */
  position: relative;
}
/* Sequence the editorial pair toward each other so the two paragraphs
   read as one rhythm instead of two isolated islands across tall rows.
   Uses Selectors Level 4 "of S" filtering (modern Chromium/Safari/Firefox). */
section.work > ul > li:nth-child(odd of .prose) { align-self: end; }
section.work > ul > li:nth-child(even of .prose) { align-self: start; }

section.work > ul > li.heading > h2 {
  font-size: var(--fs-display);
  line-height: var(--leading-tight);
  max-width: 24ch;
  color: var(--color-ink);
}

/* Prose tiles — short editorial blocks that sit *between* project
   cards. At body size in a 27vw-tall cell they read as adrift, so
   give them (a) a small kicker bar that echoes the hero streak
   motif and anchors the block to the grid, (b) a fluid, slightly
   larger type size so the copy actually fills its cell, and (c)
   the brand italic + medium-weight accents on <em>/<strong>. */
section.work > ul > li.prose::before {
  content: "";
  display: block;
  width: 4rem;
  height: 1px;
  margin-bottom: var(--space-4);
  background: linear-gradient(90deg, rgb(255 255 255 / 80%), rgb(255 255 255 / 18%));
  opacity: 0.88;
}
section.work > ul > li.prose > p {
  font-size: clamp(1.02rem, 1.22vw, 1.22rem);
  line-height: 1.62;
  max-width: 46ch;
  color: color-mix(in srgb, var(--color-ink) 86%, #000 14%);
  font-weight: 400;
  letter-spacing: -0.002em;
}
section.work > ul > li.prose > p strong {
  font-weight: 500;
  color: var(--color-ink);
}
section.work > ul > li.prose > p em {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  color: var(--color-ink);
  /* Playfair runs visually smaller than Neue Montreal at the same
     size; nudge it back up so "performs" lands at the same optical
     weight as the surrounding roman. */
  font-size: 1.08em;
  letter-spacing: 0;
}

/* Project card = li > a > video + figcaption
   -----------------------------------------------
   Design intent: the card itself is ANCHORED — it never translates.
   Only the content inside it (video zoom + caption reveal) animates.

   Why: translating the `<a>` hit-target on hover caused a feedback
   loop. When the card rose 6px, the cursor (if it was near the
   bottom edge) could end up outside the new position, triggering
   `mouseleave`. The card would snap back, recapture the cursor, and
   re-fire `mouseenter` — a ~280ms oscillation that read as jitter.
   That, plus the `<a>` and `<video>` compositor layers moving at
   different transition durations and drifting visually against each
   other, was the "slightly upwards too quickly / jittery" feel.

   Card stays put. The only hover signals are:
     • a slow, cinematic 1.035x video push-in (720ms)
     • a gradient+caption reveal choreographed as one beat */
section.work > ul > li > a {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  min-height: 240px;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--color-surface);
  isolation: isolate;
}
/* Cards use `.reveal` only for the opacity fade-in; suppress the
   default 2.5rem vertical translate so nothing is mid-animating on
   the card itself when the user hovers. */
section.work > ul > li > a.reveal {
  transform: none;
  transition: opacity var(--dur-reveal) var(--ease-out);
  will-change: opacity;
}

section.work > ul > li > a > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  will-change: transform;
  transform: scale(1);
  /* Slow cinematic push-in. 720ms reads like a lens move, not a
     button press. Previously 520ms, which was still snappy enough to
     feel like a UI element reacting. */
  transition: transform 720ms var(--ease-out);
}
section.work > ul > li > a.reveal:hover > video,
section.work > ul > li > a.reveal:focus-visible > video {
  transform: scale(1.035);
}

section.work > ul > li > a > figcaption {
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--space-7) var(--space-5) var(--space-5);
  background: linear-gradient(to top, rgb(0 0 0 / 62%) 0%, rgb(0 0 0 / 22%) 55%, transparent 100%);
  pointer-events: none;
  color: var(--color-ink);
  opacity: 0;
  transition: opacity 500ms var(--ease-out);
}
section.work > ul > li > a.reveal:hover > figcaption,
section.work > ul > li > a.reveal:focus-visible > figcaption {
  opacity: 1;
}

/* Work-grid card trapdoor reveal — mirrors the hero-card pattern just
   above. `.title` and `.category` become per-label clip bands; the
   inner span is the only thing that moves, sitting one full
   text-height below its band in the resting state and rising on
   hover/focus. The figcaption opacity transition above carries the
   gradient fade; the slide is now a pure motion event. */
section.work > ul > li > a > figcaption > .title {
  display: block;
  overflow: clip;
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 1.35rem;
  line-height: 1.15;
  text-shadow: 0 2px 10px rgb(0 0 0 / 50%), 0 1px 3px rgb(0 0 0 / 35%);
}
section.work > ul > li > a > figcaption > .category {
  display: block;
  overflow: clip;
  margin-top: var(--space-2);
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--color-muted);
  text-shadow: 0 2px 8px rgb(0 0 0 / 45%), 0 1px 2px rgb(0 0 0 / 30%);
}
section.work > ul > li > a > figcaption > .title    > .line__inner,
section.work > ul > li > a > figcaption > .category > .line__inner {
  display: block;
  transform: translate3d(0, 100%, 0);
  will-change: transform;
}
section.work > ul > li > a > figcaption > .title    > .line__inner {
  transition: transform 560ms var(--ease-out) 100ms;
}
section.work > ul > li > a > figcaption > .category > .line__inner {
  transition: transform 560ms var(--ease-out) 200ms;
}
section.work > ul > li > a.reveal:hover         > figcaption > .title    > .line__inner,
section.work > ul > li > a.reveal:hover         > figcaption > .category > .line__inner,
section.work > ul > li > a.reveal:focus-visible > figcaption > .title    > .line__inner,
section.work > ul > li > a.reveal:focus-visible > figcaption > .category > .line__inner {
  transform: translate3d(0, 0, 0);
}

/* -- CTA --------------------------------------------------------------- */
section.cta {
  position: relative;
  isolation: isolate;
  /* `clip` preserves visual clipping for the streak motif without creating
     a scroll container; `overflow: hidden` can trap `view()` timelines on
     children and leave reveal text stuck at partial opacity/progress. */
  overflow: clip;
  padding: var(--space-10) var(--gutter) var(--space-9);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
  border-top: 1px solid var(--color-faint);
}
section.cta > h2 {
  /* Mirror the contact aside's fluid pairing: display roman on one
     line, Playfair italic underneath. Clamp ceiling tracks the
     live site's 5.25rem. */
  font-size: clamp(2.5rem, 6vw, 5.25rem);
  line-height: var(--leading-tight);
  color: var(--color-ink);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  max-width: 24ch;
}
section.cta > h2 > span {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
}
section.cta > h2 > em {
  display: block;
  margin-top: 0.08em;
  padding-left: 0.25ch;
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  /* Italic runs wider than the roman — back the size off a touch so
     the two lines read as a matched pair, not a scale step. */
  font-size: 0.96em;
}

/* 8. Contact (shared by page and modal) ─────────────────────────────── */

/* Slate-split layout: title column + form column */
section.contact,
dialog#contact-modal {
  --col-title: 42fr;
  --col-form:  58fr;
}

section.contact {
  max-width: var(--reading-max);
  margin-inline: auto;
  padding: calc(var(--nav-h) + var(--space-9)) var(--gutter) var(--space-10);
  display: grid;
  grid-template-columns: var(--col-title) var(--col-form);
  gap: var(--space-9);
  min-height: 80dvh;
  align-items: start;
}

/* Title column (aside shared by page + modal) */
section.contact > aside,
dialog#contact-modal > aside {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-right: var(--space-7);
  min-height: 280px;
}
section.contact > aside > p.kicker,
dialog#contact-modal > aside > p.kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: var(--space-6);
}
section.contact > aside > h1,
section.contact > aside > h2,
dialog#contact-modal > aside > h2 {
  color: var(--color-ink);
  font-size: clamp(2.75rem, 6vw, 5.25rem);
  line-height: var(--leading-tight);
  margin-bottom: var(--space-5);
}
section.contact > aside > h1 > span,
section.contact > aside > h2 > span,
dialog#contact-modal > aside > h2 > span {
  display: block;
  text-transform: uppercase;
}
section.contact > aside > h1 > em,
section.contact > aside > h2 > em,
dialog#contact-modal > aside > h2 > em {
  display: block;
  margin-top: -0.15em;
  padding-left: 0.4ch;
}
section.contact > aside > p.tagline,
dialog#contact-modal > aside > p.tagline {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
  font-size: 1.1rem;
}
/* Hairline divider on modal — animates in on open */
dialog#contact-modal > aside > i {
  position: absolute;
  top: 0;
  right: 0;
  width: 1px;
  height: 100%;
  background: var(--color-faint);
  transform-origin: top;
}

/* Form column (form itself shared) */
section.contact > section,
dialog#contact-modal > section { min-width: 0; }

form[action="/contact"] {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  position: relative;
}
form[action="/contact"] > p[role="alert"] {
  border: 1px solid var(--color-accent);
  background: color-mix(in oklch, var(--color-accent) 10%, transparent);
  color: var(--color-ink);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--fs-small);
}
form[action="/contact"] > p[role="alert"][hidden] { display: none; }

/* Each field = a <label> wrapping its <span> (label text) + <input>. */
form[action="/contact"] > label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: relative;
}
form[action="/contact"] > label > span {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--color-muted);
  line-height: 1;
}
form[action="/contact"] > label > span > small {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--color-muted);
  opacity: 0.7;
  margin-left: 0.5ch;
  font-size: inherit;
}

/* Underlined inputs — box-shadow underline so the focus state grows
   from 1px → 2px without causing layout shift. The `:not([type=...])`
   exemptions keep the newsletter opt-in checkbox (and any future
   non-text input) from inheriting the 100%-wide underlined look. */
form[action="/contact"] input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
form[action="/contact"] textarea {
  appearance: none;
  width: 100%;
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--color-ink);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.125rem;
  line-height: 1.5;
  padding: 10px 0;
  caret-color: var(--color-ink);
  box-shadow: inset 0 -1px 0 0 var(--color-faint);
  transition: box-shadow var(--dur-med) var(--ease-out);
}
form[action="/contact"] input:not([type="checkbox"]):not([type="radio"]):not([type="file"])::placeholder,
form[action="/contact"] textarea::placeholder { color: transparent; }
form[action="/contact"] input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):hover,
form[action="/contact"] textarea:hover { box-shadow: inset 0 -1px 0 0 color-mix(in oklch, var(--color-ink) 45%, transparent); }
form[action="/contact"] input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):focus,
form[action="/contact"] textarea:focus {
  outline: none;
  box-shadow: inset 0 -2px 0 0 var(--color-ink);
}
form[action="/contact"] input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):invalid:not(:placeholder-shown),
form[action="/contact"] textarea:invalid:not(:placeholder-shown) {
  box-shadow: inset 0 -1px 0 0 var(--color-accent);
}
form[action="/contact"] textarea {
  resize: vertical;
  min-height: 132px;
  padding: 12px 0;
  line-height: 1.55;
  font-weight: 400;
  font-size: 1rem;
}

form[action="/contact"] > label.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Plain inline opt-in, tucked at the bottom of the contact form. The
   `gap` override replaces the generic `> label` column-gap so the
   checkbox sits snug against the label text instead of being pushed
   down 6px below it. */
form[action="/contact"] > label.newsletter-opt,
#contact-modal label.newsletter-opt {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.6em;
  margin-top: var(--space-4);
  color: var(--color-muted);
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.4;
  cursor: pointer;
}
form[action="/contact"] > label.newsletter-opt > input[type="checkbox"],
#contact-modal label.newsletter-opt > input[type="checkbox"] {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--color-moloko, var(--color-ink));
  cursor: pointer;
}

.admin-contacts .newsletter-badge {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 2px 8px;
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-canvas);
  background: var(--color-moloko);
  border-radius: var(--radius-pill);
  vertical-align: middle;
}

form[action="/contact"] > footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  margin-top: var(--space-4);
  flex-wrap: wrap;
  border: 0;
  padding: 0;
}
form[action="/contact"] > footer > small {
  font-family: var(--font-accent);
  font-style: italic;
  max-width: 32ch;
}

/* Submit button — split label, display face + Playfair accent.
   Uses Clockwork (brand-guided pop) so the submit moment feels earned. */
form[action="/contact"] > footer > button {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: 0.5ch;
  padding: 14px 26px;
  background: var(--color-accent);
  color: var(--color-canvas);
  border-radius: var(--radius-md);
  line-height: 1;
  overflow: hidden;
  transition: transform var(--dur-fast) var(--ease-out),
              filter var(--dur-med) var(--ease-out);
}
form[action="/contact"] > footer > button:hover { transform: translateY(-2px); filter: brightness(1.06); }
form[action="/contact"] > footer > button > span {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-body);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
form[action="/contact"] > footer > button > em {
  font-size: 1.05rem;
  color: var(--color-canvas);
}
/* Busy state — film-leader line animates across the bottom */
form[action="/contact"] > footer > button[aria-busy="true"] { pointer-events: none; }
form[action="/contact"] > footer > button[aria-busy="true"] > span,
form[action="/contact"] > footer > button[aria-busy="true"] > em { opacity: 0.45; }
form[action="/contact"] > footer > button[aria-busy="true"]::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  height: 2px;
  width: 100%;
  background: var(--color-canvas);
  transform-origin: left;
  animation: leader-line 900ms linear infinite;
}

/* Success panel (shared) */
article[data-success],
section.contact [role="status"] {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-5);
  animation: swap-in var(--dur-med) var(--ease-out);
}
article[data-success][hidden] { display: none; }
article[data-success] > p.kicker,
section.contact [role="status"] > p.kicker {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  color: var(--color-muted);
}
article[data-success] > h3,
section.contact [role="status"] > h2 {
  font-size: clamp(2rem, 4vw, 3rem);
  line-height: 0.95;
  text-transform: uppercase;
  color: var(--color-ink);
}
article[data-success] > h3 > em,
section.contact [role="status"] > h2 > em {
  display: block;
  margin-top: 0.2em;
}
article[data-success] > p,
section.contact [role="status"] > p {
  color: var(--color-ink);
  max-width: 44ch;
}
article[data-success] > p > span {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
}
article[data-success] > button[data-close] {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-loose);
  text-transform: uppercase;
  padding: 10px 18px;
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-pill);
  color: var(--color-ink);
  cursor: pointer;
  transition: background var(--dur-med) var(--ease-out), color var(--dur-med) var(--ease-out);
}
article[data-success] > button[data-close]:hover { background: var(--color-ink); color: var(--color-canvas); }

/* 9. Dialogs ────────────────────────────────────────────────────────── */

dialog {
  padding: 0;
  border: 0;
  color: var(--color-ink);
  /* canvas surface applied by shared selector above */
  /* Force viewport centering. Default UA styles do this for a plain
     dialog, but once we set display:grid on the contact modal (or the
     lightbox's fit-content sizing interacts with top-layer rendering)
     some browsers collapse to top-left; explicit inset+margin is
     deterministic. */
  position: fixed;
  inset: 0;
  margin: auto;
}
dialog::backdrop {
  background: var(--color-backdrop);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity var(--dur-med) var(--ease-out);
}
dialog[open]::backdrop { opacity: 1; }
@starting-style {
  dialog[open]::backdrop { opacity: 0; }
}

/* Lightbox */
dialog#lightbox {
  width: min(92vw, 1100px);
  max-height: 92dvh;
  background: transparent;
  overflow: visible;
}
dialog#lightbox[open] { animation: dialog-in var(--dur-med) var(--ease-out); }
dialog#lightbox > iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: var(--radius-md);
  background: #000;
  box-shadow: 0 30px 100px -20px rgb(0 0 0 / 80%);
}
dialog#lightbox > button[data-close] {
  position: absolute;
  top: -14px;
  right: -14px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--color-ink);
  color: var(--color-canvas);
  font-size: 1.5rem;
  line-height: 1;
  transition: transform var(--dur-fast) var(--ease-out);
  z-index: 2;
}
dialog#lightbox > button[data-close]:hover { transform: scale(1.08); }

/* Contact modal */
dialog#contact-modal {
  width: min(1040px, 92vw);
  max-height: min(760px, 88dvh);
  border-radius: var(--radius-md);
  overflow: hidden;
  display: grid;
  grid-template-columns: var(--col-title) var(--col-form);
  /* `minmax(0, 1fr)` is the bit that makes the inner section actually
     scroll. Without an explicit row track, grid uses an implicit
     `auto` row which stretches to fit the tallest child — the form
     column expands past the dialog's `max-height`, the dialog's
     `overflow: hidden` clips the bottom, and the section's own
     `overflow-y: auto` never activates because the section itself
     isn't height-constrained. The `0` floor (vs the default `auto`
     min-size of 1fr) lets the row honour the dialog's max-height
     instead of its children's content height. */
  grid-template-rows: minmax(0, 1fr);
}
dialog#contact-modal > aside,
dialog#contact-modal > section {
  /* Same reason as above: a grid item's `min-height` defaults to
     `auto` (= content size), which prevents `overflow-y: auto`
     children from ever scrolling. Setting `0` lets the item shrink
     below its intrinsic content height so the inner scroll kicks in. */
  min-height: 0;
}
dialog#contact-modal:not([open]) { display: none; }
dialog#contact-modal[open] { animation: dialog-up 380ms var(--ease-out); }
dialog#contact-modal[open] > aside > i {
  animation: rule-in 500ms var(--ease-out) 120ms backwards;
}
dialog#contact-modal > aside {
  padding: var(--space-9) var(--space-7) var(--space-7) var(--space-8);
  /* inherits dialog's canvas surface; transparent keeps texture continuous */
  background: transparent;
}
dialog#contact-modal > section {
  position: relative;
  padding: var(--space-9) var(--space-8) var(--space-7);
  overflow-y: auto;
}
dialog#contact-modal > button[data-close] {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--color-ink);
  color: var(--color-canvas);
  font-size: 1.4rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  transition: transform var(--dur-fast) var(--ease-out);
}
dialog#contact-modal > button[data-close]:hover { transform: scale(1.08); }

/* 10. Motion + responsive ──────────────────────────────────────────── */

@keyframes dialog-in {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes dialog-up {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes rule-in {
  from { transform: scaleY(0); }
  to   { transform: scaleY(1); }
}
@keyframes swap-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes leader-line {
  0%    { transform: scaleX(0); transform-origin: left; }
  55%   { transform: scaleX(1); transform-origin: left; }
  55.01%{ transform-origin: right; }
  100%  { transform: scaleX(0); transform-origin: right; }
}

@media (max-width: 900px) {
  header[data-site] > nav[aria-label="Primary"] { display: none; }
  header[data-site] > button[data-menu-toggle]  { display: inline-flex; }
  header[data-site] > nav#mobile-menu {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    position: absolute;
    top: calc(100% + var(--space-2));
    right: var(--gutter);
    padding: var(--space-5);
    background: var(--color-canvas);
    border: 1px solid var(--color-faint);
    border-radius: var(--radius-md);
    min-width: 220px;
  }
  header[data-site] > nav#mobile-menu[hidden] { display: none; }

  section.hero {
    min-height: 180vh;
    padding-bottom: 100vh;
  }
  section.hero > .words > header {
    /* Header already flex-column; just relax the nav padding. */
    padding-top: calc(var(--nav-h) + var(--space-4));
  }
  /* Collapse the inline row on mobile so "every" occupies its own
     line and the lede (hidden below) doesn't leave an empty flex
     slot next to it. */
  section.hero > .words > header > .row {
    display: block;
  }
  /* Hide the lede on mobile the same way the live site does — if the
     paragraph stays in the single-column flow it auto-places between
     "every" and "brand" and severs the headline. Mirror the project's
     `.visually-hidden` recipe (see section 5) so screen readers,
     search engines, and meta-scrapers still see the copy and the
     hero animation doesn't paint a detached block. */
  section.hero > .words > header > .row > p.lede {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  /* Suppress motion on the visually-hidden lede's inner span (the
     paragraph itself is already sr-only). */
  section.hero > .words > header > .row > p.lede > .line__inner {
    transform: none;
    transition: none;
  }
  /* With the lede out of play, tighten stagger on brand / "has a story"
     so they don't sit through a gap where the lede line would have run. */
  section.hero > .words > header > p.line          > .line__inner { --hero-line-delay: 200ms; }
  section.hero > .words > .blend > .blend-inner > p.line > .line__inner { --hero-line-delay: 320ms; }

  section.work > ul {
    grid-template-columns: 1fr;
    grid-auto-rows: 60vw;
  }
  section.work > ul > li.heading { grid-column: 1 / -1; padding: var(--space-5) 0; }
  /* In single-column flow keep prose blocks in normal top-down rhythm. */
  section.work > ul > li.prose,
  section.work > ul > li:nth-child(odd of .prose),
  section.work > ul > li:nth-child(even of .prose) { align-self: start; }
}

@media (max-width: 760px) {
  section.contact,
  dialog#contact-modal {
    grid-template-columns: 1fr;
  }
  section.contact { gap: var(--space-6); padding-top: calc(var(--nav-h) + var(--space-6)); }
  section.contact > aside,
  dialog#contact-modal > aside { padding-right: 0; min-height: 0; }

  dialog#contact-modal {
    width: 100vw;
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    /* In single-column mode the inner <section>'s `overflow-y: auto`
       can't scroll because nothing constrains its grid-row height —
       aside and section both lay out at their intrinsic height and
       the dialog's `overflow: hidden` clips whatever overflows. Move
       the scroll to the dialog itself so aside and the form scroll
       together as one document. `overscroll-behavior: contain` stops
       the page underneath from scrolling when the dialog hits its
       end. */
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  dialog#contact-modal > aside > i { display: none; }
  dialog#contact-modal > aside    { padding: var(--space-8) var(--space-5) var(--space-5); }
  dialog#contact-modal > section  {
    padding: var(--space-5) var(--space-5) var(--space-8);
    /* Cancel the desktop column-scroll so it doesn't fight the dialog
       scroll we just enabled above. */
    overflow: visible;
  }
  /* Keep the close button reachable as the user scrolls. `fixed` inside
     a top-layer `<dialog>` anchors to the viewport, which on mobile
     means the dialog's visible corner. */
  dialog#contact-modal > button[data-close] {
    position: fixed;
  }

  form[action="/contact"] > footer { flex-direction: column; align-items: stretch; }
  form[action="/contact"] > footer > button { justify-content: center; }
}

@media (max-width: 480px) {
  header[data-site] > a > img { height: 56px; }
  section.work > ul { grid-auto-rows: 70vw; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; animation: none !important; }
  .hero-anim > * { opacity: 1; transform: none; animation: none !important; }
}

/* 11. Filmmakers ────────────────────────────────────────────────────── */

.filmmakers-page {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: calc(var(--nav-h) + var(--space-8)) var(--space-6) var(--space-9);
}
.filmmakers-empty { color: var(--color-muted); }

.filmmakers-grid {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-6);
}

/* 3D flip container.
   The outer <li class="filmmaker"> just sets aspect ratio + perspective;
   the inner <article class="flipper"> is the rotating element. Both
   faces are absolutely positioned inside it with backface-visibility
   hidden, so only one side is visible at a time. */
.filmmaker {
  position: relative;
  aspect-ratio: 1 / 1;
  perspective: 1400px;
}
.filmmaker > .flipper {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transition: transform 700ms cubic-bezier(.2, .7, .1, 1);
  cursor: pointer;
  outline: none;
}
.filmmaker > .flipper:focus-visible {
  box-shadow: 0 0 0 2px var(--color-ink);
  border-radius: var(--radius-md);
}
.filmmaker:hover > .flipper,
.filmmaker > .flipper:focus-within,
.filmmaker > .flipper:focus {
  transform: rotateY(180deg);
}

.filmmaker .face {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.filmmaker .face--front {
  background: var(--color-surface);
  display: grid;
  grid-template-rows: 1fr auto;
}
.filmmaker .face--front > img {
  grid-row: 1 / -1;
  grid-column: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.filmmaker .face--front > .initials {
  grid-row: 1 / -1;
  grid-column: 1;
  align-self: center;
  justify-self: center;
  font-family: var(--font-display);
  font-size: 4rem;
  color: var(--color-muted);
}
.filmmaker .face--front > figcaption {
  grid-row: 2;
  grid-column: 1;
  margin: 0;
  padding: var(--space-4) var(--space-5);
  background: linear-gradient(to top, rgba(0, 0, 0, 0.78), rgba(0, 0, 0, 0));
  color: var(--color-ink);
}
.filmmaker .face--front > figcaption > h2 {
  margin: 0;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
  text-shadow: 0 2px 10px rgb(0 0 0 / 50%), 0 1px 3px rgb(0 0 0 / 35%);
}
.filmmaker .face--front > figcaption > .role {
  margin: var(--space-1) 0 0;
  font-size: 0.88rem;
  color: var(--color-muted);
  text-shadow: 0 2px 8px rgb(0 0 0 / 45%), 0 1px 2px rgb(0 0 0 / 30%);
}

.filmmaker .face--back {
  transform: rotateY(180deg);
  background: var(--color-surface);
  color: var(--color-ink);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  border: 1px solid var(--color-faint);
}
.filmmaker .face--back > header > h3 {
  margin: 0;
  font-size: 1.05rem;
}
.filmmaker .face--back > header > .role {
  margin: var(--space-1) 0 0;
  color: var(--color-muted);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.filmmaker .face--back > .bio {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--color-ink);
  overflow-y: auto;
  flex: 1 1 auto;
}

/* Social icon row on the back face. */
.filmmaker .face--back > .socials {
  list-style: none;
  margin: 0;
  padding: var(--space-3) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  border-top: 1px solid var(--color-faint);
}
.filmmaker .face--back > .socials > li > a {
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-faint);
  border-radius: 50%;
  color: var(--color-muted);
  transition: color 180ms var(--ease-out), border-color 180ms var(--ease-out);
}
.filmmaker .face--back > .socials > li > a:hover,
.filmmaker .face--back > .socials > li > a:focus-visible {
  color: var(--color-ink);
  border-color: var(--color-ink);
}
.filmmaker .face--back > .socials svg {
  width: 16px;
  height: 16px;
  fill: inherit;
  stroke: currentColor;
}

@media (prefers-reduced-motion: reduce) {
  .filmmaker > .flipper,
  .filmmaker:hover > .flipper,
  .filmmaker > .flipper:focus-within {
    transition: none;
  }
  /* Drop the stagger on project cards. The transform now lives on the
     `.line__inner` wrapper (per-label trapdoor), and the figcaption
     opacity transition still carries the visibility change with no
     motion. */
  section.hero > a.feature > figcaption > .title    > .line__inner,
  section.hero > a.feature > figcaption > .category > .line__inner,
  section.work > ul > li > a > figcaption > .title    > .line__inner,
  section.work > ul > li > a > figcaption > .category > .line__inner {
    transform: none;
    transition-duration: 0.001ms;
  }
}

/* Error pages (404 / 500 / generic) ─────────────────────────────────── */

.error-page {
  max-width: 48rem;
  margin: 0 auto;
  padding: calc(var(--nav-h) + var(--space-9)) var(--space-6) var(--space-9);
  text-align: center;
  min-height: 70vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.error-page > .kicker {
  color: var(--color-muted);
  margin: 0;
}
.error-page > .error-status {
  font-family: var(--font-accent);
  font-style: italic;
  font-size: clamp(5rem, 18vw, 11rem);
  line-height: 0.95;
  margin: var(--space-3) 0 var(--space-2);
  color: var(--color-ink);
}
.error-page > .error-headline {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 3.5vw, 2.2rem);
  line-height: 1.15;
  margin: 0 0 var(--space-5);
  max-width: 28ch;
}
.error-page > .error-quote {
  margin: 0 0 var(--space-6);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--color-faint);
  border-bottom: 1px solid var(--color-faint);
  max-width: 38rem;
}
.error-page > .error-quote > em {
  display: block;
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 1.25rem;
  line-height: 1.4;
  color: var(--color-ink);
}
.error-page > .error-quote > cite {
  display: block;
  margin-top: var(--space-2);
  font-style: normal;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--color-muted);
}
.error-page > .error-body {
  color: var(--color-muted);
  max-width: 40ch;
  margin: 0 0 var(--space-6);
}
.error-page > .error-cta {
  margin: 0;
}

/* BTS (Behind-the-Scenes, YouTube) ─────────────────────────────────────
   YouTube uploads sorted by view count, infinite-scroll grid. Cards
   echo the project-tile rhythm (16:9 thumbnail above a small caption)
   but tighter — these are external videos, the card is a launcher
   into the lightbox rather than a destination of its own. */

.bts {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: calc(var(--nav-h) + var(--space-8)) var(--space-6) var(--space-9);
}
.bts-empty {
  color: var(--color-muted);
  font-family: var(--font-accent);
  font-style: italic;
  max-width: 48rem;
}

.bts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-6) var(--space-5);
}

.bts-card {
  display: contents;
}
.bts-card__link {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  color: var(--color-ink);
  outline: none;
  border-radius: var(--radius-md);
  transition: transform var(--dur-med) var(--ease-out);
}
.bts-card__link:hover { transform: translateY(-3px); }
.bts-card__link:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-accent) 28%, transparent);
}

.bts-card__media {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: color-mix(in oklch, var(--color-canvas) 60%, var(--color-ink) 6%);
}
.bts-card__media > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow) var(--ease-out),
              filter var(--dur-med) var(--ease-out);
}
.bts-card__link:hover .bts-card__media > img {
  transform: scale(1.04);
  filter: brightness(1.05);
}
/* Play-affordance dot. Sits over the thumbnail's lower-left corner so
   it doesn't obscure the centre of the frame — the actual video has
   its own player chrome once the lightbox opens. */
.bts-card__play {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-4);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.95rem;
  color: var(--color-canvas);
  background: var(--color-ink);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
  transition: transform var(--dur-fast) var(--ease-out),
              background var(--dur-med) var(--ease-out);
}
.bts-card__link:hover .bts-card__play {
  transform: scale(1.06);
  background: var(--color-accent);
}

.bts-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0 var(--space-1);
}
.bts-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.005em;
  /* Cap at two lines so the grid stays even regardless of long
     YouTube titles. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.bts-card__meta {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-muted);
  font-size: var(--fs-small);
  font-family: var(--font-display);
  letter-spacing: 0.02em;
}
.bts-card__sep { opacity: 0.6; }

/* Sentinel: an invisible row that the IntersectionObserver in
   `app.js::infiniteLoad` watches. Sits below the last card and gets
   removed once its fetch resolves. */
.bts-sentinel {
  grid-column: 1 / -1;
  height: 1px;
  visibility: hidden;
}

@media (max-width: 700px) {
  .bts-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
}

/* Tools (open source) ────────────────────────────────────────────────── */

.tools-page {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: calc(var(--nav-h) + var(--space-8)) var(--space-6) var(--space-9);
}
.tools-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--space-6);
  /* Desktop-first rail: four-up by default for a tighter, balanced top row. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.tools-list > li {
  position: relative;
  padding: var(--space-5);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  /* Per-card accent. Each <li> sets `--accent` inline; the gradient
     reads it and fades to transparent so the colour sits at the top
     of each card and recedes into the panel below — same idea as the
     filmmaker face-front gradient, just inverted (top-down) and
     keyed to a tool-specific hue rather than black. */
  background-image: linear-gradient(
    to bottom,
    color-mix(in oklch, var(--accent, var(--color-ink)) 22%, transparent) 0%,
    color-mix(in oklch, var(--accent, var(--color-ink)) 8%, transparent) 28%,
    transparent 60%
  );
  background-size: 100% 100%;
  overflow: hidden;
  transition:
    transform 380ms cubic-bezier(.2, .7, .1, 1),
    box-shadow 380ms cubic-bezier(.2, .7, .1, 1),
    border-color 260ms var(--ease-out),
    background-size 380ms var(--ease-out);
  will-change: transform;
}
/* Thin colour bar pinned to the top edge for an unambiguous brand cue
   even when the card is short or the gradient is quiet. */
.tools-list > li::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--accent, var(--color-ink));
  opacity: 0.9;
}
.tools-list > li::after {
  content: "";
  position: absolute;
  inset: -40% -28%;
  background:
    radial-gradient(circle at 20% 20%,
      color-mix(in oklch, var(--accent, #fff) 36%, transparent) 0%,
      transparent 58%);
  opacity: 0;
  transform: translate3d(-8%, 8%, 0) scale(0.96);
  transition:
    opacity 320ms var(--ease-out),
    transform 520ms cubic-bezier(.2, .7, .1, 1);
  pointer-events: none;
}
.tools-list > li > .tool-head {
  display: grid;
  /* Fixed-height logo row + auto-height title row. The fixed first
     row guarantees every card's <h2> starts at the same Y offset
     regardless of whether the logo is a 48 px monogram, an 18 px
     wordmark, or a 56 px banner — the heading lines up across the
     grid the moment the logo varies. */
  grid-template-rows: 72px auto;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  border: 0;
  padding: 0;
}
.tools-list > li > .tool-head > .tool-logo {
  align-self: center;
  justify-self: start;
  transition: transform 380ms cubic-bezier(.2, .7, .1, 1), filter 300ms var(--ease-out);
}
.tools-list > li > .tool-head > h2 {
  margin: 0;
  font-size: 1.4rem;
  align-self: end;
  transition: transform 340ms var(--ease-out), color 280ms var(--ease-out);
}
.tool-logo--mark {
  width: 48px;
  height: 48px;
  flex: 0 0 auto;
  display: block;
}
.tool-logo--wordmark {
  height: 18px;
  width: auto;
  flex: 0 0 auto;
  display: block;
  /* The SlateHub wordmark ships in their pale-cream brand colour; mute it
     to the muted-ink token here so it reads correctly on our canvas
     without re-uploading a recoloured asset. */
  filter: brightness(0.92);
  opacity: 0.95;
}
.tool-logo--banner {
  /* Pavilion ships a wide cinematic banner. Constrain to the head row's
     vertical rhythm and let it stretch up to the card width. */
  height: 56px;
  width: auto;
  max-width: 100%;
  flex: 0 1 auto;
  display: block;
  border-radius: var(--radius-sm);
}
.tools-list > li > .tool-tag {
  color: var(--color-muted);
  margin: 0 0 var(--space-3);
  font-family: var(--font-accent);
  font-style: italic;
  transition: color 280ms var(--ease-out), transform 320ms var(--ease-out);
}
.tools-list > li > .tool-link {
  margin-top: var(--space-4);
}
.tools-list > li > .tool-link a {
  color: var(--color-ink);
  border-bottom: 1px solid var(--color-faint);
}
.tools-list > li > .tool-link a:hover {
  border-bottom-color: var(--accent, var(--color-ink));
  color: color-mix(in oklch, var(--accent, var(--color-ink)) 80%, var(--color-ink));
}
@media (hover: hover) and (pointer: fine) {
  .tools-list > li:hover,
  .tools-list > li:focus-within {
    transform: translateY(-8px) scale(1.028);
    border-color: color-mix(in oklch, var(--accent, var(--color-faint)) 42%, var(--color-faint));
    box-shadow:
      0 26px 48px rgb(0 0 0 / 34%),
      0 8px 18px color-mix(in oklch, var(--accent, #000) 16%, transparent);
    background-size: 106% 106%;
  }
  .tools-list > li:hover::after,
  .tools-list > li:focus-within::after {
    opacity: 0.5;
    transform: translate3d(4%, -6%, 0) scale(1.03);
  }
  .tools-list > li:hover > .tool-head > .tool-logo,
  .tools-list > li:focus-within > .tool-head > .tool-logo {
    transform: translateY(-2px) scale(1.05);
    filter: saturate(1.08);
  }
  .tools-list > li:hover > .tool-head > h2,
  .tools-list > li:focus-within > .tool-head > h2 {
    transform: translateY(-1px);
    color: color-mix(in oklch, var(--accent, var(--color-ink)) 60%, var(--color-ink));
  }
  .tools-list > li:hover > .tool-tag,
  .tools-list > li:focus-within > .tool-tag {
    color: color-mix(in srgb, var(--color-muted) 76%, var(--color-ink) 24%);
    transform: translateY(-1px);
  }
}
@media (max-width: 1350px) {
  .tools-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 980px) {
  .tools-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .tools-list { grid-template-columns: 1fr; }
}
.tools-bottom {
  margin-top: var(--space-7);
  color: var(--color-muted);
  max-width: 56rem;
}

/* 14. Projects (public) ─────────────────────────────────────────────── */

.projects-page {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: calc(var(--nav-h) + var(--space-8)) var(--space-6) var(--space-9);
}
.projects-empty { color: var(--color-muted); }

.project-hero {
  margin: 0 0 var(--space-8);
}
.project-hero > .surface {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
  color: inherit;
  text-decoration: none;
}
.project-hero > .surface > video,
.project-hero > .surface > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.project-hero > .surface > .placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 3rem;
  color: var(--color-muted);
}
.project-hero > .surface > figcaption {
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--space-5) var(--space-6);
  background: linear-gradient(to top, rgba(0,0,0,0.82), rgba(0,0,0,0));
}
.project-hero > .surface > figcaption > h2 {
  margin: var(--space-2) 0 var(--space-1);
  font-size: clamp(1.4rem, 3vw, 2.2rem);
}
.project-hero > .surface > figcaption > .sub {
  color: var(--color-muted);
  margin: 0;
}

.projects-grid {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 760px) {
  .projects-grid { grid-template-columns: 1fr; }
}
.projects-grid > li > .project-card {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
  color: inherit;
  text-decoration: none;
}
.projects-grid video,
.projects-grid img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.projects-grid .project-card > .placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 2.4rem;
  color: var(--color-muted);
}
.projects-grid .project-card > figcaption {
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--space-4) var(--space-5);
  background: linear-gradient(to top, rgba(0,0,0,0.78), rgba(0,0,0,0));
}
.projects-grid .project-card > figcaption > h3 {
  margin: 0;
  font-size: 1.1rem;
}
.projects-grid .project-card > figcaption > .sub {
  color: var(--color-muted);
  margin: var(--space-1) 0 0;
  font-size: 0.9rem;
}

/* 15. Project detail ────────────────────────────────────────────────── */

.project-detail {
  max-width: 960px;
  margin: 0 auto;
  padding: calc(var(--nav-h) + var(--space-8)) var(--space-6) var(--space-9);
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
}
.project-detail-head > h1 {
  font-size: clamp(2rem, 5vw, 3.6rem);
  line-height: 1.05;
  margin: var(--space-2) 0;
}
.project-detail-head > .sub { color: var(--color-muted); margin: 0; }
.project-detail-back { margin-top: var(--space-3); }
.project-detail-media {
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
}
.project-detail-media > .embed {
  position: relative;
  aspect-ratio: 16 / 9;
}
.project-detail-media > .embed > iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.project-detail-media > video,
.project-detail-media > img {
  width: 100%;
  display: block;
}
.project-detail-media > .watch-link {
  padding: var(--space-3) var(--space-4);
  color: var(--color-muted);
}
.project-detail-copy > p {
  font-size: 1.05rem;
  line-height: 1.65;
  max-width: 58ch;
}
.project-detail-client > h2,
.project-detail-bts > h2 {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-muted);
  margin: 0 0 var(--space-3);
}
.project-detail-client > .client-name {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: 1.4rem;
}
.project-detail-client > .client-socials {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.project-detail-client > .client-socials > li > a {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-faint);
  border-radius: 50%;
  color: var(--color-muted);
  transition: color 180ms var(--ease-out), border-color 180ms var(--ease-out);
}
.project-detail-client > .client-socials > li > a:hover,
.project-detail-client > .client-socials > li > a:focus-visible {
  color: var(--color-ink);
  border-color: var(--color-ink);
}
.project-detail-client > .client-socials svg {
  width: 16px;
  height: 16px;
}
.bts-gallery {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-3);
}
.bts-gallery > li > img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius-sm);
}

/* 16. Admin projects (list + form) ──────────────────────────────────── */

.admin-projects > ul { list-style: none; padding: 0; margin: 0; }
.admin-projects > ul > li {
  display: grid;
  grid-template-columns: 120px 1fr auto;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-faint);
}
.admin-projects .thumb {
  width: 120px;
  height: 68px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-surface);
  display: grid;
  place-items: center;
  color: var(--color-muted);
  font-size: 1.4rem;
}
.admin-projects .thumb > img { width: 100%; height: 100%; object-fit: cover; }
.admin-projects .info > h3 { margin: 0; font-size: 1.05rem; }
.admin-projects .info > .sub { color: var(--color-muted); margin: var(--space-1) 0; }
.admin-projects .info > .meta { color: var(--color-muted); margin: 0; }
.admin-projects button.danger {
  background: transparent;
  color: var(--color-muted);
  border: 1px solid var(--color-faint);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font: inherit;
  cursor: pointer;
}
.admin-projects button.danger:hover { color: var(--color-emphasis); border-color: currentColor; }

.admin-project-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 72rem;
}
.admin-project-form fieldset {
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  margin: 0;
}
.admin-project-form fieldset > legend {
  padding: 0 var(--space-2);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.78rem;
}
.admin-project-form fieldset.grid-two {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
}
@media (max-width: 760px) {
  .admin-project-form fieldset.grid-two { grid-template-columns: 1fr; }
}
.admin-project-form label { display: flex; flex-direction: column; gap: var(--space-2); }
.admin-project-form label.block { width: 100%; }
.admin-project-form label > span {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.72rem;
  color: var(--color-muted);
}
.admin-project-form label > span > small { text-transform: none; letter-spacing: 0; }
.admin-project-form input[type="text"],
.admin-project-form input[type="url"],
.admin-project-form input[type="number"],
.admin-project-form textarea,
.admin-project-form select {
  background: var(--color-surface);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-sm);
  color: var(--color-ink);
  font: inherit;
  padding: var(--space-3);
}
.admin-project-form select { appearance: none; cursor: pointer; }

.admin-project-form fieldset.client-socials { display: grid; gap: var(--space-3); }
.admin-project-form fieldset.client-socials > .rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}
.admin-project-form fieldset.client-socials > .rows > .row {
  display: grid;
  grid-template-columns: minmax(140px, 1fr) 3fr auto;
  gap: var(--space-3);
  align-items: center;
}
@media (max-width: 600px) {
  .admin-project-form fieldset.client-socials > .rows > .row {
    grid-template-columns: 1fr auto;
    grid-auto-rows: auto;
  }
  .admin-project-form fieldset.client-socials > .rows > .row > select {
    grid-column: 1 / -1;
  }
}
.admin-project-form fieldset.client-socials .remove {
  background: transparent;
  color: var(--color-muted);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.admin-project-form fieldset.client-socials .remove:hover {
  color: var(--color-ink);
  border-color: currentColor;
}
.admin-project-form fieldset.client-socials > .add {
  justify-self: start;
  background: transparent;
  color: var(--color-ink);
  border: 1px dashed var(--color-faint);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-4);
  font: inherit;
  cursor: pointer;
}
.admin-project-form fieldset.client-socials > .add:hover {
  border-style: solid;
  border-color: var(--color-ink);
}

.admin-project-form fieldset.teaser { display: grid; gap: var(--space-4); }
.admin-project-form fieldset.teaser video.preview {
  width: 100%;
  max-width: 520px;
  background: var(--color-surface);
  border-radius: var(--radius-sm);
}
.admin-project-form .remove-line { flex-direction: row; align-items: center; gap: var(--space-2); }
.admin-project-form .current-none { color: var(--color-muted); margin: 0; }

.admin-project-form fieldset.bts { display: grid; gap: var(--space-4); }
.admin-project-form fieldset.bts > ol.existing {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: var(--space-3);
}
.admin-project-form fieldset.bts > ol.existing > li {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.admin-project-form fieldset.bts > ol.existing > li > img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-sm);
}

.admin-project-form .form-alert {
  background: var(--color-surface);
  border: 1px solid var(--color-emphasis);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  margin: 0;
}

.admin-project-form footer.actions {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  justify-content: flex-end;
}
.admin-project-form footer.actions > .cancel {
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-faint);
}
.admin-project-form footer.actions > button {
  background: var(--color-ink);
  color: var(--color-canvas);
  border: none;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm);
  font: inherit;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  cursor: pointer;
}

/* 12. Admin filmmaker list + form ───────────────────────────────────── */

.admin-filmmakers > ul { list-style: none; padding: 0; margin: 0; }
.admin-filmmakers > ul > li {
  display: grid;
  grid-template-columns: 80px 1fr auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-faint);
}
.admin-filmmakers img,
.admin-filmmakers .placeholder {
  width: 80px; height: 80px;
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  display: grid; place-items: center;
  font-family: var(--font-display);
  font-size: 1.8rem;
  color: var(--color-muted);
  text-decoration: none;
}
.admin-filmmakers img { object-fit: cover; }
.admin-filmmakers .info > h3 { margin: 0; font-size: 1.05rem; }
.admin-filmmakers .info > .role { color: var(--color-muted); margin: var(--space-1) 0; }
.admin-filmmakers .info > .meta { color: var(--color-faint); margin: 0; }
.admin-filmmakers form { margin: 0; }
.admin-filmmakers button.danger {
  background: transparent;
  color: var(--color-muted);
  border: 1px solid var(--color-faint);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font: inherit;
  cursor: pointer;
}
.admin-filmmakers button.danger:hover { color: var(--color-emphasis); border-color: currentColor; }

a.admin-new {
  margin-left: var(--space-4);
  color: var(--color-ink);
  border-bottom: 1px solid var(--color-faint);
  padding-bottom: 1px;
}

.admin-fm-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 72rem;
}
.admin-fm-form fieldset {
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  margin: 0;
}
.admin-fm-form fieldset > legend {
  padding: 0 var(--space-2);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.78rem;
}
.admin-fm-form fieldset.grid-two {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
}
.admin-fm-form fieldset.socials {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-3);
}
.admin-fm-form fieldset.socials > .hint { grid-column: 1 / -1; margin: 0; color: var(--color-muted); }
.admin-fm-form label { display: flex; flex-direction: column; gap: var(--space-2); }
.admin-fm-form label.block { width: 100%; }
.admin-fm-form label > span {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.72rem;
  color: var(--color-muted);
}
.admin-fm-form label > span > small { text-transform: none; letter-spacing: 0; }
.admin-fm-form input[type="text"],
.admin-fm-form input[type="url"],
.admin-fm-form input[type="number"],
.admin-fm-form textarea {
  background: var(--color-surface);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-sm);
  color: var(--color-ink);
  font: inherit;
  padding: var(--space-3);
}
.admin-fm-form input:focus,
.admin-fm-form textarea:focus {
  outline: 2px solid var(--color-emphasis);
  outline-offset: 1px;
}

.admin-fm-form fieldset.headshot { display: grid; gap: var(--space-4); }
.admin-fm-form fieldset.headshot .current {
  display: flex; gap: var(--space-4); align-items: center; margin: 0;
}
.admin-fm-form fieldset.headshot .current > img {
  width: 160px; height: 160px; border-radius: var(--radius-sm); object-fit: cover; display: block;
}
.admin-fm-form fieldset.headshot .remove-line { flex-direction: row; align-items: center; gap: var(--space-2); }
.admin-fm-form fieldset.headshot .current-none { color: var(--color-muted); margin: 0; }
.admin-fm-form fieldset.headshot .upload { display: flex; flex-direction: column; gap: var(--space-3); }
.admin-fm-form fieldset.headshot .stage { display: flex; flex-direction: column; gap: var(--space-3); }
.admin-fm-form fieldset.headshot .preview {
  position: relative;
  max-width: 520px;
  user-select: none;
  touch-action: none;
  background: var(--color-surface);
  border-radius: var(--radius-sm);
  overflow: hidden;
  display: inline-block;
}
.admin-fm-form fieldset.headshot .preview > img {
  display: block;
  max-width: 100%;
  height: auto;
  pointer-events: none;
}
.admin-fm-form fieldset.headshot .box {
  position: absolute;
  border: 2px solid var(--color-ink);
  box-shadow: 0 0 0 100vmax rgba(0,0,0,0.55);
  cursor: grab;
}
.admin-fm-form fieldset.headshot .box:active { cursor: grabbing; }
.admin-fm-form fieldset.headshot .handle {
  position: absolute;
  right: -10px; bottom: -10px;
  width: 20px; height: 20px;
  background: var(--color-ink);
  border-radius: 50%;
  cursor: nwse-resize;
}
.admin-fm-form fieldset.headshot .mask { display: none; }
.admin-fm-form fieldset.headshot .hint { margin: 0; color: var(--color-muted); }

.admin-fm-form .form-alert {
  background: var(--color-surface);
  border: 1px solid var(--color-emphasis);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  margin: 0;
}

.admin-fm-form footer.actions {
  display: flex; gap: var(--space-4); align-items: center; justify-content: flex-end;
}
.admin-fm-form footer.actions > .cancel {
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-faint);
}
.admin-fm-form footer.actions > button {
  background: var(--color-ink);
  color: var(--color-canvas);
  border: none;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm);
  font: inherit;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  cursor: pointer;
}
.admin-fm-form footer.actions > button:hover { transform: translateY(-1px); }

@media (max-width: 760px) {
  .admin-fm-form fieldset.grid-two { grid-template-columns: 1fr; }
}

/* 13. Admin analytics ───────────────────────────────────────────────── */

.admin-analytics {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.admin-analytics > article.range {
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.admin-analytics > article.range > header > h2 {
  margin: 0;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-muted);
}
.admin-analytics > article.range > header > dl.stats {
  margin: var(--space-3) 0 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}
.admin-analytics dl.stats > div { margin: 0; }
.admin-analytics dl.stats dt {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}
.admin-analytics dl.stats dd {
  margin: 0;
  font-family: var(--font-display);
  font-size: 2.2rem;
  line-height: 1;
}
.admin-analytics > article.range > h3 {
  margin: 0;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--color-muted);
}
.admin-analytics table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}
.admin-analytics thead th {
  text-align: left;
  font-weight: 500;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-muted);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-faint);
}
.admin-analytics th.num,
.admin-analytics td.num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-analytics tbody td {
  padding: var(--space-2) 0;
  border-bottom: 1px dotted var(--color-faint);
}
.admin-analytics tbody tr:last-child td { border-bottom: none; }
.admin-analytics .empty { color: var(--color-muted); margin: 0; }
.admin-head > .admin-note { color: var(--color-muted); margin-top: var(--space-3); max-width: 60ch; }

/* Admin newsletter table ─────────────────────────────────────────────── */

.admin-newsletter table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
  margin-top: var(--space-4);
}
.admin-newsletter thead th {
  text-align: left;
  font-weight: 500;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--color-muted);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-faint);
}
.admin-newsletter th.num,
.admin-newsletter td.num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-newsletter tbody td {
  padding: var(--space-3);
  border-bottom: 1px dotted var(--color-faint);
  vertical-align: top;
}
.admin-newsletter tbody tr:last-child td { border-bottom: none; }
.admin-newsletter tbody tr:hover td { background: var(--color-surface); }
.admin-newsletter .muted { color: var(--color-muted); }
.admin-newsletter code {
  font-size: 0.85rem;
  color: var(--color-muted);
}
.admin-newsletter a {
  color: var(--color-ink);
  border-bottom: 1px solid var(--color-faint);
  padding-bottom: 1px;
}
.admin-newsletter a:hover { border-bottom-color: var(--color-ink); }

/* Admin backup / restore ─────────────────────────────────────────────── */

.admin-backup {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  margin-top: var(--space-5);
}
.admin-backup > .card {
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.admin-backup > .card > h2 {
  margin: 0;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-muted);
}
.admin-backup > .card p { margin: 0; color: var(--color-muted); font-size: 0.92rem; }
.admin-backup > .card code { font-size: 0.85rem; color: var(--color-ink); }
.admin-backup form label.file {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
}
.admin-backup form label.file > span {
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.admin-backup form label.file > input[type="file"] {
  font: inherit;
  color: var(--color-ink);
}
.admin-backup form footer {
  margin: var(--space-4) 0 0;
  border: 0;
  padding: 0;
}
.admin-backup form button[type="submit"] {
  background: var(--color-ink);
  color: var(--color-canvas);
  border: none;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm);
  font: inherit;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  cursor: pointer;
}

.admin-alert {
  margin: var(--space-4) 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
}
.admin-alert--ok  { border: 1px solid var(--color-faint); color: var(--color-ink); }
.admin-alert--err { border: 1px solid var(--color-accent, var(--color-ink)); color: var(--color-ink); }

.admin-analytics-total { margin-top: var(--space-2); }
.admin-analytics-total code { font-size: 0.85rem; color: var(--color-ink); }

/* 16b. Gated downloads (public + admin) ──────────────────────────────
   Public `/d/:shortcode` landing page shares the contact-aside layout;
   form reuses the contact aesthetic (underlined inputs, uppercase
   micro-labels, Playfair italic em-accent on the CTA). */

.download {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(2rem, 5vw, var(--space-9));
  /* Top padding has to clear the fixed `header[data-site]` (110 px tall
     via `--nav-h`) plus give the kicker a real margin to breathe.
     The previous `clamp(var(--space-7), 6vw, var(--space-9))` topped
     out at ~6 rem, which is just under the nav height — the kicker
     was hiding behind the navbar at most viewport widths. */
  padding-top: calc(var(--nav-h) + var(--space-7));
  padding-bottom: var(--space-9);
  padding-inline: var(--gutter);
  max-width: var(--reading-max);
  margin-inline: auto;
}
.download > aside {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  /* On wider viewports the aside copy can hold its own without
     stretching across the full column. Capping at a comfortable
     measure keeps the lede readable. */
  max-width: 38rem;
}
.download > aside > .kicker { margin-bottom: var(--space-2); }
.download > aside > h1 {
  margin: 0;
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  font-size: var(--fs-h1);
  line-height: var(--leading-tight);
  letter-spacing: -0.01em;
}
.download > aside > h1 > em { font-style: italic; }
.download > aside .tagline {
  font-family: var(--font-accent);
  font-style: italic;
  color: var(--color-muted);
  max-width: 40ch;
}
.download > section {
  /* Lift the form column slightly so the first input doesn't sit
     uncomfortably close to the kicker line on the aside. */
  padding-top: var(--space-2);
}

@media (max-width: 760px) {
  .download {
    grid-template-columns: 1fr;
    gap: var(--space-6);
    /* Nav crowds harder on phones; keep extra clearance. */
    padding-top: calc(var(--nav-h) + var(--space-5));
  }
  .download > aside { max-width: none; }
  .download > section { padding-top: 0; }
}

.download-body {
  font-size: var(--fs-body);
  line-height: var(--leading-body);
  color: var(--color-muted);
  max-width: 60ch;
}
.download-body h2,
.download-body h3 {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  color: var(--color-ink);
  margin-top: var(--space-6);
  margin-bottom: var(--space-3);
  font-size: var(--fs-h3);
}
.download-body h3 { font-size: 1rem; }
.download-body p { margin-bottom: var(--space-4); }
.download-body ul,
.download-body ol { margin: 0 0 var(--space-4) 1.25rem; }
.download-body li { margin-bottom: var(--space-2); }
.download-body a { color: var(--color-ink); border-bottom: 1px solid var(--color-faint); }
.download-body a:hover { border-bottom-color: var(--color-ink); }
.download-body code {
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 0.9em;
  padding: 0.12em 0.36em;
  border-radius: var(--radius-xs);
  background: var(--color-surface);
}
.download-body pre {
  background: var(--color-surface);
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 0.9rem;
  line-height: 1.5;
}

/* Form: mirror the /contact underlined-input treatment. Scoped with a
   single attribute selector that matches every /d/<shortcode> POST
   action so we don't have to chase dynamic action values. */
form[action^="/d/"] {
  display: flex; flex-direction: column; gap: var(--space-5);
}
form[action^="/d/"] > p[role="alert"] {
  border: 1px solid var(--color-accent);
  background: color-mix(in oklch, var(--color-accent) 10%, transparent);
  color: var(--color-ink);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--fs-small);
}
form[action^="/d/"] > label {
  display: flex; flex-direction: column; gap: 6px;
  position: relative;
}
form[action^="/d/"] > label > span {
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--color-muted);
  line-height: 1;
}
form[action^="/d/"] > label > span > small {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--color-muted);
  opacity: 0.7;
  margin-left: 0.5ch;
  font-size: inherit;
}
form[action^="/d/"] input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
  appearance: none;
  width: 100%;
  background: transparent;
  border: 0; border-radius: 0;
  color: var(--color-ink);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.125rem;
  line-height: 1.5;
  padding: 10px 0;
  caret-color: var(--color-ink);
  box-shadow: inset 0 -1px 0 0 var(--color-faint);
  transition: box-shadow var(--dur-med) var(--ease-out);
}
form[action^="/d/"] input:not([type="checkbox"]):not([type="radio"]):not([type="file"])::placeholder { color: transparent; }
form[action^="/d/"] input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):focus {
  outline: none;
  box-shadow: inset 0 -2px 0 0 var(--color-ink);
}
form[action^="/d/"] > label.honeypot {
  position: absolute; left: -9999px;
  width: 1px; height: 1px; overflow: hidden;
}
form[action^="/d/"] > footer {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-5); margin-top: var(--space-4); flex-wrap: wrap;
  border: 0; padding: 0;
}
form[action^="/d/"] > footer > small {
  font-family: var(--font-accent);
  font-style: italic;
  max-width: 44ch;
}
form[action^="/d/"] > footer > button {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: 0.5ch;
  padding: 14px 26px;
  background: var(--color-accent);
  color: var(--color-canvas);
  border-radius: var(--radius-md);
  line-height: 1;
  overflow: hidden;
  transition: transform var(--dur-fast) var(--ease-out),
              filter var(--dur-med) var(--ease-out);
}
form[action^="/d/"] > footer > button:hover { transform: translateY(-2px); filter: brightness(1.06); }
form[action^="/d/"] > footer > button > span {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-body);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
form[action^="/d/"] > footer > button > em {
  font-size: 1.05rem;
  color: var(--color-canvas);
}

.download--thanks .download-hint { margin-top: var(--space-6); color: var(--color-muted); }
.download--expired > section > p { margin-top: var(--space-5); }

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

/* Admin: downloads list, signups table, markdown side-by-side editor. */
.admin-downloads-list,
.admin-downloads-signups {
  padding: var(--space-6) var(--gutter);
  max-width: var(--content-max);
  margin-inline: auto;
}
.admin-downloads-list table,
.admin-downloads-signups table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}
.admin-downloads-list th,
.admin-downloads-list td,
.admin-downloads-signups th,
.admin-downloads-signups td {
  padding: var(--space-3) var(--space-3);
  border-bottom: 1px solid var(--color-faint);
  text-align: left;
  vertical-align: top;
}
.admin-downloads-list th,
.admin-downloads-signups th {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  font-size: var(--fs-micro);
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-ink);
}
.admin-downloads-list .num,
.admin-downloads-signups .num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-downloads-list button.danger,
.admin-downloads-signups button.danger,
.admin-newsletter button.danger,
.admin-contacts button.danger {
  background: transparent;
  border: 1px solid var(--color-faint);
  color: var(--color-muted);
  padding: 4px 10px;
  border-radius: var(--radius-xs);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  cursor: pointer;
}
.admin-downloads-list button.danger:hover,
.admin-downloads-signups button.danger:hover,
.admin-newsletter button.danger:hover,
.admin-contacts button.danger:hover {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.admin-row-actions {
  margin-top: var(--space-3);
}

/* Admin form system — used by the gated-download create/edit form.
   BEM-ish: `.admin-form` is the root, `__field` is one labelled group,
   `__split` puts a markdown editor next to a live preview, `__actions`
   pins the primary + cancel CTAs at the bottom. Designed to feel like
   the rest of the cinematic admin: dark canvas, uppercase tracked-out
   micro-labels, generous spacing, Playfair italic accents. */
.admin-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-width: 1100px;
  margin-inline: auto;
  padding: var(--space-6) var(--gutter) var(--space-9);
}
.admin-form__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.admin-form__label {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  cursor: pointer;
}
.admin-form__label-text {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--color-muted);
  line-height: 1;
}
.admin-form__hint {
  display: inline-block;
  margin-left: 0.6ch;
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  font-size: 0.75rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--color-muted);
  opacity: 0.85;
}
.admin-form__current-asset {
  font-family: var(--font-display);
  color: var(--color-ink);
  font-size: 0.9rem;
}
.admin-form__current-asset > code {
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  background: var(--color-surface);
  padding: 2px 6px;
  border-radius: var(--radius-xs);
  font-size: 0.85rem;
}

/* Inputs + textarea share the same visual chassis: dark surface, thin
   border, ink text, accent-coloured focus ring. */
.admin-form__input,
.admin-form__textarea,
.admin-form__file {
  appearance: none;
  width: 100%;
  background: color-mix(in oklch, var(--color-canvas) 70%, var(--color-ink) 5%);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-sm);
  color: var(--color-ink);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1rem;
  line-height: 1.5;
  padding: 12px 14px;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.admin-form__input:hover,
.admin-form__textarea:hover,
.admin-form__file:hover {
  border-color: color-mix(in oklch, var(--color-ink) 35%, transparent);
}
.admin-form__input:focus,
.admin-form__textarea:focus,
.admin-form__file:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-accent) 22%, transparent);
}
.admin-form__textarea {
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 0.9rem;
  line-height: 1.55;
  min-height: 200px;
  resize: vertical;
}

/* File input gets the same chassis but a slightly different rhythm —
   browsers all render the "Choose file" button differently, so we
   normalise the wrapper instead of fighting each browser's button. */
.admin-form__file {
  padding: 10px 12px;
  font-size: 0.92rem;
}
.admin-form__file::file-selector-button {
  appearance: none;
  margin-right: var(--space-3);
  padding: 6px 14px;
  background: var(--color-ink);
  color: var(--color-canvas);
  border: 0;
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  cursor: pointer;
  transition: filter var(--dur-fast) var(--ease-out);
}
.admin-form__file::file-selector-button:hover { filter: brightness(1.08); }

/* A flex row that sits an input next to a small attached action
   button. Used for the shortcode field (input + "generate"). */
.admin-form__row {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
}
.admin-form__row > .admin-form__input { flex: 1 1 auto; min-width: 0; }
.admin-form__suffix-btn {
  flex: 0 0 auto;
  padding: 0 18px;
  background: transparent;
  color: var(--color-muted);
  border: 1px solid var(--color-faint);
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.admin-form__suffix-btn:hover {
  color: var(--color-ink);
  border-color: var(--color-ink);
}

/* Editor-plus-preview pair. The label takes the left column, the
   preview pane takes the right. On narrow screens it collapses to a
   single column with the preview below the editor. */
.admin-form__field--split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: stretch;
}
.admin-form__field--split > .admin-form__label { display: flex; flex-direction: column; gap: var(--space-2); }
.admin-form__field--split > .admin-form__label > .admin-form__textarea { flex: 1; }
.admin-form__preview {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border: 1px dashed var(--color-faint);
  border-radius: var(--radius-sm);
  min-height: 220px;
  /* Pad the preview's first row so its kicker visually aligns with
     the editor's label-text (which sits above the textarea). */
  padding-top: calc(var(--space-2) + 12px);
}
.admin-form__preview-kicker {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--color-muted);
}
.admin-form__preview-body {
  color: var(--color-ink);
  font-size: var(--fs-body);
  line-height: var(--leading-body);
}
.admin-form__preview-body p { margin-bottom: var(--space-3); }
.admin-form__preview-body p:last-child { margin-bottom: 0; }
.admin-form__preview-body h1,
.admin-form__preview-body h2,
.admin-form__preview-body h3 {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  margin-top: var(--space-4);
  margin-bottom: var(--space-2);
}
.admin-form__preview-body a {
  color: var(--color-ink);
  border-bottom: 1px solid var(--color-faint);
}
.admin-form__preview-body code {
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 0.85em;
  padding: 1px 5px;
  border-radius: var(--radius-xs);
  background: color-mix(in oklch, var(--color-canvas) 60%, var(--color-ink) 6%);
}

/* Action footer. Submit on the right (primary), cancel on the left
   (low-emphasis). The submit reuses the brand pill-with-italic CTA
   so it matches the contact form's "Send / your message." rhythm. */
.admin-form__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  margin-top: var(--space-4);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-faint);
  flex-wrap: wrap;
}
.admin-form__cancel {
  font-family: var(--font-display);
  font-size: var(--fs-small);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-muted);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.admin-form__cancel:hover {
  color: var(--color-ink);
  border-bottom-color: var(--color-ink);
}
.admin-form__submit {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: 0.5ch;
  padding: 14px 26px;
  background: var(--color-accent);
  color: var(--color-canvas);
  border-radius: var(--radius-md);
  border: 0;
  line-height: 1;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out),
              filter var(--dur-med) var(--ease-out);
}
.admin-form__submit:hover {
  transform: translateY(-2px);
  filter: brightness(1.06);
}
.admin-form__submit:focus-visible {
  outline: 2px solid var(--color-ink);
  outline-offset: 3px;
}
.admin-form__submit > span {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-body);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.admin-form__submit > em {
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--color-canvas);
}

@media (max-width: 900px) {
  .admin-form__field--split { grid-template-columns: 1fr; }
  .admin-form__preview { min-height: 140px; }
}
@media (max-width: 540px) {
  .admin-form__actions { justify-content: flex-end; }
  .admin-form__cancel { order: 2; }
  .admin-form__submit { order: 1; width: 100%; justify-content: center; }
}

/* ---- List-view actions column ------------------------------------- */
/* Dual-action edit + delete pair on each row. Inline-flex so the form
   that wraps the delete button doesn't push it onto its own line. */
.admin-table__actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
}
.admin-table__actions > form { margin: 0; }
.admin-table__actions-col { width: 1%; text-align: right; }
.admin-table__btn {
  display: inline-block;
  background: transparent;
  border: 1px solid var(--color-faint);
  color: var(--color-muted);
  padding: 4px 12px;
  border-radius: var(--radius-xs);
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.admin-table__btn:hover {
  color: var(--color-ink);
  border-color: var(--color-ink);
}
.admin-table__btn.danger:hover {
  color: var(--color-accent);
  border-color: var(--color-accent);
}
.admin-table__title {
  font-weight: 700;
}

.admin-downloads-list__link-cell {
  white-space: nowrap;
}
.admin-downloads-list__link-cell > a {
  margin-right: var(--space-2);
}
.admin-table__copy.is-copied {
  /* Brief visual confirmation that the URL landed on the clipboard.
     Tinted with the accent rather than a separate green so the
     palette stays compact. */
  color: var(--color-accent);
  border-color: var(--color-accent);
}

/* 17. Brand motion + decoration ─────────────────────────────────────────
   A thin layer that brings the homepage closer to the printed brand kit:
   a reversible mask-reveal on the hero headline, and a subtle
   diagonal-streak motif on the hero + CTA canvases. Hero headline motion
   is **IntersectionObserver-gated** (`is-in` on `section.hero > .words`,
   see `static/js/app.js`) — scroll only decides on vs off; each trigger
   plays a full-duration `transform` transition, not a scroll-scrubbed
   timeline (which `.reveal` still uses where supported). */

/* -- Hero mask-reveal (IO-gated, full-duration transitions) ----------
   Unlike `.reveal` site-wide, hero lines do **not** use
   `animation-timeline: view()` — tying timeline progress to scroll made
   the trapdoor advance in sync with the scrollbar. Here each `.line__inner`
   uses `transition: transform` at a fixed duration; `static/js/app.js`
   toggles `is-in` on the parent `.words` when the hero crosses the same
   visibility band as the `.reveal` IO fallback. In → lines rise in place;
   out → they return below the trapdoor; scroll back → full rise replays.

   With scripting disabled, `@media (scripting: none)` leaves the copy
   untransformed so the page stays readable without JS. */
section.hero > .words > header > .row > p.line,
section.hero > .words > header > .row > p.lede,
section.hero > .words > header > p.line,
section.hero > .words > .blend > .blend-inner > p.line {
  overflow: clip;
}
section.hero > .words > header > .row > p.line   > .line__inner,
section.hero > .words > header > .row > p.lede   > .line__inner,
section.hero > .words > header > p.line          > .line__inner,
section.hero > .words > .blend > .blend-inner > p.line > .line__inner {
  display: block;
  will-change: transform;
  transform: translate3d(0, 100%, 0);
  transition: transform 1100ms var(--ease-out);
  transition-delay: var(--hero-line-delay, 0ms);
}
section.hero > .words > header > .row > p.line   > .line__inner { --hero-line-delay: 80ms; }
section.hero > .words > header > .row > p.lede   > .line__inner { --hero-line-delay: 180ms; }
section.hero > .words > header > p.line          > .line__inner { --hero-line-delay: 280ms; }
section.hero > .words > .blend > .blend-inner > p.line > .line__inner { --hero-line-delay: 400ms; }

@media (scripting: none) {
  section.hero > .words > header > .row > p.line   > .line__inner,
  section.hero > .words > header > .row > p.lede   > .line__inner,
  section.hero > .words > header > p.line          > .line__inner,
  section.hero > .words > .blend > .blend-inner > p.line > .line__inner {
    transform: none;
    transition: none;
  }
}

section.hero > .words.is-in > header > .row > p.line   > .line__inner,
section.hero > .words.is-in > header > .row > p.lede   > .line__inner,
section.hero > .words.is-in > header > p.line          > .line__inner,
section.hero > .words.is-in > .blend > .blend-inner > p.line > .line__inner {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  section.hero > .words > header > .row > p.line   > .line__inner,
  section.hero > .words > header > .row > p.lede   > .line__inner,
  section.hero > .words > header > p.line          > .line__inner,
  section.hero > .words > .blend > .blend-inner > p.line > .line__inner {
    transform: none;
    transition: none;
  }
}

/* -- Diagonal-streak decoration --------------------------------------
   The VI-kit streak motif, treated as a composed light-trail — not a
   tiled texture. A single oversized instance of /static/images/streaks.svg
   is anchored in the upper-right breathing area of the hero (right of
   the "EVERY BRAND" headline) and cross-faded along the streaks' own
   flow direction with a linear-gradient mask. The result reads as a
   cinematic light-trail entering the frame from off-screen, rather
   than as wallpaper. On the CTA, the same motif enters from the
   opposite corner to bookend the page. Rendered behind all content
   via `isolation: isolate` + `z-index: -1` so it never steals clicks
   or obscures video/text.

   Why a mask instead of just lowering opacity everywhere?
   The old tiled approach put streaks behind the headline too — even
   at 35% they competed, because streaks and type share a colour.
   Masking gives the streaks a "source" (upper-right) and a direction
   of dissolution, so the eye reads them as atmosphere with intent
   rather than random pattern. The headline side of the banner stays
   completely clear of streak pixels. */
/* `header` only keeps `position: relative` (containing-block anchor
   for the mobile-hidden lede's `position: absolute`) — its previous
   `isolation: isolate` + streak `::before` pair has been folded into
   the single `.words::before` motif. Header's own opaque black
   background is still what hides the sticky video behind "EVERY
   BRAND"; the streaks that used to sit on `header::before` now flow
   continuously across header AND the blend zone from `.words::before`. */
section.hero > .words > header { position: relative; }
section.cta::before {
  content: "";
  position: absolute;
  inset: -8% -4%;
  pointer-events: none;
  z-index: -1;
  background-image: url("/static/images/streaks.svg");
  background-repeat: no-repeat;
  /* Mirrored corner + smaller scale: same motif, bookend composition.
     Hero streaks enter from upper-right, CTA streaks enter from
     upper-left — the page visually rhymes without literally repeating. */
  background-size: clamp(420px, 44vw, 680px) auto;
  background-position: -10% -14%;
  -webkit-mask-image: linear-gradient(135deg, #000 0%, #000 28%, transparent 68%);
          mask-image: linear-gradient(135deg, #000 0%, #000 28%, transparent 68%);
  opacity: 0.42;
}
