/* ============================================================
   Slate — public page
   Dark, cinematic, single-column. Tokens first, then components.
   ============================================================ */

:root {
  --accent: #d9a441;
  --base-hue: 150;

  --bg: #08090b;
  --ink: #edeae4;
  --ink-dim: #a5a29b;
  --ink-faint: #6f6d68;

  --panel: rgba(255, 255, 255, 0.038);
  --panel-hi: rgba(255, 255, 255, 0.065);
  --line: rgba(255, 255, 255, 0.10);
  --line-hi: rgba(255, 255, 255, 0.22);

  --col: 620px;
  --r: 14px;
  --topbar-h: 52px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  --font: 'Manrope', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --serif: 'Instrument Serif', ui-serif, Georgia, serif;
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

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

/* ---------- atmosphere ----------------------------------------------------

   The "aurora": two oversized, blurred radial-gradient layers — a warm-start
   one (A) and a shifted one (B) — with GSAP ScrollTrigger scrubbing B's
   OPACITY (and a light drift via transform) as the visitor scrolls. Same
   crossfade mechanism as the plain two-layer version this replaced; the
   difference is each layer now carries a real filter: blur(), matching the
   soft blob look of the old LinkStack theme this is ported from.

   That blur is applied ONCE, at paint time, and never animated — opacity and
   transform are compositor-only, so the crossfade itself costs nothing extra.
   Measured attempt: animating filter: blur()+hue-rotate() directly via GSAP
   scrub (i.e. recomputing the blur every frame instead of pre-baking it) came
   in at ~4x this version's frame cost under 4x CPU throttling — filter
   changes force a real repaint every frame, no exceptions. Do not animate
   `filter` on this layer; animate opacity/transform on top of a static blur
   instead. Re-run `node scripts/dev/perf.mjs` after touching this and compare
   against the numbers in that script's header comment before committing.    */

.veil { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }

.veil span {
  position: absolute;
  top: -50%; left: -50%;
  width: 200%; height: 200%;
  display: block;
  filter: blur(64px);
  transform: translateZ(0);
}

.veil__auroraA {
  background: radial-gradient(circle at 50% 50%,
    hsl(calc(var(--base-hue) * 1deg) 58% 32% / 0.55) 0%,
    hsl(calc((var(--base-hue) + 35) * 1deg) 62% 34% / 0.32) 38%,
    hsl(calc((var(--base-hue) + 265) * 1deg) 46% 22% / 0.34) 62%,
    transparent 72%);
}

.veil__auroraB {
  opacity: var(--scroll, 0);
  will-change: opacity, transform;
  background: radial-gradient(circle at 50% 50%,
    hsl(calc((var(--base-hue) + 200) * 1deg) 60% 34% / 0.55) 0%,
    hsl(calc((var(--base-hue) + 240) * 1deg) 55% 30% / 0.32) 38%,
    hsl(calc((var(--base-hue) + 95) * 1deg) 45% 22% / 0.34) 62%,
    transparent 72%);
}

.veil::after {
  content: '';
  position: absolute;
  inset: 0;
  /* Was 120% 80% at 50% 0%, fading solid by 68% — that put the aurora's whole
     visible band in the top ~55% of the viewport, exactly where the sticky
     topbar + section header sit once you're scrolled past the hero, so it
     read as "always dark" for the entire card list. Taller ellipse + later
     stops keep a faint scroll-linked tint reaching further down the actual
     viewport without changing card/text readability (still fully opaque
     var(--bg) by the bottom edge). */
  background: radial-gradient(160% 150% at 50% 0%, transparent 22%, rgba(8, 9, 11, 0.72) 64%, var(--bg) 100%);
}

.grain { display: none; }

body[data-grain] .grain {
  display: block;
  position: fixed;
  inset: -12%;
  z-index: 1;
  pointer-events: none;
  opacity: 0.035;
  transform: translateZ(0);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain 7s steps(6) infinite;
}

@keyframes grain {
  0%,100% { transform: translate3d(0, 0, 0); }
  20% { transform: translate3d(-2%, 1.5%, 0); }
  40% { transform: translate3d(1.5%, -2%, 0); }
  60% { transform: translate3d(-1.5%, -1.5%, 0); }
  80% { transform: translate3d(2%, 1%, 0); }
}

/* ---------- shell --------------------------------------------------------- */

.wrap {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--col);
  margin: 0 auto;
  padding: 0 20px 72px;
  padding-bottom: max(72px, env(safe-area-inset-bottom));
}

/* The centre-card highlight (.card--active) and the snap both need a card to
   reach the middle of the screen, and the last card cannot unless the page
   carries on for about half a screen below it. The footer and its margins
   already supply roughly 130px of that. Fine pointer only, like the highlight
   itself: on touch this would be empty space for nothing. */
@media (hover: hover) and (pointer: fine) {
  .wrap { padding-bottom: max(72px, calc(50vh - 130px)); }
}

/* ---------- sticky topbar ------------------------------------------------- */

.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px max(20px, calc(50vw - var(--col) / 2 + 20px));
  background: rgba(8, 9, 11, 0.46);
  backdrop-filter: blur(18px) saturate(1.3);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  border-bottom: 1px solid transparent;
  transform: translateY(-100%);
  opacity: 0;
  transition: transform 0.32s var(--ease), opacity 0.32s var(--ease), border-color 0.32s;
}

/* <picture> only offers smaller WebP copies of an <img>; it takes no part in layout */
picture { display: contents; }

/* a phone has little room beside the name; every gap given back goes to the tagline */
@media (max-width: 600px) {
  .topbar { gap: 10px; padding-left: 14px; padding-right: 14px; }
  .topbar__name { gap: 7px; }
  .topbar__cta { padding: 6px 9px; letter-spacing: 0.02em; }
}

.topbar.is-on {
  transform: none;
  opacity: 1;
  border-bottom-color: var(--line);
}

.topbar__name {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-weight: 800;
  font-size: 0.95rem;
  letter-spacing: -0.02em;
}

.topbar__avatar {
  flex: none;
  width: 28px; height: 28px;
  border-radius: 50%;
  object-fit: cover;
}

/* The tagline rides in the bar so a visitor who lands mid-page on a shared
   link still gets the one-line intro. It clips with an ellipsis; a native
   <details> drops the full line below the bar. site.js blocks the toggle
   when nothing is clipped, and marks .is-clipped for the pointer cursor.
   Before clipping, site.js shrinks the line to fit, from this size down to
   --tag-min, so on a phone a short line shows whole rather than cut. */
.topbar__tag { flex: 1 1 0; min-width: 0; --tag-min: 11px; }
.topbar__tag > summary {
  list-style: none;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--serif);
  font-style: var(--hero-font-style, italic);
  font-size: 0.86rem;
  color: var(--ink-dim);
}
.topbar__tag > summary::-webkit-details-marker { display: none; }
/* too long for one line even at --tag-min: two lines, still inside the bar's height */
.topbar__tag.is-two > summary {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  white-space: normal;
  line-height: 1.15;
}
.topbar__tag.is-clipped > summary { cursor: pointer; }

.topbar__tagfull {
  position: absolute;
  top: calc(100% + 8px);
  left: max(20px, calc(50vw - var(--col) / 2 + 20px));
  right: max(20px, calc(50vw - var(--col) / 2 + 20px));
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #14161a;
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.85);
  font-family: var(--serif);
  font-style: var(--hero-font-style, italic);
  font-size: 1rem;
  line-height: 1.4;
  color: var(--ink);
}
/* the bar slides off-screen by its own height; the panel hangs below it */
.topbar:not(.is-on) .topbar__tagfull { display: none; }

.topbar__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  padding: 6px 12px;
  border-radius: 999px;
  transition: background 0.2s, border-color 0.2s;
}

.topbar__cta:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }

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

.hero {
  text-align: center;
  padding: clamp(64px, 15vh, 132px) 0 clamp(32px, 7vh, 56px);
}

.hero__ring {
  width: 168px; height: 168px;
  margin: 0 auto 26px;
  border-radius: 50%;
  padding: 3px;
  background: linear-gradient(150deg, color-mix(in srgb, var(--accent) 70%, transparent), rgba(255,255,255,0.10) 55%, transparent);
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.06),
    0 0 70px -8px color-mix(in srgb, var(--accent) 65%, transparent),
    0 26px 64px -20px color-mix(in srgb, var(--accent) 60%, transparent);
  display: grid;
  place-items: center;
}

.hero__avatar {
  width: 100%; height: 100%;
  border-radius: 50%;
  object-fit: cover;
  background: #14161a;
}

/* Liquid orb: the ring and the image morph through the same border-radius
   keyframes in lockstep (same duration, no delay, so they stay in sync
   without needing JS to coordinate them). */
body[data-motion] .hero__ring,
body[data-motion] .hero__avatar {
  animation: liquid-morph 12s ease-in-out infinite;
}

/* The hover speed-up (12s -> 4s) is deliberately NOT a CSS :hover rule —
   see the site.js block that sets it via the Web Animations API for why:
   changing animation-duration on a running animation jump-cuts to a
   different point in the keyframe cycle instead of continuing from the
   current shape, because the browser re-divides the same elapsed time by
   the new duration. JS can compensate (reposition currentTime to preserve
   the fractional progress); a static CSS rule can't. Fine-pointer only —
   see finePointer in site.js. */

@keyframes liquid-morph {
  0%   { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }
  25%  { border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%; }
  50%  { border-radius: 50% 50% 20% 80% / 25% 80% 20% 75%; }
  75%  { border-radius: 70% 30% 30% 70% / 60% 40% 60% 40%; }
  100% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }
}

.hero__initials {
  width: 100%; height: 100%;
  border-radius: 50%;
  display: grid; place-items: center;
  background: #14161a;
  font-weight: 800; font-size: 2.6rem; letter-spacing: -0.03em;
  color: var(--ink-dim);
}

.hero__name {
  margin: 0;
  font-size: clamp(2.1rem, 9vw, 3.1rem);
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1.02;
}

.hero__tagline {
  margin: 14px auto 0;
  max-width: 30ch;
  font-family: var(--serif);
  /* --hero-font-style is injected per-request in public.js: 'italic' for
     presets with a real italic cut, 'normal' for sans presets and any
     uploaded custom font (unknown italic support -> never fake-oblique). */
  font-style: var(--hero-font-style, italic);
  font-size: clamp(1.05rem, 3.4vw, 1.28rem);
  line-height: 1.4;
  color: var(--ink-dim);
}

.hero__bio {
  margin: 12px auto 0;
  max-width: 46ch;
  font-family: var(--serif);
  font-style: normal;
  font-size: 0.93rem;
  color: var(--ink-faint);
}

.socials {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 9px;
  margin-top: 26px;
}

.social {
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink-dim);
  transition: transform 0.22s var(--ease), color 0.22s, border-color 0.22s, background 0.22s;
}

.social svg { fill: currentColor; }

.social:hover {
  transform: translateY(-3px);
  color: var(--ink);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 24px;
  padding: 11px 22px;
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.92rem;
  color: #0b0b0c;
  background: var(--accent);
  box-shadow: 0 12px 30px -12px color-mix(in srgb, var(--accent) 80%, transparent);
  transition: transform 0.22s var(--ease), box-shadow 0.22s, filter 0.22s;
  cursor: pointer;
}

.cta:hover { transform: translateY(-2px); filter: brightness(1.06); }

/* ---------- résumé dropdown ------------------------------------------------ */
/* <details>/<summary> — native disclosure, no JS required to open/close.
   site.js only adds closing on outside-click and on picking a link, since
   both open in a new tab and wouldn't otherwise close the panel. */

.cta-menu { position: relative; display: inline-block; }
.cta-menu > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; }
.cta-menu > summary::-webkit-details-marker { display: none; }

.cta-menu__caret {
  flex: none;
  transition: transform 0.22s var(--ease);
}

.cta-menu[open] .cta-menu__caret { transform: rotate(180deg); }

.cta-menu__panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 180px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #14161a;
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.85);
}

.cta-menu__panel a {
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 0.86rem;
  font-weight: 500;
  color: var(--ink-dim);
  transition: background 0.15s, color 0.15s;
}

.cta-menu__panel a:hover { background: var(--panel-hi); color: var(--ink); }

.topbar .cta-menu .cta-menu__panel { left: auto; right: 0; transform: none; }

/* ---------- jump chips ---------------------------------------------------- */

/* Arrows are real flex siblings of the scroller, not an overlay on top of it —
   they need their own box so they read as separate controls instead of a
   mark half-hidden under the chips. Wheel scroll is redirected to horizontal
   while hovering (site.js) since a vertical wheel over a horizontal-only
   scroller otherwise just scrolls the page, not the chips. */
.jump-wrap {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0 0 34px;
}

.jump {
  flex: 1;
  min-width: 0;
  display: flex;
  gap: 7px;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 2px 2px 4px;
  scroll-snap-type: x proximity;
  mask-image: linear-gradient(90deg, transparent, #000 12px, #000 calc(100% - 12px), transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12px, #000 calc(100% - 12px), transparent);
}

.jump::-webkit-scrollbar { display: none; }

.jump__chip {
  flex: none;
  scroll-snap-align: start;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 7px 14px;
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}

.jump__chip:hover, .jump__chip.is-on {
  color: var(--ink);
  border-color: var(--line-hi);
  background: var(--panel);
}

/* JS toggles [hidden] based on actual scroll position/overflow, so an arrow
   never shows when there's nothing more to reveal in that direction. */
.jump__arrow {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink-dim);
  font-size: 1rem;
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}

.jump__arrow:hover { color: var(--ink); border-color: var(--line-hi); background: var(--panel-hi); }
.jump__arrow[hidden] { display: none; }

@media (max-width: 500px) {
  .jump__arrow { display: none; }
}

/* ---------- sections ------------------------------------------------------ */

.sec { margin-bottom: 46px; scroll-margin-top: 78px; }

/* Sticks below the topbar while its cards scroll past. Stays within the same
   column as everything else on the page (cards, hero, blurb) — full-bleed
   (spanning the whole viewport) made it read as a bar cutting across the
   page, since unlike .topbar this isn't nav chrome, it's page content.
   Rounded like a panel/card rather than a hard-edged strip, for the same
   reason. top: var(--topbar-h) keeps it flush under the topbar. */
.sec__head {
  position: sticky;
  top: var(--topbar-h);
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 -14px 8px;
  padding: 10px 14px;
  border-radius: var(--r);
  background: rgba(8, 9, 11, 0.46);
  backdrop-filter: blur(18px) saturate(1.3);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.sec__label { color: var(--accent); white-space: nowrap; }
.sec__rule { flex: 1; height: 1px; background: linear-gradient(90deg, var(--line), transparent); }
.sec__count {
  font-variant-numeric: tabular-nums;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 3px 9px;
  border-radius: 999px;
  color: color-mix(in srgb, var(--accent) 90%, white);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}

.sec__blurb {
  margin: -6px 0 16px;
  font-size: 0.88rem;
  color: var(--ink-faint);
}

.cards { display: flex; flex-direction: column; gap: 16px; }

/* ---------- cards --------------------------------------------------------- */

.card {
  position: relative;
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--panel);
  overflow: hidden;
  /* One transform transition, tuned to double as the entrance pop (translate
     + scale from body[data-motion] .card:not(.is-in)) and the hover/focus
     lift — a mild overshoot reads as a "pop" on entry and a spring on hover. */
  transition:
    transform 0.34s cubic-bezier(0.34, 1.5, 0.54, 1),
    opacity 0.36s var(--ease),
    border-color 0.28s, background 0.28s, box-shadow 0.28s;
}

/* The single card nearest the viewport centre (site.js) grows + lifts —
   fine-pointer only (site.js only ever adds .card--active there in the first
   place; this guard is belt-and-suspenders in case that ever changes). Scale
   is a percentage, so the same factor adds far more pixels to a tall poster
   than to a row — hence the poster-specific smaller step. The 16px .cards gap
   plus these caps keeps a focused card clear of its neighbours; the ring and
   shadow carry most of the "this one" signal so the scale can stay modest. */
@media (hover: hover) and (pointer: fine) {
  .card--active {
    transform: scale(1.05);
    z-index: 2;
    border-color: var(--line-hi);
    background: var(--panel-hi);
    box-shadow:
      0 24px 50px -20px rgba(0, 0, 0, 0.92),
      0 0 0 1px color-mix(in srgb, var(--accent) 50%, transparent);
  }
  .card--poster.card--active { transform: scale(1.03); }

  .card:hover {
    transform: translateY(-2px) scale(1.03);
    border-color: var(--line-hi);
    background: var(--panel-hi);
    box-shadow: 0 18px 40px -22px rgba(0, 0, 0, 0.9);
  }
  /* focused + hovered is the largest state */
  .card--active:hover { transform: scale(1.065); }
  .card--poster.card--active:hover { transform: scale(1.045); }
  .card:hover .shot__img { transform: scale(1.035); }
  .card:hover .card__go { opacity: 1; transform: none; }
}

.card:active { transform: scale(0.988); transition: transform 0.09s ease; }

.card__shot {
  position: relative;
  overflow: hidden;
  background: #101114;
}

.shot__img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.5s var(--ease);
}

.shot__fallback {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  color: var(--ink-faint);
  background: linear-gradient(135deg, #14161a, #0d0e11);
}

.shot__fallback svg { fill: currentColor; opacity: 0.55; }

.card__body { padding: 14px 16px 15px; }

.card__role {
  margin: 0 0 5px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--accent) 82%, white);
}

.card__title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -0.017em;
  line-height: 1.28;
}

.card__year {
  font-weight: 500;
  color: var(--ink-faint);
  letter-spacing: 0;
}

.card__meta {
  margin: 5px 0 0;
  font-size: 0.845rem;
  line-height: 1.42;
  color: var(--ink-dim);
}

.card__badge { margin: 9px 0 0; }

.card__badge span {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 4px 9px;
  border-radius: 999px;
  color: color-mix(in srgb, var(--accent) 88%, white);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
}

.card__go {
  position: absolute;
  top: 12px; right: 12px;
  width: 27px; height: 27px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: rgba(10, 11, 13, 0.7);
  backdrop-filter: blur(6px);
  color: var(--ink-dim);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.24s, transform 0.24s var(--ease);
}

.card__go svg { fill: currentColor; }

/* -- poster variant -- */
.card--poster .card__shot { aspect-ratio: 16 / 9; }
.card--poster .shot__img { position: absolute; inset: 0; }
.card--poster .card__body { padding: 15px 18px 17px; }
.card--poster .card__title { font-size: 1.16rem; }

/* -- row variant --
   The shot fills its grid track; giving it an aspect-ratio here would let it
   compute a width wider than the track and slide under the text. */
.card--row { display: grid; grid-template-columns: 132px 1fr; align-items: stretch; min-height: 84px; }
.card--row > * { min-width: 0; }
.card--row .card__shot { width: 100%; height: 100%; }
.card--row .shot__img { position: absolute; inset: 0; }
.card--row .card__body { padding: 12px 14px 12px 15px; align-self: center; }
.card--row .card__title { font-size: 0.97rem; }
.card--row .card__meta { font-size: 0.8rem; }
.card--row .card__go { top: 9px; right: 9px; }

/* -- text variant -- */
.card--text .card__body { padding: 16px 18px; text-align: center; }

/* -- laurels: festival laurel images, a row under the card's text -- */
.card__laurels { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin: 12px 0 0; }
.card__laurels img { display: block; height: 46px; width: auto; max-width: 140px; object-fit: contain; opacity: 0.94; }
.card--row .card__laurels { gap: 6px 10px; margin-top: 8px; }
.card--row .card__laurels img { height: 30px; max-width: 96px; }
.card--text .card__laurels { justify-content: center; }

/* -- press variant: a quotation from a review or an article -- */
.card--press .card__body { padding: 20px 22px 19px; }
.card__logo { display: block; height: 22px; width: auto; max-width: 160px; object-fit: contain; margin: 0 0 12px; opacity: 0.86; }
.card__quote {
  margin: 0;
  font-family: var(--serif);
  font-style: var(--hero-font-style, italic);
  font-size: 1.28rem;
  line-height: 1.38;
  color: var(--ink);
}
.card__quote::before { content: '\201C'; color: var(--accent); margin-right: 0.04em; }
.card__quote::after { content: '\201D'; color: var(--accent); margin-left: 0.04em; }
.card__source { margin: 11px 0 0; font-size: 0.8rem; letter-spacing: 0.02em; color: var(--ink-dim); }
.card__source::before { content: '\2014\00a0'; color: var(--ink-faint); }

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

.empty {
  margin: 56px auto 0;
  max-width: 30ch;
  text-align: center;
  color: var(--ink-faint);
  font-family: var(--serif);
  font-size: 1.2rem;
  line-height: 1.4;
}

.foot {
  margin-top: 54px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
  text-align: center;
  font-size: 0.82rem;
  color: var(--ink-faint);
}

.foot p { margin: 0 0 6px; }
.foot__meta { font-size: 0.76rem; letter-spacing: 0.03em; }

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

/* The reveal rule stops applying once .revealed lands, which returns the
   element to its own transitions — .card's hover transform in particular.
   Never set an inline transitionDelay for the stagger: it would delay every
   transition on the element, hover included, for the life of the page. */
body[data-motion] .reveal:not(.revealed) {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 0.6s var(--ease) var(--reveal-delay, 0ms),
    transform 0.6s var(--ease) var(--reveal-delay, 0ms);
}

body[data-motion] .reveal.is-in:not(.revealed) { opacity: 1; transform: none; }

/* Cards animate both ways: site.js adds .is-in on entry and removes it once
   the card has dropped back past the bottom edge, so scrolling up pushes it
   down and out. The transform stays in CSS (no GSAP inline styles) so that
   .card--active / :hover can scale the same element unobstructed. */
body[data-motion] .card:not(.is-in) {
  opacity: 0;
  transform: translateY(52px) scale(0.9);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
  body[data-motion] .reveal:not(.revealed) { opacity: 1; transform: none; }

  /* Tiered reduced-motion: the page still loses the travel (52px slide),
     the aurora parallax and the proximity snap — all of which move content
     past the viewer — but a card that enters still fades its opacity up in
     place, and the centre card still scales. Neither shifts anything
     relative to the viewport, so neither is a motion-sickness risk. cardSpy
     keeps running in site.js; these two rules re-allow just those two
     transitions past the blanket duration:0 above. */
  body[data-motion] .card {
    transition:
      opacity 0.45s ease, transform 0.28s ease,
      border-color 0.28s ease, background 0.28s ease, box-shadow 0.28s ease !important;
  }
  body[data-motion] .card:not(.is-in) { opacity: 0; transform: none; }

  /* Deliberate exception, not an oversight: the owner wants the avatar morph
     to keep playing even for reduced-motion visitors. The card opacity fade
     and centre-card scale (above) also run — they move nothing past the
     viewer. Everything that DOES (the 52px slide, reveal-on-scroll travel,
     the GSAP aurora drift, Lenis smooth scroll + proximity snap, which each
     re-check the media query in site.js) still respects the preference. */
  body[data-motion] .hero__ring,
  body[data-motion] .hero__avatar {
    animation-duration: 12s !important;
  }
}

/* ---------- small screens ------------------------------------------------- */

@media (max-width: 420px) {
  .card--row { grid-template-columns: 108px 1fr; }
  .card--row .card__body { padding: 10px 12px; }
  .hero__ring { width: 148px; height: 148px; }
}

/* ---------- proximity scroll-snap ------------------------------------- */
/* "proximity" not "mandatory" — it settles near a card without hard-locking
   every pass. CSS snap and Lenis fight each other (Lenis animates scrollTop,
   the browser re-snaps mid-animation), so CSS snap is only used where Lenis
   is NOT driving: touch scrolling (native, all widths) and desktop with the
   smooth setting off (html:not(.lenis)). Fine-pointer + Lenis gets the JS
   proximity snap in site.js instead — coarse pointers (touch) never get it,
   full stop, regardless of Lenis: on a fast flick there's no stable "centre"
   to settle on, and .card--active (up with .card, fine-pointer-gated) has
   the same problem for the same reason. */
@media (max-width: 640px) {
  html { scroll-snap-type: y proximity; }
  .card { scroll-snap-align: center; }
}

@media (min-width: 641px) {
  html:not(.lenis) { scroll-snap-type: y proximity; }
  html:not(.lenis) .card { scroll-snap-align: center; }
}


/* ---------- inertial scrolling (only when the setting is on) -------------- */
/* Upstream lenis.css, inlined. Lenis adds these classes to <html> itself. */

html.lenis, html.lenis body { height: auto; }

.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }

.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch],
.lenis [data-lenis-prevent-vertical],
.lenis [data-lenis-prevent-horizontal] { overscroll-behavior: contain; }

.lenis.lenis-smooth iframe { pointer-events: none; }

.lenis.lenis-autoToggle {
  transition-property: overflow;
  transition-duration: 1ms;
  transition-behavior: allow-discrete;
}

/* Lenis drives the scroll itself; the native smooth behaviour would fight it. */
html.lenis { scroll-behavior: auto !important; }

@media print {
  .veil, .grain, .topbar, .jump { display: none !important; }
  body { background: #fff; color: #000; }
}

/* ---------- visitor alert ------------------------------------------------- */
/* Pinned to the bottom so it never sits on top of the topbar or a sticky
   section header. Text is the owner's, set per share link; it stays until
   dismissed. */
.notice {
  position: fixed;
  left: 50%;
  bottom: max(16px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 40;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: min(560px, calc(100vw - 32px));
  padding: 13px 14px 13px 16px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 14px;
  background: #14161a;
  box-shadow: 0 20px 44px -18px rgba(0, 0, 0, 0.9);
}
.notice p { flex: 1; margin: 0; font-size: 0.92rem; line-height: 1.45; }
.notice__x {
  flex: none;
  width: 28px; height: 28px;
  border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  color: var(--ink-dim);
  font: inherit; font-size: 1.1rem; line-height: 1;
  cursor: pointer;
}
.notice__x:hover { background: rgba(255, 255, 255, 0.16); color: var(--ink); }
.notice[hidden] { display: none; }

.notice a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }

/* Welcome mode: the same alert, opened as a centred card (see site.js). The
   Continue button and avatar only exist in this mode; the x only outside it. */
.notice__go, .notice__avatar, .notice-veil { display: none; }

.notice.is-welcome {
  top: 50%;
  bottom: auto;
  z-index: 60;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: min(440px, calc(100vw - 40px));
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  padding: 28px 24px 22px;
  text-align: center;
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.95), 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent);
  transform: translate(-50%, -50%);
  animation: notice-pop 0.42s cubic-bezier(0.34, 1.5, 0.54, 1) both;
}
.notice.is-welcome p { flex: none; font-size: 1.06rem; line-height: 1.5; }
.notice.is-welcome .notice__x { display: none; }
.notice.is-welcome .notice__avatar {
  display: block;
  width: 52px; height: 52px;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 60%, transparent);
}
.notice.is-welcome .notice__go {
  display: inline-block;
  font: inherit; font-size: 0.95rem; font-weight: 700;
  padding: 10px 22px;
  border: 0; border-radius: 999px;
  background: var(--accent);
  color: #101114;
  cursor: pointer;
}
.notice.is-welcome .notice__go:hover { filter: brightness(1.07); }
.notice__secs { font-variant-numeric: tabular-nums; font-weight: 600; opacity: 0.7; }

.notice.is-welcome + script + .notice-veil {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 59;
  background: rgba(4, 5, 7, 0.74);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  animation: notice-fade 0.3s ease both;
}

.notice.is-settled { animation: notice-rise 0.36s cubic-bezier(0.34, 1.4, 0.54, 1) both; }

@keyframes notice-pop {
  from { opacity: 0; transform: translate(-50%, -46%) scale(0.9); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes notice-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes notice-rise {
  from { opacity: 0; transform: translate(-50%, 18px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

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

.hero__acts { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 0 12px; }
.cta--quiet {
  color: var(--ink);
  background: var(--panel-hi);
  border: 1px solid var(--line-hi);
  box-shadow: none;
}
.cta--quiet:hover { border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.topbar__mail {
  flex: none;
  display: grid; place-items: center;
  width: 32px; height: 32px;
  margin-left: -6px;
  border-radius: 50%;
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.topbar__mail:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }

/* A card that plays on the page keeps its play mark in view, so it reads as
   a video before it is hovered. */
.card--play .card__go { opacity: 1; transform: none; color: var(--accent); }

/* ---------- dialogs: player and contact ------------------------------------- */

dialog.player, dialog.contact {
  padding: 0;
  border: 0;
  color: var(--ink);
  background: transparent;
  max-width: none; max-height: none;
  overflow: visible;
}
dialog.player::backdrop, dialog.contact::backdrop {
  background: rgba(4, 5, 7, 0.84);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
dialog[open].player, dialog[open].contact { animation: dialog-in 0.22s var(--ease) both; }
@keyframes dialog-in { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: none; } }

.dialog__x {
  position: absolute; top: -44px; right: 0;
  width: 34px; height: 34px;
  border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: var(--ink);
  font: inherit; font-size: 1.3rem; line-height: 1;
  cursor: pointer;
}
.dialog__x:hover { background: rgba(255, 255, 255, 0.22); }

/* The frame is as wide as the screen allows, but never so wide that its
   height (width / --r) would not fit: a 2.39:1 film and a 16:9 one both sit
   whole on a phone held upright. */
.player__frame {
  width: min(94vw, 1100px, calc(78vh * var(--r, 1.778)));
  aspect-ratio: var(--r, 1.778);
  background: #000;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 40px 100px -30px #000;
}
.player__frame { position: relative; }
.player__frame iframe { display: block; width: 100%; height: 100%; border: 0; }

/* Shown over the frame until the player has loaded: the card's own picture,
   dimmed, with a spinner. It fades out on the iframe's load event. */
.player__wait {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 14px;
  background: #000 center / cover no-repeat;
  transition: opacity 0.35s var(--ease), visibility 0.35s;
}
.player__wait::before { content: ''; position: absolute; inset: 0; background: rgba(0, 0, 0, 0.55); }
.player__spin {
  position: relative;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.22);
  border-top-color: var(--accent);
  animation: player-spin 0.8s linear infinite;
}
.player__slow { position: relative; display: none; max-width: 30ch; padding: 0 16px; text-align: center; font-size: 0.86rem; color: var(--ink); }
.player__frame.is-slow .player__slow { display: block; }
.player__frame.is-ready .player__wait { opacity: 0; visibility: hidden; pointer-events: none; }
.player__frame.is-ready .player__spin { animation: none; }
@keyframes player-spin { to { transform: rotate(360deg); } }
.player__out {
  display: block;
  margin-top: 12px;
  text-align: center;
  font-size: 0.82rem;
  color: var(--ink-dim);
}
.player__out:hover { color: var(--accent); }

dialog.contact { width: min(440px, calc(100vw - 32px)); }
.contact__ask, .contact__done, .contact--page {
  padding: 26px 24px 24px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 16px;
  background: #14161a;
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.95);
}
dialog.contact .contact__ask { max-height: calc(100vh - 110px); overflow-y: auto; }
.contact h1, .contact h2 { margin: 0 0 6px; font-size: 1.3rem; letter-spacing: -0.03em; }
.contact__note { margin: 0 0 18px; color: var(--ink-dim); font-size: 0.92rem; line-height: 1.45; }
.contact__form { display: flex; flex-direction: column; gap: 12px; }
.contact__form label { display: flex; flex-direction: column; gap: 5px; font-size: 0.8rem; font-weight: 600; color: var(--ink-dim); }
.contact__form label em { font-style: normal; font-weight: 400; color: var(--ink-faint); }
.contact__form input, .contact__form textarea {
  font: inherit; font-size: 1rem; font-weight: 400;
  color: var(--ink);
  background: #0c0d10;
  border: 1px solid var(--line-hi);
  border-radius: 10px;
  padding: 10px 12px;
  width: 100%;
  resize: vertical;
}
.contact__form input:focus, .contact__form textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.contact__send { margin-top: 4px; align-self: flex-start; border: 0; font: inherit; font-weight: 700; }
.contact__send[disabled] { opacity: 0.6; cursor: default; }
.contact__error { margin: 0; padding: 9px 12px; border-radius: 10px; background: rgba(226, 104, 95, 0.14); color: #f0a49e; font-size: 0.88rem; }
.contact__error[hidden], .contact__ask[hidden], .contact__done[hidden] { display: none; }
.contact__done .cta, .contact--page .cta { margin-top: 6px; border: 0; font: inherit; font-weight: 700; }
/* The trap field: off-screen for people, present for programs. Not
   display: none, which the cruder ones check for. */
.contact__trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

body.solo { min-height: 100vh; display: grid; place-items: center; padding: 24px 16px; }
.contact--page { width: min(440px, 100%); }
.contact__back { margin: 16px 0 0; font-size: 0.85rem; }
.contact__back a { color: var(--ink-dim); text-decoration: underline; text-underline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  dialog[open].player, dialog[open].contact { animation: none; }
  /* a spinner turning in place moves nothing past the viewer, but slow it */
  .player__spin { animation-duration: 1.6s !important; }
}

/* ---------- showreel button ------------------------------------------------- */
/* Outlined, so it sits beside the filled résumé button without competing. */
.cta--reel {
  color: color-mix(in srgb, var(--accent) 88%, white);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: none;
}
.cta--reel:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); }

/* ---------- role filter ----------------------------------------------------- */
/* A dropdown leading the section chips' row, dressed like a chip, then a thin
   divider; it stays put while the chips scroll beside it. With too few
   sections for chips it stands centred alone (.jump-wrap--solo). site.js
   shows it and hides cards, sections and jump chips with [hidden]; each of
   those sets its own display, which would win over the browser's [hidden]
   rule (see the [hidden] gotcha in CLAUDE.md). */
.filter { flex: none; display: flex; align-items: center; }
.filter[hidden], .card[hidden], .sec[hidden], .jump__chip[hidden] { display: none; }
.filter__sep { width: 1px; height: 18px; margin: 0 6px 0 10px; background: var(--line-hi); }
.jump-wrap--solo { justify-content: center; }
.jump-wrap--solo .filter__sep { display: none; }
.filter__select {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236f6d68' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 11px center;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 7px 28px 7px 14px;
  /* as wide as the chosen role, not the longest one (where supported);
     elsewhere a long name ends in "…" */
  field-sizing: content;
  max-width: 170px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background-color 0.2s;
}
.filter__select:hover, .filter__select:focus-visible { color: var(--ink); border-color: var(--line-hi); background-color: var(--panel); outline: none; }
.filter__select option { color: #edeae4; background: #14161a; text-transform: none; letter-spacing: 0; }
/* a role chosen: the control says so, so nobody thinks cards have gone missing */
.filter.is-on .filter__select { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
@media (max-width: 600px) { .filter__select { max-width: 132px; } .filter__sep { margin: 0 4px 0 8px; } }

/* ---------- back to the top ------------------------------------------------- */
/* Shown once the page is a screen and a half down (site.js, .is-on); lifted
   above the share-link alert bar while that sits across a phone's bottom. */
.totop {
  position: fixed;
  right: max(18px, env(safe-area-inset-right));
  bottom: max(18px, env(safe-area-inset-bottom));
  z-index: 30;
  display: grid; place-items: center;
  width: 42px; height: 42px;
  border-radius: 50%;
  color: var(--accent);
  background: color-mix(in srgb, #14161a 82%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 12px 30px -14px rgba(0, 0, 0, 0.9);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), visibility 0s linear 0.25s, bottom 0.25s var(--ease);
}
.totop.is-on { opacity: 1; visibility: visible; transform: none; transition-delay: 0s; }
.totop:hover { background: color-mix(in srgb, var(--accent) 16%, #14161a); }
@media (max-width: 640px) {
  body:has(.notice:not([hidden]):not(.is-welcome)) .totop { bottom: 104px; }
}
@media (prefers-reduced-motion: reduce) { .totop { transform: none; } }

/* ---------- press kit page (/kit/<slug>) ------------------------------------ */
.kitpage { min-height: 100vh; }
.kit { width: min(820px, 100% - 40px); margin: 0 auto; padding: 28px 0 64px; }
.kit__back { display: flex; justify-content: space-between; margin: 0 0 30px; font-size: 0.85rem; color: var(--ink-faint); }
.kit__back a { color: var(--ink-dim); }
.kit__back a:hover { color: var(--ink); }
.kit__back span { letter-spacing: 0.14em; text-transform: uppercase; font-size: 0.7rem; font-weight: 700; }
.kit__head { display: grid; grid-template-columns: minmax(0, 300px) 1fr; gap: 30px; align-items: end; }
.kit__poster img { display: block; width: 100%; border-radius: 12px; box-shadow: 0 24px 50px -24px rgba(0, 0, 0, 0.9); }
.kit__title { margin: 0; font-size: clamp(1.8rem, 5vw, 2.6rem); letter-spacing: -0.03em; line-height: 1.1; }
.kit__logline {
  margin: 12px 0 0;
  font-family: var(--serif);
  font-style: var(--hero-font-style, italic);
  font-size: clamp(1.08rem, 2.6vw, 1.3rem);
  line-height: 1.4;
  color: var(--ink);
  max-width: 46ch;
}
.kit__laurels { display: flex; flex-wrap: wrap; gap: 10px 16px; margin: 18px 0 0; }
.kit__laurels img { height: 56px; width: auto; max-width: 160px; object-fit: contain; }
.kit__block { margin: 46px 0 0; }
.kit__h {
  margin: 0 0 14px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-faint);
}
.kit__h em { font-style: normal; font-weight: 500; letter-spacing: 0; text-transform: none; margin-left: 6px; }
.kit__trailer, .kit__frame {
  display: block; width: 100%; aspect-ratio: var(--r, 1.778);
  border: 0; border-radius: 12px; background: #000 center / cover no-repeat;
}
.kit__trailer { position: relative; cursor: pointer; }
.kit__play {
  position: absolute; inset: 0; margin: auto;
  width: 72px; height: 72px; border-radius: 50%;
  display: grid; place-items: center; padding-left: 4px;
  color: #0b0b0c; background: var(--accent);
  box-shadow: 0 14px 34px -10px rgba(0, 0, 0, 0.8);
  transition: transform 0.2s var(--ease);
}
.kit__trailer:hover .kit__play { transform: scale(1.06); }
.kit__note { margin: 12px 0 0; font-size: 0.86rem; color: var(--ink-dim); }
.kit__note .cta { margin-top: 4px; }
.kit__text p { margin: 0 0 1em; font-size: 1.02rem; line-height: 1.65; color: var(--ink); max-width: 64ch; }
.kit__credits { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px 24px; margin: 0; }
.kit__credits dt { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: color-mix(in srgb, var(--accent) 82%, white); }
.kit__credits dd { margin: 3px 0 0; font-size: 0.98rem; }
.kit__stills { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 10px; }
.kit__stills img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px; transition: filter 0.2s; }
.kit__stills a:hover img { filter: brightness(1.12); }
.kit__foot { display: flex; flex-wrap: wrap; gap: 0 12px; margin: 48px 0 0; padding-top: 10px; border-top: 1px solid var(--line); }
@media (max-width: 640px) {
  .kit__head { grid-template-columns: 1fr; gap: 20px; }
  .kit__poster { width: min(240px, 70%); }
  .kit__stills { grid-template-columns: 1fr 1fr; }
}
