/* ==========================================================================
   Darsa Digital
   A deliberately small stylesheet. No framework, no build step, no webfonts —
   the type is a system stack, so the page paints immediately and requests
   nothing from anyone else.

   The palette is a grading-suite palette: a near-black that is warm enough not
   to feel like a terminal, low-chroma greys, and a single brass accent used
   sparingly. Technical data is set in mono, the way a camera report is.
   ========================================================================== */

:root {
  /* ---- colour ----------------------------------------------------------
     Taken from the logo. The mark runs from a deep navy #1A2D4F to a steel
     blue #427CAB; those two are the brand, and everything else is a neutral
     tuned slightly toward them so the page reads as one object rather than
     grey with a blue sticker on it. Still dark, still low-chroma, still the
     environment the work is actually viewed in. */
  --bg:            #0A0D13;
  --bg-raised:     #111721;
  --bg-sunken:     #070910;

  --ink:           #E6E9EE;   /* 15.6:1 */
  --ink-muted:     #98A2B2;   /*  7.5:1 */
  --ink-faint:     #7E8A9C;   /*  5.6:1 — the mono labels are 11-13px, so this
                                  has to clear 4.5:1, not 3:1 */

  --rule:          #222A38;
  --rule-soft:     #171D28;

  /* The brand blues themselves, for marks and graphics. */
  --brand-deep:    #1A2D4F;
  --brand:         #427CAB;

  /* A tint of the brand blue that clears 4.5:1 on --bg, for text and rules.
     The logo blue itself only reaches 4.36:1, which is a near miss. */
  --accent:        #5C97C7;   /*  6.2:1 */
  --accent-bright: #7FB0D8;

  /* Scroll instruments — the sweep line and its position marker. Brass
     against the navy, so they read as a gauge over the page rather than
     as brand furniture. Decorative only; no text sits on this colour. */
  --instrument:    #C8A24A;

  /* The marker's colour is recomputed from its chromaticity on every frame by
     site.js. This is the value for 2000K — the top of the page — so the dot and
     the temperature are already the right colour on first paint instead of
     flashing brass and then correcting. Keep in step with K_WARM in site.js. */
  --cie-colour:    rgb(255, 139, 22);

  /* ---- type ------------------------------------------------------------ */
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto,
          "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
          "Liberation Mono", monospace;

  /* Fluid scale. Everything is derived, so changing one line rescales the page. */
  --t-hero:  clamp(2.25rem, 1.25rem + 4.6vw, 5rem);
  --t-h2:    clamp(1.5rem, 1.1rem + 1.6vw, 2.25rem);
  --t-h3:    clamp(1.125rem, 1rem + 0.5vw, 1.4rem);
  --t-lede:  clamp(1.0625rem, 0.98rem + 0.5vw, 1.3125rem);
  --t-body:  1rem;
  --t-small: 0.8125rem;
  --t-micro: 0.6875rem;

  /* ---- space ----------------------------------------------------------- */
  --s-1: 0.25rem;  --s-2: 0.5rem;   --s-3: 0.75rem;  --s-4: 1rem;
  --s-5: 1.5rem;   --s-6: 2rem;     --s-7: 3rem;     --s-8: 4rem;
  --s-9: 6rem;     --s-10: 8rem;

  --section-y: clamp(3.5rem, 2rem + 7vw, 8rem);
  --measure:   68ch;
  --page-max:  88rem;
  --nav-h:     4rem;
  --gutter:    clamp(1.25rem, 0.6rem + 3vw, 4rem);

  color-scheme: dark;
}

/* ---- light theme --------------------------------------------------------
   Offered through the nav toggle for visitors who want it; the choice
   persists in localStorage, and dark is always the default. Every colour on
   the page rides these variables, so this block is the whole theme — nothing
   below mentions dark or light. Ratios re-verified here, not carried over:
   ink 15.7:1, muted 6.8:1, faint 4.9:1, accent 6.1:1, all on --bg. */
[data-theme="light"] {
  --bg:            #F1F4F8;
  --bg-raised:     #FFFFFF;
  --bg-sunken:     #E3E9F1;

  --ink:           #141B26;
  --ink-muted:     #4A5568;
  --ink-faint:     #5D6B7E;

  --rule:          #D3DBE6;
  --rule-soft:     #E1E8F1;

  --accent:        #2E5F8A;
  --accent-bright: #1A2D4F;

  color-scheme: light;
}
[data-theme="light"] .btn--primary {
  background: var(--brand-deep);
  border-color: var(--brand-deep);
  color: #FFFFFF;
}
[data-theme="light"] .btn--primary:hover {
  background: #243D66;
  border-color: #243D66;
  color: #FFFFFF;
}
/* The wordmark that reads on dark paint is hardcoded pale; the currentColor
   twin takes the ink colour instead, so the swap is one display toggle. */
.nav__word--for-light { display: none; }
[data-theme="light"] .nav__word--for-light { display: block; }
[data-theme="light"] .nav__word--for-dark { display: none; }
[data-theme="light"] .nav__brand { color: var(--ink); }
/* The theme toggle sits with the nav links and reads as one of them. */
.theme-toggle {
  background: none;
  border: 0;
  padding: 0;
  line-height: inherit;
  white-space: nowrap;
  cursor: pointer;
  transition: color 120ms ease;
}
/* Brass carries further in the light theme, where the slate rules nearly
   vanish: major dividers go brass, minor ones stay slate. Dark is untouched. */
[data-theme="light"] .nav { border-bottom-color: var(--instrument); }
[data-theme="light"] .credits__group,
[data-theme="light"] .credits__group:last-of-type { border-color: var(--instrument); }
[data-theme="light"] .credits__group::before { background: var(--instrument); }
[data-theme="light"] .footer__nav { border-bottom-color: var(--instrument); }
/* Instruments, not destinations: the toggle and the language switch keep a
   quieter voice than the page links and stand apart from them — pushed right
   behind a rule on wide screens, flowing naturally when the row wraps. */
.theme-toggle,
.lang-switch {
  font-family: inherit;
  font-size: var(--t-small);
  color: var(--ink-muted);
  white-space: nowrap;
}
.theme-toggle:hover,
.lang-switch:hover { color: var(--ink); }
/* The pair reads as one instrument: a hairline pill holding both, pushed
   past the page links. No divider needed — the container does the separating. */
.nav__tools {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  margin-left: auto;
  border: 1px solid var(--rule-soft);
  border-radius: 999px;
  padding: 0.2rem 0.8rem;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-body);
  line-height: 1.65;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { margin: 0; font-weight: 500; line-height: 1.12; letter-spacing: -0.021em; }
p  { margin: 0 0 var(--s-4); max-width: var(--measure); }
ul { margin: 0; padding: 0; list-style: none; }
img, svg, video { display: block; max-width: 100%; height: auto; }

a { color: inherit; text-decoration-color: var(--rule); text-underline-offset: 0.22em; }
a:hover { text-decoration-color: var(--accent); }

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

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

/* ---- layout primitives -------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--section-y); }

/* ---- aura ------------------------------------------------------------------
   Sections are no longer divided by rules, so separation comes from depth:
   text sections float above the wash inside a darker blurred aura. The panel
   is the page ground at partial opacity, blurred against everything beneath
   it, with a wide soft shadow spreading the darkness past its edges so there
   is no hard rim. Poster grids and the sunken case note already have their
   own ground, so they sit this out. Heroes sit bare — no panel: the home
   hero reads over its scrim, inner heroes straight on the ground. */
.section:not(.section--posters):not(.case) > .wrap {
  background: color-mix(in srgb, var(--bg) 66%, transparent);
  backdrop-filter: blur(24px) saturate(130%);
  -webkit-backdrop-filter: blur(24px) saturate(130%);
  border-radius: 1rem;
  padding: clamp(1.5rem, 1rem + 3vw, 3rem) clamp(1.25rem, 0.6rem + 3vw, 3rem);
  box-shadow: 0 0 140px 48px color-mix(in srgb, var(--bg) 55%, transparent);
}

/* A section label: small, mono, uppercase, sitting in the left margin on wide
   screens the way a chapter marker would. */
.section__head {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-4);
  margin-bottom: var(--s-7);
}
.section__label {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.section__title { font-size: var(--t-h2); }
.section__intro {
  margin: var(--s-3) 0 0;
  color: var(--ink-muted);
  font-size: var(--t-lede);
  max-width: 56ch;
}

@media (min-width: 60rem) {
  .section__head { grid-template-columns: 12rem 1fr; gap: var(--s-6); }
  .section__label { padding-top: 0.55em; }
}

/* ---- skip link ---------------------------------------------------------- */
.skip {
  position: absolute;
  left: var(--s-4); top: var(--s-4);
  z-index: 100;
  padding: var(--s-3) var(--s-4);
  background: var(--accent);
  color: var(--bg);
  font-size: var(--t-small);
  font-weight: 600;
  text-decoration: none;
  border-radius: 3px;
  transform: translateY(calc(-100% - var(--s-5)));
  transition: transform 120ms ease;
}
.skip:focus { transform: translateY(0); }

/* ---- nav ---------------------------------------------------------------- */
/* Always the frosted glass, no fade between states. */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--rule-soft);
}
.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  min-height: 44px;
}
.nav__brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  text-decoration: none;
  flex-shrink: 0;
}
.nav__mark { width: auto; height: 1.9rem; }
.nav__word { width: auto; height: 0.95rem; opacity: 0.92; }
.nav__brand:hover .nav__word { opacity: 1; }

@media (min-width: 48rem) {
  .nav__mark { height: 2.15rem; }
  .nav__word { height: 1.1rem; }
}

/* The bar is Apple's 44px at every width. Above 48rem the links sit in line;
   below that the wordmark plus four destinations cannot share the row, so they
   fold behind the hamburger into a frosted panel. */
.nav__inner {
  flex-wrap: nowrap;
  min-height: 44px;
  gap: var(--s-3);
}

/* Hamburger: a three-bar mark drawn on a 44px square. Hidden above 48rem by
   the media reset at the end of the block. */
.nav__toggle {
  display: none;
  flex: none;
  width: 44px;
  height: 44px;
  margin-left: auto;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  padding: 0;
  color: var(--ink);
  cursor: pointer;
  /* Mirror of Apple's menutrigger: the button sits at .8, brightening to full
     on hover — the same .32s cubic-bezier(.4,0,.6,1) they use everywhere. */
  opacity: 0.8;
  transition: opacity 0.32s cubic-bezier(0.4, 0, 0.6, 1),
    color 0.32s cubic-bezier(0.4, 0, 0.6, 1);
}
.nav__toggle:hover { opacity: 1; }
.nav__toggle-bar {
  position: relative;
  display: block;
  width: 18px;
  height: 1.5px;
  border-radius: 999px;
  background: currentColor;
  transition: transform 0.32s cubic-bezier(0.4, 0, 0.6, 1);
}
.nav__toggle-bar::before,
.nav__toggle-bar::after {
  content: "";
  position: absolute;
  left: 0;
  width: 18px;
  height: 1.5px;
  border-radius: inherit;
  background: currentColor;
  transition: transform 0.32s cubic-bezier(0.4, 0, 0.6, 1);
}
.nav__toggle-bar::before { top: -6px; }
.nav__toggle-bar::after  { top: 6px; }

/* The open state, Apple's X morph: the middle bar collapses to nothing while
   the two outer bars glide to the row's centre and rotate to the diagonal, on
   Apple's curve. Closing simply runs the same transitions in reverse. */
.nav.is-open .nav__toggle-bar { transform: scaleX(0); }
.nav.is-open .nav__toggle-bar::before { transform: translateY(6px) rotate(45deg); }
.nav.is-open .nav__toggle-bar::after  { transform: translateY(-6px) rotate(-45deg); }

.nav__links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-4) var(--s-5);
  width: 100%;
}
.nav__link {
  font-size: var(--t-small);
  color: var(--ink-muted);
  text-decoration: none;
  transition: color 120ms ease;
  white-space: nowrap;
}
.nav__link:hover { color: var(--ink); }

/* The current page is marked, so you always know where you are. */
.nav__link--current { color: var(--ink); }
.nav__link--current::after {
  content: "";
  display: block;
  height: 1px;
  margin-top: 0.35em;
  background: var(--accent);
}
.nav__link--cta { color: var(--accent); }
.nav__link--cta:hover { color: var(--accent-bright); }

/* Below 48rem: one 44px row with a hamburger; the links become a frosted panel
   pinned under the bar when the toggle is open. The opening copies Apple's
   mobile menu beat for beat: a page-dimming curtain fades in on the same .32s
   cubic-bezier(.4,0,.6,1) (stepped visibility, 80ms in), and each row rises
   from -4px fading in on a 20ms-per-row stagger behind an 80ms group delay —
   closing runs the reverse cascade, last row out first. */
@media (max-width: 47.99rem) {
  .nav__toggle { display: flex; }

  .nav__links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: auto;
    margin: 0;
    padding: var(--s-2) var(--gutter) var(--s-4);
    background: color-mix(in srgb, var(--bg) 94%, transparent);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    border-bottom: 1px solid var(--rule-soft);
    /* Visibility — never display — so the rows can animate in and out. */
    visibility: hidden;
  }
  .nav.is-open .nav__links { visibility: visible; }

  /* Without JavaScript nothing can open the panel, so it is not a panel: the
     links sit in the flow under the bar and the toggle goes away. theme-boot.js
     stamps html.js before first paint; scripts off, it never arrives. */
  html:not(.js) .nav__toggle { display: none; }
  html:not(.js) .nav__links { position: static; visibility: visible; }
  html:not(.js) .nav__links > * { opacity: 1; transform: none; transition: none; }

  /* Apple's flyout rows: still, transparent, nudged up 4px at rest — then each
     one settles and lights on its staggered beat. --i/--n are stamped per row
     by site.js, so every locale staggers without touching a template. */
  .nav__links > * {
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity min(.16s + (var(--n, 6) - var(--i, 0)) * 20ms, .24s) cubic-bezier(0.4, 0, 0.6, 1),
      transform min(.16s + (var(--n, 6) - var(--i, 0)) * 20ms, .24s) cubic-bezier(0.4, 0, 0.6, 1);
    transition-delay: 0s;
  }
  .nav.is-open .nav__links > * {
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.32s cubic-bezier(0.4, 0, 0.6, 1) calc(var(--i, 0) * 20ms + 80ms),
      transform 0.32s cubic-bezier(0.4, 0, 0.6, 1) calc(var(--i, 0) * 20ms + 80ms);
  }

  /* Apple's curtain: the fixed page dim behind the open panel. The script
     hangs the element right after the header; it never takes part in layout. */
  .nav__curtain {
    position: fixed;
    inset: 0;
    z-index: 40;
    background: rgba(0, 0, 0, .4);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.32s cubic-bezier(0.4, 0, 0.6, 1) 80ms,
      visibility 0.32s step-end 80ms;
  }
  [data-theme="light"] .nav__curtain { background: rgba(232, 232, 237, .4); }
  body.nav-menu-open .nav__curtain {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.32s cubic-bezier(0.4, 0, 0.6, 1) 80ms,
      visibility 0.32s step-start 80ms;
  }
  /* The page behind holds still while the menu is open, the way theirs does. */
  body.nav-menu-open { overflow: hidden; }

  .nav__link {
    padding-block: var(--s-3);
    font-size: var(--t-body);
    border-bottom: 1px solid var(--rule-soft);
  }
  .nav__link--current::after { display: none; }
  .nav__link--cta { border-bottom: 0; }

  .nav__tools {
    margin-left: 0;
    margin-top: var(--s-3);
    align-self: flex-start;
  }
}

/* Motion-sensitive visitors get the menu at once: no morph, no stagger,
   no curtain fade — the state still changes, just instantly. */
@media (prefers-reduced-motion: reduce) {
  .nav__toggle-bar,
  .nav__toggle-bar::before,
  .nav__toggle-bar::after,
  .nav__links > *,
  .nav__curtain { transition: none !important; }
}

@media (min-width: 48rem) {
  /* Links back in line on the single 44px row. */
  .nav__inner { flex-wrap: nowrap; gap: var(--s-5); }
  .nav__links { width: auto; flex-wrap: nowrap; }
}

/* ---- chromaticity diagram ------------------------------------------------
   The CIE 1931 horseshoe sits fixed behind the page, bleeding off the right
   edge, with a marker travelling the black-body locus as you scroll — tungsten
   at the top of the document, deep daylight at the bottom — and a live readout
   of its coordinates.

   Driven by scroll progress (0 to 1), advanced by site.js once per animation frame.
   Inert until that arrives, so with the script blocked the page is complete and
   simply still. */
@keyframes cie-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.cie {
  position: fixed;
  z-index: 0;
  inset: 0;
  pointer-events: none;
  /* A CSS animation rather than a transition waiting on a JS class: the fade
     starts at first paint instead of after the deferred script has run, so it
     reads as the page arriving rather than as something loading late. */
  animation: cie-fade-in 3600ms ease both;
}

/* Two-thirds scale, pinned to the left edge with no side bleed, so the whole
   horseshoe stays on the page. The 8:9 ratio matches the SVG viewBox, so
   nothing is letterboxed. */
.cie__svg {
  position: absolute;
  top: 50%;
  left: 0;
  width: 69.33vmin;
  height: 78vmin;
  transform: translateY(-50%);
  overflow: visible;
}

/* Spectral edge: the locus line carries its own wavelength gradient
   (stroke="url(#cie-spectrum)" on the path in cie.svg — violet at the
   short-wave foot, green at the crown, red at the long-wave foot), so the
   rim reads as colour, not charcoal. No stroke colour is set here on
   purpose: the presentation attribute owns it. The grained cie-charcoal
   filter sits dormant in the SVG defs — switching it on costs real frames,
   because the travelling marker re-renders this SVG every scroll frame and
   the turbulence goes along for the ride. That was the scroll/poster
   jitter. Re-enable only with a baked (raster) grain. */
.cie__locus {
  stroke-width: 3;
  opacity: 0.95;
}
/* True-colour horseshoe interior, both themes: broken colour in brush strokes
   over bare paper — dense and vivid at the heart, running out before the rim,
   each colour sitting on its true chromaticity. Bare paper is transparent, so
   the page itself is the canvas. Baked straight into cie-fill.png
   (see scripts/cie_fill.py) and shown at full strength on both grounds. */
.cie__fill { display: block; opacity: 1; }
[data-theme="light"] .cie__fill { opacity: 1; }
/* The delivery gamuts sit in front of the wash by paint order (the polygons
   come after the fill group in cie.svg), and are drawn strong enough to read
   over the brushwork — hairlines at 0.16 simply sank into the glaze. Dash
   patterns still tell the three apart: 2020 dotted, P3 dashed, 709 solid. */
.cie__gamut { stroke-width: 1.5; opacity: 0.8; }
.cie__gamut--2020 { stroke: var(--accent); stroke-dasharray: 2 6; }
.cie__gamut--p3   { stroke: var(--accent); stroke-dasharray: 4 5; }
.cie__gamut--709  { stroke: var(--ink-muted); }

.cie__planck {
  stroke: var(--instrument);
  stroke-width: 2;
  opacity: 0.9;
}
.cie__d65 { fill: var(--ink-muted); opacity: 0.9; }

/* The marker takes the actual colour of the chromaticity it sits on — warm
   orange down at 2000K, neutral through D65, blue out past 10000K. Falls back
   to brass before the script has run. */
.cie__marker-dot  { fill: var(--cie-colour, var(--instrument)); }
.cie__marker-halo { fill: var(--cie-colour, var(--instrument)); opacity: 0.16; }

/* The readout. The one part of this that is meant to be read, so it gets real
   contrast while the diagram behind it stays atmospheric. */
/* A HUD, not part of the diagram: fixed to the corner and ABOVE the content, so
   it stays legible wherever the page has scrolled to. */
.cie__readout {
  position: fixed;
  z-index: 2;
  right: var(--gutter);
  bottom: var(--s-4);
  margin: 0;
  /* It floats over the page, so it needs its own ground — otherwise it lands on
     top of a table row and both become hard to read. */
  padding: 0.45rem 0.75rem;
  border: 1px solid var(--rule-soft);
  border-radius: 2px;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 1.1rem;
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-faint);
  opacity: 0.85;
  pointer-events: none;
  animation: cie-fade-in 3600ms ease both;
}
.cie__label { color: var(--instrument); }
.cie__readout b {
  font-weight: 500;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
/* The temperature reads in its own colour; x and y stay neutral so the row does
   not turn into a colour swatch. */
.cie__cct b { color: var(--cie-colour, var(--instrument)); }
/* In the light theme the translucent paper chip lets the page show through
   and the readout goes faint — a near-solid sunken ground instead, so the
   numbers sit on something rather than in front of everything. Full opacity,
   and the text steps up one rung of the palette, so the chip reads at a
   glance on pale paper. */
[data-theme="light"] .cie__readout {
  background: color-mix(in srgb, var(--bg-sunken) 92%, transparent);
  border-color: var(--rule);
  color: var(--ink-muted);
  opacity: 1;
}
[data-theme="light"] .cie__readout b { color: var(--ink); }
[data-theme="light"] .cie__label { color: var(--accent); }
[data-theme="light"] .tools__label { color: var(--accent); }
/* A fixed HUD that never gets out of the way is clutter: while the page is
   moving the readout steps aside, fading back in once the scroll settles. */
.cie__readout { transition: opacity 300ms ease; }
.cie__readout.is-hidden { opacity: 0; }

/* A phone needs the diagram framed differently, not switched off. Pinned to
   the left edge at the same two-thirds scale, so the locus runs through the
   middle of the screen instead of bleeding away.
   The 8:9 ratio has to hold or the diagram letterboxes and the marker stops
   sitting on the line it is meant to be sitting on. */
@media (max-width: 47.99rem) {
  .cie__svg { width: 73.33vmin; height: 82.67vmin; left: 0; }

  /* A full-width strip rather than a corner badge — there is no corner to
     spare — riding the very bottom edge past the safe area, so it covers as
     little of the scrolling text as a fixed strip can. */
  .cie__readout {
    left: var(--gutter);
    right: var(--gutter);
    bottom: calc(0.25rem + env(safe-area-inset-bottom, 0px));
    justify-content: space-between;
    gap: 0.15rem 0.5rem;
    letter-spacing: 0.08em;
    padding: 0.4rem 0.6rem;
  }
}

/* Narrowest phones: the numbers are the point, the caption is not. */
@media (max-width: 23.5rem) {
  .cie__label { display: none; }
}

/* Reading progress lived on the nav's bottom edge; removed — at low scroll
   percentages it read as a stray brass stub rather than an instrument. */
.nav { position: relative; }

/* Content sits above the diagram. */
main,
.nav,
.footer { position: relative; z-index: 1; }

/* Each credits group draws its own rule in as it comes up the screen. */
.credits__group { position: relative; }
.credits__group::before {
  content: "";
  position: absolute;
  inset: -1px 0 auto 0;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 900ms cubic-bezier(0.16, 0.84, 0.24, 1);
}
.credits__group.is-in::before { transform: scaleX(1); }

/* Reduce Motion should take away the movement, not the instrument. The marker
   position, its colour and the readout values are all baked into the HTML by
   the build at 2000K, so with scroll tracking switched off this stays a correct
   still diagram instead of a blank page. Worth getting right: Reduce Motion is
   switched on far more often on a phone than on a desktop. */
@media (prefers-reduced-motion: reduce) {
  .cie,
  .cie__readout { animation: none; }
  .credits__group::before { transition: none; transform: scaleX(1); }
}

/* ---- tiles on scroll ------------------------------------------------------
   The reference site's grid, rebuilt: columns drift on the grid's own scroll
   progress while each poster fades up from the page's black and scales from
   0.9 to 1. All of it is written by site.js frame by frame — the only job here
   is the resting state and the no-JS fallback.

   Gated on .js-scroll, which the script adds only when it is running AND motion
   is allowed. With the script blocked or Reduce Motion on, the grid is a plain
   static set of posters at full opacity. */
.tile { will-change: transform; }

.js-scroll .tile__frame {
  opacity: 0;
  transform: scale(0.9);
  transform-origin: top center;
  will-change: opacity, transform;
}

/* The frame's resting ground is already #111721 to #070910 — the page's own
   black either side of --bg — so a poster at opacity 0 is the background rather
   than a hole, which is what makes it read as a fade FROM black. */

/* ---- case note figure -----------------------------------------------------
   A frame from the shoot, full measure rather than inset, because the point of
   it is the equipment and the room — both of which vanish at thumbnail size. */
.case__figure {
  margin: var(--s-5) 0 var(--s-6);
  max-width: none;
}
.case__figure img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
  border-radius: 2px;
}
/* Article thumbnails double as the links: three small frames in a row at any
   width — compact on a phone by construction, no breakpoint needed. */
.case__gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-4);
  margin: var(--s-5) 0 var(--s-6);
}
.case__gallery .case__figure { margin: 0; }
.case__gallery .case__figure-link > img { aspect-ratio: 16 / 9; object-fit: contain; background: var(--bg-sunken); }
/* Outlet marks ride inside the caption they label, at text height — the SVG
   paints currentColor so one file serves both themes. */
.case__figure .case__logo { height: 1em; width: auto; vertical-align: -0.15em; color: var(--ink); border: 0; }
/* Raster marks cannot live at text height — they get their own line above the
   caption, large enough to read. */
.case__figure .case__logo--img { display: block; height: 26px; margin-bottom: var(--s-2); }
/* Outlet marks ride on their own line above the caption they label, all at
   the same height — vector or raster, one rule, so the text always starts at
   the same distance from the picture. */
.case__figure figcaption .case__logo--img { display: block; max-height: 26px; max-width: 100%; width: auto; height: auto; margin-bottom: var(--s-2); }
.case__figure figcaption svg.case__logo { display: block; box-sizing: content-box; height: 1em; width: auto; padding-top: calc((26px - 1em) / 2); margin-bottom: calc((26px - 1em) / 2 + var(--s-2)); }
/* Raster twins for outlets without a vector mark: one per theme, toggled
   like the nav wordmark — scoped to match the sizing rule above so the
   hiding always wins. */
.case__figure figcaption .case__logo.logo--for-light { display: none; }
[data-theme="light"] .case__figure figcaption .case__logo.logo--for-light { display: block; }
[data-theme="light"] .case__figure figcaption .case__logo.logo--for-dark { display: none; }
.case__figure-link { display: block; color: inherit; text-decoration: none; }
.case__figure-link img { transition: border-color 140ms ease; }
.case__figure-link:hover img { border-color: var(--accent); }
.case__caption {
  margin-top: var(--s-2);
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

/* The home grid gets a quiet way through to the full list. */
.tiles__more {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* A footnote, not a pitch. Sits under the last credits group at the weight of
   a caption, so it answers "is this everything?" for anyone who wonders and is
   otherwise easy to scroll past. */
.credits__note {
  margin: var(--s-5) 0 0;
  max-width: none;
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}
.credits__note a { color: var(--ink-muted); }
.credits__note a:hover { color: var(--accent); }

/* ---- CV download ----------------------------------------------------------
   The PDF is built from the same content/*.json as the table below it, so this
   is a plain link to a static file rather than anything that runs. Sits above
   the credits because that is what somebody who wants the CV came here for. */
.cvlink {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-4);
  margin: 0 0 var(--s-5);
  max-width: none;
}
.cvlink__meta {
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}
.cvlink__note {
  font-size: var(--t-small);
  color: var(--ink-faint);
}

/* ---- tools ---------------------------------------------------------------
   A separate claim from the services list: not "I can advise on this" but "the
   tool did not exist, so it was written". It sits inside the same aura panel
   as every other text section, with a quieter grid so it reads as a footnote
   to the practice rather than a product pitch — two of the three are still
   being built. */

.tools__label {
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--instrument);
  margin: 0 0 var(--s-3);
}
.tools__title { font-size: var(--t-h2); max-width: 22ch; margin: 0 0 var(--s-4); }
.tools__intro { color: var(--ink-muted); max-width: var(--measure); }

.tools__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-6);
  margin-top: var(--s-6);
}
@media (min-width: 48rem) {
  .tools__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-5); }
}

.tool { border-top: 1px solid var(--rule-soft); padding-top: var(--s-3); }
.tool__name {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-3);
  font-size: var(--t-body);
  letter-spacing: 0;
  margin: 0 0 var(--s-2);
}
/* The status carries real weight here — it is what stops "in development" from
   reading as "available". Mono and small, so it looks like a label, not a claim. */
.tool__status {
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.1rem 0.4rem;
  white-space: nowrap;
}
.tool__summary { color: var(--accent); margin: 0 0 var(--s-2); max-width: none; }
.tool__text { color: var(--ink-muted); font-size: var(--t-small); max-width: none; margin: 0; }

/* ---- hero --------------------------------------------------------------- */
/* "Full screen no matter the device": svh is the small-viewport unit, so this
   fills the screen on a phone WITHOUT the address bar cropping it, which is what
   100vh gets wrong. dvh and vh are listed first as fallbacks for older engines. */
.hero {
  display: flex;
  align-items: center;
  padding-block: var(--s-8);
}
.hero { position: relative; overflow: hidden; }
.hero__inner { position: relative; z-index: 2; width: 100%; }

/* Home hero text stands clear of the left-pinned locus: held to the right
   of the linework on wide screens (both are sized in vmin, so the clearance
   tracks the diagram at any viewport). Full width below that, where the
   diagram sits behind everything. */
@media (min-width: 48rem) {
  .page--home .hero--full .hero__inner {
    width: auto;
    max-width: none;
    /* vmin is the width on a portrait tablet, where 62vmin left a 190px
       column for a 62px headline. Cap it so the copy keeps most of the row. */
    margin-left: min(62vmin, 40vw);
    margin-right: var(--gutter);
  }
}

/* Full-bleed loop behind the home hero. Present only when a file exists. */
.hero__media { position: absolute; inset: 0; z-index: 0; }
.hero__video { width: 100%; height: 100%; object-fit: cover; filter: saturate(0.8); }

/* The scrim is what keeps the headline readable over arbitrary footage: a wash
   toward the page ground from the left and bottom, where the type sits. */
.hero__scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, var(--bg) 0%, color-mix(in srgb, var(--bg) 80%, transparent) 45%,
                    color-mix(in srgb, var(--bg) 35%, transparent) 100%),
    linear-gradient(0deg, var(--bg) 0%, transparent 55%);
}

@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }   /* the poster frame still shows through */
}
.hero__lede { max-width: 46ch; }

.hero--full {
  min-height: calc(100vh - var(--nav-h));
  min-height: calc(100dvh - var(--nav-h));
  min-height: calc(100svh - var(--nav-h));
}

/* Inner pages open large but not full, so the content below is reachable
   without a full screen of scrolling first. */
.hero--page {
  min-height: calc(52vh - var(--nav-h));
  min-height: calc(52svh - var(--nav-h));
}
.hero__eyebrow {
  margin: 0 0 var(--s-5);
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.hero__title {
  font-size: var(--t-hero);
  letter-spacing: -0.032em;
  max-width: 22ch;
  text-wrap: balance;
}
.hero__lede {
  margin-top: var(--s-6);
  max-width: 52ch;
  font-size: var(--t-lede);
  color: var(--ink-muted);
  text-wrap: pretty;
}
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-7); }

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.7rem 1.35rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  font-size: var(--t-small);
  letter-spacing: 0.02em;
  text-decoration: none;
  color: var(--ink);
  background: transparent;
  transition: border-color 140ms ease, background 140ms ease, color 140ms ease;
}
.btn:hover { border-color: var(--ink-faint); background: var(--bg-raised); }
.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #06090F;
  font-weight: 600;
}
.btn--primary:hover { background: var(--accent-bright); border-color: var(--accent-bright); color: #06090F; }
.btn--brass {
  background: var(--instrument);
  border-color: var(--instrument);
  color: #06090F;
  font-weight: 600;
}
.btn--brass:hover { background: #D9B45F; border-color: #D9B45F; color: #06090F; }

/* ---- services ----------------------------------------------------------- */
.services { display: grid; gap: 0; }

.service {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-3);
  padding-block: var(--s-7);
}
.service:first-child { padding-top: 0; }

.service__num {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.1em;
  color: var(--ink-faint);
}
.service__name { font-size: var(--t-h3); }
.service__summary {
  margin: var(--s-2) 0 var(--s-4);
  color: var(--accent);
  font-size: var(--t-body);
}
.service__text { color: var(--ink-muted); }

.service__list {
  display: grid;
  gap: var(--s-2) var(--s-6);
  margin-top: var(--s-5);
  font-family: var(--mono);
  font-size: var(--t-small);
  color: var(--ink-muted);
}
.service__list li { position: relative; padding-left: var(--s-5); line-height: 1.5; }
.service__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 0.5rem; height: 1px;
  background: var(--accent);
}

@media (min-width: 48rem)  { .service__list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 60rem)  { .service { grid-template-columns: 12rem 1fr; gap: var(--s-6); }
                             .service__num { padding-top: 0.5em; } }

/* ---- credits ------------------------------------------------------------
   Each group is a collapsed <details>. The page should not open as a wall of
   fifty jobs — the tiles and the case note carry it, and the full list is one
   click away for whoever actually wants to read it. */
.credits__group {
  border-top: 1px solid var(--rule);
}
.credits__group:last-of-type { border-bottom: 1px solid var(--rule); }

.credits__summary {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding-block: var(--s-5);
  cursor: pointer;
  list-style: none;
  transition: color 140ms ease;
}
.credits__summary::-webkit-details-marker { display: none; }
.credits__summary:hover .credits__group-name { color: var(--ink); }
.credits__summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.credits__group-name {
  font-family: var(--mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-muted);
  transition: color 140ms ease;
}
.credits__count {
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.1em;
  color: var(--ink-faint);
}

/* A plus that becomes a minus. Two rules, no icon file. */
.credits__toggle {
  position: relative;
  width: 0.75rem;
  height: 0.75rem;
  margin-left: auto;
  flex: none;
}
.credits__toggle::before,
.credits__toggle::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1px;
  background: var(--accent);
  transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1);
}
.credits__toggle::after { transform: rotate(90deg); }
.credits__group[open] .credits__toggle::after { transform: rotate(0deg); }

.credits__body { padding-bottom: var(--s-6); }

.credit {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "title  year"
    "role   role"
    "dp     dp"
    "studio studio";
  gap: var(--s-1) var(--s-4);
  padding-block: var(--s-4);
  border-bottom: 1px solid var(--rule-soft);
}
.credits__list .credit:last-child { border-bottom: 0; }

.credit__title  { grid-area: title;  font-size: 1.0625rem; letter-spacing: -0.012em; }
.credit__year   { grid-area: year; }
.credit__role   { grid-area: role;   color: var(--accent); }
.credit__dp     { grid-area: dp; }
.credit__studio { grid-area: studio; }

.credit__year,
.credit__role,
.credit__dp,
.credit__studio {
  font-family: var(--mono);
  font-size: var(--t-small);
  line-height: 1.45;
  text-wrap: pretty;
}
/* Production company names are the longest values in the table — "Echo Lake
   Entertainment", "Sony Pictures Classics" — and they are content, not labels,
   so they do not carry the tracking the column headings do. Without this a
   four-company co-production broke every name across two lines and made one row
   five times the height of its neighbours. */
.credit__studio { letter-spacing: 0.03em; }

.credit__year   { color: var(--ink-faint); font-variant-numeric: tabular-nums; padding-top: 0.3em; }
.credit__dp     { color: var(--ink-muted); }
.credit__studio { color: var(--ink-faint); }

/* One column, two facts, separated by a rule: the production's main-unit
   cinematographer above, and the person he actually worked under below — the DP
   on his own unit, or the post supervisor on a workflow job. Where those are the
   same person the second line is dropped rather than repeating the name. */
.credit__main,
.credit__unit {
  display: block;
  font-weight: inherit;
}
.credit__role .credit__unit { color: var(--ink-faint); }

.credit__unit {
  margin-top: 0.4em;
  padding-top: 0.4em;
  border-top: 1px solid var(--rule);
  color: var(--ink-faint);
}
/* No main-unit DP recorded, so there is nothing to separate it from. */
.credit__unit--only {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
  color: var(--ink-muted);
}
.credit--head .credit__unit { color: var(--ink-faint); }

.credit__name { display: block; }
/* The original-language title sits with the title, set quieter and smaller so
   the English one still reads as the heading. */
.credit__alt {
  margin-left: 0.55em;
  font-family: var(--mono);
  font-size: var(--t-small);
  letter-spacing: 0;
  color: var(--ink-faint);
  white-space: nowrap;
}
.credit__detail {
  display: block;
  margin-top: 0.15em;
  font-size: var(--t-small);
  color: var(--ink-faint);
  letter-spacing: 0;
}

.credit--head { display: none; }

/* Workflow supervision: no units, and the post supervisor in its own column. */
.credits__group--split .credit {
  grid-template-areas:
    "title  year"
    "role   role"
    "dp     dp"
    "with   with";
}
.credit__with { grid-area: with; }
.credits__group--split .credit__with {
  font-family: var(--mono);
  font-size: var(--t-small);
  line-height: 1.45;
  text-wrap: pretty;
  color: var(--ink-muted);
}

/* Sections that carry no camera column. */
.credits__group--noformat .credit {
  grid-template-areas:
    "title  year"
    "role   role"
    "dp     dp"
    "studio studio";
}

/* ---- credits on small screens ------------------------------------------------
   Below the desktop table each credit stacks title, role, cinematographer(s),
   post supervisor and studio into full-width rows. Without the column headings
   those stacked rows all looked alike — a unit ("splinter unit") read exactly
   like the unit DP below it, and the DP read exactly like the post supervisor
   below that. So each stacked fact gets its own micro label, the same words as
   the desktop headings, and the two names that share the DP cell are told
   apart: the unit DP keeps an explicit prefix, while a role's unit — already
   self-describing ("second unit") — gets a subordinate marker instead. */
@media (max-width: 75.99rem) {
  /* The supervision rows carry a studio value like every other group, so they
     need its row too — without it the studio falls back to auto placement. */
  .credits__group--split .credit {
    grid-template-areas:
      "title  year"
      "role   role"
      "dp     dp"
      "with   with"
      "studio studio";
  }

  /* Labelled blocks need air between them; the base 0.25rem row gap was drawn
     for unlabelled lines. */
  .credit { row-gap: var(--s-3); }

  .credit__role::before,
  .credit__dp::before,
  .credit__with::before,
  .credit__studio::before {
    display: block;
    margin-bottom: var(--s-1);
    font-family: var(--mono);
    font-size: var(--t-micro);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-faint);
  }
  .credit__role::before { content: "Role"; }
  html[lang="it"] .credit__role::before { content: "Ruolo"; }
  .credit__dp::before { content: "Cinematographer"; }
  html[lang="it"] .credit__dp::before { content: "Direttore della fotografia"; }
  .credit__with::before { content: "Post supervisor"; }
  html[lang="it"] .credit__with::before { content: "Supervisore di post-produzione"; }
  .credit__studio::before { content: "Studio"; }
  html[lang="it"] .credit__studio::before { content: "Studio"; }

  /* A bare second name under "Cinematographer" could be a co-DP; it is the DP
     of the unit above, so it says so. Scoped to the DP cell: a studio pair
     ("Marvel" over "Columbia") or a year range are equals, not main over
     subordinate, and keep no prefix. */
  .credit__dp .credit__unit::before { content: "Unit DP — "; }
  html[lang="it"] .credit__dp .credit__unit::before { content: "DOP di unità — "; }

  /* The unit under a role names itself ("second unit"), so a marker is enough
     to show it belongs to the role line above rather than opening a new fact. */
  .credit__role .credit__unit::before { content: "↳ "; }

  /* A supervision job with no post supervisor recorded leaves an empty row
     that reads as a missing line; drop it instead. */
  .credit__with:empty,
  .credit__studio:empty { display: none; }
}

@media (min-width: 76rem) {
  .credit {
    /* Role gets the width it needs: a wrapped role cell reads like a mistake,
       whereas a long title wrapping reads as a long title. */
    /* 12rem is measured, not chosen: the longest production company in the
       data — "Echo Lake Entertainment" — needs 189px at this size, and the
       title column has ~580px to spare at this breakpoint. */
    grid-template-columns: minmax(0, 1fr) 3.5rem 14rem 13rem 12rem;
    grid-template-areas: "title year role dp studio";
  }

  .credits__group--nostudio .credit {
    grid-template-columns: minmax(0, 1fr) 3.5rem 15rem 14rem;
    grid-template-areas: "title year role dp";
  }

  /* A row's height is set by whichever cell stacks. A cell holding a single
     value would otherwise hang off the top line of a much taller row, so it
     sits centred against the stack instead. */
  .credit__role--single,
  .credit__dp--single,
  .credit__year--single,
  .credit__studio--single { align-self: center; }

  .credits__group--split .credit {
    grid-template-columns: minmax(0, 1fr) 3.5rem 13.5rem 11rem 11rem 7.5rem;
    grid-template-areas: "title year role dp with studio";
    align-items: baseline;
    gap: var(--s-5);
  }
  .credit__year { padding-top: 0; text-align: right; }

  /* Role/Unit and Cinematographer/Unit DP read as centred blocks rather than
     ragged left columns. Title stays left as the anchor of the row, year stays
     right so the numbers line up. Only applies once the columns are genuinely
     side by side — the stacked mobile layout stays left-aligned. */
  .credit__role,
  .credit__dp,
  .credit__with,
  .credit__studio,
  .credit--head .credit__role,
  .credit--head .credit__dp,
  .credit--head .credit__with,
  .credit--head .credit__studio { text-align: center; }

  /* A centred cell shrinks to its content, so the divider inside it needs a
     floor — otherwise on a short pair it collapses to the width of the shorter
     line and reads as an underline rather than a rule between two facts. Each
     column gets a floor suited to what it holds. */
  .credit__role .credit__unit,
  .credit__dp .credit__unit   { display: inline-block; min-width: 6.5rem; }
  .credit__studio .credit__unit { display: inline-block; min-width: 6rem; }
  .credit__year .credit__unit   { display: inline-block; min-width: 2.6rem; }

  /* Where a heading carries a stacked pair — Role over Unit, Cinematographer
     over Unit DP — it is twice the height of the single labels beside it. Those
     centre against the rule between the two halves rather than sitting on the
     top line. A no-op in the sections whose headings are all single. */
  .credit--head .credit__title,
  .credit--head .credit__year,
  .credit--head .credit__studio { align-self: center; }

  .credit--head {
    display: grid;
    padding-block: 0 var(--s-3);
    border-bottom: 1px solid var(--rule);
  }
  .credit--head span {
    font-family: var(--mono);
    font-size: var(--t-micro);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-faint);
  }
  .credit--head .credit__role { color: var(--ink-faint); }
}

/* ---- project tiles ------------------------------------------------------
   The poster grid on /work. A tile with no artwork renders its title as type
   rather than a broken frame, so the grid is finished today and simply improves
   as licensed images arrive. */
/* Fixed column counts rather than auto-fill, because the interlace below has to
   know which column a tile is in. Two on a phone, three on a tablet, four on a
   desktop — the same progression the reference site uses. */
.tiles {
  display: grid;
  /* 24px both ways, which is the reference grid's gutter exactly. */
  gap: 24px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* The interlaced columns hang below the last row, so the space after the grid
     has to clear the offset as well as separate the sections. */
  margin-bottom: calc(var(--s-8) + var(--interlace));
  align-items: start;
}
@media (min-width: 48rem) { .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 66rem) { .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* The interlace: alternate columns sit 105px lower, which is the reference
   site's offset to the pixel.

   A transform and not a margin, which is the whole point. A margin is laid out,
   so it made the grid row 105px taller than the posters standing in it and left
   133px of air between one poster and the next; theirs sit 24px apart. A
   transform paints the column lower without the grid ever knowing, so the rows
   stay tight and the columns still stagger. It is also what they do.

   Fixed pixels rather than a percentage of the column: a percentage in translateY
   resolves against the element's own height, and these posters are every height
   from 383 to 484. The full 105px at every breakpoint, stepped down nowhere:
   the reference rule is a single global `.film-grid-col:nth-child(even)` at
   105px (measured in their own stylesheet), so this is one global even rule
   at 105px too. In CSS rather than script so the grid is interlaced
   with JavaScript off; past entry the scroll clock takes the property over,
   the way it does on desktop. */
.tiles { --interlace: 105px; }

.tile { margin: 0; }
/* Whole columns hang 105px lower — never scattered tiles. A transform paints
   the column lower without the grid ever knowing, so the rows stay tight, but
   that only holds while every tile in the column shares the offset: staggering
   alternate tiles diagonally (as the reference's bare even rule does at three
   columns — measured at -81px overlap on their own tablet grid) stacks one
   poster onto the next. So: evens on two columns (the whole right column) and
   on four (columns two and four, exactly like the reference), the middle
   column alone on three. */
.tile:nth-child(2n) { transform: translateY(var(--interlace)); }
@media (min-width: 48rem) {
  .tile:nth-child(2n) { transform: none; }
  .tile:nth-child(3n + 2) { transform: translateY(var(--interlace)); }
}
@media (min-width: 66rem) {
  .tile:nth-child(3n + 2) { transform: none; }
  .tile:nth-child(4n + 2),
  .tile:nth-child(4n)     { transform: translateY(var(--interlace)); }
}

/* A 4-column grid with 54 posters puts two tiles in the last row. That short
   row starts flush left — columns one and two — which is where the trailers
   belong: Invisible Boy opens the last row and Third Person follows it. The
   tail class records that the trailing row is partial at this breakpoint, and
   these rules pin the two trailers to the left. Named for the column count it
   applies to, so a future tile count rebuilds the grid without touching
   the CSS. */
@media (min-width: 66rem) {
  .tiles--tail4-2 .tile:nth-last-child(2) { grid-column: 1; }
  .tiles--tail4-2 .tile:nth-last-child(1) { grid-column: 2; }
}

.tile__frame {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: linear-gradient(150deg, var(--bg-raised), var(--bg-sunken));
  /* Frames with an imdb page are anchors: never link-coloured or underlined,
     so a typographic tile reads exactly like its unlinked neighbours. */
  color: inherit;
  text-decoration: none;
}
/* Posters keep their own proportions rather than being cropped to a common
   2:3. The set actually holds seventeen different ratios — a Netflix 4:5, an
   old 0.655 one-sheet — and letting them differ is what gives the grid its
   ragged lower edge instead of the tidy rows a forced crop produces. Only a
   tile with no artwork needs a shape imposed on it. */
.tile--empty .tile__frame { aspect-ratio: 2 / 3; }
.tile--still .tile__frame { aspect-ratio: 16 / 9; }

.tile__frame img {
  display: block;
  width: 100%;
  height: auto;
  filter: saturate(0.94);
}
.tile--still .tile__frame img,
.tile--empty .tile__frame img { height: 100%; object-fit: cover; }

.tile__placeholder {
  padding: var(--s-4);
  font-family: var(--mono);
  font-size: var(--t-small);
  line-height: 1.4;
  letter-spacing: 0.04em;
  text-align: center;
  text-wrap: balance;
  color: var(--ink-faint);
}


/* ---- optional imagery ---------------------------------------------------
   Nothing here renders unless files exist in assets/images/. The site is
   designed to be complete without them. */

/* ---- footer ------------------------------------------------------------- */
.footer {
  padding-block: var(--s-7);
  border-top: 1px solid var(--instrument);
  background: var(--bg-sunken);
}
.footer__inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--s-4);
  align-items: baseline;
}
.footer p {
  margin: 0;
  max-width: none;
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}
/* The legal string closes the page on one quiet centred line with room to
   breathe above it. */
.footer__legal {
  width: 100%;
  margin-top: var(--s-6) !important;
  text-align: center;
  text-wrap: balance;
}

/* The home page opening is the only full-screen hero, so it is allowed to be
   the largest thing on the site. Inner pages stay a step down. */
.page--home .hero__title {
  font-size: clamp(2.5rem, 1rem + 6vw, 6.5rem);
  max-width: 16ch;
}
.page--home .hero__lede { font-size: clamp(1.125rem, 1rem + 0.7vw, 1.5rem); }

/* ---- marquee -------------------------------------------------------------
   A short proof strip directly under the hero. Lead with the work, not with a
   manifesto — a producer scanning this page should hit a recognisable title
   within one screen. */
.marquee {
  padding-block: var(--s-5);
  background: var(--bg-sunken);
}
.marquee__label {
  margin: 0 0 var(--s-3);
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.marquee__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
}
.marquee__item { display: flex; flex-direction: column; gap: 0.15em; }
.marquee__title { font-size: 0.9375rem; letter-spacing: -0.01em; }
.marquee__role {
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.06em;
  color: var(--accent);
}

/* ---- pull quote ----------------------------------------------------------- */
.pull {
  margin: var(--s-7) 0 0;
  padding-left: var(--s-5);
  border-left: 2px solid var(--accent);
  max-width: var(--measure);
}
.pull__quote {
  margin: 0;
  font-size: var(--t-lede);
  line-height: 1.5;
  letter-spacing: -0.012em;
  text-wrap: pretty;
}
.pull__quote::before { content: "\201C"; }
.pull__quote::after  { content: "\201D"; }
.pull__by {
  margin-top: var(--s-3);
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---- case note -----------------------------------------------------------
   The one piece of long-form on the site. Sits on the sunken ground so it reads
   as a different kind of object from the service blocks around it. */
.case { background: var(--bg-sunken); }
.case__inner { max-width: 46rem; margin-inline: auto; }
/* Room to breathe on desktop: the strip goes full measure so the three
   frames land large instead of thumbnail-small. */
@media (min-width: 66rem) {
  .case__inner { max-width: none; }
}
.case__label {
  margin: 0 0 var(--s-4);
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.case__title { font-size: var(--t-h2); }
.case__standfirst {
  margin: var(--s-3) 0 var(--s-6);
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--ink-faint);
  max-width: none;
}
.case__text { color: var(--ink-muted); max-width: none; }
/* The standfirst is itself a <p>, so it wins :first-of-type. Target the opening
   body paragraph both ways: after a standfirst, or first when there isn't one. */
.case__standfirst + .case__text,
.case__text:first-of-type { color: var(--ink); font-size: var(--t-lede); }
.case__links {
  margin-top: var(--s-6);
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

/* ---- footer links --------------------------------------------------------- */
.footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  width: 100%;
  margin-bottom: var(--s-5) !important;
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--rule-soft);
}
.footer__nav .footer__link { font-size: var(--t-small); letter-spacing: 0; }
.footer__link { color: var(--ink-muted); text-decoration-color: var(--rule); }
.footer__link:hover { color: var(--accent); }

/* ---- print --------------------------------------------------------------
   A producer printing this to a PDF should get something legible, not a
   black rectangle that empties a toner cartridge. */
/* Top bar stays visible while scrolling. */
.nav { position: sticky; top: 0; z-index: 50; }

@media print {
  :root { --bg: #fff; --ink: #000; --ink-muted: #333; --ink-faint: #666;
          --rule: #ccc; --rule-soft: #e5e5e5; --accent: #1A2D4F; }
  .nav, .skip, .hero__actions { display: none; }
  .section > .wrap, .hero__inner {
    background: none; box-shadow: none;
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
  body { font-size: 11pt; }
  .section { padding-block: 1.5rem; break-inside: avoid; }
  a { text-decoration: none; }
}

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