/* ---------- Self-hosted fonts ----------
   Quicksand + Questrial, served from /assets/fonts instead of the Google Fonts CDN.
   Two reasons this moved in-repo: (1) the CDN <link> was render-blocking before LCP,
   and (2) every visit leaked the visitor IP to Google, which is a GDPR exposure that
   matters with public-sector and bank clients on the reference wall. These @font-face
   rules are Google verbatim (variable woff2, all subsets, font-display: swap, unchanged
   unicode-range) so rendering is byte-identical; only the origin changed. Quicksand is a
   variable font, so weights 300-700 share three subset files -- the browser downloads
   each subset once. */

/* vietnamese */
@font-face {
  font-family: 'Questrial';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/questrial-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Questrial';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/questrial-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Questrial';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/questrial-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/assets/fonts/quicksand-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/quicksand-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/quicksand-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/quicksand-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/quicksand-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Quicksand';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/quicksand-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Huizer Company — compiled from the Claude Design canvas doc "Huizer Company.dc.html" */

:root {
  --red: #D0103A;
  --red-dark: #A50B2E;
  --red-muted: #B0102F;

  --ink: #2A2A2A;
  --ink-2: #4A4A4A;
  --ink-3: #5A5A5A;
  --ink-4: #6E6E6E;
  --ink-5: #8A8A8A;
  --ink-6: #9A9A9A;

  --bg: #FFFFFF;
  --bg-soft: #FAF9F8;
  --bg-mute: #EFEDEB;

  --line: rgba(0, 0, 0, 0.08);
  --line-strong: rgba(0, 0, 0, 0.15);

  --display: 'Quicksand', system-ui, sans-serif;
  --body: 'Questrial', system-ui, sans-serif;

  --gutter: clamp(20px, 5vw, 80px);
  --ease: cubic-bezier(.22, 1, .36, 1);

  /* Logo height and nav height are derived from one source. A hardcoded nav height
     silently rots the moment the logo size changes — it already would have: the
     nav is 101px tall now, not the 87px it was. */
  --logo-h: clamp(54px, 4.6vw, 72px);
  --nav-h: calc(var(--logo-h) + 37px);   /* 18px padding x2 + 1px border */

  /* One key light, high and ~15deg left of centre: shadows fall right, hence +x.
     Warm (hue 20) rather than black — on #FAF9F8 paper a black shadow reads as dirt. */
  --shadow: 0.16 0.02 20;
  --shade-0: 0 1px 2px -1px oklch(var(--shadow) / .07);
  --shade-1: 0 1px 2px -1px oklch(var(--shadow) / .08),
             1px 4px 8px -2px oklch(var(--shadow) / .05);
  --shade-3: 0 1px 2px -1px oklch(var(--shadow) / .06),
             3px 12px 22px -6px oklch(var(--shadow) / .07),
             6px 30px 56px -18px oklch(var(--shadow) / .10);

  --fade: clamp(40px, 8vw, 120px);
}

/* var() substitution means a failed oklch() invalidates box-shadow at computed-value
   time -> `unset` -> `none`. The usual two-declaration fallback does NOT survive
   var(), so the tokens themselves must be redefined. */
@supports not (color: oklch(0 0 0)) {
  :root {
    --shade-0: 0 1px 2px -1px rgba(45, 38, 36, .07);
    --shade-1: 0 1px 2px -1px rgba(45, 38, 36, .08), 1px 4px 8px -2px rgba(45, 38, 36, .05);
    --shade-3: 0 1px 2px -1px rgba(45, 38, 36, .06), 3px 12px 22px -6px rgba(45, 38, 36, .07),
               6px 30px 56px -18px rgba(45, 38, 36, .10);
  }
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* `clip` rather than `hidden`: overflow-x:hidden on an ancestor silently breaks position:sticky. */
  overflow-x: clip;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

a { color: var(--red); text-decoration: none; }
a:hover { color: var(--red-dark); }
img { max-width: 100%; }
::selection { background: var(--red); color: #FFFFFF; }

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

/* The global focus ring is --red, which is invisible against the red contact panel
   and the solid red button — a keyboard user loses the caret entirely. Switch to
   white inside those, where it has full contrast. */
.contact :focus-visible,
.btn--solid:focus-visible {
  outline-color: #FFFFFF;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--red);
  color: #FFF;
  padding: 12px 20px;
  border-radius: 0 0 12px 0;
  font-family: var(--display);
  font-weight: 500;
}
.skip-link:focus { left: 0; color: #FFF; }

.is-red { color: var(--red); }

/* Every in-page link (nav x5, hero x2, footer x4, colophon) scrolled its target
   under the sticky nav: scroll-margin-top was 0 everywhere. */
.hero, .about, .section { scroll-margin-top: calc(var(--nav-h) + 16px); }

.shell {
  position: relative;
  width: 100%;
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* ---------- Reveal on scroll ---------- */
/* Gated on .reveal-armed, which script.js adds ONLY after it has an
   IntersectionObserver ready to remove it again. If script.js never runs, never
   loads, or throws, nothing is hidden and the page reads normally. Only the
   opacity/transform are scoped here — putting `transition` in this rule would
   override the component transitions on .card / .work__item and kill their hover. */
.reveal-armed .reveal {
  opacity: 0;
  translate: 0 34px;
}
.reveal-armed .reveal.in { opacity: 1; translate: 0; }
/* `translate`, not `transform`: `.reveal-armed .reveal.in` is (0,3,0) and beat
   `.card:hover` at (0,2,0), so `transform: none` won and BOTH hover lifts were dead
   whenever JS ran — which is always. The independent `translate` property is applied
   before `transform`, so the reveal offset and the hover lift now compose instead of
   fighting. Verified: with `.in` + a (0,2,0) hover rule, transform resolved to `none`.
   .client is excluded too — it is a .reveal and needs to carry its own transition. */
.reveal-armed .reveal:not(.card):not(.work__item):not(.client) {
  transition: opacity 1s var(--ease), translate 1s var(--ease);
}

/* ---------- Nav ---------- */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px var(--gutter);
  margin: 0 calc(-1 * var(--gutter));
  background: rgba(255, 255, 255, 0.76);
  /* saturate() is what separates glass from grey plastic: the blur averages the red
     hero passing underneath into mud, saturate puts the chroma back. Verified worst
     case — --ink-2 links on a 76% plate over #D0103A = 5.84:1, passes AA. */
  backdrop-filter: blur(18px) saturate(1.7);
  -webkit-backdrop-filter: blur(18px) saturate(1.7);
  border-bottom: 1px solid rgba(0, 0, 0, 0.07);
}

/* There was no guard at all: a browser without backdrop-filter got a bare 82% plate.
   76% without blur would be worse still, so opt back out to a near-solid one. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav { background: rgba(255, 255, 255, 0.96); }
}

.nav__logo { display: flex; align-items: center; }
/* Scales with the viewport instead of sitting at a fixed 50px. The floor is above
   the old fixed value on purpose: a lower one made the logo SMALLER than before on
   mid-size screens, where the vw term has not caught up yet. */
.nav__logo img { height: var(--logo-h); width: auto; display: block; }

.nav__menu {
  display: flex;
  align-items: center;
  gap: clamp(18px, 2.4vw, 38px);
}
.nav__menu > a:not(.btn) {
  color: var(--ink-2);
  font-size: 16px;
  letter-spacing: 0.01em;
}
.nav__menu > a:not(.btn):hover { color: var(--red); }

.nav__toggle {
  display: none;
  align-items: center;
  gap: 10px;
  background: none;
  border: 1.5px solid var(--red);
  color: var(--red);
  padding: 9px 18px;
  border-radius: 100px;
  font-family: var(--display);
  font-weight: 500;
  font-size: 15px;
  cursor: pointer;
}
.nav__toggle-bars { display: inline-flex; flex-direction: column; gap: 4px; }
.nav__toggle-bars i {
  display: block;
  width: 16px;
  height: 1.5px;
  background: currentColor;
  transition: transform .3s var(--ease);
}
.nav__toggle[aria-expanded="true"] .nav__toggle-bars i:first-child { transform: translateY(2.75px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] .nav__toggle-bars i:last-child { transform: translateY(-2.75px) rotate(-45deg); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 15px 28px;
  border-radius: 100px;
  font-family: var(--display);
  font-weight: 500;
  font-size: 16px;
  border: 1.5px solid transparent;
  transition: background .3s, color .3s, border-color .3s;
}
.btn--solid { background: var(--red); color: #FFFFFF; }
.btn--solid:hover { background: var(--red-dark); color: #FFFFFF; }

.btn--ghost { border-color: var(--line-strong); color: var(--ink); }
.btn--ghost:hover { border-color: var(--red); color: var(--red); }

.btn--ghost-red {
  border-color: var(--red);
  color: var(--red);
  padding: 9px 22px;
  font-size: 15px;
}
.btn--ghost-red:hover { background: var(--red); color: #FFFFFF; }

/* ---------- Eyebrow / section index ---------- */
.eyebrow { display: flex; align-items: center; gap: 16px; }
.eyebrow__rule { width: 44px; height: 1px; background: var(--red); }
.eyebrow__text,
.section-index {
  font-family: var(--display);
  font-size: 14px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  font-weight: 500;
}
.eyebrow__text { color: var(--ink-5); }
.section-index { color: var(--red-muted); }
/* 0.72 measured 3.35:1 on #D0103A — fails AA for 14px/500. 0.92 = 4.81:1. */
.section-index--on-red { color: rgba(255, 255, 255, 0.92); }

/* ---------- Hero ---------- */
/* Top padding was clamp(48px, 10vh, 130px) — on a tall desktop it ran to the full
   130px and left the eyebrow marooned below the nav. Tightened; the bottom keeps
   its original breathing room. */
.hero { position: relative; padding: clamp(20px, 4vh, 56px) 0 clamp(40px, 7vh, 80px); }
.hero__eyebrow { margin-bottom: clamp(28px, 5vh, 56px); animation: floatUp .7s ease both; }

.hero__title {
  font-family: var(--display);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.01em;
  /* Floor was 48px. "videoproducties" is one unbreakable 341px word at 48px, so
     every viewport under ~381px clipped it — silently, because html{overflow-x:clip}
     hides it from scrollWidth and gives no scrollbar to recover it. */
  font-size: clamp(38px, 11vw, 164px);
}
.hero__title span { display: block; animation: floatUp .7s ease both; }
.hero__title span:nth-child(1) { animation-delay: .05s; }
.hero__title span:nth-child(2) { animation-delay: .13s; }
.hero__title span:nth-child(3) { animation-delay: .21s; }

.hero__foot {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: clamp(24px, 4vw, 64px);
  align-items: end;
  margin-top: clamp(36px, 6vh, 64px);
  animation: floatUp .7s .3s ease both;
}
.hero__lead {
  max-width: 58ch;
  font-size: clamp(17px, 1.5vw, 21px);
  line-height: 1.65;
  color: var(--ink-3);
  text-wrap: pretty;
}
.hero__actions { display: flex; gap: 14px; flex-wrap: wrap; }

/* ---------- Marquee ---------- */
.marquee {
  overflow: hidden;
  white-space: nowrap;
  border-top: 1px solid rgba(0, 0, 0, 0.09);
  border-bottom: 1px solid rgba(0, 0, 0, 0.09);
  padding: 20px 0;
  margin: 0 calc(-1 * var(--gutter));
  /* The strip hard-clipped words mid-letter against nothing. -webkit- first: Chrome
     only supported the prefixed form until 120. No mask -> today's hard clip. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--fade),
                        #000 calc(100% - var(--fade)), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 var(--fade),
                        #000 calc(100% - var(--fade)), transparent 100%);
}
.marquee__track {
  display: inline-flex;
  animation: marquee 30s linear infinite;
  will-change: transform;
}
.marquee__row {
  display: inline-flex;
  align-items: center;
  gap: 40px;
  padding-right: 40px;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(20px, 2.2vw, 32px);
  color: var(--ink-2);
}
.marquee__dot { color: var(--red); font-size: 0.5em; }

/* ---------- Showreel band ----------
   Full-bleed strip of the real work, directly under the word marquee it rhymes with.
   Same mechanism as the marquee (two printed rows, -50% translate) so the two bands
   read as one system, but slower and in the opposite direction — a second strip
   moving the same way at a different speed reads as a glitch. */
.reel {
  overflow: hidden;
  margin: 0 calc(-1 * var(--gutter)) clamp(8px, 2vh, 24px);
  padding: clamp(18px, 3vh, 34px) 0;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--fade),
                        #000 calc(100% - var(--fade)), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 var(--fade),
                        #000 calc(100% - var(--fade)), transparent 100%);
}
.reel__track {
  display: flex;
  width: max-content;
  animation: reel 64s linear infinite;
  will-change: transform;
}
.reel:hover .reel__track { animation-play-state: paused; }
.reel__row { display: flex; gap: clamp(12px, 1.4vw, 20px); padding-right: clamp(12px, 1.4vw, 20px); }
.reel__item {
  position: relative;
  flex: 0 0 auto;
  width: clamp(220px, 26vw, 400px);
  aspect-ratio: 4 / 3;
  border-radius: 16px;
  overflow: hidden;
  background: var(--bg-mute);
  box-shadow: var(--shade-0);
  transition: box-shadow .5s var(--ease);
}
.reel__item:hover { box-shadow: var(--shade-1); }
.reel__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Desaturated at rest so the band supports the hero instead of shouting over it;
     full colour under the pointer. */
  filter: saturate(0.55) contrast(1.02);
  transition: filter .6s var(--ease), transform .8s var(--ease);
}
.reel__item:hover img { filter: none; transform: scale(1.05); }
.reel__cap {
  position: absolute;
  left: 14px;
  bottom: 12px;
  font-family: var(--display);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.02em;
  color: #FFFFFF;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.55);
  opacity: 0;
  translate: 0 6px;
  transition: opacity .45s var(--ease), translate .45s var(--ease);
}
.reel__item:hover .reel__cap { opacity: 1; translate: 0; }

/* ---------- Sections ---------- */
.section { padding: 0 0 clamp(72px, 12vh, 140px); }
.section--flush { padding-bottom: clamp(56px, 9vh, 100px); }

.section__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: clamp(28px, 4vh, 44px);
  flex-wrap: wrap;
}
.section__title {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(30px, 4.4vw, 64px);
  line-height: 1.02;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* ---------- About ---------- */
/* Following the image band, the full 160px top padding read as a dropout rather than
   breathing room — the band already supplies the visual break the padding exists to
   create. Only applies when the band is actually rendered. */
.reel + .about { padding-top: clamp(48px, 7vh, 92px); }

.about {
  padding: clamp(72px, 12vh, 160px) 0;
  display: grid;
  grid-template-columns: 0.42fr 1fr;
  gap: clamp(32px, 5vw, 80px);
}
.about__title {
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(30px, 4.2vw, 60px);
  line-height: 1.12;
  letter-spacing: -0.01em;
  margin-bottom: 36px;
  text-wrap: balance;
}
.about__cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(24px, 3vw, 48px);
}
.about__cols p {
  font-size: clamp(16px, 1.3vw, 19px);
  line-height: 1.7;
  color: var(--ink-3);
  text-wrap: pretty;
}

.stats {
  display: flex;
  gap: clamp(32px, 5vw, 72px);
  margin-top: clamp(40px, 6vh, 64px);
  flex-wrap: wrap;
}
/* Source order is <dt> then <dd> (the only valid content model); flex `order` puts
   the number back above its label visually without lying to assistive tech. */
.stat { display: flex; flex-direction: column; }
.stat__value {
  order: 1;
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(40px, 5vw, 72px);
  line-height: 1;
  margin: 0;
}
.stat__label { order: 2; color: var(--ink-5); font-size: 15px; margin-top: 8px; }

/* ---------- Diensten ---------- */
.services {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 18px;
}
.card {
  position: relative;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 24px;
  padding: clamp(28px, 3vw, 40px);
  /* `min-height: 340px` + `justify-content: space-between` opened a measured 59px of
     dead air between the number and the title — the leftover height had nowhere to go
     but the middle of the card. Grid already stretches all three cards to the tallest,
     so the min-height was doing nothing except manufacturing that gap. Packing from
     the top with an explicit gap puts any leftover at the BOTTOM edge, where it reads
     as breathing room instead of a hole. */
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: clamp(26px, 3vw, 38px);
  overflow: hidden;
  transition: transform .5s var(--ease), box-shadow .5s, border-color .4s, background .4s,
              opacity 1s var(--ease), translate 1s var(--ease);
}
/* A hairline that draws itself from the left on hover — the card's only moving part
   besides the lift, and it reuses the accent already in .card__num. */
.card::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .55s var(--ease);
}
.card:hover::after { transform: scaleX(1); }
.card:hover {
  border-color: color-mix(in oklab, var(--red) 55%, transparent);
  background: var(--bg);
  transform: translateY(-6px);
  box-shadow: var(--shade-3);
}
.card__num { font-family: var(--display); font-weight: 600; font-size: 15px; color: var(--red); }
.card__glyph { position: absolute; top: 26px; right: 30px; font-size: 34px; color: var(--red); }
.card__title {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(24px, 2.4vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin-bottom: 14px;
}
.card__desc { font-size: 16px; line-height: 1.6; color: var(--ink-4); text-wrap: pretty; }

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 22px;
  list-style: none;
}
.tags li {
  font-family: var(--display);
  font-weight: 400;
  font-size: 13px;
  padding: 6px 13px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 100px;
  color: var(--ink-3);
}

/* ---------- Werk ---------- */
.work { display: grid; grid-template-columns: repeat(12, 1fr); gap: 14px; }
.work__item {
  /* Derived from the inline --span-desktop/--ratio-desktop so the media queries
     below can still win. Fallbacks cover a missing inline value. */
  --span: var(--span-desktop, 6);
  --ratio: var(--ratio-desktop, 16/9);
  grid-column: span var(--span);
  position: relative;
  border-radius: 20px;
  overflow: hidden;
  background: var(--bg-mute);
  aspect-ratio: var(--ratio);
  box-shadow: var(--shade-0);
  transition: transform .5s var(--ease), box-shadow .5s, opacity 1s var(--ease),
              translate 1s var(--ease);
}
.work__item:hover {
  transform: translateY(-6px);
  box-shadow: var(--shade-3);
}
.work__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* 0.82 here, not 0.72: the hover rule below rests this layer at opacity .88, so
     .82 x .88 = .72 reproduces the original resting scrim exactly and hover deepens
     it to .82. Leaving 0.72 in place would have quietly dropped the resting scrim to
     .63 and taken the caption's contrast with it — worst over a bright sky. */
  background: linear-gradient(to top, rgba(20, 18, 18, 0.82) 0%, rgba(20, 18, 18, 0) 46%);
}
.work__cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 20px 22px;
  pointer-events: none;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
}
.work__title {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(17px, 1.5vw, 22px);
  line-height: 1.15;
  color: #FFFFFF;
}
.work__cat { font-size: 13px; color: rgba(255, 255, 255, 0.78); margin-top: 4px; }

.badge {
  flex-shrink: 0;
  font-family: var(--display);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 12px;
  border-radius: 100px;
}
.badge--video { background: var(--red); color: #FFFFFF; }
.badge--foto { background: rgba(255, 255, 255, 0.9); color: var(--ink); }

/* ---------- Referenties ---------- */
.clients {
  display: grid;
  /* 8 logos is exactly 4x2 — auto-fill resolved to 6 columns at 1600px and orphaned
     two on a second row. Side win: plates go 224px -> 350px, so the marks read. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  list-style: none;
}
/* Eight outlined-and-shadowed 16/9 boxes made the section read as a spreadsheet of
   logos rather than a wall of clients. The plate is now near-invisible at rest and
   only materialises under the pointer, so the marks carry the section and the
   containers stop competing with them. Shorter aspect closes the vertical dead air. */
.client {
  position: relative;
  /* Purely a spacing device now that the plate is invisible: it sets the row pitch.
     5/2 left a ~150px pitch around a 52px mark, so the two rows read as unrelated
     bands of white rather than one wall. */
  aspect-ratio: 3.4 / 1;
  border: 1px solid transparent;
  border-radius: 18px;
  background: transparent;
  overflow: hidden;
  transition: opacity 1s var(--ease), translate 1s var(--ease),
              background .4s var(--ease), border-color .4s var(--ease);
}
/* NO background tint on hover, ever. Seven of the eight client PNGs are opaque white
   rectangles with no alpha, so the mark is only invisibly seated while whatever sits
   behind it is also pure white. The previous rule tinted the plate to --bg-soft
   (#FAF9F8) on hover, which turned every logo into a visible white box against an
   off-white plate. The page background is #FFFFFF, so a transparent plate is the one
   state that hides the rectangles.
   Hover feedback comes from the grayscale->colour transition below instead, plus a
   hairline that cannot overlap the mark. If a tinted or dark plate is ever wanted,
   the fix is `mix-blend-mode: multiply` on the img (drops white against any lighter
   backdrop) or genuinely transparent source logos — not a background colour. */
.client:hover { border-color: var(--line); }

/* ---------- Image slots ---------- */
/* Placeholders for imagery not yet supplied. Drop an <img> inside a .slot to replace. */
.slot {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 12px;
  background-color: var(--bg-mute);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(0, 0, 0, 0.028) 0 10px,
    transparent 10px 20px
  );
}
/* The image is positioned against the slot rather than sized as a grid item:
   a centred grid item never stretches, which makes height:100% circular (row
   height depends on the image, image height depends on the row) and collapses
   it to intrinsic aspect. inset:0 gives object-fit a definite box to work in.
   The slot's own padding/place-items then only govern the placeholder label. */
.slot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.slot:has(img) { background: none; }
.slot:has(img) .slot__label { display: none; }

.slot--logo:has(img) { background: none; }

/* OPTICAL NORMALISATION — the point of this block.

   Box-fitting (`contain`/`scale-down`) sizes a logo by how well it fills the plate,
   which is a property of the plate, not the logo. Measured consequence: the seven
   280x100 wordmarks each painted 50.6% of their plate while War Child's square mark
   painted 10.2% — a fifth of the presence, for no reason other than being square.
   Area-fitting a mixed set of shapes cannot be made fair.

   A logo wall is normalised on HEIGHT instead, which is what the eye actually reads
   as "same size". Every mark is capped to one cap-height and takes whatever width it
   needs — measured at 1440px: the wordmarks land 137x49, the square mark 49x49, and
   the height spread across all eight is exactly zero. max-* can only ever shrink, so
   the no-upscaling guarantee that replaced `contain` still holds: both sources (100px
   and 256px tall) are larger than the cap, measured at 0.49x and 0.19x.

   war-child.jpg was a 75x75 raster that could not reach this cap without blurring.
   It is now 256x256, re-rendered from the vector mark that War Child publishes, so
   it is still a JPEG like every other logo here and still goes through the same
   pipeline — only the source resolution changed. */
.slot--logo img {
  position: absolute;
  inset: 50% auto auto 50%;
  translate: -50% -50%;
  width: auto;
  height: auto;
  max-width: 68%;
  max-height: clamp(38px, 3.4vw, 52px);
  object-fit: contain;   /* no-op at natural size; guards a bad asset */
  filter: grayscale(1) opacity(0.68);
  transition: filter .4s var(--ease);
}
.client:hover .slot--logo img { filter: grayscale(0) opacity(1); }
.slot__label {
  font-family: var(--display);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* --ink-5 on --bg-mute measures 2.96:1 and --ink-4 only 4.37:1 — both fail AA.
     --ink-3 = 5.91:1. */
  color: var(--ink-3);
  text-align: center;
}

/* ---------- Contact ---------- */
.contact {
  background: var(--red);
  color: #FFFFFF;
  border-radius: 32px;
  padding: clamp(36px, 6vw, 80px);
  position: relative;
  overflow: hidden;
}
/* Message left, action right. Measured before: the widest ink stopped at 617px of a
   1296px panel, so 52.4% of the width was empty and the panel read as unfinished.
   The CTA is the one element that belongs opposite the message, and unlike the
   details block that used to sit here it needs no content the owner must maintain. */
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(32px, 5vw, 64px);
  align-items: end;
}
.contact__copy { min-width: 0; }
.contact__actions { margin: 0; }

/* Off-centre key light, same direction as --shadow. Gives the flat red field a
   source and keeps the corner opposite the CTA from going dead. */
.contact__glow {
  position: absolute;
  inset: -40% -10% auto auto;
  width: min(70%, 720px);
  aspect-ratio: 1;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(closest-side,
              rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.05) 55%, transparent 72%);
}

/* Inverted button: white plate on the red panel, red text on hover. */
.btn--on-red {
  background: #FFFFFF;
  color: var(--red);
  border-color: #FFFFFF;
}
.btn--on-red:hover {
  background: transparent;
  color: #FFFFFF;
  border-color: #FFFFFF;
}

.contact__noscript {
  display: inline-block;
  font-size: 15px;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.94);
}
.contact__noscript strong { font-weight: 500; color: #FFFFFF; }
.contact__title {
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(38px, 6vw, 88px);
  line-height: 1;
  letter-spacing: -0.01em;
  margin-top: 20px;
  color: #FFFFFF;
}
.contact__lead {
  font-size: clamp(16px, 1.4vw, 20px);
  line-height: 1.6;
  margin-top: 24px;
  max-width: 44ch;
  color: rgba(255, 255, 255, 0.9);
}
.contact__details { display: flex; flex-direction: column; gap: 20px; }

.detail {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.28);
  padding-top: 16px;
  color: #FFFFFF;
}
.detail--link { transition: opacity .3s; }
.detail--link:hover { opacity: 0.8; color: #FFFFFF; }
.detail__label {
  font-family: var(--display);
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.7);
}
.detail__value {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(19px, 2vw, 26px);
  letter-spacing: -0.01em;
  line-height: 1.25;
}
.detail--link .detail__value { font-size: clamp(22px, 2.4vw, 32px); }

/* ---------- Footer ---------- */
.footer {
  border-top: 1px solid rgba(0, 0, 0, 0.1);
  padding: clamp(40px, 6vh, 64px) 0;
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr;
  gap: 32px;
}
.footer__logo { height: 44px; width: auto; display: block; }
.footer__blurb {
  margin-top: 18px;
  color: var(--ink-5);
  font-size: 15px;
  max-width: 38ch;
  line-height: 1.6;
}
.footer__col { font-size: 15px; line-height: 1.9; color: var(--ink-3); font-style: normal; }
.footer__col address { font-style: normal; }
.footer__col a {
  color: var(--ink-3);
  display: block;
  padding: 4px 0;   /* 29px -> 37px: comfortable thumb target */
}
.footer__col a:hover { color: var(--red); }
.footer__heading {
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 12px;
  color: var(--red-muted);
  margin-bottom: 10px;
  font-weight: 500;
}

.colophon {
  border-top: 1px solid rgba(0, 0, 0, 0.07);
  padding: 22px 0 44px;
  display: flex;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--ink-6);
}
.colophon a {
  color: var(--ink-6);
  /* Was 76x14px — under the 24x24 WCAG 2.5.8 (AA) minimum. */
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 5px 2px;
}
.colophon a:hover { color: var(--red); }

/* ---------- Keyframes ---------- */
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* Opposite direction to .marquee: starting at -50% and ending at 0 drifts right. */
@keyframes reel { from { transform: translateX(-50%); } to { transform: translateX(0); } }
@keyframes floatUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }

/* ---------- Responsive ---------- */
/* The canvas doc had no breakpoints — its fixed fr-ratio grids squash below ~900px. */
@media (max-width: 900px) {
  .hero__foot,
  .about,
  .about__cols { grid-template-columns: 1fr; }

  .about { padding: clamp(56px, 9vh, 96px) 0; }
  /* The CTA column collapses under the copy — `auto` on a single-column grid would
     shrink-wrap the row and strand the button. The grid's own row gap is the only
     separator here; adding margin-top as well stacked two gaps into ~72px. */
  .contact__grid { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .footer { grid-template-columns: 1fr 1fr; }
  .clients { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .work__item { --span: 6; }
  .work__item:nth-child(1),
  .work__item:nth-child(4) { --span: 12; }
}

@media (max-width: 760px) {
  .nav { flex-wrap: wrap; }
  .nav__toggle { display: inline-flex; }
  .nav__menu {
    display: none;
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    padding: 12px 0 4px;
  }
  .nav[data-open="true"] .nav__menu { display: flex; }
  .nav__menu > a:not(.btn) {
    padding: 12px 4px;
    border-bottom: 1px solid var(--line);
    font-size: 17px;
  }
  .nav__menu > .btn { margin-top: 12px; justify-content: center; }

  .footer { grid-template-columns: 1fr; }

  /* ---------- Work: swipeable on touch ----------
     Stacked full-width, the five tiles were ~2600px of vertical scrolling before the
     next section — the portfolio read as a chore. Horizontally it becomes one gesture
     per project.

     Native scroll-snap, deliberately no JS: a carousel script is the classic way to
     break a page, and this needs to survive script.js failing. Momentum, rubber-band,
     keyboard arrows, screen-reader linear order and Reduce Motion all come free from
     the platform.

     Overriding `display` to flex is what neutralises the 12-column grid — `grid-column:
     span var(--span)` simply stops applying to a flex item, so the desktop span values
     can stay on the element without fighting this. */
  .work {
    display: flex;
    grid-template-columns: none;
    gap: 12px;
    overflow-x: auto;
    overscroll-behavior-x: contain;   /* a swipe past the end must not navigate back */
    scroll-snap-type: x mandatory;
    /* Full-bleed track, but the first and last card still align to the text column. */
    margin-inline: calc(-1 * var(--gutter));
    padding-inline: var(--gutter);
    /* Insets the snapport by the same gutter. Without it, `snap-align: start` would
       align each card to the scroller's border edge and yank it flush against the
       screen edge, throwing away the gutter the padding just created. */
    scroll-padding-inline: var(--gutter);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .work::-webkit-scrollbar { display: none; }

  .work__item {
    /* <100% leaves the next card peeking, which is the whole affordance — nothing
       else on the page says "this scrolls sideways". */
    flex: 0 0 86%;
    /* `start`, not `center`: with a peek layout, centring snaps the first card to
       (viewport - card) / 2 = 44px, so it stops lining up with the section heading
       above it the moment the user scrolls back. Start keeps every card on the same
       left edge as the rest of the page. */
    scroll-snap-align: start;
    /* One ratio for the whole track. Everywhere else a slot keeps its own ratio, but
       in a horizontal row mixed ratios give every card a different height and a
       ragged, broken-looking baseline. object-fit: cover was already cropping to the
       slot, so this changes the crop, not whether there is one. */
    aspect-ratio: 4 / 3;
  }
  /* The auto-scrolling showreel is hidden on touch. It is a decorative desktop band
     (aria-hidden, same photos as the grid below), but on a phone it became a SECOND
     horizontal photo strip stacked above the real, swipeable one — and because it is
     an `overflow:hidden` CSS animation, tapping a frame only pauses it and sticks the
     :hover caption; it cannot be swiped. Two identical strips, one of them a trap.
     Removing it leaves exactly one photo section on mobile: the work carousel below,
     which swipes and carries each item's title + Video/Foto badge without a tap. */
  .reel { display: none; }

  /* The lift is a pointer affordance; under a thumb it just fights the scroll. */
  .work__item:hover { transform: none; box-shadow: var(--shade-0); }
  /* Kill the rest of the :hover flourish on touch too. Tap == sticky :hover on a
     touch device, so without this the tapped card keeps its zoomed image and raised
     caption while you try to swipe past it — exactly the "I tapped and now it's stuck"
     behaviour. The caption stays fully readable; only the motion is dropped. */
  .work__item:hover .slot img { transform: none; }
  .work__item:hover .work__cap { transform: none; }
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .marquee__track { animation: none; }
  /* The reel is a horizontal drift of photographs — exactly the vestibular trigger
     this query exists for. Frozen, it stays a legible strip of work; the second
     printed row is aria-hidden either way, so nothing is lost but the motion. */
  .reel__track { animation: none; }
  .reel__item:hover img { transform: none; }
  .work__item:hover .slot img { transform: none; }
  .reel__cap { opacity: 1; translate: 0; }
  .hero__title span,
  .hero__eyebrow,
  .hero__foot { animation: none; }
  .card:hover,
  .work__item:hover { transform: none; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}


/* ---------- Empty work tiles ----------
   Six #EFEDEB boxes each wearing a 72%-black scrim whose only job is floating white
   text over a photograph that does not exist. That reads as six broken images. Lit by
   the same key light as everything else, they become blank studio walls: deliberate
   rather than missing. :has() switches the whole treatment off per tile the moment a
   photo lands in content.json — no PHP, no per-item class, and a half-filled grid
   stays coherent.

   SPECIFICITY TRAP: `.work__item:not(:has(.slot img))` is (0,2,1) — :not/:has are
   transparent and the `.slot img` argument counts — which BEATS `.work__item:hover`
   at (0,2,0). A box-shadow here would make the empty tiles un-hoverable. Hence
   `outline`, and hence the shadows staying on the plain selector above.

   Everything here uses rgba, never oklch: Chrome 105-110 supports :has() but NOT
   oklch(), so an oklch scrim would drop back to the 72% black one while the ink rule
   still applied — dark text on a dark scrim. rgba closes that window. */
.work__item:not(:has(.slot img)) {
  background: radial-gradient(140% 120% at 28% -15%,
    #FFFFFF 0%, var(--bg-soft) 42%, var(--bg-mute) 100%);
  outline: 1px solid var(--line);   /* follows border-radius; no layout impact */
  outline-offset: -1px;
}
.work__item:not(:has(.slot img)) .slot { background: none; }

/* index.php prints the title twice on an empty tile: once as .slot__label, once as
   .work__title in the caption. It only hid because the label was 2.96:1 grey — now
   that it passes AA, all six tiles would stutter their own name in crisp ink. */
.work__item:not(:has(.slot img)) .slot__label { display: none; }

.work__item:not(:has(.slot img)) .work__scrim {
  background: linear-gradient(to top, rgba(45, 38, 36, 0.06) 0%, transparent 42%);
}
.work__item:not(:has(.slot img)) .work__title { color: var(--ink); }
.work__item:not(:has(.slot img)) .work__cat   { color: var(--ink-3); }

/* A 90%-white plate is invisible on a near-white tile. */
.work__item:not(:has(.slot img)) .badge--foto {
  background: transparent;
  color: var(--ink-3);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

/* The tile lifted, but the photograph inside it sat perfectly still — the one place
   on the page where the imagery is the product. A slow push-in plus a caption that
   rises to meet it makes the tile feel like a thing you are opening. Scale lives on
   the img, the lift on the tile, so they never fight over one property. */
.work__item .slot img {
  transition: transform .8s var(--ease), filter .5s var(--ease);
}
.work__item:hover .slot img {
  transform: scale(1.055);
  filter: brightness(1.03) saturate(1.06);
}
.work__cap { transition: transform .55s var(--ease); }
.work__item:hover .work__cap { transform: translateY(-4px); }
/* Deepen the scrim as the photo scales, so the white caption keeps its contrast over
   whatever part of the image pushes into view. Needs a base below 1 to have anywhere
   to travel to. */
.work__scrim { opacity: 0.88; transition: opacity .5s var(--ease); }
.work__item:hover .work__scrim { opacity: 1; }
