/* ===========================================================================
   danwrightmedia.com - v5
   ===========================================================================
   v4's argument, turned up. Two changes Dan asked for:

   1. The hero reel plays clean. It already carries its own burnt-in graphics,
      so putting a headline on top fights it. The promise lands on first scroll
      instead, over its own footage.

   2. Every section sits on moving footage. Nothing is a flat panel. Each clip
      is chosen for what it actually shows - the industrial film behind the
      industrial case, interview footage behind the testimonial.

   Same brand discipline as v4: monochrome, Pale Gold as the single accent,
   Manrope alone. The design still does not decorate - the footage is the
   design, and the only job of the CSS is to keep type legible on top of it.
   =========================================================================== */

:root {
  --black: #000;
  --white: #fff;
  --warm-1: #ece9e4;
  --warm-2: #a8a49d;
  --line: rgba(255, 255, 255, 0.16);
  --gold: #e6c17a;

  /* Height of the dev preview bar. The bar was removed at the swap, so this
     is 0 - body padding, the fixed nav's top offset and the picker hero's
     padding all key off it, so one value moves them together. */
  --bar: 0px;
  --font: "Manrope", "Volte Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;

  --fs-mega: clamp(2.6rem, 9.5vw, 7rem);
  --fs-h2: clamp(1.9rem, 5.6vw, 3.6rem);
  --fs-h3: clamp(1.2rem, 2.8vw, 1.6rem);
  --fs-lead: clamp(1.1rem, 2.5vw, 1.45rem);
  --fs-body: clamp(1rem, 1.7vw, 1.125rem);
  --fs-label: 0.75rem;

  --pad: clamp(1.25rem, 5vw, 4.5rem);
  --band: clamp(4.5rem, 11vw, 9rem);
  /* 1240 is a reading width. This is a video production site opening on a
     full-screen reel - on a 1920 monitor it left a third of the screen empty
     either side and capped the showreel box at 486px. */
  --max: 1440px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0; padding-top: var(--bar);
  background: var(--black); color: var(--warm-1);
  font-family: var(--font); font-size: var(--fs-body); line-height: 1.6;
  -webkit-font-smoothing: antialiased; overflow-x: hidden;
}

img, video, iframe { display: block; max-width: 100%; }
a { color: inherit; }

h1, h2, h3 { margin: 0; color: var(--white); font-weight: 800; letter-spacing: -0.035em; line-height: 0.98; }
h1 { font-size: var(--fs-mega); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; }
p { margin: 0 0 1.1rem; }
p:last-child { margin-bottom: 0; }

.wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--pad); }
.lead { font-size: var(--fs-lead); line-height: 1.45; max-width: 46ch; }

.label {
  font-size: var(--fs-label); font-weight: 700; letter-spacing: .2em;
  text-transform: uppercase; color: var(--gold); margin: 0 0 1.3rem;
}

/* -------------------------------------------------------------- dev switch */

.dev {
  position: fixed; inset: 0 0 auto 0; z-index: 300; height: var(--bar);
  display: flex; align-items: center; gap: 8px; padding: 0 var(--pad);
  background: #101012; border-bottom: 1px solid var(--line);
  font-size: .6875rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  /* Eight chips no longer fit a phone - the newest versions sat past the right
     edge with no way to reach them. The bar scrolls sideways; chips never wrap. */
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  white-space: nowrap;
}
.dev::-webkit-scrollbar { display: none; }
.dev a { flex-shrink: 0; }
.dev span { flex-shrink: 0; }
.dev span { margin-right: auto; color: var(--warm-2); }
.dev a { padding: 7px 12px; border: 1px solid var(--line); color: var(--warm-1); text-decoration: none; }
.dev a[aria-current="page"] { background: var(--gold); border-color: var(--gold); color: #23190a; }

/* ------------------------------------------------------------------ header */

.nav {
  position: fixed; top: var(--bar); left: 0; right: 0; z-index: 200;
  display: flex; align-items: center; gap: 20px; padding: 14px var(--pad);
  transition: background .35s ease, border-color .35s ease, backdrop-filter .35s;
  border-bottom: 1px solid transparent;
}
/* A scrim behind the nav at rest.
   The bar used to sit 44px down, under the dev preview strip. At the swap it
   moved to the very top, which on the concert page puts "Work" and "Team"
   directly on a blown-out stage light - white on white, effectively invisible.
   Every hero on the site is a photograph, so this belongs on the nav rather
   than on one page. It fades out once .stuck brings in the real background. */
.nav::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 132px;
  z-index: -1; pointer-events: none;
  background: linear-gradient(180deg, rgba(0,0,0,.5) 0%, rgba(0,0,0,.26) 42%, transparent 100%);
  transition: opacity .35s ease;
}
.nav.stuck::before { opacity: 0; }
.nav.stuck { background: rgba(0,0,0,.78); backdrop-filter: blur(14px); border-bottom-color: var(--line); }
.nav__logo img { height: 26px; width: auto; }
.nav__links { display: none; gap: 28px; margin-left: auto; }
.nav__links a { text-decoration: none; color: var(--warm-1); font-size: .9375rem; font-weight: 600; }
.nav__links a:hover { color: var(--white); }
@media (min-width: 900px) { .nav__links { display: flex; } }

/* --------------------------------------------------------------------- CTA */

.cta {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 52px; padding: 0 30px;
  font-family: inherit; font-size: .9375rem; font-weight: 700;
  text-decoration: none; cursor: pointer;
  border: 1px solid var(--gold); background: var(--gold); color: #23190a;
  transition: background .2s, border-color .2s, color .2s;
}
.cta:hover { background: #f0d296; border-color: #f0d296; }
.cta--ghost { background: transparent; color: var(--white); border-color: rgba(255,255,255,.4); }
.cta--ghost:hover { background: var(--white); color: var(--black); border-color: var(--white); }
.nav .cta { min-height: 42px; padding: 0 20px; margin-left: auto; font-size: .875rem; }
@media (min-width: 900px) { .nav .cta { margin-left: 0; } }
.ctas { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 2.2rem; }
@media (max-width: 599px) { .ctas { flex-direction: column; } .ctas .cta { width: 100%; } }

/* ==========================================================================
   The scene: every section is footage with type on top.
   ========================================================================== */

.scene { position: relative; isolation: isolate; overflow: hidden; }
.scene__bg { position: absolute; inset: 0; z-index: -2; }
.scene__bg video, .scene__bg img { width: 100%; height: 100%; object-fit: cover; }

/* Legibility is the whole job here. Two stops of darkening: a flat wash so
   mid-greys never fight the type, and a bottom-weighted gradient so text that
   sits low always has something solid behind it. */
/* The veil sits lighter by default so the footage actually reads, then deepens
   as that section's text comes into view - enough to keep type legible, not
   enough to notice it happening. One variable, one transition. */
.scene__bg::after {
  content: ""; position: absolute; inset: 0;
  opacity: var(--veil, .62);
  transition: opacity .8s ease;
  background:
    linear-gradient(180deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,.42) 38%, rgba(0,0,0,.88) 100%),
    rgba(0,0,0,.22);
}
.scene--soft .scene__bg::after {
  background:
    linear-gradient(180deg, rgba(0,0,0,.72) 0%, rgba(0,0,0,.62) 50%, rgba(0,0,0,.9) 100%),
    rgba(0,0,0,.3);
}

/* text in view -> full veil */
.scene.lit .scene__bg::after { --veil: 1; }

@media (prefers-reduced-motion: reduce) {
  .scene__bg::after { transition: none; }
}
.scene__inner { position: relative; padding-block: var(--band); }
.scene + .scene { border-top: 1px solid var(--line); }

/* ------------------------------------------------------------------- reel */
/*
 * Clean. The reel carries its own graphics, so nothing is laid over it - only
 * a scroll cue at the very bottom. The promise arrives on first scroll.
 */

.reel {
  position: relative; height: 100svh; min-height: 460px; background: #000;
  /* The reel carries burned-in type. Sampled every frame of the cut, it lives
     inside the central 78% of the width - so that much has to stay on screen or
     the words get sliced. */
  --reel-safe: .78;
}

/* object-fit: cover crops the sides to fill the height, which on a phone means
   showing about a quarter of the frame width and cutting every caption in half.
   Below the aspect where 78% still fits (16/9 x 0.78 = 1.387), stop filling the
   height and let the height follow from the width instead. Written as an aspect
   query rather than a width one so a tablet held upright and a short desktop
   window are covered too, not just phones. */
@media (max-aspect-ratio: 1387 / 1000) {
  .reel { height: calc(100vw * 9 / 16 / var(--reel-safe)); min-height: 0; }
}
.reel video { width: 100%; height: 100%; object-fit: cover; }
.reel::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 32%;
  background: linear-gradient(180deg, transparent, rgba(0,0,0,.85));
  pointer-events: none;
}
.reel__cue {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 2; display: grid; justify-items: center; gap: 8px;
  font-size: var(--fs-label); font-weight: 700; letter-spacing: .2em;
  text-transform: uppercase; color: var(--warm-1); text-decoration: none;
}
.reel__cue i { display: block; width: 1px; height: 26px; background: var(--gold); animation: drop 2s ease-in-out infinite; }
@keyframes drop { 0%,100% { transform: scaleY(.4); opacity: .5; } 50% { transform: scaleY(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .reel__cue i { animation: none; } }

/* -------------------------------------------------------------------- work */

.case__inner { min-height: min(80svh, 640px); display: grid; align-items: end; }
.case__sector { font-size: var(--fs-label); font-weight: 700; letter-spacing: .2em; text-transform: uppercase; color: var(--gold); }
.case__name { margin-top: .7rem; font-size: clamp(2rem, 7vw, 4.5rem); }
.case__what { margin-top: 1rem; max-width: 44ch; }
.case__link {
  display: inline-flex; align-items: center; gap: .5rem; margin-top: 1.5rem;
  font-weight: 700; text-decoration: none; color: var(--white);
  border-bottom: 2px solid var(--gold); padding-bottom: 3px;
}

/* ---------------------------------------------------------------- proof bar */

.proof { margin-top: clamp(2.2rem, 5vw, 3.4rem); padding-top: 1.5rem; border-top: 1px solid var(--line); }
.proof__k { font-size: var(--fs-label); font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: var(--warm-2); }
.proof__list {
  margin: .9rem 0 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: .6rem 2rem;
  font-size: clamp(1rem, 2.4vw, 1.35rem); font-weight: 700; color: var(--white); letter-spacing: -.01em;
}

/* ------------------------------------------------------------------ offers */

.offers { margin-top: 2.6rem; border-top: 1px solid var(--line); }
.offer { padding-block: clamp(1.5rem, 3.6vw, 2.2rem); border-bottom: 1px solid var(--line); }
@media (min-width: 860px) { .offer { display: grid; grid-template-columns: 22rem 1fr; gap: 3rem; align-items: baseline; } }
.offer p { margin: .55rem 0 0; color: var(--warm-1); max-width: 52ch; }
@media (min-width: 860px) { .offer p { margin-top: 0; } }

/* ------------------------------------------------------------------- quote */

.quote blockquote {
  margin: 0; font-size: clamp(1.3rem, 3.6vw, 2.2rem); line-height: 1.3;
  font-weight: 600; color: var(--white); letter-spacing: -.02em; max-width: 26ch;
}
.quote cite { display: block; margin-top: 1.6rem; font-style: normal; font-size: .9375rem; color: var(--warm-2); }
.quote cite b { display: block; color: var(--white); font-weight: 700; }

/* ----------------------------------------------------------------- process */

.steps { display: grid; gap: 1px; margin-top: 2.6rem; background: var(--line); border: 1px solid var(--line); }
@media (min-width: 800px) { .steps { grid-template-columns: repeat(4, 1fr); } }
.step { background: rgba(0,0,0,.55); backdrop-filter: blur(6px); padding: clamp(1.3rem, 3vw, 1.9rem); }
.step__n { font-size: var(--fs-label); font-weight: 700; letter-spacing: .2em; color: var(--gold); }
.step h3 { margin: .7rem 0 .45rem; }
.step p { color: var(--warm-1); margin: 0; font-size: .9375rem; }

/* -------------------------------------------------------------------- form */

.form { display: grid; gap: 1rem; max-width: 560px; margin-top: 2.4rem; }
.f { display: grid; gap: .45rem; }
.f label { font-size: var(--fs-label); font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--warm-2); }
/* White fields. The old near-black boxes disappeared into the section
   background - the form read as empty space rather than something to fill in. */
.f input, .f textarea {
  font: inherit; width: 100%; min-height: 52px; padding: .8rem 1rem;
  background: #f4f2ef; color: #111;
  border: 1px solid transparent; border-radius: 0;
}
.f textarea { min-height: 140px; resize: vertical; }
.f input:focus, .f textarea:focus { outline: 2px solid var(--gold); outline-offset: 1px; }
.status { font-size: .9375rem; min-height: 1.4em; }
.status[data-state="ok"] { color: var(--gold); }
.status[data-state="error"] { color: #ff9a9a; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }


/* -------------------------------------------------------------- footer social */
/* Inline SVG on currentColor, so one rule sets all three and they match the
   footer's text colour instead of arriving as three differently-coloured
   image files the way Webflow had them.
   Lives HERE, not in dwm-showcase.css: the homepage loads only this sheet, and
   without these rules an <svg> has no intrinsic size and fills its container -
   the icons came out 1296px square. */
.foot__end {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1rem;
}
/* .foot ul is (0,1,1) and sets display:grid, so this has to out-specify it. */
.foot .foot__social { display: flex; gap: .35rem; list-style: none; margin: 0; padding: 0; }
.foot .foot__social a {
  display: grid; place-items: center; width: 38px; height: 38px;
  color: rgba(255, 255, 255, 0.55); border-radius: 50%;
  transition: color .18s ease, background-color .18s ease;
}
.foot .foot__social svg { width: 19px; height: 19px; display: block; }
.foot .foot__social a:hover { color: var(--white); background: rgba(255, 255, 255, 0.08); }
.foot .foot__social a:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

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

.foot { position: relative; padding-block: clamp(3rem, 7vw, 5rem); color: var(--warm-2); font-size: .9375rem; }
.foot__grid { display: grid; gap: 2rem; }
@media (min-width: 760px) { .foot__grid { grid-template-columns: 2fr 1fr 1fr; } }
.foot b { color: var(--white); display: block; margin-bottom: .7rem; }
.foot ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .45rem; }
.foot a { text-decoration: none; }
.foot a:hover { color: var(--white); }
.foot__end { margin-top: 2.4rem; padding-top: 1.2rem; border-top: 1px solid var(--line); }

/* ------------------------------------------------------------------ reveal */

.rv { opacity: 0; transform: translateY(14px); transition: opacity .8s ease, transform .8s ease; }
.rv.on { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .rv { opacity: 1; transform: none; transition: none; } }

/* ==========================================================================
   Versatility matrix
   ==========================================================================
   The pitch: some clients want a technical piece that does a job, others want
   something with a creative idea in it. These are not opposite ends of one
   line - they are two independent axes, and the interesting work is high on
   both. Plotting the work types shows range without claiming it in words.

   SVG so it stays crisp at any size. The animation is one sequence, not
   scattered effects: axes draw, then points land in reading order, then the
   labels arrive. It only runs once, when it comes into view.
   ========================================================================== */

.matrix { margin-top: clamp(2rem, 5vw, 3rem); }

.matrix__frame {
  position: relative;
  max-width: 620px;
  margin-inline: auto;
  background: rgba(0, 0, 0, .55);
  backdrop-filter: blur(6px);
  border: 1px solid var(--line);
  padding: clamp(1.1rem, 3.5vw, 2rem);
}

.matrix svg { width: 100%; height: auto; display: block; overflow: visible; }

/* --- axes and grid ------------------------------------------------------- */

.mx-axis {
  stroke: rgba(255, 255, 255, .85);
  stroke-width: 1.4;
  stroke-dasharray: 200;
  stroke-dashoffset: 200;
  transition: stroke-dashoffset .75s cubic-bezier(.65, 0, .35, 1);
}
.matrix.on .mx-axis { stroke-dashoffset: 0; }

.mx-grid { stroke: rgba(255,255,255,.13); stroke-width: .6; opacity: 0; transition: opacity .5s ease .5s; }
.matrix.on .mx-grid { opacity: 1; }

.mx-axis-label {
  fill: var(--gold); font-size: 3.9px; font-weight: 800;
  letter-spacing: .22em; text-transform: uppercase;
  opacity: 0; transition: opacity .5s ease .35s;
}
.matrix.on .mx-axis-label { opacity: 1; }

.mx-arrow { fill: rgba(255,255,255,.85); opacity: 0; transition: opacity .3s ease .7s; }
.matrix.on .mx-arrow { opacity: 1; }

/* --- plotted points ------------------------------------------------------ */

.mx-pt { opacity: 0; transform: scale(.4); transform-box: fill-box; transform-origin: center;
         transition: opacity .38s ease, transform .38s cubic-bezier(.34,1.56,.64,1); }
.matrix.on .mx-pt { opacity: 1; transform: scale(1); }

.mx-dot { fill: var(--gold); }
.mx-halo { fill: none; stroke: var(--gold); stroke-width: .7; opacity: .35; }

.mx-label {
  fill: #fff; font-size: 3.5px; font-weight: 700; letter-spacing: -.01em;
  paint-order: stroke; stroke: rgba(0,0,0,.75); stroke-width: 1.1px;  /* readable over any frame */
  opacity: 0; transition: opacity .4s ease;
}
.matrix.on .mx-label { opacity: 1; }

/* Stagger: points land in reading order rather than all at once. */
.matrix.on .mx-pt:nth-of-type(1)  { transition-delay: .70s }
.matrix.on .mx-pt:nth-of-type(2)  { transition-delay: .78s }
.matrix.on .mx-pt:nth-of-type(3)  { transition-delay: .86s }
.matrix.on .mx-pt:nth-of-type(4)  { transition-delay: .94s }
.matrix.on .mx-pt:nth-of-type(5)  { transition-delay: 1.02s }
.matrix.on .mx-pt:nth-of-type(6)  { transition-delay: 1.10s }
.matrix.on .mx-pt:nth-of-type(7)  { transition-delay: 1.18s }
.matrix.on .mx-pt:nth-of-type(8)  { transition-delay: 1.26s }
.matrix.on .mx-pt:nth-of-type(9)  { transition-delay: 1.34s }
.matrix.on .mx-pt .mx-label { transition-delay: inherit; }

.matrix__note {
  margin-top: 1.2rem; text-align: center;
  font-size: .875rem; color: var(--warm-2);
}

@media (prefers-reduced-motion: reduce) {
  .mx-axis { stroke-dashoffset: 0; }
  .mx-grid, .mx-axis-label, .mx-arrow, .mx-label { opacity: 1; transition: none; }
  .mx-pt { opacity: 1; transform: none; transition: none; }
}

/* ==========================================================================
   Team - polaroids
   ==========================================================================
   Three photos land on the page as you reach them, tilted like prints tossed
   on a desk, then straighten when you pick one. Aidan left, Dan centre, James
   right - Dan's ordering.

   The tilt is the whole idea, so it stays small: a few degrees each, one
   landing animation, and a lift on select. Nothing spins or bounces twice.
   ========================================================================== */

.team { margin-top: clamp(2.6rem, 5.5vw, 4rem); }

/* Tighter than a standard scene: the polaroids are bright and busy, so the
   usual band of space below them reads as a hole rather than breathing room. */
#team .scene__inner { padding-bottom: clamp(3.2rem, 7vw, 5.5rem); }

.team__row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(.4rem, 2vw, 1.4rem);
  align-items: start;
  perspective: 900px;
}

.card {
  position: relative;
  display: block;
  width: 100%;
  padding: 6% 6% 0;
  background: #f4f2ee;
  border: 0;
  margin: 0;                 /* <figure> carries a default margin, <button> does not */
  font: inherit;
  text-align: center;
  color: #16161a;
  box-shadow: 0 14px 30px rgba(0, 0, 0, .55);
  transform-origin: 50% 90%;

  /* landing state */
  opacity: 0;
  transform: translateY(-46px) rotate(var(--tilt)) scale(.92);
  transition:
    opacity .5s ease,
    transform .62s cubic-bezier(.2, .9, .3, 1.25),
    box-shadow .3s ease,
    filter .3s ease;
}


/* Only the About cards are controls. The homepage renders the same component
   as <figure>s - static by design - so nothing there should invite a click. */
button.card { cursor: pointer; }

.card--left  { --tilt: -6deg; }
.card--mid   { --tilt: 1.5deg; z-index: 2; }
.card--right { --tilt: 5deg; }

.card__shot { width: 100%; aspect-ratio: 1 / 1.06; object-fit: cover; display: block; background: #ddd; }

.card__cap { display: block; padding: .65rem .2rem .8rem; }
/* Both are spans inside a span, so they need making block - otherwise the name
   and role run together on one line and wrap badly. */
.card__name { display: block; font-weight: 800; letter-spacing: -.02em; font-size: clamp(.95rem, 3.4vw, 1.25rem); line-height: 1.1; }
.card__role { display: block; margin-top: .3rem; font-size: clamp(.6rem, 2vw, .75rem); font-weight: 600;
              letter-spacing: .1em; text-transform: uppercase; color: #6b6660; }

/* The landing. Cards start at opacity 0 and drop in when .on lands on the
   section, staggered so they read as three prints put down one after another.
   Two selectors because the two pages nest them differently: About has three
   siblings in .team__row, the homepage has Dan plus a .team__rest holding the
   other two. Without both, the homepage cards stay invisible. */
.team.on .card { opacity: 1; transform: rotate(var(--tilt)); }
.team.on .team__row > .card:nth-child(1) { transition-delay: .05s; }
.team.on .team__row > .card:nth-child(2) { transition-delay: .20s; }
.team.on .team__row > .card:nth-child(3) { transition-delay: .35s; }
.team.on .team__rest > .card:nth-child(1) { transition-delay: .20s; }
.team.on .team__rest > .card:nth-child(2) { transition-delay: .35s; }

/* Unpicked cards sit back; the chosen one straightens and lifts. */
.team.has-pick .card { filter: saturate(.65) brightness(.62); }
.team.has-pick .card[aria-pressed="true"] {
  filter: none;
  transform: rotate(0deg) translateY(-10px) scale(1.05);
  box-shadow: 0 22px 46px rgba(0, 0, 0, .7);
  z-index: 3;
}

.card:focus-visible { outline: 3px solid var(--gold); outline-offset: 4px; }

/* Bio drops in under the row rather than inside a card, so the photos stay the
   same size and nothing reflows sideways. */
.team__bio {
  margin-top: clamp(2.2rem, 4.5vw, 3.2rem);
  /* No rule until there is something under it - otherwise it is a divider
     dividing nothing. */
  border-top: 1px solid transparent;
  padding-top: 0;
  transition: border-color .3s ease;
  min-height: 0;          /* nothing picked yet - do not reserve empty space */
}
.team.has-pick .team__bio { min-height: 8rem; border-top-color: var(--line); padding-top: clamp(1.6rem, 3vw, 2.4rem); }
.team__bio h3 { margin-bottom: .5rem; }
/* margin:0 was fine when a bio was always one paragraph. About carries two,
   and they ran together as one block with a bare line break between them. */
.team__bio p { color: var(--warm-1); max-width: 56ch; margin: 0; }
.team__bio p + p { margin-top: .9rem; }
.team__bio .role { color: var(--gold); font-size: var(--fs-label); font-weight: 700;
                   letter-spacing: .18em; text-transform: uppercase; margin-bottom: .5rem; }
.team__bio > div { animation: bioIn .4s ease both; }
@keyframes bioIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .card { opacity: 1; transform: rotate(var(--tilt)); transition: none; }
  .team.has-pick .card[aria-pressed="true"] { transform: rotate(0deg); }
  .team__bio > div { animation: none; }
}

/* ==========================================================================
   Testimonials
   ==========================================================================
   Three real reviews with the client's own photo, lifted from
   interviews.danwrightmedia.com. Photos are self-hosted like everything else
   rather than hot-linked from GoHighLevel's storage.

   This is the one section on the page where the footage stops. Everything above
   it is moving film behind text; here the motion cuts out and a lit card sits on
   black. After six scenes of video the stillness is what catches the eye - the
   section is loud by being the only quiet one.

   Scrolling is native CSS scroll-snap, so swipe works on a phone with no JS at
   all. The script only syncs the dots and handles taps - if it fails you can
   still swipe.
   ========================================================================== */

/* Sections that deliberately carry no footage.
   Named scene--nofilm, not .still - .still is the scattered print below, and the
   two collided: the contact section inherited position:absolute and collapsed to
   nothing. Geometry check caught it; it was invisible in a screenshot. */
.scene--nofilm, .quotes-scene { background: #0a0a0c; }

.quotes-scene { position: relative; }

/* --------------------------------------------------------- scattered stills
 * The work the reviews are about, thrown down like a pile of prints: four
 * client interview films from interviews.danwrightmedia.com and four frames
 * shot on the day. They land on scroll, same physical language as the team
 * polaroids higher up the page.
 *
 * Each still carries its own --x/--y/--r/--w/--d, so a position is one line and
 * the mobile layout is the same four variables with different numbers.
 */
.stills { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }

.still {
  position: absolute;
  left: var(--x); top: var(--y); width: var(--w);
  height: auto; border-radius: 2px;
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: 0 14px 34px rgba(0,0,0,.55);
  filter: saturate(.82) contrast(1.06) brightness(.9);
  opacity: 0;
  /* --px/--py are the parallax offsets, written by the script. Keeping them in
     the transform means the landing animation, the tilt and the parallax are one
     property rather than three fighting over it. */
  transform: translate3d(var(--px, 0px), calc(var(--py, 0px) - 30px), 0) rotate(0deg) scale(.96);
  transition: opacity .55s ease var(--d, 0ms), transform .75s cubic-bezier(.2,.8,.26,1) var(--d, 0ms);
}
.quotes-scene.lit .still {
  opacity: .62;
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0) rotate(var(--r)) scale(1);
}

/* Once the prints have landed the transform must stop being transitioned, or
   every parallax frame gets smeared through a 0.75s ease and the whole layer
   lags behind the scroll. */
.quotes-scene.parallax-live .still {
  transition: opacity .55s ease;
  will-change: transform;
}

/* Depth. Bigger, nearer-looking prints move most. Nothing moves as one sheet -
   that is the difference between parallax and a slide. */
.still:nth-child(1) { --depth: .42; }
.still:nth-child(2) { --depth: .22; }
.still:nth-child(3) { --depth: .50; }
.still:nth-child(4) { --depth: .18; }
.still:nth-child(5) { --depth: .34; }
.still:nth-child(6) { --depth: .46; }
.still:nth-child(7) { --depth: .28; }
.still:nth-child(8) { --depth: .16; }

@media (prefers-reduced-motion: reduce) {
  .still { transition: none; opacity: .62; transform: rotate(var(--r)); }
}

/* positions - deliberately uneven, and clear of the middle where the card sits */
.still:nth-child(1) { --x: -3%; --y:  3%; --r: -7deg; --w: 27%; --d:   0ms; }
.still:nth-child(2) { --x: 20%; --y: -5%; --r:  5deg; --w: 23%; --d:  90ms; }
.still:nth-child(3) { --x: 70%; --y:  1%; --r:  7deg; --w: 29%; --d:  50ms; }
.still:nth-child(4) { --x: 86%; --y: 27%; --r: -5deg; --w: 22%; --d: 180ms; }
.still:nth-child(5) { --x: -6%; --y: 42%; --r:  6deg; --w: 25%; --d: 140ms; }
.still:nth-child(6) { --x: 76%; --y: 63%; --r: -8deg; --w: 27%; --d: 230ms; }
.still:nth-child(7) { --x:  5%; --y: 74%; --r:  4deg; --w: 24%; --d: 200ms; }
.still:nth-child(8) { --x: 42%; --y: 86%; --r: -4deg; --w: 21%; --d: 280ms; }

/* Scrim: darkest through the middle so the card always wins, letting the
   stills read at the edges where nothing has to be legible. */
.quotes-scene::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    radial-gradient(78% 46% at 50% 50%,
      rgba(10,10,12,.9) 0%, rgba(10,10,12,.72) 46%, rgba(10,10,12,.3) 78%, transparent 100%),
    linear-gradient(180deg, rgba(10,10,12,.8) 0%, rgba(10,10,12,.08) 9%, rgba(10,10,12,.08) 91%, rgba(10,10,12,.8) 100%);
}

/* On a phone there is no room for a pile. Four prints at the corners, larger,
   so they read as photographs rather than confetti. */
@media (max-width: 700px) {
  .still:nth-child(n+6) { display: none; }
  .still:nth-child(1) { --x: -14%; --y:  3%; --r: -8deg; --w: 54%; }
  .still:nth-child(2) { --x:  58%; --y:  1%; --r:  6deg; --w: 56%; }
  .still:nth-child(3) { --x: -16%; --y: 82%; --r:  5deg; --w: 52%; }
  .still:nth-child(4) { --x:  62%; --y: 87%; --r: -6deg; --w: 54%; }
  .still:nth-child(5) { --x:  26%; --y: 91%; --r:  3deg; --w: 42%; }
  .quotes__track { grid-auto-columns: 84%; }
  /* room above and below the card for the prints to actually read */
  .quotes-scene .scene__inner { padding-block: calc(var(--band) + 7vh); }
}

.quotes-scene .scene__inner { z-index: 2; padding-block: calc(var(--band) + 3vh); }

.quotes { margin-top: clamp(1.4rem, 3.5vw, 2rem); }

.quotes__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 86%;          /* next card peeks, so it reads as swipeable */
  gap: clamp(.9rem, 3vw, 1.6rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* let the shadow breathe instead of being clipped by the scroll box */
  padding: 14px 0 22px;
  margin-inline: calc(var(--pad) * -1);
  padding-inline: var(--pad);
  scroll-padding-inline: var(--pad);

  /* Fade at whichever edge still has track to scroll, so a half-visible card
     bleeds out instead of being guillotined. Both values are written by the
     script from the scroll position - at either end the fade on that side goes
     to zero, because there is nothing more to suggest. */
  --edge-l: 0px;
  --edge-r: 0px;
  -webkit-mask-image: linear-gradient(90deg, transparent 0,
    #000 var(--edge-l), #000 calc(100% - var(--edge-r)), transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0,
    #000 var(--edge-l), #000 calc(100% - var(--edge-r)), transparent 100%);
}
.quotes__track::-webkit-scrollbar { display: none; }
@media (prefers-reduced-motion: reduce) { .quotes__track { scroll-behavior: auto; } }

@media (min-width: 760px)  { .quotes__track { grid-auto-columns: 60%; } }
@media (min-width: 1100px) { .quotes__track { grid-auto-columns: 44%; } }

/* the card */
.qt {
  position: relative;
  /* Centre, not start. With start alignment the snap point for card 3 sits past
     the end of the scroll range, so on a wide screen - where all three nearly fit -
     the middle card was unreachable and its dot could never light. Centring puts
     every snap point inside the range at any width, and gives a symmetric sliver
     of the neighbours either side to fade out. */
  scroll-snap-align: center;
  /* Cards match height, but the quote sits in a free middle row. Phil's review
     is twelve words and Nicola's is sixty - left to stretch, his sat at the top
     over a void. Centred, the short one reads as composed rather than unfinished,
     and every caption still lines up along the bottom. */
  display: grid; grid-template-rows: auto 1fr auto; gap: 1.15rem;
  margin: 0;
  padding: clamp(1.5rem, 5vw, 2.3rem);
  background: linear-gradient(168deg, #17171b 0%, #101013 62%, #0d0d10 100%);
  border: 1px solid rgba(230,193,122,.14);
  border-radius: 3px;
  box-shadow: 0 1px 0 rgba(255,255,255,.05) inset,
              0 18px 44px rgba(0,0,0,.6);

  /* Off-centre cards read as dimmed and are clickable - tapping one brings it to
     the middle. The whole card is the control, so there is nothing small to aim
     at on a phone. */
  cursor: pointer;
  /* --dim is written per frame from the card's distance off centre, so the
     reviews fade through each other as you move rather than snapping between
     two states when the active card changes. The short transition is only a
     smoother for the frames themselves. */
  opacity: calc(1 - var(--dim, .52) * .52);
  transition: opacity .18s linear, border-color .45s ease;
}

/* The dim depth was tuned at laptop width, where the off-centre cards are half
   out of frame anyway. On a 2500px screen all three sit fully visible and the
   far one at .48 reads as broken rather than as "not the one you are on". */
@media (min-width: 1500px) {
  .qt { opacity: calc(1 - var(--dim, .52) * .16); }
}
.qt.is-active {
  opacity: 1;
  cursor: default;
  border-color: rgba(230,193,122,.34);
}

/* The track used to be the focus target, which drew the browser's default ring
   around all three reviews at once. Focus lives on the cards now, in gold. */
.qt:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }
.quotes__track:focus { outline: none; }

@media (prefers-reduced-motion: reduce) { .qt { transition: none; } }

/* the gold mark that says "quote" before you read a word */
.qt__mark {
  position: absolute; top: -.22em; left: .34em;
  font-size: clamp(5rem, 15vw, 7rem); line-height: 1;
  font-weight: 800; color: var(--gold); opacity: .17;
  pointer-events: none; user-select: none;
}

.qt__stars { position: relative; margin: 0; color: var(--gold); font-size: .8rem; letter-spacing: .3em; }

.qt blockquote {
  position: relative; margin: 0;
  font-size: clamp(1.02rem, 2.4vw, 1.28rem);
  line-height: 1.5;
  font-weight: 500;
  color: #f2f0ee;
  letter-spacing: -.005em;
  align-self: center;
}

.qt__who {
  display: flex; align-items: center; gap: .85rem;
  padding-top: .95rem; margin-top: .3rem;
  border-top: 1px solid rgba(255,255,255,.08);
}
.qt__face {
  width: 60px; height: 60px; flex: 0 0 60px;
  border-radius: 50%; object-fit: cover; background: #222;
  box-shadow: 0 0 0 1px rgba(230,193,122,.55), 0 0 0 5px rgba(230,193,122,.09);
}
.qt__name { display: block; font-weight: 800; color: var(--white); line-height: 1.2; }
.qt__role { display: block; margin-top: .1rem; font-size: .78rem; color: var(--warm-2); line-height: 1.35; }

/* dots */
.quotes__dots { display: flex; justify-content: center; gap: .5rem; margin-top: .4rem; }
.quotes__dot {
  width: 26px; height: 3px; padding: 0; border: 0; cursor: pointer;
  background: rgba(255,255,255,.2); transition: background .25s ease;
}
.quotes__dot[aria-selected="true"] { background: var(--gold); }
.quotes__dot:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }

/* ==========================================================================
   Showreel box
   ==========================================================================
   Sits beside the promise, so the claim and the proof of it are next to each
   other. It is a poster and a button, not an embed: Vimeo's player is ~900KB
   and this page already opens on a full-screen video, so the reel only loads
   for someone who asks for it.
   ========================================================================== */

.promise { display: grid; gap: clamp(2.2rem, 5vw, 3.6rem); }
@media (min-width: 1000px) {
  /* Fractions, not a percentage. The wrap caps at 1240px, so a 34% column was
     frozen at ~373px no matter how wide the screen - 19% of a 1920 display, and
     it read as a thumbnail rather than the showreel. Fractions split whatever
     the wrap actually is, and the reel holds its share. */
  .promise {
    grid-template-columns: minmax(0, 1fr) minmax(400px, .95fr);
    align-items: center;
  }
  /* The client list runs full width under both columns on a wide screen. On a
     phone the grid collapses and DOM order takes over - claim, reel, clients -
     which keeps the reel near the top instead of below the list. */
  .promise__say { grid-column: 1; grid-row: 1; }
  .reelbox      { grid-column: 2; grid-row: 1; }
  .promise .proof { grid-column: 1 / -1; grid-row: 2; margin-top: .6rem; }
  /* --fs-mega is sized against the viewport, which was right when the headline
     had the full width. Sharing the row with the reel it ran to five lines and
     left the box stranded halfway down. Size it to its column instead. */
  .promise__say h1 { font-size: clamp(2.7rem, 4.2vw, 4.1rem); }
}

.reelbox { display: grid; gap: .85rem; }
.reelbox__k { margin: 0; }

.reelbox__open {
  position: relative; display: block; width: 100%; padding: 0;
  border: 1px solid rgba(230,193,122,.3); border-radius: 3px;
  background: #000; cursor: pointer; overflow: hidden;
  box-shadow: 0 18px 44px rgba(0,0,0,.55);
  transition: border-color .3s ease, box-shadow .3s ease, transform .3s ease;
}
.reelbox__open img { display: block; width: 100%; height: auto; opacity: .94; transition: opacity .3s ease; }
.reelbox__open:hover,
.reelbox__open:focus-visible {
  border-color: var(--gold);
  box-shadow: 0 22px 54px rgba(0,0,0,.65);
  transform: translateY(-2px);
}
.reelbox__open:hover img, .reelbox__open:focus-visible img { opacity: 1; }
.reelbox__open:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

/* play mark - a gold disc with a triangle cut from borders, no icon font */
.reelbox__go {
  position: absolute; inset: 50% auto auto 50%; translate: -50% -50%;
  width: 62px; height: 62px; border-radius: 50%;
  background: var(--gold);
  display: grid; place-items: center;
  transition: scale .3s cubic-bezier(.2,.8,.26,1);
}
.reelbox__go::after {
  content: ""; width: 0; height: 0; margin-left: 4px;
  border-left: 17px solid #0a0a0c;
  border-top: 11px solid transparent;
  border-bottom: 11px solid transparent;
}
.reelbox__open:hover .reelbox__go { scale: 1.08; }

.reelbox__vid { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: 3px; }

.reelbox__meta {
  display: flex; justify-content: space-between; gap: 1rem; margin: 0;
  font-size: .78rem; color: var(--warm-2);
}
.reelbox__meta span:last-child { font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .reelbox__open, .reelbox__open img, .reelbox__go { transition: none; }
}

/* --------------------------------------------------- deeper print pile (About) */
/* The homepage scatters eight prints. About scatters sixteen, so it needs eight
   more positions and eight more depths. Scoped to --dense so the homepage keeps
   its own arrangement untouched.
   Positions avoid the middle band, where the quote card sits. */
.quotes-scene--dense .still:nth-child(9)  { --x: 33%; --y: -7%; --r:  6deg; --w: 20%; --d: 120ms; }
.quotes-scene--dense .still:nth-child(10) { --x: 58%; --y: 88%; --r: -6deg; --w: 22%; --d: 320ms; }
.quotes-scene--dense .still:nth-child(11) { --x: -8%; --y: 20%; --r:  8deg; --w: 19%; --d: 260ms; }
.quotes-scene--dense .still:nth-child(12) { --x: 88%; --y: 50%; --r:  5deg; --w: 20%; --d: 160ms; }
.quotes-scene--dense .still:nth-child(13) { --x: 17%; --y: 92%; --r: -7deg; --w: 23%; --d: 350ms; }
.quotes-scene--dense .still:nth-child(14) { --x: 90%; --y:  6%; --r: -5deg; --w: 18%; --d: 210ms; }
.quotes-scene--dense .still:nth-child(15) { --x: -5%; --y: 60%; --r: -6deg; --w: 21%; --d: 290ms; }
.quotes-scene--dense .still:nth-child(16) { --x: 68%; --y: 30%; --r:  7deg; --w: 17%; --d: 380ms; }

.quotes-scene--dense .still:nth-child(9)  { --depth: .38; }
.quotes-scene--dense .still:nth-child(10) { --depth: .24; }
.quotes-scene--dense .still:nth-child(11) { --depth: .48; }
.quotes-scene--dense .still:nth-child(12) { --depth: .20; }
.quotes-scene--dense .still:nth-child(13) { --depth: .44; }
.quotes-scene--dense .still:nth-child(14) { --depth: .30; }
.quotes-scene--dense .still:nth-child(15) { --depth: .36; }
.quotes-scene--dense .still:nth-child(16) { --depth: .26; }

/* Sixteen prints is more competing detail than eight, so they come back a
   little from the homepage's .62 - but only a little. The first pass went to
   .38 opacity AND .62 brightness, which multiplied out to roughly a quarter of
   the homepage's and read as a fault rather than as restraint. */
.quotes-scene--dense.lit .still { opacity: .55; }
.quotes-scene--dense .still { filter: saturate(.78) contrast(1.05) brightness(.82); }

/* No extra scrim here, and no z-index override. .quotes-scene::after above is
   already that scrim and it sits at z-index 1, which is exactly why
   .quotes-scene .scene__inner is at 2. Adding a --dense rule that put the
   card back down to 1 placed it on the same layer as the scrim, and at equal
   z-index the later element wins - so the scrim painted straight over the
   review. The text rendered at rgb(97,95,92) instead of rgb(242,240,238). */

/* ------------------------------------------------- prints beside the process rail
 * The stages describe what happens on a shoot, so the shoot runs down the side
 * of them. Same print language as the review pile - these are the same physical
 * object, and inventing a second one would just be noise.
 *
 * All of them live in the right-hand half: the rail and its copy own the left,
 * and a print behind a numbered step would fight the one thing the section is
 * for. .lit is set by the scene observer on any .scene, so the reveal is free.
 */
.process { position: relative; overflow: hidden; }
.process .stills { z-index: 0; }
.process .scene__inner { position: relative; z-index: 1; }
.process.lit .still {
  opacity: 1;
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0) rotate(var(--r)) scale(1);
}
.process .still { filter: saturate(.94) contrast(1.02) brightness(.96); }

.process .still:nth-child(1) { --x: 64%; --y:  2%; --r: -5deg; --w: 19%; --d:   0ms; }
.process .still:nth-child(2) { --x: 78%; --y: 15%; --r:  6deg; --w: 16%; --d:  90ms; }
.process .still:nth-child(3) { --x: 61%; --y: 34%; --r:  4deg; --w: 21%; --d: 160ms; }
.process .still:nth-child(4) { --x: 79%; --y: 52%; --r: -7deg; --w: 17%; --d: 230ms; }
.process .still:nth-child(5) { --x: 63%; --y: 68%; --r:  5deg; --w: 20%; --d: 300ms; }
.process .still:nth-child(6) { --x: 77%; --y: 86%; --r: -4deg; --w: 18%; --d: 370ms; }

/* A phone has no right-hand half - the copy is the full width, so the prints
   would sit behind the text rather than beside it. */
@media (max-width: 899px) { .process .stills { display: none; } }

/* A phone shows the first five and no more - sixteen at phone width is a wall
   of postage stamps, and the ones behind the card are never seen anyway. */
@media (max-width: 699px) {
  .quotes-scene--dense .still:nth-child(n + 6) { display: none; }
}

/* ------------------------------------------------------------ mobile menu */
/* .nav__links is display:none below 900px, so on a phone the nav had nothing
   to tap - that is what Dan hit. The burger owns the top right below that
   width, and the CTA moves into the panel rather than crowding the bar. */
.burger {
  display: none;
  margin-left: auto;
  width: 46px; height: 46px;          /* comfortably over the 44px tap target */
  padding: 0; border: 0; background: none; cursor: pointer;
  align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
@media (max-width: 899px) {
  .burger { display: inline-flex; }
  /* :not(.menu__cta) matters - the panel lives inside .nav, so hiding "the nav
     CTA" was also hiding the one inside the menu. */
  .nav .cta:not(.menu__cta) { display: none; }
}
.burger__box { position: relative; display: block; width: 22px; height: 14px; }
.burger__bar {
  position: absolute; left: 0; height: 2px; border-radius: 2px;
  background: var(--white);
  transition: width .32s cubic-bezier(.2,.9,.3,1.2),
              transform .32s cubic-bezier(.2,.9,.3,1.2),
              opacity .16s linear;
}
/* Deliberately unequal at rest and equal once it forms the X - the resolve is
   the bit that makes it feel made rather than defaulted. */
.burger__bar:nth-child(1) { top: 0;    width: 100%; }
.burger__bar:nth-child(2) { top: 6px;  width: 68%;  transform-origin: right center; }
.burger__bar:nth-child(3) { top: 12px; width: 84%; }

.menu-open .burger__bar:nth-child(1) { width: 100%; transform: translateY(6px) rotate(45deg); }
.menu-open .burger__bar:nth-child(2) { width: 0; opacity: 0; transition-duration: .14s; }
.menu-open .burger__bar:nth-child(3) { width: 100%; transform: translateY(-6px) rotate(-45deg); }
/* The middle bar clears out first, so the other two are not rotating through it. */
.menu-open .burger__bar:nth-child(1),
.menu-open .burger__bar:nth-child(3) { transition-delay: .07s; }

.menu {
  /* A sibling of <header>, not a child. .nav.stuck carries a backdrop-filter,
     which makes the header a containing block for position:fixed descendants -
     so once the page was scrolled the panel was laid out inside the 60px bar
     instead of over the viewport. Unscrolled there was no .stuck and no bug,
     which is exactly what Dan described. */
  position: fixed; inset: 0; z-index: 190;
  display: grid; align-content: center; justify-items: start;
  gap: clamp(.5rem, 2vh, 1rem);
  padding: 0 var(--pad);
  background: rgba(8, 8, 10, .96);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  opacity: 0; pointer-events: none;
  transition: opacity .28s ease;
}
.menu[hidden] { display: grid; }        /* hidden is the a11y state; opacity does the hiding */
.menu-open .menu { opacity: 1; pointer-events: auto; }

.menu__cta { margin-left: 0; }   /* .nav .cta (0,2,0) sets margin-left:auto to
                                          push the bar button right; a bare
                                          .menu__cta (0,1,0) loses to it. */
.menu__links { display: grid; gap: clamp(.3rem, 1.6vh, .9rem); }
.menu__social { display: flex; gap: .3rem; list-style: none; margin: clamp(1.4rem, 4vh, 2.6rem) 0 0; padding: 0;
  opacity: 0; transform: translateY(14px);
  transition: opacity .34s ease, transform .38s cubic-bezier(.2,.85,.3,1); }
.menu-open .menu__social { opacity: 1; transform: none; transition-delay: .32s; }
.menu__social a { display: grid; place-items: center; width: 46px; height: 46px;
  color: rgba(255,255,255,.6); border-radius: 50%; transition: color .18s ease, background-color .18s ease; }
.menu__social svg { width: 22px; height: 22px; display: block; }
.menu__social a:active, .menu__social a:hover { color: var(--white); background: rgba(255,255,255,.08); }
.menu__links a {
  display: block; padding: .25rem 0;
  color: var(--white); text-decoration: none;
  font-size: clamp(2rem, 9vw, 3rem); font-weight: 800;
  letter-spacing: -.03em; line-height: 1.08;
  opacity: 0; transform: translateY(14px);
  transition: opacity .34s ease, transform .38s cubic-bezier(.2,.85,.3,1);
}
.menu__cta {
  margin-top: clamp(1rem, 3vh, 2rem);
  opacity: 0; transform: translateY(14px);
  transition: opacity .34s ease, transform .38s cubic-bezier(.2,.85,.3,1);
}
/* Staggered so the list arrives rather than appears. */
.menu-open .menu__links a,
.menu-open .menu__cta { opacity: 1; transform: none; }
.menu-open .menu__links a:nth-child(1) { transition-delay: .06s; }
.menu-open .menu__links a:nth-child(2) { transition-delay: .11s; }
.menu-open .menu__links a:nth-child(3) { transition-delay: .16s; }
.menu-open .menu__links a:nth-child(4) { transition-delay: .21s; }
.menu-open .menu__cta { transition-delay: .27s; }

@media (prefers-reduced-motion: reduce) {
  .burger__bar, .menu, .menu__links a, .menu__cta { transition-duration: .01ms; }
  .menu__links a, .menu__cta { transform: none; }
}

/* ------------------------------------------------------- team, without film */
/* Every other band on the homepage sits on moving footage. Dan's note was that
   this dilutes the work further down, so this one is a still surface: a soft
   pool of light with real grain over it. It reads as a table under a lamp,
   which is the right ground for a set of prints, and it costs no video. */
.team-scene {
  position: relative;
  background:
    radial-gradient(120% 88% at 50% 34%, #17171b 0%, #101013 46%, #0a0a0c 100%);
}
.team-scene__grain {
  position: absolute; inset: 0; pointer-events: none;
  opacity: .22; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}
.team-scene .scene__inner { position: relative; z-index: 1; }

/* ------------------------------------------- menu on a short/landscape screen */
/* An iPhone in landscape is 844x390. The panel is laid out for a tall viewport,
   so the first link sat at y=23 - underneath the fixed bar. Two columns uses
   the shape of the screen instead of fighting it. */
.menu { overflow-y: auto; }
@media (orientation: landscape) and (max-height: 560px) {
  .menu {
    grid-template-columns: 1fr auto;
    /* safe centre: centred while it fits, and pinned to the top edge the moment
       it would not. Plain `center` overflows in BOTH directions, which is how
       the links ended up behind the logo. --navh is measured by the script, so
       the stop is the bar's real height rather than a guess. */
    align-content: safe center;
    column-gap: clamp(2rem, 8vw, 6rem);
    row-gap: clamp(.5rem, 3vh, 1rem);
    padding-top: calc(var(--navh, 60px) + 14px);
    padding-bottom: 1.2rem;
  }
  .menu__links { grid-column: 1; grid-row: 1 / span 2; align-content: center; }
  .menu__links a { font-size: clamp(1.4rem, 11vh, 2.3rem); }
  .menu__cta { grid-column: 2; grid-row: 1; margin-top: 0; align-self: end; }
  .menu__social { grid-column: 2; grid-row: 2; margin-top: .4rem; align-self: start; }
}

/* ---------------------------------------------- work examples: a fixed veil */
/* These were on the shared scene veil, which lifts from .62 to 1 as the text
   arrives - so the footage visibly dimmed while you were watching it. Dan's
   note: hold it still, keep the top bright where the work is actually playing,
   and let the darkening happen only where the type sits.
   :has(.case__inner) picks out exactly the four case bands and nothing else. */
.scene:has(.case__inner) .scene__bg::after {
  opacity: 1;
  transition: none;
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(0,0,0,.06) 34%,
    rgba(0,0,0,.42) 60%,
    rgba(0,0,0,.82) 82%,
    rgba(0,0,0,.92) 100%);
}

/* ------------------------------------------------- headline: hold, then riffle */
/* It sat on "project" for 260ms before taking off, which read as a glitch
   rather than a decision. It now holds for 1.6s with a rumble under it - the
   word straining before it goes. */
/* Still, then a build. Amplitude grows with t^2 so nothing much happens for the
   first half and it is really going by the end - the word straining before it
   lets go. Steps are spaced on the golden angle so it never settles into a
   rhythm and start reading as a loop. */
@keyframes cyc-build {
  0% { transform: translate3d(0.00px, 0.00px, 0) rotate(0.00deg); }
  2.273% { transform: translate3d(-0.00px, 0.00px, 0) rotate(-0.00deg); }
  4.545% { transform: translate3d(0.00px, -0.00px, 0) rotate(-0.00deg); }
  6.818% { transform: translate3d(0.00px, 0.00px, 0) rotate(0.00deg); }
  9.091% { transform: translate3d(-0.01px, -0.00px, 0) rotate(-0.00deg); }
  11.36% { transform: translate3d(0.02px, -0.01px, 0) rotate(0.00deg); }
  13.64% { transform: translate3d(-0.01px, 0.02px, 0) rotate(0.00deg); }
  15.91% { transform: translate3d(-0.02px, -0.03px, 0) rotate(-0.01deg); }
  18.18% { transform: translate3d(0.06px, 0.01px, 0) rotate(0.00deg); }
  20.45% { transform: translate3d(-0.07px, 0.02px, 0) rotate(0.01deg); }
  22.73% { transform: translate3d(0.04px, -0.06px, 0) rotate(-0.02deg); }
  25% { transform: translate3d(0.04px, 0.08px, 0) rotate(0.01deg); }
  27.27% { transform: translate3d(-0.13px, -0.05px, 0) rotate(0.01deg); }
  29.55% { transform: translate3d(0.17px, -0.03px, 0) rotate(-0.03deg); }
  31.82% { transform: translate3d(-0.12px, 0.12px, 0) rotate(0.03deg); }
  34.09% { transform: translate3d(-0.03px, -0.17px, 0) rotate(-0.00deg); }
  36.36% { transform: translate3d(0.22px, 0.12px, 0) rotate(-0.04deg); }
  38.64% { transform: translate3d(-0.32px, 0.01px, 0) rotate(0.06deg); }
  40.91% { transform: translate3d(0.25px, -0.18px, 0) rotate(-0.03deg); }
  43.18% { transform: translate3d(-0.01px, 0.29px, 0) rotate(-0.04deg); }
  45.45% { transform: translate3d(-0.30px, -0.24px, 0) rotate(0.08deg); }
  47.73% { transform: translate3d(0.51px, 0.04px, 0) rotate(-0.06deg); }
  50% { transform: translate3d(-0.46px, 0.23px, 0) rotate(-0.02deg); }
  52.27% { transform: translate3d(0.12px, -0.43px, 0) rotate(0.10deg); }
  54.55% { transform: translate3d(0.35px, 0.41px, 0) rotate(-0.11deg); }
  56.82% { transform: translate3d(-0.72px, -0.15px, 0) rotate(0.02deg); }
  59.09% { transform: translate3d(0.73px, -0.25px, 0) rotate(0.10deg); }
  61.36% { transform: translate3d(-0.32px, 0.58px, 0) rotate(-0.16deg); }
  63.64% { transform: translate3d(-0.35px, -0.63px, 0) rotate(0.08deg); }
  65.91% { transform: translate3d(0.93px, 0.32px, 0) rotate(0.08deg); }
  68.18% { transform: translate3d(-1.07px, 0.22px, 0) rotate(-0.20deg); }
  70.45% { transform: translate3d(0.62px, -0.72px, 0) rotate(0.16deg); }
  72.73% { transform: translate3d(0.26px, 0.89px, 0) rotate(0.03deg); }
  75% { transform: translate3d(-1.12px, -0.57px, 0) rotate(-0.22deg); }
  77.27% { transform: translate3d(1.46px, -0.12px, 0) rotate(0.24deg); }
  79.55% { transform: translate3d(-1.03px, 0.83px, 0) rotate(-0.06deg); }
  81.82% { transform: translate3d(-0.05px, -1.17px, 0) rotate(-0.20deg); }
  84.09% { transform: translate3d(1.24px, 0.89px, 0) rotate(0.32deg); }
  86.36% { transform: translate3d(-1.88px, -0.07px, 0) rotate(-0.17deg); }
  88.64% { transform: translate3d(1.54px, -0.88px, 0) rotate(-0.14deg); }
  90.91% { transform: translate3d(-0.30px, 1.46px, 0) rotate(0.37deg); }
  93.18% { transform: translate3d(-1.26px, -1.28px, 0) rotate(-0.30deg); }
  95.45% { transform: translate3d(2.29px, 0.37px, 0) rotate(-0.03deg); }
  97.73% { transform: translate3d(-2.15px, 0.85px, 0) rotate(0.38deg); }
  100% { transform: translate3d(0.80px, -1.73px, 0) rotate(-0.44deg); }
}
.cyc--rumble { animation: cyc-build 1400ms linear forwards; }
@media (prefers-reduced-motion: reduce) { .cyc--rumble { animation: none; } }

/* ------------------------------------------------------ team head + hover */
/* The heading and the way through to About sit on one line. It wraps rather
   than shrinking, so on a phone the button drops under the heading at full
   size instead of becoming a thing too small to hit. */
.team__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(1rem, 3vw, 2.5rem); flex-wrap: wrap;
}
.team__head h2 { margin: 0; }
.team__more { flex: 0 0 auto; }


/* ------------------------------------------------------- human check box */
/* Visible reassurance, and one of three things the server checks. On its own a
   tick box stops nothing - a bot just posts human=on - so it is backed by the
   honeypot and a minimum fill time server-side. */
.hcheck {
  display: flex; align-items: center; gap: .7rem;
  margin-top: .2rem; cursor: pointer;
  font-size: .9375rem; color: var(--warm-1);
  user-select: none;
}
.hcheck input { position: absolute; opacity: 0; width: 0; height: 0; }
.hcheck__box {
  flex: 0 0 auto; width: 22px; height: 22px;
  border: 1px solid rgba(255,255,255,.42); border-radius: 3px;
  background: rgba(255,255,255,.04);
  display: grid; place-items: center;
  transition: background-color .18s ease, border-color .18s ease;
}
.hcheck__box::after {
  content: ""; width: 11px; height: 6px;
  border-left: 2px solid #23190a; border-bottom: 2px solid #23190a;
  transform: rotate(-45deg) scale(.4); transform-origin: center;
  opacity: 0; transition: opacity .16s ease, transform .2s cubic-bezier(.2,.9,.3,1.4);
  margin-top: -3px;
}
.hcheck input:checked + .hcheck__box { background: var(--gold); border-color: var(--gold); }
.hcheck input:checked + .hcheck__box::after { opacity: 1; transform: rotate(-45deg) scale(1); }
.hcheck input:focus-visible + .hcheck__box { outline: 2px solid var(--gold); outline-offset: 3px; }

/* ------------------------------------------- team row: Dan leads, two follow */
/* Dan is the one constant on a job, so his print leads. Aidan and James stack
   vertically beside him so the two of them together span his height - side by
   side they left a hole under Dan and made his print look oversized.
   The column ratio is tuned so the two stacked cards plus the button come out
   level with the bottom of Dan's card; it is checked by measuring, not by eye. */
@media (min-width: 760px) {
  .team__row {
    /* Capped rather than full-bleed. At the wrap's full width Dan's print came
       out 910px across, which is a poster, not a photograph on a table. The cap
       scales the whole group down together so the height match survives. */
    max-width: min(100%, 62rem);
    grid-template-columns: minmax(0, 1fr) minmax(0, .46fr);
    align-items: start;
    gap: clamp(1.2rem, 3.5vw, 2.6rem);
  }
  .team__rest { grid-template-columns: 1fr; gap: clamp(.6rem, 1.6vw, 1.1rem); }
}
.card--lead { --tilt: -3deg; }
.card--lead .card__shot { aspect-ratio: 1 / 1.16; }

.team__rest { display: grid; align-items: start; }
/* Sits outside .team__row on purpose. The row is capped at 62rem and sits left
   of the wrap's centre, so a button inside it centred on the group rather than
   on the page - visibly off by 152px at desktop widths. */
.team .team__more {
  display: block; width: max-content;
  margin: clamp(1.4rem, 3vw, 2.2rem) auto 0;
}
.card--small .card__name { font-size: clamp(.8rem, 1.5vw, 1rem); }
.card--small .card__role { font-size: clamp(.52rem, 1vw, .64rem); }
.card--small { padding: 5% 5% 0; }

/* Phone: same hierarchy, different shape. Dan across the top, the other two
   side by side beneath him - three equal cards lost the point entirely. */
@media (max-width: 759px) {
  .team__row { grid-template-columns: 1fr 1fr; }
  .card--lead { grid-column: 1 / -1; max-width: 72%; margin-inline: auto; }
  .card--lead .card__shot { aspect-ratio: 1 / 1.1; }
  .team__rest { display: contents; }
  .team .team__more { margin-top: 1.2rem; }
}
