/* 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; }

/* ---------- 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 ---------- */
.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;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
  transition: transform .5s var(--ease), box-shadow .5s, border-color .4s, background .4s,
              opacity 1s var(--ease), translate 1s var(--ease);
}
.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;
  background: linear-gradient(to top, rgba(20, 18, 18, 0.72) 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;
}
.client {
  position: relative;
  aspect-ratio: 16 / 9;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 18px;
  background: var(--bg-soft);
  overflow: hidden;
  box-shadow: var(--shade-0);
  transition: opacity 1s var(--ease), translate 1s var(--ease), box-shadow .4s var(--ease);
}
.client:hover { box-shadow: var(--shade-1); }   /* plates on a wall: light, no lift */

/* ---------- 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; }

/* Client marks are wordmarks on an opaque white plate, not photography —
   contain them and keep the plate white so they don't sit on a tinted card.
   Padding on the replaced element itself insets the painted image. */
.slot--logo:has(img) { background: #FFFFFF; }
.slot--logo img {
  /* scale-down = min(none, contain): fits the plate but NEVER exceeds the source
     resolution. `contain` was upscaling every logo — war-child worst at 1.82x
     (75px source blown to 137px), which is why it read as the blurriest. The plates
     got wider when the grid went 6-col-with-orphans to 4x2, so contain only got
     hungrier. A logo can now only ever be shown at its own size or smaller. */
  object-fit: scale-down;
  padding: clamp(18px, 2.2vw, 30px);
  filter: saturate(0.15) opacity(0.62);
  transition: filter .4s var(--ease);
}
.client:hover .slot--logo img { filter: none; }
.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;
}
/* Single column since the details block was replaced by one call-to-action.
   Deliberately no max-width on the column: the title is clamp(38px..88px) and
   constraining the column wrapped it onto four lines while leaving most of the
   panel empty. Only the running text is measure-limited. */
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: end;
}

.contact__actions { margin-top: clamp(28px, 4vh, 40px); }

/* 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%); } }
@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,
  .contact__grid { grid-template-columns: 1fr; }

  .about { padding: clamp(56px, 9vh, 96px) 0; }
  .contact__grid { 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) {
  /* Ratios are deliberately NOT overridden here. Every item goes full width, but a
     4/5 portrait at 390px is 487px tall against an ~844px viewport — it fits fine,
     and forcing it to 16/10 would crop the photo (object-fit: cover) for no reason
     and mean supplying two crops per slot. One ratio per slot, everywhere. */
  .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; }

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

}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .marquee__track { animation: none; }
  .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);
}

/* No-op while the slots are empty — there is no img to select. */
.work__item:hover .slot img {
  filter: brightness(1.03) saturate(1.04);
  transition: filter .5s var(--ease);
}
