/* ============================================================================
   CASE STUDY BASE
   Shared foundation for the five case study pages on the ColoredWeb Studio
   site (AIFF, Warner Bros., Siemens, Delhivery, BMC Schools). Originally the
   global sheet of the Afsar Mansuri portfolio the studies were authored in.

   ACCENT: the sheet's single accent was #d71920 (red) and is now ColoredWeb
   purple #b162fe. The token NAMES below still say "red" — they are referenced
   several hundred times across the five per-study sheets, so they were left
   alone deliberately. Read --primary-red as "the accent"; it is purple.

   The chrome this sheet defines (.topbar, .topnav, .foot) is unused — the
   studies carry CW's own header and footer from casestudy-chrome.css, which
   must load AFTER this file.

   1  Design tokens
   2  Reset & base
   3  Shared primitives (rules, stars, icons, arrows, reveals)
   4  Top bar
   5  Hero
   6  Selected projects (+ CSS thumbnail mocks)
   7  Skills / Work process / Quote
   8  Contact (+ CSS laptop & mug)
   9  Responsive
   10 Reduced motion
   ========================================================================= */

/* ---------------------------------------------------------------- 1 tokens */
:root {
  --background:     #070707;
  --surface:        #0d0d0d;
  --surface-light:  #141414;
  --primary-red:    #b162fe;
  --red-bright:     #c78bff;
  --deep-red:       #3d1470;
  --text-white:     #f5f5f5;
  --text-grey:      #a6a6a6;
  --text-dim:       #6f6f6f;
  --border:         rgba(255, 255, 255, .16);
  --border-soft:    rgba(255, 255, 255, .09);
  --border-red:     rgba(177, 98, 254, .38);

  --font-display: "Anton", "Haettenschweiler", "Arial Narrow", "Impact", sans-serif;
  /* super-condensed face for the oversized hero word only */
  --font-mega:    "Antonio", "Haettenschweiler", "Arial Narrow", "Impact", sans-serif;
  --font-cond:    "Oswald", "Arial Narrow", "Helvetica Neue", sans-serif;
  --font-body:    "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-script:  "Allura", "Segoe Script", "Brush Script MT", cursive;
  --font-serif:   "Cormorant Garamond", Georgia, "Times New Roman", serif;

  --shell:   1480px;
  --gutter:  clamp(1.25rem, 3.2vw, 2.75rem);

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

/* ------------------------------------------------------------- 2 reset/base */
*, *::before, *::after { box-sizing: border-box; }

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

/* the top bar is sticky, so anything an anchor lands on has to clear it.
   :where() keeps this at zero specificity — any element can override it. */
:where([id]) { scroll-margin-top: clamp(4.6rem, 6vw, 5.6rem); }

body {
  margin: 0;
  background: var(--background);
  color: var(--text-white);
  font-family: var(--font-body);
  font-size: clamp(.875rem, .34vw + .78rem, 1rem);
  line-height: 1.6;
  font-weight: 400;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

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

::selection { background: var(--primary-red); color: #fff; }

:focus-visible {
  outline: 2px solid var(--primary-red);
  outline-offset: 3px;
}

.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--primary-red); color: #fff;
  padding: .7rem 1.2rem; font: 600 .75rem/1 var(--font-cond);
  letter-spacing: .12em; text-transform: uppercase;
}
.skip-link:focus { left: 0; }

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* global film grain -------------------------------------------------------- */
.grain {
  position: fixed; inset: 0; z-index: 60;
  pointer-events: none; opacity: .035;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/><feColorMatrix type='saturate' values='0'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>");
  mix-blend-mode: overlay;
}

/* ------------------------------------------------------- 3 shared primitives */
.section-title {
  font-family: var(--font-cond);
  font-weight: 600;
  font-size: clamp(1.35rem, 1.65vw + .55rem, 2.05rem);
  letter-spacing: .012em;
  text-transform: uppercase;
  line-height: 1;
  display: flex;
  align-items: center;
  gap: .55em;
  white-space: nowrap;
}

.rule {
  flex: 1 1 auto;
  height: 1px;
  min-width: 1.5rem;
  background: linear-gradient(90deg, var(--border), rgba(255,255,255,.05));
}

.star { width: 1em; height: 1em; fill: var(--primary-red); flex: none; }
.star--inline { width: .62em; height: .62em; }

.link-arrow {
  display: inline-flex; align-items: center; gap: .9rem;
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.68rem, .42vw + .55rem, .8rem);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-grey);
  transition: color .3s var(--ease);
}
.link-arrow:hover { color: var(--text-white); }

.arrow { width: 2.5rem; height: .75rem; flex: none; overflow: visible; }
.arrow path {
  fill: none; stroke: currentColor; stroke-width: 1.1;
  stroke-linecap: round; stroke-linejoin: round;
}
.link-arrow:hover .arrow { transform: translateX(5px); }
.arrow { transition: transform .35s var(--ease-out); }

.icon-dot, .icon-ring, .star-badge {
  display: inline-grid; place-items: center; flex: none;
  border: 1px solid var(--border-red);
  border-radius: 50%;
}
.icon-dot { width: 1.85rem; height: 1.85rem; }
.icon-dot svg { width: .95rem; height: .95rem; }
.icon-ring { width: 2.85rem; height: 2.85rem; }
.icon-ring svg { width: 1.2rem; height: 1.2rem; }
.icon-dot svg, .icon-ring svg {
  fill: none; stroke: var(--primary-red); stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}

.script { font-family: var(--font-script); font-weight: 400; }

/* scroll reveal ------------------------------------------------------------
   Scoped to .js (set by an inline script in <head>) so a JS failure can never
   leave the page with everything at opacity 0.                             */
.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
  transition-delay: var(--d, 0ms);
  will-change: opacity, transform;
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

/* the oversized word gets a slower, wider reveal of its own */
.js .hero__word[data-reveal] {
  transform: scale(1.055);
  transition-duration: 1.7s;
}
.js .hero__word[data-reveal].is-in { transform: scale(1); }

/* the portrait drifts in horizontally rather than rising */
.js .hero-portrait img[data-reveal] {
  transform: translate3d(52px, 0, 0);
  transition-duration: 1.4s;
  transition-delay: 120ms;
}
.js .hero-portrait img[data-reveal].is-in { transform: none; }

/* ---------------------------------------------------------------- 4 top bar */
/* Sticky on every page and every width. z-index 50 is deliberate: it clears
   all page content (the tallest is the BBW card stack at 30) while staying
   UNDER .grain (60), the cursor labels and the lightboxes, so the bar keeps
   the film grain over it exactly as it did when it scrolled away. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  border-bottom: 1px solid rgba(177, 98, 254, .5);
  background: var(--background);
  transition: box-shadow .3s var(--ease), background-color .3s var(--ease);
}
/* script.js adds this once the page has moved under the bar */
.topbar.is-stuck { box-shadow: 0 10px 30px rgba(0, 0, 0, .45); }

.topbar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: clamp(48px, 4vw, 62px);
  padding-block: .55rem;
}

.topbar__id {
  display: flex; flex-direction: column; gap: .12em;
  font-family: var(--font-cond);
  font-size: clamp(.6rem, .34vw + .5rem, .72rem);
  letter-spacing: .14em; text-transform: uppercase; line-height: 1.25;
}
.topbar__role { color: var(--primary-red); font-weight: 600; }
.topbar__sub  { color: var(--text-white); font-weight: 400; }
/* projects.html only — the name links home. NO underline on hover: the hover
   state is the name going red-to-white and nothing else. The rule that drew a
   red border-bottom here was removed deliberately; do not reinstate it.
   :focus-visible is untouched — that ring comes from the global rule in the
   reset and is the only thing a keyboard visitor has to go on. */
.topbar__home { display: inline-block; }
.topbar__home:hover .topbar__role { color: #fff; }

/* primary nav -------------------------------------------------------------
   Sits where the "Currently at …" status line used to, on every page. Two
   items: a link to the archive and a disclosure that lists the case studies.
   The identity block on the left is untouched. */
.topnav {
  display: flex; align-items: center;
  flex: 0 0 auto;
  gap: clamp(1.1rem, 2.2vw, 2.2rem);
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.6rem, .34vw + .5rem, .72rem);
  letter-spacing: .15em; text-transform: uppercase;
  white-space: nowrap;
}

.topnav__link {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .35rem 0;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  color: var(--text-white);
  background: none; border: 0; cursor: pointer;
  transition: color .3s var(--ease);
}
.topnav__link:hover,
.topnav__link[aria-current="page"],
.topnav__toggle[aria-expanded="true"] { color: var(--primary-red); }

.topnav__group { position: relative; }

.topnav__caret {
  width: .62rem; height: .42rem;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform .3s var(--ease);
}
.topnav__toggle[aria-expanded="true"] .topnav__caret { transform: rotate(180deg); }

/* the panel opens off the button's aria-expanded, so the attribute is the
   single source of truth for both the state and the styling */
.topnav__menu {
  position: absolute; top: calc(100% + .55rem); right: 0; z-index: 5;
  min-width: 15rem;
  padding-block: .4rem;
  background: var(--surface);
  border: 1px solid var(--border-red);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .55);
  opacity: 0; visibility: hidden;
  transform: translateY(-6px);
  transition: opacity .25s var(--ease), transform .3s var(--ease-out),
              visibility .25s linear;
}
.topnav__toggle[aria-expanded="true"] + .topnav__menu {
  opacity: 1; visibility: visible; transform: none;
}
/* no JS, no toggle: fall back to hover / keyboard focus */
html:not(.js) .topnav__group:hover .topnav__menu,
html:not(.js) .topnav__group:focus-within .topnav__menu {
  opacity: 1; visibility: visible; transform: none;
}

.topnav__menu a {
  display: block;
  padding: .55rem 1.15rem;
  color: var(--text-grey);
  transition: color .25s var(--ease), background-color .25s var(--ease);
}
.topnav__menu a:hover,
.topnav__menu a:focus-visible { color: var(--text-white); background: rgba(177, 98, 254, .16); }
.topnav__menu a[aria-current="page"] { color: var(--primary-red); }

/* burger + mobile menu -----------------------------------------------------
   Both are phone-only: the desktop reference has no nav bar at all, so the
   nav links live exclusively inside this panel. */
.burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  width: 34px; height: 30px;
  padding: 0;
  border: 0; background: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.burger span {
  display: block; height: 2px; width: 100%;
  background: var(--text-white);
  transition: transform .38s var(--ease-out), opacity .2s linear;
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

.mobilenav {
  position: fixed; inset: 0; z-index: 150;
  display: grid; align-content: center; justify-items: start;
  /* the case-study list makes this panel tall on short phones — keep the
     overflow reachable instead of centring it off the top edge */
  align-content: safe center;
  overflow-y: auto; overscroll-behavior: contain;
  gap: clamp(1.2rem, 5vw, 2.2rem);
  padding: calc(var(--gutter) + 3.4rem) var(--gutter) var(--gutter);
  background:
    radial-gradient(90% 60% at 80% 12%, rgba(61, 20, 112, .5), transparent 62%),
    var(--background);
  opacity: 0; visibility: hidden;
  transform: translateY(-10px);
  transition: opacity .35s var(--ease), transform .45s var(--ease-out),
              visibility .35s linear;
}
.mobilenav.is-open { opacity: 1; visibility: visible; transform: none; }

.mobilenav nav { display: grid; gap: clamp(.9rem, 4vw, 1.6rem); }
.mobilenav nav a {
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 12vw, 3.4rem);
  line-height: .95;
  text-transform: uppercase;
  color: var(--text-white);
}
.mobilenav nav a:active { color: var(--primary-red); }

.mobilenav__status {
  display: flex; align-items: center; gap: .7rem;
  font-family: var(--font-cond); font-weight: 500;
  font-size: .74rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-grey);
  padding-top: clamp(.5rem, 3vw, 1rem);
  border-top: 1px solid var(--border-soft);
  width: 100%;
}
.mobilenav__status .star { width: .85rem; height: .85rem; }
.mobilenav__mail {
  font-size: 1rem; color: var(--text-white);
  border-bottom: 1px solid var(--border-red);
  padding-bottom: .2rem;
}

/* the top bar's "Case Studies" disclosure, flattened for the phone panel:
   no toggle, just the list under a label, two up so it stays short */
.mobilenav__cases { width: 100%; }
.mobilenav__cases-title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: .7rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: .7rem;
}
.mobilenav__cases ul {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .55rem 1.2rem;
}
.mobilenav__cases a {
  font-family: var(--font-cond); font-weight: 300;
  font-size: .88rem; letter-spacing: .04em;
  color: var(--text-grey);
}
.mobilenav__cases a:active,
.mobilenav__cases a[aria-current="page"] { color: var(--text-white); }

/* ------------------------------------------------------------------ 5 hero */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: clamp(560px, 56vw, 880px);
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid rgba(177, 98, 254, .5);
  background:
    radial-gradient(120% 90% at 50% 8%, rgba(61, 20, 112, .45), transparent 62%),
    var(--background);
}

/* oversized PORTFOLIO word -------------------------------------------------
   Boxed to the same rhythm as .shell so its P and final O line up with the
   top bar's "UX/UI DESIGNER" and the freelance star, and stretched to the
   full hero height (the SVG uses preserveAspectRatio="none", so the glyphs
   scale vertically independently of their width). */
.hero__word {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 60%;                     /* word occupies the top 60% of the hero */
  z-index: 0;
  pointer-events: none;
  line-height: 0;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.hero__word svg {
  width: 100%;
  height: 100%;
}
.hero__word text {
  font-family: var(--font-mega);
  /* Antonio is variable 100-700. At this degree of vertical stretch the weight
     axis only moves the stem-to-letter-width ratio from .198 (100) to .256
     (400). NOTE: the viewBox on the <svg> is cropped to this exact weight's
     ink — re-measure it if you change this number. */
  font-weight: 250;
  font-size: 236px;
  fill: url(#wordGrad);
}
.hero__word::after {           /* soft vignette so the word melts into black */
  content: "";
  position: absolute; inset: 0;
  background:
    radial-gradient(88% 105% at 50% 30%, transparent 40%, rgba(7,7,7,.24) 78%, rgba(7,7,7,.58) 100%),
    linear-gradient(180deg, transparent 0%, transparent 54%, rgba(7,7,7,.5) 84%, rgba(7,7,7,.88) 100%);
  pointer-events: none;
}

/* Legibility scrim. With the word running the full height of the hero, the
   intro copy and the stats rail would otherwise sit directly on bright red
   letterforms. This darkens the two outer columns and the base of the hero
   while leaving the centre — where the portrait stands — untouched. It sits
   above the word (z 0) and below the portrait (z 2). */
.hero::after {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(96deg,  rgba(7,7,7,.82) 0%, rgba(7,7,7,.6) 14%, rgba(7,7,7,.2) 28%, transparent 40%),
    linear-gradient(276deg, rgba(7,7,7,.72) 0%, rgba(7,7,7,.4) 11%, rgba(7,7,7,.1) 22%, transparent 32%),
    linear-gradient(0deg,   rgba(7,7,7,.5) 0%, rgba(7,7,7,.12) 15%, transparent 30%);
}

.hero__glow {
  position: absolute; z-index: 0; pointer-events: none;
  left: 50%; top: 8%;
  width: min(70vw, 900px); aspect-ratio: 1;
  transform: translateX(-48%);
  background: radial-gradient(circle, rgba(177, 98, 254,.20), transparent 62%);
  filter: blur(18px);
}

/* portrait ----------------------------------------------------------------- */
.hero-portrait {
  position: absolute;
  z-index: 2;
  bottom: 0;
  left: 50%;
  /* framed so the subject's head clears the PORTFOLIO word, his silhouette
     starts just past "MANSURI" and stops just short of the stats rail */
  transform: translateX(-40.5%);
  height: 93%;
  display: flex; align-items: flex-end;
  pointer-events: none;
}
.hero-portrait img {
  height: 100%;
  width: auto;
  max-width: none;
  object-fit: contain;
  object-position: bottom center;
  /* cinematic red rim + grounding shadow. Works best with a transparent PNG. */
  filter:
    drop-shadow(-14px 0 26px rgba(177, 98, 254, .30))
    drop-shadow(16px 6px 34px rgba(0, 0, 0, .85))
    contrast(1.04) saturate(1.05);
}
/* base shadow puddle so the figure sits on the black instead of floating */
.hero-portrait::after {
  content: "";
  position: absolute; left: 50%; bottom: -2%;
  width: 118%; height: 26%;
  transform: translateX(-50%);
  background: radial-gradient(50% 50% at 50% 100%, rgba(7,7,7,.95), transparent 72%);
  z-index: 2;
}

/* hero layout -------------------------------------------------------------- */
.hero__grid {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 1.24fr) minmax(0, .74fr);
  align-items: end;
  gap: 0 clamp(.5rem, 2vw, 2rem);
  padding-block: clamp(2rem, 4vw, 3.4rem) clamp(1.5rem, 3vw, 2.6rem);
}

.hero__intro { grid-column: 1; min-width: 0; }
.hero__rail  { grid-column: 3; }

.hero__hello {
  /* Allura's ink is ~17% shorter than Sacramento's at the same font-size, so
     these are scaled to keep the previous optical size. letter-spacing must
     stay at 0 — Allura is a connecting script and tracking breaks the joins. */
  font-size: clamp(2.2rem, 3.5vw, 3.55rem);
  line-height: 1;
  letter-spacing: 0;
  color: var(--text-white);
  margin-bottom: clamp(.1rem, .5vw, .35rem);
  text-shadow: 0 2px 18px rgba(0,0,0,.6);
}

.hero__name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(3.15rem, 9.8vw, 9.2rem);
  line-height: 1.02;                 /* open enough to separate the two lines */
  letter-spacing: -.005em;
  text-transform: uppercase;
  margin-bottom: clamp(.7rem, 1.4vw, 1.35rem);
}

/* Solid white, no fill treatment. The one shadow is separation only — the
   name sits directly on the bright red PORTFOLIO word and would otherwise
   lose its edges against it. In em so it scales with the clamp() type. */
.hero__name span {
  display: block;
  color: #fff;
  filter: drop-shadow(0 .05em .11em rgba(0, 0, 0, .78));
}

.hero__title {
  font-family: var(--font-cond);
  font-weight: 600;
  font-size: clamp(.98rem, 1.28vw + .38rem, 1.5rem);
  line-height: 1.16;
  letter-spacing: .015em;
  text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: clamp(.7rem, 1.2vw, 1.1rem);
}

.hero__lede {
  max-width: 30ch;
  font-size: clamp(.8rem, .42vw + .68rem, .95rem);
  line-height: 1.62;
  color: var(--text-grey);
  margin-bottom: clamp(1.1rem, 2.2vw, 2rem);
}

/* short red rule + CTAs — mobile hero only (see the 640px block) */
.hero__rule, .hero__cta, .hero__sub-cta { display: none; }

.hero__where {
  display: flex; align-items: center; gap: .75rem;
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.64rem, .32vw + .55rem, .78rem);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-white);
}

/* right rail --------------------------------------------------------------- */
.hero__rail {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: clamp(1.6rem, 6.8vw, 6.4rem);
  align-self: stretch;
}

.hero__tagline {
  display: flex; align-items: flex-start; gap: clamp(.7rem, 1.1vw, 1.15rem);
}
.star-badge { width: clamp(2.2rem, 2.9vw, 3.1rem); aspect-ratio: 1; }
.star-badge svg { width: 42%; height: 42%; fill: var(--text-white); }
.hero__tagline p {
  font-size: clamp(.78rem, .4vw + .66rem, .95rem);
  line-height: 1.5;
  color: var(--text-white);
  max-width: 18ch;
}
/* the rail now carries the current role over the company line, so the first
   line is emphasised and the second drops to the secondary tone */
.hero__tagline p strong {
  display: block; font-weight: 500;
  color: var(--text-white);
}
.hero__tagline p { color: var(--text-grey); }

.stats { display: grid; }
.stat {
  display: flex; align-items: center; gap: clamp(.8rem, 1.6vw, 1.5rem);
  padding-block: clamp(.6rem, 1.35vw, 1.15rem);
  border-bottom: 1px solid var(--border-soft);
}
.stat:first-child { border-top: 1px solid var(--border-soft); }
.stat__num {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 2.75vw + .35rem, 3.5rem);
  line-height: .9;
  color: var(--primary-red);
  min-width: 2.4ch;
  font-variant-numeric: tabular-nums;
}
.stat__label {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .3vw + .52rem, .74rem);
  letter-spacing: .12em; text-transform: uppercase;
  line-height: 1.35;
  color: var(--text-grey);
}

/* ============================================================================
   6  Shared section furniture (label / title / lede)
   ========================================================================= */
.sec {
  position: relative;
  padding-block: clamp(3rem, 7vw, 6.5rem);
  border-bottom: 1px solid var(--border-soft);
  background: var(--background);
}

.sec__head { margin-bottom: clamp(1.8rem, 4vw, 3.4rem); }
.sec__head--row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: clamp(1rem, 3vw, 2.5rem); flex-wrap: wrap;
}

.sec__label {
  display: flex; align-items: baseline; gap: .7em;
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.62rem, .3vw + .54rem, .76rem);
  letter-spacing: .24em; text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: clamp(.7rem, 1.6vw, 1.2rem);
}
.sec__label-num {
  font-family: var(--font-display); font-weight: 400;
  font-size: 1.9em; line-height: 1;
  letter-spacing: 0;
  color: var(--primary-red);
}
/* the em-dash between number and label, drawn rather than typed so it keeps
   its length independent of the font */
.sec__label-num::after {
  content: ""; display: inline-block;
  width: 1.1em; height: 1px; margin-left: .45em; vertical-align: .3em;
  background: currentColor; opacity: .7;
}

.sec__title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1.6rem, 3.3vw + .3rem, 3.5rem);
  line-height: 1.06;
  letter-spacing: -.012em;
  color: var(--text-white);
  max-width: 20ch;
  text-wrap: balance;
}

.sec__lede {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26ch), 1fr));
  gap: clamp(.8rem, 2vw, 2.2rem);
  max-width: 76ch;
  margin-top: clamp(1rem, 2.4vw, 1.8rem);
  color: var(--text-grey);
  font-size: clamp(.84rem, .34vw + .74rem, .98rem);
  line-height: 1.68;
}

/* Optional images fade in only once they have actually decoded. Lazy images
   are not requested until they near the viewport, so a missing file would
   otherwise flash its alt text / broken-image icon over the ghost panel
   before the error handler could remove it. Scoped to .js so that with
   JavaScript off the images simply render normally. */
.js img[data-optional] { opacity: 0; transition: opacity .6s var(--ease); }
.js img[data-optional].is-loaded { opacity: 1; }

/* shared "ghost" fallback that shows through when an image is missing ------ */
.brand__ghost, .arch__ghost, .purpose__ghost, .compare__ghost,
.proj-web-ghost, .proj-video-ghost, .proj-brand-ghost, .proj-top-ghost {
  position: absolute; inset: 0; z-index: 0;
  display: grid; place-items: center;
  font-family: var(--font-display); font-weight: 400;
  text-transform: uppercase; letter-spacing: .02em;
  color: rgba(255, 255, 255, .07);
  background:
    radial-gradient(120% 100% at 72% 12%, rgba(177, 98, 254, .22), transparent 62%),
    linear-gradient(155deg, #131315, #0a0a0b 62%, #101011);
  text-align: center; line-height: 1;
  pointer-events: none;
}

/* ============================================================================
   7  Section 01 — Selected brand collaborations
   ========================================================================= */
/* The section header and the cards share ONE grid, so the header can take the
   left seven columns and the first card can be placed into the space beside it
   rather than leaving that area empty. */
.work-layout {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: clamp(.7rem, 1.2vw, 1.15rem);
}
/* the <ul> hands its <li>s straight to .work-layout — role="list" in the markup
   restores the list semantics that display:contents drops in Safari */
.brands { display: contents; }

.sec--work .sec__head {
  grid-column: 1 / 8;
  grid-row: 1;
  align-self: start;
  margin-bottom: 0;
}
/* the copy is already inside a seven-column measure, so its own cap would only
   narrow it twice over */
.sec--work .sec__lede { max-width: none; }

/* Asymmetric placement. Spans are declared per brand so the composition is
   authored rather than emergent — reordering the list will not reflow it.
     row 1   header (7)        | 01 Victoria's Secret (5)
     row 2   02 Warner Bros (7)| 03 Siemens (5, spans rows 2–3)
     row 3   04 AIFF (7)       |
     row 4   05 Delhivery (12)
     row 5   06 L&T Finance (6)| 07 Disney x BBW (6)                         */
.brand--vs   { grid-column: 8 / -1; grid-row: 1; }
.brand--wb   { grid-column: span 7; }
.brand--sie  { grid-column: span 5; grid-row: span 2; }
.brand--aiff { grid-column: span 7; }
.brand--del  { grid-column: span 12; }
.brand--lt   { grid-column: span 6; }
/* .brand--bbw took over the slot Bank of Baroda held — same span, same ratio,
   so the composition above is unchanged. */
.brand--bbw  { grid-column: span 6; }

.brand__link {
  position: relative;
  display: block;
  width: 100%;
  /* NB: do not add height:100% here. Combined with the aspect-ratio below it
     makes the browser derive WIDTH from height, which blows the card out past
     its grid column. Only the row-spanning card gets an explicit height, and
     that one carries no aspect-ratio. */
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  transition: border-color .5s var(--ease), box-shadow .5s var(--ease),
              transform .5s var(--ease-out);
}
.brand--wb   .brand__link { aspect-ratio: 16 / 8.6; }
.brand--aiff .brand__link { aspect-ratio: 16 / 6.4; }
.brand--del  .brand__link { aspect-ratio: 16 / 5.4; }
.brand--lt   .brand__link,
.brand--bbw  .brand__link { aspect-ratio: 16 / 8.4; }

/* The two cards that fill their row instead of setting a ratio: Siemens spans
   two rows, and the feature card takes its height from the header beside it.
   Neither may carry an aspect-ratio — see the note on .brand__link above. */
.brand--sie  .brand__link { height: 100%; }
.brand--vs   .brand__link {
  height: 100%;
  /* keeps the feature card substantial even where the header runs short */
  min-height: clamp(320px, 29vw, 470px);
}

.brand__link:hover,
.brand__link:focus-visible {
  border-color: rgba(177, 98, 254, .55);
  box-shadow: 0 22px 60px -26px rgba(177, 98, 254, .6);
  transform: translateY(-3px);
}

.brand__media { position: absolute; inset: 0; overflow: hidden; }
/* sit the fallback lettering in the upper third, clear of the copy block that
   occupies the bottom of the card */
.brand__ghost { place-items: start center; padding-top: 6%; }
.brand__media img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .9s var(--ease-out), filter .6s var(--ease);
  filter: saturate(.85) contrast(1.04);
}
.brand__link:hover .brand__media img { transform: scale(1.045); filter: saturate(1) contrast(1.06); }
.brand__ghost { font-size: clamp(2.4rem, 7vw, 6rem); }

/* legibility veil + the deep-red tint that comes up on hover */
.brand__media::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  background:
    linear-gradient(0deg, rgba(7,7,7,.94) 0%, rgba(7,7,7,.6) 38%, rgba(7,7,7,.12) 68%, transparent 100%),
    linear-gradient(0deg, rgba(61, 20, 112,0), rgba(61, 20, 112,0));
  transition: background .55s var(--ease);
}
.brand__link:hover .brand__media::after {
  background:
    linear-gradient(0deg, rgba(7,7,7,.95) 0%, rgba(7,7,7,.68) 42%, rgba(7,7,7,.2) 72%, transparent 100%),
    linear-gradient(0deg, rgba(124,68,178,.34), rgba(61, 20, 112,.06));
}

/* The body fills the whole card and pushes its content to the bottom, so the
   project number can anchor to the card's top-right instead of hanging off
   the top of a bottom-anchored block (which clipped it on small cards). */
.brand__body {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: flex-start;
  justify-content: flex-end;
  gap: .32rem;
  padding: clamp(.9rem, 1.8vw, 1.6rem);
  transition: transform .55s var(--ease-out);
  pointer-events: none;          /* the <a> underneath stays the hit target */
}
.brand__link:hover .brand__body { transform: translateY(-4px); }

.brand__num {
  position: absolute;
  top: clamp(.9rem, 1.8vw, 1.6rem); right: clamp(.9rem, 1.8vw, 1.6rem);
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 2.6vw, 2.9rem); line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, .42);
  transition: color .45s var(--ease), -webkit-text-stroke-color .45s var(--ease);
}
.brand__link:hover .brand__num,
.brand__link:focus-visible .brand__num {
  color: var(--primary-red);
  -webkit-text-stroke-color: transparent;
}

.brand__industry {
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.58rem, .26vw + .52rem, .7rem);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--primary-red);
}
.brand__name {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1.1rem, 1.5vw + .5rem, 2.05rem);
  line-height: 1.06; letter-spacing: -.008em;
  color: var(--text-white);
}
.brand__project {
  font-size: clamp(.76rem, .3vw + .68rem, .9rem);
  color: var(--text-grey);
}
.brand__desc {
  font-size: clamp(.76rem, .3vw + .68rem, .88rem);
  line-height: 1.6; color: var(--text-grey);
  max-width: 46ch;
}
.brand__role {
  display: grid; gap: .1rem;
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .24vw + .55rem, .72rem);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-dim);
}
.brand__role em { font-style: normal; color: var(--text-grey); }

.brand__cta {
  display: inline-flex; align-items: center; gap: .7rem;
  margin-top: .35rem;
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.6rem, .26vw + .55rem, .72rem);
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--text-white);
}
.brand__cta .arrow { width: 1.9rem; }
.brand__link:hover .brand__cta .arrow { transform: translateX(7px); }

/* Extended copy is progressive: on pointer devices it is held back until
   hover/focus, on touch it is always visible so nothing is hover-only. */
@media (hover: hover) and (min-width: 861px) {
  .brand__desc, .brand__role {
    opacity: 0;
    max-height: 0;
    overflow: hidden;
    transform: translateY(6px);
    transition: opacity .45s var(--ease), max-height .55s var(--ease),
                transform .5s var(--ease-out);
  }
  .brand__link:hover .brand__desc,
  .brand__link:hover .brand__role,
  .brand__link:focus-visible .brand__desc,
  .brand__link:focus-visible .brand__role {
    opacity: 1; max-height: 11rem; transform: none;
  }
  /* The two compact cards only have room for one of the two, and the credit
     block is the more useful half — it carries who the work was produced
     through, which the description does not. */
  .brand--lt .brand__desc,
  .brand--bbw .brand__desc { display: none; }
}

/* ============================================================================
   8  Section 02 — Work with purpose (BMC Schools)
   ========================================================================= */
.sec--purpose {
  background:
    radial-gradient(80% 60% at 88% 4%, rgba(61, 20, 112, .26), transparent 62%),
    var(--background);
}

.purpose {
  display: grid;
  grid-template-columns: minmax(0, 1.28fr) minmax(0, .92fr);
  gap: clamp(1.4rem, 3vw, 3rem);
  align-items: start;
}

.purpose__frame {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--surface);
}
.purpose__frame img,
.purpose__video {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
}
.purpose__video { z-index: 2; opacity: 0; transition: opacity .5s var(--ease); }
.purpose__frame.is-playing .purpose__video { opacity: 1; }
.purpose__ghost {
  font-size: clamp(1.4rem, 3.4vw, 3rem);
  line-height: 1.08;
  color: rgba(255,255,255,.09);
}
.purpose__frame-cap {
  position: absolute; left: 0; bottom: 0; z-index: 3;
  padding: .7em 1.1em;
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.58rem, .26vw + .52rem, .7rem);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--text-white);
  background: linear-gradient(0deg, rgba(7,7,7,.9), transparent);
  width: 100%;
}

.purpose__copy { display: grid; gap: clamp(.6rem, 1.4vw, .95rem); }
.purpose__title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1.3rem, 1.9vw + .5rem, 2.3rem);
  line-height: 1.08; color: var(--text-white);
}
.purpose__sub {
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.66rem, .3vw + .58rem, .8rem);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: .3rem;
}
.purpose__copy p {
  font-size: clamp(.82rem, .32vw + .73rem, .95rem);
  line-height: 1.68; color: var(--text-grey);
}
/* Partner credits. Not italic — <em> is carrying "this is the named partner",
   and the treatment for that here is weight plus a lift out of the grey body
   copy to full white. */
.purpose__copy em { font-style: normal; font-weight: 600; color: var(--text-white); }

.purpose__scope {
  display: flex; flex-wrap: wrap; gap: .45rem;
  margin-top: .5rem;
}
.purpose__scope dt {
  width: 100%;
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.58rem, .26vw + .52rem, .7rem);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: .3rem;
}
.purpose__scope dd {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .26vw + .55rem, .72rem);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-grey);
  padding: .5em 1em;
  border: 1px solid var(--border-soft);
}
.purpose__link { margin-top: .8rem; color: var(--text-white); }

.purpose__strip {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(.6rem, 1.1vw, 1rem);
  margin-top: clamp(.6rem, 1.6vw, 1.2rem);
}
.purpose__cell { position: relative; }
.purpose__cell picture {
  display: block; position: relative;
  aspect-ratio: 4 / 3; overflow: hidden;
  border: 1px solid var(--border-soft);
  background:
    radial-gradient(110% 100% at 70% 10%, rgba(177, 98, 254,.16), transparent 64%),
    linear-gradient(155deg, #131315, #0a0a0b);
}
.purpose__cell img { width: 100%; height: 100%; object-fit: cover; }

/* ============================================================================
   9  Section 03 — Across disciplines
   Expansion is CSS-only (:hover / :focus-within) so it survives JS being off.
   ========================================================================= */
.disc {
  display: flex;
  gap: clamp(.5rem, .9vw, .9rem);
  min-height: clamp(320px, 40vw, 480px);
}
.disc__panel {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: clamp(1rem, 1.8vw, 1.7rem);
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background:
    radial-gradient(120% 80% at 50% 100%, rgba(61, 20, 112, .2), transparent 64%),
    var(--surface);
  transition: flex-grow .65s var(--ease-out), border-color .5s var(--ease),
              background .5s var(--ease);
}
.disc__panel:hover,
.disc__panel:focus-within {
  flex-grow: 3.1;
  border-color: rgba(177, 98, 254, .5);
  background:
    radial-gradient(120% 80% at 50% 100%, rgba(150, 14, 20, .34), transparent 66%),
    var(--surface-light);
}

/* ---- category motif -------------------------------------------------------
   A line-art watermark identifying each card's discipline, held to the top
   right corner clear of the title and (on hover) the body copy beneath it.
   Faint at rest so the panel never reads empty, then brightened and set in
   motion once the panel opens. Pure SVG line art — no images. Follows the
   same motif system as the Transformation panels on coloredweb.in so the two
   sites read as family, with this site's red standing in for the purple
   accent.

   .is-open is added by script.js on touch devices, where there is no hover to
   trigger any of this. */
.disc__motif {
  position: absolute;
  top: clamp(1rem, 1.8vw, 1.7rem); right: clamp(1rem, 1.8vw, 1.7rem);
  width: clamp(160px, 85%, 320px); height: auto;
  aspect-ratio: 5 / 4;
  pointer-events: none;
  z-index: 0;
}
/* two-tone ink: .ln strokes and .fl fills, each with a red .vi variant */
.disc__motif .ln {
  fill: none; stroke: #fff; stroke-width: 1.3;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .12; transition: opacity .6s var(--ease);
}
.disc__motif .ln.vi { stroke: var(--primary-red); opacity: .28; }
.disc__motif .fl {
  fill: #fff; opacity: .11;
  transition: opacity .6s var(--ease), transform .7s var(--ease);
}
.disc__motif .fl.vi { fill: var(--primary-red); opacity: .3; }

.disc__panel:hover .disc__motif .ln,
.disc__panel:focus-within .disc__motif .ln,
.disc__panel.is-open .disc__motif .ln { opacity: .24; }
.disc__panel:hover .disc__motif .ln.vi,
.disc__panel:focus-within .disc__motif .ln.vi,
.disc__panel.is-open .disc__motif .ln.vi { opacity: .6; }
.disc__panel:hover .disc__motif .fl,
.disc__panel:focus-within .disc__motif .fl,
.disc__panel.is-open .disc__motif .fl { opacity: .2; }
.disc__panel:hover .disc__motif .fl.vi,
.disc__panel:focus-within .disc__motif .fl.vi,
.disc__panel.is-open .disc__motif .fl.vi { opacity: .64; }

/* A · digital experiences — a red scan line sweeps the wireframe */
.disc__motif .m-scan { fill: var(--primary-red); opacity: 0; transform-box: fill-box; }
.disc__panel:hover .dm-web .m-scan,
.disc__panel:focus-within .dm-web .m-scan,
.disc__panel.is-open .dm-web .m-scan { opacity: .5; animation: discScan 2.8s ease-in-out infinite; }
@keyframes discScan { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(62px); } }

/* B · motion & film — the play ring pulses outward */
.disc__motif .m-pulse {
  fill: none; stroke: var(--primary-red); stroke-width: 1.3; opacity: 0;
  transform-box: fill-box; transform-origin: center;
}
.disc__panel:hover .dm-video .m-pulse,
.disc__panel:focus-within .dm-video .m-pulse,
.disc__panel.is-open .dm-video .m-pulse { animation: discPulse 2.1s ease-out infinite; }
@keyframes discPulse {
  0%   { transform: scale(1);   opacity: .5; }
  70%  { transform: scale(1.7); opacity: 0; }
  100% { opacity: 0; }
}

/* C · brand identity — the concentric rings turn slowly */
.disc__motif .m-spin { transform-box: fill-box; transform-origin: center; }
.disc__panel:hover .dm-brand .m-spin,
.disc__panel:focus-within .dm-brand .m-spin,
.disc__panel.is-open .dm-brand .m-spin { animation: discSpin 24s linear infinite; }
@keyframes discSpin { to { transform: rotate(360deg); } }

/* D · social & ads — bars rise off the baseline, then the trend line draws */
.disc__motif .m-bar { transform-box: fill-box; transform-origin: bottom; transform: scaleY(.5); }
.disc__panel:hover .dm-social .m-bar,
.disc__panel:focus-within .dm-social .m-bar,
.disc__panel.is-open .dm-social .m-bar { transform: scaleY(1); }
.dm-social .m-bar:nth-of-type(1) { transition-delay: .02s; }
.dm-social .m-bar:nth-of-type(2) { transition-delay: .09s; }
.dm-social .m-bar:nth-of-type(3) { transition-delay: .16s; }
.dm-social .m-bar:nth-of-type(4) { transition-delay: .23s; }
.disc__motif .m-trend {
  stroke-dasharray: 1; stroke-dashoffset: 1;
  transition: stroke-dashoffset 1.1s var(--ease) .18s, opacity .6s var(--ease);
}
.disc__panel:hover .dm-social .m-trend,
.disc__panel:focus-within .dm-social .m-trend,
.disc__panel.is-open .dm-social .m-trend { stroke-dashoffset: 0; }

/* Looping motion is decorative; the static artwork alone carries the panel. */
@media (prefers-reduced-motion: reduce) {
  .dm-brand .m-spin, .dm-web .m-scan, .dm-video .m-pulse { animation: none !important; }
}

.disc__title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1rem, 1.25vw + .4rem, 1.65rem);
  line-height: 1.1; letter-spacing: -.005em;
  color: var(--text-white);
}

.disc__body {
  display: grid; gap: .8rem;
  margin-top: .8rem;
  max-width: 40ch;
}
.disc__body p {
  font-size: clamp(.78rem, .3vw + .7rem, .9rem);
  line-height: 1.62; color: var(--text-grey);
}

/* The whole panel is the click target, without wrapping each card in a second
   anchor: the existing "View Projects" link grows an overlay that covers the
   card, so the visible link stays exactly where it is and a tap anywhere
   navigates to the same place.

   The insets are deliberately oversized rather than `inset: 0`. Above 1025px
   .disc__body becomes position:absolute, which makes IT the containing block —
   `inset: 0` would then stretch only across the body copy instead of the whole
   card. Overflowing in every direction and letting .disc__panel's own
   overflow:hidden clip it back gives the same rectangle at every breakpoint,
   whichever ancestor happens to be positioned. That clip is what bounds the
   hit area, so it has to stay on the panel. */
.disc__body .link-arrow::after {
  content: "";
  position: absolute;
  inset: -100vh -100vw;
  z-index: 2;
}
/* the pointer should read as "this card is a link" anywhere on it */
.disc__panel { cursor: pointer; }
.disc__panel:hover .link-arrow { color: var(--text-white); }
.disc__panel:hover .link-arrow .arrow { transform: translateX(5px); }
/* On pointer devices the detail is held back until a panel is opened. Touch
   and narrow screens fall through to the stacked layout further down, where
   everything is visible.
   min-width must not dip to 981px (the value used elsewhere on the page):
   the mobile-stack override further down switches at max-width:1024px, and a
   981px floor here would overlap it for 43px. In that overlap the title and
   body below go position:absolute, which would pull them out of the stacked
   column layout entirely and collapse each panel to its padding. */
@media (hover: hover) and (min-width: 1025px) {
  /* Anchor the title a FIXED distance from the panel's bottom edge instead of
     letting it sit directly above the body copy in normal flow. The body's
     height varies with how much each card's description wraps to, and since
     it still occupies layout space while hidden (opacity alone doesn't
     collapse it), that variance was pushing the title to a different height
     on every card. Reserving one constant block for the body — regardless of
     its real height — pins the title to the same spot on all four. */
  .disc__panel { --disc-reserve: 9rem; }
  .disc__title {
    position: absolute;
    left: clamp(1rem, 1.8vw, 1.7rem); right: clamp(1rem, 1.8vw, 1.7rem);
    bottom: calc(clamp(1rem, 1.8vw, 1.7rem) + var(--disc-reserve));
    white-space: nowrap;
  }
  .disc__body {
    position: absolute;
    left: clamp(1rem, 1.8vw, 1.7rem); right: clamp(1rem, 1.8vw, 1.7rem);
    bottom: clamp(1rem, 1.8vw, 1.7rem);
    margin-top: 0;
    opacity: 0;
    transform: translateY(10px);
    transition: opacity .5s var(--ease), transform .55s var(--ease-out);
  }
  .disc__panel:hover .disc__body,
  .disc__panel:focus-within .disc__body { opacity: 1; transform: none; }
}

/* ============================================================================
   10  Section 04 — Career journey
   ========================================================================= */
.cv { --cv-gap: clamp(2rem, 4vw, 3.4rem); display: grid; gap: var(--cv-gap); }

.cv__row {
  display: grid;
  grid-template-columns: minmax(0, .68fr) minmax(0, 1fr) minmax(0, 1.2fr);
  gap: clamp(1rem, 2.6vw, 2.8rem);
  align-items: start;
}

/* Every range stays on ONE line. The widest is "2026 — Present", so the type
   scales off that: nowrap plus a ceiling low enough that the longest range
   still fits its column. Without nowrap the shorter all-digit ranges fitted
   and the two longer ones broke onto a second line, which read as a bug. */
.cv__years {
  display: flex; flex-wrap: nowrap; align-items: baseline; gap: .1em .28em;
  white-space: nowrap;
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.4rem, 1.9vw + .2rem, 2.5rem);
  line-height: .96;
  color: var(--text-dim);
  transition: color .8s var(--ease);
}
.cv__row.is-in .cv__years { color: var(--text-grey); }
.cv__row--current .cv__years,
.cv__row--current.is-in .cv__years { color: var(--text-white); }
.cv__dash { color: var(--primary-red); }

/* the spine: a hairline down the left of the middle column, with a red
   segment that fills as each row reveals */
.cv__what {
  position: relative;
  padding-left: clamp(1.1rem, 2vw, 1.9rem);
}
.cv__what::before,
.cv__what::after {
  content: ""; position: absolute; left: 0; top: .35em; width: 1px;
}
.cv__what::before { bottom: calc(-1 * var(--cv-gap)); background: var(--border-soft); }
.cv__what::after  { height: 0; background: var(--primary-red); transition: height 1.1s var(--ease-out) .15s; }
.cv__row.is-in .cv__what::after { height: calc(100% - .35em); }
.cv__row:last-child .cv__what::before { bottom: auto; height: calc(100% - .35em); }

/* Only the current row carries a dot — on the past rows it marked nothing the
   spine wasn't already saying, so the markup was dropped there. */
.cv__dot {
  position: absolute; left: 0; top: .35em;
  width: 9px; height: 9px; border-radius: 50%;
  transform: translateX(-4px);
  background: var(--primary-red);
  border: 1px solid var(--primary-red);
  box-shadow: 0 0 0 4px rgba(177, 98, 254, .18), 0 0 18px rgba(177, 98, 254, .7);
}

.cv__role {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1rem, 1.1vw + .45rem, 1.45rem);
  line-height: 1.14; color: var(--text-white);
}
.cv__org {
  font-size: clamp(.8rem, .3vw + .72rem, .92rem);
  color: var(--primary-red);
  margin-top: .2rem;
}
.cv__meta {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.62rem, .26vw + .56rem, .74rem);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-dim);
  margin-top: .15rem;
}

.cv__note p {
  font-size: clamp(.82rem, .32vw + .73rem, .95rem);
  line-height: 1.68; color: var(--text-grey);
  max-width: 52ch;
}
.cv__caps {
  display: flex; flex-wrap: wrap; gap: .4rem;
  margin-top: .9rem;
}
.cv__caps li {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.58rem, .24vw + .53rem, .7rem);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-grey);
  padding: .45em .9em;
  border: 1px solid var(--border-soft);
}
/* Both labels are <p> inside .cv__note, so they have to out-specify
   `.cv__note p` (0-1-1) or they inherit body copy's size and colour and the
   whole note flattens into one grey block. Hence the .cv__note prefix.
   Three levels, three colours: description = grey body copy, "Current focus"
   = red (it only appears on the live chapter), "Selected work" = dim, since
   it is just a structural label above the links. */
.cv__note .cv__focus-label,
.cv__note .cv__selected-label {
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.58rem, .24vw + .53rem, .7rem);
  line-height: 1.3;
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--text-dim);
  margin-top: 1rem;
}
.cv__note .cv__focus-label { color: var(--primary-red); }
.cv__selected { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid var(--border-soft); }
.cv__note .cv__selected-label { margin-top: 0; margin-bottom: .55rem; }
.cv__selected-list { display: flex; flex-wrap: wrap; gap: .35rem .9rem; }
.cv__selected-list a {
  font-size: clamp(.74rem, .28vw + .67rem, .86rem);
  color: var(--text-grey);
  border-bottom: 1px solid transparent;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}
.cv__selected-list a:hover,
.cv__selected-list a:focus-visible { color: var(--text-white); border-bottom-color: var(--primary-red); }

/* ============================================================================
   11  Section 05 — Education (deliberately light)
   ========================================================================= */
.sec--edu { padding-block: clamp(2rem, 4.5vw, 3.6rem); }
.edu {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  padding: clamp(1.2rem, 2.6vw, 2.2rem) clamp(1.2rem, 2.6vw, 2.4rem);
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background:
    radial-gradient(90% 140% at 96% 50%, rgba(61, 20, 112, .2), transparent 60%),
    var(--surface);
}
.edu__ghost {
  position: absolute; right: clamp(-.5rem, 1vw, 1rem); top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(3.4rem, 12vw, 9rem);
  line-height: .8; letter-spacing: -.02em;
  color: rgba(255, 255, 255, .045);
  pointer-events: none; user-select: none;
}
.edu__body { position: relative; z-index: 1; }
.edu__body .sec__label { margin-bottom: .6rem; }
.edu__degree {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1.2rem, 1.7vw + .5rem, 2rem);
  line-height: 1.1; color: var(--text-white);
}
.edu__org {
  font-size: clamp(.8rem, .3vw + .72rem, .92rem);
  color: var(--primary-red);
  margin-top: .15rem;
}
.edu__desc {
  font-size: clamp(.8rem, .3vw + .72rem, .92rem);
  line-height: 1.66; color: var(--text-grey);
  max-width: 54ch; margin-top: .5rem;
}
.edu__tag {
  position: relative; z-index: 1;
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.58rem, .24vw + .53rem, .7rem);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--text-dim);
  padding-left: 3.2rem;
  white-space: nowrap;
}
.edu__tag::before {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 2.6rem; height: 1px; background: var(--primary-red);
}

/* ============================================================================
   12  Section 06 — Generative AI
   ========================================================================= */
/* Compare (vertical) paired with the tool wall as two equal halves. Reuses
   the 860px breakpoint the toolgrid already collapsed at, so both halves
   drop to full width in the same step rather than two different ones.
   gap supplies spacing on both axes, so it still works once the query below
   collapses this to a single column (compare stacked above the tool wall). */
.ai-layout {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.75rem, 3.4vw, 3rem);
  align-items: start;
}
.ai-layout .compare { margin-bottom: 0; }
.ai-layout .tools { margin-top: 0; }
@media (max-width: 860px) {
  .ai-layout { grid-template-columns: 1fr; }
}

.compare { margin-bottom: clamp(1.6rem, 3.4vw, 2.8rem); }
.compare__stage {
  position: relative;
  /* portrait, matches the supplied 1200x1600 before/after captures */
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--surface);
}
.compare__stage:focus-within { outline: 2px solid var(--primary-red); outline-offset: 3px; }
.compare__layer { position: absolute; inset: 0; }
.compare__layer img { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; }
/* the two fallback labels are pushed to opposite sides so they do not collide
   across the wipe line while the real images are still missing */
.compare__ghost { font-size: clamp(1rem, 2.4vw, 2rem); }
.compare__layer--before .compare__ghost { place-items: center start; padding-left: 7%; }
.compare__layer--after  .compare__ghost { place-items: center end;   padding-right: 7%; }
/* The "before" layer is clipped to the slider position.
   z-index is load-bearing, not cosmetic: clip-path makes this layer a
   STACKING CONTEXT, so it paints atomically at its own z-index. Its sibling
   --after has no clip-path and so creates no stacking context, which promotes
   that layer's <img> (z-index: 1, set above) into the shared parent context.
   Left at z-index auto the clipped layer therefore sits at level 0 and the
   after image paints straight over the whole of it — the wipe looks dead and
   only the "after" shot is ever visible, at every slider position. Anything
   above 1 fixes it; 2 keeps it under the handle (3) and the range (4). */
.compare__layer--before {
  clip-path: inset(0 calc(100% - var(--pos, 50%)) 0 0);
  z-index: 2;
}
.compare__handle {
  position: absolute; top: 0; bottom: 0; left: var(--pos, 50%); z-index: 3;
  width: 2px; margin-left: -1px;
  background: var(--primary-red);
  box-shadow: 0 0 18px rgba(177, 98, 254, .8);
  pointer-events: none;
}
.compare__handle::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 2.4rem; height: 2.4rem; transform: translate(-50%, -50%);
  border: 1px solid var(--primary-red); border-radius: 50%;
  background: rgba(7, 7, 7, .6);
}
.compare__range {
  position: absolute; inset: 0; z-index: 4;
  width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: ew-resize;
  -webkit-appearance: none; appearance: none; background: none;
}
.compare__cap {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem 1.5rem;
  margin-top: .8rem;
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .26vw + .55rem, .72rem);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-dim);
}
.compare__cap b { color: var(--primary-red); font-weight: 500; }

/* --- Tools & models ------------------------------------------------------
   A logo wall of the AI tools actually in use, sitting under the before/after
   comparison. Every tile holds an optional logo image stacked over a CSS
   monogram: the monogram is the resting state and the logo covers it once the
   file resolves, so the row reads as finished whether or not assets/ai/tools/
   has been filled in. The marks ship pre-flattened to white — twelve vendor
   palettes at full strength would shred the section — and the tile holds them
   below full strength at rest, lifting them on hover.
   ------------------------------------------------------------------------ */
.tools { margin-top: clamp(2rem, 4vw, 3.4rem); }

.tools__label {
  display: flex; align-items: center; gap: .8rem;
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.62rem, .26vw + .56rem, .74rem);
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: clamp(1rem, 1.8vw, 1.5rem);
}
.tools__rule {
  flex: 0 0 clamp(1.6rem, 4vw, 3.2rem);
  height: 1px; background: var(--primary-red);
}

/* 2 columns by default — the wall now lives in half the section width,
   beside the compare figure, so 12 tiles run 2x6 rather than 4x3. */
.toolgrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(.5rem, 1vw, .9rem);
}

/* Grid rather than flex so the phone layout below can turn the tile on its
   side — logo spanning both text rows — without a wrapper element. */
.tool {
  display: grid;
  grid-template-areas: "logo" "name" "use";
  justify-items: center; row-gap: .3rem;
  text-align: center;
  padding: clamp(.8rem, 1.6vw, 1.25rem) clamp(.5rem, 1vw, .8rem);
  border: 1px solid var(--border-soft);
  background: var(--surface);
  transition: border-color .4s var(--ease), background-color .4s var(--ease),
              transform .4s var(--ease);
}
.tool:hover {
  border-color: var(--border-red);
  background: var(--surface-light);
  transform: translateY(-3px);
}

/* logo and monogram share one grid cell, so the logo paints over the mark */
.tool__logo {
  grid-area: logo;
  display: grid; place-items: center;
  /* One height for the row AND for the artwork inside it — see the note on
     .tool__logo img for why the mark is not allowed to say "100%" here. */
  --tool-logo-h: clamp(1.7rem, 2.6vw, 2.15rem);
  width: 100%; height: var(--tool-logo-h);
  margin-bottom: .15rem;
}
.tool__logo > *,
.tool__logo img { grid-area: 1 / 1; }

/* display: contents here is load-bearing, not tidying. The <img> is wrapped in
   a <picture> (script.js's optional-image contract wants one so it can drop the
   pair together), and a percentage max-height on the img resolves against THAT
   wrapper, not against this box. <picture> is an auto-height inline box, so the
   percentage never resolves, max-height silently does nothing, and the mark
   sizes on max-width alone — which lands a 1:1 logo about four times taller
   than the row and blows the tile apart. Taking the wrapper out of the box
   model makes the <img> the grid item itself, so 100% finally means this box.
   Same trap as the percentage-height media elsewhere in the project. */
.tool__logo picture { display: contents; }

/* The marks ship as white artwork (see the note in index.html), so there is no
   logo colour left to normalise — the tile just holds them at 74% and takes
   them to full white on hover, which is the same lift the monogram makes when
   it goes red. */
/* BOTH axes are stated outright, and neither is a percentage of this box.
   Sizing the mark as height:100% + width:auto is the obvious way to write this
   and it very nearly works — seven of the eight marks obeyed it. Chrome sized
   the Flow mark off its intrinsic ratio instead and ignored height:100% AND
   max-height:100% on that one element, drawing it 106px tall in a 34px row.
   Re-origining its viewBox did not change that. Rather than keep guessing at
   which replaced-element path Chrome takes, the height here is the same
   absolute value the row uses, so there is no percentage left to resolve
   against, and object-fit does the fitting: every mark is scaled to sit inside
   an 80%-wide, one-row-tall window, whether it is a 1:1 glyph or a 6.9:1
   lockup. The marks are 16-24px artboards, so they scale UP to the row — a
   max-height alone would leave them reading smaller than the monogram they
   replaced. */
.tool__logo img {
  height: var(--tool-logo-h); width: 80%;
  object-fit: contain;
  opacity: .74;
  transition: opacity .4s var(--ease);
}
.tool:hover .tool__logo img { opacity: 1; }

.tool__mono {
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1vw + .7rem, 1.4rem);
  line-height: 1; color: rgba(255, 255, 255, .26);
  transition: color .4s var(--ease), opacity .35s var(--ease);
}
.tool:hover .tool__mono { color: rgba(177, 98, 254, .85); }

/* a logo file that actually resolved retires the monogram behind it. With JS
   off the optional-image pass never runs, so drop the mark rather than risk
   stacking it under a real logo. */
.js .tool:has(.tool__logo img.is-loaded) .tool__mono { opacity: 0; }
html:not(.js) .tool__mono { display: none; }

.tool__name {
  grid-area: name;
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.76rem, .28vw + .7rem, .88rem);
  line-height: 1.15; color: var(--text-white);
}
.tool__use {
  grid-area: use;
  font-size: clamp(.62rem, .22vw + .58rem, .7rem);
  line-height: 1.4; color: var(--text-dim);
  transition: color .4s var(--ease);
}
.tool:hover .tool__use { color: var(--text-grey); }

.tools__note {
  margin-top: clamp(1rem, 1.8vw, 1.5rem);
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.66rem, .26vw + .6rem, .78rem);
  letter-spacing: .06em; color: var(--text-dim);
}

/* Below 640 the tile turns on its side and runs full width: two columns of
   ~170px cramp the usage line onto four lines on a phone. */
@media (max-width: 640px) {
  .toolgrid { grid-template-columns: 1fr; }
  /* The logo column is 4rem rather than the square 2.4rem the monograms wanted,
     because two of the twelve marks are wordmarks. A 6.9:1 lockup inside a
     2.4rem square resolves to about 4px of letter height — present, but not
     readable as anything. 4rem buys back roughly the width they need without
     squeezing the usage line into a third row. */
  .tool {
    grid-template-areas: "logo name"
                         "logo use";
    grid-template-columns: 4rem 1fr;
    column-gap: .9rem; row-gap: .1rem;
    align-content: center; justify-items: start;
    text-align: left;
    padding: .85rem 1rem;
  }
  .tool:hover { transform: none; }
  .tool__logo {
    --tool-logo-h: 2.4rem;
    width: 4rem;
    align-self: center; margin-bottom: 0;
  }
  /* the column is already sized for the widest mark, so nothing is held back */
  .tool__logo img { width: 100%; }
  .tool__mono { font-size: 1.15rem; }
}

/* ============================================================================
   13  Section 07 — Work archive
   ========================================================================= */
.filters {
  display: flex; flex-wrap: wrap; gap: .45rem;
  margin-bottom: clamp(1rem, 2.2vw, 1.6rem);
}
.filters__btn {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .26vw + .55rem, .72rem);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-grey);
  padding: .6em 1.15em;
  border: 1px solid var(--border-soft);
  background: none; cursor: pointer;
  transition: color .3s var(--ease), border-color .3s var(--ease),
              background-color .3s var(--ease);
}
.filters__btn:hover { color: var(--text-white); border-color: rgba(177, 98, 254, .5); }
.filters__btn.is-active {
  color: #fff;
  border-color: var(--primary-red);
  background: rgba(177, 98, 254, .16);
}
.filters__status {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.58rem, .24vw + .53rem, .7rem);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: clamp(.8rem, 1.8vw, 1.3rem);
  min-height: 1em;
}

.archive {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 13.5rem), 1fr));
  gap: clamp(.7rem, 1.2vw, 1.15rem);
}
.arch__link { display: block; }
.arch__thumb {
  position: relative; display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  transition: border-color .4s var(--ease);
}
.arch__thumb--tall { aspect-ratio: 3 / 4; }
.arch__thumb--wide { aspect-ratio: 16 / 9; }
.arch__thumb img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .8s var(--ease-out);
}
.arch__ghost { font-size: clamp(1.4rem, 3.4vw, 2.6rem); }
.arch__link:hover .arch__thumb { border-color: rgba(177, 98, 254, .5); }
.arch__link:hover .arch__thumb img { transform: scale(1.05); }

.arch__meta { display: grid; gap: .1rem; padding-top: .6rem; }
.arch__meta b {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.8rem, .3vw + .72rem, .95rem);
  color: var(--text-white);
}
.arch__meta em {
  font-style: normal;
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .24vw + .55rem, .7rem);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-dim);
}
.arch__year { font-size: .7rem; color: var(--primary-red); letter-spacing: .1em; }

/* ============================================================================
   14  Section 08 — Perspective
   ========================================================================= */
.sec--persp {
  background:
    radial-gradient(70% 90% at 14% 10%, rgba(61, 20, 112, .3), transparent 62%),
    var(--background);
}
.persp {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(0, .78fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: end;
}
.persp .sec__label { grid-column: 1 / -1; }
.persp__statement {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1.75rem, 4.4vw + .3rem, 4.6rem);
  line-height: 1.04;
  letter-spacing: -.018em;
  color: var(--text-white);
  text-wrap: balance;
}
.persp__accent { color: var(--primary-red); }
.persp__side { display: grid; gap: .9rem; padding-bottom: .4rem; }
.persp__side p {
  font-size: clamp(.84rem, .32vw + .75rem, .98rem);
  line-height: 1.7; color: var(--text-grey);
}
.persp__star { width: 1.15rem; height: 1.15rem; }

/* ============================================================================
   15  Section 09 — Professional contact
   ========================================================================= */
.sec--contact { border-bottom: 0; }
.contact {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr);
  gap: clamp(1.6rem, 4vw, 4rem);
  align-items: start;
}
.contact__lede {
  max-width: 42ch; margin-top: clamp(.9rem, 2vw, 1.4rem);
  font-size: clamp(.84rem, .32vw + .75rem, .98rem);
  line-height: 1.7; color: var(--text-grey);
}
.contact__now {
  display: grid; gap: .1rem;
  margin-top: clamp(1rem, 2.2vw, 1.5rem);
  padding-left: 1rem;
  border-left: 2px solid var(--primary-red);
  font-family: var(--font-cond);
  font-size: clamp(.68rem, .28vw + .6rem, .8rem);
  letter-spacing: .12em; text-transform: uppercase;
}
.contact__now span:first-child { color: var(--text-white); font-weight: 500; }
.contact__now span:last-child  { color: var(--text-dim); font-weight: 300; }

.contact__actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: .7rem 1.1rem;
  margin-top: clamp(1.3rem, 2.8vw, 2rem);
}
.btn {
  display: inline-flex; align-items: center; gap: .8rem;
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.66rem, .28vw + .58rem, .78rem);
  letter-spacing: .18em; text-transform: uppercase;
  padding: 1em 1.5em;
  border: 1px solid transparent;
  transition: background-color .35s var(--ease), border-color .35s var(--ease),
              color .35s var(--ease), box-shadow .35s var(--ease);
}
.btn--primary {
  color: #fff;
  border-color: var(--primary-red);
  background: rgba(177, 98, 254, .16);
  box-shadow: 0 0 26px -6px rgba(177, 98, 254, .6);
}
.btn--primary:hover { background: var(--primary-red); box-shadow: 0 0 34px -4px rgba(177, 98, 254, .85); }
.btn--primary .arrow { width: 1.9rem; }
.btn--primary:hover .arrow { transform: translateX(5px); }
.btn--ghost { color: var(--text-white); border-color: var(--border); }
.btn--ghost:hover { border-color: var(--primary-red); background: rgba(177, 98, 254, .1); }
.btn--text {
  padding-inline: 0; color: var(--text-grey);
  border-bottom: 1px solid var(--border-soft);
}
.btn--text:hover { color: var(--text-white); border-bottom-color: var(--primary-red); }

.contact__list { display: grid; gap: clamp(.2rem, .5vw, .5rem); }
.contact__list li {
  display: flex; align-items: center; gap: clamp(.8rem, 1.3vw, 1.2rem);
  padding-block: clamp(.55rem, 1vw, .9rem);
  border-bottom: 1px solid rgba(177, 98, 254, .28);
}
.contact__list li:last-child { border-bottom: 0; }
.contact__list .icon-ring { transition: background-color .4s var(--ease), box-shadow .4s var(--ease); }
.contact__list li:hover .icon-ring {
  background: rgba(177, 98, 254, .14);
  box-shadow: 0 0 20px -6px rgba(177, 98, 254, .85);
}
.contact__row { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.contact__row em {
  font-style: normal;
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .28vw + .53rem, .72rem);
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim);
}
.contact__row a, .contact__row .plain {
  font-size: clamp(.8rem, .4vw + .68rem, .95rem);
  color: var(--text-white);
  overflow-wrap: anywhere;
  transition: color .3s var(--ease);
}
.contact__row a:hover { color: var(--primary-red); }

/* ============================================================================
   17  Footer
   ========================================================================= */
.foot {
  border-top: 1px solid rgba(177, 98, 254, .32);
  padding-block: clamp(1.6rem, 3.4vw, 2.6rem);
  background: var(--background);
}
.foot__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  align-items: start;
  gap: clamp(1rem, 3vw, 2.5rem);
}
.foot__name {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.95rem, .6vw + .7rem, 1.15rem);
  letter-spacing: .02em; color: var(--text-white);
}
.foot__role, .foot__disc {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .26vw + .55rem, .72rem);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-dim);
}
.foot__role { color: var(--primary-red); }
.foot__line {
  align-self: center;
  font-size: clamp(.76rem, .3vw + .68rem, .88rem);
  color: var(--text-grey);
  max-width: 34ch;
}
.foot__links {
  display: flex; flex-wrap: wrap; gap: .6rem 1.4rem;
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.62rem, .26vw + .56rem, .74rem);
  letter-spacing: .16em; text-transform: uppercase;
}
.foot__links a {
  color: var(--text-grey);
  border-bottom: 1px solid transparent;
  padding-block: .2rem;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}
.foot__links a:hover { color: var(--text-white); border-bottom-color: var(--primary-red); }
.foot__legal {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: .4rem 1.5rem;
  margin-top: clamp(1rem, 2.4vw, 1.8rem);
  padding-top: clamp(.8rem, 1.8vw, 1.2rem);
  border-top: 1px solid var(--border-soft);
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.58rem, .24vw + .53rem, .7rem);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-dim);
}

/* ------------------------------------------------------------ 9 responsive */

/* wide laptops — keep the editorial ratio but ease the type */
@media (max-width: 1200px) {
  .hero__grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr) minmax(0, .8fr); }
  .hero-portrait { transform: translateX(-44%); height: 88%; }
}

/* ---- tablet ---- */
@media (max-width: 1024px) {
  :root { --shell: 900px; }

  .hero { min-height: clamp(500px, 72vw, 680px); }

  /* display:contents lets the rail's two blocks be placed independently:
     the tagline joins the intro column, the stats stay bottom-right clear
     of the portrait. */
  .hero__grid {
    grid-template-columns: minmax(0, 1.14fr) minmax(0, .86fr);
    align-items: end;
    align-content: end;          /* pack the rows against the hero baseline */
    gap: 0 clamp(1rem, 3vw, 2rem);
  }
  .hero__intro { grid-column: 1; grid-row: 1; }
  .hero__rail  { display: contents; }
  .hero__tagline {
    grid-column: 1; grid-row: 2;
    margin-top: clamp(1.1rem, 3vw, 1.9rem);
    max-width: 30ch;
  }
  .hero__tagline p { max-width: none; }
  .stats { grid-column: 2; grid-row: 1 / span 2; align-self: end; }

  /* the intro column is wider here, so the left scrim has to reach further
     across before it releases the word */
  .hero::after {
    background:
      linear-gradient(96deg, rgba(7,7,7,.92) 0%, rgba(7,7,7,.80) 22%, rgba(7,7,7,.45) 44%, rgba(7,7,7,.12) 62%, transparent 74%),
      linear-gradient(0deg,  rgba(7,7,7,.60) 0%, rgba(7,7,7,.18) 18%, transparent 34%);
  }

  .hero-portrait {
    left: auto; right: -6%;
    transform: none;
    height: 78%;
    opacity: .85;
  }
  .hero__name { font-size: clamp(3rem, 10.5vw, 6rem); }
  .hero__lede { max-width: 34ch; }

  /* ---- post-hero sections at tablet ---- */

  /* brand grid drops from a 12-col composition to a 6-col one; the spans are
     re-authored rather than left to collapse arbitrarily. The header goes back
     to full width — there is no longer a wide enough gap beside it to fill. */
  .work-layout { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .sec--work .sec__head {
    grid-column: 1 / -1; grid-row: auto;
    margin-bottom: clamp(.6rem, 2vw, 1.4rem);
  }
  .brand--vs, .brand--wb, .brand--del { grid-column: span 6; }
  .brand--vs   { grid-row: auto; }
  .brand--sie  { grid-column: span 3; grid-row: auto; }
  .brand--aiff { grid-column: span 3; }
  .brand--lt, .brand--bbw { grid-column: span 3; }
  /* height:auto releases the row fills so the ratios can take over again */
  .brand--sie  .brand__link { height: auto; aspect-ratio: 4 / 4.6; }
  .brand--vs   .brand__link { height: auto; min-height: 0; aspect-ratio: 16 / 9.4; }
  .brand--aiff .brand__link { aspect-ratio: 4 / 4.6; }
  .brand--del  .brand__link { aspect-ratio: 16 / 8; }

  .purpose { grid-template-columns: minmax(0, 1fr); }
  .purpose__strip { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  /* the four discipline columns become a stacked list: everything visible,
     nothing depending on hover */
  .disc { flex-direction: column; min-height: 0; }
  .disc__panel { flex: none; }
  .disc__title { white-space: normal; }
  /* A stacked panel is a fraction of the height of a desktop column, so the
     watermark has to shrink with it. At the desktop 85% it measured 286x229
     inside a 338x155 panel — half of it clipped away by the panel's own
     overflow, and what was left read as a background texture rather than a
     motif sitting in the corner.

     The panel then reserves that column on its right: on the tall desktop
     cards the copy sits far below the motif and never meets it, but once the
     panel collapses to ~155px the description wraps straight through the
     artwork. Padding the text back is what keeps both readable. */
  .disc__panel {
    --disc-motif-w: clamp(84px, 27%, 140px);
    padding-right: calc(var(--disc-motif-w) + clamp(1rem, 1.8vw, 1.7rem) + .6rem);
  }
  .disc__motif { width: var(--disc-motif-w); }

  .cv__row { grid-template-columns: minmax(0, .5fr) minmax(0, 1fr); }
  .cv__note { grid-column: 2; padding-left: clamp(1.1rem, 2vw, 1.9rem); }

  .persp { grid-template-columns: minmax(0, 1fr); }
  .contact { grid-template-columns: minmax(0, 1fr); }
  .foot__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .foot__line { grid-column: 1 / -1; order: 3; }
}

/* ---- large phone / small tablet ---- */
@media (max-width: 860px) {
  .sec__head--row { align-items: flex-start; }
  .purpose__strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .edu { grid-template-columns: minmax(0, 1fr); }
  .edu__tag { padding-left: 0; padding-top: 1rem; }
  .edu__tag::before { top: 0; width: 2.6rem; }
}

/* ---- mobile ---- */
@media (max-width: 640px) {
  :root { --gutter: 1.15rem; }

  /* above .mobilenav (150) so the burger stays tappable to close the menu */
  .topbar { z-index: 200; background: transparent; }
  /* Only the two pages with a burger start transparent — their hero sits
     behind the bar at rest. Now that the bar stays put, it has to take a
     background as soon as the page moves under it. The case studies set
     their own solid colour and are left alone. */
  .topbar:has(.burger).is-stuck { background: var(--background); }
  .topbar__inner { align-items: center; gap: .75rem; }
  .topbar__id { font-size: .7rem; min-width: 0; }
  .burger { display: flex; }

  /* Pages that carry a burger hand the nav over to the full-screen panel.
     The case studies have no burger, so there the bar itself stays the nav —
     tightened up, and the identity line ellipses rather than pushing it off. */
  .topbar__inner:has(.burger) .topnav { display: none; }
  .cs-brand { min-width: 0; }
  .topbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .topnav { gap: 1rem; font-size: .62rem; letter-spacing: .1em; }
  .topnav__menu {
    min-width: min(16rem, calc(100vw - 2 * var(--gutter)));
    max-height: 60vh; overflow-y: auto; overscroll-behavior: contain;
  }

  /* ---- mobile hero ------------------------------------------------------
     Per the supplied phone design the hero stops being one stacked layer:
     the portrait comes OUT of absolute positioning into the flow, at full
     opacity, as the top block — and the copy sits below it rather than over
     it. Everything after this re-anchors around that. */
  .hero {
    display: block;
    min-height: auto;
    padding-bottom: clamp(1.5rem, 6vw, 2.5rem);
  }
  .hero__word text { letter-spacing: 0; }

  /* the word is now a band behind the portrait's head, not the whole hero */
  .hero__word { top: 2%; height: 27%; }

  /* nothing sits on the word any more, so the scrim only has to sink the
     lower half of the hero to black behind the copy */
  .hero::after {
    background: linear-gradient(180deg,
      rgba(7,7,7,.08) 0%, rgba(7,7,7,.24) 20%, rgba(7,7,7,.7) 42%, rgba(7,7,7,.96) 60%);
  }
  .hero__glow { top: 1%; width: 118vw; transform: translateX(-46%); }

  .hero-portrait {
    position: relative;
    left: auto; right: auto; bottom: auto;
    transform: none;
    display: block;
    height: auto;
    opacity: 1;
    z-index: 2;
    padding-top: clamp(1rem, 5vw, 1.8rem);
  }
  .hero-portrait::after { display: none; }      /* the mask below replaces it */
  .hero-portrait img {
    width: 93%;
    height: auto;
    margin-left: auto;
    margin-right: -4%;
    filter:
      drop-shadow(-12px 0 30px rgba(177, 98, 254, .45))
      contrast(1.05) saturate(1.05);
    /* dissolve the cropped chest into the black instead of ending on a hard
       horizontal edge above the copy */
    -webkit-mask-image: linear-gradient(180deg, #000 52%, rgba(0,0,0,.5) 76%, transparent 92%);
            mask-image: linear-gradient(180deg, #000 52%, rgba(0,0,0,.5) 76%, transparent 92%);
  }

  .hero__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(1.2rem, 5vw, 1.9rem);
    padding-top: 0;
    margin-top: clamp(-7rem, -24vw, -3rem);     /* tuck the copy into the fade */
  }
  .hero__intro   { grid-column: 1; grid-row: 1; }
  .hero__tagline { grid-column: 1; grid-row: 2; margin-top: 0; }

  /* The three figures sit SIDE BY SIDE on phones. Stacked they read as three
     unrelated rows and eat most of a screen; in a row they read as one band of
     credentials. Each stat turns into a column (number over label) and the
     borders move to the outside of the band plus dividers between cells. */
  .stats {
    grid-column: 1; grid-row: 3;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border-top: 1px solid var(--border-soft);
    border-bottom: 1px solid var(--border-soft);
  }
  .stat {
    flex-direction: column; align-items: flex-start;
    gap: .3rem;
    padding-block: clamp(.65rem, 3vw, .95rem);
    padding-inline: 0 .5rem;
    border-bottom: 0;
  }
  .stat + .stat {
    border-left: 1px solid var(--border-soft);
    padding-inline-start: clamp(.55rem, 3.2vw, .95rem);
  }
  .stat:first-child { border-top: 0; }

  .hero__hello { font-size: clamp(1.6rem, 7vw, 2.4rem); }
  .hero__name  { font-size: clamp(2.9rem, 17.4vw, 5rem); }
  .hero__title { font-size: clamp(1.05rem, 4.9vw, 1.5rem); }
  /* the design sets the paragraph on a narrower measure than the rest of the
     copy rather than letting it run gutter to gutter */
  .hero__lede  { max-width: 36ch; }
  .hero__tagline p { max-width: 26ch; }
  /* min-width is what kept the numbers aligned in the stacked list; in three
     narrow columns it only forces overflow, so it goes */
  .stat__num { min-width: 0; font-size: clamp(1.45rem, 7.6vw, 2.3rem); }
  .stat__label { font-size: clamp(.52rem, 2.4vw, .66rem); letter-spacing: .1em; }

  /* short red rule between the red subtitle and the paragraph */
  .hero__rule {
    display: block;
    width: 2.4rem; height: 3px;
    background: var(--primary-red);
    margin-bottom: clamp(.9rem, 4vw, 1.3rem);
  }

  .hero__cta {
    display: flex; align-items: center; justify-content: center;
    position: relative;
    width: 100%;
    margin-top: clamp(1.1rem, 5vw, 1.7rem);
    padding: 1.05em 3.2em 1.05em 1.4em;
    border: 1px solid var(--primary-red);
    border-radius: 6px;
    font-family: var(--font-cond); font-weight: 500;
    font-size: clamp(.78rem, 3.4vw, .95rem);
    letter-spacing: .22em; text-transform: uppercase;
    color: var(--text-white);
    background: linear-gradient(180deg, rgba(177, 98, 254,.10), rgba(177, 98, 254,.02));
    box-shadow: 0 0 26px -4px rgba(177, 98, 254, .55),
                inset 0 0 22px rgba(177, 98, 254, .12);
    transition: background-color .35s var(--ease), box-shadow .35s var(--ease);
  }
  .hero__cta .arrow {
    position: absolute; right: 1.3em;
    width: 1.6rem; color: var(--primary-red);
  }
  .hero__cta:active {
    background: rgba(177, 98, 254, .22);
    box-shadow: 0 0 34px -4px rgba(177, 98, 254, .8);
  }

  /* quiet secondary link under the CTA — deliberately not a second button */
  .hero__sub-cta {
    display: block;
    margin-top: clamp(.8rem, 3.5vw, 1.1rem);
    text-align: center;
    font-family: var(--font-cond); font-weight: 300;
    font-size: clamp(.66rem, 3vw, .78rem);
    letter-spacing: .18em; text-transform: uppercase;
    color: var(--text-grey);
    padding-block: .4rem;
  }
  .hero__sub-cta:active { color: var(--text-white); }

  /* ---- post-hero sections on phones ---- */

  /* Section 01's cards are not handled here — they have their own block below
     that opens at 767px rather than 640px. Splitting them across two queries
     only produced two sets of rules fighting over the same six properties. */

  .purpose__strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .purpose__frame { aspect-ratio: 4 / 3; }

  /* the timeline collapses to a single column: years sit above the role, and
     the note keeps the same spine indent so nothing overlaps */
  .cv__row { grid-template-columns: minmax(0, 1fr); gap: .8rem; }
  .cv__years { font-size: clamp(1.6rem, 9vw, 2.6rem); }
  .cv__note { grid-column: 1; padding-left: clamp(1.1rem, 4vw, 1.9rem); }

  .archive { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .arch__thumb, .arch__thumb--tall, .arch__thumb--wide { aspect-ratio: 4 / 3; }

  .contact__actions .btn { width: 100%; justify-content: center; }
  .btn--text { width: auto !important; }

  .foot__inner { grid-template-columns: minmax(0, 1fr); }
  .foot__line { order: 0; }
}

/* ---- Section 01 brand cards on phones -------------------------------------
   Below 768px the asymmetric composition has nothing left to be asymmetric
   about: every card is the full container width, so the ratios that carry the
   desktop grid only squash each card into a letterbox that the copy then has
   to fight. Here they become one poster proportion instead — tall enough that
   the image reads on its own and the text block sits in a gradient that is
   the same density on every card regardless of what is behind it.

   The breakpoint is 767px rather than the 640px used elsewhere so the range
   between them stops showing two half-width cards per row. Everything above
   767px — spans, ratios, hover, type — is untouched.                        */
@media (max-width: 767px) {

  .work-layout {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 18px;               /* card-to-card rhythm; the header keeps its
                                    own margin-bottom above */
  }
  .sec--work .sec__head { grid-column: 1 / -1; }
  .brand--vs, .brand--wb, .brand--sie,
  .brand--aiff, .brand--del, .brand--lt, .brand--bbw {
    grid-column: 1 / -1;
    grid-row: auto;
  }

  /* ONE proportion for all seven. The clamp is what keeps the deck even:
     118vw is the poster ratio, the 400/440 bounds stop a 320px screen from
     producing a card too short for the copy and a 430px one from producing a
     card taller than the fold. Ratios and the two height:100% cards from the
     wider breakpoints all have to be released for this to apply. */
  .brand .brand__link {
    aspect-ratio: auto;
    height: clamp(400px, 118vw, 440px);
    min-height: 400px;
    max-height: 440px;
  }

  /* Per-card framing. The crop that works in a 16/5.4 desktop band is rarely
     the crop that works in a portrait one, so the two cards whose subject sits
     high are pinned to the top and Warner Bros is pushed off-centre to keep
     its figure out of the copy. No image files change. */
  .brand--vs   .brand__media img { object-position: center top; }
  .brand--bbw  .brand__media img { object-position: center top; }
  .brand--wb   .brand__media img { object-position: 62% center; }
  .brand--sie  .brand__media img,
  .brand--aiff .brand__media img,
  .brand--del  .brand__media img,
  .brand--lt   .brand__media img { object-position: center center; }

  .brand__media img {
    width: 100%; height: 100%;
    object-fit: cover;
    transition: transform .6s var(--ease-out), filter .6s var(--ease);
  }

  /* Directional scrim rather than a flat veil: the top of the image stays
     essentially untouched and the bottom lands on the same near-black on every
     card, which is what makes readability independent of the photograph.

     The ramp is pulled up to meet the copy. The tallest block is 232px in a
     400px card — the 320px case where a brand name wraps to two lines — which
     puts the category label 42% down. A scrim that only reaches .58 at 62%
     leaves that label out in the open, and it was landing unreadably on the
     AIFF stadium and the Bath & Body Works bottles. Reaching .62 by 40% is
     what covers the block at every supported width and wrap count, and the
     top 30% — where the faces sit on every current cover — stays clean.

     The hover variant is pinned to the same value so a touch device that
     latches :hover after a tap cannot shift it. */
  .brand__media::after,
  .brand__link:hover .brand__media::after {
    background: linear-gradient(
      to bottom,
      rgba(0, 0, 0, .02) 10%,
      rgba(0, 0, 0, .14) 26%,
      rgba(0, 0, 0, .62) 40%,
      rgba(0, 0, 0, .92) 64%,
      rgba(0, 0, 0, .98) 100%
    );
  }

  /* The body still spans the whole card — that is what gives .brand__num a
     containing block to hang off the top-right of. The 18/16 insets come from
     padding, and justify-content:flex-end (set above) does the bottom
     anchoring. gap goes to 0 so the margins below are the only spacing. */
  .brand__body {
    gap: 0;
    padding: 0 18px 16px;
    /* tight halo + wide bloom: the 9.5px red category label is the one element
       small enough that the scrim alone does not carry it over a busy crop */
    text-shadow: 0 1px 3px rgba(0, 0, 0, .6), 0 2px 16px rgba(0, 0, 0, .5);
  }

  .brand__num {
    top: 14px; right: 14px;
    font-size: 24px;
    -webkit-text-stroke: 1px rgba(255, 255, 255, .32);
    /* drop-shadow rather than text-shadow: the glyph is a stroke around a
       transparent fill, and only a filter picks that stroke up — which is what
       keeps the numeral legible where the image runs bright behind it */
    filter: drop-shadow(0 1px 6px rgba(0, 0, 0, .55));
  }

  .brand__industry {
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 1.2px;
    line-height: 1.3;
    margin-bottom: 8px;
    /* The accent red tops out at 4.05:1 against pure black, so this label is
       the one run the scrim cannot carry on its own. A tight opaque halo round
       the glyph edges is what holds it together at 9.5px over a busy crop —
       measurably more effective here than darkening the image further. */
    text-shadow: 0 1px 2px rgba(0, 0, 0, .9), 0 0 10px rgba(0, 0, 0, .8);
  }
  .brand__name {
    font-size: clamp(23px, 7vw, 28px);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -.01em;
    overflow-wrap: break-word;
  }
  .brand__project {
    font-size: 14px;
    line-height: 1.35;
    opacity: .75;
    margin-top: 6px;
    margin-bottom: 8px;
  }
  /* Two lines, always — an unclamped description is the one thing that can
     push the copy block past the card it is anchored inside. */
  .brand__desc {
    font-size: 12.5px;
    line-height: 1.45;
    max-width: 290px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
            line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* One metadata line. The literal "Role:" in the markup is a bare text node,
     so it cannot be selected — collapsing the line's font-size and letter-
     spacing to zero and restoring both on the <em> is what drops it visually
     while leaving it in the accessibility tree and untouched for desktop. */
  .brand__role {
    margin-top: 12px;
    font-size: 8.5px;
    letter-spacing: .6px;
    line-height: 1.4;
    opacity: .55;
    color: var(--text-white);
  }
  .brand__role-line { font-size: 0; letter-spacing: 0; }
  .brand__role-line em {
    font-size: 8.5px;
    letter-spacing: .6px;
    color: inherit;
  }

  .brand__cta {
    margin-top: 13px;
    min-height: 42px;          /* the card is the hit target; this is the
                                  visual affordance, and it also keeps the
                                  label clear of the bottom edge */
    align-items: center;
    gap: 10px;
    font-size: 9.5px;
    letter-spacing: 1px;
    transition: color .3s var(--ease);
  }
  .brand__cta .arrow { width: 26px; flex: none; }
  /* driven off the link, never off a second anchor inside the card */
  .brand__link:hover .brand__cta,
  .brand__link:focus-visible .brand__cta { color: var(--primary-red); }
  .brand__link:focus-visible .brand__cta .arrow { transform: translateX(6px); }

  /* Entrance. Reuses the sitewide [data-reveal] observer, which unobserves on
     first intersection, so this settles once and never replays. Scoped to .js
     so a script failure leaves the cards in their resting state. */
  .js .brand[data-reveal] .brand__media img {
    transform: scale(1.035);
  }
  .js .brand[data-reveal].is-in .brand__media img {
    transform: scale(1);
  }
  .js .brand[data-reveal] .brand__body {
    transform: translateY(10px);
    transition: transform .6s var(--ease-out);
  }
  .js .brand[data-reveal].is-in .brand__body {
    transform: translateY(0);
  }
}

/* The sitewide reduced-motion block resets [data-reveal] itself but not these
   descendants, which would otherwise start offset and merely snap into place. */
@media (max-width: 767px) and (prefers-reduced-motion: reduce) {
  .js .brand[data-reveal] .brand__media img,
  .js .brand[data-reveal] .brand__body { transform: none; }
}

@media (max-width: 400px) {
  .archive { grid-template-columns: minmax(0, 1fr); }
  .cv__caps li, .purpose__scope dd { padding: .45em .8em; }
  .filters__btn { padding: .55em .9em; }
}

/* --------------------------------------------------------- 10 reduced motion */
@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;
  }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* print — not a priority, but keep it from producing 40 black pages */
@media print {
  .grain, .hero__glow, .stage { display: none; }
  body { background: #fff; color: #000; }
}

/* ============================================================================
   11  Projects page (projects.html)
   Self-contained block kept separate from the numbered homepage sections
   above — everything it needs (tokens, ghost panel, .filters, .link-arrow,
   .cv__caps chip, ::selection etc.) is already shared from earlier in this
   file; only the page-specific card types are defined here.
   ========================================================================= */

/* ---- page intro ---- */
.projhero {
  position: relative;
  padding-block: clamp(2.6rem, 7vw, 5.5rem) clamp(2rem, 5vw, 3.5rem);
  border-bottom: 1px solid var(--border-soft);
  background:
    radial-gradient(70% 55% at 85% 0%, rgba(61, 20, 112, .32), transparent 62%),
    var(--background);
}
.projhero__eyebrow {
  display: flex; align-items: center; gap: .7em;
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.62rem, .3vw + .54rem, .76rem);
  letter-spacing: .24em; text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: clamp(.9rem, 2vw, 1.4rem);
}
.projhero__eyebrow .star-badge { width: 1.7rem; }
.projhero__title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1.9rem, 4vw + .3rem, 4.1rem);
  line-height: 1.05; letter-spacing: -.012em;
  color: var(--text-white);
  max-width: 22ch;
}
.projhero__title .accent { color: var(--primary-red); }
.projhero__lede {
  margin-top: clamp(1rem, 2.4vw, 1.6rem);
  max-width: 58ch;
  color: var(--text-grey);
  font-size: clamp(.86rem, .34vw + .76rem, 1rem);
  line-height: 1.68;
}

/* ---- filter row + panels ---- */
.sec--projects { padding-top: clamp(2.2rem, 5vw, 3.6rem); }
.proj-section-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: clamp(1rem, 3vw, 2rem); flex-wrap: wrap;
  margin-bottom: clamp(1.4rem, 3vw, 2.2rem);
}
.proj-panel-title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1.25rem, 1.7vw + .4rem, 2rem);
  line-height: 1.1; color: var(--text-white);
}
.proj-panel-sub {
  margin-top: .45rem;
  max-width: 46ch;
  font-size: .82rem; line-height: 1.55;
  color: var(--text-grey);
}
.proj-count {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.62rem, .26vw + .56rem, .74rem);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-dim);
  flex: none;
}
/* Spacing between panels is a flex gap, not a sibling margin: a sibling
   combinator (.proj-panel + .proj-panel) still matches a [hidden] panel, so
   filtering down to one category left it carrying margin meant for the panel
   stacked below a VISIBLE predecessor — a big gap under "Videos" but none
   under "Websites", since Websites has no preceding sibling at all. gap only
   ever applies between panels that are actually rendered. */
[data-proj-panels] { display: flex; flex-direction: column; gap: clamp(3.2rem, 7vw, 5.5rem); }

/* ---- top work — one brand per full-width row ----
   Deliberately the widest card type on the page: these seven are the lead
   credentials, so each gets its own row rather than competing in a grid. The
   <ol> carries the running order semantically; the visible numbering is the
   printed .proj-top-num in each corner, the way the homepage brand cards do
   it (the sitewide reset already strips the list markers). */
.proj-top-list { display: grid; gap: clamp(.9rem, 1.8vw, 1.4rem); }
.proj-top-card {
  border: 1px solid var(--border-soft);
  background: var(--surface);
  transition: border-color .4s var(--ease), transform .45s var(--ease-out), box-shadow .45s var(--ease);
}
.proj-top-card:hover,
.proj-top-card:focus-within {
  border-color: rgba(177, 98, 254, .5);
  box-shadow: 0 22px 55px -26px rgba(177, 98, 254, .55);
  transform: translateY(-3px);
}
.proj-top-link {
  display: grid;
  grid-template-columns: minmax(0, .48fr) minmax(0, 1fr);
  align-items: stretch;
}
.proj-top-media { position: relative; overflow: hidden; aspect-ratio: 16 / 10; }
.proj-top-media img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .7s var(--ease-out);
}
.proj-top-card:hover .proj-top-media img { transform: scale(1.05); }
.proj-top-ghost { font-size: clamp(1.6rem, 4vw, 3rem); }
.proj-top-body {
  position: relative;
  display: flex; flex-direction: column; align-items: flex-start;
  gap: .35rem;
  padding: clamp(1.1rem, 2.4vw, 2rem);
  /* room for the numeral, so a long title never runs under it */
  padding-right: clamp(3.4rem, 6vw, 5.5rem);
}
.proj-top-num {
  position: absolute;
  top: clamp(.9rem, 1.8vw, 1.5rem); right: clamp(.9rem, 1.8vw, 1.5rem);
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.4vw, 2.6rem); line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, .42);
  transition: color .45s var(--ease), -webkit-text-stroke-color .45s var(--ease);
}
.proj-top-card:hover .proj-top-num,
.proj-top-card:focus-within .proj-top-num {
  color: var(--primary-red);
  -webkit-text-stroke-color: transparent;
}
.proj-top-industry {
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.58rem, .26vw + .52rem, .7rem);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--primary-red);
}
.proj-top-title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1.15rem, 1.4vw + .5rem, 1.9rem);
  line-height: 1.06; letter-spacing: -.008em;
  color: var(--text-white);
}
.proj-top-project { font-size: clamp(.76rem, .3vw + .68rem, .9rem); color: var(--text-grey); }
.proj-top-desc {
  font-size: clamp(.76rem, .3vw + .68rem, .88rem);
  line-height: 1.6; color: var(--text-grey);
  max-width: 52ch;
}
.proj-top-credit {
  font-family: var(--font-cond); font-weight: 300;
  font-size: clamp(.6rem, .24vw + .55rem, .72rem);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-dim);
}
.proj-top-cta { margin-top: auto; padding-top: .7rem; }
.proj-top-card:hover .proj-top-cta { color: var(--text-white); }
.proj-top-card:hover .proj-top-cta .arrow { transform: translateX(5px); }

/* ---- websites ---- */
.proj-web-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.6rem);
}
.proj-web-card {
  border: 1px solid var(--border-soft);
  background: var(--surface);
  transition: border-color .4s var(--ease), transform .45s var(--ease-out), box-shadow .45s var(--ease);
}
.proj-web-card:hover {
  border-color: rgba(177, 98, 254, .5);
  box-shadow: 0 22px 55px -26px rgba(177, 98, 254, .55);
  transform: translateY(-3px);
}
.proj-web-chrome {
  display: flex; align-items: center; gap: .5rem;
  padding: .7rem 1rem;
  border-bottom: 1px solid var(--border-soft);
}
.proj-web-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-dim); opacity: .6; }
.proj-web-url {
  margin-left: .4rem;
  font-family: var(--font-cond); font-weight: 300;
  font-size: .68rem; letter-spacing: .04em;
  color: var(--text-dim);
  border: 1px solid var(--border-soft);
  padding: .2em .6em;
}
.proj-web-shot {
  --shot-h: clamp(210px, 26vw, 320px);
  position: relative; height: var(--shot-h); overflow: hidden;
}
/* These are full-length page captures, so the shot is laid in at its natural
   height instead of being cropped to the frame. Hovering then walks it from
   top to bottom like a real page scroll and releasing eases it back up.
   min() stops a capture SHORTER than the frame from sliding down into empty
   space — such a shot simply stays put. */
/* --shot-scroll / --shot-return are written per card by script.js from the
   shot's measured travel, so every capture walks at the same pixels-per-second
   however long the page it came from is. The literals here are the fallback
   for a card whose image never resolved, or with JS off. */
.proj-web-shot img {
  position: absolute; top: 0; left: 0; z-index: 1;
  width: 100%; height: auto;
  transition: transform var(--shot-return, 1.9s) var(--ease-out);
}
@media (hover: hover) {
  .proj-web-card:hover .proj-web-shot img {
    transform: translateY(min(0px, calc(var(--shot-h) - 100%)));
    transition-duration: var(--shot-scroll, 14s);
    /* linear, NOT --ease-out. That curve is an expo-out — the right shape for
       a 350ms nudge, but over a 20s walk it spends most of the distance in the
       first second and then crawls: measured at 787px in second one before
       easing to a stop. A page being scrolled moves at a constant rate, and
       constant is also what makes the per-card duration above mean anything. */
    transition-timing-function: linear;
  }
}
/* The sitewide reduced-motion rule only collapses the duration, which would
   turn the scroll into an instant jump — hold the shot still instead. */
@media (prefers-reduced-motion: reduce) {
  .proj-web-card:hover .proj-web-shot img { transform: none; }
}
.proj-web-ghost { font-size: clamp(1.6rem, 4vw, 3rem); }
.proj-web-body {
  padding: clamp(1.1rem, 2.2vw, 1.5rem);
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 1rem;
}
.proj-web-industry {
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.58rem, .26vw + .52rem, .68rem);
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: .4rem;
}
.proj-web-title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(1rem, .9vw + .6rem, 1.3rem);
  color: var(--text-white);
  margin-bottom: .45rem;
}
.proj-web-desc {
  font-size: clamp(.76rem, .3vw + .68rem, .86rem);
  line-height: 1.6; color: var(--text-grey);
  max-width: 40ch;
  margin-bottom: .7rem;
}
.proj-tag-row { margin-top: 0; margin-bottom: .8rem; }
.proj-web-live { flex: none; margin-top: .2rem; }
.proj-web-live .arrow { width: 1.7rem; }

/* ---- videos ---- */
.proj-video-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(.9rem, 1.8vw, 1.4rem);
}
.proj-video-card {
  position: relative;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  cursor: pointer;
  transition: border-color .4s var(--ease), transform .45s var(--ease-out), box-shadow .45s var(--ease);
}
.proj-video-card:hover {
  border-color: rgba(177, 98, 254, .5);
  box-shadow: 0 20px 50px -22px rgba(177, 98, 254, .55);
  transform: translateY(-3px);
}
.proj-video-thumb { position: relative; aspect-ratio: 16 / 9; overflow: hidden; }
.proj-video-thumb img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .6s var(--ease-out);
}
.proj-video-card:hover .proj-video-thumb img { transform: scale(1.06); }
.proj-video-ghost { font-size: clamp(1.4rem, 3.4vw, 2.6rem); }
.proj-video-thumb::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  background: linear-gradient(0deg, rgba(7,7,7,.85) 0%, rgba(7,7,7,.15) 55%, rgba(7,7,7,.05) 100%);
}
.proj-play-btn {
  position: absolute; z-index: 3; inset: 0;
  display: grid; place-items: center;
}
.proj-play-btn span {
  width: 3rem; height: 3rem; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .35);
  color: #fff;
  transition: transform .4s var(--ease-out), background-color .3s var(--ease), border-color .3s var(--ease);
}
.proj-video-card:hover .proj-play-btn span {
  transform: scale(1.12);
  background: var(--primary-red);
  border-color: transparent;
}
.proj-video-body { padding: clamp(.9rem, 1.8vw, 1.3rem); }
.proj-video-title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.92rem, .5vw + .7rem, 1.05rem);
  color: var(--text-white);
  margin-bottom: .3rem;
}
.proj-video-desc { font-size: .8rem; line-height: 1.5; color: var(--text-grey); }

/* ---- brand identity ---- */
.proj-brand-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(.9rem, 1.8vw, 1.4rem);
}
.proj-brand-card {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  cursor: pointer;
  transition: border-color .4s var(--ease), transform .45s var(--ease-out), box-shadow .45s var(--ease);
}
.proj-brand-card:hover {
  border-color: rgba(177, 98, 254, .5);
  box-shadow: 0 20px 50px -22px rgba(177, 98, 254, .55);
  transform: translateY(-3px);
}
.proj-brand-ghost { font-size: clamp(1.3rem, 3vw, 2.2rem); }
.proj-brand-main {
  position: relative; z-index: 1;
  width: 100%; height: 100%; object-fit: contain;
  padding: 14%;
  transition: transform .5s var(--ease-out);
}
.proj-brand-card:hover .proj-brand-main { transform: scale(1.05); }
.proj-brand-overlay {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: clamp(.9rem, 1.8vw, 1.3rem);
  background: linear-gradient(0deg, rgba(7,7,7,.94) 0%, rgba(7,7,7,.62) 46%, rgba(7,7,7,.06) 100%);
}
.proj-brand-industry {
  font-family: var(--font-cond); font-weight: 400;
  font-size: clamp(.56rem, .24vw + .5rem, .66rem);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: .3rem;
}
.proj-brand-title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: clamp(.92rem, .5vw + .7rem, 1.1rem);
  color: var(--text-white);
  margin-bottom: .55rem;
}
.proj-brand-swatches { display: flex; gap: .35rem; margin-bottom: .6rem; }
.proj-brand-swatches span {
  width: 14px; height: 14px; border-radius: 3px;
  border: 1px solid rgba(255, 255, 255, .25);
}
.proj-brand-view {
  font-family: var(--font-cond); font-weight: 400;
  font-size: .66rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-white);
  opacity: 0; transform: translateY(6px);
  transition: opacity .35s var(--ease), transform .35s var(--ease-out);
}
.proj-brand-card:hover .proj-brand-view,
.proj-brand-card:focus-visible .proj-brand-view { opacity: 1; transform: none; }

/* ------------------------------------------------------------------------
   The wall — shared Pinterest-style masonry
   ------------------------------------------------------------------------
   Used by BOTH the Videos and Marketing panels, so a reel, a Meta ad and a
   16:9 film all sit in the same layout primitive and only differ by height.

   column-count, not grid row-spans, on purpose. The grid approach needs a JS
   sizer and, more importantly, it is the one that leaves holes: a card two
   columns wide can strand a gap the packer cannot fill. Here EVERY card is
   exactly one column wide and varies only in height, so the ragged bottom is
   the only unevenness and there is nothing to strand.

   Nothing sets an explicit height. Each <img> carries its true width/height
   attributes from the manifest, so the browser reserves the right box before
   the bytes land and no card ever re-flows into a different column mid-load —
   which is what makes a lazy-loaded masonry jump around otherwise. */
.proj-wall {
  column-count: 4;
  column-gap: clamp(.6rem, 1.2vw, 1rem);
}
.proj-wall-card {
  position: relative;
  display: block;
  width: 100%;
  break-inside: avoid;
  margin-bottom: clamp(.6rem, 1.2vw, 1rem);
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  cursor: pointer;
  transition: border-color .4s var(--ease), box-shadow .45s var(--ease);
}
.proj-wall-card:hover,
.proj-wall-card:focus-within {
  border-color: rgba(177, 98, 254, .5);
  box-shadow: 0 20px 50px -22px rgba(177, 98, 254, .55);
}
/* The whole tile is the button, so the hit area matches what the eye reads
   as one card and keyboard focus lands in exactly one place per piece. */
.proj-wall-media {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  color: inherit;
  text-align: left;
}
.proj-wall-media img {
  width: 100%;
  height: auto;
  display: block;
  transition: transform .6s var(--ease-out);
}
.proj-wall-card:hover .proj-wall-media img { transform: scale(1.04); }

/* The hover clip. Injected by script.js only once a card is hovered, so the
   wall costs 40 posters at rest rather than 40 video elements. */
.proj-wall-preview {
  position: absolute; inset: 0; z-index: 2;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .35s var(--ease);
  pointer-events: none;
}
.proj-wall-preview.is-playing { opacity: 1; }

.proj-wall-overlay {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: clamp(.7rem, 1.4vw, 1.05rem);
  background: linear-gradient(0deg, rgba(7,7,7,.92) 0%, rgba(7,7,7,.12) 62%, transparent 100%);
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.proj-wall-card:hover .proj-wall-overlay,
.proj-wall-media:focus-visible .proj-wall-overlay { opacity: 1; }
.proj-wall-brand {
  font-family: var(--font-cond); font-weight: 400;
  font-size: .58rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: .28rem;
}
.proj-wall-title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: .86rem; line-height: 1.3;
  color: var(--text-white);
}

/* Corner furniture. These stay visible at rest — the format tag is how the
   wall stays readable as a mix, and a play badge that only appears on hover
   is invisible on touch, where there is no hover at all. */
/* Bottom-left, not top-left: ad creative puts the client's logo and headline
   along the top edge, and a badge pinned up there lands straight on the
   wordmark (it was cutting "Bath & Body Works" in half on every 1:1 Meta ad).
   The bottom strip is where the hover gradient already darkens, so the tag
   also reads better there. */
.proj-wall-tag {
  position: absolute; bottom: .5rem; left: .5rem; z-index: 4;
  padding: .2rem .45rem;
  font-family: var(--font-cond); font-weight: 400;
  font-size: .56rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-white);
  background: rgba(7, 7, 7, .66);
  border: 1px solid rgba(255, 255, 255, .18);
  backdrop-filter: blur(6px);
}
.proj-wall-time {
  position: absolute; right: .5rem; bottom: .5rem; z-index: 4;
  padding: .15rem .38rem;
  font-family: var(--font-cond);
  font-size: .58rem; letter-spacing: .06em;
  color: var(--text-white);
  background: rgba(7, 7, 7, .72);
}
/* Both badges occupy the same bottom strip the overlay fills on hover, so they
   hand over to it rather than sitting on top of the title. They are the
   at-rest scanning aid; the overlay is the detail view. */
.proj-wall-tag,
.proj-wall-time { transition: opacity .3s var(--ease); }
.proj-wall-card:hover .proj-wall-tag,
.proj-wall-card:hover .proj-wall-time,
.proj-wall-media:focus-visible .proj-wall-tag,
.proj-wall-media:focus-visible .proj-wall-time { opacity: 0; }
.proj-wall-play {
  position: absolute; inset: 0; z-index: 3;
  display: grid; place-items: center;
  pointer-events: none;
}
.proj-wall-play span {
  width: 2.5rem; height: 2.5rem; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .35);
  color: #fff;
  transition: transform .4s var(--ease-out), background-color .3s var(--ease),
              border-color .3s var(--ease), opacity .3s var(--ease);
}
.proj-wall-card:hover .proj-wall-play span {
  transform: scale(1.12);
  background: var(--primary-red);
  border-color: transparent;
}
/* Once the clip is actually rolling the badge is just covering it. */
.proj-wall-card.is-previewing .proj-wall-play span { opacity: 0; }
.proj-wall-play svg { width: 14px; height: 14px; fill: currentColor; }

/* Carousels read as a stack: two offset plates behind the top slide, plus a
   slide count. Drawn with borders rather than real slides so a 7-slide set
   still costs one image on the wall. */
.proj-wall-card--stack { overflow: visible; }
.proj-wall-card--stack::before,
.proj-wall-card--stack::after {
  content: "";
  position: absolute; z-index: -1;
  left: 0; right: 0; top: 0; bottom: 0;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  transition: transform .45s var(--ease-out);
}
.proj-wall-card--stack::before { transform: translate(5px, 5px); }
.proj-wall-card--stack::after  { transform: translate(10px, 10px); opacity: .6; }
.proj-wall-card--stack:hover::before { transform: translate(7px, 7px); }
.proj-wall-card--stack:hover::after  { transform: translate(14px, 14px); }

/* Emailers and landing pages are up to 15471px tall. Shown whole they would
   be a hairline; this caps the card and fades the cut so it reads as a
   deliberate crop of a long page, not a broken image. The full artwork is
   what opens. */
.proj-wall-media--long { max-height: 520px; overflow: hidden; }
.proj-wall-media--long::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; height: 45%;
  background: linear-gradient(0deg, var(--surface) 6%, transparent 100%);
  pointer-events: none;
}

/* ---- marketing (legacy masonry — retained for any page still using it) ---- */
.proj-marketing-masonry { column-count: 3; column-gap: clamp(.9rem, 1.8vw, 1.4rem); }
.proj-marketing-card {
  position: relative;
  display: block;
  break-inside: avoid;
  margin-bottom: clamp(.9rem, 1.8vw, 1.4rem);
  overflow: hidden;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  cursor: pointer;
  transition: border-color .4s var(--ease), box-shadow .45s var(--ease);
}
.proj-marketing-card:hover {
  border-color: rgba(177, 98, 254, .5);
  box-shadow: 0 20px 50px -22px rgba(177, 98, 254, .55);
}
.proj-marketing-card img {
  width: 100%; display: block;
  transition: transform .6s var(--ease-out);
}
.proj-marketing-card:hover img { transform: scale(1.04); }
.proj-marketing-overlay {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: clamp(.8rem, 1.6vw, 1.2rem);
  background: linear-gradient(0deg, rgba(7,7,7,.9) 0%, rgba(7,7,7,.1) 60%, transparent 100%);
  opacity: 0; transition: opacity .35s var(--ease);
}
.proj-marketing-card:hover .proj-marketing-overlay,
.proj-marketing-card:focus-visible .proj-marketing-overlay { opacity: 1; }
.proj-marketing-cat {
  font-family: var(--font-cond); font-weight: 400;
  font-size: .6rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--primary-red);
  margin-bottom: .3rem;
}
.proj-marketing-title {
  font-family: var(--font-cond); font-weight: 500;
  font-size: .88rem; color: var(--text-white);
}

/* ---- lightbox (video embeds + enlarged images) ---- */
/* The single track is pinned to the container's content box — `minmax(0, 1fr)`
   rather than the implicit `auto`. An auto row sizes to the item's max-content
   height, and during track sizing the item's own `height: min(100%, 90vh)` has
   no percentage basis yet, so its contribution falls back to the media's
   NATURAL pixel height: 1200px for a square marketing still, 1920px for a
   reel. The row grew to that, and `place-items: center` then centred an 810px
   box inside a 1200px row — the media rendered ~195px below the middle of the
   screen with its bottom half, its counter and its nav arrows off the
   viewport. A 1fr row is the content box and nothing else, so both `100%` and
   the centring finally mean what they read as. Same failure the case-study
   viewers hit — see .bbw-lb__stage in casestudy-disneyxbbw.css. */
.proj-lightbox {
  position: fixed; inset: 0; z-index: 300;
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  --lb-pad: clamp(1rem, 4vw, 3rem);
  /* Height the media is actually allowed, as an absolute length — see the
     is-image/is-media block for why a percentage cannot do this job. The
     trailing 2px is the box's hairline border, top and bottom: the cap lands
     on the media's content box, so without it the framed box comes out that
     much taller than the stage and bleeds past the padding. */
  --lb-stage-h: calc(100vh - 3.4rem - var(--lb-pad) - 2px);
  /* Asymmetric on purpose: the close button hangs OUTSIDE the inner box
     (top: -2.6rem) so the stage has to reserve its strip up top. Before the
     row was pinned the media never filled the stage, so the shortfall was
     hidden; now that it does, 1rem of padding on a phone would put the button
     off the top edge. 3.4rem clears the 2.6rem offset plus the button. */
  padding: 3.4rem var(--lb-pad) var(--lb-pad);
  background: rgba(4, 4, 5, .92);
  opacity: 0; visibility: hidden;
  transition: opacity .3s var(--ease), visibility .3s linear;
}
/* Phone chrome: the stage has to shrink with the collapsing URL bar or the
   media is measured against a viewport that is not on screen yet. */
@supports (height: 1dvh) {
  .proj-lightbox { --lb-stage-h: calc(100dvh - 3.4rem - var(--lb-pad) - 2px); }
}
.proj-lightbox.is-open { opacity: 1; visibility: visible; }
.proj-lightbox-inner {
  position: relative;
  /* The embed box is width-driven, so a short viewport used to run it off the
     bottom. Capping the WIDTH by what the stage height affords — rather than
     capping the height and letting the ratio break — keeps it a true 16:9 on
     a laptop in landscape, which is the shape YouTube expects. */
  width: min(100%, 1040px, calc(var(--lb-stage-h) * 16 / 9));
  /* A grid item's automatic minimum size is its content's min-content size,
     which for a height-driven replaced element is its ratio-derived width.
     Left at auto it would push a 16:9 reel past the edge of a narrow stage
     instead of letting max-width bring it back in. */
  min-width: 0; min-height: 0;
  aspect-ratio: 16 / 9;
  background: #000;
  border: 1px solid var(--border-red);
  box-shadow: 0 40px 100px -30px rgba(0, 0, 0, .8);
}
.proj-lightbox-inner iframe {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
}
/* Image and wall-media states — a 9:16 reel, a 1:1 ad and a 2880x3980 landing
   page each size to their own ratio inside the stage rather than to the
   iframe's fixed 16:9 box.

   Sizing is handed to the MEDIA and the box shrink-wraps it, so the border,
   the nav arrows and the counter all hug the picture instead of framing empty
   space. That means the box's own height is `auto`, so the cap on the media
   has to be an absolute length: a percentage max-height resolves against
   nothing here and is dropped, which is the trap that let a still run off the
   bottom in the first place. Width stays a percentage — that one does resolve
   against the shrink-to-fit width, and unlike 100vw it is not thrown off by
   the scrollbar. With both axes capped and no object-fit in play, the
   replaced-element rules scale the picture on its own ratio, so there are
   never letterbox bands to see. */
.proj-lightbox-inner.is-image,
.proj-lightbox-inner.is-media {
  width: auto; height: auto; aspect-ratio: auto;
  max-width: 100%;
  background: none; border-color: var(--border-soft);
}
.proj-lightbox-inner.is-image img,
.proj-lightbox-inner.is-media video,
.proj-lightbox-inner.is-media img {
  display: block;
  width: auto; height: auto;
  max-width: 100%;
  max-height: var(--lb-stage-h);
}
/* Only while a frame decodes — with the box hugging the media there is no
   band left for it to show through afterwards. */
.proj-lightbox-inner.is-media video,
.proj-lightbox-inner.is-media img { background: #000; }
/* Shown in place of the player when the page is running off disk and the
   popup to youtube.com was blocked as well. */
.proj-lightbox-note {
  position: absolute; inset: 0;
  display: grid; align-content: center; justify-items: center;
  gap: .8rem; padding: 2rem; text-align: center;
  font-family: var(--font-cond); font-weight: 300;
  font-size: .95rem; line-height: 1.6;
  color: var(--text-grey);
}
.proj-lightbox-note a {
  color: var(--text-white);
  border-bottom: 1px solid var(--primary-red);
  padding-bottom: .1rem;
  transition: color .3s var(--ease);
}
.proj-lightbox-note a:hover { color: var(--primary-red); }

.proj-lightbox-nav {
  position: absolute; top: 50%; z-index: 5;
  transform: translateY(-50%);
  width: 2.4rem; height: 2.4rem;
  display: grid; place-items: center;
  border: 1px solid var(--border-soft);
  background: rgba(7, 7, 7, .72);
  color: var(--text-white);
  cursor: pointer;
  transition: border-color .3s var(--ease), color .3s var(--ease), background-color .3s var(--ease);
}
.proj-lightbox-nav:hover { border-color: var(--primary-red); color: var(--primary-red); }
.proj-lightbox-nav--prev { left: .6rem; }
.proj-lightbox-nav--next { right: .6rem; }
.proj-lightbox-count {
  position: absolute; left: 50%; bottom: .7rem; z-index: 5;
  transform: translateX(-50%);
  padding: .2rem .55rem;
  font-family: var(--font-cond);
  font-size: .68rem; letter-spacing: .1em;
  color: var(--text-white);
  background: rgba(7, 7, 7, .76);
}

.proj-lightbox-close {
  position: absolute; top: -2.6rem; right: 0; z-index: 4;
  width: 2.2rem; height: 2.2rem;
  display: grid; place-items: center;
  border: 1px solid var(--border-soft);
  background: var(--surface);
  color: var(--text-white);
  cursor: pointer;
  transition: border-color .3s var(--ease), color .3s var(--ease);
}
.proj-lightbox-close:hover { border-color: var(--primary-red); color: var(--primary-red); }

@media (max-width: 1200px) {
  .proj-wall { column-count: 3; }
}

@media (max-width: 1024px) {
  .proj-video-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .proj-brand-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .proj-marketing-masonry { column-count: 2; }
  /* --shell collapses to 900px at exactly this width (see :root above), so the
     wall is laid out against 900px here, not 1024px — three columns is ~290px
     each, which still carries a 9:16 reel. Do not raise this to four. */
  .proj-wall { column-count: 3; }
}

@media (max-width: 860px) {
  .proj-web-grid { grid-template-columns: minmax(0, 1fr); }
  /* Below this the media column is too narrow to read as an image, so the row
     stacks: cover on top, copy beneath. */
  .proj-top-link { grid-template-columns: minmax(0, 1fr); }
  .proj-top-media { aspect-ratio: 16 / 9; }
  .proj-top-cta { margin-top: .3rem; }
}

/* ---- Top Work cards on phones ---------------------------------------------
   Same seven projects as section 01 of the homepage, so they get the same
   phone treatment: one poster proportion, the copy overlaid on a scrim
   rather than stacked beneath a letterbox. The 16/9 media band this replaces
   threw away a third of every cover — on Victoria's Secret it cropped the
   faces out — and the split card was the only thing forcing that ratio.
   Kept in step with the .brand block at the 767px breakpoint above; if one
   changes, change both.                                                     */
@media (max-width: 767px) {
  .proj-top-card { overflow: hidden; }

  .proj-top-link {
    position: relative;
    display: block;
    grid-template-columns: none;
    height: clamp(400px, 118vw, 440px);
  }

  /* the media stops being a row of the grid and becomes the card itself */
  .proj-top-media { position: absolute; inset: 0; aspect-ratio: auto; }
  .proj-top-ghost { font-size: clamp(2.4rem, 7vw, 6rem); place-items: start center; padding-top: 6%; }

  /* Per-card framing, matching the homepage: the two covers whose subject sits
     high are pinned to the top, and Warner Bros is pushed off-centre to keep
     its figures clear of the copy. No image files change. */
  .proj-top-card--vs   .proj-top-media img,
  .proj-top-card--bbw  .proj-top-media img { object-position: center top; }
  .proj-top-card--wb   .proj-top-media img { object-position: 62% center; }
  .proj-top-card--sie  .proj-top-media img,
  .proj-top-card--aiff .proj-top-media img,
  .proj-top-card--del  .proj-top-media img,
  .proj-top-card--lt   .proj-top-media img { object-position: center center; }

  /* Directional scrim: the top of the crop stays untouched and the bottom
     lands on the same near-black on every card, which is what makes the copy
     readable independently of the photograph. */
  .proj-top-media::after {
    content: ""; position: absolute; inset: 0; z-index: 2;
    background: linear-gradient(
      to bottom,
      rgba(0, 0, 0, .02) 10%,
      rgba(0, 0, 0, .14) 26%,
      rgba(0, 0, 0, .62) 40%,
      rgba(0, 0, 0, .92) 64%,
      rgba(0, 0, 0, .98) 100%
    );
  }

  /* The body spans the whole card — that is what gives .proj-top-num a
     containing block to hang off the top-right of — and anchors its content
     to the bottom. */
  .proj-top-body {
    position: absolute; inset: 0; z-index: 3;
    justify-content: flex-end;
    gap: 0;
    padding: 0 18px 16px;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .6), 0 2px 16px rgba(0, 0, 0, .5);
  }

  .proj-top-num {
    top: 14px; right: 14px;
    font-size: 24px;
    -webkit-text-stroke: 1px rgba(255, 255, 255, .32);
    /* the glyph is a stroke round a transparent fill, so only a filter picks
       it up — text-shadow would do nothing here */
    filter: drop-shadow(0 1px 6px rgba(0, 0, 0, .55));
  }

  .proj-top-industry {
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 1.2px;
    line-height: 1.3;
    margin-bottom: 8px;
    /* the accent red tops out at 4.05:1 on black; a tight opaque halo is what
       holds this label together at 9.5px over a busy crop */
    text-shadow: 0 1px 2px rgba(0, 0, 0, .9), 0 0 10px rgba(0, 0, 0, .8);
  }
  .proj-top-title {
    font-size: clamp(23px, 7vw, 28px);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -.01em;
    overflow-wrap: break-word;
  }
  .proj-top-project {
    font-size: 14px;
    line-height: 1.35;
    opacity: .75;
    margin-top: 6px;
    margin-bottom: 8px;
  }
  /* Two lines, always — an unclamped description is the one thing that can
     push the copy block past the card it is anchored inside. AIFF has the
     longest title AND the longest description of the seven. */
  .proj-top-desc {
    font-size: 12.5px;
    line-height: 1.45;
    max-width: 290px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
            line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .proj-top-cta {
    margin-top: 13px;
    min-height: 42px;          /* the card is the hit target; this is the
                                  visual affordance, and it keeps the label
                                  clear of the bottom edge */
    align-items: center;
    gap: 10px;
    font-size: 9.5px;
    letter-spacing: 1px;
  }
  .proj-top-cta .arrow { width: 26px; flex: none; }
}

@media (max-width: 700px) {
  /* Two columns, not one. A wall at one column stops being a wall — and at
     ~170px a card is still legible because every tile is artwork, not text. */
  .proj-wall { column-count: 2; }
  .proj-wall-title { font-size: .78rem; }
  .proj-wall-tag { font-size: .5rem; }
  /* The offset plates eat real width at this size; keep the hint, halve it. */
  .proj-wall-card--stack::before { transform: translate(3px, 3px); }
  .proj-wall-card--stack::after { transform: translate(6px, 6px); }
  .proj-lightbox-nav { width: 2rem; height: 2rem; }
}

@media (max-width: 640px) {
  .projhero__title { font-size: clamp(2rem, 9vw, 3rem); }
  .proj-video-grid { grid-template-columns: minmax(0, 1fr); }
  .proj-brand-grid { grid-template-columns: minmax(0, 1fr); }
  .proj-marketing-masonry { column-count: 1; }
  .proj-web-body { flex-direction: column; }
  /* The phone caps that used to live here (80vh / 78vh on the box) are gone:
     the box no longer carries a height at all, and re-introducing one is what
     hands the media a committed height that max-width can only letterbox
     against. --lb-stage-h already insets the stage on every width. */
  /* "All Work" drops off the mobile filter row — only the named categories
     remain. The underlying filter still defaults to "all" (every project
     shown), this only hides that one pill. */
  [data-proj-filter="all"] { display: none; }

  /* The five that remain go edge to edge on a single row: an even 5-column
     grid rather than the wrapping flex row, which sized each pill to its own
     label and pushed "Marketing" onto a second line. A display:none pill is
     never placed, so the grid lands exactly five across.
     Scoped to the projects filter row — the homepage archive filters share
     .filters and keep their natural widths. */
  [data-proj-filters] {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: .3rem;
  }
  [data-proj-filters] .filters__btn {
    padding: .5em .25em;
    font-size: .58rem;
    letter-spacing: .05em;
    line-height: 1.3;
    text-align: center;
    /* "Brand Identity" is the one label too long for a fifth of a phone
       screen; it wraps inside its own cell, and grid's default stretch keeps
       all five buttons the same height regardless. */
  }
}
