/* ==========================================================================
   Elevate Media HI
   Palette and typeface carried over from the Squarespace site: black, white,
   Elevate orange, and Poppins. The tonal steps between them are new, and so
   is everything else.

   Contents
     01  Tokens
     02  Reset and base
     03  Type scale
     04  Buttons, labels, links
     05  Motion primitives (reveal, marquee, spotlight)
     06  Nav
     07  Hero
     08  Kinetic band
     09  Services
     10  Work
     11  About
     12  Clients
     13  Process
     14  Contact
     15  Footer
     16  Reduced motion
   ========================================================================== */

/* 01  Tokens ============================================================== */

:root {
  /* The four colors the old site used. Everything else is a tonal step. */
  --ink: #000000;
  --paper: #ffffff;
  --accent: #ff7c1a;
  --panel: #282828;   /* kept for the record: the old site's dark grey. No
                         longer applied anywhere; the studio section that used
                         it now sits on --ink-1 with the rest of the page. */

  /* Tonal steps inside the black, so dark surfaces can sit on each other */
  --ink-0: #070707;
  --ink-1: #0e0e0e;
  --ink-2: #151515;
  --ink-3: #1c1c1c;
  --line: rgba(255, 255, 255, .11);
  --line-2: rgba(255, 255, 255, .06);

  /* Orange, deepened for gradients and pressed states */
  --accent-deep: #e86c00;
  --accent-glow: rgba(255, 124, 26, .38);

  --text: #ffffff;
  --muted: rgba(255, 255, 255, .58);
  /* .52 is the lowest alpha that clears 4.5:1 against --ink-0 with margin. */
  --faint: rgba(255, 255, 255, .52);

  /* Type. Heading line height of .9 is the old site's signature, kept. */
  --font: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --wrap: 1200px;
  --wide: 1560px;
  --gutter: clamp(20px, 4vw, 56px);
  --nav-h: 76px;

  --ease: cubic-bezier(.22, 1, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);

  color-scheme: dark;
}

/* 02  Reset and base ====================================================== */

@font-face { font-family: "Poppins"; font-style: normal; font-weight: 300; font-display: swap; src: url("../fonts/poppins-300.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/poppins-400.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 500; font-display: swap; src: url("../fonts/poppins-500.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 600; font-display: swap; src: url("../fonts/poppins-600.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 700; font-display: swap; src: url("../fonts/poppins-700.woff2") format("woff2"); }

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

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

body {
  margin: 0;
  background: var(--ink-0);
  color: var(--text);
  font-family: var(--font);
  font-weight: 300;
  font-size: 16px;
  line-height: 1.8;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

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

/* The grain sits over everything and is what keeps the flat blacks from
   looking like flat blacks. One repeating SVG, no image request. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.wrap { width: min(100% - var(--gutter) * 2, var(--wrap)); margin-inline: auto; }
.wrap-wide { width: min(100% - var(--gutter) * 2, var(--wide)); margin-inline: auto; }

section { position: relative; }
.sec { padding-block: clamp(72px, 8vw, 122px); }
.sec-tight { padding-block: clamp(56px, 7vw, 96px); }

[hidden] { display: none !important; }

::selection { background: var(--accent); color: #000; }

:where(a, button, input, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

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

.skip {
  position: absolute; left: 12px; top: -60px; z-index: 200;
  background: var(--accent); color: #000; padding: 10px 18px;
  font-weight: 600; text-decoration: none; transition: top .2s;
}
.skip:focus { top: 12px; }

/* 03  Type scale ========================================================== */

h1, h2, h3, h4 {
  font-weight: 500;
  line-height: .9;          /* carried from the old site, it is the look */
  letter-spacing: -.02em;
  margin: 0;
  text-wrap: balance;
}

.display {
  font-weight: 600;
  font-size: clamp(2.9rem, 9.4vw, 8.4rem);
  line-height: .86;
  letter-spacing: -.035em;
  text-transform: uppercase;
}

.h1 { font-size: clamp(2.6rem, 6.4vw, 5.6rem); text-transform: uppercase; letter-spacing: -.03em; }
.h2 { font-size: clamp(2rem, 4.4vw, 3.6rem); text-transform: uppercase; }
.h3 { font-size: clamp(1.4rem, 2.2vw, 2.1rem); }

p { margin: 0 0 1.2em; }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  line-height: 1.65;
  color: var(--muted);
  max-width: 54ch;
}

.muted { color: var(--muted); }

/* Outlined type. Dropped to a thinner stroke on small screens or it turns
   into mud at display sizes. */
.stroke {
  color: transparent;
  -webkit-text-stroke: 2.5px var(--accent);
  paint-order: stroke fill;
}
@media (max-width: 720px) { .stroke { -webkit-text-stroke-width: 1.4px; } }

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

/* 04  Buttons, labels, links ============================================== */

.label {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 22px;
}
.label::before {
  content: "";
  width: 26px; height: 1px;
  background: currentColor;
  opacity: .7;
}

.btn {
  --btn-bg: var(--accent);
  --btn-fg: #000;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 17px 34px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid var(--btn-bg);
  border-radius: 0;
  cursor: pointer;
  overflow: hidden;
  transition: color .3s var(--ease), border-color .3s var(--ease), background-color .3s var(--ease);
  will-change: transform;
}
/* The fill wipes up from the bottom rather than cross-fading. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--paper);
  transform: translateY(101%);
  transition: transform .45s var(--ease);
  z-index: 0;
}
.btn > * { position: relative; z-index: 1; }
.btn:hover::before, .btn:focus-visible::before { transform: translateY(0); }
.btn:hover, .btn:focus-visible { color: #000; border-color: var(--paper); }

.btn-ghost {
  --btn-bg: transparent;
  --btn-fg: var(--text);
  border-color: rgba(255, 255, 255, .28);
}
.btn-ghost::before { background: var(--accent); }
.btn-ghost:hover, .btn-ghost:focus-visible { color: #000; border-color: var(--accent); }

.btn-sm { padding: 13px 24px; font-size: .72rem; }

.arrow {
  width: 1em; height: 1em;
  flex: none;
  transition: transform .35s var(--ease);
}
.btn:hover .arrow { transform: translateX(4px); }

/* A quiet text link that draws its own underline on hover */
.tlink {
  color: var(--text);
  text-decoration: none;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size .4s var(--ease), color .3s;
  padding-bottom: 2px;
}
.tlink:hover, .tlink:focus-visible { background-size: 100% 1px; color: var(--accent); }

/* The peak lifted out of the logo's A. Used as a bullet, a divider and a cue. */
.peak { width: .62em; height: .9em; fill: currentColor; flex: none; }

/* 05  Motion primitives =================================================== */

/* Nothing is hidden until JS says it can be. If the script never runs, the
   page is simply a static page rather than a blank one. */
.js [data-reveal] { opacity: 0; transform: translateY(26px); }
.js [data-reveal].in {
  opacity: 1;
  transform: none;
  transition: opacity .85s var(--ease-out), transform .85s var(--ease-out);
}
.js [data-reveal="fade"] { transform: none; }
.js [data-reveal].in { transition-delay: var(--d, 0ms); }

/* Set by the script when the reveal finishes. Without this, every revealed
   element keeps its transform transition and stagger delay forever, and any
   later transform on the same element has to fight them. */
.js [data-reveal].in.settled,
.js .line.in.settled > span { transition: none; transition-delay: 0s; }

/* Line-by-line reveal for display headings. The mask is the wrapper. */
.lines { display: block; }
.line { display: block; overflow: hidden; padding-bottom: .06em; }
.line > span { display: block; }
.js .line > span { transform: translateY(105%); }
.js .line.in > span {
  transform: none;
  transition: transform 1s var(--ease-out);
  transition-delay: var(--d, 0ms);
}

/* Cursor-reactive tile. Two stacked layers of the peak mark: one flat and
   almost invisible, one brighter but masked to a circle that follows the
   pointer. JS only writes two custom properties. */
/* The peak from the logo, tiled as a criss-cross lattice: upright marks on a
   square grid with an inverted one dropped into every gap. The two sets read
   as diagonals crossing each other instead of rows and columns. */
[data-spotlight] {
  --pat-tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='144' viewBox='0 0 200 144'%3E%3Cg fill='%23ff7c1a' fill-rule='evenodd'%3E%3Cpath transform='translate(-38.00 -54.65) scale(0.5205)' d='M73 0L146 210H0ZM73 102L102 210H41Z'/%3E%3Cpath transform='translate(162.00 -54.65) scale(0.5205)' d='M73 0L146 210H0ZM73 102L102 210H41Z'/%3E%3Cpath transform='translate(-38.00 89.35) scale(0.5205)' d='M73 0L146 210H0ZM73 102L102 210H41Z'/%3E%3Cpath transform='translate(162.00 89.35) scale(0.5205)' d='M73 0L146 210H0ZM73 102L102 210H41Z'/%3E%3Cpath transform='translate(138.00 126.65) rotate(180) scale(0.5205)' d='M73 0L146 210H0ZM73 102L102 210H41Z'/%3E%3C/g%3E%3C/svg%3E");
}

.pat, .pat-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--pat-tile);
  background-size: 184px 133px;
}
.pat { opacity: .028; }
.pat-glow {
  opacity: 0;
  transition: opacity .5s var(--ease);
  -webkit-mask-image: radial-gradient(260px circle at var(--mx, 50%) var(--my, 50%), #000 0%, transparent 72%);
  mask-image: radial-gradient(260px circle at var(--mx, 50%) var(--my, 50%), #000 0%, transparent 72%);
}
.pat-lit .pat-glow { opacity: .16; }

/* Marquee. Six copies of the word set at 120s so ultrawide screens never
   catch up with the end of the track. */
.mq { overflow: hidden; }
.mq-track {
  display: flex;
  width: max-content;
  animation: mq 56s linear infinite;
}
.mq-track > * { flex: none; }
@keyframes mq { from { transform: translateX(0); } to { transform: translateX(-50%); } }

.hairline { height: 1px; background: var(--line); border: 0; margin: 0; }

/* 06  Nav ================================================================= */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 100;
  display: flex;
  align-items: center;
  min-height: var(--nav-h);
  padding-inline: var(--gutter);
  background: transparent;
  transition: background-color .4s var(--ease), border-color .4s var(--ease), min-height .4s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.stuck {
  min-height: 62px;
  background: rgba(7, 7, 7, .82);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--line-2);
}

.nav-inner {
  width: 100%;
  max-width: var(--wide);
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 28px;
}

.nav-logo { display: block; flex: none; }
.nav-logo img { height: 30px; width: auto; }
@media (max-width: 720px) { .nav-logo img { height: 24px; } }

.nav-links {
  display: flex;
  align-items: center;
  gap: 34px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav-links a {
  color: var(--text);
  text-decoration: none;
  font-size: .78rem;
  font-weight: 400;
  letter-spacing: .16em;
  text-transform: uppercase;
  opacity: .78;
  transition: opacity .25s, color .25s;
}
.nav-links a:hover, .nav-links a:focus-visible { opacity: 1; color: var(--accent); }

.nav-cta { display: inline-flex; }

.burger {
  display: none;
  width: 44px; height: 44px;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--line);
  color: var(--text);
  cursor: pointer;
  padding: 0;
}
.burger span {
  display: block;
  width: 18px; height: 1.5px;
  background: currentColor;
  position: relative;
  transition: background-color .2s;
}
.burger span::before, .burger span::after {
  content: "";
  position: absolute;
  left: 0;
  width: 18px; height: 1.5px;
  background: currentColor;
  transition: transform .3s var(--ease);
}
.burger span::before { top: -6px; }
.burger span::after { top: 6px; }
.burger[aria-expanded="true"] span { background-color: transparent; }
.burger[aria-expanded="true"] span::before { transform: translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] span::after { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 860px) {
  .burger { display: inline-flex; }
  .nav-links, .nav-cta { display: none; }

  .nav-menu {
    position: fixed;
    inset: 0;
    z-index: 99;
    background: var(--ink-0);
    display: grid;
    align-content: center;
    justify-items: start;
    gap: 6px;
    padding: 0 var(--gutter);
  }
  .nav-menu[hidden] { display: none !important; }
  .nav-menu a {
    color: var(--text);
    text-decoration: none;
    font-size: clamp(2rem, 9vw, 3.4rem);
    font-weight: 500;
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: -.02em;
  }
  .nav-menu a:hover { color: var(--accent); }
  .nav-menu .btn { margin-top: 26px; }
  body.menu-open { overflow: hidden; }
}
@media (min-width: 861px) { .nav-menu { display: none !important; } }

/* 07  Hero ================================================================ */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  padding-block: calc(var(--nav-h) + 40px) clamp(40px, 6vw, 72px);
  overflow: hidden;
  isolation: isolate;
}

.hero-media { position: absolute; inset: 0; z-index: -2; }
.hero-media video, .hero-media img {
  position: absolute;
  top: 50%; left: 50%;
  width: max(100vw, 178vh);
  height: max(56.25vw, 100%);
  transform: translate(-50%, -50%);
  object-fit: cover;
  filter: grayscale(.72) contrast(1.08) brightness(.72);
}

/* Three stops so the footage reads at the top, the type reads at the bottom,
   and the section resolves into solid ink with no visible seam. */
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(to bottom, rgba(7,7,7,.62) 0%, rgba(7,7,7,.30) 34%, rgba(7,7,7,.86) 78%, var(--ink-0) 99%),
    radial-gradient(120% 80% at 18% 88%, var(--accent-glow) 0%, transparent 58%);
}

/* The explicit width and height are load bearing. A canvas is a replaced
   element with an intrinsic 300x150, and an absolutely positioned replaced
   element with auto sizing keeps that intrinsic size and ignores inset, so
   the particle field was only ever drawing into a 300x150 box in the corner
   of the hero rather than across it. */
#hero-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
}

.hero-inner {
  position: relative;
  display: grid;
  gap: clamp(30px, 4vw, 54px);
}

.hero-title { margin: 0; }
.hero-title .line > span { padding-right: .04em; }

.hero-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(26px, 4vw, 60px);
}

.hero-copy { max-width: 44ch; }

.hero-actions { display: flex; flex-wrap: wrap; gap: 14px; }

.hero-stats {
  display: flex;
  gap: clamp(26px, 4vw, 56px);
  margin: 0;
  padding: 0;
}
.hero-stats div { display: grid; gap: 2px; }
.hero-stats dt {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--faint);
  order: 2;
}
.hero-stats dd {
  margin: 0;
  font-size: clamp(2rem, 3.4vw, 3rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -.03em;
  order: 1;
}
.hero-stats .accent { color: var(--accent); }

.scroll-cue {
  position: absolute;
  right: 18px;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  justify-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--faint);
  writing-mode: vertical-rl;
}
.scroll-cue::after {
  content: "";
  width: 1px; height: 54px;
  background: linear-gradient(var(--accent), transparent);
  animation: cue 2.4s var(--ease) infinite;
  transform-origin: top;
}
@keyframes cue { 0%, 100% { transform: scaleY(.3); opacity: .4; } 50% { transform: scaleY(1); opacity: 1; } }
@media (max-width: 1200px) { .scroll-cue { display: none; } }

@media (max-width: 560px) {
  .hero-stats { gap: 18px; }
  .hero-stats dt { font-size: .58rem; letter-spacing: .14em; }
}

/* 08  Kinetic band ======================================================== */

/* Room above and below for the ribbon's rise and fall across the viewport. */
.kinetic-wrap { overflow: hidden; padding-block: clamp(30px, 3.6vw, 56px); }

.kinetic {
  background: var(--accent);
  color: #000;
  padding-block: clamp(24px, 3vw, 40px);
  border-block: 2px solid var(--ink-0);
  /* Wider than the viewport so the short ends clip off screen instead of
     showing the wedge a rotation leaves behind. */
  width: 118%;
  margin-left: -9%;
  transform: rotate(-3.4deg);
}

.kinetic .mq-track { gap: 0; }

.kword {
  display: inline-flex;
  align-items: center;
  font-size: clamp(2.2rem, 6.4vw, 5.2rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.03em;
  text-transform: uppercase;
  padding-inline: .22em;
  color: transparent;
  -webkit-text-stroke: 2px #000;
  transition: color .35s var(--ease);
}
.kword:hover { color: #000; }
.kchar { display: inline-block; will-change: transform; }

.kdot {
  display: inline-flex;
  align-items: center;
  padding-inline: .1em;
  color: rgba(0, 0, 0, .42);
}
.kdot .peak { width: .5em; height: .72em; }

/* 09  Services ============================================================ */

.services { background: var(--ink-0); }

.sec-head {
  display: grid;
  gap: 22px;
  margin-bottom: clamp(44px, 6vw, 78px);
}
.sec-head > :is(h1, h2, .h1, .h2) { max-width: 17ch; }
.sec-head > p { max-width: 60ch; }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: clamp(16px, 2vw, 26px);
}

.card {
  position: relative;
  background: var(--ink-2);
  border: 1px solid var(--line-2);
  padding: 0 0 34px;
  overflow: hidden;
  transform-style: preserve-3d;
  will-change: transform;
}
.card::before {
  content: "";
  position: absolute;
  inset: 0 auto auto 0;
  width: 100%; height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s var(--ease);
  z-index: 3;
}
.card:hover::before { transform: scaleX(1); }

.card-media {
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--ink-3);
}
.card-media img, .card-media video {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.05);
  transition: filter .6s var(--ease), transform .8s var(--ease);
}
.card:hover .card-media img, .card:hover .card-media video {
  filter: grayscale(0) contrast(1);
  transform: scale(1.04);
}
.card-media video { position: absolute; inset: 0; opacity: 0; transition: opacity .45s var(--ease); }
.card:hover .card-media video { opacity: 1; }

/* These cards do more on hover than they show at rest, so each carries a
   slow ring that says so without saying it. It stops the moment you arrive,
   because by then the card is answering for itself. Staggered so the three
   never pulse together, which would read as a loading state. */
.card-hint {
  position: absolute;
  top: 16px; right: 16px;
  z-index: 3;
  width: 24px; height: 24px;
  pointer-events: none;
  transition: opacity .45s var(--ease);
}
.card-hint::before, .card-hint::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 2px solid var(--accent);
  border-radius: 50%;
}
/* A touch of shadow so the ring holds against a pale frame as well as a dark
   one. The three service images run from near black to near white. */
.card-hint::before { opacity: .75; box-shadow: 0 0 10px rgba(0, 0, 0, .45); }
.card-hint::after { animation: ping 2.1s var(--ease-out) infinite; animation-delay: var(--d, 0s); }
/* The ring is out by 42% of the cycle, so with the cards offset a fifth of a
   second apart the second is rising while the first is still fading. That
   overlap is what makes it read as one pulse travelling left to right rather
   than three lights blinking in turn. */
@keyframes ping {
  0%   { transform: scale(.6);  opacity: .85; }
  42%  { transform: scale(1.9); opacity: 0; }
  100% { transform: scale(1.9); opacity: 0; }
}
.card:hover .card-hint, .card:focus-within .card-hint { opacity: 0; }

.card-glare {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  z-index: 2;
  transition: opacity .3s;
  mix-blend-mode: overlay;
}

.card-body { padding: 30px clamp(22px, 2.4vw, 32px) 0; transform: translateZ(22px); }
.card-body h3 { margin-bottom: 14px; font-size: 1.55rem; }
.card-body p { color: var(--muted); font-size: .95rem; line-height: 1.75; margin: 0; }

.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}
.card-head h3 { margin-bottom: 0; }
.card-no {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .2em;
  color: var(--accent);
  flex: none;
}

/* 10  Work ================================================================ */

.work { background: var(--ink-1); overflow: hidden; }

.work-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 26px;
  margin-bottom: clamp(38px, 5vw, 60px);
}

.hscroll { position: relative; }
.hs-track {
  display: flex;
  gap: clamp(16px, 2vw, 28px);
  padding-inline: var(--gutter);
  will-change: transform;
}

.hs-card {
  position: relative;
  flex: none;
  width: clamp(272px, 42vw, 520px);
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--ink-3);
  border: 1px solid var(--line-2);
  text-decoration: none;
  color: inherit;
}
.hs-card img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* Colour at rest. The gradient below still carries the caption. */
  filter: brightness(.82);
  transition: filter .7s var(--ease), transform 1.1s var(--ease);
}
.hs-card:hover img { filter: brightness(1); transform: scale(1.05); }
.hs-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(7,7,7,.92) 4%, transparent 62%);
}
.hs-body {
  position: absolute;
  inset: auto 0 0;
  padding: clamp(22px, 2.4vw, 34px);
  z-index: 1;
  display: grid;
  gap: 8px;
}
.hs-body .kicker {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--accent);
}
.hs-body h3 { font-size: clamp(1.3rem, 1.9vw, 1.8rem); text-transform: uppercase; }
.hs-body p { margin: 0; color: var(--muted); font-size: .88rem; line-height: 1.6; }

.hs-ghost {
  position: absolute;
  top: 10px; right: 18px;
  font-size: clamp(3.4rem, 6vw, 6rem);
  font-weight: 700;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, .17);
  z-index: 1;
  pointer-events: none;
}

.hs-bar {
  position: relative;
  height: 2px;
  background: var(--line);
  margin: clamp(30px, 4vw, 48px) 0 0;
  overflow: hidden;
}
.hs-bar i {
  position: absolute;
  inset: 0 auto 0 0;
  width: 14%;
  background: var(--accent);
  transform-origin: left;
}

/* On touch and narrow screens the pin is replaced by a real scroll row. */
@media (max-width: 900px), (hover: none) {
  .hs-track {
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 6px;
  }
  .hs-track::-webkit-scrollbar { display: none; }
  .hs-card { scroll-snap-align: start; }
  .hs-bar { display: none; }
}

/* 11  About =============================================================== */

.about {
  position: relative;
  /* Matches the process section. #282828 was the old site's dark grey and it
     sat too bright between two near black sections. */
  background: var(--ink-1);
  overflow: hidden;
}

.about-grid {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(34px, 5vw, 78px);
  align-items: center;
}
@media (max-width: 900px) { .about-grid { grid-template-columns: 1fr; } }

.about-media { position: relative; }
.about-media img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}
.about-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(14,14,14,.72), transparent 55%);
  pointer-events: none;
}
.about-cap {
  position: absolute;
  left: 0; bottom: 0;
  z-index: 1;
  background: var(--accent);
  color: #000;
  padding: 10px 18px;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .18em;
  text-transform: uppercase;
}

.about-body { position: relative; z-index: 1; }
.about-body h2 { margin-bottom: 26px; }
.about-body p { color: rgba(255, 255, 255, .72); }

.stats {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px 24px;
  margin-top: 42px;
  padding-top: 34px;
  border-top: 1px solid rgba(255, 255, 255, .14);
}
.stats li { display: grid; gap: 4px; }
.stats b {
  font-size: clamp(1.05rem, 1.35vw, 1.34rem);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -.015em;
  color: var(--accent);
  hyphens: none;
  text-wrap: balance;
}
/* Scoped to a direct child. An unscoped `.stats span` also matched the
   counter span nested inside the number and, at equal specificity but later in
   the file, shrank it to label size. */
.stats li > span {
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .5);
  line-height: 1.5;
}

/* 12  Clients ============================================================= */

.clients { background: var(--ink-0); }

/* A wall of twelve marks says nothing on its own. The note is what turns the
   shapes into a claim. */
.wall-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px 40px;
  margin-bottom: 30px;
}
.wall-head .label { margin-bottom: 0; }
.wall-note {
  margin: 0;
  max-width: 46ch;
  font-size: .94rem;
  line-height: 1.7;
  color: var(--muted);
}

.client-grid {
  display: grid;
  margin: 0;
  padding: 0;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1px;
  background: var(--line-2);
  border: 1px solid var(--line-2);
}
.client-grid li {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 10;
  padding: 22px;
  background: var(--ink-1);
  list-style: none;
  transition: background-color .4s var(--ease);
}
.client-grid li:hover { background: var(--ink-3); }
@media (max-width: 1100px) { .client-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 680px)  { .client-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 420px)  { .client-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.client-grid img {
  max-height: 46px;
  width: auto;
  opacity: .5;
  filter: grayscale(1);
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
.client-grid li:hover img { opacity: 1; transform: scale(1.06); }

/* 13  Process ============================================================= */

.process { background: var(--ink-1); position: relative; overflow: hidden; }

.process-svg {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  z-index: 0;
}
.process-svg path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.2;
  opacity: .16;
}

.steps {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: clamp(26px, 3.4vw, 48px);
  counter-reset: step;
  margin: 0;
  padding: 0;
  list-style: none;
}
.step { position: relative; padding-top: 18px; border-top: 1px solid var(--line); }
.step-no {
  display: block;
  font-size: clamp(3.4rem, 7vw, 5.6rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.04em;
  color: transparent;
  -webkit-text-stroke: 1.5px var(--accent);
  margin-bottom: 18px;
}
.step h3 {
  font-size: clamp(1.05rem, 1.35vw, 1.3rem);
  text-transform: uppercase;
  line-height: 1.22;
  margin-bottom: 0;
  max-width: 18ch;
}
.step p { color: var(--muted); font-size: .95rem; margin: 12px 0 0; }

/* 14  Contact ============================================================= */

.contact { background: var(--ink-0); position: relative; overflow: hidden; }

.contact-grid {
  display: grid;
  /* The heading needs less room than it had, so the portrait takes it. At
     about 400 wide it is also nearer the photo's own 4:5 at the form's height,
     so the fill trims less of it. */
  grid-template-columns: minmax(0, .78fr) minmax(0, 400px) minmax(0, 1.35fr);
  gap: clamp(24px, 3.2vw, 52px);
  align-items: start;
}

/* The portrait sits between the heading and the form, and takes its height
   from the form beside it rather than a number, so the two stay level however
   the form reflows. The column is close to the photograph's own 4:5, so the
   fill trims a few pixels rather than recomposing the shot. */
.contact-face {
  margin: 0;
  align-self: stretch;
  display: flex;
  flex-direction: column;
}
.contact-face img {
  /* Zero basis and zero height, so the photo contributes nothing to the row's
     height and simply fills whatever the form beside it sets. Without this, a
     wider column let the photo's own height take over the row. */
  flex: 1 1 0;
  width: 100%;
  height: 0;
  min-height: 320px;
  object-fit: cover;
  object-position: 50% 28%;
}
.contact-face figcaption {
  flex: none;
  background: var(--accent);
  color: #000;
  padding: 9px 14px;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .18em;
  text-transform: uppercase;
}

@media (max-width: 1240px) {
  .contact-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .contact-face { grid-column: 1 / -1; max-width: 360px; align-self: start; }
  .contact-face img { flex: none; height: auto; aspect-ratio: 4 / 5; }
}
@media (max-width: 900px) { .contact-grid { grid-template-columns: 1fr; } }

.contact-lede :is(h1, h2) { margin-bottom: 28px; }
.contact-lede :is(h1, h2) + .lede { margin-top: 0; }

.contact-meta {
  display: grid;
  gap: 14px;
  margin-top: 36px;
  padding-top: 30px;
  border-top: 1px solid var(--line);
}
.contact-meta a {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: 1.05rem;
}

.form { display: grid; gap: 18px; }
.field { display: grid; gap: 8px; }
.field label {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--faint);
}
.field input, .field textarea {
  width: 100%;
  padding: 15px 16px;
  background: var(--ink-2);
  border: 1px solid var(--line);
  color: var(--text);
  font-family: inherit;
  font-size: .98rem;
  font-weight: 300;
  border-radius: 0;
  transition: border-color .3s, background-color .3s;
}
.field textarea { resize: vertical; min-height: 140px; line-height: 1.65; }
.field input:focus, .field textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--ink-3);
}
.field input::placeholder, .field textarea::placeholder { color: rgba(255, 255, 255, .26); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 560px) { .field-row { grid-template-columns: 1fr; } }

.form-note {
  font-size: .84rem;
  color: var(--faint);
  margin: 0;
}
.form-status {
  font-size: .9rem;
  padding: 12px 16px;
  border-left: 2px solid var(--accent);
  background: var(--ink-2);
  margin: 0;
}

/* 15  Footer ============================================================== */

.footer {
  background: var(--ink-0);
  border-top: 1px solid var(--line-2);
  padding-block: clamp(44px, 5vw, 70px);
}
.footer-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 26px;
  padding-bottom: 34px;
}
.footer-top img { height: 30px; width: auto; opacity: .9; }
.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 26px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.footer-links a {
  color: var(--muted);
  text-decoration: none;
  font-size: .78rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  transition: color .25s;
}
.footer-links a:hover { color: var(--accent); }
.footer-bot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 14px;
  padding-top: 26px;
  border-top: 1px solid var(--line-2);
  font-size: .76rem;
  color: var(--faint);
}
.footer-bot p { margin: 0; }

/* 16  Reduced motion ====================================================== */
/* Belt and suspenders: the script bails out on its own, and the CSS kills
   anything it might have missed. Nothing here is allowed to hide content. */

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

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

  .js [data-reveal], .js .line > span { opacity: 1 !important; transform: none !important; }
  .mq-track { animation: none !important; }
  .kinetic { transform: none; width: 100%; margin-left: 0; }
  .scroll-cue::after { animation: none; transform: scaleY(1); }
  .card-media video, .pat-glow, #hero-canvas { display: none !important; }
  .card-hint::after { display: none; }
  .card-media img, .client-grid img { filter: none !important; }
}

/* 17  Sub pages =========================================================== */
/* contact.html and terms.html. The nav on these pages ships with .stuck
   already applied, since there is no hero for it to sit over. */

.sub { padding-top: var(--nav-h); }
.page-top { padding-top: clamp(48px, 6vw, 84px); }

.contact-portrait {
  margin: 40px 0 0;
  max-width: 320px;
}
.contact-portrait img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: 50% 22%;
  filter: grayscale(1) contrast(1.05);
}
.contact-portrait figcaption {
  display: inline-block;
  margin-top: -1px;
  background: var(--accent);
  color: #000;
  padding: 9px 16px;
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .18em;
  text-transform: uppercase;
}

/* Legal copy. The old page pasted this into code blocks, so the whole point
   here is that it reads as a document. */
.legal { max-width: 74ch; }
.legal > .h1 { margin-bottom: 40px; }
.legal h2 {
  font-size: clamp(1.5rem, 2.6vw, 2.1rem);
  text-transform: uppercase;
  letter-spacing: -.02em;
  margin: 56px 0 20px;
  color: var(--accent);
}
.legal h3 {
  font-size: 1.05rem;
  font-weight: 500;
  line-height: 1.35;
  text-transform: none;
  letter-spacing: 0;
  margin: 34px 0 10px;
}
.legal p { color: var(--muted); }
.legal ul {
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 8px;
}
.legal li {
  position: relative;
  padding-left: 22px;
  color: var(--muted);
  line-height: 1.7;
}
.legal li::before {
  content: "";
  position: absolute;
  left: 0; top: .78em;
  width: 8px; height: 1px;
  background: var(--accent);
}
.legal blockquote {
  margin: 18px 0;
  padding: 18px 22px;
  background: var(--ink-2);
  border-left: 2px solid var(--accent);
}
.legal blockquote p { margin: 0; font-size: .94rem; }
.legal .eff {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
}
.legal .hairline { margin: 52px 0 0; }

@media (max-width: 640px) {
  .stats { grid-template-columns: 1fr 1fr; }
  .stats li:last-child { grid-column: 1 / -1; }
}

/* Word figures. Some of these tiles carry a phrase rather than a number, so
   they need their own size: display type set for two or three digits runs off
   the grid the moment you put a word in it. */
.hero-stats dd.is-word {
  font-size: clamp(1.18rem, 1.75vw, 1.72rem);
  letter-spacing: -.015em;
  line-height: 1.12;
  text-wrap: balance;
  align-self: end;
  padding-bottom: .22em;
}

/* 18  Portfolio =========================================================== */
/*
   The sizing engine is the justified-row algorithm Flickr uses. Every item in
   a row is given a flex-grow equal to its own aspect ratio against a zero
   basis, so its width is its share of the row:

       width = (its ratio / sum of the row's ratios) * (row - gaps)

   Because each image then keeps its natural height at that width, every item
   in a row lands at the same height on its own, with no cropping and no fixed
   aspect boxes. Rows are packed at build time, where the ratios are known, and
   written into the markup. Packing in the browser after images decode is what
   produces layout shift.

   A row of one cannot flush, so a solo item is bounded by height instead and
   a lone film cannot balloon to the full width.

   The same engine runs the client work and the personal gallery; only the
   target row height differs. That is what makes the page read as one thing.
*/

/* The reading measure belongs on the prose, not on a wrapper that also holds
   the display heading, which should keep growing with the page. */
.pf-intro > .lede { max-width: 58ch; }

/* -- rows ---------------------------------------------------------------- */
.row {
  --pg: clamp(8px, .9vw, 14px);
  display: flex;
  gap: var(--pg);
  margin-bottom: var(--pg);
}
.row:last-child { margin-bottom: 0; }
.row > .fig { flex: var(--r) 1 0; min-width: 0; }

/* A solo item cannot flush, so it is given a share of the container rather
   than stretched. That share is its own ratio over a target ratio sum, which
   is the same arithmetic the flush rows use, so a solo row scales with the
   window exactly like every other row. Sizing it from a fixed pixel height
   was the bug: it stayed 782px wide from 1024 to 1920, which read as 100% of
   the column on a laptop and 60% on a large display. */
.row-solo > .fig {
  flex: 0 1 auto;
  width: min(100%, calc(var(--r) / var(--tsum, 2.47) * 100%));
}

.fig {
  position: relative;
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: var(--ink-2);
}
.fig img {
  display: block;
  width: 100%;
  height: auto;
  transition: transform 1s var(--ease), filter .5s var(--ease);
}
.fig:hover img { transform: scale(1.035); filter: brightness(1.06); }

/* -- video, as just another ratio in the row ----------------------------- */
.fig-vid { cursor: pointer; color: inherit; width: 100%; }
/* A poster is a still of a film, not one of the photographs, so it is held to
   the ratio its row was packed with. Without this a provider thumbnail that
   does not match the video's shape silently breaks the row it sits in. */
.fig-vid img { aspect-ratio: var(--r); object-fit: cover; }
.fig-vid iframe { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }
.fig-play {
  position: absolute;
  inset: 50% auto auto 50%;
  transform: translate(-50%, -50%);
  width: clamp(44px, 4vw, 62px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent);
  color: #000;
  box-shadow: 0 6px 30px rgba(0, 0, 0, .45);
  transition: transform .4s var(--ease);
  pointer-events: none;
}
.fig-vid:hover .fig-play { transform: translate(-50%, -50%) scale(1.09); }
.fig-play svg { width: 38%; height: 38%; fill: currentColor; margin-left: 8%; }
.fig-label {
  position: absolute;
  left: 0; bottom: 0;
  padding: 9px 14px;
  background: var(--ink-0);
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--muted);
  pointer-events: none;
}

/* -- the reel, the one thing that earns width ---------------------------- */
/* A share of the container, not a share of the viewport height. Height based
   sizing made the reel change size when the window got shorter and stop
   growing when it got wider. */
.reel { max-width: 82%; }

/* -- a project ----------------------------------------------------------- */
/* Title beside the work rather than above it, and it holds position while
   the media scrolls past, so you always know what you are looking at. */
.proj {
  display: grid;
  grid-template-columns: minmax(0, clamp(148px, 15%, 232px)) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 54px);
  padding-block: clamp(44px, 5vw, 86px);
  border-top: 1px solid var(--line-2);
}
.proj:first-of-type { border-top: 0; padding-top: 0; }

.proj-meta { position: sticky; top: calc(var(--nav-h) + 22px); align-self: start; }
.proj-meta h2 {
  font-size: clamp(1rem, 1.25vw, 1.2rem);
  text-transform: uppercase;
  letter-spacing: .01em;
  line-height: 1.25;
}
.proj-title { margin: 8px 0 0; color: var(--muted); font-size: .92rem; line-height: 1.5; }
.proj-role {
  display: block;
  margin-top: 14px;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent);
}
.proj-note { margin: 16px 0 0; color: var(--faint); font-size: .85rem; line-height: 1.65; }
.proj-link { display: inline-block; margin-top: 14px; font-size: .82rem; }

@media (max-width: 900px) {
  .proj { grid-template-columns: minmax(0, 1fr); }
  .proj-meta { position: static; }
}

/* Below this the rows themselves are the wrong shape, so each item takes the
   full width and keeps its own height. */
@media (max-width: 560px) {
  .row { flex-direction: column; }
  .row > .fig, .row-solo > .fig { width: 100%; flex: none; }
}

/* -- personal work ------------------------------------------------------- */
/* Same rows, packed shorter so more of the set is on screen at once. */
.gal { display: block; }

.pf-cta { text-align: center; display: grid; justify-items: center; gap: 26px; }

@media (prefers-reduced-motion: reduce) {
  .fig img { transform: none !important; filter: none !important; }
}

/* Honeypot field. Moved off screen rather than hidden, so bots still find and fill it. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
