/* ---- 0. FONTS (self-hosted) -------------------------------- */
/* Served from this folder, not from Google. The site makes no request to
   any third party — nothing to break if a CDN moves, nothing sent to
   anyone else's server when a recruiter opens the page.
   All three faces are SIL Open Font License; see fonts/LICENSE-*.txt.
   font-display: swap so text paints immediately in the fallback. */

@font-face {
  font-family: 'Instrument Serif';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/instrument-serif-latin-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Instrument Serif';
  font-style: italic; font-weight: 400; font-display: swap;
  src: url('fonts/instrument-serif-latin-400-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/inter-tight-latin-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter Tight';
  font-style: italic; font-weight: 400; font-display: swap;
  src: url('fonts/inter-tight-latin-400-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/inter-tight-latin-500-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/inter-tight-latin-600-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/jetbrains-mono-latin-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/jetbrains-mono-latin-500-normal.woff2') format('woff2');
}

/* ============================================================
   TS — VISUAL & COMMUNICATION DESIGN
   Design system stylesheet
   ------------------------------------------------------------
   Editorial / Swiss-adjacent system.
   All tokens live in :root. Change them here, everything follows.
   ============================================================ */

/* ---- 1. TOKENS ------------------------------------------- */

:root {
  /* -- Colour -- */
  --paper:        #EFEDE7;   /* page background — warm paper */
  --paper-2:      #E5E2DA;   /* recessed surfaces, image wells */
  --ink:          #16150F;   /* primary text */
  --ink-70:       #5C5A51;   /* secondary text */
  /* Both of the values below were retuned when the dot grid went onto the
     paper. A dot is the paper darkened by 6%, so any glyph with a dot
     behind it is read against #E2E0DA rather than #EFEDE7 — and the old
     tones were passing AA on plain paper by hundredths (4.54 and 4.60),
     with nothing left to give. Measured against the dot, not the paper:
       --ink-40      5.51:1 on paper, 4.89:1 over a dot
       --accent-text 5.34:1 on paper, 4.74:1 over a dot
     Hue and saturation are unchanged; only lightness moved. */
  --ink-40:       #615E59;   /* tertiary / metadata */
  --rule:         #C9C5BA;   /* hairline dividers */
  --accent:       #C4462A;   /* vermillion — single accent, used sparingly */
  /* #C4462A is 4.21:1 on paper: fine for large text and non-text marks,
     not for body-size text. Small accent text uses this instead. */
  --accent-text:  #A93C24;
  --accent-soft:  #EBD9D2;

  /* The dot grid, as a token rather than a literal. It is ink at 9% on
     paper; on a dark page it has to become paper on ink or the grid
     disappears, and --ink-40 and --accent-text are both tuned against
     the dot rather than against flat paper, so it cannot be an
     afterthought. */
  --dot:          rgba(22, 21, 15, .09);

  /* Cleared / blocked, in the Clario case study. Tokens rather than the
     literals they were, because a fixed dark green and dark red are only
     legible on a light page -- they measured 2.82:1 and 2.87:1 on the dark
     ground, which is a status colour that cannot report its status. Both
     dark values keep the light theme's own saturation and move only in
     value: solving for saturation instead is what produced cyan body text
     the first time I tried this. */
  --ok:           #2E6B45;
  --stop:         #9B3324;   /* 5.18:1 over a dot */

  /* Dark inversion (used on .invert sections + project heroes) */
  --ink-inv:      #EFEDE7;
  --paper-inv:    #16150F;
  --rule-inv:     #35332B;

  /* -- Type families -- */
  --font-display: "Instrument Serif", "Times New Roman", serif;
  --font-sans:    "Inter Tight", -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-mono:    "JetBrains Mono", "SF Mono", "Roboto Mono", monospace;

  /* -- Type scale (fluid) -- */
  --t-display:  clamp(4rem, 13.5vw, 13rem);
  --t-h1:       clamp(2.5rem, 6.5vw, 5.5rem);
  --t-h2:       clamp(1.75rem, 3.4vw, 2.75rem);
  --t-h3:       clamp(1.25rem, 1.9vw, 1.5rem);
  --t-lead:     clamp(1.0625rem, 1.5vw, 1.3125rem);
  --t-body:     1rem;
  --t-small:    0.875rem;
  --t-label:    0.6875rem;
  --t-micro:    0.625rem;

  /* -- Spacing scale -- */
  --s-1:  4px;
  --s-2:  8px;
  --s-3:  12px;
  --s-4:  16px;
  --s-5:  24px;
  --s-6:  32px;
  --s-7:  48px;
  --s-8:  64px;
  --s-9:  96px;
  --s-10: 128px;
  --s-11: 192px;

  /* -- Grid --
     Design target is 1920 × 1080. Content stops growing at --max-w and
     centres beyond it; below it the layout is fluid all the way down. */
  --max-w: 1920px;
  --margin: 24px;
  --gutter: 24px;
  --cols: 12;

  /* Horizontal padding that keeps content centred once past --max-w.
     Used by .wrap AND the fixed bars so they always line up. */
  --pad-x: max(var(--margin), calc((100% - var(--max-w)) / 2 + var(--margin)));

  /* -- Chrome -- */
  --bar-h: 48px;

  /* -- Motion -- */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --dur-fast: 180ms;
  --dur: 400ms;
  --dur-slow: 800ms;
}

/* Large desktop — the 1920 design target. Margins and gutters open up so the
   page doesn't read as a 1440 layout stretched across a bigger screen. */
@media (min-width: 1600px) {
  :root {
    --margin: 48px;
    --gutter: 32px;
    --bar-h: 56px;
    /* a little more presence on a 1920 canvas */
    --t-display: clamp(4rem, 13.5vw, 15rem);   /* 240px cap */
    --t-h1:      clamp(2.5rem, 6.5vw, 6rem);   /*  96px cap */
  }
}

@media (min-width: 1200px) and (max-width: 1599px) {
  :root { --margin: 32px; --gutter: 24px; }
}

@media (max-width: 900px) {
  :root { --margin: 16px; --gutter: 16px; --bar-h: 44px; }
}

/* ---- 1b. DARK ---------------------------------------------
   The ground is #273C43, which is not a colour anyone picked: it is the
   median of the night curtain's own field. The page a visitor lands on
   and the panel that lifts off it are then literally the same colour.

   Only the tokens change. Every rule already reads them -- --rule alone
   is used 115 times, --ink-40 58 -- so the theme flips without touching
   a selector.

   Values were solved, not chosen, and solved over the DOT rather than
   over flat paper, because the dot grid is what the type actually sits
   on. The neutrals are pure greys -- saturation 0.00, no hue at all --
   and the primary is flat white. Two earlier passes got this wrong in
   the same direction: the first maximised saturation at the ground's hue
   and produced cyan text; the second tinted the greys 0.08 and 0.13
   toward teal to mirror the warm cast the light theme's greys carry, and
   they sank into the ground. A neutral cannot share the background's hue
   and stay legible on it. Only the accent is allowed any saturation.
   The reference is the night curtain itself, where "Take your time."
   is white over the same field.

   The ground is Victorian Peacock 950 and the neutrals sit on it at the
   same RATIOS the light theme uses, not at some WCAG floor. That
   distinction caught a real bug: --rule was solved to 3:1 and shipped at
   4.27:1, but the light theme's hairline is 1.47:1 -- nearly three times
   quieter. Every divider on the site would have read as a heavy border.
   Rules, wells and the dot grid are now solved against the light
   theme's own measured relationships; only the type is solved against
   WCAG, because only type has to be read.

   The dot grid is 6% white, not the 7% that matching the light theme's
   CONTRAST RATIO gave. Ratio parity is not perceptual parity: light dots
   on a dark ground read far stronger than dark dots on a light one at the
   same ratio. Measured in CIE L* instead, the light grid sits 6.77 below
   its paper and 7% white sat 7.75 above this one -- about 15% too loud,
   which is why the texture announced itself in dark and whispered in
   light. 6% lands at 6.81.

   Which theme shows is decided by the CLOCK, not the OS: night runs
   19:00-07:00, the same rule and the same sessionStorage key the curtain
   uses, so the panel a visitor lifts and the page underneath can never
   disagree. An inline script in every <head> sets the class before first
   paint. The media query below is the no-JS fallback only -- with no
   script there is no clock, and the system setting is the best guess
   left. Declared twice because .theme-light has to be able to win
   against a dark OS, and CSS cannot share one block.

   Carried over rather than fixed: --accent behind --paper is 4.21:1 in
   light and 3.88:1 here. Both sit under 4.5, both are fine for the large
   type they are used on, and forcing dark alone to 4.5 would have pulled
   the vermillion off-brand to beat a bar the light theme never met. */

@media (prefers-color-scheme: dark) {
  :root:not(.theme-light) {
    --paper:        #061923;   /* Victorian Peacock 950 */
    --paper-2:      #082331;   /* 1.11:1 on paper -- light theme's is 1.11 */
    --ink:          #FFFFFF;   /* 17.93:1 */
    --ink-70:       #E7E7E7;   /* 14.50:1 */
    --ink-40:       #C9C9C9;   /* 10.83:1 */
    --rule:         #0C3A4E;   /* 1.48:1 -- light theme's hairline is 1.47 */
    --accent:       #FB6632;   /* Orioles Orange 400, 6.02:1 */
    --accent-text:  #FC906B;   /* Orioles Orange 300, 7.98:1 */
    --accent-soft:  #3C2A26;   /* 1.32:1 */
    --ok:           #449E66;   /* 4.52:1 over a dot, saturation held from light */
    --stop:         #FA523A;   /* 4.52:1 over a dot, saturation held from light */
    --ink-inv:      #16150F;
    --paper-inv:    #EFEDE7;
    --rule-inv:     #C9C5BA;
    --dot:          rgba(255, 255, 255, .06);   /* see the note in 1b */
  }
}

:root.theme-dark {
    --paper:        #061923;   /* Victorian Peacock 950 */
    --paper-2:      #082331;   /* 1.11:1 on paper -- light theme's is 1.11 */
    --ink:          #FFFFFF;   /* 17.93:1 */
    --ink-70:       #E7E7E7;   /* 14.50:1 */
    --ink-40:       #C9C9C9;   /* 10.83:1 */
    --rule:         #0C3A4E;   /* 1.48:1 -- light theme's hairline is 1.47 */
    --accent:       #FB6632;   /* Orioles Orange 400, 6.02:1 */
    --accent-text:  #FC906B;   /* Orioles Orange 300, 7.98:1 */
    --accent-soft:  #3C2A26;   /* 1.32:1 */
    --ok:           #449E66;   /* 4.52:1 over a dot, saturation held from light */
    --stop:         #FA523A;   /* 4.52:1 over a dot, saturation held from light */
    --ink-inv:      #16150F;
    --paper-inv:    #EFEDE7;
    --rule-inv:     #C9C5BA;
    --dot:          rgba(255, 255, 255, .06);   /* see the note in 1b */
}

/* ---- 2. RESET -------------------------------------------- */

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

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

/* Nothing on this site scrolls sideways. `clip` rather than `hidden`
   because `hidden` turns the body into a scroll container, which silently
   breaks every position: sticky on the page. */
html, body { overflow-x: clip; max-width: 100%; }

body {
  margin: 0;
  /* A dot grid on the paper. One radial-gradient tile repeated by the
     browser — no image request, no extra bytes, and it scales to any
     viewport. The dot is kept very close to the paper colour: it should
     register as texture at arm's length and disappear behind text, so it
     is set at 8% ink rather than a visible grey. */
  background-color: var(--paper);
  background-image: radial-gradient(var(--dot) 1px, transparent 1px);
  background-size: 22px 22px;
  background-position: 0 0;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: 1.45;
  font-feature-settings: "kern" 1, "liga" 1;
  -webkit-font-smoothing: antialiased;
  padding-top: var(--bar-h);
  padding-bottom: var(--bar-h);
}

/* Anything that lays its own ground over the page — the dark case-study
   bands, the fixed chrome, the concierge panel — must cover the dots rather
   than let them show through, or the texture reads as dirt on the panel. */
.topbar, .botbar, .ts-ask-panel, .clario-ai-band, .clario-marketing-hero,
.asset-clearance-case .clearance-flow-handoff { background-image: none; }

h1, h2, h3, h4, p, figure, ul, ol, dl, dd { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img, video { display: block; max-width: 100%; height: auto; }

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

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

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

/* ---- 3. TYPE PRIMITIVES ---------------------------------- */

.display {
  font-family: var(--font-display);
  font-size: var(--t-display);
  line-height: 0.86;
  letter-spacing: -0.02em;
  font-weight: 400;
}

.h1 {
  font-family: var(--font-display);
  font-size: var(--t-h1);
  line-height: 0.94;
  letter-spacing: -0.015em;
  font-weight: 400;
}

.h2 {
  font-family: var(--font-sans);
  font-size: var(--t-h2);
  line-height: 1.06;
  letter-spacing: -0.02em;
  font-weight: 500;
}

.h3 {
  font-family: var(--font-sans);
  font-size: var(--t-h3);
  line-height: 1.2;
  letter-spacing: -0.01em;
  font-weight: 500;
}

.lead {
  font-size: var(--t-lead);
  line-height: 1.42;
  letter-spacing: -0.005em;
  max-width: 46ch;
}

.body { font-size: var(--t-body); line-height: 1.55; max-width: 62ch; }
.small { font-size: var(--t-small); line-height: 1.5; }

.label {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 400;
}

.micro {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  line-height: 1;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.muted  { color: var(--ink-70); }
.muted-2{ color: var(--ink-40); }
.accent { color: var(--accent); }
.italic { font-style: italic; }

/* ---- 4. LAYOUT ------------------------------------------- */

.wrap {
  padding-inline: var(--pad-x);
  width: 100%;
}

.grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--gutter);
}

.rule {
  height: 1px;
  background: var(--rule);
  width: 100%;
  border: 0;
}

.section { padding-block: var(--s-9); }
.section-tight { padding-block: var(--s-7); }

/* More air on the 1920 canvas — vertical rhythm scales with the horizontal */
@media (min-width: 1600px) {
  .section { padding-block: var(--s-10); }
  .section-tight { padding-block: var(--s-8); }
  .hero { padding-block: var(--s-9) var(--s-8); }
}

/* The vertical rhythm was set for a 1920 canvas and never scaled back down.
   96px top AND bottom is a reasonable breath between sections on a desktop;
   on a 375px screen it is 192px of empty paper between every block — most of
   a viewport height spent on nothing, which is what made the phone layout
   feel like scrolling through gaps rather than work. Halved here. */
@media (max-width: 760px) {
  .section       { padding-block: var(--s-7); }   /* 96 -> 48 */
  .section-tight { padding-block: var(--s-6); }   /* 48 -> 32 */
}

/* Section header: mono label + hairline, used to open every block */
.sec-head {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--s-6);
}
.sec-head .sec-num { color: var(--accent-text); }
.sec-head .sec-title { flex: 1; }
.sec-head .sec-meta { color: var(--ink-40); }

/* ---- 5. FIXED CHROME (top nav + bottom status bar) -------- */

.topbar, .botbar {
  position: fixed;
  left: 0;
  right: 0;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  gap: var(--s-5);
  padding-inline: var(--pad-x);   /* same as .wrap — bars stay on the grid */
  background: var(--paper);
  z-index: 100;
}

.topbar { top: 0; border-bottom: 1px solid var(--rule); }
.botbar { bottom: 0; border-top: 1px solid var(--rule); }

.brand { display: flex; align-items: baseline; gap: var(--s-2); }
.brand strong { font-weight: 500; letter-spacing: 0.02em; }

.nav { display: flex; gap: var(--s-5); margin-left: auto; }
.nav a { position: relative; padding-bottom: 2px; }
.nav a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.nav a:hover::after, .nav a[aria-current="page"]::after { transform: scaleX(1); }
/* 11px nav text, so it needs the text-safe accent: the display vermillion
   measures 4.21:1 on --paper and this is the one accent-coloured word a
   keyboard or low-vision user has to read on every page. */
.nav a[aria-current="page"] { color: var(--accent-text); }

.bar-spacer { flex: 1; }
.bar-item { color: var(--ink-40); white-space: nowrap; }
.bar-item.status { color: var(--ink); display: flex; align-items: center; gap: var(--s-2); }

/* The site's day/night switch. Deliberately the same object as the one on
   the curtain -- same 34x18 track, same 10px knob, same half-opacity rest
   state -- because it is the same decision, made twice: once on the panel
   and once on the page behind it. Both write the same sessionStorage key.

   It sits in the footer beside the Mumbai clock, which is the honest
   place for it: the theme is chosen by a clock, and that is the clock. */
.theme-switch {
  display: inline-flex; align-items: center; gap: var(--s-3);
  padding: 0; border: 0; background: none;
  color: var(--ink-40); font: inherit;
  cursor: pointer;
}
.theme-switch:hover { color: var(--ink); }

/* Both words ship in the markup and CSS picks one. The obvious version --
   JS writing textContent, the way the curtain's toggle does it -- puts the
   word one deferred script behind the class on <html>: the page paints dark
   and the switch says "Day" until script.js runs. The curtain gets away
   with it because it fades in; a footer that is already on screen does not.

   Fixed width, because "Day" and "Night" are different lengths and the
   footer is a flex row -- without it every toggle nudges the row. */
/* Right-aligned, so the gap to the track is fixed and the word grows away
   from it. Left-aligned, the reserved 5ch left a ragged hole after "Day"
   that read as a broken gap rather than a shorter word. */
.theme-word { display: inline-block; min-width: 5ch; text-align: right; }
.theme-word .tw-night { display: none; }
:root.theme-dark .theme-word .tw-day { display: none; }
:root.theme-dark .theme-word .tw-night { display: inline; }
.theme-track {
  position: relative; display: block;
  width: 34px; height: 18px;
  border: 1px solid currentColor; border-radius: 999px;
  opacity: .5;
  transition: opacity var(--dur) var(--ease);
}
.theme-switch:hover .theme-track,
.theme-switch:focus-visible .theme-track { opacity: 1; }
.theme-knob {
  position: absolute; top: 3px; left: 3px;
  width: 10px; height: 10px; border-radius: 50%;
  background: currentColor;
  transition: transform var(--dur) var(--ease);
}
.theme-switch[aria-checked="true"] .theme-knob { transform: translateX(16px); }
@media (prefers-reduced-motion: reduce) {
  .theme-knob { transition: none; }
}

.dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
  display: inline-block;
  flex: none;
}

/* ---- 6. HERO --------------------------------------------- */

.hero {
  padding-block: var(--s-8) var(--s-7);
  border-bottom: 1px solid var(--rule);
  /* background-color is set in the inversion block below, driven per-frame */
}

/* ---- HERO GROUND ------------------------------------------------------
   These --hero-* variables were driven frame-by-frame by the animation, so
   the whole band inverted with the plate. Nothing sets them now, and every
   rule below carries the page token as its fallback — which is what the
   hero renders from today. They are kept rather than inlined because the
   fallbacks are already correct and flattening them would be a large edit
   for no visible change. */

/* The hero paints its own ground, which is opaque, so the dot grid on
   <body> was being covered — the one section on the landing page with no
   texture. It carries its own copy of the grid instead. background-color
   still comes from --hero-bg, so the per-frame inversion is untouched and
   the dots simply ride on whatever ground the animation has chosen. */
.hero {
  background-color: var(--hero-bg, var(--paper));
  /* The dot is mixed from the hero's own foreground, not from a fixed ink.
     A flat dark ink is invisible on the accent ground — 6% of it over
     vermillion is no change at all — so the one section that inverts was
     also the one that lost its texture, for half of every loop.
     --hero-fg steps between two poles rather than sliding, so this repaints
     on the flip and not on every frame.

     Held at 7% against the 9% used on the paper elsewhere: cream text on
     the accent ground already measures 4.11:1 before any texture goes
     behind it, so the hero has less room to spend than the rest of the
     site. */
  background-image: radial-gradient(
    color-mix(in srgb, var(--hero-fg, #16150F) 7%, transparent) 1px,
    transparent 1px);
  background-size: 22px 22px;
}

.hero .hero-name,
.hero .hero-name .italic,
.hero .h3 { color: var(--hero-fg, var(--ink)); }

.hero .label,
.hero .muted,
.hero .muted-2,
.hero .hero-meta span { color: var(--hero-fg-mute, var(--ink-40)); }

.hero,
.hero .hero-role { border-color: var(--hero-rule, var(--rule)); }

/* Never strobe a section for someone who asked for less motion.
   format-morph already suppresses the event, this is belt and braces. */
/* Reduced motion: format-morph never emits, so the variables stay unset and
   the fallbacks above already hold the section on paper. Pinned here anyway
   in case the values were set before the preference changed. */
@media (prefers-reduced-motion: reduce) {
  .hero {
    --hero-bg: var(--paper);
    --hero-fg: var(--ink);
    --hero-fg-mute: var(--ink-40);
    --hero-rule: var(--rule);
  }
}

/* Three items: one at each edge and one dead centre. space-between would
   only centre the middle one if all three were the same width, which they
   are not — so the row is a three-column grid instead. */
/* Off by default: on a wide screen the topbar already carries the name, and
   printing it twice within 80px of itself is noise. It appears at exactly
   the width where .brand goes display:none. */
.hero-id { display: none; }

@media (max-width: 760px) {
  .hero-id {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s-2) var(--s-4);
    margin: 0 0 var(--s-4);
    padding-bottom: var(--s-4);
    border-bottom: 1px solid var(--hero-rule, var(--rule));
  }
  .hero-id strong { font-weight: 500; letter-spacing: .02em; color: var(--hero-fg, var(--ink)); }
  .hero-id span { color: var(--hero-fg-mute, var(--ink-40)); }
}

.hero-meta {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  gap: var(--s-5);
  color: var(--hero-fg-mute, var(--ink-40));
  padding-bottom: var(--s-6);
}
.hero-meta > :last-child { text-align: right; }

/* Deliberately NOT stacked on a phone. Three short strings fit one row at
   10px; stacking them wastes three lines above the name for no gain. */
@media (max-width: 640px) {
  .hero-meta { gap: var(--s-3); font-size: 10px; letter-spacing: 0.04em; }
}
@media (max-width: 400px) {
  .hero-meta { font-size: 9px; letter-spacing: 0.02em; }
}

.hero-name {
  display: block;
  margin-bottom: var(--s-3);
}

.hero-spec {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .6em;
  color: var(--hero-fg, var(--ink));
}
/* The separator dots between "Product designer · 6 years · …". They read
   --ink-40 directly, which is a PAGE token: on the accent beat the words
   around them turned dark for the orange ground while the dots stayed the
   page's light grey, so the punctuation sat in a different theme from its
   own sentence. Everything else in the hero already went through
   --hero-fg-mute; these three rules were the ones that never did. */
.hero-spec span { color: var(--hero-fg-mute, var(--ink-40)); }

/* One centred column. This was two equal columns — name left, animation
   right — and with the animation gone the left column would have held all
   the copy against half a screen of nothing. Centring is the honest layout
   for a hero that is now only type. */
.hero-split {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: var(--gutter);
}

/* The wordmark is an inline-block carrying an absolutely-positioned canvas,
   so it centres with the text; the meta row is its own three-column grid
   and needs an explicit width to stay centred rather than stretching. */
.hero-split .hero-meta,
.hero-split .hero-spec { justify-content: center; }
.hero-split .hero-spec { width: 100%; }

/* The wordmark needs a definite width or it disappears.
   justify-items:center sizes every grid item to its content, which for an
   <h1> holding nothing but `width:100%` SVG is circular — so the SVG fell
   back to the 300px default every replaced element gets when it has no
   resolvable width. On a 2560px screen the name rendered at 300px: an
   eighth of the screen, smaller than the nav above it. Stretching the item
   and capping it keeps the name centred and lets it read as a masthead. */
.hero-split .hero-name {
  justify-self: stretch;
  width: 100%;
  /* Uncapped, this ran the full 1824px of content width and stopped being a
     masthead -- at that size the name is the page rather than the top of it,
     and nothing below it can hold its own. The cap keeps it decisively large
     on a wide monitor while leaving the meta rows and the spec line room to
     register as part of the same composition. */
  max-width: min(100%, 1180px);
  margin-inline: auto;
}

/* .hero-slot, the .tsfm-* plate rules and the --mono / --ink-mute token
   bridge that fed the component's caption all lived here. They went with
   the animation. The two-column tablet and phone rules went with them: a
   single centred column needs no breakpoint to take itself apart. */

@media (max-width: 1200px) and (min-width: 761px) {
  .hero-split { gap: var(--s-6); }
}

.hero-role {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}

/* ---- 7. MARQUEE ------------------------------------------ */

.marquee {
  overflow: hidden;
  border-block: 1px solid var(--rule);
  padding-block: var(--s-3);
  white-space: nowrap;
}
.marquee-track {
  display: inline-flex;
  /* Same value as the gap inside .marquee-item below. The separator has to
     sit exactly halfway between two brands; when these two differed the
     slash hugged the word before it. */
  gap: var(--s-5);
  animation: slide 38s linear infinite;
  will-change: transform;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-item { display: inline-flex; align-items: center; gap: var(--s-5); }
.marquee-item .sep { color: var(--accent); }

@keyframes slide {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
  html { scroll-behavior: auto; }
}

/* ---- 8. FEATURED WORK LIST ------------------------------- */

.work-list { border-top: 1px solid var(--rule); }

.work-row {
  display: grid;
  /* Three tracks, not four. The fourth was the year column; with the year gone
     a 64px track would still be reserved and the tags would stop short of the
     right margin for no visible reason. */
  grid-template-columns: 72px 1fr auto;
  gap: var(--gutter);
  align-items: center;
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--rule);
  transition: padding-inline var(--dur) var(--ease), background var(--dur) var(--ease);
}
.work-row:hover { background: var(--paper-2); padding-inline: var(--s-4); }
.work-row .w-id { color: var(--ink-40); }
.work-row .w-title { font-size: var(--t-h3); font-weight: 500; letter-spacing: -0.01em; }
.work-row .w-tags { color: var(--ink-70); }
.work-row .w-year { color: var(--ink-40); text-align: right; }

@media (max-width: 720px) {
  /* One column now. The second track existed to hold the year beside the
     title; empty, it would still take a gutter's worth of space to the right
     of every title. */
  .work-row { grid-template-columns: 1fr; row-gap: var(--s-2); }
  .work-row .w-id { grid-column: 1 / -1; }
  .work-row .w-tags { grid-column: 1 / -1; }
}

/* ---- 9. FEATURED CARDS (image-led) ----------------------- */

.cards { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--gutter) ; row-gap: var(--s-8); }

/* One featured project across the full width, then a uniform 3-up grid.
   Every grid card is the same size and the same shape — no mixed spans. */
.card { display: block; grid-column: span 4; }
.card.wide { grid-column: span 12; }
/* The two product case studies read as a matched pair rather than a queue:
   they are the same kind of work, and stacking them full-width made the
   second one look like an afterthought to the first. Half each, same row. */
.card.pair { grid-column: span 6; }
.card[data-project-state="held"] { display: none !important; }

.card-media {
  background: var(--paper-2);
  border: 1px solid var(--rule);
  aspect-ratio: 16 / 9;   /* the ratio the key visuals are actually built in */
  overflow: hidden;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* 16:9, because that is the ratio every campaign key visual is built in.
   At 2:1 the hero was slicing the tagline off the bottom of the HYROX
   frame — 11% of the height is enough to lose a line of type. */
.card.wide .card-media { aspect-ratio: 16 / 9; }
.card-media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-slow) var(--ease); }
/* Some creatives do not fit a 16:9 card: a square post, or a 2.8:1 banner.
   Cropping them eats the lockup, so they are shown whole — and the space
   left over is filled with a blurred, scaled copy of the same image. It
   matches by construction, so a peach banner gets a peach surround and a
   marble post gets marble, with no colour hard-coded anywhere. */
.card-media.fit { padding: 0; }
.card-media.fit::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--bleed);
  background-size: cover;
  background-position: center;
  filter: blur(26px) saturate(1.08);
  transform: scale(1.25);      /* hides the blur's soft edge */
  z-index: 0;
}
.card-media.fit img {
  position: relative;
  z-index: 1;
  object-fit: contain;
}
.card:hover .card-media.fit img { transform: scale(1.02); }
.card:hover .card-media img { transform: scale(1.03); }

/* Fleet keeps both Figma thumbnail directions: the typographic refinement
   appears only on hover/focus, while the original remains the default. */
.fleet-card-media .fleet-thumb-hover {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--dur-med) var(--ease), transform var(--dur-slow) var(--ease);
}
.fleet-card-media .fleet-thumb-default {
  transition: opacity var(--dur-med) var(--ease), transform var(--dur-slow) var(--ease);
}
.card:hover .fleet-card-media .fleet-thumb-hover,
.card:focus-visible .fleet-card-media .fleet-thumb-hover { opacity: 1; }
.card:hover .fleet-card-media .fleet-thumb-default,
.card:focus-visible .fleet-card-media .fleet-thumb-default { opacity: 0; }

/* No zoom on this one. The 1.03 scale above is the hover affordance for
   cards whose art is a still -- here the crossfade to the italic lockup is
   already the affordance, and scaling underneath it made the type creep
   mid-swap, which reads as a rendering fault rather than a deliberate
   change. Two hover animations fighting is one too many. */
.card:hover .fleet-card-media img,
.card:focus-visible .fleet-card-media img { transform: none; }

.placeholder-note {
  color: var(--ink-40);
  text-align: center;
  padding: var(--s-4);
}

/* Grid, not wrapping flex. The foot is a fixed shape — title and discipline
   on one row, outcome across both beneath — and flex cannot promise that:
   it breaks lines BEFORE it shrinks items, so a title wider than the space
   left beside the discipline pushed the discipline onto its own row rather
   than wrapping itself. "adidas × Copenhagen Half Marathon" is 380px in a
   448px foot, so it did exactly that. A grid track just wraps its text. */
.card-foot {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: var(--s-4);
  row-gap: 0;
  padding-top: var(--s-4);
  margin-top: var(--s-4);
  border-top: 1px solid var(--rule);
}
/* The title takes the slack and wraps inside its own box; the discipline
   holds its width. Without this a long title (adidas × Copenhagen Half
   Marathon is 380px against a 448px foot) pushed the discipline onto a
   row of its own, so the foot became three stacked rows instead of a
   title/discipline pair with the outcome beneath. min-width:0 because a
   flex item will not shrink below its longest word otherwise. */
.card-foot .c-title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.01em;
}
/* Disciplines as chips. As a run of text they read as one long label —
   "Product Strategy · Rights" — when they are actually two separate
   claims about the work; a box round each says so. Right-aligned and
   wrapping, so a card with two of them never pushes the title. */
.card-foot .c-meta {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: flex-end;
  color: var(--ink-40);
}
.card-foot .c-tag {
  padding: 5px 8px;
  border: 1px solid var(--rule);
  border-radius: 3px;
  line-height: 1;
  white-space: nowrap;
}

/* The outcome line. Wraps onto its own row under the title and discipline
   rather than becoming a third item competing for the same baseline.

   It was --ink-40 to begin with, which is exactly the colour of .c-meta
   beside it — same tone, near enough the same size — so the outcome read
   as a second helping of metadata and disappeared. It carries --ink-70
   now, which puts three steps in the foot: title in full ink, outcome in
   secondary ink, discipline in tertiary. The foot also had 12px of
   padding sized for a single line and was tight once a third row landed
   in it. */
.card-foot .c-out {
  grid-column: 1 / -1;
  max-width: 54ch;
  margin-top: var(--s-2);
  color: var(--ink-70);
  font-size: var(--t-small);
  line-height: 1.45;
  /* These lines are short enough that normal wrapping leaves a stub on the
     last row — the Clario one dropped "into it." onto a line of its own.
     balance evens the rows instead, the same treatment the About statement
     already uses. Browsers that do not support it wrap as before. */
  text-wrap: balance;
}

/* The lead card's foot is three times the width of the others, so the 54ch
   measure that keeps a 448px card readable is the only thing breaking its
   line. Given the room, it reads as one line. */
.card.wide .card-foot .c-out { max-width: none; }

/* Laptop — three-up gets cramped below ~1200, so drop thirds to two-up */
@media (min-width: 901px) and (max-width: 1200px) {
  .card { grid-column: span 6; }
}

/* Keep the desktop grid on a phone rather than collapsing to one card per
   row. Six of twelve is two-up, which is what the layout does at every other
   size; stacking them full width turned the landing page into a very long
   column of near-identical crops with no sense of a body of work. The wide
   card stays wide — it is the lead piece and is meant to break the rhythm. */
@media (max-width: 900px) {
  .card { grid-column: span 6; }
  .card.wide { grid-column: span 12; }
  /* The pair holds side by side down to here. Below it each one needs the
     full width: the Clario card carries a headline and a browser mock in
     one composition, and at ~330px that becomes unreadable rather than
     merely small. */
  .card.pair { grid-column: span 12; }
  /* The row gap was tuned for full-width cards. Two-up needs far less. */
  .cards { row-gap: var(--s-6); }
}

/* At two-up on a 375px screen each card is about 170px wide. The foot was a
   two-column row of title and meta, which at that width left the title on
   three lines beside a cramped discipline label. Stack it. */
@media (max-width: 560px) {
  /* One track, so title, discipline and outcome each take a row. */
  .card-foot {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    row-gap: 2px;
  }
  .card-foot .c-title { font-size: 0.95rem; line-height: 1.2; }
  .card-foot .c-meta { justify-content: flex-start; }
  /* A 164px card cannot hold two chips and their padding side by side. */
  .card-foot .c-tag { padding: 4px 6px; }
  .card-foot .c-out { margin-top: var(--s-2); }
}

/* ---- 10. ARCHIVE TABLE ----------------------------------- */

.table { width: 100%; border-top: 1px solid var(--rule); }

.table-head, .table-row {
  display: grid;
  grid-template-columns: 88px minmax(0, 2.2fr) minmax(0, 2fr) minmax(0, 1.4fr) 64px;
  gap: var(--gutter);
  align-items: center;
  padding-block: var(--s-3);
  border-bottom: 1px solid var(--rule);
}
.table-head { color: var(--ink-40); position: sticky; top: var(--bar-h); background: var(--paper); z-index: 10; }
.table-row { transition: background var(--dur-fast) var(--ease); }
.table-row:hover { background: var(--paper-2); }
.table-row .t-title { font-weight: 500; }
.table-row .t-id, .table-row .t-year { color: var(--ink-40); }

/* The one-line description, inside the title cell rather than as a sixth
   column: the row is already 88px + 2.2fr + 2fr + 1.4fr + 64px, and a sixth
   track would have squeezed every other one. */
.t-desc {
  display: block;
  margin-top: 3px;
  max-width: 62ch;
  font-weight: 400;
  font-size: .875rem;
  line-height: 1.35;
  color: var(--ink-40);
}

/* ---- Index, grid view ---------------------------------------------
   The same ten rows, re-laid as tiles. script.js injects one <img> per
   row from its data-preview and toggles .is-grid here; nothing about the
   index is duplicated to support it.

   In list view the thumbnail is display:none, which removes it from the
   row's grid entirely — the five text columns keep their track sizes and
   the list is untouched. */
.t-thumb { display: none; }

/* auto-fill at a small track rather than a fixed three columns: at 1824px
   three tiles meant 600px each, which is a poster per project rather than
   an index. 240px tracks give six across at full width and step down on
   their own, so the tile stays the same size at every width instead of
   stretching to fill the row. */
.table.is-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
  border-top: 0;
  /* dense back-fills the hole a filtered-out row would otherwise leave
     beside the wide feature tile. */
  grid-auto-flow: dense;
  align-items: start;
}
.table.is-grid .table-head { display: none; }

.table.is-grid .table-row {
  display: flex;
  flex-direction: column;
  /* The list rule sets align-items:center to centre five columns on one
     baseline. Turned on its side by flex-direction:column that becomes
     HORIZONTAL centring, which had every tile's copy centred. */
  align-items: stretch;
  gap: 0;
  padding: 0 0 var(--s-4);
  border: 0;
  border-radius: 12px;
  overflow: hidden;
  background: var(--paper-2);
}
.table.is-grid .table-row:hover { background: var(--paper-2); }

.table.is-grid .t-thumb {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--rule);
}
.table.is-grid .t-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .5s cubic-bezier(.2, .7, .3, 1);
}
.table.is-grid .table-row:hover .t-thumb img,
.table.is-grid .table-row:focus-visible .t-thumb img { transform: scale(1.04); }

/* Title and client only. At 240px wide a tile cannot carry five fields
   without becoming a paragraph, and the discipline list is the first thing
   that stops earning its space — it is still in the list view, which is
   where anyone comparing projects will be. */
.table.is-grid .t-id     { display: none; }
.table.is-grid .t-focus  { display: none; }
/* The description belongs to the list, where the title column is 2.2fr.
   In a 240px tile it turns a three-line card into a seven-line one. */
.table.is-grid .t-desc   { display: none; }
.table.is-grid .t-title  { padding: 10px 12px 0; font-size: .92rem; line-height: 1.25; }
.table.is-grid .t-client { padding: 4px 12px 0; color: var(--ink-40); }
.table.is-grid .t-year   { padding: 2px 12px 0; }
.table.is-grid .table-row { padding-bottom: 10px; }

/* The one wide tile that keeps this a bento rather than a uniform card
   grid. Which row gets it is decided in script.js, not by :first-child. */
.table.is-grid .is-feature { grid-column: span 2; }
.table.is-grid .is-feature .t-thumb { aspect-ratio: 16 / 9; }

@media (max-width: 700px) {
  .table.is-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .table.is-grid .is-feature { grid-column: 1 / -1; }
}
@media (max-width: 420px) {
  .table.is-grid { grid-template-columns: minmax(0, 1fr); }
  .table.is-grid .is-feature .t-thumb { aspect-ratio: 4 / 3; }
}
@media (prefers-reduced-motion: reduce) {
  .table.is-grid .t-thumb img { transition: none; }
}
.table-row .t-focus, .table-row .t-client { color: var(--ink-70); }
.table-row .t-year { text-align: right; }

@media (max-width: 860px) {
  .table-head { display: none; }
  .table-row {
    grid-template-columns: 1fr auto;
    row-gap: var(--s-1);
    padding-block: var(--s-4);
  }
  .table-row .t-id { grid-column: 1; }
  .table-row .t-year { grid-column: 2; grid-row: 1; }
  .table-row .t-title { grid-column: 1 / -1; font-size: var(--t-h3); }
  .table-row .t-focus, .table-row .t-client { grid-column: 1 / -1; }
}

/* View toggle */
.toggle { display: flex; gap: var(--s-1); align-items: center; }
.toggle a, .toggle button {
  padding: 6px 10px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink-40);
  transition: all var(--dur-fast) var(--ease);
}
.toggle a[aria-current="true"], .toggle button[aria-pressed="true"] {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}

/* ---- 11. PROJECT PAGE ------------------------------------ */

.proj-hero {
  padding-block: var(--s-7) var(--s-6);
}
.proj-title { margin-block: var(--s-4) var(--s-5); }

.credits {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gutter) var(--gutter);
  padding-block: var(--s-5);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.credit-key { color: var(--ink-40); margin-bottom: var(--s-2); }
.credit-val { font-size: var(--t-small); }
@media (max-width: 760px) { .credits { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.media-full {
  background: var(--paper-2);
  border: 1px solid var(--rule);
  aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center;
  margin-block: var(--s-7);
  overflow: hidden;
}
.media-full img { width: 100%; height: 100%; object-fit: cover; }

.media-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gutter); margin-block: var(--s-7); }
.media-pair > div {
  background: var(--paper-2); border: 1px solid var(--rule);
  aspect-ratio: 4 / 5;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
@media (max-width: 760px) { .media-pair { grid-template-columns: 1fr; } }

.proj-text {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gutter);
  margin-block: var(--s-7);
}
.proj-text .pt-label { grid-column: span 3; color: var(--ink-40); }
.proj-text .pt-body  { grid-column: span 7; }
@media (max-width: 860px) {
  .proj-text .pt-label, .proj-text .pt-body { grid-column: 1 / -1; }
}

/* Case-study additions: keep the editorial system, add only the controls and
   evidence treatments needed by product work. */
.case-cta {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--s-4);
  border: 1px solid var(--ink);
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.case-cta:hover,
.case-cta:focus-visible { color: var(--paper); background: var(--ink); }
.case-sources a,
.case-source-link {
  border-bottom: 1px solid var(--rule);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.case-sources a:hover,
.case-source-link:hover { color: var(--accent); border-color: var(--accent); }
.case-note {
  max-width: 78ch;
  margin-top: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border-left: 2px solid var(--accent);
  background: var(--accent-soft);
}
.case-quote {
  max-width: 24ch;
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 3.6rem);
  line-height: .98;
  letter-spacing: -.02em;
}
@media (max-width: 760px) {
  .case-note { padding: var(--s-4); }
  .case-quote { font-size: clamp(2rem, 10vw, 3rem); }
}

/* ---- ASSET CLEARANCE CASE STUDY ------------------------- */

/* The case study retints the accent blue, but this used to sit on <body>, so
   it also recoloured the fixed chrome — the footer status dot and the active
   nav link went blue on this page only. Custom properties inherit, so scoping
   the declaration to <main> keeps the case study blue while the site chrome
   stays on the portfolio vermillion. */
.asset-clearance-case main {
  --accent: #315f85;
  --accent-soft: #dce9f1;
  --accent-text: #2b5474;   /* 4.7:1 on --paper, for 11px accent text */
}

/* The retint has to be themed too, and for the same reason the greys did:
   these are literals, so on the dark ground the accent text measured
   2.24:1 and the soft ground stayed a near-white panel in the middle of a
   dark page. Hue and saturation are held from the light values; only
   lightness moves, so it is recognisably the same blue. */
@media (prefers-color-scheme: dark) {
  :root:not(.theme-light) .asset-clearance-case main {
    --accent: #366891;        /* 3.03:1, non-text */
    --accent-soft: #18303E;   /* 1.31:1 ground */
    --accent-text: #4B93CA;   /* 4.52:1 over a dot */
  }
}
:root.theme-dark .asset-clearance-case main {
    --accent: #366891;
    --accent-soft: #18303E;
    --accent-text: #4B93CA;
}

.asset-clearance-case .proj-title {
  max-width: none;
}

.asset-clearance-case .proj-hero .lead {
  max-width: 54ch;
}

.clearance-launch {
  position: relative;
  width: 100%;
  height: min(760px, calc(100svh - 24px));
  min-height: 660px;
  overflow: hidden;
  isolation: isolate;
  background: #0a0f1d;
  color: #f5f6f8;
}

.clearance-flow-bg {
  position: absolute;
  z-index: 0;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: .82;
  transform: scale(1.035);
}

.clearance-card-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform-origin: center bottom;
  animation: clearance-bg-drift 10s ease-in-out infinite alternate;
}

.clearance-flow-shell {
  position: relative;
  z-index: 1;
  display: grid;
  height: 100%;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.clearance-flow-bar {
  display: grid;
  min-height: 78px;
  padding: 14px clamp(18px, 2.5vw, 34px);
  grid-template-columns: minmax(180px, .72fr) minmax(480px, 1.28fr);
  gap: var(--s-4);
  align-items: center;
  border-bottom: 1px solid rgba(245, 246, 248, .16);
  background: rgba(8, 12, 23, .72);
  backdrop-filter: blur(20px);
}

.clearance-flow-brand {
  display: flex;
  min-width: 0;
  align-items: baseline;
  gap: 12px;
}

.clearance-flow-brand strong { font-size: .9rem; font-weight: 600; }
.clearance-flow-brand span { color: rgba(245, 246, 248, .5); font-size: .72rem; }

.clearance-flow-menu {
  display: grid;
  min-width: 0;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
}

.clearance-flow-menu button {
  display: inline-flex;
  min-height: 46px;
  padding: 10px 14px;
  align-items: center;
  justify-content: center;
  gap: 9px;
  border: 1px solid rgba(245, 246, 248, .14);
  border-radius: 999px;
  color: rgba(245, 246, 248, .66);
  font-size: .8rem;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.clearance-flow-menu button span {
  color: rgba(245, 246, 248, .36);
  font-family: var(--font-mono);
  font-size: .68rem;
}

.clearance-flow-menu button:hover { border-color: rgba(245, 246, 248, .38); color: #fff; }
.clearance-flow-menu button[aria-selected="true"] {
  border-color: #f5f6f8;
  background: #f5f6f8;
  color: #11151e;
}
.clearance-flow-menu button[aria-selected="true"] span { color: #666b75; }

.clearance-flow-viewport {
  min-height: 0;
  overflow: hidden;
}

.clearance-flow-track {
  display: flex;
  width: 100%;
  height: 100%;
  transform: translate3d(0, 0, 0);
  transition: transform 520ms cubic-bezier(.22, 1, .36, 1);
  will-change: transform;
}

.clearance-flow-slide {
  position: relative;
  flex: 0 0 100%;
  width: 100%;
  min-width: 0;
  height: 100%;
  overflow: hidden;
}

.clearance-flow-overview {
  display: grid;
  padding: clamp(34px, 5vw, 68px);
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: var(--s-5);
  align-items: center;
}

.clearance-flow-copy {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  flex-direction: column;
}

.clearance-flow-copy .label,
.clearance-repo-intro .label,
.clearance-workspace-head .label { color: rgba(245, 246, 248, .54); }

.clearance-flow-copy h2,
.clearance-repo-intro h2,
.clearance-workspace-head h2 {
  margin: var(--s-3) 0 0;
  font-family: var(--font-display);
  font-size: clamp(4.4rem, 7vw, 7.8rem);
  font-weight: 400;
  line-height: .86;
  letter-spacing: -.045em;
}

.clearance-flow-copy h2 em { font-weight: 400; }

.clearance-primary-action {
  display: inline-flex;
  min-height: 48px;
  margin-top: var(--s-5);
  padding: 12px 22px;
  align-items: center;
  justify-content: center;
  border: 1px solid #f5f6f8;
  border-radius: 999px;
  background: #f5f6f8;
  color: #11151e;
  font-size: .9rem;
  font-weight: 600;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.clearance-primary-action:hover { background: #fff; transform: translateY(-2px); }
.clearance-primary-action:focus-visible { outline: 2px solid #fff; outline-offset: 4px; }
.clearance-primary-action:disabled { cursor: wait; opacity: .58; transform: none; }

.clearance-flow-preview {
  position: absolute;
  z-index: 1;
  right: -5%;
  bottom: -17%;
  width: min(66%, 900px);
}

.clearance-flow-preview img,
.clearance-workspace-preview img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 13px 13px 0 0;
  box-shadow: 0 -24px 80px rgba(4, 8, 18, .38);
}

.clearance-flow-repo {
  display: grid;
  padding: clamp(28px, 4vw, 56px) clamp(28px, 5vw, 72px);
  grid-template-columns: minmax(0, .88fr) minmax(360px, .72fr);
  gap: clamp(36px, 7vw, 100px);
  align-items: center;
}

.clearance-repo-intro { max-width: 600px; }
.clearance-repo-intro h2 { font-size: clamp(4rem, 6.2vw, 7rem); }
.clearance-repo-intro > p:last-child {
  max-width: 46ch;
  margin-top: var(--s-4);
  color: rgba(245, 246, 248, .66);
  font-size: 1rem;
  line-height: 1.5;
}

.clearance-repo-card,
.clearance-server-card {
  width: min(100%, 440px);
  justify-self: end;
  padding: clamp(24px, 3vw, 36px);
  border-radius: 18px;
  background: #f5f4ef;
  color: #151820;
  box-shadow: 0 26px 90px rgba(0, 0, 0, .34);
}

.clearance-repo-wordmark {
  font-family: var(--font-mono);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .22em;
}

.clearance-repo-card h3,
.clearance-server-card h3 {
  margin: var(--s-3) 0 var(--s-5);
  font-family: var(--font-display);
  font-size: clamp(2.3rem, 3.6vw, 3.7rem);
  font-weight: 400;
  line-height: .95;
}

.clearance-repo-card label {
  display: grid;
  gap: 7px;
  margin-top: 14px;
  color: #5d6068;
  font-size: .74rem;
  font-weight: 500;
}

.clearance-repo-card input {
  width: 100%;
  height: 50px;
  padding: 0 14px;
  border: 1px solid #d2d2ce;
  border-radius: 8px;
  background: #fff;
  color: #151820;
  font: inherit;
}

.clearance-repo-card input:focus { border-color: #151820; outline: 2px solid rgba(21, 24, 32, .13); outline-offset: 2px; }
.clearance-repo-card .clearance-primary-action,
.clearance-server-card .clearance-primary-action {
  width: 100%;
  margin-top: var(--s-4);
  border-color: #151820;
  border-radius: 8px;
  background: #CEA281;
  color: #fff;
}

.clearance-login-message {
  min-height: 2.4em;
  margin-top: 12px;
  /* The login card is LIGHT (#f5f4ef), not the dark panel around it — this
     sits on a knocked-out form, so the muted grey has to go darker, not
     lighter. Measured 4.63:1. */
  color: #5c626b;
  font-size: .7rem;
  line-height: 1.4;
}
.clearance-login-message.is-error { color: #a83226; }
.clearance-login-message.is-checking { color: #2e5f86; }

.clearance-server-card[hidden] { display: none; }
.clearance-server-card .label { color: #5f6f64; }
.clearance-server-card dl { margin: 0; border-top: 1px solid #d7d6d1; }
.clearance-server-card dl div {
  display: flex;
  padding: 12px 0;
  justify-content: space-between;
  gap: 18px;
  border-bottom: 1px solid #d7d6d1;
}
.clearance-server-card dt { color: #71747b; font-size: .76rem; }
.clearance-server-card dd { margin: 0; font-size: .76rem; font-weight: 600; }

.clearance-flow-workspace {
  display: flex;
  padding: clamp(28px, 4vw, 52px) clamp(28px, 5vw, 72px) 0;
  flex-direction: column;
}

.clearance-workspace-head {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-5);
}
.clearance-workspace-head h2 { font-size: clamp(3.6rem, 5vw, 5.7rem); }
.clearance-workspace-head .clearance-primary-action { margin: 0 0 8px; }
.clearance-workspace-preview {
  position: relative;
  width: min(88%, 1080px);
  margin: var(--s-5) auto 0;
}

.clearance-flow-controls {
  display: flex;
  min-height: 62px;
  padding: 10px clamp(18px, 2.5vw, 34px);
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  border-top: 1px solid rgba(245, 246, 248, .16);
  background: rgba(8, 12, 23, .72);
  backdrop-filter: blur(20px);
}

.clearance-flow-controls > .label { color: rgba(245, 246, 248, .48); }
.clearance-flow-controls > div { display: flex; gap: 8px; }
.clearance-flow-controls button {
  min-height: 40px;
  padding: 9px 15px;
  border: 1px solid rgba(245, 246, 248, .22);
  border-radius: 999px;
  color: rgba(245, 246, 248, .82);
  font-size: .76rem;
}
.clearance-flow-controls button:hover:not(:disabled) { border-color: #fff; color: #fff; }
.clearance-flow-controls button:disabled { cursor: default; opacity: .3; }

#clearance-app { scroll-margin-top: 72px; }
#clearance-app:focus { outline: none; }
.clario-embed-shell {
  width: 100%;
  height: min(900px, 78vw);
  min-height: 680px;
  overflow: hidden;
  border: 1px solid #272c34;
  background: #080a0f;
}
.clario-embed-shell iframe { display: block; width: 100%; height: 100%; border: 0; background: #080a0f; }
.clario-embed-caption { display: flex; justify-content: space-between; gap: var(--s-4); }
.clario-embed-caption a { flex: 0 0 auto; border-bottom: 1px solid currentColor; }

.clearance-flow-slide[aria-hidden="true"] { pointer-events: none; }
.clearance-flow-slide[aria-hidden="false"] { pointer-events: auto; }

@media (prefers-reduced-motion: reduce) {
  .clearance-flow-track { transition: none; }
}

/* Sized against the card, not the viewport.
   Every measurement below used to be a vw clamp, which was right while this
   card spanned all twelve columns: the card was the viewport. Pairing it with
   Fleet halved it to six, the box became 896x504 -- but the vw clamps still
   read the 2560px screen and stayed pinned at their maximums, so 739px of
   composition was laid into a 504px box and "Protect production." got sliced
   off by the overflow:hidden. cqw makes the type track the card through any
   future layout change instead of needing a new breakpoint each time. */
.clearance-card-media {
  container-type: inline-size;
  isolation: isolate;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: stretch;
  justify-content: stretch;
  background: #080b11;
  color: #f5f6f8;
}

.clearance-card-copy {
  position: relative;
  z-index: 2;
  display: flex;
  width: auto;
  /* Measured at 736px: the composition came to 412px inside a 414px box.
     It fitted, but two pixels is not headroom -- a fallback font rendering
     slightly taller, or a browser zoom, and it clips again. Trimmed about a
     tenth out of the vertical rhythm so there is room to absorb that. */
  padding: clamp(16px, 4.4cqw, 52px);
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  background:
    linear-gradient(90deg, rgba(4, 7, 12, .82) 0%, rgba(4, 7, 12, .62) 100%),
    url("media/asset-clearance/clario-hero-dileepa-nipun.webp") 22% 52% / cover no-repeat;
  color: #f5f6f8;
  text-align: left;
}

.clearance-card-logo {
  display: block;
  margin-bottom: clamp(12px, 3cqw, 36px);
  font-family: var(--font-sans);
  font-size: clamp(1rem, 2.6cqw, 1.9rem);
  font-weight: 600;
  letter-spacing: -.04em;
}
.clearance-card-logo span { color: #f26b38; }
.clearance-card-copy > .label { color: rgba(245, 246, 248, .58); }
.clearance-card-copy strong {
  margin-top: clamp(6px, 1.5cqw, 18px);
  font-family: var(--font-sans);
  font-size: clamp(1.4rem, 5.6cqw, 3.7rem);
  font-weight: 500;
  line-height: .9;
  letter-spacing: -.042em;
  word-spacing: .08em;
  font-kerning: normal;
  font-feature-settings: "kern" 1;
  text-shadow: 0 2px 24px rgba(0, 0, 0, .28);
}
.clearance-card-copy em { color: rgba(245, 246, 248, .66); font-style: normal; font-weight: 500; }
.clearance-card-steps { margin-top: clamp(10px, 2.4cqw, 26px); color: rgba(245, 246, 248, .56); }
.clearance-card-cta {
  display: inline-flex;
  min-height: 42px;
  margin-top: clamp(12px, 2.2cqw, 26px);
  padding: 10px 18px;
  align-items: center;
  gap: 12px;
  border-radius: 8px;
  background: #f4f4f1;
  color: #0b0e14;
  font-size: .82rem;
  font-weight: 600;
}

.clearance-card-window {
  position: relative;
  z-index: 1;
  min-width: 0;
  overflow: hidden;
  border-left: 1px solid rgba(255, 255, 255, .1);
  background:
    radial-gradient(circle at 20% 18%, rgba(58, 91, 160, .28), transparent 38%),
    #0a0d13;
}

.clearance-card-window img {
  position: absolute;
  top: 11%;
  left: 7%;
  display: block;
  width: 172%;
  max-width: none;
  height: auto;
  object-fit: contain;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 9px;
  box-shadow: -18px 24px 70px rgba(0, 0, 0, .52);
  animation: clearance-window-float 7s ease-in-out infinite;
}

.clearance-card-browser {
  position: absolute;
  z-index: 2;
  top: 8%;
  left: 7%;
  display: flex;
  width: 172%;
  height: 28px;
  padding: 0 10px;
  align-items: center;
  gap: 5px;
  border: 1px solid rgba(255, 255, 255, .12);
  border-bottom: 0;
  border-radius: 9px 9px 0 0;
  background: #171b23;
}
.clearance-card-browser span { width: 5px; height: 5px; border-radius: 50%; background: rgba(255, 255, 255, .32); }
.clearance-card-browser b { margin-left: 7px; color: rgba(255, 255, 255, .34); font-family: var(--font-mono); font-size: .48rem; font-weight: 400; }

.card:hover .clearance-card-window img { animation-play-state: paused; transform: translateY(-7px) scale(1.01); }

/* The animated screen sits exactly where the still does, so the swap has
   no shift in it. Every position value is restated from the still above,
   because the base .card-vid rule sets inset:0 / width:100% / height:100%
   to cover a whole card — here that stretched the clip across the copy
   column and buried the Clario logo and headline.

   Two classes, not one. .card-vid is declared several thousand lines
   later in this file; at equal specificity it wins on order alone, so a
   single-class override here is silently ignored. */
.clearance-card-window .clearance-card-vid {
  position: absolute;
  inset: auto;
  top: 11%;
  left: 7%;
  width: 172%;
  /* The still carries max-width:none for the same reason: the global media
     reset caps img and video at 100%, which clipped this to the window
     width and left it 489px short of the frame it is replacing. */
  max-width: none;
  height: auto;
  aspect-ratio: 960 / 540;
  object-fit: cover;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 9px;
  box-shadow: -18px 24px 70px rgba(0, 0, 0, .52);
}

@keyframes clearance-bg-drift {
  from { transform: scale(1.01) translate3d(0, 0, 0); }
  to { transform: scale(1.055) translate3d(0, -1.25%, 0); }
}

@keyframes clearance-window-float {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(0, -9px, 0); }
}

.asset-exhibit .clearance-preview {
  border-radius: 0;
  box-shadow: none;
}

.asset-hero {
  position: relative;
  min-height: min(720px, calc(100dvh - 48px));
  overflow: hidden;
  isolation: isolate;
  background: #8fb9d7;
}
.asset-cloud-canvas,
.asset-hero-scrim {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.asset-cloud-canvas { display: block; }
.asset-hero-scrim {
  z-index: 1;
  background: linear-gradient(90deg, rgba(19, 36, 52, .62), rgba(19, 36, 52, .18) 58%, rgba(19, 36, 52, 0));
  pointer-events: none;
}
.asset-hero-copy {
  position: relative;
  z-index: 2;
  display: flex;
  min-height: inherit;
  flex-direction: column;
  justify-content: flex-end;
  align-items: flex-start;
  padding-top: var(--s-7);
  padding-bottom: calc(var(--s-7) + 48px);
  color: #f5f5f1;
  text-shadow: 0 1px 18px rgba(20, 40, 55, .18);
}
.asset-hero-copy h1 {
  max-width: 9ch;
  margin-top: var(--s-3);
  font-family: var(--font-display);
  font-size: clamp(5rem, 12vw, 11.5rem);
  font-weight: 400;
  line-height: .78;
  letter-spacing: -.055em;
}
.asset-hero-copy > p:last-child {
  max-width: 24ch;
  margin-top: var(--s-5);
  font-size: clamp(1.25rem, 2.1vw, 2rem);
  line-height: 1.06;
}
.asset-kicker { letter-spacing: .08em; }
.asset-summary { padding-top: var(--s-6); }

.asset-story,
.asset-system-section,
.asset-decision-section,
.asset-preview-section,
.asset-match-section,
.asset-ai-section,
.asset-evidence-section,
.asset-validation-section,
.asset-next-section {
  padding-block: var(--s-8);
}

.asset-story {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gutter);
}
.asset-story-head { grid-column: span 5; }
.asset-story-head h2,
.asset-section-copy h2,
.asset-ai-head h2,
.asset-evidence-copy h2,
.asset-next-copy h2 {
  margin-top: var(--s-3);
  font-family: var(--font-display);
  font-size: clamp(3rem, 6.4vw, 6.8rem);
  font-weight: 400;
  line-height: .9;
  letter-spacing: -.035em;
}
.asset-story-body {
  grid-column: 7 / span 5;
  padding-top: var(--s-6);
}
.asset-story-body .body { margin-top: var(--s-5); }
.asset-story-body blockquote {
  margin-top: var(--s-6);
  padding-left: var(--s-5);
  border-left: 2px solid var(--accent);
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 3vw, 2.8rem);
  line-height: 1.02;
}

.asset-photo-sequence {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: var(--s-2);
  padding-bottom: var(--s-8);
}
.asset-photo {
  aspect-ratio: 4 / 5;
  min-width: 0;
  overflow: hidden;
  background: var(--paper-2);
}
.asset-photo-wide { aspect-ratio: 8 / 5; }
.asset-photo img { width: 100%; height: 100%; object-fit: cover; }
.asset-photo-note {
  grid-column: 1 / -1;
  max-width: 64ch;
  margin-top: var(--s-2);
  color: var(--ink-40);
}

.asset-section-copy {
  max-width: 870px;
  margin-bottom: var(--s-6);
}
.asset-section-copy .body,
.asset-ai-head .body,
.asset-evidence-copy .body,
.asset-next-copy .body {
  max-width: 62ch;
  margin-top: var(--s-5);
}
.asset-system-grid {
  display: grid;
  grid-template-columns: 1fr 1.35fr 1fr;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.asset-system-grid article {
  min-height: 260px;
  padding: var(--s-5);
  border-right: 1px solid var(--rule);
}
.asset-system-grid article:last-child { border-right: 0; }
.asset-system-grid .is-focus { background: var(--accent-soft); }
.asset-system-grid h3 {
  margin: var(--s-6) 0 var(--s-3);
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 4vw, 4rem);
  font-weight: 400;
  line-height: 1;
}
.asset-system-grid p:last-child { max-width: 30ch; }

.asset-decision-example {
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: var(--s-7);
  align-items: end;
  padding: var(--s-6);
  border: 1px solid #b8cad7;
  background: #e9f1f5;
}
.asset-decision-title {
  max-width: 16ch;
  margin: var(--s-4) 0;
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 4vw, 4.6rem);
  line-height: .95;
}
.asset-timeline { display: grid; gap: var(--s-5); }
.asset-timeline-row {
  display: grid;
  grid-template-columns: 80px minmax(0, 1fr) 118px;
  gap: var(--s-3);
  align-items: center;
}
.asset-timeline-track {
  position: relative;
  height: 18px;
  background: rgba(49, 95, 133, .14);
}
.asset-timeline-campaign,
.asset-timeline-licence {
  position: absolute;
  inset-block: 0;
  left: 0;
  background: var(--accent);
}
.asset-timeline-campaign { width: 100%; }
.asset-timeline-licence { width: 43%; background: #b24b3c; }

.clearance-preview {
  overflow: hidden;
  border: 1px solid #262a2f;
  border-radius: 14px;
  background: #15171a;
  color: #f0f0ec;
  box-shadow: 0 32px 80px rgba(45, 66, 82, .2);
  font-family: var(--font-sans);
}
.clearance-preview-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  min-height: 84px;
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid #30343a;
  background: #1c1f23;
}
.clearance-wordmark { font-size: 1.1rem; font-weight: 600; }
.clearance-sub { margin-top: 3px; color: #9da4ad; font-size: .8rem; }
.clearance-window { text-align: right; }
.clearance-window span { display: block; color: #9da4ad; font-size: .72rem; }
.clearance-window strong { display: block; margin-top: 4px; font-size: .84rem; }
.clearance-preview-body {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(300px, .75fr);
  min-height: 620px;
}
.clearance-library { padding: var(--s-5); }
.clearance-library-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: var(--s-4);
}
.clearance-library-head > div { display: grid; gap: 4px; }
.clearance-library-head span { color: #8e959e; font-size: .75rem; }
.clearance-library-head .clearance-progress { color: #c8cdd3; }
.clearance-filters {
  display: flex;
  gap: 6px;
  margin-bottom: var(--s-4);
  overflow-x: auto;
  scrollbar-width: none;
}
.clearance-filters::-webkit-scrollbar { display: none; }
.clearance-filter {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 32px;
  padding: 6px 10px;
  border: 1px solid #353a41;
  border-radius: 7px;
  background: transparent;
  color: #aeb4bc;
  font-size: .72rem;
  white-space: nowrap;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.clearance-filter span {
  color: #858d96;
  font-family: var(--font-mono);
  font-size: .62rem;
}
.clearance-filter:hover { border-color: #69727c; color: #f0f0ec; }
.clearance-filter:focus-visible { outline: 2px solid #91b8d4; outline-offset: 2px; }
.clearance-filter.is-active { border-color: #777f89; background: #30353b; color: #f0f0ec; }
.clearance-assets {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-3);
}
.clearance-asset {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s-3);
  align-items: start;
  padding: 8px;
  border: 1px solid #30343a;
  border-radius: 10px;
  background: #1b1e22;
  color: inherit;
  text-align: left;
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.clearance-asset:hover { border-color: #59616b; transform: translateY(-2px); }
.clearance-asset:active { transform: translateY(-1px); }
.clearance-asset:focus-visible { outline: 2px solid #91b8d4; outline-offset: 3px; }
.clearance-asset.is-selected { border-color: #91b8d4; background: #232a31; }
.clearance-asset[hidden] { display: none; }
.clearance-asset img {
  grid-column: 1 / -1;
  width: 100%;
  aspect-ratio: 4 / 5;
  border-radius: 6px;
  object-fit: cover;
}
.clearance-asset-copy { display: grid; gap: 3px; }
.clearance-asset-copy strong { font-size: .82rem; font-weight: 500; }
.clearance-asset-copy span { color: #8e959e; font-size: .7rem; }
.clearance-status {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: .68rem;
  font-weight: 600;
  white-space: nowrap;
}
.clearance-status.is-risk { color: #ffd6d1; background: #552a27; }
.clearance-status.is-clear { color: #cfeada; background: #244335; }
.clearance-status.is-review { color: #f4dfb5; background: #493b24; }
.clearance-inspector {
  display: flex;
  flex-direction: column;
  padding: var(--s-5);
  border-left: 1px solid #30343a;
  background: #202328;
}
.clearance-inspector-head {
  display: flex;
  justify-content: space-between;
  align-items: start;
  gap: var(--s-3);
  padding-bottom: var(--s-5);
  border-bottom: 1px solid #353a41;
}
.clearance-inspector-label { display: block; margin-bottom: 5px; color: #8e959e; font-size: .72rem; }
.clearance-inspector-head strong { font-size: 1.05rem; }
.clearance-facts { display: grid; margin-top: var(--s-4); }
.clearance-facts div { padding: var(--s-3) 0; border-bottom: 1px solid #353a41; }
.clearance-facts dt { color: #8e959e; font-size: .7rem; }
.clearance-facts dd { margin-top: 6px; font-size: .85rem; }
.clearance-decision {
  margin-top: auto;
  padding: var(--s-4);
  border-radius: 10px;
  background: #15171a;
}
.clearance-decision span { color: #8e959e; font-size: .7rem; }
.clearance-decision strong { display: block; margin-top: var(--s-2); font-size: .95rem; }
.clearance-decision p { margin-top: var(--s-2); color: #b7bdc5; font-size: .76rem; line-height: 1.45; }

.asset-match-grid {
  display: grid;
  grid-template-columns: 1.2fr .8fr;
  gap: var(--s-2);
}
.asset-match-grid article {
  min-height: 230px;
  padding: var(--s-5);
  border: 1px solid var(--rule);
}
.asset-match-grid article:nth-child(1),
.asset-match-grid article:nth-child(4) { background: var(--accent-soft); }
.asset-match-grid h3,
.asset-ai-layout h3,
.asset-validation-grid h3 {
  margin: var(--s-5) 0 var(--s-3);
  font-family: var(--font-display);
  font-size: clamp(2rem, 3vw, 3.4rem);
  font-weight: 400;
  line-height: 1;
}
.asset-match-rank { color: var(--ink-40); font-family: var(--font-mono); font-size: var(--t-label); text-transform: uppercase; }
.asset-match-grid p { max-width: 42ch; }

.asset-ai-head { max-width: 900px; margin-bottom: var(--s-6); }
.asset-ai-layout {
  display: grid;
  grid-template-columns: 1.25fr .75fr;
  grid-template-rows: repeat(2, minmax(180px, auto));
  gap: var(--s-2);
}
.asset-ai-layout > div {
  padding: var(--s-5);
  border: 1px solid var(--rule);
}
.asset-ai-layout > div:first-child {
  grid-row: 1 / 3;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  background: var(--accent-soft);
}
.asset-ai-layout p { max-width: 44ch; }

.asset-evidence-section {
  display: grid;
  grid-template-columns: 1.2fr .8fr;
  gap: var(--s-7);
  align-items: end;
}
.asset-evidence-copy h2 { max-width: 10ch; }
.asset-evidence-links { display: grid; gap: var(--s-2); }
.asset-evidence-links a {
  display: grid;
  gap: var(--s-5);
  padding: var(--s-5);
  border: 1px solid var(--rule);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.asset-evidence-links a:hover { color: var(--accent); border-color: var(--accent); transform: translateY(-2px); }
.asset-evidence-links strong { font-family: var(--font-display); font-size: clamp(1.8rem, 3vw, 3rem); font-weight: 400; }

.asset-validation-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-2);
}
.asset-validation-grid article {
  min-height: 240px;
  padding: var(--s-5);
  border-top: 1px solid var(--rule);
}
.asset-validation-grid article:nth-child(3),
.asset-validation-grid article:nth-child(4) { background: var(--paper-2); }
.asset-validation-grid p { max-width: 44ch; }

.asset-next-section { border-top: 1px solid var(--rule); }
.asset-next-copy { max-width: 940px; }

@media (max-width: 960px) {
  .clearance-launch { height: 720px; min-height: 660px; }
  .clearance-flow-bar { grid-template-columns: 1fr; gap: 10px; }
  .clearance-flow-brand { justify-content: space-between; }
  .clearance-flow-overview { grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr); }
  .clearance-flow-copy h2 { font-size: clamp(4rem, 8vw, 6rem); }
  .clearance-flow-preview { right: -16%; width: 76%; }
  .clearance-flow-repo { grid-template-columns: minmax(0, 1fr) minmax(340px, .82fr); gap: 28px; }
  .asset-story-head,
  .asset-story-body { grid-column: 1 / -1; }
  .asset-story-body { padding-top: 0; }
  .asset-photo-sequence { grid-template-columns: repeat(2, 1fr); }
  .asset-photo-wide { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
  .asset-system-grid { grid-template-columns: 1fr; }
  .asset-system-grid article { min-height: 210px; border-right: 0; border-bottom: 1px solid var(--rule); }
  .asset-system-grid article:last-child { border-bottom: 0; }
  .asset-decision-example,
  .asset-evidence-section { grid-template-columns: 1fr; }
  .clearance-preview-body { grid-template-columns: 1fr; }
  .clearance-inspector { min-height: 480px; border-left: 0; border-top: 1px solid #30343a; }
}

@media (max-width: 680px) {
  .clearance-launch { height: 780px; min-height: 740px; }
  .clearance-flow-bar { min-height: 112px; padding: 12px 14px 10px; }
  .clearance-flow-brand strong { font-size: .82rem; }
  .clearance-flow-brand span { font-size: .66rem; }
  .clearance-flow-menu {
    grid-template-columns: repeat(3, minmax(124px, 1fr));
    overflow-x: auto;
    scrollbar-width: none;
  }
  .clearance-flow-menu::-webkit-scrollbar { display: none; }
  .clearance-flow-menu button { min-height: 42px; padding: 8px 11px; font-size: .72rem; white-space: nowrap; }
  .clearance-flow-overview { display: block; padding: 30px 20px; }
  .clearance-flow-copy h2 { font-size: clamp(3.65rem, 16vw, 5rem); }
  .clearance-flow-preview { right: -54%; bottom: -4%; width: 164%; }
  .clearance-flow-repo { display: block; padding: 22px 20px 28px; overflow-y: auto; }
  .clearance-repo-intro h2 { font-size: clamp(3.2rem, 14vw, 4rem); }
  .clearance-repo-intro > p:last-child { margin-top: var(--s-3); font-size: .86rem; }
  .clearance-repo-card,
  .clearance-server-card { width: 100%; margin-top: 20px; padding: 20px; justify-self: auto; }
  .clearance-repo-card h3,
  .clearance-server-card h3 { font-size: 2.45rem; }
  .clearance-repo-card input { height: 46px; }
  .clearance-flow-workspace { padding: 24px 20px 0; }
  .clearance-workspace-head { align-items: flex-start; }
  .clearance-workspace-head h2 { font-size: 3.25rem; }
  .clearance-workspace-head .clearance-primary-action { min-height: 44px; padding-inline: 17px; }
  .clearance-workspace-preview { width: 146%; margin: 36px 0 0 -7%; }
  .clearance-flow-controls { min-height: 58px; padding: 8px 14px; }
  .clearance-flow-controls button { min-height: 42px; }
  .asset-hero { min-height: 560px; }
  .asset-hero-copy { padding-block: var(--s-6); }
  .asset-hero-copy { padding-bottom: calc(var(--s-6) + 44px); }
  .asset-hero-copy h1 { font-size: clamp(4.3rem, 22vw, 7rem); line-height: .84; }
  .asset-hero-copy > p:last-child { font-size: 1.25rem; }
  .asset-story,
  .asset-system-section,
  .asset-decision-section,
  .asset-preview-section,
  .asset-match-section,
  .asset-ai-section,
  .asset-evidence-section,
  .asset-validation-section,
  .asset-next-section { padding-block: var(--s-7); }
  .asset-photo-sequence { grid-template-columns: 1fr; }
  .asset-photo-wide { grid-column: auto; aspect-ratio: 4 / 5; }
  .asset-decision-example { padding: var(--s-4); }
  .asset-timeline-row { grid-template-columns: 64px minmax(0, 1fr); }
  .asset-timeline-row > span:last-child { grid-column: 2; }
  .clearance-preview { border-radius: 0; }
  .clario-embed-shell { height: 760px; min-height: 760px; }
  .clario-embed-caption { align-items: flex-start; flex-direction: column; }
  .clearance-preview-top { align-items: flex-start; padding: var(--s-4); }
  .clearance-window strong { max-width: 16ch; }
  .clearance-library { padding: var(--s-4); overflow-x: auto; scrollbar-width: none; }
  .clearance-library::-webkit-scrollbar { display: none; }
  .clearance-library-head { align-items: start; gap: var(--s-3); }
  .clearance-library-head .clearance-progress { max-width: 12ch; text-align: right; }
  .clearance-filter { min-height: 40px; padding-inline: 12px; }
  .clearance-assets { grid-template-columns: repeat(3, minmax(210px, 1fr)); }
  .clearance-inspector { padding: var(--s-4); }
  .asset-match-grid,
  .asset-ai-layout,
  .asset-validation-grid { grid-template-columns: 1fr; }
  .asset-ai-layout { grid-template-rows: auto; }
  .asset-ai-layout > div:first-child { grid-row: auto; min-height: 280px; }
  .asset-evidence-links a { padding: var(--s-4); }
}

@media (max-width: 560px) {
  .clearance-card-copy { width: auto; padding: 14px; }
  .clearance-card-logo { margin-bottom: 12px; font-size: 1rem; }
  .clearance-card-copy > .label,
  .clearance-card-steps { display: none; }
  .clearance-card-copy strong {
    margin-top: 0;
    font-size: clamp(1.08rem, 5.15vw, 1.24rem);
    line-height: 1.04;
    letter-spacing: -.028em;
    word-spacing: .08em;
  }
  .clearance-card-cta { min-height: 28px; margin-top: 12px; padding: 5px 8px; font-size: .58rem; }
  .clearance-card-window img,
  .clearance-card-browser { left: 8%; width: 210%; }
  .clearance-card-browser { height: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .clearance-card-bg,
  .clearance-card-window { animation: none; }
  .clearance-flow-track { transition: none; }
}

/* Next project */
.next-proj {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-5);
  padding-block: var(--s-8);
  border-top: 1px solid var(--rule);
}
.next-proj .np-title { font-family: var(--font-display); font-size: var(--t-h1); line-height: 1; }
.next-proj:hover .np-title { color: var(--accent); }

/* ---- 12. ABOUT ------------------------------------------- */

.about-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--gutter); }
.about-block { grid-column: span 12; display: grid; grid-template-columns: subgrid; }

.def-list { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--gutter); padding-block: var(--s-5); border-bottom: 1px solid var(--rule); }
.def-key { grid-column: span 3; color: var(--ink-40); }
.def-val { grid-column: span 9; }
.def-val ul li { padding-block: 3px; }
@media (max-width: 760px) {
  .def-key { grid-column: 1 / -1; margin-bottom: var(--s-2); }
  .def-val { grid-column: 1 / -1; }
}

.link-list { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5); }
.link-list a { border-bottom: 1px solid var(--rule); padding-bottom: 2px; transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
.link-list a:hover { color: var(--accent); border-color: var(--accent); }

.portrait {
  background: var(--paper-2);
  border: 1px solid var(--rule);
  aspect-ratio: 3 / 4;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.portrait img { width: 100%; height: 100%; object-fit: cover; }

/* ---- 13. CONTACT ----------------------------------------- */

.contact-big {
  font-family: var(--font-display);
  font-size: var(--t-h1);
  line-height: 1;
  display: inline-block;
  border-bottom: 2px solid var(--rule);
  padding-bottom: var(--s-2);
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.contact-big:hover { color: var(--accent); border-color: var(--accent); }

.field { display: flex; flex-direction: column; gap: var(--s-2); margin-bottom: var(--s-5); }
.field input, .field textarea, .field select {
  font: inherit;
  font-size: var(--t-small);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rule);
  padding: var(--s-3) 0;
  color: var(--ink);
  border-radius: 0;
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-color: var(--accent);
}
.field textarea { resize: vertical; min-height: 120px; }
::placeholder { color: var(--ink-40); }

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: 14px 22px;
  border: 1px solid var(--ink);
  border-radius: 999px;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover { background: var(--ink); color: var(--paper); }
.btn.solid { background: var(--ink); color: var(--paper); }
.btn.solid:hover { background: var(--accent); border-color: var(--accent); }

/* ---- 14. INVERTED SECTION -------------------------------- */

.invert {
  background: var(--paper-inv);
  color: var(--ink-inv);
}
.invert .rule, .invert .sec-head { border-color: var(--rule-inv); }
.invert .muted-2, .invert .bar-item { color: #8A877D; }

/* ---- 15. LOADER ------------------------------------------ */

/* Seen once a session. Someone moving between the work and the CV should
   not have to knock four times. `entered` is set before first paint by an
   inline script in the head; `curtained` holds the scroll while the panel
   is up and is dropped the moment it is dismissed. */
html.entered .loader { display: none; }
html.curtained, html.curtained body { overflow: hidden; }

.loader {
  /* One place to change the pull. The visibility delay below is derived
     from these, so the panel can never disappear before it has finished
     opening. */
  --drape-dur: 1000ms;
  --drape-delay: 160ms;
  --ease-drape: cubic-bezier(0.65, 0, 0.35, 1);

  position: fixed; inset: 0;
  z-index: 999;
  overflow: hidden;
  /* The type blends, so the panel must be its own compositing group or
     the glyphs would multiply against the page instead of the field. */
  isolation: isolate;
  pointer-events: none;
  /* No opacity here. The paper lives on the drapes, so that what leaves
     the screen is two solid halves sliding apart rather than the whole
     thing quietly going transparent. */
  transition: visibility 0s linear calc(var(--drape-dur) + var(--drape-delay));
}
/* Nothing is clickable until the count lands, so an early click cannot
   skip past a panel that has not finished introducing itself. */
.loader.ready { pointer-events: auto; cursor: pointer; }
.loader.done { visibility: hidden; pointer-events: none; }

/* The sheet carries the field and does the travelling; the panel around
   it only clips. Raised rather than dropped, so the page is uncovered
   from the floor up and the first thing revealed is the ground the
   content stands on rather than its ceiling.

   The image is a god-rays field: value noise sampled in polar
   coordinates about an origin two units below the frame, which is why it
   reads as concentric bands fanning up and out rather than as blobs. It
   is baked, not rendered live, and that is not a shortcut -- the
   reference's own shader reports speed 0 with no animation frame
   pending and u_time pinned at 0, so what it ships is a single still
   frame. Baking it costs 22 KB and needs no canvas, no WebGL context
   and no fallback for machines that cannot give it one.

   The flat colour underneath is the field's own base, so a slow
   connection shows the right hue rather than white. */
/* The curtain is a picture now, not a shader. Two illustrations of the same
   room, one by day and one at night, swapped by the same clock that drives
   the site theme.

   The URLs are in custom properties on purpose: they point at the image
   host while the artwork is still being settled, and moving to local files
   is a two-line change here rather than a hunt through the stylesheet. */
:root {
  --curtain-day:   url("media/curtain-day.webp");
  --curtain-night: url("media/curtain-night.webp");
  --breeze-dur: 50s;
}

.loader-drape {
  position: absolute; inset: 0;
  overflow: hidden;
  background-color: #14232B;
  transform-origin: 52% 48%;
  animation: breeze var(--breeze-dur) ease-in-out infinite alternate;
  transition: transform var(--drape-dur) var(--ease-drape);
}

/* Both illustrations are mounted at once and crossfaded. Swapping a single
   background-image meant the incoming file had not been decoded yet, so the
   flat background colour showed through for a beat on every toggle -- the
   solid screen. Two layers cannot do that: whichever is fading in has been
   in the document since first paint. */
.loader-img {
  position: absolute; inset: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  transition: opacity 620ms var(--ease);
  will-change: opacity;
}
.loader-img--day   { background-image: var(--curtain-day);   opacity: 1; }
.loader-img--night { background-image: var(--curtain-night); opacity: 0; }
html.curtain-night .loader-img--day   { opacity: 0; }
html.curtain-night .loader-img--night { opacity: 1; }

/* The lamps breathe. One warm wash over the frame rather than per-lamp
   glows: placing those needs the artwork's own pixel coordinates and the
   file is not on disk yet. Screen blending means this can only lift what is
   already bright, so the lit areas move and the dark room barely does --
   which is most of the effect regardless. */
/* Only the fairy lights breathe. This layer is those pixels and nothing
   else: the strings along the shelf and down the hanging plant, found by
   picking small isolated warm blobs out of the night frame and throwing
   away anything wider than 60px, which is what excludes the globe lamps,
   the pendant and the monitors. 23 of them.

   It sits above both illustrations and is screen-blended, so it can only
   add light where light already is. Two overlapping cycles at 6.1s and
   9.7s -- coprime enough that the strings never pulse in unison. */
.loader-fairy {
  position: absolute; inset: 0;
  pointer-events: none;
  background-image: url("media/curtain-fairy.webp");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  mix-blend-mode: screen;
  opacity: 0;
  animation: fairy-a 6.1s ease-in-out infinite alternate,
             fairy-b 9.7s ease-in-out infinite alternate;
}
html.curtain-night .loader-fairy { opacity: 1; }
@keyframes fairy-a { from { opacity: .55; } to { opacity: 1; } }
@keyframes fairy-b { from { filter: brightness(.9); } to { filter: brightness(1.15); } }
@media (prefers-reduced-motion: reduce) {
  .loader-fairy { animation: none; }
}
@media (prefers-reduced-motion: reduce) {
}

/* Barely there by design: a 4% swell and about a percent of drift over the
   better part of a minute, with no readable direction of travel. Enough that
   the panel is not dead, not enough to notice unless you look for it. */
@keyframes breeze {
  from { transform: scale(1.00) translate3d(0, 0, 0); }
  to   { transform: scale(1.04) translate3d(-0.9%, -0.6%, 0); }
}

/* The dismissal slides the drape away, and that transform has to win over
   the breeze or the panel would fight itself on the way out. */
.loader.done .loader-drape { animation: none; }

/* The copy sits on a picture now, so it needs its own ground. A scrim
   weighted to the top and bottom edges where the toggle and the status row
   live, and a soft wash across the middle for the headline. Weak enough that
   the illustration still reads through it. */
.loader-drape::after {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to bottom,
      rgba(8, 18, 24, .42) 0%,
      rgba(8, 18, 24, .10) 22%,
      rgba(8, 18, 24, .18) 50%,
      rgba(8, 18, 24, .12) 72%,
      rgba(8, 18, 24, .52) 100%);
}

@media (prefers-reduced-motion: reduce) {
  .loader-drape { animation: none; }
}
.loader.done .loader-drape {
  transform: translateY(-100%);
  transition-delay: var(--drape-delay);
}

/* Measured off the reference rather than guessed at, because a CSS noise
   filter cannot produce this: feTurbulence is Perlin-based and always
   leaves spatial correlation, and blended as grey it brightens as much
   as it darkens. The real thing is black at every pixel with all the
   variation in the alpha channel, seventeen discrete levels, and an
   autocorrelation of -0.003 at lag 1 -- independent per pixel, no
   structure at any scale. So it is a generated file, sampled i.i.d. from
   that exact histogram: mean 0.395 against 0.393, sd 0.382 against
   0.382, 38.1% fully transparent against 38.3%.

   PNG-8 with a palette of one colour and a tRNS alpha table, which is
   what gets 160,000 noisy pixels into 80 KB. As RGBA the same image is
   103 KB.

   400px tile shown at 200px: on a 2x screen that is one texture pixel
   per device pixel, which is the size the tooth was designed at. */
/* The baked image is the floor, not the fallback-of-last-resort: it is
   always painted, and the canvas simply draws the same field on top once
   it has a context. No WebGL, reduced motion, or a failed compile and
   the still image is what shows, which is a complete picture rather than
   a hole. The canvas only ever fades in once it has rendered a frame. */
/* Night. The field inverts, so everything on it does too: white type,
   and screen instead of multiply -- screen can only lighten, which is
   the mirror of the reasoning for the day palette.

   These are literals, and they have to be. They used to read var(--paper),
   which was correct only while --paper was always the light warm white.
   The moment the page gained a dark theme, --paper on a dark page became
   the near-black ground and the night curtain's type turned deep teal on
   deep teal -- it vanished into its own background. The curtain is a
   photograph, not a themed surface: its field does not change when the
   page theme changes, so its type must not either. */
html.curtain-night .loader { color: #FFFFFF; }
html.curtain-night .loader .label,
html.curtain-night .loader .muted-2 { color: #FFFFFF; }

html.curtain-night .loader-drape { background-color: #14232B; }

/* The grain, back out of the shader and onto its own layer, because that is
   where the reference keeps it. Read off the live site: a 400x400 PNG whose
   RGB is pure black and whose noise lives entirely in the alpha channel,
   drawn at background-size 200px so the tile is halved on the way in, tiled,
   at opacity .2, blended NORMAL.

   Every number here is measured, not chosen. Their tile: mean alpha 100.3,
   sd 97.4, 17 palette levels, neighbour correlation -0.0032 (true white
   noise, no blur), 80 KB. Mine: 100.7, 97.7, 17 levels, +0.0002, 80 KB.

   I had this layer once and threw it away for the wrong reason. It was
   fringing colour on the mid orange, and the cause was mix-blend-mode:
   overlay, which is per-channel -- it was putting 7.95 levels into red and
   28.19 into green. The fix for that was `normal`, which is what the
   reference uses. Instead I moved the grain into the fragment shader, where
   it became per-device-pixel noise at twenty-eight times this amplitude,
   and that is what has been breaking up.

   Halving a 400px tile to 200px is doing real work too: the browser
   averages 2x2 blocks on the way down, which softens the noise, and the
   tile is pinned to CSS pixels so nothing stretches it when the drape is
   covered onto a tall phone. That was the other failure -- baked grain
   scaled by `cover` clumped into blobs. */
/* Raised above .loader-grain (z-index 1). Both are position:absolute with
   auto z-index otherwise, so the grain would have painted over the type. */
.loader-bar, .loader-invite { z-index: 2; }

/* The panel's type sits back off the field rather than punching through it.
   White on the night ground measured 10.8:1 and the day ink 12.4:1, which is
   document contrast on something that is meant to read as a held breath. At
   .72 they come to roughly 6:1 -- still clear of AA for the sizes in use,
   with the field allowed to be the loudest thing on screen.

   Opacity rather than a softer colour, because both of these carry
   mix-blend-mode (multiply by day, screen by night) and changing the colour
   changes how they blend; changing opacity does not. */
.loader-line, .loader-bar { opacity: .92; }

.loader-grain {
  position: absolute; inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image: url("media/grain-400.png");
  background-size: 200px;
  background-repeat: repeat;
  /* .12 rather than .2. The illustrations already carry grain of their own,
     so the tile was a second helping on top of a first -- and doubled noise
     is what WebP's chroma subsampling turns into coloured speckle. */
  opacity: .12;
}

.loader-gl {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  z-index: 0;              /* under the grain, which is over the whole field */
  /* No fade. The first frame is drawn synchronously, so there is nothing
     to fade up from; a transition here only stretched the flat drape
     colour into a visible flash. */
  opacity: 0;
}
.loader-gl.live { opacity: 1; }

/* The canvas holds a still between palette changes, so the breeze stays on
   the drape and moves it exactly as it moved the divs. Nothing to hand
   over, nothing to switch off. */

/* No grain layer any more. It lived here as a tiled PNG under a blend
   mode, which cost a 90 KB file, a rescale at every DPR, and -- because
   overlay is per channel -- a coloured speckle on anything saturated.
   The shader adds it instead, one value across all three channels. */

/* Top centre. Centred with auto margins for the same reason the scroll
   cue is: nothing here may depend on `transform`, which the reveal
   keyframes overwrite.

   z-index because .loader-invite is inset:0 and sits after this in the
   markup -- it was covering the switch and eating the click, which is why
   the toggle did nothing. */
.loader-toggle {
  position: absolute;
  top: var(--margin);
  left: 0; right: 0;
  width: max-content;
  margin-inline: auto;
  z-index: 2;
  display: inline-flex; align-items: center; gap: var(--s-3);
  padding: 6px 8px;
  border: 0; background: none;
  color: inherit; font: inherit;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}

.loader.ready .loader-toggle { opacity: .72; }   /* dialled back with the rest of the panel type */
.loader.done .loader-toggle { opacity: 0; transition-duration: 200ms; }


.loader-switch {
  position: relative; display: block;
  width: 34px; height: 18px;
  border: 1px solid currentColor; border-radius: 999px;
  opacity: 0.5;
  transition: opacity var(--dur) var(--ease);
}
.loader-toggle:hover .loader-switch,
.loader-toggle:focus-visible .loader-switch { opacity: 1; }

/* The knob is the only thing that moves, and no keyframe touches it, so
   transform is safe here. */
.loader-knob {
  position: absolute; top: 3px; left: 3px;
  width: 10px; height: 10px; border-radius: 50%;
  background: currentColor;
  transition: transform var(--dur) var(--ease);
}
html.curtain-night .loader-knob { transform: translateX(16px); }

.loader-bar {
  position: absolute;
  left: var(--pad-x); right: var(--pad-x); bottom: var(--margin);
  /* baseline, not flex-end. Equal box bottoms are not equal baselines: the
     count is Instrument Serif and the labels are mono, and their
     descenders differ, so bottom-aligning them left the percentage
     visibly off the line the other two sat on.

     Three equal columns rather than space-between, so the middle item is
     centred on the panel regardless of how wide READY or the count are.
     space-between would push it off-centre as the count grows. */
  display: flex; align-items: baseline;
}
.loader-bar > * { flex: 1 1 0; min-width: 0; }
.loader-bar > :nth-child(2) { text-align: center; }
.loader-bar > :nth-child(3) { text-align: right; }
.loader .count { font-family: var(--font-display); font-size: var(--t-h1); line-height: 1; }

/* The field is the site's own paper and vermillion, so the type is ink,
   the same as every other page. What does change is weight and size:
   .muted-2 is tuned for ink on flat paper and measured 1.81:1 here,
   which is a real failure, not a near miss. Everything on the curtain is
   full strength.

   Sizes are the site's own. I inflated these to 1.5rem once, to buy
   contrast headroom by crossing WCAG's 18pt threshold; that was solving
   my problem at the expense of the design. Fixing the field's clipping
   made the headroom appear on its own -- 4.59:1 at 11px -- so the labels
   went back. */
/* Literals for the same reason the night rules are -- see the note there. */
.loader { color: #FFFFFF; }
.loader .label,
.loader .muted-2 { color: #FFFFFF; }

.loader-invite {
  position: absolute; inset: 0;
  /* Holds only the headline, but it covers the whole panel, so as a hit
     target it silently blocked everything layered under it. Clicks fall
     through to .loader, which is where the dismiss listener lives. */
  pointer-events: none;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--s-5);
  text-align: center;
  padding-inline: var(--pad-x);
  transition: opacity 200ms var(--ease);
}

/* Word by word rather than the block arriving whole. Three words at
   140ms apart is close to the pace of reading them, so the line finishes
   assembling at about the moment it has been read. */
.loader-line span { display: inline-block; opacity: 0; transform: translateY(0.4em); }
/* The cue is one line of text so it can carry a baseline; the arrow is
   lifted out of the flow and parked above it, which costs nothing because
   that space is empty. */
.loader-scroll { position: relative; display: block; }
.loader-arrow {
  position: absolute;
  left: 0; right: 0; margin-inline: auto;
  bottom: calc(100% + 5px);
  width: 18px; height: 18px; display: block;
}

/* A small, slow rise. It is the same gesture the panel answers with, so
   the cue rehearses the outcome rather than just decorating it. */
@keyframes nudge {
  0%, 100% { transform: translateY(2px); }
  50%      { transform: translateY(-3px); }
}
.loader.ready .loader-arrow { animation: nudge 2.6s ease-in-out infinite; }

.loader.ready .loader-line span { animation: word-in 720ms var(--ease) forwards; }
.loader.ready .loader-line span:nth-child(1) { animation-delay: 100ms; }
.loader.ready .loader-line span:nth-child(2) { animation-delay: 240ms; }
.loader.ready .loader-line span:nth-child(3) { animation-delay: 380ms; }
.loader.ready .loader-scroll { animation: word-in 720ms var(--ease) 620ms forwards; }
.loader-scroll { opacity: 0; }

@keyframes word-in {
  from { opacity: 0; transform: translateY(0.4em); }
  to   { opacity: 1; transform: none; }
}

/* The words go before the cloth does, or they ride the sheet upward and
   the last thing read is a line of type leaving through the ceiling. */
.loader-bar { transition: opacity var(--dur) var(--ease); }
.loader.done .loader-bar,
.loader.done .loader-scroll,
.loader.done .loader-invite {
  opacity: 0;
  transform: none;
  transition-duration: 200ms;
}
.loader-line { font-family: var(--font-display); font-size: var(--t-h1); line-height: 1.05; }

/* The type sits in the field rather than on it: multiplied against the
   field, so a vermillion band underneath tints the glyphs red-brown and
   paper leaves them near-black. Measured, the colour walks 5 to 18 levels
   of 255 as the field moves.

   Multiply, not the overlay the reference uses. Overlay suits her light
   type on a mid field; with dark ink on a light one it barely darkens and
   measured 1.5:1 here. Multiply can never make the type lighter than
   plain ink, so contrast only improves -- it rose in all four zones. */
/* No blend mode: multiply and screen both dissolve type into a
   detailed illustration. The scrim carries legibility instead. */

/* The control is the whole viewport, so the usual focus ring would draw a
   box around the entire screen. The keyboard state moves onto the hint
   instead: same information, at the size the eye is already reading. */
.loader:focus { outline: none; }
.loader:focus-visible .loader-scroll .label {
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* Phones. All three sit on one line: the cue's label shares its bottom
   edge with READY and with the count, and the arrow simply rises above
   it, which costs nothing because that space is empty anyway. Lifting the
   whole cue clear of the bar, as it was, put it on a second baseline and
   read as a misalignment.

   The count comes down so the three fit the width without touching, and
   the cue's own label is what has to clear them, not the arrow. */
@media (max-width: 600px) {
  /* The count is the only thing wide enough to crowd its neighbours, so it
     is the only thing that shrinks. Everything stays on the one line. */
  .loader .count { font-size: 1.75rem; }
  .loader-arrow { width: 15px; height: 15px; bottom: calc(100% + 4px); }
}

/* The field is a still image, so there is nothing in it to stop. What is
   left to answer for is the pull, which is large-area travel and the
   kind that actually provokes symptoms, so it is replaced with a fade
   rather than merely slowed. */
@media (prefers-reduced-motion: reduce) {
  .loader-line span { opacity: 1; transform: none; animation: none; }
  .loader-scroll { opacity: 1; animation: none; }
  .loader-arrow { animation: none; }
  .loader-drape { transition: opacity var(--dur) var(--ease); }
  .loader.done .loader-drape { transform: none; opacity: 0; transition-delay: 0s; }
  .loader { transition: visibility 0s linear var(--dur); }
}

/* ---- 16. STYLEGUIDE PAGE HELPERS ------------------------- */

.swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--s-4); }
.swatch-chip { height: 88px; border: 1px solid var(--rule); margin-bottom: var(--s-2); }
.spec-row { display: grid; grid-template-columns: 180px 1fr; gap: var(--gutter); padding-block: var(--s-4); border-bottom: 1px solid var(--rule); align-items: baseline; }
@media (max-width: 700px) { .spec-row { grid-template-columns: 1fr; } }

/* ---- 16b. STAT ROW (About) -------------------------------- */
/* Four hard numbers, given room. auto-fit so it goes 4 -> 2 -> 1
   without a media query. */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--gutter) var(--s-5);
  padding-block: var(--s-6);
  border-block: 1px solid var(--rule);
}
.stats .stat-n {
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 3.4vw, 3.6rem);
  line-height: 1;
  letter-spacing: -0.02em;
  margin-bottom: var(--s-3);
}
.stats .stat-l { color: var(--ink-40); }

/* ---- 16c. PRESENTATION MOCKUPS ---------------------------- */
/* An angled wall of the delivered artwork with a phone sitting on top of it.
   All CSS — the images are the same files the case study already loads, so
   nothing is re-hosted and nothing is faked. Both loops are doubled in the
   markup so the animation meets itself with no seam. */

.mk-wall {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background: var(--mk-ground, var(--ink));
  border: 1px solid var(--rule);
  isolation: isolate;
}

.mk-wall-grid {
  position: absolute;
  /* oversized and offset so the rotation never exposes a bare corner */
  top: -42%;
  left: -26%;
  width: 152%;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.9vw;
  transform: rotate(-12deg);
  animation: mk-drift 46s linear infinite;
  opacity: 0.85;
}
.mk-tile { overflow: hidden; border-radius: 2px; }
.mk-tile img { display: block; width: 100%; height: auto; }

@keyframes mk-drift {
  from { transform: rotate(-12deg) translateY(0); }
  to   { transform: rotate(-12deg) translateY(-50%); }
}

/* the phone: a bezel, a screen, and a column of creatives moving through it */
.mk-phone {
  position: absolute;
  right: 7%;
  top: 50%;
  transform: translateY(-50%);
  width: clamp(160px, 20%, 320px);
  aspect-ratio: 9 / 19;
  background: #0E0D09;
  border-radius: clamp(18px, 2.2vw, 38px);
  padding: clamp(5px, 0.6vw, 10px);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.45);
}
.mk-screen {
  height: 100%;
  border-radius: clamp(14px, 1.7vw, 30px);
  overflow: hidden;
  background: var(--paper);
  display: flex;
  align-items: flex-start;
}
.mk-feed {
  display: flex;
  flex-direction: column;
  gap: 8%;
  padding-block: 8%;
  width: 100%;
  animation: mk-feed 34s linear infinite;
}
.mk-feed img { display: block; width: 100%; height: auto; }

@keyframes mk-feed {
  from { transform: translateY(0); }
  to   { transform: translateY(-50%); }
}

/* Light-ground variant of the wall (reference 1): the tiles read as physical
   prints on a table rather than screens on a brand colour. */
.mk-wall--light .mk-wall-grid { opacity: 1; gap: 1.3vw; }
.mk-wall--light .mk-tile {
  background: #fff;
  border-radius: 3px;
  box-shadow: 0 10px 26px rgba(22, 21, 15, 0.14);
}

/* ---- device array (reference 3) --------------------------- */
/* Phones on a shelf, each with its own drop, scale and tilt so the row has a
   rhythm instead of looking like a spec sheet. --dy is a fraction of the
   frame height, so the stagger holds at every width. */
.mk-array {
  background: var(--mk-ground, var(--paper-2));
  border: 1px solid var(--rule);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(10px, 1.6vw, 34px);
  padding: clamp(30px, 5vw, 88px) clamp(16px, 3vw, 56px) clamp(40px, 6vw, 96px);
  overflow: hidden;
}
.mk-dev {
  flex: 0 1 clamp(84px, 16%, 232px);
  transform: translateY(calc(var(--dy, 0) * 26%)) scale(var(--s, 1)) rotate(var(--r, 0deg));
  transform-origin: top center;
  transition: transform var(--dur-slow) var(--ease);
}
.mk-dev-screen {
  aspect-ratio: 9 / 19.5;
  background: #0E0D09;
  border-radius: clamp(10px, 1.5vw, 26px);
  padding: clamp(3px, 0.4vw, 7px);
  box-shadow: 0 24px 54px rgba(22, 21, 15, 0.26);
  overflow: hidden;
}
.mk-dev-screen img,
.mk-dev-screen video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: clamp(7px, 1.1vw, 20px);
}
@media (hover: hover) {
  .mk-array:hover .mk-dev { transform: translateY(calc(var(--dy, 0) * 14%)) scale(var(--s, 1)) rotate(0deg); }
}

/* ---- hand-held plate (reference 2) ------------------------ */
/* A photographed hand holding a blank phone. The post is positioned into the
   screen with percentages measured off the plate, so it stays seated at any
   size. Change the four values below if the plate is ever re-shot. */
.mk-hand {
  position: relative;
  background: var(--mk-ground, var(--paper-2));
  border: 1px solid var(--rule);
  overflow: hidden;
}
.mk-hand-plate { display: block; width: 100%; height: auto; position: relative; z-index: 2; }
.mk-hand-screen {
  position: absolute;
  z-index: 1;
  left: var(--sx, 25.8%);
  top: var(--sy, 8.4%);
  width: var(--sw, 48.6%);
  height: var(--sh, 71.4%);
  border-radius: 2.2%;
  overflow: hidden;
}
.mk-hand-screen img { display: block; width: 100%; height: 100%; object-fit: cover; }

@media (max-width: 760px) {
  .mk-wall { aspect-ratio: 4 / 5; }
  .mk-wall-grid { grid-template-columns: repeat(3, 1fr); width: 190%; left: -45%; }
  .mk-phone { right: 50%; transform: translate(50%, -50%); width: 44%; }
}

/* Two looping animations behind a phone is exactly the kind of thing that
   makes people motion-sick. Stop both, keep the composition. */
@media (prefers-reduced-motion: reduce) {
  .mk-wall-grid { animation: none; }
  .mk-feed      { animation: none; }
  .mk-dev       { transition: none; }
}

/* ---- 17b. INTERACTION LAYER ------------------------------- */
/* Built by script.js. Everything here is inert until that runs,
   so the page is complete and correct with JavaScript off. */

/* the image that follows the cursor along a row of titles */
.row-preview {
  position: fixed;
  top: 0;
  left: 0;
  width: 300px;
  margin: 0;
  z-index: 90;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
  will-change: transform;
}
.row-preview.on { opacity: 1; }
.row-preview img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: 10px;
  box-shadow: 0 22px 50px rgba(22, 21, 15, 0.20);
}

/* the disc that appears over a project card */
.cursor-tag {
  position: fixed;
  top: 0;
  left: 0;
  /* 78px was a badge you moved around the page. At 52 it reads as a cursor,
     which is what it now is — the native pointer is hidden underneath it. */
  width: 52px;
  height: 52px;
  border-radius: 50%;
  /* Deliberately NOT tokenised. This is a brand object, not a page surface:
     it must look identical in light, dark and auto. When it took --accent and
     --paper the eyes inverted with the theme and the disc read as blank. */
  background: #C4462A;
  color: #EFEDE7;
  display: grid;
  place-items: center;
  z-index: 95;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.6);
  transition: opacity var(--dur-fast) var(--ease), scale var(--dur) var(--ease);
  will-change: transform;
}
.cursor-tag.on { opacity: 1; scale: 1; }
.cursor-tag .label { color: #EFEDE7; }   /* literal, for the reason above */

/* The disc IS the cursor over these, so the real one goes. Without this the
   craft tiles show a magnifying glass (they carry cursor: zoom-in) sitting on
   top of the eyes. Gated behind the class the script adds, so with JS off the
   normal pointer is untouched. */
.has-cursor-tag .card,
.has-cursor-tag .card *,
.has-cursor-tag .tile[data-full],
.has-cursor-tag .tile[data-full] * { cursor: none; }

/* The eyes blink on their own, and again the moment the disc appears.
   transform-box: fill-box is the part that is easy to get wrong — without it
   an SVG transform-origin resolves against the whole viewport rather than the
   shape, and "scaleY from the centre" collapses the eyes off-screen instead
   of shutting them. */
.cursor-eyes .eyes {
  transform-box: fill-box;
  transform-origin: center;
  animation: eye-blink 4.6s var(--ease) infinite;
}
/* Restart the blink on every entry: the disc is only visible for a moment at
   a time, and a 4.6s idle cycle would often never fire while it is on screen. */
.cursor-tag.on .cursor-eyes .eyes { animation: eye-blink 4.6s var(--ease) infinite, eye-wake 460ms var(--ease) 1; }

@keyframes eye-blink {
  0%, 88%, 100% { transform: scaleY(1); }
  92%           { transform: scaleY(0.08); }
  96%           { transform: scaleY(1); }
}
/* One deliberate blink as it opens, slightly slower than the idle one. */
@keyframes eye-wake {
  0%   { transform: scaleY(0.08); }
  55%  { transform: scaleY(1.06); }
  100% { transform: scaleY(1); }
}

/* buttons lean toward the pointer; the transition is only for the
   release, the lean itself is driven per-frame */
.btn { transition: transform var(--dur) var(--ease), background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }

/* A row is a link, so it already has focus styles; the preview is
   pointer-only by design and never traps the keyboard. */
@media (prefers-reduced-motion: reduce) {
  .row-preview, .cursor-tag { display: none; }
  .btn { transition: none; }
}

/* ---- 17c. ASSET VIEWER ------------------------------------ */
/* Opens the uncropped file over the page. Created by script.js,
   so with JavaScript off the tiles simply do not open. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 4vw, 64px);
  background: rgba(22, 21, 15, 0.94);
}
.lightbox.on { display: flex; }

.lb-stage {
  margin: 0;
  max-width: min(100%, 1500px);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-4);
}
.lightbox:not(.is-film) video,
.lightbox.is-film img { display: none; }

.lb-stage video {
  display: block;
  max-width: 100%;
  max-height: 78vh;
  width: auto;
  height: auto;
  background: var(--ink);
}

.lb-stage img {
  display: block;
  max-width: 100%;
  max-height: 78vh;
  width: auto;
  height: auto;
  object-fit: contain;
  background: var(--ink);
}
.lb-stage figcaption { color: var(--paper); text-align: center; }

.lb-close,
.lb-nav {
  position: absolute;
  background: none;
  border: 1px solid rgba(239, 237, 231, 0.32);
  color: var(--paper);
  width: 52px;
  height: 52px;
  border-radius: 50%;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.lb-close:hover,
.lb-nav:hover { background: var(--accent); border-color: var(--accent); }
.lb-close:focus-visible,
.lb-nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.lb-close { top: clamp(14px, 3vw, 34px); right: clamp(14px, 3vw, 34px); }
.lb-prev  { left: clamp(10px, 2vw, 30px); top: 50%; transform: translateY(-50%); }
.lb-next  { right: clamp(10px, 2vw, 30px); top: 50%; transform: translateY(-50%); }

.lb-count {
  position: absolute;
  left: 50%;
  bottom: clamp(14px, 3vw, 34px);
  transform: translateX(-50%);
  color: rgba(239, 237, 231, 0.62);
}

@media (max-width: 640px) {
  .lb-prev, .lb-next { top: auto; bottom: clamp(14px, 3vw, 34px); transform: none; }
  .lb-prev { left: 16px; }
  .lb-next { right: 16px; }
  .lightbox:not(.is-film) video,
.lightbox.is-film img { display: none; }

.lb-stage video {
  display: block;
  max-width: 100%;
  max-height: 78vh;
  width: auto;
  height: auto;
  background: var(--ink);
}

.lb-stage img { max-height: 66vh; }
}

/* ---- 17d. PEN-TOOL WORDMARK ------------------------------- */
/* The hero name is the font's own outlines. Hovering reveals the anchor
   points and control handles that make them, at their real coordinates,
   plus a live readout of where the cursor is in the glyph's own units. */

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

.penmark {
  position: relative;
  z-index: 5;              /* a pulled point draws over everything in the hero */
  display: block;
  width: 100%;
  cursor: crosshair;
  /* pan-y, not none: a vertical swipe still scrolls the page, so the
     wordmark is not a dead zone on a phone. Once the browser decides a
     gesture is horizontal it hands us every move after it, and the point
     then follows in both axes. */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}
.penmark.dragging { cursor: grabbing; }
.penmark-svg {
  display: block;
  width: 100%;
  height: auto;
  /* deliberately not clipped — a pulled point is supposed to run past the
     column and over the animation, which sits behind it */
  overflow: visible;
}
/* The points are drawn on a canvas so 1,136 of them cost one paint rather
   than 1,136 DOM nodes. The canvas deliberately overhangs the wordmark —
   at inset:0 it clipped every anchor and handle the moment a point was
   dragged past the right edge, which is exactly where they get dragged. */
/* Sized with explicit width/height, NOT with opposing insets. A canvas is a
   replaced element: the moment script sets cv.width the box falls back to
   that intrinsic pixel size, so `width: auto` between left and right offsets
   silently resized the whole thing and the points went out of register.
   Percentages here are of the wordmark, so it scales with the type. */
.penmark-pts {
  position: absolute;
  /* The horizontal overhang used to be 260% of the wordmark. That was sized
     for a 300px name; against a 1680px one it asks for a 4400px canvas, and
     at dpr 2 that is a 40-megapixel backing store for a handful of dots. A
     fixed 260px each side is more room than any point is ever dragged into
     and keeps the buffer flat as the name grows. Vertical stays
     proportional — the letterforms are what points get pulled off. */
  left: -260px;
  top: -70%;
  width: calc(100% + 520px);
  height: 240%;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.penmark.live .penmark-pts { opacity: 1; }

/* the hint sits with the other hero metadata and steps back once the
   visitor has worked out that the points move */
.pen-hint { color: var(--hero-pen, var(--accent)); }
.penmark.live ~ * .pen-hint { opacity: 0.4; }

/* The hint is a desktop affordance only. A touch device cannot drag a point —
   the script hands the gesture back to the page so the hero still scrolls — so
   there is no instruction to give, and a line explaining an interaction the
   visitor cannot perform is just noise above the name. Gated on pointer
   capability rather than width: a narrow window on a laptop still has a mouse
   and still gets the hint. */
@media (hover: none) and (pointer: coarse) {
  .pen-hint { display: none; }

  /* Removing the hint leaves two items in a three-track grid. A display:none
     element claims no track, so "Mumbai" took 1fr and the years landed in the
     middle "auto" track with an empty 1fr stranded to their right — which is
     why they read as centred rather than flush. Two tracks, and the years sit
     hard against the right margin where the last item belongs. */
  .hero-meta { grid-template-columns: 1fr auto; }
}

/* the coordinate readout, riding just under the cursor */
.penmark-xy {
  position: fixed;
  top: 0; left: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.35;
  letter-spacing: 0.04em;
  color: var(--hero-fg-mute, var(--ink-40));
  white-space: pre;
}
.penmark-xy.on { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .penmark-pts { transition: none; }
}
@media (hover: none) {
  .penmark { cursor: default; }
  /* bigger targets, since the points are being aimed at with a fingertip */
  .penmark-xy { font-size: 12px; }
}

/* ---- 18. PHONE ------------------------------------------------
   The fixed bars are a single flex row of fixed-width text. On a 390px
   screen the brand tagline wrapped to four lines and pushed the nav off
   the right edge; the footer ran off too. Below 760px the bars drop what
   is decoration and keep what is navigation. */
@media (max-width: 760px) {
  /* The wordmark leaves the bar entirely on a phone. Sharing 375px with five
     nav items pushed Contact out to x=425 — off the screen — and the sideways
     scroll meant to rescue it gave no visual hint it was there, so the contact
     link on a hiring portfolio was effectively unreachable on mobile. The name
     is the first thing in the hero anyway; the bar does not need it twice. */
  .brand { display: none; }

  .topbar, .botbar { gap: var(--s-4); }
  .topbar { font-size: 10px; }

  /* With the brand gone the nav owns the whole bar, so the links spread across
     it instead of huddling at one end. Nothing overflows, nothing is clipped. */
  .nav {
    margin-left: 0;
    width: 100%;
    gap: var(--s-3);
    justify-content: space-between;
  }
  .nav a { white-space: nowrap; }

  /* 13px of text is not a tap target. The type size stays; the hit area grows
     to the full height of the bar — 44px, the smallest a thumb hits reliably.
     The underline has to be repositioned because the anchor is now much taller
     than its text, and bottom: 0 would strand it at the floor of the bar. */
  .nav a,
  .botbar .bar-item {
    display: flex;
    align-items: center;
    min-height: var(--bar-h);
  }
  .nav a { padding-bottom: 0; }
  .nav a::after { bottom: calc(50% - 0.8em); }

  /* The hero meta row is three items wide on a 375px screen and the third one
     wrapped, pushing the wordmark down. Only the label goes: "2022–2026" on
     its own is short enough to sit on the line and still says everything the
     full phrase did in this context. */
  .hero-years-label { display: none; }

  /* same problem, 11px tall: the section's "View full archive" escape hatch */
  .sec-meta {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* footer: keep the status and the email, drop the clock and the year */
  .botbar { font-size: 10px; }
  .botbar .bar-item:nth-last-child(1),
  .botbar .bar-item:nth-last-child(2) { display: none; }
}

@media (max-width: 400px) {
  .topbar, .botbar { font-size: 9px; gap: var(--s-3); }
  .nav { gap: var(--s-2); }
}

/* ---- 17e. HALFTONE HEADING -------------------------------- */
/* The words are drawn twice: once as real text, which is what a screen
   reader and a no-JS visitor get, and once as a character grid on canvas.
   Only one of them is ever visible. */
.halftone { position: relative; }
.halftone-canvas { display: none; }

.halftone.on .halftone-text {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.halftone.on .halftone-canvas {
  display: block;
  width: 100%;
  height: auto;
  cursor: crosshair;
}

/* On a phone the display type was running off the column and the canvas was
   being sampled before layout settled, so the heading showed as nothing at
   all. Below 760px it falls back to real type — legible beats clever. */
@media (max-width: 760px) {
  .halftone.on .halftone-canvas { display: none; }
  .halftone.on .halftone-text {
    position: static;
    width: auto; height: auto;
    overflow: visible;
    clip-path: none;
    white-space: normal;
  }
}

/* ---- 17f. ARC (craft) ------------------------------------- */
/* A shallow dome of overlapping cards that turns as the page scrolls.
   Size and position are written per frame by script.js — every card is
   just a box at 0,0 until then — so nothing here fixes a measure that
   the arc maths would only have to override. */

.cascade {
  position: relative;
  /* The dome's height is a consequence of its span, not a free choice:
     0.896 x the half-span, which is the drop at the far end plus half an
     end card. Declaring it as a ratio reserves the right box before any
     script runs, so the page does not jump. */
  aspect-ratio: 1 / 0.3091;
  margin-bottom: var(--s-8);
  touch-action: pan-y;
  /* The band is sized to contain the whole arc, so clipping never cuts a
     card — it is here to stop a wide card at the end of the sweep from
     reaching past the viewport edge. */
  overflow: hidden;
}
.cas-track { position: absolute; inset: 0; }
.cascade.ready { cursor: grab; }
.cascade.dragging { cursor: grabbing; }

.cas-card {
  position: absolute;
  top: 0;
  left: 0;
  padding: 0;
  margin: 0;
  border: 0;
  background: var(--paper-2);
  cursor: pointer;
  overflow: hidden;
  will-change: transform, width, height;
  transform-origin: center center;
}
.cas-card img,
.cas-card video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.cas-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.cas-meta {
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  bottom: var(--s-5);
  display: flex;
  justify-content: space-between;
  color: var(--ink-40);
  pointer-events: none;
  z-index: 300;
}

/* The hint doubles as the reward. Every other label on this site is
   uppercase mono; this one drops to lowercase when the arc is moving,
   and that break in register is the whole joke. Both states occupy the
   same box, so nothing beside them shifts when they trade places. */
.cas-hint { position: relative; display: inline-block; }
.cas-hint > span { transition: opacity 0.18s ease; }
.cas-hint-go {
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
  opacity: 0;
  color: var(--accent-text);
  text-transform: none;
  letter-spacing: 0.02em;
}
.cascade.spinning .cas-hint-rest { opacity: 0; }
.cascade.spinning .cas-hint-go { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .cas-hint > span { transition: none; }
}

/* no JS: a plain scroller instead of a heap of absolutely-placed cards */
.cascade:not(.ready) { overflow-x: auto; height: auto; aspect-ratio: auto; padding-block: var(--s-5); }
.cascade:not(.ready) .cas-track { position: static; display: flex; gap: var(--gutter); }
.cascade:not(.ready) .cas-card {
  position: static; flex: none; top: auto;
  height: clamp(150px, 15vw, 300px);
  width: calc(clamp(150px, 15vw, 300px) * var(--ar, 1));
}

@media (max-width: 760px) {
  /* Five seats down here, so the cards are bigger and the box deeper: the
     span widens to 0.44 of the width and the depth is 1.154 x the
     half-span, which is 0.5076 of the width. That number is the arc
     alone, and reserving only the arc was the bug — .cas-meta is pinned to
     the bottom of the same box, so "Drag / 01 / 17" was landing at y155 in
     a 190px band while the end cards reach y180. The label printed over
     the artwork.

     So the band now carries the arc plus a strip the meta row owns
     outright. script.js measures the box but only reads its width — the
     dome is built from the half-span, never from the height — so making
     the box deeper moves nothing on the arc. */
  .cascade {
    aspect-ratio: auto;
    height: calc(100vw * 0.5076 + 34px);
    margin-bottom: var(--s-6);
  }
  .cas-meta { bottom: var(--s-3); }
}

/* ---- 17g. ABOUT PORTRAIT ---------------------------------- */
/* Two drawings in the same box. The resting one sits on top; hover fades
   it away and reveals the blushing one underneath. The fade is short on
   purpose — the two are drawn, not filtered, so their lines do not
   register and a slow dissolve shows both sets at once. */
.portrait--swap {
  position: relative;
  /* Square now, and both drawings are cropped to the same box so the
     blush swap does not shift on hover. A tall portrait at the full
     width of its column was reading as a hero image; at 1:1 and capped
     it reads as what it is — a picture of a person, beside the text. */
  aspect-ratio: 1 / 1;
  max-width: 260px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  overflow: hidden;
}
.portrait--swap img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.portrait-base { transition: opacity 170ms var(--ease); }

@media (hover: hover) and (pointer: fine) {
  .portrait--swap:hover .portrait-base { opacity: 0; }
}

/* Keyboard users get the same thing: the figure is in the tab order via the
   CV link beside it, so the swap follows focus within the column. */
.c-3:focus-within .portrait--swap .portrait-base { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .portrait-base { transition: none; }
}

/* ---- 17i. ARTWORK HANDLING -------------------------------- */
/* Deterrence, not protection. Anything on screen has already been
   downloaded to the visitor's machine — this only stops the casual
   drag-to-desktop and long-press-to-save. It is scoped to artwork on
   purpose: text stays selectable, links stay right-clickable, and the CV
   still downloads, because making a recruiter fight the page is a worse
   outcome than someone keeping a JPEG. */

.tile img, .tile video,
.cas-card img, .cas-card video,
.bh-img img, .bh-img video,
.mk-tile img, .mk-tile video,
.card-media img, .card-media video,
.media-full img, .media-full video,
.portrait img {
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;   /* iOS long-press "Save Image" sheet */
}

/* ---- 17h. SITE GUIDE ------------------------------------- */
/* Retired. The navigator and the assistant were merged into one launcher —
   Find Your Way, with Chata-gata answering inside it — so this whole block
   became dead weight shipping on every page. Its rules now live beside the
   panel they style, in assistant.css.

   The two things worth keeping from it are recorded there: the launcher
   clears the fixed .botbar rather than sitting on it, and it keeps a 44px
   tap target on a phone even though the visible box is smaller. */


/* ---- 16a. BEHANCE-ORDER PROJECT STACK -------------------- */
/* Mirrors Behance's own layout engine so case studies read in the exact
   sequence they were authored in:
     .bh-img  — a full-width ImageModule
     .bh-row  — a MediaCollectionModule, 2/3/4 images on one row
   Behance sizes row items to equal HEIGHT with widths proportional to each
   image's aspect ratio. `flex-grow: <aspect-ratio>` reproduces that exactly,
   which is why the generator writes an inline --ar on every row item. */

/* 24px between stacked modules rather than 32. The work should read as one
   continuous case study, and the wider gap was breaking it into slices. */
.bh-stack { display: flex; flex-direction: column; gap: var(--s-5); }

.bh-img,
.bh-row { margin: 0; }

/* video is listed everywhere img is. The animated GIFs were re-encoded as
   H.264 and the <img> became a <video>; anything this rule missed fell back
   to the intrinsic video size and blew the column apart. */
.bh-img img,
.bh-img video,
.bh-row img,
.bh-row video,
.mk-tile img,
.mk-tile video,
.media-full img,
.media-full video {
  display: block;
  width: 100%;
  height: auto;
  background: var(--paper-2);
}

/* Locally hosted loops (the DOOH deliverables). Muted and silent by design —
   these are out-of-home frames, they never had audio. Paused under
   prefers-reduced-motion by script.js, which also gives them controls. */
.bh-row video[data-paused] { cursor: pointer; }

/* A YouTube EmbedModule. 16:9, full bleed of the stack, same rhythm as an
   image module. No fixed height — the ratio box does the work. */
.bh-video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--ink);
}
.bh-video iframe,
.bh-video .video-facade {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* Click-to-play facade. The poster is the whole surface; the play mark and
   the caption sit on top of it. Nothing loads from YouTube until the click. */
.video-facade {
  padding: 0;
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  overflow: hidden;
  font: inherit;
  text-align: left;
}
.video-facade .video-poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow) var(--ease), opacity var(--dur) var(--ease);
}
.video-facade::after {          /* just enough scrim to hold the caption */
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(22,21,15,.55) 0%,
                                      rgba(22,21,15,0) 42%);
  opacity: .9;
  transition: opacity var(--dur) var(--ease);
}
.video-facade .video-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 84px;
  height: 84px;
  border-radius: 50%;
  /* Deliberately NOT tokenised. This is a brand object, not a page surface:
     it must look identical in light, dark and auto. When it took --accent and
     --paper the eyes inverted with the theme and the disc read as blank. */
  background: #C4462A;
  color: #EFEDE7;
  display: grid;
  place-items: center;
  z-index: 1;
  transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.video-facade .video-play svg {
  /* the optical centre of a triangle sits left of its bounding box */
  margin-left: 3px;
}
.video-facade .video-cap {
  position: absolute;
  left: var(--s-5);
  right: var(--s-5);
  bottom: var(--s-5);
  z-index: 1;
  color: var(--paper);
}

@media (hover: hover) {
  .video-facade:hover .video-poster { transform: scale(1.02); opacity: .82; }
  .video-facade:hover .video-play   { transform: translate(-50%, -50%) scale(1.06); }
}
.video-facade:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .video-facade .video-poster,
  .video-facade .video-play { transition: none; }
  .video-facade:hover .video-poster { transform: none; }
  .video-facade:hover .video-play   { transform: translate(-50%, -50%); }
}
@media (max-width: 640px) {
  .video-facade .video-play { width: 62px; height: 62px; }
  .video-facade .video-cap  { left: var(--s-4); right: var(--s-4); bottom: var(--s-4); }
}

/* Loose assets (Polycab). One grid per aspect ratio, so every tile in a
   given grid is the same shape and the rows line up without cropping.
   auto-FIT, not auto-fill: with only 3 or 5 assets in a group, auto-fill would
   leave phantom empty tracks and strand the tiles at the far left. auto-fit
   collapses them, and the max cap stops three squares from stretching to 600px
   each. Result: sensible tiles, left-aligned, no media queries. */
.bh-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--min, 240px), var(--max, 340px)));
  justify-content: start;   /* tiles hug the left edge, like the rest of the grid */
  gap: var(--s-6) var(--gutter);
  align-items: start;
}
/* Section dividers lifted from the delivered deck: a black band with a single
   word. The shared placeholder is cream, which flashes as a pale slab behind a
   black banner while it decodes, so these get the ink colour instead. */
.bh-divider img { background: var(--ink); }

.bh-grid > figure { margin: 0; min-width: 0; }
/* A project whose assets come in two shapes gets two grids, one per shape, so
   the rows line up inside each. They are emitted back to back, so the break
   between them has to be stated here rather than left to a utility class the
   generator would have to remember to add. */
.bh-grid + .bh-grid { margin-top: var(--s-7); }
.bh-grid img,
.bh-grid video {
  display: block;
  width: 100%;
  height: auto;
  background: var(--paper-2);
}
.bh-grid .bh-cap {
  margin-top: var(--s-3);
  color: var(--ink-40);
}
/* On a phone the cap stops making sense — a 300px tile in a 358px column just
   leaves an odd gutter. Let them fill instead. */
@media (max-width: 640px) {
  .bh-grid { grid-template-columns: repeat(auto-fit, minmax(var(--min, 240px), 1fr)); }
}

.bh-row {
  display: flex;
  gap: var(--gutter);
  align-items: flex-start;
  /* once an item hits its ceiling the row no longer fills the width, so
     centre it rather than leaving a ragged gap on one side */
  justify-content: center;
}
.bh-row > figure {
  margin: 0;
  flex: var(--ar, 1) 1 0;
  min-width: 0;
}

.bh-cap {
  padding-top: var(--s-2);
  color: var(--ink-40);
}

/* Instagram-grid variant. Where a project's rows are all the same shape the
   set reads as one wall of posts, and a 24px gutter breaks it into nine
   separate pictures instead. Hairline gaps in both directions.
   The negative margin is doing real work: .bh-stack is a flex column with its
   own gap between modules, so tightening only the row's horizontal gap would
   leave the columns 2px apart and the rows still 24px apart — a grid of
   stripes. This cancels the stack gap back down to match. */
.bh-stack--grid .bh-row { gap: 2px; }
/* Only rows of the SAME width join up. Three-across next to three-across is one
   continuous wall; the pair at the end is a different set and keeps its normal
   spacing, otherwise a two-up welds onto the bottom of a three-up and the
   column edges no longer line through. */
.bh-stack--grid .bh-row[data-n="3"] + .bh-row[data-n="3"],
.bh-stack--grid .bh-row[data-n="2"] + .bh-row[data-n="2"],
.bh-stack--grid .bh-row[data-n="4"] + .bh-row[data-n="4"] {
  margin-top: calc(2px - var(--s-5));
}
@media (max-width: 760px) {
  .bh-stack--grid .bh-row { gap: 2px; }
}

/* Rows of 4 get tight on a laptop — go two-by-two before stacking */
@media (max-width: 1100px) {
  .bh-row[data-n="4"] { flex-wrap: wrap; }
  .bh-row[data-n="4"] > figure { flex: var(--ar, 1) 1 40%; }
}

/* Rows stay rows on a phone. They used to collapse to one image per line,
   which kept the order but destroyed the grouping — and the grouping is the
   point. A row is a set meant to be read together: three crops of the same
   key visual, a spec sheet at 4:5 / 15:16 / 1:1, a before and after. Stacked
   full width they become unrelated pictures that happen to follow each other,
   and Aquaguard's three rows of three lose the comparison entirely.

   Rows of four still break two-by-two from the 1100px rule above; twelve
   percent of a 375px screen is not an image, it is a swatch. */
@media (max-width: 760px) {
  .bh-stack { gap: var(--s-5); }
  /* Tighter gutter so three across still has room for the images. */
  .bh-row { gap: var(--s-3); }
}

/* ---- 16b. CONTACT SHEET (craft page) --------------------- */
/* A masonry was wrong here: CSS columns fill top-to-bottom, so nothing lines
   up left-to-right and mixed aspect ratios make the rag worse.
   This is a real grid — every tile in a section shares one aspect ratio, so
   rows AND columns align. `auto-fill` + `minmax` keeps it responsive without
   a single media query; the column count falls out of the available width. */

.sheet {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--min, 280px), 1fr));
  gap: var(--gutter);
  align-items: start;
}

.sheet > figure { margin: 0; }

.sheet .tile {
  /* a real <button>, so the viewer is reachable by keyboard */
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: zoom-in;
  aspect-ratio: var(--tile, 1);
  background: var(--paper-2);
  border: 1px solid var(--rule);
  /* Rounded rectangle on every craft creative. The arc cards are deliberately
     left square — they read as cut sheets of artwork passing through a frame,
     and rounding them turns the loop into a row of app icons.
     overflow was already hidden, so the image is clipped by the radius and no
     corner of the artwork pokes past the border. */
  border-radius: 10px;
  overflow: hidden;
}
.sheet .tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* a film tile: the poster, and a mark that says it plays */
.sheet .tile.is-film { position: relative; cursor: pointer; }
.sheet .tile-play {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  /* Deliberately NOT tokenised. This is a brand object, not a page surface:
     it must look identical in light, dark and auto. When it took --accent and
     --paper the eyes inverted with the theme and the disc read as blank. */
  background: #C4462A;
  color: #EFEDE7;
  display: grid;
  place-items: center;
  transition: transform var(--dur) var(--ease);
}
.sheet .tile-play svg { margin-left: 2px; }
@media (hover: hover) {
  .sheet > figure:hover .tile-play { transform: scale(1.12); }
}

.sheet .tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;      /* uniform crop — this is what makes them align */
  object-position: center;
  transition: transform var(--dur-slow) var(--ease), opacity var(--dur) var(--ease);
}

/* Hover lifts the whole tile out of the grid rather than zooming the crop —
   on a wall of thumbnails you want to SEE the piece, not a tighter crop of it.
   z-index puts it over its neighbours; the grid does not reflow. */
.sheet > figure {
  transition: transform var(--dur) var(--ease);
  will-change: transform;
}
.sheet .tile { transition: box-shadow var(--dur) var(--ease); }

@media (hover: hover) {
  .sheet > figure:hover {
    transform: scale(1.28);
    position: relative;
    z-index: 5;
  }
  .sheet > figure:hover .tile {
    box-shadow: 0 18px 50px rgba(22, 21, 15, 0.22);
  }
  .sheet > figure:hover .tile img { opacity: 1; }
  /* edge tiles grow inwards so they do not run off the page */
  .sheet > figure:first-child:hover  { transform-origin: left center; }
  .sheet > figure:last-child:hover   { transform-origin: right center; }
}
.sheet > figure:focus-within {
  transform: scale(1.28);
  position: relative;
  z-index: 5;
}
@media (prefers-reduced-motion: reduce) {
  .sheet > figure { transition: none; }
  .sheet > figure:hover { transform: none; }
  .sheet > figure:hover .tile { box-shadow: 0 0 0 2px var(--accent); }
}

.sheet figcaption {
  padding-top: var(--s-2);
  color: var(--ink-40);
  min-height: 1.2em;      /* keeps captioned and uncaptioned tiles level */
}

/* Section ratios, matched to what each format actually is */
.sheet--social  { --tile: 1;      --min: 280px; }  /* 1:1 posts       */
.sheet--banners { --tile: 16/9;   --min: 340px; }  /* display banners */
.sheet--print   { --tile: 1/1.414;--min: 260px; }  /* A-series        */
.sheet--cgi     { --tile: 4/3;    --min: 340px; }  /* renders         */
.sheet--ooh     { --tile: 9/16;   --min: 240px; }  /* standees        */

@media (max-width: 560px) {
  .sheet { grid-template-columns: repeat(auto-fill, minmax(min(var(--min, 280px), 100%), 1fr)); }
}

/* ---- 16c. MASONRY (kept for any free-form gallery) ------- */

.masonry { columns: 3; column-gap: var(--gutter); }
.masonry > figure {
  break-inside: avoid;
  margin: 0 0 var(--gutter);
  display: inline-block;
  width: 100%;
}
.masonry img {
  width: 100%; height: auto; display: block;
  background: var(--paper-2); border: 1px solid var(--rule);
}
.masonry figcaption { padding-top: var(--s-2); color: var(--ink-40); }
.masonry.two { columns: 2; }

@media (min-width: 1600px) { .masonry { columns: 4; } }
@media (max-width: 1100px) { .masonry { columns: 2; } }
@media (max-width: 760px)  { .masonry.two { columns: 1; } }
@media (max-width: 560px)  { .masonry { columns: 1; } }

/* ---- 17. COLUMN SPANS ------------------------------------ */
/* Use these instead of inline grid-column so mobile can collapse them. */

/* The span is set on grid-column-END, not the grid-column shorthand.
   `grid-column: span 7` expands to start:span 7 / end:auto, so pairing it
   with .c-start-6 — which sets grid-column-start — overwrote the span and
   left the element one track wide. Setting the end instead lets a start
   class and a span class combine, which is the whole point of having
   both. Alone, end:span N places identically to the old shorthand. */
.c-1  { grid-column-end: span 1; }
.c-2  { grid-column-end: span 2; }
.c-3  { grid-column-end: span 3; }
.c-4  { grid-column-end: span 4; }
.c-5  { grid-column-end: span 5; }
.c-6  { grid-column-end: span 6; }
.c-7  { grid-column-end: span 7; }
.c-8  { grid-column-end: span 8; }
.c-9  { grid-column-end: span 9; }
.c-10 { grid-column-end: span 10; }
.c-11 { grid-column-end: span 11; }
.c-12 { grid-column: 1 / -1; }
.c-start-6 { grid-column-start: 6; }

@media (max-width: 1024px) {
  .c-start-6 { grid-column-start: auto; }
  .c-7, .c-8 { grid-column-end: span 9; }
}

@media (max-width: 860px) {
  .c-1, .c-2, .c-3, .c-4, .c-5,
  .c-6, .c-7, .c-8, .c-9, .c-10, .c-11 { grid-column: 1 / -1; }
}

/* ---- 18. UTILITIES --------------------------------------- */

.flex { display: flex; }
.between { justify-content: space-between; }
.center { align-items: center; }
.baseline { align-items: baseline; }
.col { flex-direction: column; }
.gap-2 { gap: var(--s-2); } .gap-3 { gap: var(--s-3); }
.gap-4 { gap: var(--s-4); } .gap-5 { gap: var(--s-5); }
.gap-6 { gap: var(--s-6); } .gap-8 { gap: var(--s-8); }
.mt-3 { margin-top: var(--s-3); } .mt-4 { margin-top: var(--s-4); }
.mt-5 { margin-top: var(--s-5); } .mt-6 { margin-top: var(--s-6); }
.mt-7 { margin-top: var(--s-7); } .mt-8 { margin-top: var(--s-8); }
.mb-3 { margin-bottom: var(--s-3); } .mb-4 { margin-bottom: var(--s-4); }
.mb-5 { margin-bottom: var(--s-5); } .mb-6 { margin-bottom: var(--s-6); }
.mb-7 { margin-bottom: var(--s-7); } .mb-8 { margin-bottom: var(--s-8); }
.wrap-nowrap { white-space: nowrap; }
.txt-right { text-align: right; }

.skip {
  position: absolute; left: -9999px;
  background: var(--ink); color: var(--paper); padding: var(--s-3) var(--s-4); z-index: 1000;
}
.skip:focus { left: var(--margin); top: var(--s-3); }

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

/* ---- 19. HDFC PIXEL CASE STUDY --------------------------- */

.pixel-case {
  --pixel-cyan: #19B9CC;
  --pixel-pink: #E5148A;
  --pixel-yellow: #F4DE11;
  --pixel-night: #071211;
  --pixel-selected: var(--pixel-cyan);
}

.pixel-section { padding-block: var(--s-9); }
.pixel-case .proj-title,
.pixel-case .case-quote,
.pixel-case .h2,
.pixel-case .h3 { text-wrap: balance; }
.pixel-case .lead,
.pixel-case .body,
.pixel-case .small { text-wrap: pretty; }
.pixel-kicker { flex-wrap: wrap; }
.pixel-overline { color: var(--accent); margin-bottom: var(--s-4); }
.pixel-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 9fr) minmax(180px, 3fr);
  gap: var(--gutter);
  align-items: end;
}
.pixel-hero-note {
  display: grid;
  gap: var(--s-3);
  padding-bottom: var(--s-5);
  color: var(--ink-70);
}
.pixel-hero-note span { padding-bottom: var(--s-2); border-bottom: 1px solid var(--rule); }
.pixel-hero-media,
.pixel-screen { margin-top: var(--s-7); }
.pixel-hero-media img,
.pixel-screen img {
  display: block;
  width: 100%;
  height: auto;
  background: var(--paper-2);
}
.pixel-hero-media figcaption,
.pixel-screen figcaption,
.pixel-invite-video figcaption {
  display: flex;
  justify-content: space-between;
  padding-top: var(--s-3);
  color: var(--ink-40);
}
.pixel-credits { margin-top: var(--s-4); }

.pixel-copy-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
  align-items: start;
}
.pixel-copy-grid > .case-quote { grid-column: span 5; }
.pixel-body-copy { grid-column: 7 / -1; }
.pixel-personas { margin-top: var(--s-6); border-top: 1px solid var(--rule); }
.pixel-personas article {
  display: grid;
  grid-template-columns: minmax(180px, 1fr) 2fr;
  gap: var(--gutter);
  padding-block: var(--s-4);
  border-bottom: 1px solid var(--rule);
}
.pixel-personas .label { color: var(--ink-70); }
.pixel-audience-label { margin-top: var(--s-7); color: var(--accent); }
.pixel-context-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: var(--s-7);
  border-top: 1px solid var(--rule);
}
.pixel-context-stats article {
  padding: var(--s-4) var(--s-4) var(--s-4) 0;
  border-bottom: 1px solid var(--rule);
}
.pixel-context-stats article + article { padding-left: var(--s-4); border-left: 1px solid var(--rule); }
.pixel-context-stats strong {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 5vw, 4.75rem);
  font-weight: 400;
  line-height: .95;
}
.pixel-context-stats span { display: block; margin-top: var(--s-3); color: var(--ink-70); }
.case-sources { margin-top: var(--s-4); max-width: 68ch; }

.pixel-lab {
  background: var(--pixel-night);
  color: #F3F1E9;
  --rule: #29403D;
  --ink-40: #82918E;
  --ink-70: #BAC4C1;
}
.pixel-lab .sec-num { color: var(--pixel-selected); }
.pixel-lab-grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--gutter);
  margin-top: var(--s-7);
  align-items: stretch;
}
.pixel-builder {
  min-height: 560px;
  border: 1px solid var(--rule);
  padding: var(--s-6);
  display: flex;
}
.pixel-step { width: 100%; display: flex; flex-direction: column; align-items: flex-start; }
.pixel-step[hidden] { display: none; }
.pixel-step .h2 { margin-block: var(--s-5) var(--s-6); max-width: 15ch; }
.pixel-step-label { color: var(--pixel-selected); }
.pixel-step .btn { border-color: currentColor; }
.pixel-step .btn.solid { background: #F3F1E9; color: var(--pixel-night); }
.pixel-step .btn.solid:hover { background: var(--pixel-selected); border-color: var(--pixel-selected); }
.pixel-step .btn:disabled { opacity: .35; cursor: not-allowed; }
.pixel-choice-row,
.pixel-pack-grid,
.pixel-date-grid { width: 100%; }
.pixel-choice-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3); }
.pixel-choice,
.pixel-pack,
.pixel-date {
  min-height: 56px;
  border: 1px solid var(--rule);
  color: inherit;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.pixel-choice:hover,
.pixel-pack:hover,
.pixel-date:hover { border-color: #F3F1E9; }
.pixel-choice.is-selected,
.pixel-pack.is-selected,
.pixel-date.is-selected {
  border-color: var(--pixel-selected);
  background: var(--pixel-selected);
  color: var(--pixel-night);
}
.pixel-choice:nth-child(2).is-selected { background: var(--pixel-pink); border-color: var(--pixel-pink); }
.pixel-choice:nth-child(3).is-selected { background: var(--pixel-yellow); border-color: var(--pixel-yellow); }
.pixel-pack-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); }
.pixel-pack {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-4);
  text-align: left;
}
.pixel-pack strong { font-size: var(--t-small); font-weight: 500; }
.pixel-pack small { color: var(--ink-40); line-height: 1.35; }
.pixel-pack.is-selected small { color: rgba(7, 18, 17, .7); }
.pixel-feedback { color: var(--ink-70); margin-top: var(--s-4); }
.pixel-date-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3); }
.pixel-date { min-height: 88px; font-family: var(--font-display); font-size: clamp(1.8rem, 3vw, 3rem); }
.pixel-date sup { font-family: var(--font-body); font-size: .3em; }
.pixel-summary { width: 100%; margin-top: var(--s-4); }
.pixel-summary div {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: var(--s-4);
  padding-block: var(--s-4);
  border-bottom: 1px solid var(--rule);
}
.pixel-summary dt { color: var(--ink-40); }
.pixel-actions {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-top: auto;
  padding-top: var(--s-6);
}
.pixel-actions.is-end { justify-content: flex-end; }
.pixel-actions .btn { margin-top: 0; }
.pixel-preview {
  position: relative;
  min-height: 560px;
  border: 1px solid var(--rule);
  background: #101E1C;
  overflow: hidden;
}
.pixel-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pixel-preview figcaption {
  position: absolute;
  left: var(--s-5);
  right: var(--s-5);
  bottom: var(--s-5);
  display: grid;
  gap: var(--s-2);
  padding: var(--s-4);
  background: rgba(7, 18, 17, .92);
  border-left: 3px solid var(--pixel-selected);
}
.pixel-preview figcaption .label { color: var(--ink-40); }
.pixel-preview figcaption strong { font-size: var(--t-lead); font-weight: 500; }

.pixel-decision-intro,
.pixel-card-intro {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
  align-items: start;
}
.pixel-decision-intro .case-quote,
.pixel-card-intro .case-quote { grid-column: span 5; }
.pixel-decision-intro .body,
.pixel-card-intro .body { grid-column: 7 / -1; max-width: 62ch; }

.pixel-decisions {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.pixel-decisions article { padding-top: var(--s-4); border-top: 1px solid var(--rule); }
.pixel-decisions .label { color: var(--accent); }
.pixel-decisions .h3 { margin-block: var(--s-4); }

.pixel-asset-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.pixel-asset-slot {
  min-height: 360px;
  display: grid;
  place-items: center;
  padding: var(--s-5);
  border: 1px solid var(--rule);
  background: var(--paper-2);
  color: var(--ink-40);
  text-align: center;
}
.pixel-asset-wide { grid-column: 1 / -1; min-height: 620px; }
.pixel-asset-slot > div { max-width: 34ch; }
.pixel-asset-slot .small { margin-top: var(--s-3); color: currentColor; }

.pixel-card-family { margin-top: var(--s-7); }
.pixel-card-family img {
  display: block;
  width: 100%;
  height: auto;
  background: #211F21;
}
.pixel-card-family figcaption,
.pixel-booth-screen figcaption,
.pixel-website-capture figcaption {
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  padding-top: var(--s-3);
  color: var(--ink-40);
}
.pixel-colour-notes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.pixel-colour-notes article { padding-top: var(--s-4); border-top: 4px solid var(--pixel-yellow); }
.pixel-colour-notes article:nth-child(2) { border-color: var(--pixel-cyan); }
.pixel-colour-notes article:nth-child(3) { border-color: var(--pixel-pink); }
.pixel-colour-notes .small { margin-top: var(--s-3); color: var(--ink-70); }
.pixel-asset-note { margin-top: var(--s-6); max-width: 68ch; }

.pixel-event {
  background: var(--pixel-night);
  color: #F3F1E9;
  --rule: #29403D;
  --ink-40: #82918E;
}
.pixel-event .sec-num { color: var(--pixel-pink); }
.pixel-event-grid {
  display: grid;
  grid-template-columns: 5fr 7fr;
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.pixel-flow { border-top: 1px solid var(--rule); }
.pixel-flow li {
  display: grid;
  grid-template-columns: 52px 1fr 1fr;
  gap: var(--s-4);
  align-items: baseline;
  padding-block: var(--s-4);
  border-bottom: 1px solid var(--rule);
}
.pixel-flow .label { color: var(--pixel-yellow); }
.pixel-flow small { color: var(--ink-40); }
.pixel-event .pixel-asset-slot { background: #101E1C; border-color: var(--rule); color: var(--ink-40); }
.pixel-booth-assets .pixel-asset-wide { min-height: 380px; }
.pixel-booth-screen img { display: block; width: 100%; height: auto; background: #101E1C; }
.pixel-booth-screen figcaption { color: var(--ink-40); }

.pixel-invite-grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.pixel-invite-video video { width: 100%; display: block; background: var(--ink); }
.pixel-invite-copy { display: flex; flex-direction: column; align-items: flex-start; }
.pixel-invite-copy > .body { max-width: 62ch; margin-top: var(--s-5); }
.pixel-invite-slot { width: 100%; margin-top: auto; min-height: 260px; }

.pixel-film-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.pixel-film-intro { margin-top: var(--s-7); max-width: 68ch; }
.pixel-film-poster { position: relative; display: block; aspect-ratio: 9 / 16; overflow: hidden; background: var(--paper-2); }
.pixel-film-poster img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform var(--dur-slow) var(--ease); }
.pixel-film-poster span {
  position: absolute;
  left: var(--s-4);
  bottom: var(--s-4);
  padding: var(--s-3);
  background: var(--ink);
  color: var(--paper);
}
.pixel-film-poster:hover img { transform: scale(1.025); }
.pixel-film .h3 { margin-block: var(--s-4) var(--s-2); }
.pixel-film .small { color: var(--ink-70); }

.pixel-website-grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--gutter);
  margin-top: var(--s-7);
  align-items: start;
}
.pixel-browser-placeholder { border: 1px solid var(--rule); }
.pixel-browser-bar { display: flex; justify-content: space-between; padding: var(--s-3); border-bottom: 1px solid var(--rule); color: var(--ink-40); }
.pixel-browser-placeholder .pixel-asset-slot { border: 0; min-height: 480px; }
.pixel-website-capture img { display: block; width: 100%; height: auto; }
.pixel-website-capture figcaption { padding: var(--s-3); border-top: 1px solid var(--rule); }

.pixel-ecosystem { background: var(--pixel-yellow); color: var(--pixel-night); --rule: rgba(7,18,17,.28); }
.pixel-ecosystem .sec-num { color: var(--pixel-pink); }
.pixel-system-map {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  align-items: center;
  margin-top: var(--s-8);
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 6vw, 6.8rem);
  line-height: .95;
}
.pixel-system-map i { font-family: var(--font-body); font-size: .45em; font-style: normal; color: var(--pixel-pink); }
.pixel-system-copy { margin-top: var(--s-8); max-width: 58ch; }
.pixel-principles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: var(--s-8);
  border-top: 1px solid var(--rule);
}
.pixel-principles article { padding: var(--s-4) var(--s-5) var(--s-4) 0; border-bottom: 1px solid var(--rule); }
.pixel-principles article + article { padding-left: var(--s-5); border-left: 1px solid var(--rule); }
.pixel-principles .small { margin-top: var(--s-3); }

.pixel-outcome-grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.pixel-metric-slots { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-top: 1px solid var(--rule); }
.pixel-metric-slots article { padding: var(--s-4); border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.pixel-metric-slots strong { display: block; font-family: var(--font-display); font-size: clamp(3rem, 6vw, 6rem); font-weight: 400; line-height: 1; }
.pixel-metric-slots .label { display: block; margin-top: var(--s-4); color: var(--ink-40); }
.pixel-evidence-note { margin-top: var(--s-6); max-width: 72ch; }

@media (min-width: 1600px) {
  .pixel-builder,
  .pixel-preview { min-height: 680px; }
  .pixel-asset-wide { min-height: 760px; }
  .pixel-booth-assets .pixel-asset-wide { min-height: 420px; }
}

@media (max-width: 1000px) {
  .pixel-lab-grid,
  .pixel-event-grid,
  .pixel-invite-grid,
  .pixel-website-grid,
  .pixel-outcome-grid { grid-template-columns: 1fr; }
  .pixel-invite-slot { margin-top: var(--s-6); }
  .pixel-browser-placeholder { margin-top: var(--s-4); }
  .pixel-preview { min-height: auto; aspect-ratio: 16 / 9; }
}

@media (max-width: 760px) {
  .pixel-section { padding-block: var(--s-8); }
  .pixel-hero-grid { grid-template-columns: 1fr; }
  .pixel-hero-note { grid-template-columns: repeat(2, 1fr); }
  .pixel-copy-grid { grid-template-columns: 1fr; }
  .pixel-copy-grid > .case-quote,
  .pixel-body-copy { grid-column: 1; }
  .pixel-decision-intro,
  .pixel-card-intro { grid-template-columns: 1fr; }
  .pixel-decision-intro .case-quote,
  .pixel-decision-intro .body,
  .pixel-card-intro .case-quote,
  .pixel-card-intro .body { grid-column: 1; }
  .pixel-personas article { grid-template-columns: 1fr; gap: var(--s-2); }
  .pixel-context-stats,
  .pixel-colour-notes,
  .pixel-principles { grid-template-columns: 1fr; }
  .pixel-context-stats article + article,
  .pixel-principles article + article { padding-left: 0; border-left: 0; }
  .pixel-builder { min-height: 520px; padding: var(--s-5); }
  .pixel-step .h2 { font-size: clamp(2rem, 9vw, 3rem); }
  .pixel-decisions,
  .pixel-film-grid { grid-template-columns: 1fr; }
  .pixel-film-poster { aspect-ratio: 4 / 5; }
  .pixel-asset-grid { grid-template-columns: 1fr; }
  .pixel-asset-wide { grid-column: 1; min-height: 420px; }
  .pixel-asset-slot { min-height: 280px; }
  .pixel-flow li { grid-template-columns: 36px 1fr; }
  .pixel-flow small { grid-column: 2; }
  .pixel-system-map { gap: var(--s-3); }
  .pixel-metric-slots { grid-template-columns: 1fr; }
  .pixel-metric-slots article { border-right: 0; }
  .pixel-actions { align-items: stretch; }
}

@media (max-width: 480px) {
  .pixel-choice-row { grid-template-columns: 1fr; }
  .pixel-pack-grid { grid-template-columns: 1fr; }
  .pixel-date-grid { grid-template-columns: repeat(2, 1fr); }
  .pixel-hero-note { grid-template-columns: 1fr; }
  .pixel-actions { flex-direction: column-reverse; }
  .pixel-actions .btn { width: 100%; text-align: center; }
}

@media (prefers-reduced-motion: reduce) {
  .pixel-film-poster img,
  .pixel-choice,
  .pixel-pack,
  .pixel-date { transition: none; }
  .pixel-film-poster:hover img { transform: none; }
}

/* ---- CLARIO CASE STUDY V2 ------------------------------- */

.clario-case-v2 {
  --clario-ink: #0a0d13;
  --clario-panel: #11151d;
  --clario-paper: #f3f2ee;
  --clario-rule: #d5d4cf;
  --clario-orange: #f26b38;
}

.clario-case-v2 main { overflow: clip; }

.clario-sticky-nav-shell {
  position: sticky;
  top: var(--bar-h);
  z-index: 90;
  color: #fff;
  background: rgba(7, 9, 14, .94);
  border-bottom: 1px solid rgba(255, 255, 255, .12);
  backdrop-filter: blur(18px) saturate(1.15);
}

.clario-sticky-nav-shell .clario-product-nav {
  width: min(calc(100% - 48px), 1440px);
  margin: 0 auto;
  border-bottom: 0;
}

.clario-marketing-hero {
  position: relative;
  min-height: 744px;
  height: calc(100svh - var(--bar-h) - 76px);
  max-height: 904px;
  overflow: hidden;
  isolation: isolate;
  background: #07090e;
  color: #fff;
}

.clario-marketing-bg,
.clario-marketing-shade {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.clario-marketing-bg {
  z-index: 0;
  object-fit: cover;
  object-position: center 52%;
  transform: scale(1.035);
}

.clario-marketing-shade {
  z-index: 1;
  background: linear-gradient(180deg, rgba(0, 0, 0, .9) 0%, rgba(3, 6, 12, .74) 28%, rgba(5, 8, 14, .42) 56%, rgba(2, 4, 8, .78) 100%);
}

.clario-marketing-inner {
  position: relative;
  z-index: 2;
  width: min(calc(100% - 48px), 1440px);
  height: 100%;
  margin: 0 auto;
}

.clario-product-nav {
  display: grid;
  height: 76px;
  grid-template-columns: minmax(150px, .8fr) minmax(420px, 1.4fr) minmax(150px, .8fr);
  align-items: center;
  gap: 24px;
  border-bottom: 1px solid rgba(255, 255, 255, .14);
  font-family: var(--font-sans);
  font-size: .76rem;
}

.clario-product-brand {
  justify-self: start;
  font-size: 1.08rem;
  font-weight: 600;
  letter-spacing: -.03em;
}

.clario-product-brand span,
.clario-dot { color: var(--clario-orange); }

.clario-product-links {
  display: flex;
  justify-content: center;
  gap: clamp(20px, 3vw, 42px);
  color: rgba(255, 255, 255, .65);
}

.clario-product-links a,
.clario-hero-link { transition: color var(--dur-fast) var(--ease); }
.clario-product-links a:hover,
.clario-hero-link:hover { color: #fff; }

.clario-nav-actions {
  display: flex;
  justify-self: end;
  align-items: center;
  gap: 10px;
}

.clario-nav-cta {
  display: inline-flex;
  min-height: 38px;
  padding: 9px 15px;
  align-items: center;
  gap: 10px;
  border: 1px solid rgba(255, 255, 255, .74);
  border-radius: 999px;
  background: rgba(9, 12, 18, .42);
  backdrop-filter: blur(14px);
  font-weight: 500;
}

.clario-nav-cta:hover { background: #fff; color: #0a0d13; }

.clario-nav-figma {
  display: inline-flex;
  width: 38px;
  height: 38px;
  flex: none;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, .74);
  border-radius: 999px;
  background: rgba(9, 12, 18, .42);
  backdrop-filter: blur(14px);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.clario-nav-figma:hover { background: #fff; border-color: #fff; }

.clario-hero-copy {
  position: relative;
  z-index: 3;
  display: flex;
  width: min(100%, 1080px);
  margin: clamp(76px, 10vh, 126px) auto 0;
  align-items: center;
  flex-direction: column;
  text-align: center;
}

.clario-eyebrow {
  padding: 6px 10px;
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 999px;
  background: rgba(7, 10, 16, .24);
  color: rgba(255, 255, 255, .72);
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.clario-hero-copy h1 {
  max-width: none;
  margin: 18px 0 0;
  font-family: var(--font-sans);
  font-size: clamp(3.2rem, 4.65vw, 5.25rem);
  font-weight: 500;
  line-height: .94;
  letter-spacing: -.045em;
  word-spacing: .1em;
  font-kerning: normal;
  font-feature-settings: "kern" 1;
}

.clario-hero-copy h1 .clario-hero-line { display: block; }
.clario-hero-copy h1 .clario-hero-line-primary { color: #fff; }
.clario-hero-copy h1 .clario-hero-line-secondary { color: rgba(255, 255, 255, .58); }

.clario-hero-copy > p:not(.clario-eyebrow) {
  max-width: 52ch;
  margin: 22px auto 0;
  color: rgba(255, 255, 255, .66);
  font-size: .94rem;
  line-height: 1.45;
}

.clario-hero-actions {
  display: flex;
  margin-top: 24px;
  align-items: center;
  gap: 22px;
  font-size: .8rem;
}

.clario-hero-primary {
  display: inline-flex;
  min-height: 44px;
  padding: 11px 18px;
  align-items: center;
  gap: 12px;
  border-radius: 999px;
  background: #fff;
  color: #0a0d13;
  font-weight: 600;
  box-shadow: 0 14px 36px rgba(0, 0, 0, .22);
}

.clario-hero-primary:hover { transform: translateY(-2px); }
.clario-hero-link { color: rgba(255, 255, 255, .74); }

.clario-hero-product {
  --clario-lift: 0px;
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: -25%;
  width: min(84%, 1120px);
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 14px 14px 0 0;
  background: #080a0f;
  box-shadow: 0 -22px 80px rgba(0, 0, 0, .42);
  transform: translate3d(-50%, var(--clario-lift), 0);
  transition: transform 80ms linear;
  will-change: transform;
}

.clario-browser-chrome {
  display: flex;
  height: 34px;
  padding: 0 12px;
  align-items: center;
  gap: 6px;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
  background: #11141a;
}

.clario-browser-chrome span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .28);
}

.clario-browser-chrome b {
  margin-left: 8px;
  color: rgba(255, 255, 255, .36);
  font-family: var(--font-mono);
  font-size: .54rem;
  font-weight: 400;
}

.clario-hero-product img { display: block; width: 100%; height: auto; }

.clario-project-intro { padding-top: clamp(56px, 6vw, 92px); }
.clario-project-kicker { flex-wrap: wrap; }

.clario-intro-grid {
  display: grid;
  margin-top: clamp(44px, 5.5vw, 78px);
  grid-template-columns: minmax(0, 1.15fr) minmax(320px, .85fr);
  gap: clamp(40px, 8vw, 120px);
  align-items: end;
}

.clario-intro-grid .proj-title {
  margin-top: var(--s-3);
  font-size: clamp(4.1rem, 7.5vw, 8rem);
  line-height: .84;
}

.clario-intro-grid > .lead {
  max-width: 40ch;
  padding-bottom: 6px;
}

.clario-credits { margin-top: clamp(48px, 6vw, 82px); }
.clario-case-section { padding-top: clamp(64px, 6vw, 96px); }

.clario-problem-grid,
.clario-journey-intro,
.clario-ai-intro {
  display: grid;
  padding-top: clamp(30px, 3.6vw, 52px);
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--s-4);
}

.clario-problem-grid > .case-quote,
.clario-journey-intro > .case-quote,
.clario-ai-intro > .case-quote { grid-column: span 5; }

.clario-problem-copy,
.clario-journey-intro > .body,
.clario-ai-intro > .body { grid-column: 7 / -1; }

.clario-problem-copy .lead { margin-bottom: var(--s-4); }

.clario-signal-grid {
  display: grid;
  margin-top: var(--s-5);
  grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.clario-signal-grid article {
  display: grid;
  min-height: 138px;
  padding: 20px;
  align-content: space-between;
  border-right: 1px solid var(--rule);
}

.clario-signal-grid article:last-child { border-right: 0; }
.clario-signal-grid strong { font-family: var(--font-display); font-size: 3rem; font-weight: 400; }
.clario-signal-grid span { max-width: 16ch; color: var(--muted); font-size: .76rem; line-height: 1.35; }

.clario-journey-band {
  margin-top: clamp(40px, 3vw, 48px);
  padding-bottom: clamp(64px, 5vw, 80px);
  background: var(--paper-2);
}

.clario-journey-band .clario-case-section { padding-top: clamp(48px, 4vw, 64px); }

.clario-journey {
  margin: clamp(38px, 4.5vw, 64px) 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.clario-journey li {
  position: relative;
  display: flex;
  min-height: 338px;
  padding: 20px 16px 18px;
  flex-direction: column;
  border-right: 1px solid var(--rule);
}

.clario-journey li:last-child { border-right: 0; }
/* was #97958e at 2.51:1 — the step numerals are the only wayfinding in
   this list, so they need to be readable, not decorative */
.clario-journey-index { color: var(--ink-40); font-family: var(--font-mono); font-size: .68rem; }
.clario-journey li > .label { margin-top: 64px; color: var(--ink-40); }
.clario-journey h3 { margin-top: 10px; font-size: 1.16rem; font-weight: 600; letter-spacing: -.02em; }
.clario-journey li > p:not(.label) { margin-top: 10px; color: var(--ink-70); font-size: .78rem; line-height: 1.45; }

.clario-mode {
  width: fit-content;
  margin-top: auto;
  padding: 6px 8px;
  border: 1px solid #c3c1ba;
  border-radius: 999px;
  color: #67655f;
  font-size: .62rem;
  font-weight: 500;
}

.clario-mode.is-ai { border-color: #8d7aa5; color: #655074; }
.clario-mode.is-human { border-color: #8b9a8e; color: #506756; }

.clario-walkthrough {
  height: min(780px, calc(100svh - 24px));
  min-height: 680px;
  margin-top: clamp(32px, 4vw, 56px);
  border-radius: 10px;
  background: #0a0d13;
}

.clario-walkthrough .clearance-flow-bar,
.clario-walkthrough .clearance-flow-controls { background: #0d1118; }
.clario-walkthrough .clearance-flow-repo { background: #0a0d13; }
.clario-walkthrough .clearance-flow-workspace { background: #0b0e14; }
.clario-walkthrough .clearance-workspace-head h2 { max-width: 14ch; font-size: clamp(3.2rem, 5vw, 5.4rem); }
.clario-walkthrough .clearance-workspace-preview { width: min(92%, 1120px); margin-top: 32px; }

.clearance-flow-handoff {
  display: grid;
  padding: clamp(34px, 5vw, 68px);
  grid-template-columns: minmax(0, .9fr) minmax(420px, 1.1fr);
  gap: clamp(40px, 8vw, 110px);
  align-items: center;
  background: #0a0d13;
}

.clario-handoff-copy h2 {
  max-width: 10ch;
  margin: var(--s-3) 0 0;
  font-family: var(--font-display);
  font-size: clamp(4rem, 6vw, 7rem);
  font-weight: 400;
  line-height: .86;
  letter-spacing: -.045em;
}

.clario-handoff-copy > p:not(.label) {
  max-width: 44ch;
  margin-top: var(--s-4);
  color: rgba(255, 255, 255, .62);
  line-height: 1.5;
}

.clario-handoff-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid rgba(255, 255, 255, .18);
  border-bottom: 1px solid rgba(255, 255, 255, .18);
}

.clario-handoff-stats article {
  display: grid;
  min-height: 190px;
  padding: 22px 18px;
  align-content: space-between;
  border-right: 1px solid rgba(255, 255, 255, .18);
}

.clario-handoff-stats article:last-child { border-right: 0; }
.clario-handoff-stats strong { font-family: var(--font-display); font-size: clamp(3.2rem, 5vw, 5rem); font-weight: 400; }
.clario-handoff-stats span { color: rgba(255, 255, 255, .58); font-size: .72rem; }

.clario-decision-grid {
  display: grid;
  margin-top: clamp(32px, 4vw, 56px);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
}

.clario-decision-grid article {
  min-height: 250px;
  padding: clamp(24px, 3vw, 38px);
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.clario-decision-grid .label { color: var(--muted); }
.clario-decision-grid h3 { max-width: 14ch; margin-top: 44px; font-family: var(--font-display); font-size: clamp(2.4rem, 4vw, 4.2rem); font-weight: 400; line-height: .9; }
.clario-decision-grid p { max-width: 45ch; margin-top: 18px; color: var(--muted); font-size: .86rem; line-height: 1.5; }

.clario-product-proof {
  margin-top: clamp(48px, 6vw, 88px);
  padding: 10px;
  border-radius: 10px;
  background: #090c12;
  color: #fff;
}

.clario-proof-bar {
  display: flex;
  min-height: 44px;
  padding: 0 6px 8px;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  color: rgba(255, 255, 255, .52);
}

.clario-proof-bar a { color: #fff; border-bottom: 1px solid rgba(255, 255, 255, .45); }
.clario-proof-links { display: flex; align-items: center; gap: var(--s-4); }
.clario-product-proof > img { display: block; width: 100%; height: auto; }
.clario-product-proof figcaption { display: flex; padding: 12px 6px 4px; justify-content: space-between; gap: var(--s-4); color: rgba(255, 255, 255, .52); }

.clario-ai-band {
  margin-top: clamp(40px, 3vw, 48px);
  padding-bottom: clamp(64px, 5vw, 80px);
  background: #0a0d13;
  color: #f4f3ef;
}

.clario-ai-band .clario-case-section { padding-top: clamp(48px, 4vw, 64px); }
.clario-ai-band .sec-head { border-color: rgba(255, 255, 255, .22); }
.clario-ai-band .sec-num,
.clario-ai-band .sec-meta { color: rgba(255, 255, 255, .46); }
.clario-ai-band .case-quote { color: #f4f3ef; }
.clario-ai-intro > .body { color: rgba(255, 255, 255, .64); }

.clario-responsibility-grid {
  display: grid;
  margin-top: clamp(42px, 5vw, 72px);
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid rgba(255, 255, 255, .2);
  border-bottom: 1px solid rgba(255, 255, 255, .2);
}

.clario-responsibility-grid article {
  min-height: 330px;
  padding: clamp(24px, 3vw, 38px);
  border-right: 1px solid rgba(255, 255, 255, .2);
}

.clario-responsibility-grid article:last-child { border-right: 0; }
.clario-responsibility-grid article.is-ai { background: #18131d; }
.clario-responsibility-grid .label { color: rgba(255, 255, 255, .5); }
.clario-responsibility-grid h3 { margin-top: 54px; font-family: var(--font-display); font-size: clamp(2.5rem, 4vw, 4.2rem); font-weight: 400; line-height: .9; }
.clario-responsibility-grid ul { display: grid; margin-top: 36px; padding: 0; gap: 12px; list-style: none; }
.clario-responsibility-grid li { padding-top: 12px; border-top: 1px solid rgba(255, 255, 255, .12); color: rgba(255, 255, 255, .62); font-size: .8rem; }
.clario-boundary-note { margin-top: 20px; color: rgba(255, 255, 255, .46); }

.clario-validation-grid {
  display: grid;
  margin-top: clamp(32px, 4vw, 56px);
  grid-template-columns: minmax(0, 1fr) minmax(340px, .9fr);
  gap: clamp(48px, 8vw, 120px);
}

.clario-validation-grid ol { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--rule); }
.clario-validation-grid li { display: grid; padding: 22px 0; grid-template-columns: 44px 1fr; gap: 14px; border-bottom: 1px solid var(--rule); }
.clario-validation-grid li > span { color: var(--muted); font-family: var(--font-mono); font-size: .68rem; }
.clario-validation-grid strong { font-size: .92rem; font-weight: 600; }
.clario-validation-grid li p { max-width: 48ch; margin-top: 7px; color: var(--muted); font-size: .78rem; line-height: 1.45; }

@media (max-width: 1100px) {
  .clario-marketing-hero { min-height: 684px; }
  .clario-product-nav { grid-template-columns: 1fr auto; }
  .clario-product-links { display: none; }
  .clario-hero-copy { margin-top: 86px; }
  .clario-hero-product { bottom: -10%; width: 92%; }
  .clario-journey { overflow-x: auto; grid-template-columns: repeat(6, minmax(210px, 1fr)); scrollbar-width: thin; }
  .clearance-flow-handoff { grid-template-columns: 1fr; overflow-y: auto; }
  .clario-handoff-copy h2 { font-size: clamp(3.5rem, 8vw, 5.4rem); }
}

@media (max-width: 760px) {
  .clario-marketing-hero { min-height: 0; height: auto; max-height: none; }
  .clario-marketing-inner {
    width: min(calc(100% - 28px), 1440px);
    padding-top: 34px;
    padding-bottom: 24px;
    height: auto;
    box-sizing: border-box;
  }
  .clario-sticky-nav-shell .clario-product-nav { width: min(calc(100% - 28px), 1440px); }
  .clario-product-nav { height: 66px; }
  .clario-nav-cta { min-height: 36px; padding: 8px 12px; }
  .clario-nav-figma { width: 34px; height: 34px; }
  .clario-hero-copy { margin-top: 0; }
  .clario-hero-copy h1 {
    max-width: 100%;
    margin-top: 14px;
    font-size: clamp(1.75rem, 7.4vw, 2.1rem);
    line-height: .98;
    letter-spacing: -.036em;
  }
  .clario-hero-copy h1 .clario-hero-line-primary {
    max-width: none;
    margin-inline: auto;
    white-space: nowrap;
  }
  .clario-hero-copy h1 .clario-hero-line-secondary {
    margin-top: 2px;
    font-size: 1em;
    white-space: nowrap;
  }
  .clario-hero-copy > p:not(.clario-eyebrow) {
    max-width: 36ch;
    margin-top: 14px;
    font-size: .8rem;
    text-wrap: balance;
  }
  .clario-hero-actions { margin-top: 18px; flex-direction: column; gap: 9px; }
  .clario-hero-primary { min-height: 42px; padding: 10px 17px; }
  .clario-marketing-hero .clario-hero-product {
    position: relative;
    left: auto;
    bottom: auto;
    width: calc(100% - 2px);
    margin: 26px auto 0;
    transform: none;
  }
  .clario-browser-chrome { height: 26px; }
  .clario-intro-grid,
  .clario-validation-grid { grid-template-columns: 1fr; }
  .clario-intro-grid { margin-top: 44px; gap: 32px; }
  .clario-intro-grid .proj-title {
    max-width: 12ch;
    font-size: clamp(3.15rem, 14vw, 4.5rem);
    line-height: .92;
    text-wrap: balance;
  }
  .clario-problem-grid,
  .clario-journey-intro,
  .clario-ai-intro { grid-template-columns: 1fr; }
  .clario-problem-grid > .case-quote,
  .clario-journey-intro > .case-quote,
  .clario-ai-intro > .case-quote,
  .clario-problem-copy,
  .clario-journey-intro > .body,
  .clario-ai-intro > .body { grid-column: 1; }
  .clario-signal-grid { grid-template-columns: 1fr; }
  .clario-signal-grid article { min-height: 108px; border-right: 0; border-bottom: 1px solid var(--rule); }
  .clario-signal-grid article:last-child { border-bottom: 0; }
  .clario-journey li { min-height: 300px; }
  .clario-walkthrough { min-height: 760px; border-radius: 0; }
  .clario-walkthrough .clearance-flow-menu { overflow-x: auto; grid-template-columns: repeat(3, minmax(150px, 1fr)); }
  .clario-walkthrough .clearance-flow-workspace { padding-top: 28px; }
  .clario-walkthrough .clearance-workspace-head { align-items: flex-start; flex-direction: column; }
  .clario-walkthrough .clearance-workspace-head h2 { font-size: 3.15rem; }
  .clario-walkthrough .clearance-workspace-preview { width: 150%; margin-left: -8%; }
  .clearance-flow-handoff { padding: 28px 20px; }
  .clario-handoff-stats { grid-template-columns: 1fr; }
  .clario-handoff-stats article { min-height: 92px; border-right: 0; border-bottom: 1px solid rgba(255, 255, 255, .18); }
  .clario-handoff-stats article:last-child { border-bottom: 0; }
  .clario-decision-grid { grid-template-columns: 1fr; }
  .clario-decision-grid article { min-height: 220px; }
  .clario-product-proof { margin-inline: calc(var(--gutter) * -1); border-radius: 0; }
  .clario-product-proof figcaption { flex-direction: column; }
  .clario-responsibility-grid { grid-template-columns: 1fr; }
  .clario-responsibility-grid article { min-height: 290px; border-right: 0; border-bottom: 1px solid rgba(255, 255, 255, .2); }
  .clario-responsibility-grid article:last-child { border-bottom: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .clario-hero-product { transition: none; transform: translate3d(-50%, 0, 0); }
  .clario-hero-primary { transition: none; }
}

/* ---- CLARIO: EVIDENCE, STATUS, SCOPE ---------------------- */
/* Added for the deeper product sections. Everything here follows the
   existing case-study conventions: 12-column wrap grid, mono .label,
   hairline rules, no new colour language beyond the status states. */

.clario-cost-intro,
.clario-status-intro,
.clario-reject-intro,
.clario-scope-intro {
  display: grid;
  padding-top: clamp(30px, 3.6vw, 52px);
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--s-4);
}
.clario-cost-intro > .case-quote,
.clario-status-intro > .case-quote,
.clario-reject-intro > .case-quote,
.clario-scope-intro > .case-quote { grid-column: span 5; }
.clario-cost-intro > .body,
.clario-status-intro > .body,
.clario-scope-intro > .body { grid-column: 7 / -1; }
.clario-reject-intro > .case-quote { grid-column: span 8; }

/* -- the two failure scenarios -- */
.clario-scenarios {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.clario-scenario {
  border-top: 1px solid var(--ink);
  padding-top: var(--s-5);
}
.clario-scenario h3 {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: var(--s-2) 0 var(--s-4);
}
.clario-scenario .body + .body { margin-top: var(--s-4); }
.clario-scenario-note {
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--ink-70);
}
.clario-scenario-flag { color: var(--accent); font-weight: 700; }

/* -- the capacity cascade -- */
.clario-cascade {
  margin-top: var(--s-8);
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gutter);
  padding-top: var(--s-6);
  border-top: 1px solid var(--ink);
}
.clario-cascade-copy { grid-column: span 5; }
.clario-cascade-copy h3 {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: var(--s-2) 0 var(--s-4);
}
.clario-cascade-chain {
  grid-column: 7 / -1;
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: cascade;
}
/* Each step is indented one notch further than the last, so the drift away
   from the original campaign is visible as a shape before it is read. */
.clario-cascade-chain li {
  counter-increment: cascade;
  position: relative;
  padding: var(--s-3) 0 var(--s-3) var(--s-6);
  border-bottom: 1px solid var(--rule);
  font-size: 0.95rem;
}
.clario-cascade-chain li::before {
  content: counter(cascade, decimal-leading-zero);
  position: absolute;
  left: 0;
  font-family: var(--mono, var(--font-mono));
  font-size: 0.72rem;
  color: var(--ink-40);
  letter-spacing: 0.06em;
}
.clario-cascade-chain li:nth-child(n+4) { padding-left: calc(var(--s-6) + var(--s-4)); }
.clario-cascade-chain li:nth-child(n+6) { padding-left: calc(var(--s-6) + var(--s-7)); }
.clario-cascade-chain li:last-child {
  border-bottom: 0;
  color: var(--accent);
  font-weight: 500;
}

/* -- where the gate belongs -- */
.clario-gate-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gutter);
  padding-top: clamp(30px, 3.6vw, 52px);
}
.clario-gate-copy { grid-column: span 6; }
.clario-gate-copy .body + .body { margin-top: var(--s-4); }
.clario-pipeline {
  grid-column: 8 / -1;
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.9rem;
}
.clario-pipeline li {
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--rule);
  color: var(--ink-40);
}
/* The two Clario steps are the only ones set in ink — the rest of the
   pipeline is context, and should read as context. */
.clario-pipeline li.is-clario {
  color: var(--ink);
  font-weight: 500;
  border-left: 2px solid var(--accent);
  padding-left: var(--s-4);
}

/* -- roles -- */
.clario-roles { margin-top: var(--s-8); padding-top: var(--s-6); border-top: 1px solid var(--ink); }
.clario-roles-head h3 {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin-bottom: var(--s-3);
  max-width: 22ch;
}
.clario-roles-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gutter);
  margin: var(--s-6) 0 var(--s-5);
}
.clario-roles-grid article {
  border-top: 1px solid var(--rule);
  padding-top: var(--s-4);
}
.clario-roles-grid article.is-primary { border-top-color: var(--ink); }
.clario-roles-grid h4 {
  font-size: 1.05rem;
  font-weight: 500;
  margin: var(--s-2) 0 var(--s-3);
}
.clario-roles-grid p:last-child { font-size: 0.9rem; line-height: 1.5; color: var(--ink-70); }
/* The excluded role is greyed and struck at the title, so the exclusion is
   legible without reading the copy. */
.clario-roles-grid article.is-excluded { opacity: 0.62; }
.clario-roles-grid article.is-excluded h4 { text-decoration: line-through; text-decoration-thickness: 1px; }

/* -- status model -- */
.clario-status-set {
  list-style: none;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: grid;
  gap: 0;
  border-top: 1px solid var(--ink);
}
.clario-state {
  display: grid;
  grid-template-columns: 56px 1fr;
  gap: var(--s-4);
  align-items: start;
  padding: var(--s-5) 0;
  border-bottom: 1px solid var(--rule);
}
/* The mark carries the state on its own. Colour is a reinforcement, never
   the signal — the glyph and the written label both survive greyscale and
   are what a colour-blind reader actually uses. */
.clario-state-mark {
  font-size: 1.5rem;
  line-height: 1;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid currentColor;
  border-radius: 50%;
}
.clario-state.is-cleared  .clario-state-mark { color: var(--ok); }
.clario-state.is-limited  .clario-state-mark { color: #8a6220; }
.clario-state.is-conflict .clario-state-mark { color: var(--stop); }
.clario-state h3 { font-size: var(--t-h3); font-weight: 500; letter-spacing: -0.01em; }
.clario-state-def { margin: var(--s-2) 0 var(--s-3); max-width: 46ch; }
.clario-state-fact {
  font-size: 0.9rem;
  color: var(--ink-70);
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
}
.clario-state-fact .label { margin-right: var(--s-3); color: var(--ink-40); }

.clario-margin-note {
  margin-top: var(--s-6);
  padding: var(--s-5);
  background: var(--paper-2);
  border-left: 2px solid var(--accent);
}
.clario-margin-note .body { max-width: 68ch; }

/* -- market / channel -- */
.clario-dimension-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.clario-dimension-grid article { border-top: 1px solid var(--ink); padding-top: var(--s-5); }
.clario-dimension-grid h3 {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: var(--s-2) 0 var(--s-3);
}
.clario-market-list { list-style: none; margin: var(--s-5) 0 0; padding: 0; }
.clario-market-list li {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: var(--s-4);
  align-items: baseline;
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.9rem;
}
.clario-market-list b { font-family: var(--mono, var(--font-mono)); font-weight: 500; font-size: 0.8rem; }
.clario-market-list em { font-style: normal; font-size: 0.8rem; white-space: nowrap; }
.clario-market-list em.is-ok   { color: var(--ok); }
.clario-market-list em.is-warn { color: #8a6220; }

.clario-principle-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid var(--ink);
}
.clario-principle-row p { font-size: 1.05rem; line-height: 1.45; max-width: 40ch; }
.clario-principle-row .label { display: block; color: var(--accent); margin-bottom: var(--s-2); }

/* -- rejected -- */
.clario-reject-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter) var(--gutter);
  margin-top: var(--s-7);
}
.clario-reject-grid article { border-top: 1px solid var(--ink); padding-top: var(--s-4); }
.clario-reject-grid h3 {
  font-size: 1.05rem;
  font-weight: 500;
  margin-bottom: var(--s-3);
  /* struck through: these are the things the product does not do */
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--ink-40);
}
.clario-reject-grid p { font-size: 0.9rem; line-height: 1.55; color: var(--ink-70); }

/* -- MVP scope -- */
.clario-scope-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--s-7);
}
.clario-scope-col { border-top: 1px solid var(--ink); padding-top: var(--s-4); }
.clario-scope-col ul { list-style: none; margin: var(--s-4) 0 0; padding: 0; }
.clario-scope-col li {
  padding: var(--s-3) 0 var(--s-3) var(--s-5);
  border-bottom: 1px solid var(--rule);
  position: relative;
  font-size: 0.95rem;
}
.clario-scope-col.is-keep li::before {
  content: "\2713";
  position: absolute; left: 0; color: var(--ok);
}
.clario-scope-col.is-cut li { color: var(--ink-40); text-decoration: line-through; text-decoration-thickness: 1px; }
.clario-scope-col.is-cut li::before {
  content: "\2014";
  position: absolute; left: 0; text-decoration: none; color: var(--ink-40);
}

/* -- success metrics -- */
.clario-metrics { margin-top: var(--s-8); padding-top: var(--s-6); border-top: 1px solid var(--ink); }
.clario-metrics-head h3 {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin-bottom: var(--s-3);
}
.clario-metric-primary {
  margin: var(--s-6) 0;
  padding: var(--s-5);
  background: var(--paper-2);
  border-left: 2px solid var(--accent);
}
.clario-metric-statement {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2.6vw, 2.1rem);
  line-height: 1.15;
  margin: var(--s-3) 0;
  max-width: 26ch;
}
.clario-metric-list { list-style: none; margin: 0 0 var(--s-5); padding: 0; }
.clario-metric-list li {
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.95rem;
  color: var(--ink-70);
}

/* -- responsive -- */
@media (max-width: 900px) {
  .clario-reject-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .clario-roles-grid  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 760px) {
  .clario-cost-intro > .case-quote,
  .clario-status-intro > .case-quote,
  .clario-reject-intro > .case-quote,
  .clario-scope-intro > .case-quote,
  .clario-cost-intro > .body,
  .clario-status-intro > .body,
  .clario-scope-intro > .body,
  .clario-gate-copy,
  .clario-pipeline,
  .clario-cascade-copy,
  .clario-cascade-chain { grid-column: 1 / -1; }

  .clario-scenarios,
  .clario-dimension-grid,
  .clario-principle-row,
  .clario-scope-grid,
  .clario-reject-grid,
  .clario-roles-grid { grid-template-columns: 1fr; }

  /* the cascade indent is a desktop device; on a phone it just eats width */
  .clario-cascade-chain li:nth-child(n+4),
  .clario-cascade-chain li:nth-child(n+6) { padding-left: var(--s-6); }

  .clario-state { grid-template-columns: 40px 1fr; gap: var(--s-3); }
  .clario-state-mark { width: 34px; height: 34px; font-size: 1.15rem; }
  .clario-market-list li { grid-template-columns: 1fr auto; row-gap: 2px; }
  .clario-market-list em { grid-column: 2; }
}

/* These were once literal greys, nudged by hundredths against a band tint of
   #ECEBE6 so the muted tokens would clear 4.5 on it. Two things were wrong
   with that. The tint is real but the journey list on the clearance page is
   not inside the band -- it sits on --paper, so the nudge was correcting for
   a surface that was not under it. And a literal grey cannot follow a theme:
   on the dark ground the step copy measured 2.76:1 and simply was not there.
   Tokens now. The light theme gains a little contrast rather than losing it,
   because --ink-70 at 5.91:1 is stronger than the 5.55:1 it replaces. */
.clario-journey-band .sec-head .sec-meta { color: var(--ink-40); }
.clario-journey-index { color: var(--ink-40); }
.clario-journey li > .label { color: var(--ink-40); }

/* ============================================================
   CREATIVE SYSTEMS CASE STUDY (work-creative-systems.html)
   Scoped with a .cs- prefix so nothing here can reach the
   other project pages.
   ============================================================ */

/* Sidebar note sitting alongside body copy. Rule on the leading
   edge rather than a box, to stay in the Swiss register. */
.cs-aside {
  border-left: 1px solid var(--rule);
  padding-left: var(--s-5);
}
.cs-aside .small { color: var(--ink-70); }

/* -- Attribute -> decision mapping ------------------------- */
.cs-map {
  border-top: 1px solid var(--rule);
}
.cs-map-row {
  display: grid;
  grid-template-columns: minmax(140px, 200px) 32px 1fr;
  align-items: start;
  gap: var(--s-4);
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--rule);
}
.cs-map-in { color: var(--ink); padding-top: 2px; }
.cs-map-arrow {
  color: var(--accent);
  font-family: var(--font-mono);
  padding-top: 1px;
}
.cs-map-out { display: block; }
.cs-map-out strong {
  display: block;
  font-size: var(--t-h3);
  font-family: var(--font-display);
  font-weight: 400;
  margin-bottom: var(--s-2);
}
.cs-map-out .small { display: block; max-width: 64ch; }

/* The final row is the "never derived" boundary — it reads as a
   constraint, not another mapping, so it gets the accent. */
.cs-map-row:last-child .cs-map-in { color: var(--accent-text); }

/* -- Three-column comparison blocks ------------------------ */
.cs-cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-7);
  border-top: 1px solid var(--rule);
  padding-top: var(--s-6);
}
.cs-col .small { color: var(--ink-70); }

.cs-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.cs-list li {
  padding-block: var(--s-3);
  border-bottom: 1px solid var(--rule);
  color: var(--ink-70);
}
.cs-list li:first-child { padding-top: 0; }
.cs-list li:last-child { border-bottom: 0; }

@media (max-width: 900px) {
  .cs-cols { grid-template-columns: 1fr; gap: var(--s-6); }
  .cs-aside {
    border-left: 0;
    border-top: 1px solid var(--rule);
    padding-left: 0;
    padding-top: var(--s-5);
    margin-top: var(--s-6);
  }
}

@media (max-width: 640px) {
  /* Stack the mapping: the arrow column stops earning its width. */
  .cs-map-row { grid-template-columns: 1fr; gap: var(--s-2); }
  .cs-map-arrow { display: none; }
  .cs-map-in { color: var(--accent-text); }
}





/* ---- CLARIO: STANDING STRIP ------------------------------
   Fifteen-second credibility note between the hero and the first
   claim. Deliberately compact: it exists to establish that the
   author operates this pipeline, then get out of the way. */
.clario-standing { padding-block: var(--s-7) var(--s-6); }
.clario-standing-inner {
  border-top: 1px solid var(--rule);
  padding-top: var(--s-5);
  display: grid;
  grid-template-columns: minmax(160px, 1fr) 3fr;
  gap: var(--s-6);
  align-items: start;
}
.clario-standing .lead { max-width: 64ch; }
.clario-standing-figs {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4) var(--s-8);
}
.clario-standing-figs li { color: var(--ink-40); }
.clario-standing-figs strong {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: 400;
  color: var(--ink);
  margin-bottom: 2px;
}
@media (max-width: 760px) {
  .clario-standing-inner { grid-template-columns: 1fr; gap: var(--s-4); }
  .clario-standing-figs { gap: var(--s-4) var(--s-6); }
}






/* .case-note rides --accent-soft, which is dark in this theme, so it wants
   the light ink -- not the pinned dark ink the journey band needs. */

/* The journey band keeps its light #ECEBE6 surface in dark mode, so the
   section numeral inside it needs the LIGHT-theme blue, not the lifted one
   that was computed for the dark surface. */

/* These hero/nav links carry no colour of their own and were inheriting the
   browser default blue. Invisible in light mode against the dark nav, plainly
   wrong in dark. They follow their container in both themes now. */
.clario-product-links a { color: inherit; }



/* ---- Profiles: align to the column edge -------------------------
   The list sat left-aligned inside a half-width column, which read as
   floating in the middle of the page rather than belonging to an edge. */
.about-profiles .link-list { justify-content: flex-end; }
.about-profiles .label { text-align: right; }
@media (max-width: 760px) {
  .about-profiles .link-list { justify-content: flex-start; }
  .about-profiles .label { text-align: left; }
}

/* ---- About page ------------------------------------------- */
/* These two carried inline padding before, which meant the mobile block
   below could not override them without !important. Same values, now
   somewhere a media query can reach. */
/* ---- About: measure -----------------------------------------------
   The site's 1920px measure was set for image grids, where a wide row is
   a virtue. About is almost entirely prose, and at 1920 it stops being
   readable: on a 2560px display the experience bullets were running to
   roughly 190 characters a line, against a comfortable 45–75. The eye
   loses the start of the next line and the page reads as a wall.

   The page keeps the site's full width — that is what leaves room for the
   photographs beside the intro — so the fix is applied to the text rather
   than the container: no paragraph runs past 68 characters whatever the
   viewport does. Left alone, the experience bullets reached roughly 190
   on a 2560px display, against a comfortable 45–75. */
.page-about .def-val > * { max-width: 68ch; }


/* Open at the top, close at the bottom. The air above the portrait is the
   page drawing breath before it speaks; the figures follow immediately
   after the description, so there is nothing to cross to reach them. */
/* 64px above, matching Contact. It was 96px — About was opening a full
   32px lower than every other page for no reason anyone could see, and
   with the bento now sitting beside the headline the extra air pushed the
   whole landing down the screen. */
/* 64px both ends. The bottom was 32 against the top's 64, so the bento
   sat twice as close to the brand strip below it as to the header above. */
.about-intro { padding-block: var(--s-8); }
.about-end { padding-bottom: var(--s-9); }

.about-eyebrow { margin-bottom: var(--s-6); }

/* The opening statement. Long enough to have a shape, short enough that
   the eye takes it in one go. The measure is set in em rather than ch on
   a wide screen: ch is the width of a "0", and Instrument Serif's figures
   are far narrower than its lowercase, so a ch measure on a display serif
   runs a good deal longer than it reads. */
.about-statement {
  max-width: 22ch;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 5.6vw, 4.6rem);
  font-weight: 400;
  font-style: normal;
  color: var(--ink);
  /* 1.08 rather than the old 1.02. One line carries an italic with a p and
     a g in it, and Instrument Serif Italic drops its descenders further
     than the roman — at 1.02 those tails met the ascenders of the line
     below. */
  line-height: 1.08;
  letter-spacing: -0.02em;
  /* The breaks are authored now, so there is nothing left to balance. */
  text-wrap: initial;
}

/* The one emphasised phrase. Italic of the SAME family rather than a
   second typeface, and the accent carried by the words that matter
   instead of the whole headline. At 4.21:1 on paper and 3.52:1 over a
   background dot the vermillion clears AA's 3:1 for large text, and this
   never renders below 34px. */
.stmt-em {
  font-style: italic;
  color: var(--accent);
}

/* Authored lines on the two-column landing; inline again below it so a
   narrow screen wraps on its own terms. */
@media (min-width: 761px) {
  .about-statement .stmt-line { display: block; }
}

/* On the two-column landing the headline is sized off ITS OWN COLUMN, not
   the viewport. A vw-based cap could not fill a column that keeps growing:
   at 2560 the type stopped at 4.6rem while the column ran on to 1468px,
   which is where the dead gutter came from. 7.4cqw holds the headline at
   three lines whatever the column does, so the width is used instead of
   left over.

   Container queries need an explicit container; .about-open-copy declares
   one below. */
@media (min-width: 981px) {
  .about-open-copy { container-type: inline-size; }
  .about-statement {
    max-width: none;
    /* 9.1, up from 7.4. That number was tuned for three roman lines that
       set their own breaks. Instrument Serif Italic runs narrower, and
       four authored lines are each shorter than a wrapped one, so the
       longest line was filling only 74% of its column. 9.1 puts it at
       about 92% at every width, which is as close as the longest line
       should sit to the measure. */
    font-size: clamp(2.4rem, 9.1cqw, 8.5rem);
  }
}

/* Single column below 981: the copy column is the full page width, so cqw
   would jump the headline from ~34px to ~68px across the breakpoint.
   Viewport units keep it continuous. */
@media (min-width: 761px) and (max-width: 980px) {
  .about-statement { max-width: min(100%, 15em); }
}

/* The pen-tool anchors from the home hero, set inline. Sized in em so
   they scale with the statement, and nudged off the baseline because an
   optical centre on a serif line sits above the true centre. */
.anchor-glyph {
  display: inline-block;
  width: 0.3em;
  height: 0.3em;
  margin-left: 0.14em;
  vertical-align: 0.34em;
  fill: var(--accent);
}
.anchor-glyph--open {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.4;
}

/* ---- The landing: copy left, pictures right ----------------------
   The photographs used to sit two thirds down the page, which meant the
   reader met the CV before they met the person. The collage is capped
   rather than given a fraction: at half of 1824 it would be 876 wide and,
   at the reference proportions, 1064 tall — one picture block taller than
   most viewports. */
/* Two equal columns, and the whole composition capped rather than left to
   stretch.

   The cap is the fix for the widening gutter. With one column a fraction
   and the other capped in px, the headline's line length stayed put at
   ~679px while its column kept growing: 80px of gutter at 1600, 445px at
   2844. Equal fr columns inside a 1440 cap hold the same relationship at
   every width above it, and below 1536 the cap does not bind at all — so
   on every ordinary laptop this still runs the full measure and the
   bento's right edge still lines up with the nav above it. */
/* Full width, no cap: the bento's right edge lines up with every section
   below it, the way Archive and Contact do.

   The bento is capped instead, because its height is a function of its
   width — uncapped at 2560 it would be 1141px tall. The copy column takes
   whatever is left, and the headline is sized off that column (below) so
   the extra width is used rather than left as gutter.

   The copy is centred against the bento. That is only safe because the
   eyebrow now sits outside this grid — when it was inside, centring
   carried it down too and About opened ~60px lower than Archive and
   Contact despite identical section padding. */
.about-open {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, min(46%, 800px));
  gap: clamp(40px, 4vw, 88px);
  align-items: center;
}

@media (max-width: 980px) {
  .about-open {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    max-width: none;
  }
}

/* The subhead. One step down from the display serif, held narrower than
   the headline above it so the two rag against each other rather than
   reading as one grey block. */
.about-sub {
  max-width: 46ch;
  margin: var(--s-6) 0 0;
  font-size: clamp(1.1rem, 1.5vw, 1.35rem);
  line-height: 1.45;
  letter-spacing: -.008em;
  color: var(--ink-70);     /* 5.91:1 on paper, 5.24:1 over a dot */
}

.about-meta .avail { display: inline-flex; align-items: center; gap: 8px; }

/* width, not max-width: .portrait--swap holds two absolutely-positioned
   images and so has no intrinsic width of its own. In the old two-column
   layout the grid column gave it one; in a justify-items:start stack
   nothing does, and it collapsed to the width of its own border. */
.about-face .portrait--swap { width: 100%; max-width: 260px; }

/* Three steps down from the statement rather than one cliff: 74px serif,
   28px sans in full ink, 16px sans in secondary ink. The measures step too
   — the lead is held narrower than the paragraph under it, so the two rag
   against each other and read as two things instead of one grey block.

   Both measures are in ch, which resolves against each element's OWN font
   size: 34ch on the 28px lead and 60ch on the 16px body came out within
   5px of each other, which is why the first attempt still looked like one
   column. These numbers are the measured result, not the arithmetic. */
/* Availability and the CV sit on one line under the subhead. */
.about-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4) var(--s-6);
  margin-top: var(--s-7);
}


/* ---- The bento ----------------------------------------------------
   Four columns, three rows, seven tiles. Placement is explicit but the
   ROW HEIGHTS are not: no fixed row track and no aspect-ratio on the
   container. Two reasons.

   First, the player. Spotify's embed is a fixed 152px tall. Any scheme
   that derives its tile's height from the container's width will, at some
   width, make that tile shorter than the iframe inside it — and the
   iframe breaks out. Row 3 is `auto`, so it is sized by its content and
   that cannot happen at any width.

   Second, the photographs. Row heights come from aspect-ratio on the
   tiles themselves, so every tile scales with the container rather than
   with the viewport. That matters because this column is 626px beside the
   copy and 884px stacked below it — a vw-based row height would be right
   in one case and wrong in the other.

   Only one tile per row needs a ratio; the rest of the row stretches to
   match and object-fit crops. The ones chosen are the ones whose crop I
   care about:
     studio / flowers  5:6  — native 3:4, a gentle trim
     agra              3:2  — native 3:2, no crop at all
   mangroves inherits agra's row and lands near 0.72 against its native
   0.67, which is closer than anything I could have set deliberately. */
.about-bento {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.1vw, 14px);
  width: 100%;
}

/* 12px flat, not a clamp.

   Six of these tiles I control completely; the seventh holds a Spotify
   embed whose card corner is baked into a cross-origin document and
   cannot be reached. Rather than impose a radius the player can never
   match — and then paper over the difference with a background fill that
   only works if I guessed its black correctly — the grid adopts the
   embed's own 12px. The one corner I cannot author becomes the one every
   other corner is measured against, so they agree by construction instead
   of by approximation.

   Flat rather than responsive for the same reason: the embed's radius does
   not scale with the viewport, so neither can this. */
.bento {
  margin: 0;
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  background: var(--paper-2);
}

/* Row 1 is square, not 5:6. At 5:6 the bento came out 806px tall against
   a 578px copy column — taller than the viewport once the header is
   counted, so the landing could not fit one screen, and the 228px height
   difference is what threw the two columns out of alignment. Square trims
   both portraits modestly and takes ~70px off the whole block. */
.bento--studio    { grid-area: 1 / 1 / 2 / 3; aspect-ratio: 1; }
.bento--flowers   { grid-area: 1 / 3 / 2 / 5; aspect-ratio: 1; }
.bento--agra      { grid-area: 2 / 1 / 3 / 3; aspect-ratio: 3 / 2; }
.bento--mangroves { grid-area: 2 / 3 / 3 / 4; }
.bento--behance   { grid-area: 2 / 4 / 3 / 5; }
.bento--play      { grid-area: 3 / 1 / 4 / 4; }
.bento--linkedin  { grid-area: 3 / 4 / 4 / 5; }

/* Absolutely positioned, and that is load-bearing rather than tidiness.

   Left in normal flow, an <img> contributes its own intrinsic height to
   the grid row. Mangroves is 933 x 1400; at 128px wide that is 191px,
   which pushed row 2 to 191 while agra — pinned by aspect-ratio: 3/2 —
   stayed at 179. The result was 12px of dead cream under agra and a row
   gap that measured 14px on one side and 26px on the other.

   Taking the images out of flow means ONLY the tiles carrying an explicit
   aspect-ratio can set a row height, which is the whole premise of the
   layout. */
.bento img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .5s cubic-bezier(.2, .7, .3, 1),
              opacity .42s var(--ease);
}

/* A tile that has not arrived yet should look like a tile, not a hole.
   .bento already carries --paper-2, so the well is never white; this adds a
   slow sheen across it so the wait reads as loading rather than as broken,
   and fades the photograph in over it once the bytes land. script.js clears
   is-loading per figure on the image's load event, so each tile resolves on
   its own instead of the whole grid waiting for the slowest file. */
/* The morph plate fills its tile and sits under the foot row. The script
   sizes its own canvas from this box, so the tile owns the geometry and the
   animation follows -- no fixed pixel size to keep in sync with the grid. */
.bento-morph {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.bento-morph .tsfm-plate { width: 100%; height: 100%; }
/* z-index only. This said position:relative, to lift the foot above the morph
   canvas -- but .bento-foot is positioned with inset:auto 0 0 0, and relative
   ignores inset offsets the way absolute honours them, so the foot fell back
   into normal flow at the top of the tile and took its scrim with it. The
   gradient belongs at the bottom, under the Behance mark. Measured: y0 before,
   y164 after. A positioned element already stacks above a z-index:0 sibling,
   so raising z-index is all that was ever needed. */
.bento--behance .bento-foot { z-index: 1; }

.bento.is-loading img { opacity: 0; }
.bento.is-loading::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(100deg,
              transparent 18%,
              color-mix(in srgb, var(--ink) 5%, transparent) 50%,
              transparent 82%);
  background-size: 220% 100%;
  animation: bento-sheen 1.5s var(--ease) infinite;
}
@keyframes bento-sheen {
  from { background-position: 140% 0; }
  to   { background-position: -40% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .bento.is-loading::after { animation: none; }
  .bento img { transition: none; }
}

/* The caption rides a scrim rather than the photograph, because the
   photographs are not uniform: white type is legible over the mangroves
   and invisible over the flower market. */
.bento figcaption {
  position: absolute;
  inset: auto 0 0 0;
  padding: 30px 12px 11px;
  color: #fff;
  background: linear-gradient(to top, rgba(16, 15, 11, .8), transparent);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .28s ease, transform .28s ease;
}

/* :focus-visible as well as :hover — the photographs are tabbable, so the
   captions have to be reachable without a pointer. */
.bento:hover img,
.bento:focus-visible img { transform: scale(1.05); }
.bento:hover figcaption,
.bento:focus-visible figcaption { opacity: 1; transform: none; }

.bento:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Hovering one tile settles the others back. Excludes the player: dimming
   something the reader is actively listening to reads as a fault. */
@media (hover: hover) {
  .about-bento:hover .bento:not(:hover):not(.bento--play) { opacity: .72; }
  .bento { transition: opacity .3s ease; }
}

/* The player, bare. It had a paper card and a heading around it, which was
   a box drawn around a box: Spotify's embed already ships its own rounded
   dark card, so the outer one only added a cream margin and a label
   nobody needs. The tile is now just the frame, with the radius pulled in
   to sit on the embed's own corners rather than outside them.

   line-height: 0 kills the inline descender gap under the iframe — a few
   pixels of cream that would otherwise show below the black card. */
/* Radius comes from .bento now, the same as every other tile. It was
   pinned to 12px to sit on the Spotify embed's own corner radius, which
   made it the one odd shape in the grid.

   The embed's corners are baked into a cross-origin iframe and cannot be
   changed, so clipping the tile wider than them would show a sliver of
   paper in each corner. The background is set to the embed's own surface
   black instead, which fills that sliver invisibly. */
.bento--play {
  display: block;
  padding: 0;
  background: #121212;
  border: 0;
  /* Kills the inline descender space under the iframe — without it a few
     pixels of tile show below the black card. */
  line-height: 0;
  /* The floor the row is built on. The iframe below is height:100%, which
     against an auto-height tile would be circular and collapse the row;
     this gives it something real to resolve against. 152 is the height
     Spotify's compact player is designed for. */
  min-height: 152px;
}

/* Absolutely positioned to the tile's four edges rather than sized with
   width/height percentages.

   inset:0 makes the fit exact by construction: no line-height artifact, no
   inline descender gap, and no percentage height resolving against a
   parent whose own height is still being decided. Whatever the tile is,
   the frame is precisely that, on all four sides.

   This matters for diagnosis as much as for looks. With the tile-to-frame
   inset provably zero, any remaining unevenness between the card's top and
   left edges is inside Spotify's own document, which is cross-origin and
   cannot be reached from here. */
.bento--play iframe {
  position: absolute;
  inset: 0;
  display: block;
  border: 0;
  width: 100%;
  height: 100%;
}

/* Behance and LinkedIn. Edge to edge, like the photographs — the earlier
   version inset them with padding and a border, which made two tiles that
   were mostly empty paper and read as gaps in the grid rather than tiles
   in it. The subject fills the tile and the label rides a scrim over it,
   exactly as the captions do. */
.bento--link {
  display: block;
  text-decoration: none;
  background: var(--paper-2);
}

/* The scrim, lightened from .82 to .55 and given a longer, softer fade so
   it reads as shading rather than as a band laid over the bottom of the
   tile.

   .55 is not a taste number. The portrait's lower band runs up to
   rgb(251,243,232) — the near-white ground of the drawing — which is the
   worst case white type has to sit on. At .55 the mark clears 4.52:1
   there, so it passes AA for NORMAL text, not merely for the large text
   it actually is. That margin is the point: it holds even if the mark
   scales down. Over the Behance tile the same scrim gives 11.5:1.

   pointer-events: none so the scrim does not sit between the pointer and
   the tile beneath it — see the note on the portrait swap below. */
.bento-foot {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 48px 13px 12px;
  background: linear-gradient(to top,
    rgba(16, 15, 11, .55) 0%,
    rgba(16, 15, 11, .34) 42%,
    transparent 100%);
  pointer-events: none;
}
/* The platform marks, set as letterforms rather than traced from the
   official brand SVGs. Both wordmarks ARE letterforms — Behance's "Bē" and
   LinkedIn's "in" — so this reads as the mark rather than as an
   approximation of it, which is what redrawing the paths from memory would
   have produced.

   Monochrome on purpose. The brand blues (#1769FF and #0A66C2) would be
   the only two cool colours anywhere on this site, and on the LinkedIn
   tile they would sit directly on a warm cream drawing. */
.bento-logo {
  font-family: var(--font-sans);
  font-size: clamp(1.7rem, 2.5vw, 2.35rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.03em;
  color: #fff;
  transition: transform .25s ease;
}
/* LinkedIn's mark is tighter and sits lower than Behance's, which carries
   a macron and needs the headroom. */
.bento-logo--in { letter-spacing: -.06em; }

.bento--link:hover .bento-logo,
.bento--link:focus-visible .bento-logo { transform: translateY(-2px); }

/* The animation that filled this tile is gone. The accent was always the
   tile's own background rather than something the canvas painted — it was
   the fallback for a blocked script — so removing the canvas leaves the
   block of colour the bento was designed around. */
.bento--behance { background: var(--accent); }

/* The drawn portrait, filling the tile. aspect-ratio and the max-width are
   cleared because here the box is whatever the grid row gives it — the
   shared .portrait--swap rules still do the stacking and the blush fade.
   object-position pulls the crop up so a narrow tile keeps the face and
   loses the shoulders, not the other way round. */
.bento-face {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
  max-width: none;
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 0;
  background: var(--paper-2);
}
.bento-face img { object-position: 50% 20%; }

/* The blush swap is driven off the TILE, not off .portrait--swap.

   The shared rule is `.portrait--swap:hover .portrait-base`, which fires
   only while the pointer is over the drawing itself. In this tile the foot
   scrim is a sibling stacked on top of the drawing's bottom ~48px, so
   crossing into that strip took the pointer off the portrait and the face
   snapped back mid-hover — the swap worked on the upper part of the tile
   and died on the lower. Hanging it on .bento--linkedin covers the whole
   tile, and pointer-events:none on the scrim keeps it out of the way of
   anything else.

   Focus gets the same thing: the tile is a link, so it is in the tab
   order. */
@media (hover: hover) and (pointer: fine) {
  .bento--linkedin:hover .portrait-base { opacity: 0; }
}
.bento--linkedin:focus-visible .portrait-base { opacity: 0; }

/* A rotated border corner rather than a glyph, so the arrow is the same
   weight as the hairlines elsewhere at every size. */
.bento-arrow {
  flex: none;
  width: 7px; height: 7px;
  margin-bottom: 3px;
  border-top: 1.5px solid rgba(255, 255, 255, .8);
  border-right: 1.5px solid rgba(255, 255, 255, .8);
  transform: rotate(45deg);
  transition: transform .25s ease;
}
.bento--link:hover .bento-arrow,
.bento--link:focus-visible .bento-arrow { transform: rotate(45deg) translate(2px, -2px); }

/* Below this a four-column bento gives the two single-column tiles about
   60px of width, which is narrower than the word "Behance". Two columns
   instead, flowing in source order — which is why nothing here needs
   `order`: the markup is already the reading order. */
@media (max-width: 760px) {
  .about-bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .bento--studio, .bento--flowers, .bento--agra,
  .bento--mangroves, .bento--behance,
  .bento--play, .bento--linkedin { grid-area: auto; }

  /* The link tiles need a ratio here, not just the photographs. Everything
     inside them is absolutely positioned — the portrait fills the tile and
     the label rides a scrim — so they contribute nothing to their own
     height. On desktop that is invisible because a taller sibling sizes
     the row; alone in a row, LinkedIn collapsed to 0px. */
  .bento--studio, .bento--flowers,
  .bento--agra, .bento--mangroves,
  .bento--behance, .bento--linkedin { aspect-ratio: 1; }

  /* Player last, so the four photographs and the two profiles pair off
     cleanly two-up instead of each being stranded beside an empty cell.
     Visual order only — these are seven independent tiles, so nothing is
     resequenced for a screen reader. */
  .bento--play { grid-column: 1 / -1; order: 1; }

  /* No hover on a touch screen, so the captions would never be seen.
     They sit under the scrim permanently instead. */
  .bento figcaption { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .bento, .bento img, .bento figcaption, .bento-arrow { transition: none; }
}


/* The figures belong to the description above them, so the seam between
   the two sections is closed up rather than left at full section width. */
.about-intro + .section-tight { padding-top: var(--s-6); }


/* Capabilities, side by side. auto-fit rather than a fixed count so the
   five columns reflow to four, three and two on the way down without a
   breakpoint for each step. */
.cap-grid {
  display: grid;
  /* 210 held five columns across the old 1824px measure comfortably. On
     the narrower About column it still fits five, at 211px each — narrow
     enough that almost every entry wrapped to two lines. 250 drops it to
     four and lets the entries sit on one. */
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--s-7) var(--gutter);
  border-top: 1px solid var(--rule);
  padding-top: var(--s-6);
}
.cap-col .def-key { margin-bottom: var(--s-4); }
.cap-col ul { margin: 0; padding: 0; list-style: none; }
.cap-col li { padding-block: 4px; }

/* Chips, borrowing the project cards' discipline box so both pages use one
   shape for "a discipline". Scoped rather than unscoping .c-tag itself:
   that rule is load-bearing on the landing page and there is no reason to
   put it at risk for a visual match. */
.cap-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.cap-tags .c-tag {
  padding: 5px 8px;
  border: 1px solid var(--rule);
  border-radius: 3px;
  line-height: 1;
  color: var(--ink-40);
}

/* Two columns, not four, so every group lands on at most two rows of
   chips.

   The arithmetic forces it. The widest group is 991px of chips including
   gaps; on two rows that needs ~496px of column. Four across gives 348px
   at a 1600 viewport, which wraps it to four rows. Two across gives 736px
   there and 896px at full width, so the widest group sits on two rows and
   the other three sit on one.

   Below 1200px two columns would fall under that 496px floor, so it drops
   to a single full-width column where everything fits on one row. Nothing
   is cut to achieve this. */
.about-caps .cap-grid { grid-template-columns: minmax(0, 1fr); }

@media (min-width: 1200px) {
  .about-caps .cap-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/* ---- Experience: one line, detail behind a disclosure -------------
   <details> rather than a scripted accordion: keyboard operable and
   announced as expandable for free, and the content stays in the DOM for
   search and for anyone printing the page. */
.role-title { margin: 0 0 var(--s-2); }
.role-org   { margin: 0 0 var(--s-3); }
.role-line  { margin: 0; max-width: 78ch; }

.role-more { margin-top: var(--s-4); }
.role-more summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  color: var(--ink-40);
  list-style: none;             /* Firefox */
  transition: color .2s ease;
}
.role-more summary::-webkit-details-marker { display: none; }
.role-more summary:hover { color: var(--ink); }
.role-more summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The arrow. A rotated border corner rather than a glyph, so it is the
   same weight as the hairlines around it at every size. */
.role-chev {
  width: 6px; height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform .25s ease;
}
.role-more[open] .role-chev { transform: translateY(1px) rotate(-135deg); }
.role-more[open] .role-more-txt::after { content: none; }

.role-more ul {
  margin: var(--s-4) 0 0;
  padding-left: 1.1em;
  max-width: 84ch;
  color: var(--ink-70);
}
.role-more li { padding-block: 3px; }

@media (prefers-reduced-motion: reduce) {
  .role-chev { transition: none; }
}

/* ---- Clario, short version ----------------------------------------
   ONE grid for every block on this page.

   These six blocks stack directly on top of each other, and each had grown
   its own grid: minmax of 320, 260, 340, 240 and 240, plus a fixed six,
   plus a lone max-width of 1100px on the prose. Five different track sizes
   and two different container widths meant no two blocks shared a column
   edge — the body copy broke at one x, the verdict cards at another about
   100px away, and the page read as though it had been assembled rather
   than laid out.

   Six columns, and every block spans a whole number of them: prose 3+3,
   card rows 2+2+2, the journey 1x6. Every vertical edge on the page now
   falls on the same six lines. */
.clario-short-grid,
.clario-catch-list,
.clario-journey,
.clario-choice-grid,
.clario-ai,
.clario-verdicts {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--s-5) var(--gutter);
}
.clario-short-grid  > *              { grid-column: span 3; }
.clario-choice-grid > *              { grid-column: span 3; }
.clario-catch-list  > li             { grid-column: span 2; }
.clario-ai          > div            { grid-column: span 2; }
.clario-verdicts    > li             { grid-column: span 2; }
.clario-journey     > li             { grid-column: span 1; }

/* The measure still caps the line length inside its three columns; it is
   the column EDGES that had to agree, not the text width. */
.clario-short-grid .body { margin: 0; max-width: 62ch; }

@media (max-width: 900px) {
  .clario-short-grid,
  .clario-catch-list,
  .clario-journey,
  .clario-choice-grid,
  .clario-ai,
  .clario-verdicts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .clario-short-grid > *, .clario-choice-grid > *,
  .clario-catch-list > li, .clario-ai > div,
  .clario-verdicts > li, .clario-journey > li { grid-column: span 1; }
}
@media (max-width: 560px) {
  .clario-short-grid,
  .clario-catch-list,
  .clario-journey,
  .clario-choice-grid,
  .clario-ai,
  .clario-verdicts { grid-template-columns: minmax(0, 1fr); }
}

.clario-catch { margin-top: var(--s-7); }
.clario-catch-list {
  margin: var(--s-4) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: catch;
}
.clario-catch-list li {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
}
/* The count is generated, so reordering or dropping one cannot leave the
   numbering wrong. */
.clario-catch-list li::before {
  counter-increment: catch;
  content: "0" counter(catch);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .13em;
  color: var(--accent);
}
.clario-catch-list li > span:last-child { font-size: .95rem; color: var(--ink-70); }

.clario-verdicts {
  margin: var(--s-7) 0 0;
  padding: 0;
  list-style: none;
}
.clario-verdicts li {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "mark name" "mark note";
  gap: 2px var(--s-3);
  align-items: start;
  padding: var(--s-4);
  border: 1px solid var(--rule);
  border-radius: 12px;
}
.clario-verdicts .cv-mark { grid-area: mark; font-size: 1.1rem; line-height: 1.2; }
.clario-verdicts strong    { grid-area: name; }
.clario-verdicts .small    { grid-area: note; }
/* Their own tokens, deliberately not --accent-text: this page redefines
   that to a blue for its case-study palette, which turned the Conflict
   marker blue. A stop state cannot inherit a page palette. --ok and
   --stop are declared once at :root and themed there, so they answer to
   the page's light level without answering to its hue. */
.cv-ok   { color: var(--ok); }   /* 5.43:1 */
.cv-warn { color: #8A5A12; }   /* 5.05:1 */
.cv-stop { color: var(--stop); }   /* 5.34:1 */

/* The six-step journey. Numbered by counter, so reordering or dropping a
   step cannot leave the sequence wrong. */
.clario-journey {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--gutter);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: cj;
}
.clario-journey li {
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
}
.clario-journey li::before {
  counter-increment: cj;
  content: "0" counter(cj);
  display: block;
  margin-bottom: var(--s-3);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .13em;
  color: var(--accent-text);
}
.cj-act { margin: 2px 0 6px; font-weight: 500; }
.clario-journey p, .clario-ai p { margin-block: 0; }

/* Decisions: what was built one side, what was refused the other. Two
   columns rather than two stacked lists, because the whole point is that
   they are the same decision seen from both directions. */
.clario-choice-grid {
}
.clario-choice { margin: 0; }
.clario-choice dt {
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
  font-weight: 500;
}
.clario-choice dd {
  margin: 4px 0 var(--s-4);
  max-width: 54ch;
  font-size: .95rem;
  color: var(--ink-70);
}
/* The refused column is marked by a rule colour, not by a red cross on
   every row — five crosses read as five errors rather than five choices. */
.clario-choice--no dt { border-top-color: var(--ink-40); }

.clario-ai {
  margin-bottom: var(--s-5);
}
.clario-ai > div { padding-top: var(--s-3); border-top: 1px solid var(--rule); }
.clario-ai ul { margin: var(--s-3) 0 0; padding-left: 1.1em; }
.clario-ai li { padding-block: 3px; }

/* The prototype, embedded. aspect-ratio rather than a fixed height so it
   keeps the app's own proportions as the column narrows; min-height stops
   it collapsing to a letterbox on a phone. */
.clario-embed {
  border: 1px solid var(--rule);
  border-radius: 12px;
  overflow: hidden;
  background: var(--paper-2);
  max-width: 1240px;
}
/* The stage is the visible box; the frame inside it is always 1440x900 and
   is scaled down to fit. --clario-k is written by script.js from the real
   stage width, and the stage height follows it so there is never a band of
   empty paper under the app. The 0.42 fallback is roughly a phone. */
.clario-embed-stage {
  position: relative;
  width: 100%;
  overflow: hidden;
  height: calc(900px * var(--clario-k, .42));
}
.clario-embed-stage iframe {
  display: block;
  width: 1440px;
  height: 900px;
  border: 0;
  background: var(--paper);
  transform: scale(var(--clario-k, .42));
  transform-origin: top left;
}

/* Rhythm: more air above a section head than below it, so each head
   groups with the content it introduces instead of floating between two
   sections. Scoped to About — the case studies set their own pace. */
.page-about .section-tight { padding-block: var(--s-8) var(--s-6); }

/* The statement raised the ceiling on this page, so the numbers were
   left looking timid against it. */
.page-about .stats .stat-n { font-size: clamp(2.6rem, 4.4vw, 4.4rem); }

/* ---- About, small screens ---------------------------------
   Scoped to .page-about throughout. .def-list and .sec-head are used on
   other pages, so the rules that touch them must not leak.

   The page was built for a 12-column desktop grid where the portrait sits
   in a narrow third of the row. On a phone every .c-N collapses to the
   full width, so the portrait inherited all 343px and stood 459px tall —
   the whole first screen was one image and the reader never reached a
   sentence. The same collapse turned four statistics into four full-width
   rows. Both are width problems rather than content problems, so both are
   fixed by giving those blocks their own measure instead of cutting
   anything out. */
@media (max-width: 760px) {
  .page-about .about-intro { padding-block: var(--s-6) var(--s-5); }
  .page-about .about-end { padding-bottom: var(--s-7); }
  .page-about .section-tight { padding-block: var(--s-5); }

  /* A portrait, not a hero. Capped by width rather than cropped by
     height, so the drawing is never cut — the hover swap depends on both
     images framing identically. */
  .page-about .portrait--swap { max-width: 228px; }

  /* Two by two. Four stacked rows read as a list of unrelated facts;
     paired, they read as one measure of scale. */
  .page-about .stats {
    grid-template-columns: 1fr 1fr;
    gap: var(--s-5) var(--s-4);
    padding-block: var(--s-5);
  }
  .page-about .stats .stat-n {
    font-size: clamp(1.9rem, 8.6vw, 2.6rem);
    margin-bottom: var(--s-2);
  }
  .page-about .stats .stat-l { line-height: 1.35; }

  /* The client list is one list set in columns, not three blocks. Two-up
     holds it to six rows instead of ten. */

  /* .def-key already carries a margin-bottom, so the grid row gap on top
     of it was doubling the space under every label. */
  .page-about .def-list { row-gap: 0; padding-block: var(--s-5); }

  /* The section meta ("Agency & in-house") was wrapping mid-phrase while
     fighting the title for a 375px line. Give it its own line. */
  .page-about .sec-head { flex-wrap: wrap; row-gap: var(--s-2); margin-bottom: var(--s-5); }
  .page-about .sec-head .sec-title { flex: 1 0 auto; }
  .page-about .sec-head .sec-meta { flex-basis: 100%; }
}

/* ---- Hover video --------------------------------------------
   Sits exactly over the still and fades in, so the card never reflows and
   the poster is what you see if the file is slow, refused or absent. */
.card-media { position: relative; }
.card-vid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .28s var(--ease);
  pointer-events: none;
}
.card-vid.on { opacity: 1; }

/* ---- The unbuilt second product piece ------------------------------
   A <div>, not an <a>. There is nowhere to go yet, and a card that looks
   clickable and does nothing is worse than one that plainly is not: the
   cursor stays default and it is skipped in the tab order.

   The blur is baked into the image rather than applied with a CSS filter.
   A filter on a large element repaints on every scroll frame, and this
   sits above the fold on the landing page. */
.card--soon { cursor: default; }
.card--soon .card-media { position: relative; }
.card--soon img { filter: saturate(.55); }

.soon-badge {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 9px 16px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  white-space: nowrap;
}
.card--soon .c-title { color: var(--ink-40); }

/* Row preview: the video replaces the still only once it is playing. */
.row-preview video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .22s var(--ease);
}
.row-preview.has-video video { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .card-vid, .row-preview video { display: none; }
}


/* The hero's GROUND is repainted every animation frame and must never be
   transitioned -- an ease there lags and smears the beat.

   Only --hero-fg is eased, and only because it STEPS: the ink jumps between
   two tones when the better one changes, and 260ms stops that reading as a
   snap.

   --hero-fg-mute and --hero-pen are deliberately NOT eased. They are
   recalculated every frame to track the ground, so easing them makes the
   rendered colour chase a moving target and never arrive -- measured at
   1.14:1 while the code had correctly computed 20,9,6. A transition is only
   ever correct on a value that settles. */
.hero, .hero * { transition: none; }
.hero .hero-name { transition: color .14s var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .hero .hero-name { transition: none; }
}

/* ---- Hover previews: rounded ------------------------------- */
.row-preview {
  border-radius: 10px;
  overflow: hidden;
}
.row-preview img,
.row-preview video { border-radius: inherit; }
.card-media { border-radius: 10px; overflow: hidden; }
.card-vid { border-radius: inherit; }

/* ---- Craft sheets: fixed columns per section ----------------
   Each section states how many tiles a row should hold. Columns step down
   on narrower screens so a 6-up row never squeezes to postage stamps. */
.sheet[data-cols] {
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
}
.sheet[data-cols="4"] { --cols: 4; }
.sheet[data-cols="5"] { --cols: 5; }
.sheet[data-cols="6"] { --cols: 6; }

@media (max-width: 1280px) {
  .sheet[data-cols="6"] { --cols: 4; }
  .sheet[data-cols="5"] { --cols: 4; }
}
@media (max-width: 900px) {
  .sheet[data-cols="6"], .sheet[data-cols="5"], .sheet[data-cols="4"] { --cols: 3; }
}
@media (max-width: 640px) {
  .sheet[data-cols="6"], .sheet[data-cols="5"], .sheet[data-cols="4"] { --cols: 2; }
}

/* Posters are portrait and every one is a different shape, so cropping them
   to a shared tile loses the composition -- which is the whole point of a
   poster. Contain them on their own ground instead. */
.sheet--print .tile {
  aspect-ratio: 3 / 4;
  background: var(--paper-2);
  display: grid;
  place-items: center;
  overflow: hidden;
}
.sheet--print .tile img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* ---- Collapse to one row, with a reveal ---------------------
   The button is injected by script.js, which measures which tiles actually
   share the first row rather than trusting a column count -- so it stays
   correct at every breakpoint and after a resize. */
.sheet-more {
  display: inline-flex;
  align-items: center;
  gap: .6em;
  margin-top: var(--s-5);
  padding: 0 var(--s-2);
  min-height: 44px;
  background: none;
  border: 0;
  color: var(--ink-70);
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  border-bottom: 1px solid var(--rule);
}
.sheet-more:hover { color: var(--accent-text); border-color: var(--accent-text); }
.sheet-more:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 3px; }
.sheet-more .chev { transition: transform var(--dur-fast) var(--ease); }
.sheet-more[aria-expanded="true"] .chev { transform: rotate(180deg); }
