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

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

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

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

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

:root {
  --ink: #090909;
  --paper: #f6f4ef;
  --mist: #b7b4ad;
  --line: rgba(9, 9, 9, 0.14);
  --yellow: #f4c400;
  --gutter: clamp(1rem, 3vw, 3.25rem);
  --p: 0;
  --hero-fade: 1;
  --surface-progress: 0;
  --service-intro: 0;
  --capabilities: 0;
  --service-copy: 0;
  --structure-reveal: 0;
  --copy-reveal: 0;
  --media-reveal: 0;
  --rows-reveal: 0;
  --handoff: 0;
  --content-x: 0px;
  --content-y: 0px;
  --branding-x: 0px;
  --branding-y: 0px;
  --branding-opacity: 1;
  --word-scale: 1;
  --media-scale: 1;
  --media-top: 42vh;
  --media-width: min(58vw, 68rem);
  --media-height: min(49vh, 33rem);
  --media-opacity: 1;
  --media-dim: 0;
  --reel-opacity: 1;
  --chrome-opacity: 1;
  /* These four ARE the navbar's resting geometry, and `setHeroProgress` writes the
     same numbers back every frame on the homepage — full width, flush to the top,
     105px tall, no glass (matching fairlightcreative.com, measured at 1440×900).
     They used to describe a centred 60px pill left over from an earlier design,
     which the homepage's rAF loop overwrote on its first frame and no one saw. The
     inner pages have no such loop, so they rendered the stale pill and the nav did
     not match the homepage. If the navbar's resting size changes, change it in BOTH
     places: here and in `setHeroProgress`. Width is `100%` rather than `100vw` so a
     visible scrollbar does not push the bar into horizontal overflow. */
  --nav-compact: 1;
  --nav-width: 100%;
  --nav-top: 0px;
  --nav-height: 105px;
  --service-gutter: max(5vw, calc((100vw - 100rem) / 2));
  --page-wash-left: 0;
  --page-wash-right: 0;
  /* Dark surfaces are a deep navy rather than flat black. Kept very dark so
     paper type still holds ~16:1 contrast — the blue is meant to read across
     large areas (the wash, the footer panel) and not as a colour cast on
     small ones. The three steps preserve the depth order the flat-black
     values had: the footer panel sat *below* the wash and the action cell
     *above* it, and that relationship is unchanged. */
  --surface-dark: #0d1826;
  --surface-dark-deep: #0a1220;
  --surface-dark-raised: #142235;

  /* The wash is that navy. There is no light-coloured phase — the circles are
     what bring the dark theme in, and they do not appear before it. */
  --page-wash-surface: var(--surface-dark);
}

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

html {
  scroll-behavior: smooth;
  background: var(--paper);
}

body {
  margin: 0;
  overflow-x: hidden;
  color: var(--ink);
  background: var(--paper);
  font-family: "Satoshi", sans-serif;
  -webkit-font-smoothing: antialiased;
}

main {
  position: relative;
  z-index: 1;
  /* Load-bearing for `.statement`, not decoration. `z-index: 1` makes `main` a
     stacking context, which isolates it for blending — so `mix-blend-mode`
     inside it composites against main's own group, whose backdrop starts
     transparent. `body`'s paper is outside that group and invisible to it.
     Without this, `difference` on white had nothing to subtract from and the
     statement painted literal white on paper. Repeating paper here gives the
     blend the light backdrop the maths assumes; it changes nothing visually
     on its own, since body is already this colour. */
  background: var(--paper);
}

/* A page-level colour field, not a section background. Two fixed circles
   grow from opposing edges while the document continues its normal scroll,
   then remain behind Work and About as their shared surface. */
.page-wash {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.page-wash__circle {
  position: absolute;
  display: block;
  width: 82vmax;
  height: 82vmax;
  background: var(--page-wash-surface);
  border-radius: 50%;
  will-change: transform;
}

.page-wash__circle--left {
  bottom: -35vmax;
  left: -31vmax;
  transform: scale(calc(var(--page-wash-left) * 2.2));
}

.page-wash__circle--right {
  top: -34vmax;
  right: -29vmax;
  transform: scale(calc(var(--page-wash-right) * 2.2));
}

body.menu-open,
body:has(.reel-dialog[open]) {
  overflow: hidden;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  color: inherit;
  font: inherit;
}

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

::selection {
  color: var(--ink);
  background: var(--yellow);
}

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

/* Cursor: a small ring with a dot at its centre. Ported from the port-3000
   prototype. The dot tracks the pointer exactly while the ring eases in
   behind it, and the ring widens over interactive elements to replace the
   affordance the system cursor normally carries in its shape. */
.cursor-field {
  position: absolute;
  inset: 0;
  z-index: 400;
  display: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.cursor-field[data-active="true"] {
  opacity: 1;
}

.cursor-field__ring,
.cursor-field__dot {
  position: fixed;
  top: 0;
  left: 0;
  display: block;
  border-radius: 50%;
  pointer-events: none;
  will-change: transform;
}

.cursor-field__ring {
  width: 26px;
  height: 26px;
  border: 1px solid rgba(9, 9, 9, 0.55);
  transition:
    width 220ms cubic-bezier(0.22, 1, 0.36, 1),
    height 220ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.cursor-field__dot {
  width: 4px;
  height: 4px;
  background: var(--ink);
}

.cursor-field[data-over="true"] .cursor-field__ring {
  width: 42px;
  height: 42px;
  border-color: var(--ink);
}

.cursor-field[data-over="true"][data-on-dark="true"] .cursor-field__ring {
  border-color: var(--paper);
}

/* The dark client bar is the one surface an ink cursor would disappear
   against, so the pair inverts over it. Flagged from JS, since the field is
   not a sibling of the section the pointer is over. */
.cursor-field[data-on-dark="true"] .cursor-field__ring {
  border-color: rgba(246, 244, 239, 0.75);
}

.cursor-field[data-on-dark="true"] .cursor-field__dot {
  background: var(--paper);
}

/* The native cursor is only given up once the custom one is actually running. */
html[data-custom-cursor="true"],
html[data-custom-cursor="true"] a,
html[data-custom-cursor="true"] button {
  cursor: none;
}

/* A modal <dialog> renders in the top layer, above the cursor field, so the
   ring would vanish while the showreel is open. Hand the native cursor back
   for as long as it is. */
html:has(.reel-dialog[open]) .cursor-field {
  display: none;
}

html[data-custom-cursor="true"]:has(.reel-dialog[open]),
html[data-custom-cursor="true"]:has(.reel-dialog[open]) a,
html[data-custom-cursor="true"]:has(.reel-dialog[open]) button {
  cursor: auto;
}

@media (hover: hover) and (pointer: fine) {
  .cursor-field { display: block; }
}

.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 1000;
  padding: 0.75rem 1rem;
  color: var(--paper);
  background: var(--ink);
  transform: translateY(-160%);
}

.skip-link:focus {
  transform: translateY(0);
}

/* Splash ---------------------------------------------------------------
   A shorter, quieter cut of the approved three-H assembly from port 3000.
   There is no counter, slogan or grain: the work enters inside the letters,
   becomes the exact supplied mark, then that same object docks into the nav. */
.splash-intro {
  display: none;
}

/* The authored grid display otherwise outranks the browser's default
   `[hidden] { display: none; }`, leaving an invisible-looking but interactive
   full-screen layer behind after `finish()`. This is especially important for
   the deterministic `?nosplash` review route. */
.js .splash-intro[hidden] { display: none; }

.js .splash-intro {
  position: fixed;
  inset: 0;
  z-index: 5000;
  display: grid;
  min-height: 100svh;
  overflow: hidden;
  place-items: center;
  background: #090909;
}

.js.splash-running,
.js.splash-running body {
  overflow: hidden;
}

.splash-intro__skip {
  position: absolute;
  top: clamp(1rem, 2.2vw, 1.75rem);
  right: var(--gutter);
  z-index: 2;
  display: inline-flex;
  min-width: 44px;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0;
  color: rgba(246, 244, 239, 0.62);
  background: transparent;
  border: 0;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.splash-intro__skip:hover,
.splash-intro__skip:focus-visible {
  color: #ffdb1f;
}

.splash-intro__stage {
  position: relative;
  width: min(78vw, 74svh, 42rem);
  aspect-ratio: 1669 / 1644;
  transform-origin: 50% 50%;
  will-change: transform;
}

.splash-intro__canvas,
.splash-intro__mark {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.splash-intro__canvas {
  pointer-events: none;
}

.splash-intro__mark {
  overflow: visible;
}

.splash-intro__bar {
  shape-rendering: crispEdges;
}

.splash-intro__video-plane {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}

.splash-intro__video-plane video {
  width: 1px;
  height: 1px;
}

.splash-intro__exact-body,
.splash-intro__ring,
.splash-intro__ring-arc {
  opacity: 0;
  visibility: hidden;
}

.splash-intro__ring-arc {
  fill: none;
  stroke: white;
  stroke-width: 64;
  stroke-linecap: round;
  will-change: stroke-dashoffset;
}

@media (max-width: 47.999rem) {
  .splash-intro__stage {
    width: min(92vw, 66svh);
  }

  .splash-intro__skip {
    top: 0.75rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .js .splash-intro {
    display: none !important;
  }
}

.site-header {
  position: fixed;
  top: var(--nav-top);
  left: 50%;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: var(--nav-width);
  height: var(--nav-height);
  padding: 0 calc((var(--gutter) * (1 - var(--nav-compact))) + (1.25rem * var(--nav-compact)));
  color: var(--paper);
  background: rgba(9, 9, 9, calc(0.9 - (0.04 * var(--nav-compact))));
  border: 1px solid rgba(246, 244, 239, calc(0.13 * var(--nav-compact)));
  border-radius: calc(0.85rem * var(--nav-compact));
  box-shadow: 0 14px 40px rgba(0, 0, 0, calc(0.15 * var(--nav-compact)));
  backdrop-filter: blur(calc(10px + (8px * var(--nav-compact)))) saturate(90%);
  opacity: var(--chrome-opacity);
  transform: translateX(-50%);
  transition: border-color 300ms ease;
  will-change: width, height, top, opacity;
}

.site-header::before {
  position: absolute;
  inset: -1px;
  z-index: -1;
  content: "";
  background: transparent;
  border-radius: inherit;
  opacity: var(--nav-compact);
  pointer-events: none;
}

.site-header[data-hero-hidden="true"] {
  pointer-events: none;
}

.site-header[data-scrolled="true"] {
  border-color: rgba(246, 244, 239, calc(0.18 * var(--nav-compact)));
}

.site-header > .wordmark,
.site-header > .primary-nav {
  opacity: calc(1 - var(--nav-compact));
  pointer-events: auto;
}

.site-header[data-compact="true"] > .wordmark,
.site-header[data-compact="true"] > .primary-nav {
  pointer-events: none;
}

.compact-nav {
  position: absolute;
  inset: 0 1.25rem;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1.5rem;
  opacity: var(--nav-compact);
  pointer-events: none;
}

.site-header[data-compact="true"] .compact-nav {
  pointer-events: auto;
}

.compact-nav nav {
  display: flex;
  align-items: center;
  gap: clamp(0.8rem, 1.4vw, 1.5rem);
  font-size: 0.7rem;
  font-weight: 500;
}

.compact-nav nav:last-child {
  justify-content: flex-end;
}

.compact-nav__mark {
  font-size: 0.84rem;
  font-weight: 700;
  letter-spacing: 0.16em;
}

.compact-nav__mark i {
  margin-right: 0.12rem;
  color: var(--yellow);
  font-family: "Gambetta", serif;
  font-style: normal;
  font-weight: 500;
}

.wordmark {
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: baseline;
  font-size: clamp(1.35rem, 1.8vw, 2rem);
  font-weight: 700;
  letter-spacing: 0.16em;
  line-height: 1;
}

.wordmark span {
  margin-right: 0.16em;
  color: var(--yellow);
  font-family: "Gambetta", serif;
  font-size: 1.08em;
  font-weight: 500;
  letter-spacing: 0;
}

.primary-nav {
  display: flex;
  align-items: center;
  gap: clamp(1.35rem, 3vw, 3.6rem);
  font-size: clamp(0.78rem, 0.93vw, 1rem);
  font-weight: 500;
}

.primary-nav a {
  position: relative;
  padding: 0.65rem 0;
}

.primary-nav a::after {
  position: absolute;
  right: 0;
  bottom: 0.34rem;
  left: 0;
  height: 1px;
  content: "";
  background: var(--yellow);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1);
}

.primary-nav a:hover::after,
.primary-nav a:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left;
}

.menu-toggle {
  display: none;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
}

.hero-track {
  position: relative;
  height: 300svh;
}

.hero-sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  min-height: 42rem;
  overflow: hidden;
  isolation: isolate;
  background: transparent;
}

.hero-sticky::after {
  position: absolute;
  inset: 0;
  z-index: 0;
  content: "";
  pointer-events: none;
  opacity: 0.11;
  background-image: radial-gradient(rgba(9, 9, 9, 0.16) 0.55px, transparent 0.55px);
  background-size: 6px 6px;
  mix-blend-mode: multiply;
}

.service-surface {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: transparent;
  transform: none;
}

.orbital-grid {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  opacity: calc(0.68 * var(--hero-fade));
  pointer-events: none;
}

.orbital-grid i {
  position: absolute;
  width: min(34vw, 38rem);
  aspect-ratio: 1;
  border: 1px solid rgba(9, 9, 9, 0.045);
  border-radius: 50%;
}

.orbital-grid i::before,
.orbital-grid i::after {
  position: absolute;
  inset: 12%;
  content: "";
  border: inherit;
  border-radius: inherit;
}

.orbital-grid i::after {
  inset: 27%;
}

.orbital-grid__left {
  top: 18%;
  left: -16%;
}

.orbital-grid__right {
  right: -12%;
  bottom: -14%;
}

.hero-composition,
.hero-words {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
}

.hero-words {
  z-index: 6;
  color: var(--ink);
  mix-blend-mode: normal;
}

/* Two registered copies make the film/type collision spatial rather than
   threshold-based. The ink copy is punched out by the live media rectangle;
   the contrast copy exists only inside it. As the frame expands or exits,
   the boundary therefore travels through the individual letterforms. */
.hero-words--base {
  -webkit-mask-image: linear-gradient(#000 0 0), linear-gradient(#000 0 0);
  -webkit-mask-position: 0 0, 50% var(--media-top);
  -webkit-mask-size: 100% 100%, var(--media-width) var(--media-height);
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-composite: xor;
  mask-image: linear-gradient(#000 0 0), linear-gradient(#000 0 0);
  mask-position: 0 0, 50% var(--media-top);
  mask-size: 100% 100%, var(--media-width) var(--media-height);
  mask-repeat: no-repeat;
  mask-composite: exclude;
  will-change: mask-position, mask-size;
}

.hero-words--film {
  color: var(--paper);
  mix-blend-mode: difference;
  clip-path: inset(
    var(--media-top)
    calc((100vw - var(--media-width)) / 2)
    calc(100vh - var(--media-top) - var(--media-height))
    calc((100vw - var(--media-width)) / 2)
  );
  will-change: clip-path;
}

.hero-word,
.hero-qualifier {
  position: absolute;
  margin: 0;
  white-space: nowrap;
}

.hero-word {
  color: inherit;
  font-family: "Gambetta", serif;
  font-size: clamp(4.8rem, 8.2vw, 9.25rem);
  font-weight: 400;
  line-height: 0.82;
  letter-spacing: -0.055em;
  text-transform: uppercase;
  will-change: transform;
}

.hero-word--content {
  top: 16.5vh;
  left: 14vw;
  transform: translate3d(var(--content-x), var(--content-y), 0) scale(var(--word-scale));
  transform-origin: left top;
}

/* `right` here is LOAD-BEARING for script.js. `brandingX` lerps the word from
   this hero position to the services position (`--service-gutter` from the
   right), so the delta is `width * 0.1 - serviceGutter`. The two numbers must
   be changed together or the word lands in the wrong place in the What We Do
   state.

   The diagonal is the point of this composition — CONTENT top-left, this word
   bottom-right, with the qualifiers filling the opposite corners. It briefly
   held "Management" (pulled left to 36vw to fit a trailing qualifier) and that
   collapsed the diagonal, so it is back where it was. The word now CHANGES
   instead of moving. */
.hero-word--branding {
  top: 29vh;
  right: 10vw;
  opacity: var(--branding-opacity);
  transform: translate3d(var(--branding-x), var(--branding-y), 0) scale(var(--word-scale));
  transform-origin: right top;
}

/* The word briefly carried a second span that cross-faded to "Management" for
   the What We Do state. Removed at the user's request — "Branding" stands in
   the heading in both states — along with `--word-out` / `--word-in` and the
   `wordOut` / `wordIn` pair in script.js. */

.hero-qualifier {
  color: #aaa69f;
  font-family: "Gambetta", serif;
  font-size: clamp(2rem, 3.45vw, 4.2rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.035em;
  opacity: var(--hero-fade);
  transform: translateY(calc((1 - var(--hero-fade)) * -0.7rem));
}

.hero-qualifier--moves {
  top: 21.6vh;
  left: 50.2vw;
}

.hero-qualifier--strategy {
  top: 33.4vh;
  left: 22vw;
}

.strategy-rule {
  position: absolute;
  top: 39.4vh;
  left: 22vw;
  width: min(22vw, 26rem);
  height: 2px;
  background: var(--yellow);
  opacity: var(--hero-fade);
  transform: scaleX(var(--hero-fade));
  transform-origin: left;
}

.hero-media {
  position: absolute;
  top: var(--media-top);
  left: 50%;
  z-index: 2;
  width: var(--media-width);
  height: var(--media-height);
  overflow: hidden;
  opacity: var(--media-opacity);
  background: var(--surface-dark);
  clip-path: inset(0 0 0 0);
  transform: translate3d(-50%, 0, 0) scale(var(--media-scale));
  transform-origin: center center;
  will-change: clip-path, opacity, transform;
}

.js:not(.is-ready) .hero-media {
  clip-path: inset(0 49.96% 0 49.96%);
}

.js.is-ready .hero-media {
  animation: media-open 2.7s 0.1s cubic-bezier(0.76, 0, 0.24, 1) both;
}

@keyframes media-open {
  from { clip-path: inset(0 49.96% 0 49.96%); }
  to { clip-path: inset(0 0 0 0); }
}

.hero-media__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.js:not(.is-ready) .hero-media__video {
  transform: scale(1.18);
}

.js.is-ready .hero-media__video {
  animation: media-settle 3.6s 0.1s cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes media-settle {
  from { transform: scale(1.18); }
  to { transform: scale(1); }
}

.hero-media__shade {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(rgba(0, 0, 0, var(--media-dim)), rgba(0, 0, 0, var(--media-dim))),
    linear-gradient(180deg, transparent 44%, rgba(0, 0, 0, 0.36));
  pointer-events: none;
}

.reel-trigger {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  justify-items: center;
  gap: 1.1rem;
  padding: 0;
  color: #fff;
  background: transparent;
  border: 0;
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.28em;
  opacity: var(--reel-opacity);
  transform: translate(-50%, -43%);
  pointer-events: auto;
  cursor: pointer;
}

.hero-sticky[data-reel-hidden="true"] .reel-trigger {
  pointer-events: none;
}

.reel-trigger__disc {
  position: relative;
  display: grid;
  width: clamp(4.4rem, 6vw, 6.2rem);
  aspect-ratio: 1;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.9);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.08);
  backdrop-filter: blur(5px);
  transition: color 320ms ease, background 320ms ease, transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

.reel-trigger__disc i {
  width: 0;
  height: 0;
  margin-left: 0.3rem;
  border-top: 0.72rem solid transparent;
  border-bottom: 0.72rem solid transparent;
  border-left: 1.08rem solid currentColor;
}

.reel-trigger:hover .reel-trigger__disc,
.reel-trigger:focus-visible .reel-trigger__disc {
  color: var(--ink);
  background: #fff;
  transform: scale(1.08);
}

.hero-meta {
  position: absolute;
  top: 65vh;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: clamp(0.8rem, 2.25vw, 2.6rem);
  font-size: clamp(0.61rem, 0.74vw, 0.78rem);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.23em;
  opacity: var(--hero-fade);
}

.hero-meta--left { left: 5.4vw; }
.hero-meta--right { right: 5.4vw; }

.status-dot {
  display: block;
  width: 0.45rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--yellow);
}

.scroll-cue {
  position: absolute;
  bottom: 1.5rem;
  left: 50%;
  z-index: 6;
  display: grid;
  justify-items: center;
  gap: 0.72rem;
  font-size: 0.64rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.28em;
  opacity: var(--hero-fade);
  transform: translateX(-50%);
}

.scroll-cue i {
  position: relative;
  width: 1px;
  height: 2.3rem;
  overflow: hidden;
  background: rgba(9, 9, 9, 0.2);
}

.scroll-cue i::after {
  position: absolute;
  inset: 0;
  content: "";
  background: var(--yellow);
  animation: scroll-line 1.8s ease-in-out infinite;
  transform-origin: top;
}

@keyframes scroll-line {
  0% { transform: translateY(-100%); }
  48% { transform: translateY(0); }
  100% { transform: translateY(100%); }
}

.service-kicker,
.service-affix,
.service-detail,
.service-concept,
.service-divider {
  position: absolute;
  pointer-events: none;
}

.service-kicker,
.service-affix,
.service-concept,
.service-divider {
  z-index: 5;
  opacity: var(--service-copy);
}

.service-kicker,
.service-concept {
  clip-path: inset(0 0 calc((1 - var(--structure-reveal)) * 100%) 0);
}

.service-affix {
  clip-path: inset(0 0 calc((1 - var(--copy-reveal)) * 100%) 0);
  transform: translateY(calc((1 - var(--copy-reveal)) * 0.65rem));
}

.service-detail {
  z-index: 1;
  opacity: var(--service-copy);
}

.service-kicker {
  top: 12.5vh;
  left: 50%;
  margin: 0;
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.24em;
  transform: translate(-50%, calc((1 - var(--structure-reveal)) * 0.65rem));
}

.service-kicker::before,
.service-kicker::after {
  position: absolute;
  top: 50%;
  width: 3.5vw;
  height: 1px;
  content: "";
  background: var(--line);
}

.service-kicker::before { right: calc(100% + 1rem); }
.service-kicker::after { left: calc(100% + 1rem); }

.service-affix {
  top: 22vh;
  color: #6e6b65;
  font-family: "Gambetta", serif;
  font-size: clamp(1.2rem, 1.5vw, 1.7rem);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.025em;
}

.service-concept {
  top: 3.2vh;
  left: 2vw;
  margin: 0;
  font-size: 0.54rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.service-divider {
  top: 18vh;
  bottom: 6vh;
  left: 50%;
  width: 1px;
  background: rgba(9, 9, 9, 0.08);
  transform: scaleY(var(--structure-reveal));
  transform-origin: top;
}

.service-affix--agency { left: var(--service-gutter); }
.service-affix--personal {
  top: 8.5vh;
  right: var(--service-gutter);
}

.service-detail {
  top: 29.5vh;
  width: min(39vw, 42rem);
}

.service-detail--content { left: var(--service-gutter); }
.service-detail--branding { right: var(--service-gutter); }

.service-detail > p {
  max-width: 14rem;
  margin: 0;
  color: #55524d;
  font-size: clamp(0.72rem, 0.78vw, 0.86rem);
  line-height: 1.45;
  clip-path: inset(0 0 calc((1 - var(--copy-reveal)) * 100%) 0);
  transform: translateY(calc((1 - var(--copy-reveal)) * 0.65rem));
}

.service-detail--branding > p {
  margin-left: auto;
  text-align: right;
}

.service-media-frame,
.service-links {
  opacity: var(--service-copy);
}

.service-media-frame {
  clip-path: inset(0 0 calc((1 - var(--media-reveal)) * 100%) 0);
  transform: translateY(calc((1 - var(--media-reveal)) * 0.85rem));
}

.service-links {
  clip-path: inset(0 0 calc((1 - var(--rows-reveal)) * 100%) 0);
  transform: translateY(calc((1 - var(--rows-reveal)) * 0.85rem));
}

.hero-sticky[data-services-interactive="true"] .service-detail {
  pointer-events: auto;
}

.service-media-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 7;
  margin: clamp(1rem, 2.2vh, 1.4rem) 0 0;
  overflow: hidden;
  background: var(--surface-dark-deep);
}

.service-media-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.92) contrast(1.03);
  transform: scale(1);
  transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
}

.service-media-frame:hover img {
  transform: scale(1.015);
}

.service-links {
  display: grid;
  margin-top: clamp(0.7rem, 1.4vh, 1rem);
  border-top: 1px solid var(--line);
}

.service-links a {
  min-height: 2.5rem;
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 0.8rem;
  padding: 0.56rem 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  /* Was clamp(0.76rem, 0.82vw, 0.9rem) — the vw term collapsed to the 12.2px
     floor at 1440, which is what made the service names hard to read. */
  font-size: clamp(0.92rem, 0.98vw, 1.06rem);
  transition: padding 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

.service-links a .service-link__number {
  color: #6f6b64;
  font-size: 0.72rem;
}

.service-links a strong {
  font-weight: 500;
  line-height: 1.2;
  transition: transform 360ms cubic-bezier(0.22, 1, 0.36, 1);
}

.service-links a i {
  margin-left: auto;
  color: #77736c;
  font-style: normal;
  transition: color 280ms ease, transform 360ms cubic-bezier(0.22, 1, 0.36, 1);
}

.service-link__description {
  grid-column: 2 / 3;
  max-width: 32rem;
  max-height: 0;
  overflow: hidden;
  /* Slightly darker as well as larger — at 10.9px in #5b5852 this was doing
     more to crowd the service name than to explain it. */
  color: #4c4942;
  font-size: clamp(0.82rem, 0.86vw, 0.94rem);
  line-height: 1.4;
  opacity: 0;
  transform: translateY(-0.2rem);
  transition:
    max-height 440ms cubic-bezier(0.22, 1, 0.36, 1),
    margin 440ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 260ms ease,
    transform 440ms cubic-bezier(0.22, 1, 0.36, 1);
}

.service-links a.is-active {
  padding-top: 0.75rem;
  padding-bottom: 0.78rem;
}

.service-links a.is-active .service-link__description {
  /* Raised with the font size — 2.8em clipped the third line at the new size,
     which is the "written over" overlap. */
  max-height: 4.2em;
  margin-top: 0.35rem;
  opacity: 1;
  transform: translateY(0);
}

.service-links a:hover strong,
.service-links a:focus-visible strong {
  transform: translateX(0.3rem);
}

.service-links a:hover i,
.service-links a:focus-visible i,
.service-links a.is-active i {
  color: var(--yellow);
  transform: translateX(0.35rem);
}

/* Work stage -----------------------------------------------------------
   Scroll-driven slot rotation. The section is three viewports tall; the
   layout inside sticks and the three projects rotate through three fixed
   slots (hero, lower-left, lower-right) at each third of the travel.
   Slot geometry lives in CSS grid so GSAP Flip animates real box
   dimensions rather than scaling — the dashed stroke and caption type
   stay undistorted through the move. */
/* The gap between What We Do and Our work. The hero's sticky stage holds the
   resolved composition until its track ends and then Our work replaces it
   through normal document flow — with no gap, the incoming section's outlined
   title climbed over the still-visible service rows. This is that clearance,
   in flow, so it costs the handoff nothing. */
.workstage {
  position: relative;
  z-index: 2;
  margin-top: clamp(3rem, 18svh, 11rem);
  height: 300vh;
  background: transparent;
  isolation: isolate;
  --ws-gap: clamp(1rem, 2.2vw, 2rem);
  --ws-pad: clamp(0.75rem, 1.75vw, 1.5625rem);
  --ws-stagger: clamp(1.5rem, 4.25vw, 3.85rem);
  --ws-dash: rgba(9, 9, 9, 0.32);
}

.workstage::before {
  display: none;
}

.workstage__sticky {
  position: sticky;
  z-index: 1;
  /* Clear the compact nav that floats over the top of the page. */
  top: calc(4.5rem + var(--ws-gap));
  padding: 0 var(--gutter);
}

.workstage__layout {
  /* Flip parks the projects in absolute position mid-flight; they must
     resolve against this box, not the page. */
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-template-rows: auto auto var(--ws-stagger) auto;
  align-items: start;
  min-width: 0;
  gap: var(--ws-gap);
}

.workstage__title {
  position: relative;
  grid-column: 1 / span 4;
  grid-row: 1;
  margin: 0;
  font-family: "Gambetta", serif;
  /* Sized to fit its own column on ONE line, not to the viewport. The title sits in
     4 of 12 columns — roughly 0.30 × viewport — and "Our work" measures about 3.7×
     its font-size, so anything above ~8.1vw wraps. It was 9vw, which fitted on a
     wide monitor and broke onto two lines on a laptop. 7.4vw leaves headroom, and
     the `svh` cap keeps it in proportion on a short screen. `nowrap` makes a future
     regression obvious rather than silent. */
  font-size: min(clamp(2.5rem, 7.4vw, 9.5rem), 15svh);
  font-weight: 400;
  line-height: 0.83;
  letter-spacing: -0.055em;
  white-space: nowrap;
}

/* The letterforms read as a loading bar: horizontal stripes that fill
   from left to right as the projects rotate. */
.workstage__title-ghost,
.workstage__title-fill {
  display: block;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.workstage__title-ghost {
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(9, 9, 9, 0.3) 0,
    rgba(9, 9, 9, 0.3) 6px,
    transparent 6px,
    transparent 11px
  );
}

.workstage__title-fill {
  position: absolute;
  inset: 0;
  clip-path: inset(0 100% 0 0);
  background-image: repeating-linear-gradient(
    to bottom,
    var(--yellow) 0,
    var(--yellow) 6px,
    transparent 6px,
    transparent 11px
  );
}

.workstage__intro {
  grid-column: 5 / span 4;
  grid-row: 1;
  align-self: end;
  max-width: 26rem;
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.5;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* The dashed box is the slot; the media sits inset inside it. */
.workstage__project,
.workstage__more {
  min-width: 0;
  padding: var(--ws-pad);
  border: 1px dashed var(--ws-dash);
}

.workstage__project.is-slot-a {
  grid-column: 5 / span 5;
  grid-row: 2 / span 2;
}

.workstage__project.is-slot-b {
  grid-column: 1 / span 4;
  grid-row: 3 / span 2;
  align-self: start;
}

.workstage__project.is-slot-c {
  grid-column: 5 / span 4;
  grid-row: 4;
}

.workstage__more {
  grid-column: 9 / span 2;
  grid-row: 4;
  align-self: start;
}

.workstage__more a {
  display: block;
  padding: 0.9rem 1rem;
  background: rgba(9, 9, 9, 0.06);
  font-size: 0.7rem;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  transition: background 400ms ease, color 400ms ease;
}

.workstage__more a:hover {
  background: var(--yellow);
  color: var(--ink);
}

.workstage__frame {
  display: block;
  overflow: hidden;
}

.workstage__frame video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  filter: saturate(0.92);
  transition: filter 500ms ease, transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}

.workstage__frame:hover video {
  filter: saturate(1.05);
  transform: scale(1.02);
}

.workstage__caption {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-top: var(--ws-pad);
}

.workstage__caption h3 {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.workstage__caption span {
  font-size: 0.78rem;
  letter-spacing: 0.1em;
}

/* About ----------------------------------------------------------------
   An editorial split inspired by Fairlight's disciplined hero geometry:
   one running rule, copy on the left, still imagery on the right and a true
   centre axis. The facts are a typographic rail rather than boxed cards. */
/* Statement ------------------------------------------------------------
   A full-viewport breath between the pinned work stage and About. It runs
   on the shared page wash rather than its own colour, so the break comes
   from the change of rhythm — one centred line of display type in a lot of
   air, after a dense scroll-driven section — rather than from a new
   background. Lines are hand-broken so the rag is deliberate. */
.statement {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
  /* A band between two sections, not a screen of its own. */
  min-height: clamp(15rem, 34svh, 24rem);
  padding: clamp(3.5rem, 7vh, 6rem) var(--gutter);
  background: transparent;
}

.statement__text {
  max-width: 60rem;
  margin: 0;
  font-family: "Gambetta", serif;
  font-size: clamp(1.9rem, 4.6vw, 4.75rem);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.035em;
  text-align: center;
  text-wrap: balance;
}

/* Each word masks its own reveal, so the line assembles a word at a time.
   The padding gives descenders room inside the mask and is pulled back out
   again so the leading is unchanged. */
.statement__word {
  display: inline-block;
  overflow: hidden;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
  vertical-align: bottom;
}

.statement__word > span {
  display: block;
  will-change: transform;
}

/* The reference accents its final stop in brand colour. Ours stays ink: this
   section is transparent over the shared page wash, whose colour is being
   worked on, and brand yellow disappears against the current olive. Swap to
   var(--yellow) if the wash settles on something it reads against. */
.statement__text em {
  font-style: normal;
}

.about {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  align-items: start;
  column-gap: clamp(1rem, 2vw, 2rem);
  min-height: min(92rem, 118svh);
  padding: clamp(5rem, 8vw, 8rem) var(--gutter) clamp(5rem, 8vw, 8rem);
  overflow: hidden;
  background: transparent;
}

.about::after {
  position: absolute;
  top: clamp(8.7rem, 13vw, 12.4rem);
  bottom: clamp(5rem, 8vw, 8rem);
  left: 50%;
  width: 1px;
  content: "";
  background: rgba(9, 9, 9, 0.16);
}

.about__copy {
  position: relative;
  z-index: 1;
  grid-column: 1 / span 6;
  grid-row: 2;
  display: flex;
  min-width: 0;
  min-height: min(78svh, 48rem);
  flex-direction: column;
  padding: clamp(3.5rem, 6vw, 6rem) clamp(2rem, 5vw, 5rem) 0 0;
}

.about__collage {
  position: relative;
  z-index: 1;
  grid-column: 7 / -1;
  grid-row: 2;
  align-self: start;
  min-width: 0;
  min-height: min(78svh, 48rem);
  margin-top: clamp(2rem, 5vw, 5rem);
  padding-left: clamp(2rem, 5vw, 5rem);
}

.about__media {
  position: absolute;
  margin: 0;
  overflow: hidden;
  background: var(--mist);
}

.about__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.88) contrast(1.02);
}

.about__media--large {
  top: 2%;
  right: 0;
  width: 88%;
  height: 65%;
}

.about__media--small {
  bottom: 4%;
  left: 0;
  width: 54%;
  height: 38%;
  border: 0.45rem solid var(--page-wash-surface);
}

.about__place {
  position: absolute;
  bottom: 0;
  right: 0;
  width: max-content;
  margin: 0;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid currentColor;
  font-size: 0.66rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
}

.about__meta {
  position: relative;
  z-index: 1;
  grid-column: 1 / -1;
  grid-row: 1;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 0;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid rgba(9, 9, 9, 0.35);
  font-size: 0.66rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
}

.about h2 {
  margin: 0;
  font-family: "Gambetta", serif;
  font-size: clamp(4rem, 7vw, 8rem);
  font-weight: 400;
  line-height: 0.84;
  letter-spacing: -0.055em;
}

.about__lead {
  max-width: 29rem;
  margin: clamp(3rem, 6vw, 6rem) 0 0 clamp(1.5rem, 8vw, 8rem);
  color: #6c6962;
  font-size: clamp(1rem, 1.15vw, 1.2rem);
  line-height: 1.55;
}

.about__cta {
  display: flex;
  width: min(100%, 29rem);
  min-height: 4.5rem;
  align-items: center;
  justify-content: space-between;
  margin: clamp(2.5rem, 5vw, 4.5rem) 0 0 clamp(1.5rem, 8vw, 8rem);
  padding: 0.2rem 0;
  border-top: 1px solid rgba(9, 9, 9, 0.45);
  border-bottom: 1px solid rgba(9, 9, 9, 0.16);
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.15em;
}

.about__cta i {
  font-family: "Gambetta", serif;
  font-size: 1.5rem;
  font-style: normal;
  transition: translate 350ms cubic-bezier(0.22, 1, 0.36, 1);
}

.about__cta:hover i,
.about__cta:focus-visible i { translate: 0.3rem -0.3rem; }

.about__facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: auto 0 0;
  border-top: 1px solid rgba(9, 9, 9, 0.28);
}

.about__facts > div {
  display: flex;
  min-width: 0;
  min-height: 8.25rem;
  flex-direction: column;
  justify-content: space-between;
  padding: clamp(1rem, 1.7vw, 1.5rem) clamp(1rem, 1.7vw, 1.5rem) 0 0;
}

.about__facts > div + div {
  padding-left: clamp(1rem, 1.7vw, 1.5rem);
  border-left: 1px solid rgba(9, 9, 9, 0.14);
}

.about__facts dt {
  font-family: "Gambetta", serif;
  font-size: clamp(2rem, 3.4vw, 3.6rem);
  line-height: 1;
  letter-spacing: -0.03em;
}

.about__facts dt b {
  font-weight: 400;
}

.about__facts dd {
  margin: 0.9rem 0 0;
  font-size: 0.64rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}

/* Client logo cloud ----------------------------------------------------
   Static at rest; the cloud softens to reveal one editorial CTA on hover. */
/* The strip runs dark regardless of the page wash — it is the one band on the
   page that inverts on its own, which is what separates the client evidence
   from the editorial around it. Logos are drawn with `currentColor`, so
   setting `color` here flips every mark, rule and label with the background. */
.client-bar {
  position: relative;
  z-index: 4;
  overflow: hidden;
  color: var(--paper);
  background: var(--surface-dark);
  border-block: 1px solid rgba(246, 244, 239, 0.16);
}

.client-bar__inner {
  position: relative;
  width: min(72rem, calc(100% - (2 * var(--gutter))));
  margin: 0 auto;
  padding: clamp(3.5rem, 6vw, 5.5rem) 0;
}

.client-bar__label {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 0 0 clamp(2.5rem, 4vw, 3.75rem);
  font-size: 0.63rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: rgba(246, 244, 239, 0.58);
}

.client-bar__label i {
  flex: 1;
  height: 1px;
  background: rgba(246, 244, 239, 0.18);
  font-style: normal;
}

.client-bar__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: center;
  gap: clamp(2.5rem, 4vw, 4rem) clamp(3rem, 5vw, 5rem);
  width: min(62rem, 100%);
  margin: 0 auto;
  transition: opacity 500ms ease, filter 500ms ease;
}

.client-logo {
  display: flex;
  min-height: 2.25rem;
  align-items: center;
  color: var(--paper);
}

.client-logo svg {
  width: 100%;
  max-width: 11rem;
  height: 1.8rem;
  margin: auto;
  overflow: visible;
}

.client-logo path,
.client-logo rect {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}

.client-logo .client-logo__fill { fill: currentColor; stroke: none; }

.client-logo text {
  fill: currentColor;
  stroke: none;
  font-family: Arial, sans-serif;
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: 0.02em;
}

.client-logo:first-child text { font-size: 1.7rem; font-weight: 900; }
.client-logo:nth-child(2) text { font-family: "Gambetta", serif; font-size: 1.35rem; font-weight: 500; letter-spacing: 0.14em; }
.client-logo:nth-child(3) text { font-style: italic; }
.client-logo:nth-child(6) path { stroke-linecap: round; stroke-width: 3; }
.client-logo__serif { font-family: "Gambetta", serif !important; font-weight: 500 !important; }
.client-logo__sub { font-size: 0.42rem !important; font-weight: 500 !important; letter-spacing: 0.22em !important; }

.client-bar__hover {
  position: absolute;
  inset: 4.5rem 0 2rem;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transform: scale(0.95);
  transition: opacity 500ms ease, transform 500ms ease;
}

.client-bar__hover a {
  padding: 0 0 0.35rem;
  border-bottom: 1px solid rgba(246, 244, 239, 0.45);
  pointer-events: auto;
  font-size: 0.85rem;
}

.client-bar__hover a span { margin-left: 0.4rem; }

@media (hover: hover) and (pointer: fine) {
  .client-bar:hover .client-bar__grid,
  .client-bar:focus-within .client-bar__grid {
    opacity: 0.24;
    filter: blur(2px);
  }

  .client-bar:hover .client-bar__hover,
  .client-bar:focus-within .client-bar__hover {
    opacity: 1;
    transform: scale(1);
  }
}

.client-bar__mobile-cta { display: none; }

.contact-footer {
  position: relative;
  z-index: 4;
  /* Height-derived, not width-derived. Every dimension in this footer used to be a
     `vw` clamp, which encodes "how WIDE is the screen" and was then used to size
     things that have to fit VERTICALLY. On a 16:10 laptop (1587×882) that produced a
     1188px footer in an 882px viewport — correct on a tall monitor, far too big on a
     MacBook. The vertical measurements now read `svh`. */
  min-height: clamp(34rem, 76svh, 62rem);
  padding: clamp(1.75rem, 4.5svh, 6.5rem) 0 clamp(1.25rem, 2.5svh, 3rem);
  overflow: hidden;
  isolation: isolate;
  /* The barrier. The footer used to have NO ground of its own — it inherited
     the page's paper, so its floating film and panel read as two more pieces
     of page content and there was no edge anywhere saying "the page ends
     here". Giving it the brand navy draws that edge for free: on the paper
     inner pages the top of this element is now a hard horizontal boundary.

     The ground is `--surface-dark` while the film and panel inside it stay
     `--surface-dark-deep`, so they still read as layers ON the slab rather
     than dissolving into it. The hairline keeps the boundary visible on the
     homepage too, where the fixed navy wash behind is the same value. */
  color: var(--paper);
  background: var(--surface-dark);
  border-top: 1px solid rgba(246, 244, 239, 0.16);
}

.contact-footer__film {
  position: absolute;
  top: clamp(1.5rem, 3vw, 3rem);
  right: 3vw;
  width: min(69vw, 76rem);
  height: clamp(14rem, 32svh, 32rem);
  margin: 0;
  overflow: hidden;
  color: var(--paper);
  background: var(--surface-dark-deep);
  border: 1px solid rgba(246, 244, 239, 0.14);
}

/* Real footage, replacing the code-native route/signal field. AGENTS.md's
   stated background direction is real 3Heads footage rather than abstract
   shapes, and Ethos — the reference named for this slot — actually runs
   photography of real people, not motion graphics (it carries no video at
   all). `studio.mp4` is the closest match in the library: a panel event,
   bright room, real audience. */
.contact-footer__motion-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: var(--surface-dark-deep);
  object-fit: cover;
}



.contact-footer__panel {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  width: min(86vw, 94rem);
  margin-top: clamp(3rem, 17svh, 12rem);
  margin-left: max(4vw, calc((100vw - 106rem) / 2));
  color: var(--paper);
  background: var(--surface-dark-deep);
  box-shadow: 0 2rem 5rem rgba(9, 9, 9, 0.13);
}

.contact-footer__intro {
  grid-column: 1 / span 8;
  min-height: clamp(9rem, 23svh, 26rem);
  padding: clamp(1.35rem, 3.2svh, 4.25rem) clamp(1.75rem, 4vw, 4.25rem);
}

.contact-footer__kicker {
  margin: 0 0 clamp(3rem, 5vw, 4.75rem);
  color: rgba(246, 244, 239, 0.58);
  font-size: 0.61rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

.contact-footer__intro h2 {
  display: grid;
  margin: 0;
  font-family: "Gambetta", serif;
  /* `min(width-clamp, height-cap)`: the width clamp still sets the type on a wide
     screen, and the `svh` cap takes over the moment the viewport is short. */
  font-size: min(clamp(2rem, 5.9vw, 6.6rem), 6svh);
  font-weight: 400;
  line-height: 0.84;
  letter-spacing: -0.055em;
}

.contact-footer__intro h2 span:last-child { padding-left: 0.56em; }

.contact-footer__intro h2 em {
  color: #c7c879;
  font-style: normal;
}

.contact-footer__lead {
  max-width: 29rem;
  margin: clamp(1rem, 3svh, 3.5rem) 0 0 auto;
  color: rgba(246, 244, 239, 0.68);
  font-family: "Gambetta", serif;
  font-size: clamp(1.2rem, 1.52vw, 1.52rem);
  line-height: 1.36;
}

.contact-footer__action {
  position: relative;
  grid-column: 9 / -1;
  display: flex;
  min-width: 0;
  min-height: clamp(9rem, 23svh, 24rem);
  flex-direction: column;
  justify-content: space-between;
  align-items: stretch;
  padding: clamp(1.35rem, 3.2svh, 3rem) clamp(1.5rem, 3vw, 3rem);
  overflow: hidden;
  text-align: left;
  background: var(--surface-dark-raised);
  border-left: 1px solid rgba(246, 244, 239, 0.12);
}

.contact-footer__action > p {
  position: relative;
  z-index: 2;
  max-width: 16rem;
  margin: 0;
  color: rgba(246, 244, 239, 0.62);
  font-size: 0.7rem;
  line-height: 1.55;
  text-transform: uppercase;
  letter-spacing: 0.11em;
}

.contact-footer__button {
  position: relative;
  z-index: 2;
  display: grid;
  width: 100%;
  min-height: clamp(12rem, 16vw, 16rem);
  align-content: space-between;
  margin-top: auto;
  padding: 1.1rem 0 0.9rem;
  overflow: hidden;
  color: var(--paper);
  background: transparent;
  border-top: 1px solid rgba(199, 200, 121, 0.52);
  border-bottom: 1px solid rgba(246, 244, 239, 0.12);
  transition: background-color 500ms cubic-bezier(0.22, 1, 0.36, 1);
}

.contact-footer__button-label,
.contact-footer__button-note {
  position: relative;
  z-index: 2;
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.15em;
}

.contact-footer__button-note {
  display: flex;
  justify-content: space-between;
  color: rgba(246, 244, 239, 0.45);
  font-weight: 500;
}

.contact-footer__button-note i { color: var(--yellow); font-style: normal; }

.contact-footer__morph {
  position: absolute;
  inset: 2.6rem 0 2rem;
  display: grid;
  place-items: center;
}


.contact-footer__base {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1.5fr 1fr 1.35fr 1fr;
  gap: clamp(2rem, 4vw, 5rem);
  align-items: start;
  /* Flush with the panel. It used to overhang to the left and run into the screen
     edge, which read as the footer falling off the page — and because the overhang
     had no background of its own it also clipped the Explore column on the paper
     pages. Aligning it to the panel fixes both and needs no compensating background,
     though one is kept so the row reads as its own band. */
  width: 100%;
  margin-left: 0;
  background: var(--surface-dark-deep);
  /* The base deliberately overhangs the panel to bleed to the screen edge, so its
     left padding is the ONLY thing keeping the Explore column off that edge. It was
     ~32px, which read as clipped. */
  padding: clamp(1rem, 2.2svh, 3rem) clamp(2rem, 4vw, 5rem);
  border-top: 1px solid rgba(246, 244, 239, 0.14);
}

.contact-footer__mark {
  width: max-content;
  color: var(--yellow);
}

.contact-footer__mark img { width: clamp(3.6rem, 4.6vw, 5rem); height: auto; }

.contact-footer__nav,
.contact-footer__details,
.contact-footer__meta {
  display: grid;
  gap: 0.72rem;
  /* Was 0.67rem — 10.7px, below a comfortable reading size for real links. */
  font-size: 0.84rem;
  line-height: 1.5;
}

.contact-footer__nav > span,
.contact-footer__details > span:first-child {
  margin-bottom: 0.3rem;
  color: rgba(246, 244, 239, 0.42);
  text-transform: uppercase;
  letter-spacing: 0.16em;
}

.contact-footer__details > span:last-child { color: rgba(246, 244, 239, 0.45); }
.contact-footer__meta { justify-items: end; text-align: right; color: rgba(246, 244, 239, 0.5); }

.contact-footer__nav a,
.contact-footer__details a,
.contact-footer__meta a {
  width: max-content;
  max-width: 100%;
  transition: color 220ms ease;
}

.contact-footer__nav a:hover,
.contact-footer__details a:hover,
.contact-footer__meta a:hover { color: var(--yellow); }

.reel-dialog {
  width: 100vw;
  max-width: none;
  height: 100svh;
  max-height: none;
  margin: 0;
  padding: 0;
  color: #fff;
  background: #050a12;
  border: 0;
}

.reel-dialog::backdrop {
  background: #050a12;
}

.reel-dialog video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.reel-dialog__close {
  position: fixed;
  top: 1.2rem;
  right: 1.2rem;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  min-height: 44px;
  padding: 0 1rem;
  color: #fff;
  background: rgba(0, 0, 0, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.42);
  cursor: pointer;
}

.reel-dialog__close span { font-size: 1.4rem; }

@media (max-width: 62rem) {
  :root {
    --media-width: 68vw;
  }

  .primary-nav {
    position: absolute;
    top: calc(-1 * var(--nav-top));
    left: calc((var(--nav-width) - 100vw) / 2);
    display: grid;
    align-content: center;
    justify-items: start;
    gap: 0;
    width: 100vw;
    height: 100svh;
    padding: 6rem var(--gutter) 3rem;
    color: var(--ink);
    background: var(--yellow);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-1rem);
    transition: opacity 300ms ease, transform 400ms cubic-bezier(0.22, 1, 0.36, 1), visibility 300ms;
  }

  .primary-nav a {
    width: 100%;
    padding: 0.5rem 0;
    font-family: "Gambetta", serif;
    font-size: clamp(2.6rem, 9vw, 5.5rem);
    font-weight: 400;
    line-height: 1;
    letter-spacing: -0.04em;
  }

  .menu-open .primary-nav {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }

  .menu-toggle {
    position: relative;
    z-index: 2;
    display: grid;
    place-items: center;
    cursor: pointer;
  }

  .menu-toggle span { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.15em; }
  .menu-toggle i { display: none; }

  .compact-nav {
    grid-template-columns: 1fr;
    justify-items: center;
  }

  .compact-nav nav { display: none; }

  .menu-open .site-header[data-compact="true"] > .primary-nav {
    pointer-events: auto;
  }

  .hero-word { font-size: clamp(4.3rem, 10vw, 6rem); }
  .hero-qualifier { font-size: clamp(1.7rem, 3.6vw, 2.5rem); }
  .hero-meta--left { left: 3vw; }
  .hero-meta--right { right: 3vw; }

  .contact-footer { min-height: 58rem; }
  .contact-footer__film { right: 0; width: 92vw; height: 30rem; }
  .contact-footer__panel {
    width: calc(100vw - 2rem);
    margin-top: 12rem;
    margin-left: 1rem;
  }
  .contact-footer__intro { grid-column: 1 / span 7; }
  .contact-footer__action { grid-column: 8 / -1; }
  .contact-footer__base { grid-template-columns: 1.25fr 1fr 1.35fr 0.9fr; }
}

@media (max-width: 47.999rem) {
  /* Words already wrap on their own, so only the band height changes here. */
  .statement { min-height: auto; padding: 3.5rem var(--gutter); }

  :root {
    --media-top: 43vh;
    --media-width: calc(100vw - 2rem);
    --media-height: max(41vh, 17rem);
    /* Matches `setHeroProgress`'s mobile branch (`navHeight = mobile ? 72 : 105`),
       for the same reason as the desktop values above. */
    --nav-width: 100%;
    --nav-height: 72px;
  }

  .site-header { height: var(--nav-height); }
  .wordmark { font-size: 1.3rem; }

  .hero-track { height: 404svh; }
  .hero-sticky { min-height: 40rem; }

  /* Was clamp(3.5rem, 16.5vw, 5.4rem). The hero word here is "Branding", which
     fitted at that size — but the same element also has to hold "Management"
     if the second word is ever lengthened again. The word is
     right-anchored with `white-space: nowrap`, so a word that does not fit
     bleeds off the LEFT edge rather than wrapping. Sized so the longer of the
     two clears a 1rem gutter at full scale, down to 320. */
  .hero-word {
    font-size: clamp(2.6rem, 13.5vw, 4.6rem);
  }

  .hero-word--content {
    top: 15vh;
    left: 1rem;
  }

  .hero-word--branding {
    top: 27vh;
    right: 1rem;
  }

  .hero-qualifier {
    font-size: clamp(1.42rem, 7vw, 2.3rem);
  }

  .hero-qualifier--moves {
    top: 22.1vh;
    right: 1rem;
    left: auto;
  }

  .hero-qualifier--strategy {
    top: 35.1vh;
    left: 1rem;
  }

  .strategy-rule {
    top: 39.5vh;
    left: 1rem;
    width: 54vw;
  }

  .hero-media {
    min-height: 17rem;
  }

  .hero-meta {
    top: auto;
    bottom: 10vh;
    font-size: 0.56rem;
    letter-spacing: 0.13em;
  }

  .hero-meta--left { left: 1rem; }
  .hero-meta--right { display: none; }

  .scroll-cue { bottom: 1rem; }

  .service-kicker { top: 9.5vh; }
  .service-kicker::before,
  .service-kicker::after { width: 2rem; }

  .service-affix {
    font-size: 1.15rem;
    transition: none;
  }

  .service-affix--agency {
    top: 20vh;
    left: 1rem;
    transform: translateY(calc(((1 - var(--service-intro)) * 1.2rem) - (var(--mobile-service-phase) * 72vh)));
  }
  /* 82vh/94vh before Content Production gained a fifth row, which pushed the
     content list down into this block. Both tops move down 6vh and both phase
     translates grow by the same 6vh, so the resolved positions after
     `--mobile-service-phase` are unchanged (10vh affix / 23vh detail) — only
     the pre-phase stack gets its clearance back. */
  .service-affix--personal {
    top: 88vh;
    right: auto;
    left: 1rem;
    transform: translateY(calc(((1 - var(--service-intro)) * 1.2rem) - (var(--mobile-service-phase) * 78vh)));
  }

  .service-detail {
    width: calc(100vw - 2rem);
  }

  .service-detail--content {
    top: 23vh;
    left: 1rem;
    transform: translateY(calc(((1 - var(--service-intro)) * 1.2rem) - (var(--mobile-service-phase) * 72vh)));
  }

  .service-detail--branding {
    top: 100vh;
    right: auto;
    left: 1rem;
    transform: translateY(calc(((1 - var(--service-intro)) * 1.2rem) - (var(--mobile-service-phase) * 77vh)));
  }

  .service-detail > p {
    max-width: 16rem;
    font-size: 0.76rem;
  }

  .service-detail--branding > p {
    margin-left: 0;
    text-align: left;
  }

  .service-media-frame {
    aspect-ratio: 16 / 7;
    margin-top: 0.85rem;
  }

  .service-links {
    margin-top: 0.65rem;
  }

  .service-links a {
    min-height: 2.75rem;
    font-size: 1rem;
  }

  .service-link__description { font-size: 0.86rem; }

  .service-concept,
  .service-divider { display: none; }

  /* No room to rotate three slots: stack them and drop the pin. */
  .workstage { height: auto; padding: 5rem 0 6rem; }
  .workstage__sticky { position: static; }
  .workstage__layout { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; gap: 2rem; }
  .workstage__title { grid-column: 1; grid-row: auto; font-size: clamp(3.5rem, 16vw, 5rem); white-space: normal; }
  .workstage__intro { grid-column: 1; grid-row: auto; max-width: none; }
  .workstage__project.is-slot-a,
  .workstage__project.is-slot-b,
  .workstage__project.is-slot-c,
  .workstage__more { grid-column: 1; grid-row: auto; }
  .workstage__frame video { aspect-ratio: 4 / 3; }

  .about { grid-template-columns: minmax(0, 1fr); min-height: 0; padding: 4.5rem 1rem 3.5rem; }
  .about::after { display: none; }
  /* Two labels side by side both wrap to two lines at this width. */
  .about__meta { grid-column: 1; grid-row: 1; flex-direction: column; gap: 0.35rem; }
  .about__copy { grid-column: 1; grid-row: 2; min-height: 0; padding: 3.5rem 0 2rem; }
  .about__collage { grid-column: 1; grid-row: 3; position: relative; min-height: 28rem; margin-top: 2rem; padding-left: 0; }
  .about__media--large { width: 86%; height: 68%; }
  .about__media--small { width: 58%; height: 40%; bottom: 8%; }
  .about h2 { font-size: clamp(3.5rem, 15vw, 5rem); }
  .about__lead { margin-top: 2.25rem; margin-left: 0; }
  .about__cta { width: 100%; margin: 2.25rem 0 0; }
  .about__facts { grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 3.5rem; }
  .about__facts > div { min-height: 7rem; padding: 1rem 0.65rem 0 0; }
  .about__facts > div + div { padding-left: 0.65rem; }
  .about__facts dt { font-size: clamp(1.7rem, 8vw, 2.4rem); }
  .about__facts dd { font-size: 0.5rem; letter-spacing: 0.09em; }

  .client-bar {
    min-height: 0;
  }
  .client-bar__inner { width: calc(100% - 2rem); padding: 3.25rem 0 2.5rem; }
  .client-bar__label { align-items: flex-start; margin-bottom: 2.75rem; font-size: 0.56rem; letter-spacing: 0.15em; }
  .client-bar__label span { max-width: 11rem; }
  .client-bar__label i { margin-top: 0.4rem; }
  .client-bar__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.25rem 2rem; }
  .client-logo svg { height: 1.65rem; }
  .client-bar__hover { display: none; }
  .client-bar__mobile-cta { display: flex; align-items: center; justify-content: space-between; margin-top: 2.75rem; padding-top: 1rem; border-top: 1px solid rgba(8, 8, 8, 0.14); font-size: 0.82rem; }

  .contact-footer {
    min-height: 0;
    padding: 4rem 0 1rem;
    overflow: hidden;
  }

  .contact-footer__film {
    top: 2rem;
    right: 0;
    width: calc(100vw - 1rem);
    height: 22rem;
  }


  .contact-footer__panel {
    grid-template-columns: minmax(0, 1fr);
    width: calc(100vw - 2rem);
    margin-top: 13rem;
    margin-left: 1rem;
  }

  .contact-footer__intro,
  .contact-footer__action,
  .contact-footer__base { grid-column: 1; }

  .contact-footer__intro {
    min-height: 27rem;
    padding: 2rem 1.25rem 2.5rem;
  }

  .contact-footer__kicker { margin-bottom: 3.5rem; }
  .contact-footer__intro h2 { font-size: clamp(3.25rem, 15vw, 4.5rem); }
  .contact-footer__intro h2 span:last-child { padding-left: 0.24em; }
  .contact-footer__lead { max-width: 20rem; margin-top: 2.5rem; font-size: 1rem; }

  .contact-footer__action {
    min-height: 20rem;
    padding: 2rem 1.25rem;
    border-top: 1px solid rgba(246, 244, 239, 0.12);
    border-left: 0;
  }

  .contact-footer__button { width: 100%; min-height: 11rem; }

  .contact-footer__base {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2.5rem 1.25rem;
    width: calc(100% + 1rem);
    margin-left: 0;
    padding: 2rem 1.25rem 1.5rem;
  }

  .contact-footer__mark { grid-column: 1 / -1; }
  .contact-footer__details a { overflow-wrap: anywhere; }
  .contact-footer__meta { grid-column: 1 / -1; grid-template-columns: repeat(3, auto); justify-content: space-between; justify-items: start; gap: 0.75rem; text-align: left; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .hero-track { height: 100svh; }
  .hero-media { clip-path: inset(0) !important; }
  .hero-media__video { transform: none !important; }

  /* Show all three projects at once rather than rotating them. */
  .workstage { height: auto; padding-bottom: 6rem; }
  .workstage__sticky { position: static; }
  .workstage__title-fill { clip-path: inset(0) !important; }

  /* initCursor() bails under this preference, so the native cursor is still
     in play — make sure no stale field can sit over it. */
  .cursor-field { display: none !important; }
}

/* Dark wash -------------------------------------------------------------
   The page wash turns to ink once the work stage has finished its sequence
   and starts to leave, carrying everything that sits on it into a dark
   theme. Driven by `data-wash-dark` on the root from initWashTheme(), a
   threshold rather than a scrub so the type flips once and cleanly instead
   of passing through an unreadable mid-contrast band. Everything below is
   an override of a rule that assumes ink-on-light, kept in one block so the
   dependency is visible rather than scattered through the file. */
:root {
  --wash-fade: 700ms cubic-bezier(0.22, 1, 0.36, 1);
}

.page-wash__circle {
  transition: background-color var(--wash-fade);
}

.statement,
.about,
.statement__text,
.about h2,
.about__lead,
.about__facts dt,
.about__facts dd,
.about__meta,
.about__place {
  transition: color var(--wash-fade), border-color var(--wash-fade);
}

:root[data-wash-dark="true"] {
  --ws-dash: rgba(246, 244, 239, 0.34);
}

:root[data-wash-dark="true"] .about {
  color: var(--paper);
}

/* The statement inverts per-pixel instead of on a threshold.
   `difference` against pure white gives almost exactly the two brand values
   already in use: white over paper (#f6f4ef) resolves to rgb(9,11,16), which
   is our ink (#090909); white over the navy (#0d1826) resolves to a warm
   rgb(242,231,217), a shade off paper. So each letter flips at the moment the
   circle edge crosses it rather than when the page-wide flag flips at 0.55 —
   which is the whole point, since the circles reach the band diagonally and a
   single threshold made the left and right halves change together.

   Requires the wash to share this element's stacking context; see the note in
   index.html about why `.page-wash` was moved inside `main`. It also requires
   `main` to carry an opaque paper background — the "white over paper" half of
   the maths above is only true if paper is actually painted inside main's
   blending group. See the comment on `main`. */
.statement {
  color: #fff;
  mix-blend-mode: difference;
}

:root[data-wash-dark="true"] .about__lead {
  color: rgba(246, 244, 239, 0.62);
}

:root[data-wash-dark="true"] .about__meta,
:root[data-wash-dark="true"] .about__place {
  border-color: rgba(246, 244, 239, 0.4);
}

:root[data-wash-dark="true"] .about__facts {
  border-top-color: rgba(246, 244, 239, 0.32);
}

:root[data-wash-dark="true"] .about__facts > div + div {
  border-left-color: rgba(246, 244, 239, 0.16);
}

/* The work stage is still partly on screen while the wash turns, so its
   dashed slots and loading title have to survive the change too. */
:root[data-wash-dark="true"] .workstage {
  color: var(--paper);
}

/* Text inside `.workstage` cannot use `mix-blend-mode` — the section sets
   `isolation: isolate`, so it forms its own stacking context and the wash is
   not part of it. `.is-washed` is set per element by `applyWash()` in
   script.js, from the same circle geometry the wash itself uses, so each
   caption flips at the moment the navy actually reaches it rather than when
   the page-wide flag crosses 0.55. */
.workstage [data-wash-invert] {
  transition: color var(--wash-fade);
}

.workstage [data-wash-invert].is-washed,
.workstage [data-wash-invert].is-washed h3,
.workstage [data-wash-invert].is-washed span,
.workstage [data-wash-invert].is-washed a,
.workstage [data-wash-invert].is-washed p {
  color: var(--paper);
}

:root[data-wash-dark="true"] .workstage__title-ghost {
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(246, 244, 239, 0.32) 0,
    rgba(246, 244, 239, 0.32) 6px,
    transparent 6px,
    transparent 11px
  );
}

:root[data-wash-dark="true"] .workstage__title-fill {
  background-image: repeating-linear-gradient(
    to bottom,
    var(--yellow) 0,
    var(--yellow) 6px,
    transparent 6px,
    transparent 11px
  );
}

:root[data-wash-dark="true"] .workstage__more a {
  background: rgba(246, 244, 239, 0.1);
}

:root[data-wash-dark="true"] .workstage__more a:hover {
  color: var(--ink);
  background: var(--yellow);
}

@media (prefers-reduced-motion: reduce) {
  .page-wash__circle,
  .statement,
  .about { transition: none; }
}

/* Mark ------------------------------------------------------------------
   The real vector, separated into its ring plus seven axis-aligned bars.
   Bars are the actual geometry measured off the original traced paths, not
   a redrawing: a pixel diff against the original renders 0.12% differing,
   all of it edge anti-aliasing. That is what lets them be animated without
   breaking the rule in SPLASH_EXPERIMENT_01.md against approximations. */
.mark-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.wordmark__mark,
.compact-nav__mark-icon {
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  margin-right: 0.28em;
  color: var(--yellow);
  vertical-align: -0.16em;
}

/* The nav sits compact permanently, so this is the lockup that is actually
   seen. At 1.15em the ring and its bars silted up; 1.7em is the smallest
   size at which the three letterforms still read as letterforms. */
.compact-nav__mark-icon {
  width: 1.7em;
  height: 1.7em;
  margin-right: 0.4em;
  vertical-align: -0.46em;
}

/* Footer morph ----------------------------------------------------------
   The mark holds until the footer arrives, then its bars route into a
   diagonal arrow inside the ring and stay there. The ring never moves —
   it is the constant, and the letterforms are what reorganise. */
.contact-footer__morph .mark {
  display: block;
  width: clamp(5rem, 7.5vw, 7.5rem);
  height: clamp(5rem, 7.5vw, 7.5rem);
  overflow: visible;
  color: currentColor;
}

.mark__logo,
.mark__bar {
  fill: currentColor;
}

/* Deliberately no transform-box/transform-origin here. GSAP resolves the
   origin itself and bakes the compensation into the matrix it writes; adding
   a CSS origin on top applies that shift a second time and throws every bar
   about a ring-radius off target. */

/* Navbar to the Fairlight spec ------------------------------------------
   Re-measured off fairlightcreative.com at 1440x900. The earlier note here
   said the bar is "fully transparent with no glass, radius or shadow" — that
   was wrong. It measured only the outer `.nav_fixed` wrapper and missed its
   child. Fairlight runs TWO nested elements:

     .nav_fixed     1425x105 at (0,0), fixed, transparent, 22.45px side pad
     .nav_container 1197x75  at (114,30), rgba(0,0,0,0.8), blur(15px),
                    5.61px radius, 18.7px padding, flex/space-between

   So the bar is a contained dark-glass panel sitting 30px below the top of a
   transparent 105px frame, 84% of viewport width and centred. Codex's
   original dark-glass pill was closer to this than the "correction" that
   superseded it; the pill was the right idea at the wrong size.

   Links are Satoshi 400 at 14.97px, sentence case, normal tracking. Their CTA
   is Gambetta 500 at the same size in a red pill — we have no nav CTA and one
   is deliberately not invented here.

   Layout mechanism is intentionally NOT copied: Fairlight centres its brand
   with an absolutely-positioned wrapper spanning the container, because its
   flex row would otherwise push it off-centre. Our `.compact-nav` is a
   3-column grid that already centres the mark, so only the box metrics are
   taken. */
.site-header {
  padding: 0 clamp(1rem, 1.56vw, 1.4rem);
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
}

.site-header::before {
  display: none;
}

/* `.compact-nav` becomes Fairlight's `.nav_container` — the dark glass panel
   inside the transparent frame, not a full-bleed inset. 84% of the frame
   width (1197/1425), centred, 75px tall, 30px down from the top. */
.compact-nav {
  inset: auto;
  top: 30px;
  left: 50%;
  /* Fairlight's own panel is 84% of the frame; narrowed by 30% at the user's
     request (84 × 0.7). The 75px height and glass treatment stay on spec. */
  width: 58.8%;
  height: 75px;
  padding: 0 18.7px;
  color: var(--paper);
  /* Raised from Fairlight's 0.8 — at that value the page read through the bar
     enough to muddy the links. Still translucent, still blurred. */
  background: rgba(0, 0, 0, 0.92);
  border-radius: 5.61px;
  backdrop-filter: blur(15px);
  transform: translateX(-50%);
  transition: background var(--wash-fade), color var(--wash-fade);
}

/* Once the wash reaches the top of the viewport the bar inverts: a dark bar on
   a dark page loses its edge entirely, so it becomes the light element. Driven
   by `data-nav-dark`, NOT `data-wash-dark` — the bar sits where the right-hand
   circle lands first, so it has to flip before the page type does. */
:root[data-nav-dark="true"] .compact-nav {
  color: var(--ink);
  background: rgba(246, 244, 239, 0.94);
}

/* Brand yellow is near-invisible on the light bar, so the mark takes ink there
   and keeps its yellow on the dark one. */
:root[data-nav-dark="true"] .compact-nav__mark-icon {
  color: var(--ink);
}

/* Type is deliberately above Fairlight's 14.97px now — in a bar narrowed to
   58.8% their size read as undersized for the reduced width. Gap tightened to
   keep both link groups clear of the centred mark at the new width. */
.compact-nav nav {
  gap: clamp(0.9rem, 1.5vw, 1.4rem);
  font-family: "Satoshi", sans-serif;
  font-size: 1.06rem;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

/* The bar's links had no hover state at all — the only affordance was the
   custom cursor, which used to disappear against the dark bar entirely (see
   the `onDark` note in script.js). Resting links sit slightly back from the
   bar's own colour so that hover has somewhere to travel TO: hovering lifts
   them to full `currentColor` and wipes the rule in from the left. Same
   `::after` idiom and easing as `.primary-nav a`, so the two navs behave
   alike. Both states inherit `color`, so this works on the dark bar and on
   the inverted `data-nav-dark` one without a second rule. */
.compact-nav nav a {
  position: relative;
  padding: 0.35rem 0;
  color: inherit;
  opacity: 0.74;
  transition: opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.compact-nav nav a::after {
  position: absolute;
  right: 0;
  bottom: 0.1rem;
  left: 0;
  height: 1px;
  content: "";
  background: var(--yellow);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1);
}

.compact-nav nav a:hover,
.compact-nav nav a:focus-visible,
.compact-nav nav a[aria-current="page"] {
  opacity: 1;
}

.compact-nav nav a:hover::after,
.compact-nav nav a:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* The current page keeps a static rule so it reads as "you are here" rather
   than as a stuck hover. */
.compact-nav nav a[aria-current="page"]::after {
  transform: scaleX(1);
  transform-origin: left;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .compact-nav nav a,
  .compact-nav nav a::after {
    transition: none;
  }
}

/* Just the mark now — the typed "HEADS" beside it was a reconstruction of a
   lockup, not the lockup. Fairlight's brand is 30px tall in a 75px bar; ours
   is a circular mark, so the 30px HEIGHT is what transfers, not the width.
   With the text gone, the tracking and right margin that spaced it are dead. */
.compact-nav__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.935rem;
  letter-spacing: normal;
}

.compact-nav__mark-icon {
  width: 30px;
  height: 30px;
  margin-right: 0;
  vertical-align: middle;
}

/* Mobile frame is 72px (written by `setHeroProgress`, not by the `--nav-height`
   declared in CSS), so the desktop 30px+75px panel would overrun it. Kept
   narrower than Fairlight's 84% would allow at this width, and the 30px top
   offset is not worth its space on a small screen. */
@media (max-width: 47.999rem) {
  .compact-nav {
    top: 12px;
    width: 92%;
    height: 52px;
    padding: 0 14px;
  }

  .compact-nav nav { font-size: 0.95rem; }
  .compact-nav__mark-icon { width: 26px; height: 26px; }
}

/* The OUTER frame is transparent, so anything sitting directly on it (the
   full-width `.wordmark` and `.primary-nav`, used by the mobile menu and the
   accessibility path) takes the page's ink and inverts once the wash turns.
   `.compact-nav` is exempt and sets its own `--paper` above: it now carries
   80% black glass, so its type sits on dark in BOTH wash states and must not
   invert to ink with the rest of the page. */
.site-header {
  color: var(--ink);
  transition: color var(--wash-fade);
}

:root[data-wash-dark="true"] .site-header {
  color: var(--paper);
}

/* ===========================================================================
 * Inner pages (work.html, about.html)
 * ---------------------------------------------------------------------------
 * Built to the supplied references, saved as `Inspiration/work-inner-page-ref.png`
 * and `Inspiration/about-inner-page-ref.png`.
 *
 * These pages use the SAME `.site-header` as the homepage — one nav component
 * across the whole site. It needs no page-specific CSS: the header interpolates its
 * geometry from `--nav-compact` / `--nav-width` / `--nav-top` / `--nav-height`, and
 * those custom properties' `:root` values already describe the compact pill. The
 * homepage's hero loop only moves them away from that resting state and back; with
 * no hero, they simply stay there.
 *
 * An earlier version of these pages had a bespoke `.page-header` (light pill, gold
 * CTA) taken from the mockups. It was removed: two headers on one site read as a
 * defect. Do not reintroduce one — restyle `.site-header` if the nav must change.
 *
 * The static site has no templating, so the header, mark sprite and footer strip
 * markup are duplicated across the files — acceptable for a prototype, and the
 * thing to fix first if these pages become real.
 * ======================================================================== */

.page {
  background: var(--paper);
}

/* Top padding clears the navbar rather than guessing at it — the bar is
   full-width and flush to the top, so a fixed value would need updating every time
   its height changed. */
/* `--page-gap` and `--page-pad-bottom` exist so that the full-bleed bands which
   need to CANCEL them (see `.process--flush` and `.careers`) can reference the
   same value instead of restating the clamp. The gap changes at 48rem; a
   hardcoded negative margin was therefore right at one breakpoint and wrong at
   the other. */
.page-main {
  position: relative;
  z-index: 1;
  display: grid;
  --page-gap: clamp(2.5rem, 5vw, 4.5rem);
  --page-pad-bottom: clamp(3rem, 6vh, 5rem);
  gap: var(--page-gap);
  padding: calc(var(--nav-height) + clamp(1.5rem, 4vh, 3rem)) var(--gutter) var(--page-pad-bottom);
}

/* Shared page furniture --------------------------------------------------- */

.page-eyebrow {
  display: inline-block;
  margin: 0;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--ink);
  font-size: 0.66rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}

.page-link {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--yellow);
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.dot { color: var(--yellow); }


/* Work index -------------------------------------------------------------- */

/* One rule for every inner-page heading so they cannot drift apart — they are
   required to match, and two separate rules is how that requirement gets broken
   by the next person who tunes one of them.
   Gambetta 400 with tight tracking, the same treatment as the homepage's display
   type (`.hero-word`, `.workstage__title`, `.about h2`). Weight matters: only
   Gambetta 400 and 500 are loaded, so a 700 here would be a synthesised fake bold. */
.page-hero__title {
  max-width: 18ch;
  margin: 1.2rem 0 0;
  font-family: "Gambetta", serif;
  font-size: clamp(2.3rem, 6vw, 3.9rem);
  font-weight: 400;
  line-height: 1.03;
  letter-spacing: -0.035em;
}

/* REMOVED with the old Work header: `.work-index__hero`, `__intro`, `__title`,
   `__lead` and the `__emblem*` set — two overlapping yellow ellipses with
   "Stories / that / connect" set in the intersection. It was replaced by the
   shared `.page-hero`, which every inner page now uses. The emblem markup and
   these rules are recorded in AGENT_LOG.md if it is ever wanted back. */

.work-index__filters {
  display: grid;
  gap: 1rem;
}

.work-index__filter-set {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(0.9rem, 2.4vw, 2rem);
}

.work-index__filter-set button {
  position: relative;
  padding: 0 0 0.5rem;
  color: rgba(9, 9, 9, 0.55);
  background: none;
  border: 0;
  font-size: 0.66rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  cursor: pointer;
  transition: color 200ms ease;
}

.work-index__filter-set button:hover { color: var(--ink); }

.work-index__filter-set button.is-active { color: var(--ink); }

.work-index__filter-set button.is-active::after {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 2px;
  content: "";
  background: var(--yellow);
}

.work-index__all {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  justify-self: start;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

/* No panel behind the grid — the cards sit directly on paper, the same ground as the
   rest of the page. The grey field it used to have boxed the section off from
   everything above and below it. Padding goes with it; the gutter is `.page-main`'s
   job, and keeping both double-inset the grid. */
.work-index__grid {
  display: grid;
  gap: clamp(1rem, 1.8vw, 2rem);
}

.work-index__region,
.work-index__row {
  display: grid;
  gap: clamp(0.75rem, 1.4vw, 1.5rem);
  min-width: 0;
}

.work-index__region--left { align-content: start; }

.work-card {
  display: grid;
  align-content: start;
  gap: 0.85rem;
  min-width: 0;
}

.work-card__frame {
  display: block;
  overflow: hidden;
  aspect-ratio: 1 / 1;
  /* Only ever seen behind a loading image; now that the grid has no panel it needs to
     be close to paper or every cell flashes a grey block on first paint. */
  background: #e6e5e0;
}

.work-card__frame--case-study {
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(2.6rem, 4.5vw, 5rem) clamp(1.4rem, 3vw, 3rem);
  color: var(--ink);
}

.work-card__frame--case-study::after {
  position: absolute;
  inset: 0.8rem;
  content: "";
  border: 1px solid rgba(9, 9, 9, 0.16);
  pointer-events: none;
}

.work-card__frame--bebeto { background: #f4d8a7; }
.work-card__frame--champneys { background: #cfc4ae; }
.work-card__frame--water-junkie { background: #d8e8e6; }

.work-card__case-type,
.work-card__case-result {
  position: absolute;
  z-index: 1;
  font-size: 0.6rem;
  font-weight: 500;
  line-height: 1.35;
  text-transform: uppercase;
  letter-spacing: 0.13em;
}

.work-card__case-type {
  top: 1.25rem;
  left: 1.25rem;
}

.work-card__case-result {
  right: 1.25rem;
  bottom: 1.25rem;
  text-align: right;
}

/* The default frame is square. These two break that up so ten cards do not read as
   ten identical boxes — the job the stat cards used to do. Applied per card in the
   markup rather than by `:nth-child`, so reordering or filtering the grid cannot
   silently reshuffle which card is which shape. */
.work-card__frame--wide { aspect-ratio: 4 / 3; }
.work-card__frame--portrait { aspect-ratio: 4 / 5; }

.work-card__frame img,
.work-card__frame video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
}

.work-card__frame--case-study img {
  position: relative;
  z-index: 1;
  width: min(76%, 25rem);
  height: auto;
  object-fit: contain;
}

.work-card__frame--champneys img { width: min(84%, 22rem); }
.work-card__frame--water-junkie img { width: min(62%, 18rem); }

.work-card__frame:hover img,
.work-card__frame:hover video {
  transform: scale(1.04);
}

/* The stacked card in the reference: two stills sharing one frame. */
.work-card__frame--stack {
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: 2px;
}

.work-card__frame--stack img { height: 100%; }

/* Satoshi, not a monospace stack. These labels were set in `ui-monospace` to copy
   the reference's technical index look, but that made them the only third family on
   the site — and a system stack at that, so they rendered differently on every
   machine. The uppercase and the wide tracking carry most of that character
   anyway. */
.work-card__label {
  display: grid;
  margin: 0;
  gap: 0.2rem;
  font-family: "Satoshi", sans-serif;
  font-size: 0.68rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  line-height: 1.45;
}

.work-card__label span:last-child { color: rgba(9, 9, 9, 0.55); }

.work-card--tall .work-card__frame { aspect-ratio: auto; height: 100%; }

.work-card--tall {
  height: 100%;
  grid-template-rows: 1fr auto;
}

/* Filtering: the grid carries the active discipline, the cards match on their own. */
.work-index__grid[data-filter]:not([data-filter="all"]) .work-card { display: none; }
.work-index__grid[data-filter="content"] .work-card[data-category="content"],
.work-index__grid[data-filter="branding"] .work-card[data-category="branding"],
.work-index__grid[data-filter="campaign"] .work-card[data-category="campaign"],
.work-index__grid[data-filter="social"] .work-card[data-category="social"] {
  display: grid;
}

.work-index__empty {
  margin: 0;
  color: rgba(9, 9, 9, 0.55);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.back-to-top {
  display: inline-flex;
  align-items: center;
  justify-self: end;
  gap: 0.55rem;
  padding: 0.75rem 1.2rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

/* Page header ---------------------------------------------------------------
 * The shared header for the inner pages (About, Work, Contact). A copy column
 * on the left and a photographic stage on the right: one large image filling a
 * rounded panel, a caption set into its upper-left, a small inset lapping the
 * lower-right corner, and the mark as a badge on that inset's leading edge.
 *
 * Photographs, not video. The About page ran a looping `team.mp4` here; three
 * autoplaying videos across three pages is a lot of bandwidth for furniture
 * that sits above the fold, and stills hold the composition better because
 * the caption has a predictable area to sit on.
 *
 * Everything inside `.page-hero__stage` is positioned as a PERCENTAGE of the
 * stage, not in rem — the stage is an aspect-ratio box, so percentages keep the
 * caption, inset and badge in the same relationship at every width instead of
 * drifting apart as the panel scales.
 */

.page-hero {
  display: grid;
  align-items: center;
  gap: clamp(2rem, 4vw, 3.5rem);
}

/* `.page-hero__title` is declared with the other page heading above — see the
   note there for why they share one rule. */

.page-hero__lead {
  max-width: 34ch;
  margin: 1.1rem 0 1.5rem;
  color: rgba(9, 9, 9, 0.66);
  font-size: 0.9rem;
  line-height: 1.5;
}

.page-hero__stage {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  justify-self: end;
}

.page-hero__film {
  position: absolute;
  inset: 0;
  overflow: hidden;
  margin: 0;
  background: var(--surface-dark);
  border-radius: 14px;
}

.page-hero__film img,
.page-hero__film video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The caption is paper type sitting directly on a photograph, so it cannot
   depend on the photograph being dark. The scrim is a gradient weighted to the
   left half — where the words are — rather than a flat wash over the whole
   panel, which would dull the image everywhere to solve a problem that exists
   in one corner. */
.page-hero__film::after {
  position: absolute;
  inset: 0;
  content: "";
  background: linear-gradient(
    100deg,
    rgba(8, 14, 24, 0.82) 0%,
    rgba(8, 14, 24, 0.62) 32%,
    rgba(8, 14, 24, 0.12) 62%,
    rgba(8, 14, 24, 0.05) 100%
  );
}

/* Caption and plus are wrapped rather than positioned individually: the
   caption runs to a different number of lines on each page, so a `top` on the
   plus of its own put it through the last line on the longer ones. */
.page-hero__legend {
  position: absolute;
  top: 25%;
  left: 6.2%;
  z-index: 2;
  display: grid;
  justify-items: start;
  gap: clamp(0.7rem, 1.8vh, 1.5rem);
}

.page-hero__caption {
  margin: 0;
  color: var(--paper);
  font-family: "Gambetta", serif;
  font-size: clamp(1.25rem, 2.5vw, 2.45rem);
  font-weight: 400;
  line-height: 1.36;
  letter-spacing: -0.02em;
}

.page-hero__plus {
  color: var(--yellow);
  font-size: clamp(1rem, 1.5vw, 1.4rem);
  font-style: normal;
  line-height: 1;
}

/* Two faint arcs centred on the badge, the same device the reference uses to
   tie the inset back into the panel. Decorative and `aria-hidden`; drawn as
   one SVG so the dashes stay even as the stage scales. */
.page-hero__arc {
  position: absolute;
  top: 50%;
  right: 4%;
  z-index: 1;
  width: 46%;
  height: auto;
  aspect-ratio: 1;
  overflow: visible;
  transform: translateY(-50%);
  color: rgba(246, 244, 239, 0.32);
  pointer-events: none;
}

.page-hero__inset {
  position: absolute;
  right: 2.2%;
  bottom: 6%;
  z-index: 2;
  overflow: hidden;
  width: 35%;
  margin: 0;
  aspect-ratio: 3 / 2;
  background: var(--surface-dark-deep);
  border: 4px solid var(--paper);
  border-radius: 8px;
}

.page-hero__inset img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Sits ON the inset's leading edge, straddling it — hence `translate(-50%)`
   against the inset's own left position rather than a grid cell of its own. */
.page-hero__badge {
  position: absolute;
  right: calc(2.2% + 35% - 1.5%);
  bottom: 20%;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 9%;
  min-width: 2.4rem;
  aspect-ratio: 1;
  background: var(--paper);
  border-radius: 50%;
  box-shadow: 0 10px 30px rgba(9, 9, 9, 0.28);
  transform: translateX(50%);
}

.page-hero__badge img { width: 58%; height: auto; }

@media (max-width: 47.999rem) {
  .page-hero__stage { aspect-ratio: 4 / 3; }

  /* The badge's percentage width collapses to something smaller than the mark
     inside it on a narrow stage, and the inset needs to stay tappable-sized. */
  .page-hero__inset { width: 42%; }
  .page-hero__badge {
    right: calc(2.2% + 42% - 2%);
    width: 14%;
  }
}

/* About and Work use a tighter version of the shared hero. This trims the image
   stage by roughly eleven percent at both breakpoints while preserving every
   internal percentage relationship. */
.page-hero--compact .page-hero__stage { aspect-ratio: 3 / 2; }

@media (min-width: 48rem) {
  .page-hero--compact .page-hero__stage { aspect-ratio: 2 / 1; }
}

/* Story ------------------------------------------------------------------- */

.story {
  display: grid;
  gap: 1.6rem;
  padding: clamp(2rem, 4vw, 3.5rem) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.story__layout {
  display: grid;
  gap: clamp(1.5rem, 3vw, 3rem);
}

/* One continuous track behind the dots, with a yellow fill driven by
   `--story-progress` (0–1, written by the scroll scrub in initStoryTimeline).
   Previously this was a rule drawn per list item; a single track is what lets one
   bar fill smoothly across all four years instead of four separate segments. */
/* One size for the marker, shared by the rail dots and the travelling head, so the
   head passes THROUGH the dots instead of bulging past them. The track is centred on
   that same column by derivation rather than by a hand-tuned `left`. */
.story__rail-wrap {
  position: relative;
  --story-dot: 0.72rem;
  --story-line: 2px;
}

.story__track {
  position: absolute;
  top: 0.55rem;
  bottom: 0.55rem;
  left: calc((var(--story-dot) - var(--story-line)) / 2);
  width: var(--story-line);
  background: var(--line);
}

.story__track i {
  display: block;
  width: 100%;
  height: calc(var(--story-progress, 0) * 100%);
  background: var(--yellow);
}

.story__rail {
  display: grid;
  margin: 0;
  padding: 0;
  gap: 1.6rem;
  list-style: none;
}

.story__rail li {
  position: relative;
  padding-left: 2rem;
}

.story__rail button {
  display: grid;
  padding: 0;
  gap: 0.3rem;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
}

/* Sits above the track so the filling bar passes behind the dots, not over them. */
.story__rail button::before {
  position: absolute;
  z-index: 1;
  top: 0.32rem;
  left: 0;
  width: var(--story-dot);
  height: var(--story-dot);
  content: "";
  background: var(--paper);
  border: 1px solid rgba(9, 9, 9, 0.35);
  border-radius: 50%;
  transition: background 240ms ease, border-color 240ms ease, transform 240ms ease;
}

.story__rail button.is-active::before,
.story__rail button.is-passed::before {
  background: var(--yellow);
  border-color: var(--yellow);
}

/* Deliberately no `transform: scale()` on the active dot. It used to scale 1.25×,
   which meant the dot grew just as the head arrived on it and the two never matched.
   The yellow fill and the bold title already mark the active chapter. */

/* The chapter being read is at full strength; the rest step back so the rail shows
   where you are without needing a second indicator. */
.story__rail button {
  opacity: 0.45;
  transition: opacity 300ms ease;
}

.story__rail button.is-active { opacity: 1; }

.story__year {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
}

.story__rail-title {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.story__rail-copy {
  max-width: 30ch;
  color: rgba(9, 9, 9, 0.6);
  font-size: 0.76rem;
  line-height: 1.5;
}

.story__panel { display: grid; gap: 0.75rem; }

.story__panel-year {
  margin: 0;
  color: var(--yellow);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.story__panel-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.story__panel-copy {
  max-width: 52ch;
  margin: 0;
  color: rgba(9, 9, 9, 0.66);
  font-size: 0.84rem;
  line-height: 1.6;
}

.story__figure {
  overflow: hidden;
  margin: 0.4rem 0 0;
  aspect-ratio: 16 / 9;
  background: #d9d8d3;
}

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

/* Careers ------------------------------------------------------------------
 * Section three of the About page, and deliberately the quietest thing on it:
 * a copy column and three short notes, on the paper ground, between the story
 * and the dark Meet the team band. Kept small on purpose — there are no open
 * roles, so a full careers layout would over-promise.
 */
.careers {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3.25rem);
  /* The closing band. It follows the navy Meet the team and runs straight into
     the navy footer, so the last third of the page is one continuous dark field
     with no colour change to cross — the same ending as the Work page's
     Process. Full-bleed by the same negative-gutter trick as `.team`; the
     negative top margin cancels `.page-main`'s row gap so the two navy bands
     meet, and the bottom margin (shared with `.process--flush` above) cancels
     the page's bottom padding so the footer starts immediately. */
  margin-inline: calc(var(--gutter) * -1);
  margin-top: calc(var(--page-gap) * -1);
  padding: clamp(3rem, 8vh, 5.5rem) var(--gutter) clamp(3.5rem, 9vh, 6rem);
  color: var(--paper);
  background: var(--surface-dark);
  /* A hairline rather than nothing: `.team` above is the same navy, and without
     it the two bands read as one section. */
  border-top: 1px solid rgba(246, 244, 239, 0.14);
}

.careers .page-eyebrow { border-bottom-color: rgba(246, 244, 239, 0.5); }

.careers__copy { display: grid; justify-items: start; gap: 1rem; }

.careers__title {
  max-width: 20ch;
  margin: 0;
  font-family: "Gambetta", serif;
  font-size: clamp(1.95rem, 4.6vw, 3rem);
  font-weight: 400;
  line-height: 1.04;
  letter-spacing: -0.035em;
}

.careers__lead {
  max-width: 52ch;
  margin: 0;
  color: rgba(246, 244, 239, 0.7);
  font-size: 0.9rem;
  line-height: 1.6;
}

.careers__notes {
  display: grid;
  align-content: start;
  margin: 0;
  padding: 0;
  list-style: none;
}

.careers__notes li {
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr);
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.85rem 0;
  border-bottom: 1px solid rgba(246, 244, 239, 0.18);
  color: rgba(246, 244, 239, 0.78);
  font-size: 0.84rem;
  line-height: 1.5;
}

.careers__notes li:first-child { border-top: 1px solid rgba(246, 244, 239, 0.18); }

.careers__notes span {
  color: var(--yellow);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.12em;
}

@media (min-width: 48rem) {
  .careers {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    align-items: start;
  }
}

/* Team -------------------------------------------------------------------- */

/* Meet the team is a dark band in the same brand navy as `.process` and the
   footer panel, full-bleed by the same negative-gutter trick rather than by
   leaving `main`. */
.team {
  display: grid;
  gap: 1.6rem;
  margin-inline: calc(var(--gutter) * -1);
  padding: clamp(3.5rem, 9vh, 6.5rem) var(--gutter) clamp(3.5rem, 9vh, 6rem);
  color: var(--paper);
  background: var(--surface-dark);
}

.team .page-eyebrow {
  border-bottom-color: rgba(246, 244, 239, 0.5);
}

.team__head {
  display: grid;
  align-items: end;
  gap: 1.2rem;
}

.team__title {
  margin: 0;
  font-family: "Gambetta", serif;
  font-size: clamp(1.95rem, 4.6vw, 3rem);
  font-weight: 400;
  line-height: 1.04;
  letter-spacing: -0.035em;
}

.team__aside { display: grid; gap: 0.9rem; justify-items: start; }

.team__aside p {
  max-width: 34ch;
  margin: 0;
  color: rgba(246, 244, 239, 0.7);
  font-size: 0.84rem;
  line-height: 1.55;
}

.team__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  gap: clamp(0.75rem, 1.5vw, 1.5rem);
  list-style: none;
}

.team-card {
  display: grid;
  gap: 0.75rem;
  outline: 0;
}

.team-card:focus-visible {
  outline: 1px solid var(--yellow);
  outline-offset: 0.4rem;
}

.team-card__portrait {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background: var(--surface-dark-raised);
}

.team-card__portrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}

.team-card:hover .team-card__portrait img,
.team-card:focus .team-card__portrait img {
  transform: scale(1.018);
}

.team-card__bio {
  position: absolute;
  z-index: 1;
  right: 0;
  bottom: 0;
  left: 0;
  padding: clamp(0.75rem, 1.5vw, 1.05rem);
  color: var(--surface-dark);
  background: var(--yellow);
  transform: translateY(calc(100% + 1px));
  transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1);
}

.team-card__bio p {
  max-width: 34ch;
  margin: 0;
  font-size: clamp(0.64rem, 1vw, 0.78rem);
  font-weight: 650;
  line-height: 1.45;
  letter-spacing: -0.005em;
}

.team-card:hover .team-card__bio,
.team-card:focus .team-card__bio,
.team-card:focus-within .team-card__bio {
  transform: translateY(0);
}

.team-card__meta {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin: 0;
  gap: 0.75rem;
  text-transform: uppercase;
}

.team-card__identity {
  display: grid;
  gap: 0.22rem;
}

.team-card__identity strong {
  font-size: 0.72rem;
  line-height: 1.2;
  letter-spacing: 0.12em;
}

.team-card__identity small {
  color: rgba(246, 244, 239, 0.56);
  font-size: 0.58rem;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0.1em;
}

.team-card__meta i {
  color: var(--yellow);
  font-style: normal;
  font-size: 0.95rem;
  transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1);
}

.team-card:hover .team-card__meta i,
.team-card:focus .team-card__meta i,
.team-card:focus-within .team-card__meta i {
  transform: rotate(45deg);
}

@media (min-width: 48rem) {
  .page-main { --page-gap: clamp(3.5rem, 6vw, 6rem); }

  .work-index__filters {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }

  .work-index__grid { grid-template-columns: minmax(0, 1.32fr) minmax(0, 1fr); }
  .work-index__row--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .work-index__row--trio { grid-template-columns: 1.2fr 1fr 0.66fr; }

  /* The quad row is a child of `.work-index__grid`, which is two columns, so
     without the span it sat in column one and stacked its four cards into a
     single narrow strip. Both this and the column rule were lost in the CSS
     deletion logged on 2026-08-21 — restored, and deliberately uneven so the
     row reads as a composition rather than four equal boxes. */
  .work-index__row--quad {
    grid-column: 1 / -1;
    grid-template-columns: 1.25fr 0.8fr 1.05fr 0.9fr;
    align-items: start;
  }

  .page-hero { grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); }
  .story__layout { grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr); }
  .team__head { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .team__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Pinned story section, desktop only ---------------------------------------
 * The section is pinned for one viewport-height of scroll per chapter, so it has
 * to fit a viewport exactly — anything taller and the visitor is held in a
 * section whose bottom they cannot reach. The panel's image is the flexible row:
 * everything else is content-height and the figure absorbs whatever is left.
 * `initStoryTimeline` gates the pin on the same 48rem breakpoint. */
@media (min-width: 48rem) {
  .story {
    min-height: 100svh;
    align-content: center;
    padding: clamp(1.5rem, 3vh, 2.5rem) 0;
  }

  /* `start`, not `center`: the compact rail is about a third of the panel's height,
     so centring it left a large dead gap above and below and made the two columns
     look unrelated. Aligned to the top it reads against the year. */
  .story__layout { align-items: start; }

  .story__panel {
    max-height: 86svh;
    grid-template-rows: auto auto auto auto minmax(8rem, 1fr);
  }

  .story__figure {
    height: 100%;
    min-height: 0;
    aspect-ratio: auto;
  }
}

/* Process -------------------------------------------------------------------
 * Built to fairlightcreative.com's process section: a dark full-bleed band, a
 * centred wide-tracked eyebrow, a centred serif heading, and three cards that are
 * an image with a body beneath it.
 *
 * Full-bleed is done with a negative inline margin against `.page-main`'s gutter
 * rather than by moving the section out of `main` — it has a heading and belongs in
 * the document outline where it is.
 */
.process {
  margin-inline: calc(var(--gutter) * -1);
  padding: clamp(3.5rem, 9vh, 6.5rem) var(--gutter) clamp(3.5rem, 9vh, 6rem);
  color: var(--paper);
  /* Was `var(--ink)` — flat black. The dark surfaces elsewhere on the site are
     the brand navy (the homepage wash, the footer panel), so a black band here
     read as a different system. `--surface-dark` is the same navy those use. */
  background: var(--surface-dark);
  display: grid;
  justify-items: center;
  gap: clamp(1.75rem, 4vw, 3rem);
}

.process__eyebrow {
  margin: 0;
  color: rgba(246, 244, 239, 0.66);
  font-size: 0.68rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.24em;
}

.process__title {
  max-width: 20ch;
  margin: 0;
  font-family: "Gambetta", serif;
  font-size: clamp(1.9rem, 4.4vw, 3.1rem);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.03em;
  text-align: center;
  text-wrap: balance;
}

.process__list {
  display: grid;
  width: 100%;
  margin: 0;
  padding: 0;
  gap: clamp(1rem, 2vw, 1.6rem);
  list-style: none;
}

/* `1fr auto` with the body's copy pushed down by `margin-top: auto` is what keeps
   the three cards level and their paragraphs on a shared baseline, however uneven
   the titles and copy lengths are. */
/* Card and media placeholder were #151515 / #202020, neutral greys left from
   the flat-black band. Against the navy they read as a warm cast rather than
   as depth, so both move into the navy family. `--surface-dark-raised` is the
   token for a surface sitting *above* the ground, which is what a card is. */
.process-card {
  display: grid;
  grid-template-rows: auto 1fr;
  overflow: hidden;
  background: var(--surface-dark-raised);
  border-radius: 12px;
}

.process-card__media {
  margin: 0;
  overflow: hidden;
  aspect-ratio: 16 / 11;
  background: #1b2c42;
}

.process-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.process-card__body {
  display: grid;
  align-content: start;
  gap: 0.6rem;
  padding: clamp(1.25rem, 2vw, 1.75rem);
}

.process-card__body h3 {
  margin: 0;
  font-family: "Gambetta", serif;
  font-size: clamp(1.35rem, 2.2vw, 1.75rem);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.03em;
}

.process-card__body p {
  max-width: 34ch;
  margin: auto 0 0;
  color: rgba(246, 244, 239, 0.68);
  font-size: 0.84rem;
  line-height: 1.55;
}

/* Light variant — the Contact page's "What happens next". Same band, on paper.
   Work's Process is the dark one; there the band is a full stop at the end of
   a light page, whereas on Contact it follows the navy contact panel and a
   second dark band in a row read as one long slab. Every value here is the
   paper-ground counterpart of the rule above it, nothing else changes. */
/* Flush variant — the Work page, where the navy band is the last thing in
   `main` and should run straight into the navy footer. The negative margin
   cancels `.page-main`'s bottom padding; keep the two values in step. The
   footer's own hairline is dropped in that case, since a seam between two
   identical navies is the gap the user asked to remove, just thinner. */
.process--flush,
.careers {
  margin-bottom: calc(var(--page-pad-bottom) * -1);
}

main:has(.process--flush) + .contact-footer,
main:has(.careers) + .contact-footer {
  border-top: 0;
}

.process--light {
  color: var(--ink);
  background: transparent;
}

.process--light .process__eyebrow { color: rgba(9, 9, 9, 0.6); }

.process--light .process-card {
  background: var(--paper);
  border: 1px solid var(--line);
}

.process--light .process-card__media { background: #e6e5e0; }

.process--light .process-card__body p { color: rgba(9, 9, 9, 0.66); }

@media (min-width: 48rem) {
  .process__list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.story__rail-meta {
  color: rgba(9, 9, 9, 0.5);
  font-size: 0.72rem;
}

.story__panel-meta {
  margin: 0;
  color: rgba(9, 9, 9, 0.5);
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

/* The year becomes the panel's display element rather than a small label — it is
   the one thing that changes on every step, so it is what should move. */
.story__panel-year {
  font-family: "Gambetta", serif;
  font-size: clamp(2.6rem, 6vw, 4.4rem);
  font-weight: 400;
  line-height: 0.9;
  letter-spacing: -0.04em;
  color: var(--ink);
}


.story__panel-title {
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  letter-spacing: 0.06em;
}

@media (min-width: 48rem) {
  .story__rail { gap: 0.15rem; }

  .story__rail li::before { display: none; }

  /* Compact rows: the year sets the rhythm, the title sits beside it. */
  .story__rail button {
    grid-template-columns: 3.6rem minmax(0, 1fr);
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.6rem 0;
  }

  .story__rail button::before { top: 0.95rem; }

  .story__year { font-size: 0.85rem; font-weight: 700; }

  .story__rail-title {
    font-size: 0.78rem;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
  }

  .story__rail button.is-active .story__year { color: var(--yellow); }

  .story__rail button.is-active .story__rail-title { font-weight: 700; }

  /* The rail is an index on desktop, not a second copy of the panel. The copy
     and meta stay in the markup — `initStoryTimeline` reads them as the single
     source of truth for the panel — but they are not shown twice. */
  .story__rail-copy,
  .story__rail-meta { display: none; }

  /* year / title / meta / copy / figure. The figure is the flexible row and
     now takes everything the thumbnail strip and the arrow row used to. */
  .story__panel { grid-template-rows: auto auto auto auto minmax(8rem, 1fr); }
}

/* Nudges the rail's first row onto the year's optical baseline. */
@media (min-width: 48rem) {
  .story__rail-wrap { padding-top: clamp(1rem, 2.5vh, 2.25rem); }
}

/* Timeline line, made legible again -----------------------------------------
 * Compacting the rail to six rows shrank the track to ~215px, so the yellow fill
 * had barely any distance to travel and the movement stopped reading. Three
 * changes: the rows are spread so the track spans most of the panel's height, the
 * line is thicker, and the fill carries a bright head at its leading edge so the
 * eye has something to follow rather than watching a bar get imperceptibly longer.
 */
@media (min-width: 48rem) {
  .story__rail { gap: clamp(1.4rem, 5.6vh, 4.2rem); }

  .story__rail-wrap { --story-line: 3px; }

  .story__track i {
    position: relative;
    border-radius: 2px;
  }

  /* No travelling head. There was a glowing dot riding the end of the fill;
     the user asked for a plain line that simply lights each dot as it reaches
     it, so the leading edge is now unmarked and the dots do the work. */
}

/* Contact page ---------------------------------------------------------------
 * Built to fairlightcreative.com's contact section, in the brand navy rather than
 * black: a dark full-bleed split, wide-tracked eyebrow, a two-line serif heading
 * (solid then grey italic), labelled details in the lower left, and a panel filling
 * the right edge.
 *
 * Sized off `svh` throughout for the same reason as the footer — this is a
 * one-screen composition, and a `vw`-derived height is only correct on the one
 * screen shape it was tuned on.
 */
/* The contact panel is the page opening, including the area behind the fixed
   navbar. Its own top padding clears the navigation without exposing the
   paper page background, and the light process section begins after at least
   one complete viewport of navy. */
.page-main--contact {
  padding-top: 0;
  gap: 0;
}

.contact-panel {
  display: grid;
  align-content: start;
  min-height: 100svh;
  /* Full-bleed against `.page-main`'s gutter. */
  margin-inline: calc(var(--gutter) * -1);
  padding: calc(var(--nav-height) + clamp(2rem, 6svh, 5rem)) var(--gutter) clamp(2rem, 5svh, 4.5rem);
  gap: clamp(1.75rem, 4svh, 3rem);
  color: var(--paper);
  background: var(--surface-dark);
}

.contact-panel__copy {
  display: grid;
  align-content: space-between;
  gap: clamp(1.75rem, 5svh, 4.5rem);
}

.contact-panel__eyebrow {
  margin: 0;
  color: rgba(246, 244, 239, 0.6);
  font-size: 0.68rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.24em;
}

/* Two lines: the statement solid, the invitation grey and italic beneath it. */
.contact-panel__title {
  display: grid;
  margin: 0;
  font-family: "Gambetta", serif;
  font-size: min(clamp(2.1rem, 5.4vw, 5.8rem), 8.5svh);
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: -0.035em;
}

.contact-panel__title em {
  color: rgba(246, 244, 239, 0.42);
  font-style: italic;
}

.contact-panel__details {
  display: grid;
  margin: 0;
  gap: clamp(1rem, 2.6svh, 2rem);
}

.contact-panel__details dt {
  margin: 0 0 0.35rem;
  color: rgba(246, 244, 239, 0.5);
  font-size: 0.64rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

.contact-panel__details dd {
  margin: 0;
  font-size: clamp(0.95rem, 1.2vw, 1.15rem);
  line-height: 1.45;
}

.contact-panel__details a {
  color: inherit;
  border-bottom: 1px solid transparent;
  transition: color 200ms ease, border-color 200ms ease;
}

.contact-panel__details a:hover,
.contact-panel__details a:focus-visible {
  color: var(--yellow);
  border-bottom-color: var(--yellow);
}

.contact-panel__details i { font-style: normal; }

.contact-panel__media {
  position: relative;
  overflow: hidden;
  margin: 0;
  aspect-ratio: 4 / 3;
  background: var(--surface-dark-deep);
  border-radius: 10px;
}

.contact-panel__media video,
.contact-panel__media iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}

/* Fairlight uses a pre-rendered dark monochrome map. OpenStreetMap's embed only
   provides light Mapnik tiles here, so invert their luminance after removing
   colour: pale land becomes charcoal, mid-tone roads remain readable and dark
   labels become light. Interaction lifts the contrast slightly but never
   flashes back to the coloured tile set. Filters on an iframe are safe here —
   no text of ours is inside it. */
.contact-panel__media--map iframe {
  filter: grayscale(1) invert(0.82) contrast(1.08) brightness(0.92);
  transition: filter 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

.contact-panel__media--map:hover iframe,
.contact-panel__media--map:focus-within iframe {
  filter: grayscale(1) invert(0.82) contrast(1.16) brightness(1);
}

/* Top-left, not bottom-left: OpenStreetMap renders its attribution along the
   bottom edge and that has to stay legible — it is the licence condition, not
   decoration. */
.contact-panel__media--map figcaption {
  position: absolute;
  top: 0;
  left: 0;
  padding: 0.5rem 0.85rem;
  color: var(--paper);
  background: rgba(10, 18, 32, 0.86);
  border-bottom-right-radius: 8px;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  pointer-events: none;
}

/* REMOVED: `.contact-next*` — the ruled three-column list that used to sit
   here. Replaced by the Work page's `.process` band at the user's request, so
   the two "how we work" sections on the site are one component. The contact
   copy moved across unchanged; only the presentation changed. */

/* Ordered variant of the process card. Work's three steps are a spine, not a
   sequence, so they carry no numbers; the contact page's are literally 01→03.
   Scoped through `__body` deliberately: `.process-card__body p` sets the card's
   body colour and would otherwise out-specify a bare `.process-card__index`. */
.process-card__body .process-card__index {
  margin: 0;
  color: var(--yellow);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
}

@media (min-width: 48rem) {
  /* The split. The panel is one complete screen, and the media column
     bleeds off the right edge exactly as the reference's map does — hence the
     negative right margin cancelling the section's own gutter. */
  .contact-panel {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
    align-content: stretch;
    align-items: stretch;
    min-height: 100svh;
    padding-right: 0;
    gap: clamp(2rem, 4vw, 4rem);
  }

  .contact-panel__media {
    aspect-ratio: auto;
    height: 100%;
    border-radius: 10px 0 0 10px;
  }
}
