/* ==========================================================================
   Jason Valdovinos — portfolio
   Two surfaces: dark (work index, contact) and light (case studies, about).
   Set via <html data-surface="dark|light">.
   ========================================================================== */

/* --- reset -------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img, video { display: block; max-width: 100%; }
button { font: inherit; }

/* --- tokens ------------------------------------------------------------- */
:root {
  --ink:        #0A0A0A;
  --paper:      #FAFAF8;
  --muted-dark: #8C8C8C;
  --muted-light:#6E6E6B;
  --rule-dark:  rgba(255,255,255,.16);
  --rule-light: rgba(10,10,10,.16);
  /* Page-level rules divide sections; soft rules divide rows inside one. Two
     weights is what keeps a dense spread readable instead of gridded. */
  --rule-dark-soft:  rgba(255,255,255,.085);
  --rule-light-soft: rgba(10,10,10,.085);

  --font-display: 'Syne', ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
  --font-mono:    'Syne Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --gutter: clamp(1.25rem, 4vw, 3.25rem);
  --header-h: 76px;
  --ease: cubic-bezier(.2, .7, .2, 1);
}

html[data-surface="dark"] {
  --bg: var(--ink);
  --fg: var(--paper);
  --muted: var(--muted-dark);
  --rule: var(--rule-dark);
  --rule-soft: var(--rule-dark-soft);
  --inverse-bg: var(--paper);
  --inverse-fg: var(--ink);
  color-scheme: dark;
}
html[data-surface="light"] {
  --bg: var(--paper);
  --fg: var(--ink);
  --muted: var(--muted-light);
  --rule: var(--rule-light);
  --rule-soft: var(--rule-light-soft);
  --inverse-bg: var(--ink);
  --inverse-fg: var(--paper);
  color-scheme: light;
}

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1rem, .95rem + .2vw, 1.0625rem);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

::selection { background: var(--fg); color: var(--bg); }

a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--fg); color: var(--bg); padding: .75rem 1rem;
}
.skip:focus { left: 0; }

/* --- shared type -------------------------------------------------------- */
.mono {
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1.4;
}
.label {
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
}
.display {
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.03em;
  line-height: .92;
  text-wrap: balance;
}

.wrap { padding-inline: var(--gutter); }
.measure { max-width: 62ch; }

hr.rule { border: 0; border-top: 1px solid var(--rule); margin: 0; }

/* --- header ------------------------------------------------------------- */
.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  height: var(--header-h);
  display: flex; align-items: center; gap: 1.5rem;
  padding-inline: var(--gutter);
  color: var(--fg);
  transition: background-color .3s var(--ease), backdrop-filter .3s var(--ease);
}
.site-header.is-stuck {
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
}
.brand {
  font-family: var(--font-display);
  font-weight: 700; font-size: .75rem;
  letter-spacing: .09em; text-transform: uppercase;
  line-height: 1.2; margin-right: auto;
  white-space: nowrap;
}
/* Full title + nav needs ~980px; below that the role is dropped rather than
   allowed to wrap and push the header out of shape. */
@media (max-width: 1000px) { .brand .brand-role { display: none; } }
.brand .brand-role { color: var(--muted); }
.brand .brand-role::before { content: " · "; }

.nav { display: flex; align-items: center; gap: clamp(.9rem, 2.2vw, 1.75rem); }
.nav a {
  font-family: var(--font-mono);
  font-size: .6875rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted);
  padding-block: .35rem;
  border-bottom: 1px solid transparent;
  transition: color .2s var(--ease), border-color .2s var(--ease);
}
.nav a:hover, .nav a:focus-visible { color: var(--fg); }
.nav a[aria-current="page"] { color: var(--fg); border-bottom-color: currentColor; }
.nav .icon-link { display: grid; place-items: center; padding: .35rem; border-bottom: 0; }
.nav .icon-link svg { width: 17px; height: 17px; display: block; }
.nav .icon-link[aria-current="page"] { color: var(--fg); }
.nav-sep { width: 1px; height: 14px; background: var(--rule); }

@media (max-width: 720px) {
  :root { --header-h: 64px; }
  .nav { gap: .85rem; }
}
@media (max-width: 520px) {
  /* Contact keeps its text link; the icon is a direct mailto, not a duplicate */
  .nav-sep { display: none; }
  .nav a[href$="contact.html"]:not(.icon-link) { display: none; }
}
@media (max-width: 380px) {
  /* Smallest phones: shrink the chrome so the LinkedIn icon still fits. */
  .brand { font-size: .6875rem; letter-spacing: .07em; }
  .nav { gap: .6rem; }
  .nav a { font-size: .625rem; letter-spacing: .1em; }
  .nav .icon-link { padding: .3rem 0; }
  .nav .icon-link svg { width: 15px; height: 15px; }
}

/* --- footer ------------------------------------------------------------- */
.site-footer {
  margin-top: clamp(5rem, 12vw, 10rem);
  padding: 2rem var(--gutter) 2.5rem;
  border-top: 1px solid var(--rule);
  display: flex; flex-wrap: wrap; gap: 1rem 2rem;
  align-items: baseline; justify-content: space-between;
  color: var(--muted);
}
.site-footer a { border-bottom: 1px solid transparent; transition: color .2s var(--ease), border-color .2s var(--ease); }
.site-footer a:hover { color: var(--fg); border-bottom-color: currentColor; }
.footer-links { display: flex; flex-wrap: wrap; gap: 1.25rem; }

/* --- page intro --------------------------------------------------------- */
.page-top { padding-top: calc(var(--header-h) + clamp(2rem, 6vw, 4.5rem)); }

/* ==========================================================================
   WORK INDEX
   ========================================================================== */
.work-hero { position: relative; }
.work-hero__media { aspect-ratio: 21 / 9; background: #000; overflow: hidden; }
.work-hero__media > * { width: 100%; height: 100%; object-fit: cover; }

.work-intro { padding: 0 var(--gutter) 1rem; }

.work-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2px;
  padding-inline: 2px;
}
/* 1 / 2 / 4 columns. The case tiles fill the grid in order; the CTA is pinned
   to the LAST column so it always reads as the bottom-right corner of the page
   rather than as another case study in the run. With an even number of cases
   it starts a row of its own, which is the point. */
@media (min-width: 560px)  { .work-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .work-grid { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 560px)  { .tile--cta { grid-column: -2 / -1; } }

.tile { position: relative; display: flex; flex-direction: column; overflow: hidden; background: #050505; container-type: inline-size; }
.tile__frame { position: relative; flex: 1 1 auto; min-height: 0; aspect-ratio: 16 / 10; overflow: hidden; container-type: inline-size; }
@media (max-width: 560px) { .tile__frame { aspect-ratio: 4 / 3; } }

/* --zoom scales past black bars baked into a letterboxed source, so every tile
   fills its frame the same way. 1 for normal footage. */
.tile__media { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transform: scale(var(--zoom, 1));
  transition: transform .7s var(--ease), filter .5s var(--ease), opacity .5s var(--ease); }

/* typographic placeholder shown until real media is dropped in */
.tile__placeholder {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background:
    radial-gradient(120% 120% at 25% 15%, color-mix(in srgb, var(--accent) 62%, #000) 0%, transparent 62%),
    radial-gradient(140% 140% at 85% 90%, color-mix(in srgb, var(--accent) 34%, #000) 0%, transparent 58%),
    #0B0B0B;
  transition: filter .5s var(--ease);
}
.tile__placeholder span {
  font-weight: 800; text-transform: uppercase; letter-spacing: -.04em;
  /* --ph-size is set per case in build.py so long brand names never clip */
  font-size: max(1rem, min(3rem, var(--ph-size, 12cqw))); line-height: .9;
  color: rgba(255,255,255,.13);
  padding-inline: 1rem; text-align: center;
  max-width: 100%; overflow: hidden;
}

.tile__overlay {
  position: absolute; inset: 0; z-index: 2;
  /* Items stay stretched to the tile width and centre via text-align; centring
     them with justify-items would size each to max-content and spill. */
  display: grid; align-content: center; justify-items: stretch;
  gap: .75rem; padding: 1.5rem; text-align: center;
  background: rgba(6,6,6,.62);
  opacity: 0; transition: opacity .35s var(--ease);
}
.tile__overlay h2 {
  font-weight: 800; text-transform: uppercase; letter-spacing: -.035em; line-height: .95;
  /* --overlay-size is set per case in build.py from the longest word */
  font-size: max(.95rem, min(2.25rem, var(--overlay-size, 9cqw)));
  color: #fff; text-wrap: balance; overflow-wrap: break-word;
}
.tile__overlay .mono { color: rgba(255,255,255,.72); }

@media (hover: hover) {
  .tile:hover .tile__media, .tile:focus-visible .tile__media { transform: scale(calc(var(--zoom, 1) * 1.04)); }
  .tile:hover .tile__overlay, .tile:focus-visible .tile__overlay { opacity: 1; }
}
@media (hover: none) {
  /* touch: no hover, so keep the title permanently legible over the media */
  .tile__overlay { opacity: 1; background: linear-gradient(to top, rgba(6,6,6,.78) 0%, rgba(6,6,6,.15) 60%, transparent 100%);
    align-content: end; text-align: left; }
  .tile__caption { display: none; }
}

/* Stacked by default; side-by-side once the tile is wide enough to hold both. */
/* Eighth tile — inverted so it reads as an invitation, not a case study. */
.tile--cta .tile__frame {
  background: var(--paper); color: var(--ink);
  display: grid; align-content: space-between; gap: 1rem;
  padding: clamp(1.1rem, 2.2vw, 1.75rem);
  transition: background-color .35s var(--ease), color .35s var(--ease);
}
.tile--cta .cta-eyebrow { color: color-mix(in srgb, var(--ink) 55%, transparent); }
.tile--cta .cta-body { display: grid; gap: .6rem; }
.tile--cta .cta-title {
  font-weight: 800; text-transform: uppercase; letter-spacing: -.035em; line-height: .95;
  font-size: max(1.375rem, min(2.25rem, var(--cta-size, 12cqw)));
  overflow-wrap: break-word;
}
.tile--cta .cta-sub {
  color: color-mix(in srgb, var(--ink) 62%, transparent);
  font-size: .875rem; line-height: 1.4; max-width: 30ch;
}
/* Pinned to the corner so it never competes with the copy for height. */
.tile--cta .cta-arrow {
  position: absolute; top: clamp(1.1rem, 2.2vw, 1.75rem); right: clamp(1.1rem, 2.2vw, 1.75rem);
  display: grid; place-items: center;
  width: 2.25rem; height: 2.25rem; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent);
  transition: transform .35s var(--ease), border-color .35s var(--ease);
}
/* Narrow tiles keep the headline and drop the supporting line.
   NB: container queries measure the content box, so this is the padded width. */
@container (max-width: 300px) { .tile--cta .cta-sub { display: none; } }
.tile--cta .cta-arrow svg { width: 15px; height: 15px; }
@media (hover: hover) {
  .tile--cta:hover .tile__frame, .tile--cta:focus-visible .tile__frame {
    background: var(--ink); color: var(--paper);
  }
  .tile--cta:hover .cta-eyebrow { color: color-mix(in srgb, var(--paper) 60%, transparent); }
  .tile--cta:hover .cta-sub { color: color-mix(in srgb, var(--paper) 68%, transparent); }
  .tile--cta:hover .cta-arrow { transform: translate(3px, -3px); border-color: color-mix(in srgb, var(--paper) 45%, transparent); }
}

.tile__caption {
  display: grid; gap: .2rem;
  padding: .75rem .25rem 1.75rem;
  color: var(--muted);
}
@container (min-width: 380px) {
  .tile__caption {
    grid-auto-flow: column; justify-content: space-between;
    align-items: baseline; gap: 1rem;
  }
}
.tile__caption .name { color: var(--fg); }
@media (hover: hover) {
  .tile:hover .tile__caption .name { text-decoration: underline; text-underline-offset: .25em; }
}

/* ==========================================================================
   CASE STUDY — editorial: mono body copy, hairline structure, tight rhythm.
   Everything aligns to the same page gutters, top to bottom.
   ========================================================================== */
.case-head { container-type: inline-size; }

/* --- the client lockup ---------------------------------------------------
   Painted as CSS masks, exactly like the About marquee: only the alpha of the
   source matters, so a white wordmark and a coloured one come out the same ink
   and both invert on a dark surface. --w/--h arrive unitless from build.py so
   the whole lockup can be scaled in one place. Each mark keeps its own height
   from logos.json — that file sizes marks by equal optical WEIGHT, not equal
   height, so forcing a common box here would both float them below the label
   and flatten a square mark against a long wordmark.
   ------------------------------------------------------------------------- */
.clientmark { --k: 1; display: flex; flex-direction: column; align-items: flex-start; gap: clamp(1rem, 2vw, 1.5rem); }
.clientmark__logo {
  flex: 0 1 auto;
  width: calc(var(--w) * 1px * var(--k)); height: calc(var(--h) * 1px * var(--k));
  max-width: 100%;
  background-color: var(--fg);
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: left center; mask-position: left center;
}
@media (max-width: 1023.98px) { .clientmark { --k: .85; } }
@media (max-width: 560px)     { .clientmark { --k: .7; } }

/* Masthead line: the relationship, small, above the title, with a rule under
   it. The title identifies the work; this identifies whose it was. */
.case-meta {
  display: flex; flex-wrap: wrap; gap: .35rem 1.5rem; justify-content: space-between;
  padding-bottom: .8rem; border-bottom: 1px solid var(--rule);
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
}

/* Scope, small, under the title — a credit line, not a row of tags. */
.case-disciplines {
  margin-top: clamp(1rem, 2vw, 1.5rem);
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .12em; text-transform: uppercase; line-height: 1.8;
  color: var(--muted);
}

/* Two numbers from build.py, both container-relative:
     --title-size  where the whole title sets on ONE line
     --title-max   the hard ceiling past which a word would break mid-character
   Take the smaller. On a wide screen that is --title-size, so the title fills
   its measure on one line instead of stacking three deep with the right half
   empty. On a phone --title-max wins and the title wraps, whole words only.
   The 1.75rem floor is deliberately *inside* the min() — it must never beat
   --title-max, or a long word splits. */
.case-title {
  font-weight: 700;
  font-size: min(3.25rem, var(--title-max, 12cqw), max(1.75rem, var(--title-size, 4cqw)));
  overflow-wrap: break-word;
}


/* Hero sits under the title at every width, aligned to the same margins. */
.case-hero { margin-top: clamp(1.5rem, 3vw, 2.5rem); }

.media-frame { position: relative; overflow: hidden; background: color-mix(in srgb, var(--fg) 6%, var(--bg)); }
/* --ratio is set per case in build.py: cinematic films are letterboxed inside
   16:9 and vertical films are 9:16, so a fixed frame would bar or crop them. */
.media-frame.is-hero { aspect-ratio: var(--ratio, 16 / 9); }
@media (max-width: 640px) {
  .media-frame.is-hero:not([style*="--ratio"]) { aspect-ratio: 4 / 3; }
}
/* A tall hero would swallow the screen — cap it and let cover do the rest. */
.media-frame.is-hero { max-height: 82vh; }
.media-frame > img, .media-frame > video { width: 100%; height: 100%; object-fit: cover; }
.media-frame__placeholder {
  position: absolute; inset: 0; display: grid; place-items: center; gap: .5rem;
  background:
    radial-gradient(110% 110% at 30% 20%, color-mix(in srgb, var(--accent) 55%, #000) 0%, transparent 65%),
    radial-gradient(130% 130% at 80% 85%, color-mix(in srgb, var(--accent) 30%, #000) 0%, transparent 60%),
    #0C0C0C;
}
.media-frame__placeholder .mono { color: rgba(255,255,255,.42); }

/* The one line that has to land. */
.case-hook { padding-block: clamp(2.75rem, 6vw, 5rem) clamp(1.5rem, 3vw, 2.5rem); }
/* Runs the full measure, like the film above it and the band below. It was
   capped at 22ch, which set it at about a third of the page with the rest
   empty. Smaller than the title, so the hierarchy still reads. */
.case-hook p {
  font-weight: 700; letter-spacing: -.025em; line-height: 1.15;
  font-size: clamp(1.25rem, 2.4vw, 2rem);
  text-wrap: pretty;
}

/* --- the case body -------------------------------------------------------
   From here down the page is the same module as About: a large label on the
   left, the content on the right, a hairline between each section. .sec and
   .sec--wide are defined once, further down, and shared by both pages.
   ------------------------------------------------------------------------- */
/* --- the case band ------------------------------------------------------
   Three columns split by vertical rules: PARTNER (the marks), PROJECT (the
   opportunity and what came of it) and the role. Modelled on
   teamepiphany.com/delta-ces, which Jason sent as the reference.

   Three text columns need real width, so the band only splits at 1100px and
   stacks below that. The proportions are set by what each column holds, not by
   symmetry: PARTNER needs room for a mark and nothing else, PROJECT carries
   the body copy and gets the most, the role list sits between them.
   ------------------------------------------------------------------------- */
.case-band { border-top: 1px solid var(--rule); display: grid; }
.case-band__col { padding-block: clamp(1.75rem, 3vw, 2.5rem) clamp(2rem, 3.5vw, 3rem); }
.case-band__col + .case-band__col { border-top: 1px solid var(--rule); }
/* Stacked, a column has the whole page and the copy would run past 90
   characters a line. Cap it so the stacked layout reads as well as the split
   one; above the breakpoint the columns are already narrow enough. */
@media (max-width: 1099.98px) {
  .case-band__col--project, .case-band__col--role { max-width: 44rem; }
}
@media (min-width: 1100px) {
  .case-band { grid-template-columns: minmax(0, 2.5fr) minmax(0, 5.5fr) minmax(0, 4fr); }
  .case-band__col + .case-band__col {
    border-top: 0; border-left: 1px solid var(--rule);
    padding-left: clamp(1.75rem, 3vw, 2.75rem);
  }
  .case-band__col:not(:last-child) { padding-right: clamp(1.75rem, 3vw, 2.75rem); }
  /* A list in a column this narrow reads better a step down from the running
     text — it is an index of what he did, not prose. */
  .case-band__col--role .contrib { font-size: .9375rem; }
}

/* The label for every block on a case study: a small eyebrow with a rule under
   it. Drawn as a border rather than text-decoration so it never reads as a
   link. Case pages set these small on purpose — the head already carries the
   display type, and About's large labels fought the title. */
.case-label {
  display: inline-block;
  font-family: var(--font-mono); font-weight: 400;
  font-size: .6875rem; letter-spacing: .12em; text-transform: uppercase;
  line-height: 1.4; color: var(--fg);
  border-bottom: 1px solid currentColor; padding-bottom: .35em;
  margin-bottom: clamp(1.5rem, 2.5vw, 2rem);
}

/* Full-width blocks under the band — the partner ecosystem and the gallery. */
.case-wide { border-top: 1px solid var(--rule); padding-block: clamp(1.75rem, 3vw, 2.75rem) 0; }

.case-band p + p { margin-top: .9rem; }
/* The opening and the payoff take the ink; the detail between them steps back,
   so one long Project column still reads as a shape. */
.case-band p:not(.case-lead):not(.case-outcome__text):not(.credit) { color: var(--muted); }
/* Outcome follows Opportunity inside the same column — give it air. */
.case-band .case-outcome__text { margin-top: clamp(1.5rem, 3vw, 2.25rem); }

/* The payoff, and the opening of the opportunity — a touch larger than the
   body copy. One step up, used twice; every other paragraph stays at reading
   size, which is what keeps a long case from reading as a wall. */
.case-outcome__text, .case-lead { font-size: clamp(.9375rem, .82rem + .35vw, 1.125rem); }

/* Contribution: a run of rows on soft rules. The rule is what separates one
   item from the next — no bullets, no numbers, nothing hanging in the margin. */
.contrib { display: grid; gap: 0; border-top: 1px solid var(--rule-soft); }
.contrib li { padding-block: .7rem; border-bottom: 1px solid var(--rule-soft); }
.credit { margin-top: 1.5rem; color: var(--muted); }

/* watch link */
.watch { margin-top: 1.75rem; }
.link-btn {
  display: inline-flex; align-items: center; gap: .5rem;
  font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .12em; text-transform: uppercase;
  padding-bottom: .4rem;
  border-bottom: 1px solid var(--rule);
  transition: border-color .25s var(--ease), gap .25s var(--ease);
}
.link-btn:hover, .link-btn:focus-visible { border-bottom-color: currentColor; gap: .8rem; }
.link-btn svg { width: 12px; height: 12px; }

/* The partner ecosystem: named sub-parts of a case as columns split by
   hairlines. Rules are drawn as borders on the cells, not as gaps over a
   coloured background — a partial last row would otherwise leave a solid
   block. */
.breakdown-grid { display: grid; border-top: 1px solid var(--rule); }
.breakdown {
  padding: clamp(1.25rem, 2vw, 1.75rem) 0 clamp(1.75rem, 3vw, 2.5rem);
  display: grid; gap: .6rem; align-content: start;
}
.breakdown + .breakdown { border-top: 1px solid var(--rule); }

@media (min-width: 640px) and (max-width: 1023.98px) {
  .breakdown-grid { grid-template-columns: repeat(2, 1fr); }
  .breakdown { border-top: 0; border-left: 1px solid var(--rule); padding-inline: clamp(1rem, 1.8vw, 1.5rem); }
  .breakdown:nth-child(2n+1) { border-left: 0; padding-left: 0; }
  .breakdown:nth-child(2n)   { padding-right: 0; }
  .breakdown:nth-child(n+3)  { border-top: 1px solid var(--rule); }
}
@media (min-width: 1024px) {
  .breakdown-grid { grid-template-columns: repeat(3, 1fr); }
  .breakdown { border-top: 0; border-left: 1px solid var(--rule); padding-inline: clamp(1rem, 1.8vw, 1.5rem); }
  .breakdown:nth-child(3n+1) { border-left: 0; padding-left: 0; }
  .breakdown:nth-child(3n)   { padding-right: 0; }
  .breakdown:nth-child(n+4)  { border-top: 1px solid var(--rule); }
}
.breakdown__kicker { display: block; }
/* A small partner mark at the head of each column — recognition before the
   name is read. Same mask technique as the client lockup, one size for all. */
.breakdown__mark {
  display: block; margin-bottom: .75rem;
  width: calc(var(--w) * 1px); height: 36px; max-width: 100%;
  background-color: color-mix(in srgb, var(--fg) 78%, transparent);
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: left center; mask-position: left center;
}
.breakdown h3 {
  font-weight: 800; text-transform: uppercase; letter-spacing: -.02em; line-height: 1.05;
  font-size: clamp(1.0625rem, 1.3vw, 1.3125rem); overflow-wrap: break-word;
}
.breakdown p { color: var(--muted); }

/* A 12-column media grid. Each item declares how wide it sits and its own
   aspect ratio, so a portrait still stays portrait and a film can run full
   width without anything being cropped to a single house shape. */
.gallery {
  display: grid; grid-template-columns: 1fr;
  gap: clamp(.5rem, 1vw, .875rem);
  padding-bottom: clamp(1rem, 2vw, 1.5rem);
  align-items: start;
}
@media (min-width: 640px)  { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .gallery { grid-template-columns: repeat(12, 1fr); } }

.gallery figure { margin: 0; }
@media (min-width: 640px) and (max-width: 1023.98px) {
  .gallery figure.is-full { grid-column: span 2; }
}
@media (min-width: 1024px) {
  .gallery figure         { grid-column: span 6; }
  .gallery figure.is-full    { grid-column: span 12; }
  .gallery figure.is-half    { grid-column: span 6; }
  .gallery figure.is-third   { grid-column: span 4; }
  .gallery figure.is-quarter { grid-column: span 3; }
}
.gallery .media-frame { aspect-ratio: var(--ratio, 4 / 3); }

/* prev / next */
.pager { margin-top: clamp(3rem, 6vw, 5rem); border-top: 1px solid var(--rule); display: grid; }
@media (min-width: 760px) { .pager { grid-template-columns: 1fr 1fr; } }
.pager a { padding: clamp(1.5rem, 3vw, 2.5rem) var(--gutter); display: grid; gap: .5rem; transition: background-color .3s var(--ease); }
.pager a + a { border-top: 1px solid var(--rule); }
@media (min-width: 760px) {
  .pager a + a { border-top: 0; border-left: 1px solid var(--rule); text-align: right; }
  /* First and last case studies only have one neighbour — keep it on its own side. */
  .pager--next-only a { grid-column: 2; text-align: right; border-left: 1px solid var(--rule); }
}
.pager a:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); }
.pager .pager-title { font-weight: 700; text-transform: uppercase; letter-spacing: -.02em; line-height: 1.05; font-size: clamp(1.125rem, 2.2vw, 1.625rem); }
.pager .label { color: var(--muted); }

/* ==========================================================================
   ABOUT — a stack of sections: a large display label on the left, the
   structured content on the right, a hairline between each.
   Case studies deliberately do NOT use this; see .case-band above.
   ========================================================================== */

/* Running text on these pages is Syne Mono at reading size, not display Syne.
   That is what keeps them compact rather than stretched. */
.prose-mono {
  font-family: var(--font-mono);
  font-size: clamp(.875rem, .8rem + .3vw, 1.0625rem);
  line-height: 1.75;
  letter-spacing: 0;
}

.editorial { max-width: 84rem; margin-inline: auto; padding-inline: var(--gutter); }

.about-intro { padding-bottom: clamp(2rem, 4vw, 3rem); }
.about-intro h1 { font-size: clamp(2rem, 5vw, 3.75rem); }

.sec {
  border-top: 1px solid var(--rule);
  padding-block: clamp(2rem, 4vw, 3.25rem) clamp(2.25rem, 4.5vw, 3.5rem);
  display: grid; gap: clamp(1.25rem, 2.5vw, 1.75rem);
}
@media (min-width: 860px) {
  .sec { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: clamp(2rem, 5vw, 4rem); }
}
/* Capabilities: the label still reads as the left third — this just claws a
   little of the gap and label column back for the three-across list so mono
   phrases like "Talent Identification + Packaging" stay on one line. */
@media (min-width: 860px) {
  .sec--caps { grid-template-columns: minmax(0, 3.25fr) minmax(0, 8.75fr); gap: clamp(1.5rem, 3.5vw, 3rem); }
}
/* The container lives on the WRAPPER, not the label — an element is never its
   own query container, so cqw on the label itself would resolve against the
   viewport instead of the column. */
.sec__labelwrap { container-type: inline-size; }
/* --label-size is computed in build.py from the longest section label, so every
   label shares one size and none overruns its column into the content. */
.sec__label {
  font-weight: 800; text-transform: uppercase; letter-spacing: -.03em; line-height: 1;
  font-size: clamp(1.375rem, 2.6vw, 2.25rem);
}
@media (min-width: 860px) {
  .sec__labelwrap .sec__label { font-size: max(1.125rem, min(2.25rem, var(--label-size, 8cqw))); }
}
.sec__body > * + * { margin-top: 1rem; }
.sec__lead { font-size: clamp(1.5rem, 3.2vw, 2.5rem); margin-bottom: clamp(1rem, 2vw, 1.5rem); }

/* capabilities — grouped lists, not cards. Experience and Contact reuse this
   same module at two columns; Capabilities itself runs three (.cap-cols--3),
   all on one baseline with identical header treatment and column width. */
.cap-cols { display: grid; gap: clamp(1.75rem, 3vw, 2.5rem); }
@media (min-width: 640px) { .cap-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Three text columns need real width before they split — same call the case
   band makes (see .case-band above): stay stacked past 640px, then past 860px
   too, until 1150px actually has room. Below that, three narrow columns start
   wrapping mono phrases mid-word ("Talent Identification + Packaging" is the
   long pole); a single stacked column at that width has no such problem. The
   gap is tighter than the two-column case as well, so the width it frees goes
   to the columns instead. */
@media (min-width: 640px) { .cap-cols--3 { grid-template-columns: 1fr; } }
@media (min-width: 1150px) {
  .cap-cols--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(1rem, 1.4vw, 1.5rem); }
}
.cap-col h3 {
  font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .85rem;
}
.cap-col li { line-height: 1.9; }
/* A three-across list needs a size step down from running-text prose-mono
   (the same move .case-band__col--role makes for its narrower column) so
   the longer phrases clear one line instead of wrapping. */
@media (min-width: 1150px) {
  .cap-cols--3 .cap-col ul { font-size: .8125rem; letter-spacing: -.01em; }
}

/* experience + education share one section */
.cv-rows { display: grid; gap: clamp(1.1rem, 2vw, 1.5rem); }
.cv-row { display: grid; gap: .15rem; }
/* Jobs read as one line — company then dates, set together rather than pushed
   to opposite edges of the column. */
.cv-row--job { display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .75rem; }
.cv-row--job .cv-role { flex: 1 0 100%; }
.cv-row .cv-company { font-weight: 700; letter-spacing: -.015em; font-size: 1.0625rem; text-transform: uppercase; }
.cv-row .cv-role, .cv-row .cv-years { color: var(--muted); }


/* brands — a continuous marquee under a full-width label. The label is clamped
   rather than container-sized, because it has no column to fit inside. */
.sec--brands { display: block; }
.sec--brands .sec__label {
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
  font-size: clamp(1.375rem, 2.6vw, 2.25rem);
}
.marquee { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); }
/* The gap lives on the group, not the track. With a gap between the two
   groups instead, translating -50% lands half a gap short and the loop jumps.
   Giving each group a trailing margin makes the repeating unit exactly half
   the track, so the seam is invisible. */
.marquee__track {
  display: flex; width: max-content; gap: 0;
  animation: marquee-scroll 48s linear infinite;
}
.marquee:hover .marquee__track, .marquee:focus-within .marquee__track { animation-play-state: paused; }
.marquee__item {
  font-weight: 800; text-transform: uppercase; letter-spacing: -.02em; white-space: nowrap;
  font-size: clamp(1.125rem, 2.2vw, 1.75rem);
  color: color-mix(in srgb, var(--fg) 38%, transparent);
}

/* Logos are painted as a mask, so the colour inside the source file is
   irrelevant — a white wordmark and a magenta one both come out the same ink,
   and they invert automatically on a dark surface. --m and the box size are
   set per logo from assets/logos/logos.json. */
.marquee__logo {
  flex: 0 0 auto;
  background-color: color-mix(in srgb, var(--fg) 72%, transparent);
  -webkit-mask-size: contain;   mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  transition: background-color .3s var(--ease);
}
.marquee:hover .marquee__logo { background-color: var(--fg); }
.marquee__group { align-items: center; }
@keyframes marquee-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .marquee { mask-image: none; -webkit-mask-image: none; }
  .marquee__track { animation: none; width: auto; flex-wrap: wrap; gap: .75rem 2rem; }
  .marquee__group + .marquee__group { display: none; }
}
.marquee__group {
  display: flex; gap: clamp(2rem, 5vw, 4rem);
  margin-right: clamp(2rem, 5vw, 4rem);
}

/* contact section */
.contact-lines { color: var(--muted); }
.contact-lines span { display: block; }
/* Stacked, not inline — they sit in a column now. */
.contact-links { display: grid; justify-items: start; gap: .9rem; }
.contact-links a {
  display: inline-flex; align-items: baseline; gap: .45rem;
  font-family: var(--font-mono); font-size: .75rem; letter-spacing: .12em; text-transform: uppercase;
  padding-bottom: .35rem; border-bottom: 1px solid var(--rule);
  transition: border-color .25s var(--ease), gap .25s var(--ease);
}
.contact-links a:hover { border-bottom-color: currentColor; gap: .8rem; }

/* --- entrance ----------------------------------------------------------- */
.js .reveal { opacity: 0; transform: translateY(14px); }
.js .reveal.is-in { opacity: 1; transform: none; transition: opacity .7s var(--ease), transform .7s var(--ease); }
@media (prefers-reduced-motion: reduce) { .js .reveal { opacity: 1; transform: none; } }
