/* ==========================================================================
   Old Oak Film
   Every value here comes from tokens.css. No arbitrary numbers.
   ========================================================================== */

/* --- Reset ---------------------------------------------------------------- */

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

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

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd { margin: 0; }

ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }

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

/* WordPress emits width/height attributes. Without height:auto the browser
   honours the attribute height while scaling the width — which distorts every
   image. Nothing on this site is ever stretched. */
img {
  height: auto;
}

input, button, textarea, select { font: inherit; color: inherit; }

/* --- Base ----------------------------------------------------------------- */

html {
  background: var(--ground);
  /* Sticky header must not obscure a focused element — WCAG 2.2 SC 2.4.11 */
  scroll-padding-top: calc(var(--space-7) + var(--space-3));
}

body {
  background: var(--ground);
  color: var(--paper);
  font-family: var(--font);
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100%;
  overflow-x: hidden;
}

h1, .t-display {
  font-size: var(--fs-display);
  font-weight: var(--fw-light);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  text-wrap: balance;
}

h2, h3, .t-heading {
  font-size: var(--fs-heading);
  font-weight: var(--fw-light);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
}

.t-meta {
  font-size: var(--fs-meta);
  font-weight: var(--fw-regular);
  line-height: var(--lh-meta);
  letter-spacing: var(--ls-meta);
  color: var(--paper-dim);
}

a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }

/* Space above a heading is double the space below it. This one rule does most
   of the work of signalling hierarchy without any size drama. */
.prose > * + * { margin-top: var(--space-2); }
.prose > * + :is(h2, h3) { margin-top: var(--space-6); }
.prose > :is(h2, h3) + * { margin-top: var(--space-4); }
.prose { max-width: var(--measure); }
.prose a { text-decoration: underline; }

/* --- Focus ---------------------------------------------------------------- */

:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: 3px;
}
:focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: 0;
  z-index: 200;
  padding: var(--space-2) var(--space-3);
  background: var(--paper);
  color: var(--ground);
  transform: translateY(-120%);
}
.skip-link:focus { transform: translateY(var(--space-1)); }

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

/* --- Layout --------------------------------------------------------------- */

/* Every page edge shares --gutter. Nothing opts out. */
.wrap {
  width: 100%;
  max-width: calc(var(--content) + (var(--gutter) * 2));
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--wide { max-width: calc(var(--wide) + (var(--gutter) * 2)); }
.wrap--full { max-width: none; }

.section { padding-block: var(--section-top) var(--section); }
.section + .section { padding-top: 0; }

/* --- Header --------------------------------------------------------------- */

.header {
  position: relative;
  z-index: 100;
  /* Equal space above the logo and below it, before content starts. */
  padding-block: var(--space-6);
}

@media (max-width: 767px) {
  .header { padding-block: var(--space-4); }
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

/* The open nav is a fixed overlay inside the header's stacking context, so the
   logo and the toggle need to sit above it — otherwise the X you press to
   close the menu is painted underneath it. */
.header__logo {
  display: block;
  flex: 0 0 auto;
  position: relative;
  z-index: 95;
}
.header__logo img,
.header__logo svg {
  width: auto;
  height: 112px;
  display: block;
}

/* The mark is a badge with the wordmark knocked out, so it takes the header's
   own colour — which is why it is painted as `currentColor` behind a mask
   rather than set as a background image. See the note in header.php for why it
   is a mask at all and not an inline <svg>. */
.header__logo { color: var(--paper); }

.header__logo-mark {
  display: block;
  height: 112px;
  /* The viewBox is square, so one number drives both axes. An empty element has
     no intrinsic size, so unlike the old inline SVG this cannot be width:auto. */
  aspect-ratio: 1;
  background-color: currentColor;
  -webkit-mask: url("../img/logo.svg") no-repeat center / contain;
  mask: url("../img/logo.svg") no-repeat center / contain;
}

@media (max-width: 767px) {
  .header__logo img,
  .header__logo svg { height: 84px; }
  .header__logo-mark { height: 84px; }
}

.header--overlay {
  position: absolute;
  inset-inline: 0;
  top: 0;
}

/* --- Navigation ----------------------------------------------------------- */

.nav__list {
  display: flex;
  gap: var(--space-5);
  align-items: center;
}

.nav a {
  display: inline-block;
  font-size: var(--fs-meta);
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  text-decoration: none;
  padding-block: var(--space-1);
}

.nav a:hover { text-decoration: underline; }
.nav [aria-current="page"] { text-decoration: underline; }

/* Hamburger — morphs to an X. Transform-only, so it stays on the compositor. */

.nav-toggle {
  display: none;
  position: relative;
  z-index: 95;
  width: 44px;
  height: 44px;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--paper);
}

.nav-toggle__line {
  position: absolute;
  left: 10px;
  right: 10px;
  height: 2px;
  background: currentColor;
  transition:
    transform var(--dur-nav) var(--ease),
    opacity calc(var(--dur-nav) / 2) var(--ease);
}

.nav-toggle__line--top { top: 15px; }
.nav-toggle__line--mid { top: 21px; }
.nav-toggle__line--bot { top: 27px; }

/* The middle line goes first, then the outer two rotate to meet it. The 40ms
   stagger is what makes it read as deliberate rather than mechanical. */
.nav-toggle__line--mid { transition-delay: 0ms; }
.nav-toggle__line--top,
.nav-toggle__line--bot { transition-delay: 40ms; }

.nav-toggle[aria-expanded="true"] .nav-toggle__line--mid {
  opacity: 0;
  transform: scaleX(0);
  transition-delay: 0ms;
}
.nav-toggle[aria-expanded="true"] .nav-toggle__line--top {
  transform: translateY(6px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__line--bot {
  transform: translateY(-6px) rotate(-45deg);
}

@media (max-width: 767px) {
  .nav-toggle { display: block; }

  .nav {
    position: fixed;
    inset: 0;
    z-index: 90;
    background: var(--ground);
    display: flex;
    align-items: center;
    padding: var(--gutter);
    opacity: 0;
    visibility: hidden;
    transition:
      opacity var(--dur-nav) var(--ease),
      visibility 0s linear var(--dur-nav);
  }
  .nav.is-open {
    opacity: 1;
    visibility: visible;
    transition:
      opacity var(--dur-nav) var(--ease),
      visibility 0s;
  }
  .nav__list {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
  }
  .nav a { font-size: var(--fs-heading); text-transform: none; }
}

/* --- Hero ----------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-items: end;
  overflow: hidden;
  background: var(--ground-deep);
}

.hero__media { position: absolute; inset: 0; }
.hero__media img,
.hero__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero__media img { height: 100%; }

/* Any text over media gets a scrim. Hard rule — it's how the live site's nav
   ended up invisible on light footage. */
.hero__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--media-ground) 85%, transparent) 0%,
    color-mix(in srgb, var(--media-ground) 55%, transparent) 40%,
    transparent 75%
  );
}

.hero__body {
  position: relative;
  padding-block: var(--section);
  max-width: var(--measure);
}

.hero__eyebrow { margin-bottom: var(--space-2); }
.hero__title { margin-bottom: var(--space-3); }
.hero__intro { margin-bottom: var(--space-4); }

/* --- Buttons / links ------------------------------------------------------ */

.link-more {
  display: inline-block;
  font-size: var(--fs-meta);
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  text-decoration: none;
  padding-block: var(--space-1);
  border-bottom: 1px solid var(--paper);
}
.link-more:hover { border-bottom-color: var(--paper-dim); }

/* --- Work archive ---------------------------------------------------------

   A contact sheet, not a table. The stills carry deliberate aspect ratios — a
   poster is 2:3, a social cut 9:16 — and a rigid grid punishes that: every row
   takes the height of its tallest card and the short ones leave holes. Columns
   let each still be its own shape and close the gaps, which is what makes the
   varied ratios read as chosen rather than as a layout that went wrong. */

.archive {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5) var(--space-4);
  /* Cards hang from the top of their row rather than stretching to match the
     tallest. Grid is what makes a double-width card possible at all — CSS
     columns can only span every column or none — and the cost is that rows
     align, so a short card leaves air beneath it. That air is the price of the
     feature; the varied ratios still survive intact. */
  align-items: start;
}

@media (min-width: 600px)  { .archive { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .archive { grid-template-columns: repeat(3, 1fr); } }

/* Double width from 600px up. Below that there is only one column, so a
   featured project is simply the first thing you meet, which is enough. */
@media (min-width: 600px) {
  .archive .card--featured { grid-column: span 2; }
}

/* Packed by archive.js: a fine row unit, and each card claims as many units as
   it needs. Cards then sit at their true heights and a column closes up under a
   short one, instead of waiting for the tallest card in its row.

   Row spacing moves into the card's own margin, because the span already counts
   it — a row-gap on top would double it. */
.archive.is-packed {
  grid-auto-rows: 4px;
  row-gap: 0;
  align-items: start;
}
.archive.is-packed .card { margin-bottom: var(--space-5); }

/* The title and the filters share a band, sitting on the same baseline — the
   homepage statement's shape, reused. Puts the controls beside the work they
   act on instead of a screen above it. */

.work__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
}

/* In the band the row is a sibling of the title, not a block under it. */
.work__head .filters { margin-top: 0; }

.work__intro { margin-top: var(--space-4); }

/* Close enough to the archive to read as its controls. */
.work .archive { margin-top: var(--space-5); }

/* Cards respond to their container, not the viewport, so the same component
   works in a 3-up grid and in a sidebar. */
.card { container-type: inline-size; }

.card a { text-decoration: none; display: block; }

.card__media {
  position: relative;
  /* Set per card from the image's real dimensions — see project_card(). */
  aspect-ratio: var(--ratio, 16 / 9);
  overflow: hidden;
  background: var(--media-ground);
  margin-bottom: var(--space-2);
}
.card__media img { width: 100%; height: 100%; object-fit: cover; }

/* A breath of shade over each still, lifted on hover — the image comes up to
   full strength rather than anything moving. 12% black: enough that the lift
   registers, not enough to read as a scrim.

   --media-ground, not --ground: this is a surface sitting on footage, and those
   are fixed in both schemes so the picture is always the brightest thing on
   screen.

   Only where there is a pointer to hover with. On touch there is nothing to
   lift the shade, so it would just be every still permanently dimmed. */
@media (hover: hover) {
  .card__media::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--media-ground);
    opacity: 0.12;
    pointer-events: none;
  }

  .card:hover .card__media::after,
  .card:focus-within .card__media::after { opacity: 0; }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .card__media::after { transition: opacity var(--dur-state) var(--ease); }
}

/* No scale on hover. A still is a composed frame — the director chose that
   crop — and pushing it 3% past its own edges to acknowledge a cursor is a
   stock gesture that costs the framing. The whole card lifts instead: the
   still comes out from under its shade and the type comes up with it.

   Only the heading actually rests dimmed. The meta line and the standfirst
   already sit at --paper-dim, which in light mode is 4.97:1 against the ground
   — there is no room to rest them any darker without dropping under AA. So
   those keep their resting colour and only brighten, and brightening can never
   cost contrast in either scheme. */

.card__title { color: var(--card-ink, var(--paper)); }

@media (hover: hover) {
  .card { --card-ink: color-mix(in srgb, var(--paper) 90%, var(--ground)); }

  .card:hover,
  .card:focus-within { --card-ink: var(--paper); }

  .card:hover :is(.card__meta, .card__credits, .card__text),
  .card:focus-within :is(.card__meta, .card__credits, .card__text) {
    color: color-mix(in srgb, var(--paper-dim) 40%, var(--paper));
  }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .card__title,
  .card__meta,
  .card__credits,
  .card__text { transition: color var(--dur-state) var(--ease); }
}

.card__title { margin-bottom: var(--space-1); }
.card__meta { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.card__meta > * + *::before { content: "·"; margin-right: var(--space-1); }

@container (min-width: 520px) {
  .card__title { font-size: var(--fs-heading); }
}

/* --- Video facade --------------------------------------------------------- */
/* No third-party JS until someone presses play. */

.video {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--media-ground);
  overflow: hidden;
  color: var(--on-media);
}

.video + .video { margin-top: var(--space-3); }

.video__trigger {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--paper);
}

.video__poster { width: 100%; height: 100%; object-fit: cover; }

.video__icon {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 72px;
  height: 72px;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.video__icon circle {
  fill: color-mix(in srgb, var(--ground) 60%, transparent);
  stroke: var(--paper);
  stroke-width: 1.5;
}
.video__icon path { fill: var(--paper); }

@media (prefers-reduced-motion: no-preference) {
  .video__icon { transition: transform var(--dur-state) var(--ease); }
  .video__trigger:hover .video__icon { transform: translate(-50%, -50%) scale(1.08); }
}

.video__label {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  text-align: left;
  pointer-events: none;
}

.video iframe,
.video__native { width: 100%; height: 100%; border: 0; display: block; }

/* An uploaded film is letterboxed rather than cropped. A platform iframe fills
   its frame because the platform already letterboxes inside it; a bare <video>
   does not, and cover would cut the top and bottom off a 2.35:1 film. */
.video__native { object-fit: contain; background: var(--media-ground); }

/* Fallback when JS is off: the poster is a plain link to the watch page. */
.video__nojs { position: absolute; inset: 0; }

/* --- Project single ------------------------------------------------------- */

.project__header { margin-bottom: var(--space-5); }
.project__title { margin-bottom: var(--space-2); }
.project__subtitle { color: var(--paper-dim); max-width: var(--measure); }

/* Facts and credits as one list, near the top — a commissioner should see who
   did what without scrolling. */
.project__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-3) var(--space-4);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
}
.project__fact dt { margin-bottom: 2px; }
.project__fact dd { margin: 0; font-size: var(--fs-meta); line-height: var(--lh-meta); }

.project__body { margin-top: var(--space-6); max-width: var(--measure); }
.project__media { margin-top: var(--space-6); }
.project__poster { width: 100%; height: auto; }

.gallery {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
  align-items: start; /* stop grid stretching items out of ratio */
}
.gallery img { width: 100%; height: auto; }
@media (min-width: 700px) { .gallery { grid-template-columns: repeat(2, 1fr); } }

/* --- Footer --------------------------------------------------------------- */

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

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  justify-content: space-between;
  align-items: flex-start;
}

.footer__social { display: flex; gap: var(--space-3); }
.footer__social a {
  display: grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
}
.footer__social svg { width: 20px; height: 20px; fill: currentColor; }

/* --- Notices (editorial placeholders) ------------------------------------- */

.notice {
  padding: var(--space-3);
  border: 1px dashed var(--line);
  color: var(--paper-dim);
  font-size: var(--fs-meta);
}

/* --- Motion --------------------------------------------------------------- */

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

/* Flagship hero with no still yet: type on the ground, no borrowed imagery. */
.hero--plain {
  min-height: 70svh;
  align-items: center;
  background: var(--ground);
}

/* Standalone footer links need a 24px minimum target — SC 2.5.8. */
.footer a:not(.footer__social a) {
  display: inline-block;
  min-height: 24px;
  padding-block: 2px;
}

/* --- Video layout --------------------------------------------------------
   Mirrors how the current site presents a project: one film at full width,
   then the rest two-up. */

.videos__lead { margin-bottom: var(--space-3); }

.videos__rest {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}
@media (min-width: 700px) {
  .videos__rest { grid-template-columns: repeat(2, 1fr); }
}
.videos__rest .video + .video { margin-top: 0; }

/* --- Poster-led hero -----------------------------------------------------
   The poster carries its own title treatment, so it sits at its true ratio
   with the text beneath it rather than behind it. */

.hero--poster {
  display: block;
  min-height: 0;
  background: var(--ground);
  padding-top: 0;
  /* .hero clips its media. There is no media to clip here — the poster is
     width:100%, height:auto — and clipping cost the descender of the g in
     "Rising Up", which sits below the line box at --lh-display. */
  overflow: visible;
}

.hero--poster .hero__poster {
  display: block;
  width: 100%;
  height: auto;
}

/* The billing plate. One step darker than the ground so it reads as part of
   the poster rather than as the start of the page, and butted straight up
   under the image so the two are plainly one object. Its width is the
   poster's, both being in the same .wrap--wide. */
.hero--poster .hero__body {
  max-width: none;
  padding: var(--space-5) var(--space-5) var(--space-6);
  background: var(--ground-sunk);
}

/* Room after the plate before the work starts. The flagship is its own act. */
.hero--poster + .section {
  padding-top: calc(var(--section) * 1.25);
}

/* --- About ----------------------------------------------------------------

   Text only. The first paragraph is set as a standfirst — larger, lighter,
   tighter leading — and the rest runs at body size. Pure CSS, so the copy in
   the editor stays exactly as the client wrote it. */

.about__head { margin-bottom: var(--space-6); }

.about__text {
  max-width: 62ch;
}

.about__text > p { text-wrap: pretty; }

/* Standfirst. */
.about__text > p:first-child {
  font-size: var(--fs-heading);
  font-weight: var(--fw-light);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  /* Wider than the body it introduces: it is a 60-word paragraph, so a narrow
     measure would run it as a tall thin column. */
  max-width: 46ch;
  text-wrap: pretty;
}

/* Everything after it. */
.about__text > p + p {
  margin-top: var(--space-4);
}

.about__text > p:first-child + p {
  margin-top: var(--space-6);
  padding-top: var(--space-6);
  border-top: 1px solid var(--line);
}

@media (min-width: 900px) {
  /* Standfirst sits wider than the body, so the two read as distinct
     registers rather than one ragged column. */
  .about__text > p:first-child { max-width: 46ch; }
  .about__text > p:first-child + p { max-width: 60ch; }
}

/* --- Hero: title left, billing spreading right ---------------------------- */

.hero__body--split {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  align-items: start;
}

@media (min-width: 900px) {
  .hero__body--split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-7);
    align-items: end;
  }
}

.hero__lead .hero__title { margin-bottom: 0; }

.hero__aside .hero__intro { margin-bottom: var(--space-4); }

/* Facts run across, not down. */
.hero__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}
.hero__fact dt { margin-bottom: 2px; }
.hero__fact dd { margin: 0; font-size: var(--fs-meta); line-height: var(--lh-meta); }

.hero__cta { margin: 0; }

/* --- Project header with a supporting still ------------------------------- */

.project__header--split {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  align-items: start;
}

@media (min-width: 900px) {
  .project__header--split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.55fr);
    gap: var(--space-7);
  }
}

.project__side { margin: 0; }
.project__side img { width: 100%; height: auto; }

/* --- Work filters ---------------------------------------------------------

   These replaced four section pages, each led by an autoplaying loop. The
   categories are still how the work is read; they are just no longer places
   you go. One page, narrowed where you stand. */

.filters { margin-top: var(--space-6); }

.filters__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}

.filters__item {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  /* SC 2.5.8 wants 24px of target; the padding does it without a visible box. */
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--paper-dim);
  font-size: var(--fs-meta);
  line-height: var(--lh-meta);
  letter-spacing: var(--ls-meta);
  text-decoration: none;
  white-space: nowrap;
}

@media (prefers-reduced-motion: no-preference) {
  .filters__item {
    transition: color var(--dur-state) var(--ease),
                border-color var(--dur-state) var(--ease);
  }
}

.filters__item:hover { color: var(--paper); border-color: var(--line-strong, var(--paper-dim)); }

/* The active one is marked by weight and a solid edge, never by colour alone. */
.filters__item.is-active {
  color: var(--paper);
  border-color: var(--paper);
}

/* No counts on the pills. A number beside a category reads as a boast when it
   is high and an apology when it is low — "Narrative 1" said the quiet part
   out loud. The filter still only prints terms that have work in them, so an
   empty category never appears and the count was never telling anyone
   anything they needed. */

/* On a narrow screen the five pills wrap to three rows and push the work most
   of a screen down. One row that scrolls instead, bleeding to both gutters so
   the cut-off pill is its own affordance.

   Two things this rule has to get right. It must sit *after* the base
   .filters__list, or flex-wrap:wrap wins on source order. And the padding needs
   `.filters` in front of it to out-specify the `ul[class]` reset, which zeroes
   padding at 0,1,1 and would otherwise leave the first pill flush to the edge.

   The vertical padding is not decorative: overflow-x clips vertically too, and
   without it a focused pill loses its outline. */
@media (max-width: 599px) {
  .filters {
    margin-inline: calc(var(--gutter) * -1);
  }

  .filters .filters__list {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    padding-inline: var(--gutter);
    padding-block: var(--space-1);
    scrollbar-width: none;
  }

  .filters .filters__list::-webkit-scrollbar { display: none; }
}
/* --- Page transition -----------------------------------------------------

   Cross-document View Transitions. The browser owns the fade, so the click
   starts the navigation IMMEDIATELY — nothing is held back waiting for an
   animation to finish, which is what made the earlier JS approach stall:
   it faded out first and only then began fetching.

   Both documents opt in, the old page's snapshot cross-fades into the new
   one, and where the API isn't supported the navigation is simply instant.
   No library, no preventDefault, no blank interval. */

@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  /* Symmetrical cross-fade — smooth rather than snappy, and identical in
     both directions so back feels the same as forward. True ease-in-out:
     eases away and eases in, rather than the front-loaded standard curve. */
  animation-duration: 280ms;
  animation-timing-function: ease-in-out;
  animation-fill-mode: both;
}

/* The light/dark swap uses the same machinery, a touch slower — a change of
   scheme is a bigger visual jump than a change of page, and wants longer to
   land. */
html.is-theme-switching::view-transition-old(root),
html.is-theme-switching::view-transition-new(root) {
  animation-duration: 380ms;
  animation-timing-function: ease-in-out;
}

/* Both snapshots occupy the same space, so the outgoing one must not paint
   over the incoming one opaquely — this is what makes it read as a dissolve
   rather than a flicker. */
html.is-theme-switching::view-transition-old(root) { mix-blend-mode: normal; }

/* The header and footer are constant across pages, so holding them still
   stops the furniture flickering while the content changes underneath. */
.header,
.footer {
  view-transition-name: none;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* Fallback for browsers without cross-document view transitions (Firefox at
   time of writing). The incoming page fades in on load — no fade-out, because
   a fade-out has to block navigation to be seen, and blocking is exactly the
   stall we removed. Never delays a click. */

@supports not (view-transition-name: none) {
  @media (prefers-reduced-motion: no-preference) {
    body {
      animation: oof-page-in 260ms cubic-bezier(0.4, 0, 0.2, 1) both;
    }
  }
}

@keyframes oof-page-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* --- Theme toggle --------------------------------------------------------
   A circle split down the middle: the half that is filled shows the scheme
   you would switch TO, which is the convention people already read. */

.theme-toggle {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--paper);
  cursor: pointer;
}

/* The nav is uppercase, so the eye centres on the cap height, not the line
   box — and the cap-height centre sits ~1.25px above it. Measured, not
   guessed. The nudge lives in a custom property because the hover rotation
   uses the same transform and would otherwise overwrite it. */
.theme-toggle svg {
  --nudge: -1.25px;
  width: 22px;
  height: 22px;
  display: block;
  overflow: visible;
  transform: translateY(var(--nudge));
}
.aperture > circle { fill: none; stroke: currentColor; stroke-width: 1.4; }
.aperture__fill { fill: currentColor; }

/* The iris turns, the way one does. */
@media (prefers-reduced-motion: no-preference) {
  .theme-toggle svg { transition: transform 400ms var(--ease); }
  .theme-toggle:hover svg { transform: translateY(var(--nudge)) rotate(60deg); }
}

/* The toggle's optical centre is its circle, which is centred in a 44px hit
   area. Centring the flex row therefore lines that circle up with the middle
   of the nav words — the 44px target stays, it just isn't what you see. */
.header__tools {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

/* The nav is a flex row of links; centre it on its own text so the row's
   cross-axis centre really is the middle of the words. */
.header__tools .nav { display: flex; align-items: center; }

@media (max-width: 767px) {
  .header__tools { gap: var(--space-1); }
}


/* --- About blocks --------------------------------------------------------- */

.about__block { margin-top: var(--space-7); max-width: var(--measure); }
.about__block h2 { margin-bottom: var(--space-4); }

.clients {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-2) var(--space-4);
}
.clients__item {
  padding-top: var(--space-2);
  border-top: 1px solid var(--line);
}

/* --- Homepage statement --------------------------------------------------
   Line left, the way through on the right, sitting on the same baseline. */

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

.statement__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;      /* button rides the bottom of the text */
  justify-content: space-between;
  gap: var(--space-4) var(--space-6);
}

/* Heading size, on Elliott's preference.
   ⚠️ This is deliberately *not* what call 3 asked for. Jamie called the Old Oak
   home page text "a step too large"; it was dropped to body size on 8 September
   and put back the same day because the smaller setting read worse. Flagged
   rather than quietly reverted — it is an open disagreement with the client, not
   an oversight, and it should go back to him rather than be discovered. */
.statement__line {
  flex: 1 1 30ch;
  margin: 0;
  max-width: 48ch;
  font-size: var(--fs-heading);
  font-weight: var(--fw-light);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  text-wrap: pretty;
}

.statement__more { flex: 0 0 auto; margin: 0; }

@media (max-width: 700px) {
  .statement__inner { align-items: flex-start; }
}

/* --- Stacked run ---------------------------------------------------------
   Two columns, each a run of projects stacked one under another — image at
   its own ratio, then title, credits and text. Not a grid: rows never line
   up, which is the point. Multi-column rather than grid because a grid would
   force a shared row height and reintroduce the uniformity we just removed. */

.stack {
  columns: 1;
  column-gap: var(--space-6);
}

@media (min-width: 800px) {
  .stack { columns: 2; }
}

.stack .card {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  margin-bottom: var(--space-7);
}

.card--stacked .card__media { margin-bottom: var(--space-3); }
.card--stacked .card__title { margin-bottom: var(--space-2); }
.card--stacked .card__credits { margin-top: var(--space-1); }
.card--stacked .card__text {
  margin-top: var(--space-3);
  max-width: 46ch;
  color: var(--paper-dim);
}

/* A project with no still is a supported state, not a broken one — so it gets
   a rule and some air rather than an empty frame. Sits among image cards as a
   text entry, which is legible as a choice. */
.card--textonly {
  padding-top: var(--space-3);
  border-top: 1px solid var(--line);
}
.card--textonly .card__title { margin-top: 0; }
