/* ==========================================================================
   Humans Remain — site styles
   Tokens come from _ds (the design system). Nothing here invents a value that
   the design system already defines; everything references var(--token).
   ========================================================================== */

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

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

* {
  margin: 0;
  padding: 0;
}

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

/* width/height attributes on <img> are worth keeping — they reserve space and
   prevent layout shift — but they also fix the rendered height, which stops
   aspect-ratio from doing anything. Letting height be auto restores it. */
img {
  height: auto;
}

/* --- Site-level tokens -------------------------------------------------
   Fluid type and layout values derived from the design system's fixed sizes.
   The _ds values are the ceiling; clamp() scales down from there.
   ---------------------------------------------------------------------- */

:root {
  --accent: var(--hr-red);

  --page-max: 1280px;
  --gutter: clamp(18px, 4vw, 56px);

  --type-display-lg: clamp(48px, 11vw, 180px);
  --type-display-md: clamp(30px, 3.8vw, 62px);
  --type-display-sm: clamp(26px, 3vw, 44px);
  --type-body: clamp(16px, 1.15vw, 19px);
  --type-mono: 11.5px;
  --type-mono-lg: 12px;

  --rule: 3px;
  --logo-gap: clamp(48px, 6vw, 104px);

  /* Tracking for project titles. Ozik is already very tight by design, so the
     display face needs less negative tracking than a normal grotesque would.
     One token so every section title moves together. */
  --title-tracking: -0.008em;

  /* One size for every project blurb, so 01 and 02–04 don't set at different
     scales. Matches what 01 was already using. */
  --project-body: clamp(16px, 1.25vw, 20px);

  /* Optical size of the mascot artwork itself. The header uses the untrimmed
     source in a larger box (the file is ~44% artwork, rest transparent), so
     this figure is what that renders to — the footer uses the trimmed mark and
     matches it directly. */
  --mascot-size: clamp(48px, 4.8vw, 68px);
}

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

html {
  scroll-behavior: smooth;
}

body {
  background: var(--surface-default);
  color: var(--text-default);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--type-body);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

.page {
  max-width: var(--page-max);
  margin: 0 auto;
}

a {
  color: inherit;
}

a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* --- Shared type ------------------------------------------------------- */

.mono {
  font-family: var(--font-mono);
  font-size: var(--type-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.4;
}

.display {
  font-family: var(--font-display);
  font-weight: var(--display-weight-black);
  text-transform: uppercase;
  letter-spacing: var(--display-letter-spacing);
  line-height: 0.86;
}

.rule {
  height: var(--rule);
  background: var(--hr-black);
}

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

.site-header {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 10px var(--space-5);
  padding-inline: var(--gutter);
}

.site-header__tagline {
  flex: 1 1 0;
  min-width: 200px;
  margin-bottom: var(--space-3);
}

.site-header__tagline .slash {
  color: var(--accent);
}

.site-header__mascot {
  position: relative;
  flex: 0 0 auto;
  width: clamp(110px, 11vw, 156px);
  z-index: 3;
}

/* Decorative only. It sits close to the nav on narrow screens, so it must not
   intercept taps meant for the links beside it. */
.site-header__mascot img {
  width: 100%;
  margin-bottom: -26px;
  pointer-events: none;
}

.site-nav {
  flex: 1 1 0;
  display: flex;
  justify-content: flex-end;
  gap: clamp(18px, 3vw, 44px);
  margin-bottom: var(--space-3);
}

.site-nav a,
.site-nav__pending {
  font-family: var(--font-mono);
  font-size: var(--type-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  transition: color 120ms linear;
}

.site-nav a:hover {
  color: var(--accent);
}

/* Not yet a link: rendered as a span so it isn't focusable or clickable, and
   with the default cursor so it doesn't invite a click that goes nowhere.
   Swap for an <a href="/about"> when the page exists. */
.site-nav__pending {
  cursor: default;
}

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

.hero {
  padding: clamp(22px, 4vw, 54px) var(--gutter) 0;
}

.hero__wordmark {
  width: 100%;
  height: auto;
}

.hero__lede {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(24px, 4vw, 64px);
  padding-block: clamp(26px, 3.4vw, 44px) clamp(24px, 3vw, 40px);
}

/* Sized so the longest forced line fits the column — the <br> in the markup
   decides the break, keeping each half of the antithesis on its own line. */
.hero__statement {
  flex: 1 1 420px;
  max-width: 24ch;
  font-size: clamp(26px, 3.1vw, 50px);
  letter-spacing: -0.014em;
  color: var(--accent);
}

.hero__intro {
  flex: 1 1 340px;
  max-width: 46ch;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 20px;
}

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

.btn {
  font-family: var(--font-mono);
  font-size: var(--type-mono-lg);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background 120ms linear, color 120ms linear,
    border-color 120ms linear;
}

.btn--solid {
  background: var(--hr-black);
  color: var(--surface-default);
  padding: 14px 22px;
}

.btn--solid:hover {
  background: var(--accent);
}

.btn--underline {
  padding: 14px 2px;
  border-bottom: 2px solid var(--hr-black);
}

.btn--underline:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* --- Video ------------------------------------------------------------
   A .video is a fixed-ratio box. The poster sits underneath; the player is
   injected by site.js only when the box approaches the viewport, and removed
   when it is far away. That keeps at most a couple of players alive at once.
   --------------------------------------------------------------------- */

.video {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--hr-black);
  overflow: hidden;
}

/* Box ratios must match the source footage exactly. Vimeo's player letterboxes
   rather than cropping, so any mismatch leaves bars — and the poster showing
   through them. */
.video--9x16 {
  aspect-ratio: 9 / 16;
}

.video--4x3 {
  aspect-ratio: 4 / 3;
}

.video__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--hr-black);
  opacity: 0;
  transition: opacity 300ms linear;
}

.video__poster.is-loaded {
  opacity: 1;
}

/* The poster stays put underneath the player rather than fading out. Because
   the box ratio matches the footage, the player covers it exactly — and if
   autoplay is blocked (Low Power Mode, data saver), the still frame remains
   instead of a black rectangle. */

.video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  opacity: 0;
  transition: opacity 400ms linear;
  pointer-events: none;
}

.video iframe.is-playing {
  opacity: 1;
}

/* --- Watch affordance + lightbox ---------------------------------------
   The grid players are muted ambient loops. Clicking one opens the same film
   full size, with sound and controls, in an overlay — so the page keeps its
   quiet texture and the work is still watchable.
   ---------------------------------------------------------------------- */

.video[data-vimeo] {
  cursor: pointer;
}

.video__watch {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 8px 12px;
  background: var(--hr-black);
  color: var(--surface-default);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 160ms linear, transform 160ms linear;
  pointer-events: none;
}

.video[data-vimeo]:hover .video__watch,
.video[data-vimeo]:focus-visible .video__watch {
  opacity: 1;
  transform: none;
}

.video__watch::before {
  content: '';
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 4px 0 4px 7px;
  border-color: transparent transparent transparent currentColor;
}

/* Touch devices don't get the cue at all. Tapping a video is the intuitive
   gesture there, and the whole frame is already the hit area — a label just
   adds clutter to something people will try anyway. */
@media (hover: none) {
  .video__watch {
    display: none;
  }
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 4vw, 56px);
  background: rgba(0, 0, 0, 0.94);
}

.lightbox.is-open {
  display: flex;
}

.lightbox__stage {
  position: relative;
  width: min(100%, 1120px);
  aspect-ratio: 16 / 9;
  background: var(--hr-black);
}

.lightbox__stage--9x16 {
  aspect-ratio: 9 / 16;
  width: auto;
  height: min(100%, 900px);
}

.lightbox__stage--4x3 {
  aspect-ratio: 4 / 3;
}

.lightbox__stage iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.lightbox__close {
  position: absolute;
  top: clamp(12px, 2vw, 24px);
  right: clamp(12px, 2vw, 24px);
  z-index: 2;
  padding: 10px 16px;
  border: 0;
  background: var(--surface-default);
  color: var(--hr-black);
  font-family: var(--font-mono);
  font-size: var(--type-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 120ms linear, color 120ms linear;
}

.lightbox__close:hover {
  background: var(--accent);
  color: var(--surface-default);
}

/* --- Marquee ----------------------------------------------------------- */

/* The logo row sits between two rules — the collaborators heading above and
   the selected-work rule below. --band-pad is used on both sides so the row is
   optically centred in that band rather than drifting toward the top. */
.collaborators {
  padding-top: clamp(26px, 3.4vw, 52px);
  --band-pad: clamp(14px, 1.6vw, 26px);
}

.collaborators__head {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 28px);
  padding-inline: var(--gutter);
}

.collaborators__head h2 {
  font-family: var(--font-mono);
  font-size: var(--type-mono-lg);
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
}

.collaborators__head .rule {
  flex: 1 1 auto;
}

/* Inset to the same column edges as the rules above and below, so the logos
   enter and leave on the same line everything else is aligned to. */
.marquee {
  overflow: hidden;
  padding-block: var(--band-pad);
  margin-inline: var(--gutter);
}

/* The track animates -50%, so its two halves must be byte-identical in width.
   A gap on the track itself would sit between the halves and make -50% land
   half a gap out of register — the loop would visibly jump. Instead each group
   carries its own trailing space as padding-right, so a group is self-contained
   and -50% lands exactly one half onward. site.js clones groups until the
   track is at least twice the container width, otherwise the strip runs out of
   logos before the loop comes round. */
.marquee__track {
  display: flex;
  align-items: center;
  width: max-content;
  animation: marquee 38s linear infinite;
}

.marquee__group {
  display: flex;
  align-items: center;
  gap: var(--logo-gap);
  padding-right: var(--logo-gap);
}

/* All logos share one optical height so the row reads as a single baseline,
   regardless of each mark's own proportions. */
.logo {
  flex: 0 0 auto;
  height: 56px;
  background: var(--hr-black);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  transition: background 120ms steps(1);
}

.logo:hover {
  background: var(--accent);
}

@keyframes marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

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

/* No padding here: the marquee's own bottom padding provides the space above
   this rule, which is what keeps the logo row equidistant between the two. */
.work {
  padding-top: 0;
}

/* The rule is this element's top border, so it spans the element's full width.
   Using margin rather than padding for the gutter keeps that border inside the
   same content box as the collaborators rule above — otherwise it runs wider
   on both sides and the two don't line up. */
.work__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-5);
  margin-inline: var(--gutter);
  border-top: var(--rule) solid var(--hr-black);
  padding-top: var(--space-4);
}

.work__head span {
  font-family: var(--font-mono);
  font-size: var(--type-mono-lg);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.project {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(24px, 4vw, 56px);
  /* Block padding halved: the gap between projects is this value doubled where
     two sections meet, so it was reading as a break rather than an interval. */
  padding: clamp(16px, 2.5vw, 40px) var(--gutter);
}

@media (min-width: 900px) {
  .project {
    grid-template-columns: 1.55fr 1fr;
    align-items: start;
  }
}

/* Three vertical frames in a row. The middle one drops slightly so the row
   reads as a considered arrangement rather than a strip of thumbnails. */
.project__media {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.project__media .video {
  aspect-ratio: 9 / 16;
}

@media (min-width: 700px) {
  .project__media .video:nth-child(2) {
    margin-top: 11%;
  }
}

.project__number {
  font-family: var(--font-display);
  font-weight: var(--display-weight-black);
  font-size: clamp(40px, 4.4vw, 86px);
  line-height: 0.8;
  color: var(--accent);
}

.project__client {
  font-family: var(--font-mono);
  font-size: clamp(10.5px, 0.72vw, 12px);
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-top: var(--space-5);
}

/* Mobile minimums are raised across the section titles: at phone widths the
   display face was setting small enough to read as body type rather than as a
   heading. The maximums are unchanged, so desktop is unaffected. */
.project__title {
  font-size: clamp(42px, 3.5vw, 64px);
  letter-spacing: var(--title-tracking);
  margin-top: var(--space-3);
}

/* Reference sets the role line in the body colour, not the accent — the red is
   reserved for the number, so it stays the loudest thing in the block. */
.project__role {
  font-family: var(--font-mono);
  font-size: clamp(10.5px, 0.72vw, 12px);
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-top: var(--space-3);
}

/* Measure caps exist to stop lines getting too long on wide screens. On a
   phone the column is already narrower than the cap, so applying it there just
   leaves the copy short of the edge. Capped from 700px up only. */
.project__body {
  margin-top: var(--space-5);
  font-size: var(--project-body);
  line-height: 1.42;
}

@media (min-width: 700px) {
  .project__body {
    max-width: 38ch;
  }
}

/* The closing standard, set apart so it isn't skimmed as the tail of a list. */
.project__body--claim {
  margin-top: var(--space-4);
}

/* --- Section divider ---------------------------------------------------
   A single rule at the same weight as the section rules elsewhere. Built as a
   flex row so a mark can be dropped between two .divider__rule spans later if
   wanted — the segments flex around whatever sits between them.
   ---------------------------------------------------------------------- */

/* No padding of its own: the projects either side already carry generous
   block padding, and adding more here pushed the sections apart far enough
   that the divider read as a separator rather than a beat. The negative
   margin pulls that inherited space in a little further. */
.divider {
  display: flex;
  align-items: center;
  gap: clamp(20px, 3vw, 48px);
  margin-inline: var(--gutter);
  margin-block: clamp(-32px, -2.4vw, -16px);
  padding-block: 0;
}

.divider__rule {
  flex: 1 1 auto;
  height: var(--rule);
  background: var(--hr-black);
}

.divider__mark {
  flex: 0 0 auto;
  width: clamp(26px, 2.6vw, 40px);
  height: auto;
}

/* --- Project variant: panel -------------------------------------------
   Two stacked 16:9 frames beside a solid panel. The panel stretches to the
   height of the video stack, and the mascot is pushed to its base with an
   auto top margin so it sits on the floor of the panel at any height.
   ---------------------------------------------------------------------- */

.project--panel {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: clamp(22px, 2.5vw, 36px);
}

/* Panel to the left, frames to the right. Only from 900px up: when the columns
   stack on a phone, source order (frames, then copy) is the right reading
   order. The --panel-right modifier opts back out, so 02 and 04 sit on
   opposite sides of the page. */
@media (min-width: 900px) {
  .project--panel {
    flex-direction: row-reverse;
  }

  .project--panel-right {
    flex-direction: row;
  }
}

/* One 16:9 player in the media column, rather than the row of three verticals
   that 01 uses. */
.project__media--single {
  grid-template-columns: 1fr;
}

.project__media--single .video {
  aspect-ratio: 16 / 9;
  margin-top: 0;
}

/* A single landscape player needs more width than three verticals do to carry
   the same weight, so this variant gives the media column a larger share. */
@media (min-width: 900px) {
  .project--wide-media {
    grid-template-columns: 1.95fr 1fr;
  }
}

/* Second player in the panel stack is narrower and centred, so the two frames
   step rather than sitting flush — the ratios differ, and a flush edge made
   that read as a mistake. */
.project__stack .video--4x3 {
  width: 68%;
  margin-inline: auto;
}

.project__stack {
  flex: 1 1 560px;
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.4vw, 18px);
}

.panel {
  flex: 1 1 260px;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: clamp(22px, 2.2vw, 34px);
  background: var(--surface-inverse);
  color: var(--text-on-inverse);
}

.panel__head {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.panel__number {
  font-family: var(--font-display);
  font-weight: var(--display-weight-black);
  font-size: clamp(40px, 4.4vw, 74px);
  line-height: 0.8;
  letter-spacing: -0.018em;
  color: var(--accent);
}

.panel__client,
.panel__role {
  font-family: var(--font-mono);
  font-size: clamp(10.5px, 0.72vw, 12px);
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.panel__title {
  font-size: clamp(42px, 3.2vw, 52px);
  line-height: 0.84;
  letter-spacing: var(--title-tracking);
}

.panel__meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.panel__role {
  letter-spacing: 0.1em;
  color: var(--accent);
}

.panel__body {
  font-size: var(--project-body);
  line-height: 1.45;
}

/* Sized so the panel doesn't outgrow the video stack beside it. The mark is
   tall (roughly 1:1.9), so width has an outsized effect on panel height. */
/* The panel stretches to the height of the video stack beside it, so there's
   usually leftover space below the copy. `margin: auto` centres the mark in
   whatever that space turns out to be, rather than pinning it to the floor and
   piling the entire gap above it. */
/* Small by default. Below 900px the panel is full-width, so a percentage that
   reads as a mark on a narrow column becomes a poster on a phone — the cap is
   raised only once there's a column to sit in. */
.panel__mascot {
  width: min(22%, 72px);
  height: auto;
  margin: auto;
  align-self: center;
  pointer-events: none;
}

@media (min-width: 900px) {
  .panel__mascot {
    width: min(38%, 124px);
  }
}

/* --- Project variant: wide --------------------------------------------
   Title runs full width above a row of two 16:9 frames and a text column.
   The second frame drops so the row steps down rather than sitting flat.
   ---------------------------------------------------------------------- */

.project--wide {
  display: block;
}

.project__banner {
  font-size: clamp(52px, 9vw, 164px);
  line-height: 0.82;
  letter-spacing: var(--title-tracking);
  /* Optical alignment: pull left by the display face's side bearing so the
     glyph edge lines up with the column, not the glyph's bounding box. */
  margin-left: -0.024em;
  margin-bottom: clamp(18px, 2vw, 28px);
}

.project__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: clamp(18px, 2.2vw, 32px);
}

.project__row .video {
  flex: 1 1 320px;
  aspect-ratio: 16 / 9;
}

.project__row .video:nth-child(2) {
  margin-top: clamp(0px, 4vw, 64px);
}

.project__aside {
  flex: 1 1 240px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

@media (min-width: 700px) {
  .project__aside {
    max-width: 38ch;
  }
}

.project__num,
.panel__number {
  font-family: var(--font-display);
  font-weight: var(--display-weight-black);
  font-size: clamp(40px, 4.4vw, 74px);
  line-height: 0.8;
  letter-spacing: -0.018em;
  color: var(--accent);
}

.project__aside .panel__client {
  color: var(--text-default);
}

/* Secondary line — dimmed rather than coloured, so the number stays the only
   accent in this block. */
.project__aside-role {
  font-family: var(--font-mono);
  font-size: clamp(10.5px, 0.72vw, 12px);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.62;
}

.project__aside-body {
  font-size: clamp(16px, 1.1vw, 18px);
  line-height: 1.45;
}

/* --- Project variant: overlap -----------------------------------------
   Two frames of unequal width, with the title pulled up over their lower
   edge. The negative top margin is in em so the overlap scales with the
   type rather than drifting as the headline resizes.
   ---------------------------------------------------------------------- */

.project--overlap {
  display: block;
  position: relative;
}

.project__frames {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: clamp(12px, 1.4vw, 18px);
}

.project__frames .video:first-child {
  flex: 2 1 520px;
}

.project__frames .video:last-child {
  flex: 1 1 240px;
  margin-top: clamp(0px, 3vw, 48px);
}

.project__overlap-title {
  position: relative;
  z-index: 2;
  margin: -0.28em 0 0 -0.024em;
  font-size: clamp(56px, 8.4vw, 150px);
  line-height: 0.8;
  letter-spacing: var(--title-tracking);
  color: var(--accent);
  /* Sits over the imagery, so it must not intercept the cursor. */
  pointer-events: none;
}

.project__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-4) clamp(24px, 4vw, 64px);
  padding-top: clamp(14px, 1.8vw, 24px);
}

/* --- Project variant: lead frame ---------------------------------------
   A full-width player at the top with the title rising into it, then a row
   below: second frame at left, credits and copy at right. The credits stack
   above the copy here, matching how 01 and 02 are set.
   ---------------------------------------------------------------------- */

.project__lead {
  width: 100%;
}

.project__split {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(20px, 2.5vw, 40px);
  padding-top: clamp(20px, 2.5vw, 40px);
}

@media (min-width: 900px) {
  .project__split {
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
}

.project__text-block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}

.project__text-block .project__facts-copy {
  padding-top: var(--space-3);
  max-width: none;
}

/* Mirrored variant: the wide frame moves to the right and the title sets to
   the right edge, so this section leans opposite to 04. The small frame takes
   the step down instead, keeping the same stagger in reverse. */
.project--overlap-right .project__frames .video:first-child {
  flex: 1 1 240px;
  margin-top: clamp(0px, 3vw, 48px);
}

.project--overlap-right .project__frames .video:last-child {
  flex: 2 1 520px;
  margin-top: 0;
}

.project--overlap-right .project__overlap-title {
  text-align: right;
  /* Accent rather than black: the frames behind it are light, and black type
     was disappearing into the footage. */
  color: var(--accent);
  /* Optical pull applies to the right edge here rather than the left. */
  margin-left: 0;
  margin-right: -0.024em;
}


.project__facts-num {
  font-family: var(--font-display);
  font-weight: var(--display-weight-black);
  font-size: clamp(32px, 3vw, 54px);
  line-height: 0.8;
  letter-spacing: -0.018em;
}

.project__facts-body,
.project__facts-copy {
  flex: 1 1 320px;
  font-size: var(--project-body);
  line-height: 1.45;
}

/* Multiple paragraphs in the facts row need their own stack, otherwise the
   flex parent lays them out side by side. */
.project__facts-copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* ~12–14 words a line. Narrower than this and the copy sat well short of the
   role line above it, which made the block look accidental. */
@media (min-width: 700px) {
  .project__facts-body,
  .project__facts-copy {
    max-width: 62ch;
  }
}

/* Illustration filling the space to the right of the facts copy. */
.project__facts-mark {
  display: none;
}

@media (min-width: 1000px) {
  .project__facts-mark {
    display: block;
    width: clamp(120px, 12vw, 190px);
    height: auto;
    margin-left: auto;
    align-self: flex-end;
  }
}

/* A heavy frame around this section's players.

   Outline rather than border, deliberately. A border is subtracted from the
   box, so a 16:9 frame with 5px sides leaves a content box of a slightly
   different ratio — the Vimeo player then letterboxes to fit, and its
   transparent bars let the poster underneath show through as slivers at the
   edges. An outline is painted outside the layout box, so the player keeps the
   exact ratio it was given. */
.project--framed .video {
  outline: 5px solid var(--hr-black);
  outline-offset: 0;
}

/* A frame with no source yet: renders as a black plate at the right ratio so
   the layout is correct while the asset is still to come. */
.video:not([data-vimeo]):not([data-vimeo-play]) .video__poster {
  display: none;
}

/* --- Click-to-play video ----------------------------------------------
   For pieces meant to be watched rather than felt: the still holds, and the
   player is only created when someone asks for it — with sound and controls.
   ---------------------------------------------------------------------- */

.video--cta .video__poster {
  opacity: 1;
}

.video__play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: 14px 22px;
  border: 0;
  background: var(--hr-black);
  color: var(--surface-default);
  font-family: var(--font-mono);
  font-size: var(--type-mono-lg);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 120ms linear, color 120ms linear;
}

.video__play:hover {
  background: var(--accent);
}

.video__play-icon {
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 6px 0 6px 10px;
  border-color: transparent transparent transparent currentColor;
}

.video--cta.is-playing .video__play,
.video--cta.is-playing .video__poster {
  display: none;
}

/* The played embed keeps its controls, so it must accept the cursor. */
.video--cta iframe {
  pointer-events: auto;
  opacity: 1;
}

/* --- About page --------------------------------------------------------
   One column, generous space. Measure is capped well below the page width so
   the text stays readable rather than running the full 1280px.
   ---------------------------------------------------------------------- */

.about {
  padding: clamp(32px, 5vw, 72px) var(--gutter) clamp(20px, 2.4vw, 36px);
}

.about__eyebrow {
  font-size: var(--type-mono-lg);
  letter-spacing: 0.16em;
  color: var(--accent);
  margin-bottom: clamp(24px, 3vw, 44px);
}

/* Measure is wide enough to hold the longest forced line — the <br> in the
   markup decides where it breaks, not the container running out. */
.about__headline {
  font-size: clamp(40px, 6.4vw, 116px);
  line-height: 0.82;
  letter-spacing: -0.018em;
  max-width: 24ch;
  margin-left: -0.02em;
}

/* The headline runs at negative tracking, which pulls the period hard against
   the preceding letter. This adds the space back for the dot only, so the rest
   of the line keeps its tight setting. Value is in em so it scales with the
   type at every breakpoint. */
.about__headline .dot,
.studio__headline .dot {
  color: var(--accent);
  margin-left: 0.055em;
}

.about__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(20px, 2.2vw, 30px);
  max-width: 58ch;
  padding-top: clamp(32px, 4vw, 64px);
}

.about__lede {
  font-size: clamp(19px, 1.6vw, 26px);
  line-height: 1.38;
}

.about__body p {
  font-size: clamp(16px, 1.15vw, 19px);
  line-height: 1.5;
}

.about__body .btn {
  margin-top: clamp(8px, 1.2vw, 18px);
}

/* Section pattern: a mono label in a left column, content in a right column,
   divided by a rule. Below 900px the label sits above its content. */
.about-section {
  display: grid;
  grid-template-columns: 1fr;
  align-items: start;
  gap: clamp(16px, 2vw, 28px) clamp(24px, 4vw, 64px);
  padding-block: clamp(28px, 3.4vw, 52px);
  border-top: 1px solid rgba(0, 0, 0, 0.2);
}

/* Breathing room between the page headline and the first section rule. */
.about-section:first-of-type {
  margin-top: clamp(36px, 5vw, 88px);
}

/* Below 900px the label sits above, and the mark shares a row with the copy so
   it stays visible on a phone rather than being dropped. */
.about-section:has(.about-section__mark) {
  grid-template-columns: 1fr auto;
  grid-template-areas:
    'label label'
    'body  mark';
}

.about-section:has(.about-section__mark) .about-section__label {
  grid-area: label;
}

.about-section:has(.about-section__mark) .about-section__body {
  grid-area: body;
}

.about-section__mark {
  grid-area: mark;
  display: block;
  width: clamp(56px, 14vw, 96px);
  align-self: end;
  justify-self: end;
}

@media (min-width: 900px) {
  .about-section {
    grid-template-columns: 200px 1fr;
  }

  /* A third column appears only when a section carries a mark, so sections
     without one keep their copy at full measure. */
  .about-section:has(.about-section__mark) {
    grid-template-columns: 200px 1fr auto;
    grid-template-areas: 'label body mark';
  }

  .about-section__mark {
    width: clamp(90px, 9vw, 140px);
  }
}

.about-section__label {
  font-family: var(--font-mono);
  font-size: var(--type-mono-lg);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.about-section__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(16px, 1.8vw, 24px);
  max-width: 62ch;
}

.about-section__body p {
  font-size: clamp(16px, 1.15vw, 19px);
  line-height: 1.5;
}

.about-section__body .about__lede {
  font-size: clamp(21px, 2.2vw, 34px);
  line-height: 1.24;
  max-width: 26ch;
}

/* The "Meet" section carries the portrait alongside its copy. */
.about-section--person .about-section__body {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(24px, 3vw, 44px);
  max-width: none;
  align-items: start;
}

@media (min-width: 700px) {
  .about-section--person .about-section__body {
    grid-template-columns: 1fr 300px;
  }
}

.about-section--person .about-section__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(16px, 1.8vw, 24px);
  max-width: 58ch;
}

/* Copy beside the portrait. Below 900px the portrait drops under the text
   rather than shrinking into a stamp. */
.about__intro {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(32px, 4vw, 64px);
  align-items: start;
  padding-top: clamp(32px, 4vw, 64px);
}

@media (min-width: 900px) {
  .about__intro {
    grid-template-columns: 1.25fr 1fr;
  }
}

.about__intro .about__body {
  padding-top: 0;
}

.about__portrait {
  margin: 0;
}

.about__portrait img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  background: var(--hr-canvas-gray);
}

.about__portrait figcaption {
  font-family: var(--font-mono);
  font-size: var(--type-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.6;
  padding-top: var(--space-3);
}

/* --- Frame grid --------------------------------------------------------
   Mixed portrait and landscape stills. Each cell declares its own ratio, so
   dropping an image in never changes the layout — the box is already the
   right shape and object-fit handles the crop.
   ---------------------------------------------------------------------- */

.frames {
  padding: clamp(16px, 1.8vw, 28px) var(--gutter) clamp(20px, 2.4vw, 36px);
}

.frames__head {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 28px);
  padding-bottom: clamp(20px, 2.4vw, 36px);
}

.frames__head h2 {
  font-family: var(--font-mono);
  font-size: var(--type-mono-lg);
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
}

.frames__head .rule {
  flex: 1 1 auto;
}

/* Staggered masonry. site.js distributes frames into explicit columns, packing
   each one into whichever column is currently shortest — that's what keeps the
   bottom edge ragged-but-tight instead of leaving holes. Each column then gets
   its own top offset so the row doesn't start on a single flat line, echoing
   the stepped frames in the project sections.

   Before the script runs, the flex fallback below still lays the frames out
   sensibly, so nothing collapses if JS is slow or blocked. */
.frames__grid {
  --frame-gap: clamp(12px, 1.4vw, 20px);
  display: flex;
  align-items: flex-start;
  gap: var(--frame-gap);
}

.frames__col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--frame-gap);
}

/* Column offsets are set by site.js, not here: they're derived from how tall
   each column actually ended up, so a short column is nudged down to sit
   between its neighbours instead of leaving a hole under it. */

/* Fallback only: applies until the script wraps frames into columns. */
.frames__grid > .frame {
  flex: 1 1 220px;
}

.frame {
  position: relative;
  display: block;
  width: 100%;
  margin: 0 0 var(--frame-gap);
  overflow: hidden;
  background: var(--hr-black);
  /* Never split a frame across a column boundary. */
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
}

.frame--9x16 {
  aspect-ratio: 9 / 16;
}

.frame--16x9 {
  aspect-ratio: 16 / 9;
}

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

/* Empty state: dashed outline and a label naming what belongs here. Delete
   the <span> and add an <img> to fill it. */
.frame:not(:has(img)) {
  background: transparent;
  outline: 1px dashed rgba(0, 0, 0, 0.35);
  outline-offset: -1px;
}

.frame__label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-3);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--type-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.45;
}

/* --- Studio ------------------------------------------------------------ */

/* Inset to the same column edges as the projects above. Previously the gutter
   was padding, which put the black background out at the page edge while every
   section above it started one gutter in. */
.studio {
  background: var(--surface-inverse);
  color: var(--text-on-inverse);
  margin-inline: var(--gutter);
  padding: clamp(52px, 7vw, 116px) clamp(24px, 3vw, 52px);
}

.studio__eyebrow {
  font-size: var(--type-mono-lg);
  letter-spacing: 0.16em;
  color: var(--accent);
  margin-bottom: clamp(28px, 3.4vw, 52px);
}

.studio__headline {
  font-size: clamp(46px, 7.6vw, 138px);
  line-height: 0.74;
  letter-spacing: -0.008em;
  max-width: 24ch;
  margin-left: -0.02em;
}


.studio__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: clamp(28px, 4vw, 72px);
  padding-top: clamp(32px, 4vw, 64px);
}

.studio__copy {
  flex: 1 1 420px;
  max-width: 52ch;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(24px, 2.6vw, 38px);
}

.studio__copy p {
  font-size: clamp(17px, 1.35vw, 22px);
  line-height: 1.45;
}

/* Inverted button: the solid style flipped for the black panel. */
.btn--invert {
  background: var(--surface-default);
  color: var(--hr-black);
}

.btn--invert:hover {
  background: var(--accent);
  color: var(--surface-default);
}

.capabilities {
  flex: 0 1 480px;
  list-style: none;
  align-self: flex-start;
}

.capabilities li {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: 14px 0;
  border-top: 2px solid var(--surface-default);
  font-family: var(--font-mono);
  font-size: var(--type-mono-lg);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.capabilities li:last-child {
  border-bottom: 2px solid var(--surface-default);
}

.capabilities span {
  color: var(--accent);
}

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

.site-footer {
  padding: clamp(52px, 7vw, 116px) var(--gutter) 0;
}

/* On the About page the frame grid runs right up to the footer, so the usual
   generous top padding stacks with the grid's own and opens a hole. */
.frames + .site-footer {
  padding-top: clamp(28px, 3.4vw, 56px);
}

.footer__prompt {
  font-size: clamp(30px, 3.6vw, 58px);
  line-height: 0.86;
  letter-spacing: -0.018em;
  max-width: 26ch;
  color: var(--accent);
  margin-bottom: clamp(20px, 2.4vw, 34px);
}

/* Sized to hold the full address on one line at desktop widths. The export
   ran larger and broke mid-word, which reads as a bug rather than a choice. */
.footer__email {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: var(--display-weight-black);
  font-size: clamp(28px, 5.4vw, 96px);
  line-height: 0.84;
  letter-spacing: -0.022em;
  text-transform: uppercase;
  text-decoration: none;
  overflow-wrap: normal;
  word-break: keep-all;
  transition: color 120ms linear;
}

.footer__email:hover {
  color: var(--accent);
}

.footer__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px clamp(24px, 4vw, 64px);
  border-top: var(--rule) solid var(--hr-black);
  margin-top: clamp(32px, 4vw, 64px);
  padding: var(--space-4) 0 clamp(28px, 3vw, 44px);
}

.footer__meta p,
.footer__social a {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.footer__social {
  display: flex;
  gap: clamp(18px, 2.4vw, 34px);
}

.footer__social a {
  text-decoration: none;
  border-bottom: 2px solid var(--hr-black);
}

.footer__social a:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.footer__copyright {
  opacity: 0.6;
}

.footer__mascot {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-block: clamp(20px, 2.6vw, 40px) clamp(24px, 3vw, 44px);
}

/* Same optical size and upright posture as the header mark, so the two read
   as one motif bookending the page rather than two different drawings. */
.footer__mascot img {
  width: var(--mascot-size);
}

.footer__still {
  position: absolute;
  right: 0;
  bottom: clamp(10px, 1.2vw, 18px);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.6;
}

.footer__bar {
  height: clamp(10px, 1.4vw, 16px);
  background: var(--hr-black);
}

/* --- Reveal ------------------------------------------------------------ */

[data-reveal] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 420ms linear, transform 520ms cubic-bezier(0.2, 0.75, 0.25, 1);
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }

  [data-reveal] {
    opacity: 1;
    transform: none;
  }
}
