/* type.css — REVISION-2 pass (Oct 2026). Loaded AFTER site.css in every page head; later rules win.
   Owner: Typography (REVISION-2 R1 safety net, R2 type + ink, R3 clay tokens, R4 density, R11 two-tone clay tokens,
   shared .page-hero slab; DESIGN-SPEC §2.0 cascade rules still apply).
   R7: light theme only. Nothing here depends on html.dark; site.css's html.dark rules stay inert because no script
   adds the class any more.
   Families: Bricolage Grotesque (display) · Host Grotesk (UI/body) · Source Serif 4 (article body only).
   Mono is retired as a brand face: --font-mono is the system mono (no download) for code/DOI fallbacks only.
   !important appears once, on the R1 safety net (it must beat cards.css `.kicker { display: … !important }`).
   Tailwind utilities live in @layer, so every unlayered rule below already beats them. */

/* ============ metric-matched local fallbacks (first rules in the file) ============ */
@font-face { font-family: "Bricolage Fallback";    src: local("Arial"), local("Helvetica Neue"), local("Helvetica"); size-adjust: 106.15%; ascent-override: 87.61%; descent-override: 25.44%; line-gap-override: 0%; }
@font-face { font-family: "Host Grotesk Fallback";  src: local("Arial"), local("Helvetica Neue"), local("Helvetica"); size-adjust: 105.75%; ascent-override: 95.98%; descent-override: 29.79%; line-gap-override: 0%; }
@font-face { font-family: "Host Grotesk Fallback";  src: local("Arial Italic"), local("Helvetica Neue Italic"); font-style: italic; size-adjust: 105.75%; ascent-override: 95.98%; descent-override: 29.79%; line-gap-override: 0%; }
@font-face { font-family: "Source Serif Fallback";  src: local("Charter"), local("Georgia"); size-adjust: 101.64%; ascent-override: 101.93%; descent-override: 32.96%; line-gap-override: 0%; }
@font-face { font-family: "Source Serif Fallback";  src: local("Charter Italic"), local("Georgia Italic"); font-style: italic; size-adjust: 93.73%; ascent-override: 110.53%; descent-override: 35.74%; line-gap-override: 0%; }

/* ============ tokens ============ */
:root {
  color-scheme: light;                   /* R7: native controls + scrollbars stay light even on a dark OS */

  /* ---- Families (R2) ---- */
  --font-display: "Bricolage Grotesque", "Bricolage Fallback", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-sans:    "Host Grotesk", "Host Grotesk Fallback", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-serif:   "Source Serif 4", "Source Serif Fallback", Charter, "Iowan Old Style", Georgia, serif;
  --font-mono:    ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --font-brand:   var(--font-display);     /* legacy alias (index.html hero) */
  --font-numeric: var(--font-sans);        /* site.css 3272–3289 consumers (pills, time, [data-stat]): Host Grotesk, tabular below */

  /* ---- Weights (R2): titles 650–700, wordmark 750, card titles 600–650, body 400, meta 500 ---- */
  --fw-wordmark: 750; --fw-display: 700; --fw-section: 650; --fw-card: 620; --fw-body: 400; --fw-meta: 500;

  /* ---- Fluid type scale (px at 375 → 1440). Bricolage sets wider than the old serif, so titles step down. ---- */
  --fs-wordmark:   clamp(3.25rem, 2.28rem + 4.13vw, 6rem);       /* 52 → 96  */
  --fs-display:    clamp(2.25rem, 1.6rem + 2.6vw, 3.75rem);       /* 36 → 60  page-hero titles (R4) */
  --fs-h1:         clamp(1.625rem, 1.4rem + 1vw, 2.25rem);        /* 26 → 36  section titles (site.css 814 consumer) */
  --fs-h2:         var(--fs-h1);
  --fs-row:        clamp(1.25rem, 1.16rem + 0.38vw, 1.5rem);      /* 20 → 24  row titles */
  --fs-h3:         clamp(1.125rem, 1.06rem + 0.28vw, 1.3125rem);  /* 18 → 21  card / tile titles */
  --fs-lead:       clamp(1.375rem, 1.24rem + 0.56vw, 1.75rem);    /* 22 → 28  home lead story */
  --fs-stat:       clamp(2.25rem, 1.95rem + 1.31vw, 3.125rem);    /* 36 → 50  stat numerals */
  --fs-cta:        clamp(2rem, 1.65rem + 1.5vw, 3rem);            /* 32 → 48  CTA h2 */
  --fs-name:       clamp(1.75rem, 1.53rem + 0.94vw, 2.375rem);    /* 28 → 38  director name */
  --fs-body:       1rem;
  --fs-reading:    clamp(1.125rem, 1.08rem + 0.19vw, 1.25rem);    /* 18 → 20  article body (Medium) */
  --fs-dek:        clamp(1.1875rem, 1.12rem + 0.28vw, 1.375rem);  /* 19 → 22  article dek */
  --fs-article-h1: clamp(1.75rem, 1.2rem + 3vw, 2.625rem);        /* 28 → 42  Medium title */
  --fs-feed-title: clamp(1.25rem, 1.15rem + 0.4vw, 1.5rem);       /* 20 → 24  feed row title */
  --fs-lede:       clamp(1rem, 0.96rem + 0.2vw, 1.125rem);        /* 16 → 18  hero + section ledes */
  --fs-meta:       0.875rem;                                      /* 14 */
  --fs-caption:    0.8125rem;                                     /* 13 */
  --fs-eyebrow:    0.75rem;                                       /* 12  small labels (site.css name kept) */
  --fs-index:      0.75rem;                                       /* legacy consumer value; indices are retired (R1) */
  --fs-micro:      0.6875rem;                                     /* 11  badges only, never running text */

  /* ---- Line heights ---- */
  --lh-wordmark: 0.96; --lh-display: 1.04; --lh-h2: 1.12; --lh-h3: 1.25; --lh-row: 1.22;
  --lh-tight: 1.15; --lh-body: 1.6; --lh-reading: 1.6; --lh-meta: 1.45;

  /* ---- Tracking (R2): display −0.025em, section −0.02em, card −0.012em ---- */
  --track-wordmark: -0.025em; --track-display: -0.018em; --track-h2: -0.015em; --track-h3: -0.01em;
  /* User report "text joined instead of space": Bricolage's space glyph is narrow, and negative display
     tracking narrows it further, so big titles read as one word. Open the word gap on every heading. */
  --word-display: 0.09em;
  --track-sans-title: -0.016em;   /* Host Grotesk ≥ 24px (Medium: −0.016em @24, −0.011em @42) */
  --track-sans-h1: -0.011em; --track-meta: 0.005em;
  --track-eyebrow: 0.04em; --tracking-eyebrow: var(--track-eyebrow);   /* legacy name: site.css 680, 1133, 2415 */
  --track-index: 0.02em; --track-reading: -0.003em;

  /* ---- Numerals ---- */
  --nums-data:  lining-nums tabular-nums;
  --nums-prose: lining-nums proportional-nums;

  /* ---- Measure ---- */
  --measure-reading: 680px; --measure-feed: 728px; --measure-prose: 70ch; --measure-lede: 48rem;

  /* ---- Spacing (R4 density) ---- */
  --space-3xs: .25rem; --space-2xs: .5rem; --space-xs: .75rem; --space-sm: 1rem; --space-md: 1.5rem;
  --space-lg: 2rem; --space-xl: 3rem; --space-2xl: 4.5rem;
  --section-py: clamp(2rem, 1.4rem + 2.4vw, 3.5rem);
  --card-pad:   clamp(1.125rem, 1rem + .6vw, 1.5rem);
  --row-py:     clamp(1rem, .9rem + .5vw, 1.375rem);
  --gutter:     clamp(1rem, .6rem + 1.6vw, 1.75rem);
  --grid-gap:   1rem;                     /* 1.25rem ≥ 768 (media query below) */
  /* R14 one page width: the page-hero slab, every content region and the footer slab share this outer box —
     --container-wide centred, never closer to the edge than --gutter. Use as
     `max-width: none; margin-inline: var(--page-inset); padding-inline: 0` so visible edges line up exactly.
     100% stays unresolved here and resolves against the consuming element's containing block.
     Cap raised from site.css's 1920px to 2240px (user, 2560px screen: "empty space … gap both side"): 160px
     margins at 2560 instead of 320px; every viewport ≤ 2042px is unchanged (94vw wins below the cap). Not uncapped:
     reading measures (article 680px, prose ~80ch) and card proportions gain nothing past ~2200px — the extra
     width goes to more grid columns (home, people, publications, news), never wider cards. Every consumer
     (hero slabs, content regions, nav, footer, site.css .max-w-[1440px]) reads this one token. */
  --container-wide: min(94vw, 2240px);
  --page-inset: max(var(--gutter), (100% - var(--container-wide)) / 2);

  /* ---- Radii (soft clay; R3) ---- */
  --radius-xs: 4px; --radius-sm: .625rem; --radius: 1rem; --radius-lg: 1.5rem; --radius-pill: 999px;

  /* ---- Ink (R2): navy-cast text, never grey or pure black ---- */
  --ink-strong: #0C2340;                  /* headings · 14.6:1 on --clay, 13.4:1 on --bg */
  --ink:        #1E2A40;                  /* body · 13.3:1 on --clay */
  --ink-2:      #56627A;                  /* meta · 5.7:1 on --clay, 5.2:1 on --bg, 5.25:1 on --clay-blue,
                                             5.5:1 on --clay-a, 5.7:1 on --clay-b (AA on both R11c tones) */
  --accent-ink: #1D428A;                  /* links, title hover · 8.8:1 on --clay, 8.6:1 on --clay-a, 8.9:1 on --clay-b */
  --gold-ink:   #8A6400;                  /* gold text on light, sparingly · 4.96:1 on --clay, 4.82:1 on --clay-gold,
                                             4.82:1 on --clay-a, 5.03:1 on --clay-b, 4.58:1 on --bg (AA everywhere;
                                             chips inside tone cards are still white clay, 5.4:1, per R11) */
  --navy-ink:   var(--accent-ink);        /* legacy alias */
  /* legacy site.css semantics re-pointed so Tailwind-era markup picks up the navy-cast ink */
  --fg:         var(--ink);
  --fg-muted:   var(--ink-2);
  --fg-subtle:  var(--ink-2);

  /* ---- Clay (R3, verbatim) — 21st.dev 24338 Neomorphism Metric Card: outer dual shadow + inset light/shade ---- */
  --bg: #E8EDF5;                 /* clay ground (page background; was #f1f5fa) */
  --clay: #F3F6FA;               /* default card surface */
  --clay-blue: #E6EEFB;          /* role tint: research/projects/publications */
  --clay-gold: #FBF2DA;          /* role tint: featured/awards/grants */
  --clay-radius: 1.5rem;         /* cards 24px; media inside 1rem; small tiles 1.125rem */
  --clay-shadow:
    14px 14px 30px -6px rgb(23 42 82 / .17),
    -10px -10px 24px -6px rgb(255 255 255 / .95),
    inset 3px 3px 7px rgb(255 255 255 / .9),
    inset -5px -5px 12px rgb(23 42 82 / .07);
  --clay-shadow-hover:
    22px 22px 44px -8px rgb(23 42 82 / .23),
    -12px -12px 28px -6px rgb(255 255 255 / 1),
    inset 3px 3px 7px rgb(255 255 255 / .95),
    inset -6px -6px 14px rgb(23 42 82 / .09);
  --clay-pressed: inset 6px 6px 12px rgb(23 42 82 / .14), inset -6px -6px 12px rgb(255 255 255 / .9);
  --clay-navy: #0B3466;          /* navy clay slabs: stat strip, director, CTA, page heroes */
  --clay-navy-shadow:
    16px 16px 36px -8px rgb(0 28 66 / .42),
    -8px -8px 22px -8px rgb(255 255 255 / .8),
    inset 3px 3px 8px rgb(255 255 255 / .14),
    inset -6px -6px 14px rgb(0 0 0 / .28);
  --clay-media-shadow: inset 0 0 0 1px rgb(255 255 255 / .35), 0 8px 18px -10px rgb(23 42 82 / .35);
  /* additive helpers built from R3's stated values */
  --clay-radius-tile: 1.125rem; --clay-radius-media: 1rem; --clay-radius-slab: 1.75rem;
  --clay-chip-shadow: 3px 3px 6px rgb(23 42 82 / .12), -3px -3px 6px rgb(255 255 255 / .9);
  --duration-clay: 320ms;

  /* ---- R11 two-colour clay: alternating cards separate from each other and from --bg ----
     cards.css owns .tone-a / .tone-b / .tone-alt; page owners apply them. On the R11c tones (A / B): titles
     --ink-strong 14.2:1 / 14.8:1, body --ink 12.9:1 / 13.5:1, meta --ink-2 5.5:1 / 5.7:1. Chips inside either tone are
     white clay chips. Row bands inside one clay sheet (news feed) are derived by their owner from these tones,
     e.g. color-mix(in oklab, var(--clay-a) 65%, #fff), which stays within R11c's #F3F7FD / #FEF9EC limit. */
  --clay-a:        #EDF3FC;      /* tone A: light Marquette blue clay (R11c: lighter, was #DCE7FA) */
  --clay-b:        #FEF7E4;      /* tone B: light Marquette gold clay (R11c: lighter, was #FBEFC9) */
  --clay-a-accent: #1D428A;      /* tone-A key colour (= --accent-ink) for strokes, rings and icon wells. Never a card top bar (R11b) */
  --clay-b-accent: #E0A800;      /* tone-B key colour: genealogy gold stroke + connector rings, featured-grant well/halo. Never a card top bar (R11b) */
  --clay-a-edge:   0 0 0 0 transparent;   /* R11b: no top colour bar — the two tone surfaces alone separate the cards (kept as a no-op layer so existing shadow stacks stay valid) */
  --clay-b-edge:   0 0 0 0 transparent;

  /* ---- Surfaces: legacy names re-pointed at clay so any leftover consumer is a clay surface ---- */
  --bg-elevated: var(--clay);
  --paper:       #ffffff;                 /* article sheet (R5) and form wells */
  --paper-2:     #f7f8fa;
  --glass-bg:    var(--clay);             /* de-glass: site.css glass consumers become opaque clay */
  --navy-panel:  var(--clay-navy);        /* every navy surface is navy clay */
  --line:        #dde4ee;                 /* decorative separators only (R3 bans borders on surfaces) */
  --line-2:      color-mix(in oklab, var(--ink-strong) 12%, transparent);
  --line-ctl:    #7f8896;                 /* CONTROL boundaries (inputs, selects): 3.3:1 on --clay (WCAG 1.4.11) */
  --rule:        var(--ink-strong);       /* Medium blockquote rule */
  --cream:       #FFF3D6;                 /* hover tint for white type on navy/photo */

  /* ---- Elevation: legacy aliases → clay (site.css .card/.content-card/carousel arrows consume these) ---- */
  --elev-card:  var(--clay-shadow);
  --elev-hover: var(--clay-shadow-hover);
  --elev-panel: var(--clay-shadow);
  --elev-navy:  var(--clay-navy-shadow);
  --shadow-sm: var(--elev-card); --shadow: var(--elev-hover); --shadow-xl: var(--elev-panel);

  /* ---- Nav capsule (R5: clay 92 % + blur kept only for legibility over the photo hero) ---- */
  --glass-nav-bg:   color-mix(in oklab, var(--clay) 92%, transparent);
  --glass-nav-blur: 16px;

  /* ---- Motion ---- */
  --ease-out:    cubic-bezier(.2, .8, .2, 1);
  --ease-in-out: cubic-bezier(.4, 0, .2, 1);
  --ease-settle: cubic-bezier(.23, 1, .32, 1);
  --ease-spring: cubic-bezier(.34, 1.35, .48, 1);
  --duration-fast: 150ms; --duration-standard: 280ms; --duration-slow: 600ms;
  --duration-ink: 420ms;       /* font-weight ink gain (Host Grotesk is duplexed: zero reflow) */

  /* ---- Focus ---- */
  --focus-ring: 2px solid var(--brand-blue); --focus-offset: 2px;
}
@media (min-width: 48em) { :root { --grid-gap: 1.25rem; } }
/* Below 360px the R4 36px floor can't hold the longest hero word ("announcements" ≈ 7.3em) on the slab's
   inner line (320px → 248px), so the floor follows the viewport there; ≥ 360 the R4 clamp is untouched. */
@media (max-width: 22.4375em) { :root { --fs-display: clamp(1.75rem, 9.5vw, 2.25rem); } }

/* base */
html { font-family: var(--font-sans); font-synthesis: none; font-kerning: normal; text-rendering: optimizeLegibility;
       font-feature-settings: "kern", "liga", "calt"; }
body { font-size: var(--fs-body); line-height: var(--lh-body); font-weight: 400; color: var(--ink); background: var(--bg); }
.font-display { font-family: var(--font-display); letter-spacing: var(--track-h3); }   /* site.css 176 set −0.01em */
/* Bricolage ships no italic: the gold "Lab" is a synthesised oblique (the WebGL canvas mirrors this). */
.hero-lab { font-synthesis: style; }


/* ============ R1 — no eyebrows, no indices (safety net; markup owners remove them) ============
   (0,2,0) beats cards.css `.kicker { display: inline-flex !important }` (0,1,0) and site.css 2579. */
.kicker, .t-eyebrow, .t-index,
:root :is(.kicker, .t-eyebrow, .t-index) { display: none !important; }


/* ============ text-style helpers ============ */
.t-display { font-family: var(--font-display); font-weight: var(--fw-section); letter-spacing: var(--track-h2); color: var(--ink-strong); text-wrap: balance; }
.t-meta    { font: var(--fw-meta) var(--fs-meta)/var(--lh-meta) var(--font-sans); letter-spacing: var(--track-meta); color: var(--ink-2); font-variant-numeric: var(--nums-data); }
/* ink gain — the one DOM hover the sans owns (duplexed widths: zero reflow) */
.fx-ink { transition: font-weight var(--duration-ink) var(--ease-out), color var(--duration-standard) var(--ease-out); }
@media (prefers-reduced-motion: no-preference) {
  a.fx-ink:is(:hover, :focus-visible), a:is(:hover, :focus-visible) .fx-ink { font-weight: 600; }
  .fx-ink--nav:is(:hover, :focus-visible) { font-weight: 600; }
  .fx-ink--strong:is(:hover, :focus-visible), a:is(:hover, :focus-visible) .fx-ink--strong { font-weight: 700; }
}
/* .t-sharpen was a Newsreader opsz hover; Bricolage's opsz changes widths, so only the nowrap survives */
.t-sharpen { white-space: nowrap; }
/* never on the WebGL targets: no axis settings, wdth stays 100, opsz follows the size */
h1.hero-wordmark, h1.page-hero__title { font-variation-settings: normal; font-optical-sizing: auto; font-stretch: normal; }
/* Bricolage numeral in gold on navy (publications hero lede counter) — 8.2:1 on #0B3466 */
.t-num-gold { font-family: var(--font-display); font-weight: var(--fw-section); color: var(--brand-gold); font-variant-numeric: var(--nums-data); }


/* ============ page hero — a navy clay slab directly under the nav (R3, R4, R14) ============
   Inset from the page edges on the one page width (R14: --page-inset = --container-wide, min(94vw, 2240px),
   centred; the gutter below that), so the slab's edges match the content regions and the footer slab on every
   page at 1024–2560. Radius 1.75rem, no border, no seam. The faint top-left light is the
   21st.dev 3176 Animated 3D Card rim light (135deg white → transparent), kept static. Same (0,1,0)/(0,2,2)
   specificity as site.css 740/1088/1854/3572 and later in the cascade, so no !important. */
.page-hero {
  max-width: none;
  margin-inline: var(--page-inset);
  padding-block: 2.25rem 2rem;
  border: 0;
  border-radius: var(--clay-radius-slab);
  background:
    radial-gradient(120% 150% at 0% 0%, rgb(255 255 255 / .075), transparent 52%),
    var(--clay-navy);
  box-shadow: var(--clay-navy-shadow);
  color: #fff;
}
.page-hero::before, .page-hero::after { content: none; }   /* the 3px gold seam + pinstripe read as borders on a slab */
/* site.css 3446 pulls the first section under the capsule and 3572 pads it back; the slab sits below instead */
main > section.page-hero:first-of-type { margin-top: .75rem; padding-top: 2.25rem; }

/* Inner layout: the title on its own line, then ONE meta row where the lede and its stat pills / view tabs /
   count / search form sit side by side ≥ 1024 (space-between) and stack below. Two markup shapes are served:
   (a) flat — lede and pills are direct children of the container (people, alumni);
   (b) wrapped — a row div around them (.pub-hero__row, .listing-hero__row, .hero-meta-row …).
   A page's own id-scoped row rules (e.g. #pub-hero .pub-hero__row) out-specify these and win. */
/* The slab is the width cap (R14): no container inside it re-caps at site.css's 1280/1440/1600, and only the
   direct child takes the slab's inner padding. */
.page-hero :is(.container-narrow, .container-standard, .container-wide) { max-width: none; }
.page-hero > :is(.container-narrow, .container-standard, .container-wide) {
  margin-inline: 0;
  padding-inline: clamp(1.25rem, .5rem + 2.6vw, 3rem);
}
/* margin: Tailwind mt-4 on older markup; fit-content keeps the WebGL canvas (sized to the h1 box) on the words */
.page-hero__title { margin-block: 0; width: fit-content; max-width: 100%; }
/* (a) flat */
.page-hero > :is(.container-standard, .container-wide):has(> .page-hero__lede) {
  display: grid; grid-template-columns: minmax(0, 1fr); row-gap: .875rem;
}
.page-hero > :is(.container-standard, .container-wide):has(> .page-hero__lede) > * { grid-column: 1 / -1; margin-block: 0; }
/* (b) wrapped (never the wrapper that also holds the title, e.g. news' intro block) */
.page-hero :is(.container-standard, .container-wide) > :has(> .page-hero__lede):not(:has(> .page-hero__title)) {
  display: flex; flex-direction: column; gap: .875rem; margin-top: .875rem;
}
.page-hero :is(.container-standard, .container-wide) > :has(> .page-hero__lede):not(:has(> .page-hero__title)) > * { margin-block: 0; }
@media (min-width: 64em) {
  .page-hero > :is(.container-standard, .container-wide):has(> .page-hero__lede) {
    grid-template-columns: minmax(0, 1fr) auto; column-gap: clamp(1.5rem, 3vw, 3.5rem); align-items: center;
  }
  .page-hero > :is(.container-standard, .container-wide):has(> .page-hero__lede) > .page-hero__lede { grid-column: 1; }
  .page-hero > :is(.container-standard, .container-wide):has(> .page-hero__lede) > .page-hero__lede + * { grid-column: 2; justify-self: end; }
  .page-hero :is(.container-standard, .container-wide) > :has(> .page-hero__lede):not(:has(> .page-hero__title)) {
    flex-direction: row; align-items: center; justify-content: space-between; column-gap: clamp(1.5rem, 3vw, 3.5rem);
  }
  .page-hero :is(.container-standard, .container-wide) > :has(> .page-hero__lede):not(:has(> .page-hero__title)) > .page-hero__lede { flex: 0 1 auto; }
  .page-hero :is(.container-standard, .container-wide) > :has(> .page-hero__lede):not(:has(> .page-hero__title)) > :not(.page-hero__lede) { flex: none; }
  .page-hero .page-hero__lede ~ .topic-pills { justify-content: flex-end; max-width: 34rem; }
}
/* R14 no hollow slab: a hero with only a title and a lede (about, contact, 404) would leave the right half of
   the wide slab (up to 2240px) empty, so from 1600 the lede moves up beside the title, bottom-aligned at the far end.
   Flex, not grid: the title keeps its one-line width (capped at 60%) and only the lede gives way — at 1600
   "About the Ubicomp Lab" is ~613px and the lede still sets in two lines at ~745px. */
@media (min-width: 100em) {
  .page-hero > :is(.container-standard, .container-wide):has(> .page-hero__lede:last-child) {
    display: flex; align-items: flex-end; justify-content: space-between; column-gap: clamp(1.5rem, 3vw, 3.5rem);
  }
  .page-hero > :is(.container-standard, .container-wide):has(> .page-hero__lede:last-child) > .page-hero__title { flex: none; max-width: 60%; }
  .page-hero > :is(.container-standard, .container-wide):has(> .page-hero__lede:last-child) > .page-hero__lede { flex: 0 1 var(--measure-lede); min-width: 0; }
}

h1.page-hero__title {
  font: var(--fw-display) var(--fs-display)/var(--lh-display) var(--font-display);
  letter-spacing: var(--track-display);
  color: #fff;
  text-wrap: balance;
  hyphens: manual; -webkit-hyphens: manual;
  /* never split a word (user rule): break-word only breaks a word wider than the whole slab line, and unlike
     `anywhere` it keeps the longest word in the min-content size, so a hero column sized by its content
     (e.g. news' intro beside the filters at 1024–1279) widens to fit "announcements" instead of cutting it */
  overflow-wrap: break-word;
}
/* short gold pill under hero titles (site.css 1284; gradient at 1862 → flat gold) */
.page-hero__title::after {
  width: 2.5rem; height: 3px; margin-top: .625rem; border-radius: 999px; background: var(--brand-gold); opacity: 1;
}

/* ledes (site.css 797–805, 819–823) */
.section-header__lede, .page-hero__lede {
  font-family: var(--font-sans); font-weight: 400;
  font-size: var(--fs-lede); line-height: 1.5;
  max-width: var(--measure-lede);
  text-wrap: pretty;
}
.page-hero__lede { color: rgb(255 255 255 / .82); }        /* 8.8:1 on #0B3466, 10:1 on #0B2A55 */
.section-header__lede { margin-top: .5rem; color: var(--ink-2); }


/* ============ section titles (site.css 808–823, 2580) — title only, compact ============ */
.section-header { margin-bottom: 1.25rem; }
.section-header__title {
  margin-top: 0;
  font: var(--fw-section) var(--fs-h1)/var(--lh-h2) var(--font-display);
  letter-spacing: var(--track-h2);
  color: var(--ink-strong);
  text-wrap: balance;
}
/* the gold underline still draws on .is-in (site.css 572–588, 1866) — now a short 3px pill */
.section-header__title.h-underline { padding-bottom: .45rem; }
.section-header__title.h-underline::after {
  height: 3px; width: 2.25rem; border-radius: 999px; background: var(--brand-gold); transition-duration: .7s;
}


/* ============ prose outside the article body (project description; site.css 465–474) ============
   Body copy is Host Grotesk (Bricolage is display-only). The Medium article body (.article-body) is Blog's. */
.prose:not(.article-body) :is(h2, h3) {
  font-family: var(--font-display); font-weight: var(--fw-section); letter-spacing: var(--track-h2);
  color: var(--ink-strong); text-wrap: balance;
}
.prose:not(.article-body) h2 {
  font-size: clamp(1.375rem, 1.27rem + .45vw, 1.75rem); line-height: 1.2; margin: 1.4em 0 .45em;
}
.prose:not(.article-body) h3 {
  font-size: 1.1875rem; line-height: 1.3; letter-spacing: var(--track-h3); margin: 1.3em 0 .35em;
}
.prose:not(.article-body) :is(p, li) {
  font-family: var(--font-sans); font-weight: 400;
  font-size: clamp(1rem, .97rem + .15vw, 1.0625rem); line-height: 1.7;
  color: var(--ink); max-width: var(--measure-prose);
  font-variant-numeric: var(--nums-prose);
}
.prose:not(.article-body) :is(ul, ol) { color: var(--ink); }
.prose:not(.article-body) ul > li::marker { color: var(--gold-ink); }
.prose:not(.article-body) :is(strong, b) { font-weight: 600; color: var(--ink-strong); }
.prose:not(.article-body) a {
  color: var(--accent-ink); text-decoration: underline; text-decoration-thickness: 1px;
  text-underline-offset: .2em; text-decoration-skip-ink: auto;
}
.prose:not(.article-body) a:hover { text-decoration-thickness: 2px; }
.prose:not(.article-body) blockquote {
  border-left: 3px solid var(--brand-gold); padding-left: 1.125rem;   /* a real border: survives forced-colors */
  font-family: var(--font-sans); font-style: italic; color: var(--ink);
}


/* ============ small labels that used to be mono eyebrows — now quiet Host Grotesk, sentence case ============ */
.article-share__label, .stat-card__label {
  font-family: var(--font-sans); font-weight: var(--fw-meta); font-size: var(--fs-caption); line-height: 1.35;
  letter-spacing: .01em; text-transform: none;
}
.article-share__label { color: var(--ink-2); }


/* ============ captions, bylines, numerals ============ */
/* figure captions centred; the lightbox caption (site.css 1675, white on black) is left alone.
   .caption-body takes the type only: its one use is the home photo hero, coloured by index.html. */
figcaption:not(.lightbox__caption), .caption-body {
  font-family: var(--font-sans); font-weight: 400; font-size: var(--fs-caption); line-height: 1.45;
}
figcaption:not(.lightbox__caption) { color: var(--ink-2); }
figure > figcaption:not(.lightbox__caption) { text-align: center; }

/* bylines (site.css 728–733, 2880) */
.byline { font-family: var(--font-sans); font-weight: 400; font-size: var(--fs-meta); line-height: 1.5; color: var(--ink-2); }
.byline strong { font-weight: 600; color: var(--ink-strong); }

/* numerals: tabular wherever data lives (Host Grotesk digits are tabular by default); display numerals are
   Bricolage, set explicitly. Everything else site.css 3272–3289 points at --font-numeric is Host Grotesk. */
[data-counter], .num, .stat-card__value, time { font-variant-numeric: var(--nums-data); }
.stat-card__value, .news-headline__day, .t-num-gold { font-family: var(--font-display); font-weight: var(--fw-section); }


/* ============ form controls: clay wells on clay (publications filter bar), translucent on the navy slab (news) ============ */
.filter-bar :is(input, select), .page-hero form :is(input, select) {
  font: 400 .9375rem/1.25 var(--font-sans);
  height: 2.75rem;
  border-radius: .875rem;
  background: var(--clay);
  border: 1px solid var(--line-ctl);
  box-shadow: inset 2px 2px 5px rgb(23 42 82 / .08);
  color: var(--ink);
}
.filter-bar input::placeholder { color: #5f6b82; opacity: 1; }                         /* 4.95:1 on --clay */
.filter-bar :is(input, select):focus {
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent-ink) 20%, transparent);
}
/* on navy: white .42 edge = 3.5:1; the gold outline comes from site.css 3612 (--focus-ring on .page-hero) */
.page-hero form :is(input, select) {
  background: rgb(255 255 255 / .08);
  border-color: rgb(255 255 255 / .42);
  box-shadow: inset 2px 2px 6px rgb(0 0 0 / .18);
  color: #fff;
}
.page-hero form input::placeholder { color: rgb(255 255 255 / .72); opacity: 1; }
.page-hero form :is(input, select):focus {
  border-color: var(--brand-gold);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand-gold) 28%, transparent);
}
/* the open option list is painted by the OS on its own sheet: give it ink on paper so it never inherits the white text */
.page-hero form select option { color: var(--ink); background-color: var(--paper); }
/* phones: iOS Safari zooms the page when a focused control is under 16px. Height stays 2.75rem; desktop keeps 15px. */
@media (max-width: 640px) {
  .filter-bar :is(input, select), .page-hero form :is(input, select) { font-size: 16px; }
}

/* Word gaps on Bricolage headings (see --word-display). :where() keeps this at zero specificity so any
   component that sets its own word-spacing wins; the Fraunces home wordmark keeps the live site's spacing.
   text-fx.js draws each word at its DOM position, so the WebGL titles follow automatically. */
:where(h1, h2, h3, h4, .t-display, .font-display, .page-hero__title, .section-header__title):not(.hero-wordmark) {
  word-spacing: var(--word-display);
}
