/* ============================================================
   Self-hosted fonts — no third-party font requests.
   All five families are served from assets/fonts/ as WOFF2, so visiting a
   Reed page sends nothing to Google Fonts, fonts.gstatic.com or jsDelivr.
   Only the weights and styles the design actually uses are shipped.
   Licenses are retained alongside the files in assets/fonts/.
   font-display: swap keeps text readable during load; every stack ends in a
   system fallback so a failed asset degrades rather than disappearing.
   ============================================================ */
@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('fonts/public-sans-300-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/public-sans-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/public-sans-500-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/public-sans-600-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/public-sans-700-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Public Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/public-sans-400-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('fonts/newsreader-300-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/newsreader-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/newsreader-500-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url('fonts/newsreader-300-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/newsreader-400-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Lexend';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('fonts/lexend-300-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Lexend';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/lexend-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Lexend';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/lexend-500-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Lexend';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/lexend-600-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Lexend';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/lexend-700-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/jetbrains-mono-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-500-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/jetbrains-mono-400-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'OpenDyslexic';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/opendyslexic-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'OpenDyslexic';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/opendyslexic-700-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'OpenDyslexic';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/opendyslexic-400-italic.woff2') format('woff2');
}

/* Reed Digital Access — shared stylesheet
   One source of truth for tokens, themes, layout and components.
   Theme + font state live as data attributes on <html>, and size/spacing as
   the --ts / --lhs multipliers, all set by a blocking script in <head> so
   there is no flash of the wrong settings on navigation. */

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--fg); font-family: var(--bf); }
img { max-width: 100%; }

/* ---------- typographic roles ----------
   --bf body, --hf heading, --uf utility. The Default choice restores the
   designed mix; any explicit choice flattens all three to one family.
   Size and spacing are separate concerns, handled by --ts / --lhs below. */
:root {
  --brand-body: 'Public Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --brand-heading: 'Newsreader', Georgia, 'Times New Roman', serif;
  --brand-utility: 'JetBrains Mono', ui-monospace, 'SF Mono', Consolas, monospace;
  --bf: var(--brand-body);
  --hf: var(--brand-heading);
  --uf: var(--brand-utility);
  /* --ts and --lhs are the site-wide multipliers the Text & display control
     writes. Every declared font-size is calc(Npx * var(--ts)) and every
     unitless line-height is calc(N * var(--lhs)), so a reader's choice scales
     the whole type system proportionally and the heading/body/utility
     hierarchy is preserved rather than flattened. */
  /* Plum accent that stays visible on every background. The dark theme needs
     the light lavender, because brand plum is the dark surface colour there. */
  --accent-plum: #4A3358;
  --ts: 1;
  --lhs: 1;
  --bs: calc(17px * var(--ts));
  --bl: calc(1.7 * var(--lhs));
}
html[data-font="public"]  { --bf: var(--brand-body); --hf: var(--brand-body); --uf: var(--brand-body); }
html[data-font="serif"]   { --bf: var(--brand-heading); --hf: var(--brand-heading); --uf: var(--brand-heading); }
html[data-font="lexend"]  { --bf: 'Lexend', var(--brand-body); --hf: 'Lexend', var(--brand-body); --uf: 'Lexend', var(--brand-body); }
html[data-font="dys"]     { --bf: 'OpenDyslexic', var(--brand-body); --hf: 'OpenDyslexic', var(--brand-body); --uf: 'OpenDyslexic', var(--brand-body); }
html[data-font="mono"]    { --bf: var(--brand-utility); --hf: var(--brand-utility); --uf: var(--brand-utility); }

/* Literal code is semantic and never follows the reader's font choice. */
code { font-family: var(--brand-utility); }

/* ---------- themes ---------- */
html[data-theme="paper"] {
  --bg: #FDFCFA; --fg: #1A1620; --muted: #6B6259;
  --surface: #F7F5F2; --surface-2: #FDFCFA; --surface-3: #EDE8E0;
  --line: #DBD5CB; --eyebrow: #0E7C86;
  --label: #41522F; --link: #0C6C75;
  --focus-a: #1A1620; --focus-b: #FDFCFA;
  --brand-panel: #4A3358; --brand-edge: transparent;
  --card-audit: #1F3352; --card-content: #4A5D3A; --card-tech: #4A3358; --card-ux: #1F5459; --card-num: rgba(253,252,250,0.92);  
  --header-grad: #FDFCFA;
}
html[data-theme="warm"] {
  --accent-plum: #4A3358;
  --bg: #F5EFE2; --fg: #2A241C; --muted: #655C4C;
  --surface: #EFE7D6; --surface-2: #F9F4EA; --surface-3: #E4DAC5;
  --line: #D6C9AE; --eyebrow: #0A606A;
  --label: #41522F; --link: #0A606A;
  --focus-a: #2A241C; --focus-b: #F9F4EA;
  --brand-panel: #4A3358; --brand-edge: transparent;
  --card-audit: #1F3352; --card-content: #4A5D3A; --card-tech: #4A3358; --card-ux: #1F5459; --card-num: rgba(253,252,250,0.92);  
  --header-grad: #F5EFE2;
}
html[data-theme="dark"] {
  --accent-plum: #C4A5DC;
  --bg: #191520; --fg: #F0EDE8; --muted: #B3ABB8;
  --surface: #241E2E; --surface-2: #1F1A28; --surface-3: #2E2738;
  --line: #3E3648; --eyebrow: #5CC9D3;
  --label: #A9CE8A; --link: #5CC9D3;
  --focus-a: #F0EDE8; --focus-b: #191520;
  --brand-panel: #241E2E; --brand-edge: rgba(253,252,250,0.22);
  --card-audit: #233961; --card-content: #3F5231; --card-tech: #513A64; --card-ux: #1C4D52; --card-num: rgba(253,252,250,0.92);  
  --header-grad: linear-gradient(100deg, #1E1929 0%, #251E33 40%, #30253F 74%, #3A2B50 100%);
}
html[data-theme="contrast"] {
  --accent-plum: #3B0A5C;
  --bg: #FFFFFF; --fg: #000000; --muted: #000000;
  --surface: #FFFFFF; --surface-2: #FFFFFF; --surface-3: #FFFFFF;
  --line: #000000; --eyebrow: #00575F;
  --label: #233017; --link: #00575F;
  --focus-a: #000000; --focus-b: #FFFFFF;
  --brand-panel: #150F1C; --brand-edge: #FFFFFF;
  --card-audit: #150F1C; --card-content: #150F1C; --card-tech: #150F1C; --card-ux: #150F1C; --card-num: rgba(253,252,250,0.92);  
  --header-grad: #FFFFFF;
}

/* ---------- links & focus ---------- */
a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--fg); }
*:focus-visible { outline: 3px solid var(--focus-a); outline-offset: 0; box-shadow: 0 0 0 6px var(--focus-b); }

.skip {
  position: absolute; left: -9999px; top: 8px; z-index: 60;
  background: #5CC9D3; color: #1A1620; padding: 12px 18px;
  font-family: var(--bf); font-size: calc(15px * var(--ts)); font-weight: 600; text-decoration: none;
}
.skip:focus { left: 8px; }

/* ---------- layout ---------- */
.wrap { max-width: 1180px; margin: 0 auto; padding: 0 40px; }
main { min-height: 60vh; }

/* ---------- header ---------- */
.site-header {
  background: var(--header-grad);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}
.header-row {
  max-width: 1180px; margin: 0 auto; padding: 18px 40px;
  /* 18px gap, not 24px: with eight destinations the brand, nav and Text &
     display control need every pixel of the 1100px content box to share one
     row. Measured requirement is 1081px. */
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
}
/* Wide desktop: brand and the nav group share one row, vertically centred.
   nowrap on the ROW keeps the brand left and the nav group right; the group
   itself may still wrap internally, so nothing is ever pushed to a line of
   its own and no offsets or absolute positioning are involved. */
@media (min-width: 1000px) {
  .header-row { flex-wrap: nowrap; }
  .brand { flex: 0 0 auto; }
  .header-group { flex: 1 1 auto; min-width: 0; }
}
.brand { display: flex; align-items: center; gap: 13px; padding: 6px; margin: -6px; text-decoration: none; color: inherit; }
.brand img { display: block; width: 68px; height: 68px; flex: none; }
/* Two real <img> elements rather than a JS src swap, so the correct logo is
   present before script runs and cannot flash on navigation. */
.brand picture { display: block; flex: none; line-height: calc(0 * var(--lhs)); }
.brand picture.logo-dark { display: none; }
html[data-theme="dark"] .brand picture.logo-light { display: none; }
html[data-theme="dark"] .brand picture.logo-dark { display: block; }
.brand-text { display: flex; flex-direction: column; gap: 2px; }
.wordmark {
  font-family: var(--bf); font-size: calc(17px * var(--ts)); font-weight: 600;
  letter-spacing: 0.03em; line-height: calc(1.1 * var(--lhs)); color: #4A3358;
}
html[data-theme="dark"] .wordmark { color: #F0EDE8; }
.wordmark-tag { font-family: var(--hf); font-style: italic; font-size: calc(14px * var(--ts)); font-weight: 300; line-height: calc(1.1 * var(--lhs)); color: var(--eyebrow); }

/* nav and the Text & display control share a row so the control can
   never be orphaned on a line of its own */
.header-group { margin-left: auto; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }
.site-nav { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.site-nav a {
  display: inline-flex; align-items: center; box-sizing: border-box; text-decoration: none;
  /* 6px sides, not 10px: with the eighth destination added, the row needed
     1147px inside a 1100px content box, so the Text & display button wrapped
     to a second line on every desktop. The 4px flex gap still separates the
     labels and the 44px minimum target is untouched. */
  min-height: 44px; padding: 11px 6px; white-space: nowrap;
  font-family: var(--bf); font-size: calc(15.5px * var(--ts)); font-weight: 600; color: var(--fg);
}
.site-nav a[aria-current="page"] { color: var(--eyebrow); text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 5px; }
/* Nav links sit 4px apart with 6px side padding, so the global ring's 3px
   outline plus 6px outer halo overlapped its neighbours and read as two
   vertical bars rather than a box. Drawing the ring INSIDE the 44px target
   encloses the link on all four sides and cannot collide with the next link.
   The active-page state stays a teal underline, so focus and current page
   remain distinguishable. */
.site-nav a:focus-visible {
  outline: 3px solid var(--focus-a);
  outline-offset: -3px;
  box-shadow: inset 0 0 0 5px var(--focus-b);
}

/* Slim teal rules between desktop nav links. Generated with ::before on each
   link except the first, so no glyph enters the accessible name and the link
   semantics are untouched. Hidden in the disclosure nav, where the links are
   stacked and a vertical rule would make no sense. */
.site-nav a + a { position: relative; }
.site-nav a + a::before {
  content: ""; position: absolute; left: -3px; top: 50%;
  transform: translateY(-50%);
  width: 1px; height: 1em;
  background: var(--eyebrow); opacity: 0.42;
  pointer-events: none;
}

.prefs-toggle {
  display: flex; align-items: center; min-height: 44px; padding: 11px 14px;
  white-space: nowrap; flex-shrink: 0; cursor: pointer;
  font-family: var(--bf); font-size: calc(15px * var(--ts)); font-weight: 600;
  border: 2px solid var(--eyebrow); background: transparent; color: var(--eyebrow);
}
.prefs-toggle[aria-expanded="true"] { background: var(--eyebrow); color: #FDFCFA; }
html[data-theme="dark"] .prefs-toggle[aria-expanded="true"] { color: #101018; }

/* ---------- Text & display panel ---------- */
.prefs-panel { background: #4A3358; border-bottom: 1px solid var(--line); }
.prefs-grid {
  max-width: 1180px; margin: 0 auto; padding: 26px 40px 30px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); gap: 26px;
  align-items: start;
}
.prefs-cluster { display: flex; flex-direction: column; gap: 10px; }

/* Wide layout: Font holds the left column across both rows; Text size and Line
   spacing sit across the top of the right area; Background (with Reset beside
   it) sits beneath them, spanning the same two right-hand tracks.
   Done with grid spans on the existing DOM order — Font, Text size, Line
   spacing, Background+Reset — so nothing reorders and the keyboard sequence is
   still exactly the reading sequence. Below this width the groups stack in
   source order on their own. */
@media (min-width: 900px) {
  .prefs-grid { grid-template-columns: repeat(3, 1fr); }
  .prefs-cluster[aria-labelledby="pref-font"] { grid-row: span 2; }
  .prefs-bg-group { grid-column: span 2; }
}
.prefs-label { font-family: var(--uf); font-size: calc(10.5px * var(--ts)); letter-spacing: 0.14em; text-transform: uppercase; color: #5CC9D3; }
/* The six font choices sit two across and three down where there is room.
   Plain grid placement, so DOM order (Default, Public Sans, Newsreader,
   Lexend, OpenDyslexic, Monospace) is also the reading and tab order. */
.font-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.theme-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.chip {
  min-height: 44px; padding: 11px 13px; cursor: pointer; font: inherit;
  font-family: var(--bf); font-size: calc(14px * var(--ts)); font-weight: 600; color: #FDFCFA;
  border: 2px solid rgba(253,252,250,0.22); background: transparent; text-align: left;
}
.chip[aria-pressed="true"] { border-color: #5CC9D3; background: rgba(92,201,211,0.16); }
.chip-font { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; line-height: calc(1.25 * var(--lhs)); min-width: 0; overflow-wrap: anywhere; }
.chip-note { font-size: calc(11px * var(--ts)); opacity: 0.78; }
/* each option previews in its own face */
.chip-font[data-font-key="default"] { font-family: var(--brand-body); }
.chip-font[data-font-key="public"] { font-family: 'Public Sans', sans-serif; }
.chip-font[data-font-key="serif"] { font-family: 'Newsreader', Georgia, serif; }
.chip-font[data-font-key="lexend"] { font-family: 'Lexend', 'Public Sans', sans-serif; }
.chip-font[data-font-key="dys"] { font-family: 'OpenDyslexic', 'Public Sans', sans-serif; }
.chip-font[data-font-key="mono"] { font-family: 'JetBrains Mono', monospace; }

.stepper { display: flex; align-items: center; gap: 10px; }
.step-btn {
  width: 44px; height: 44px; flex: none; cursor: pointer; font-size: calc(19px * var(--ts)); line-height: calc(1 * var(--lhs));
  border: 2px solid rgba(253,252,250,0.22); background: transparent; color: #FDFCFA; font-family: var(--bf);
}
.step-value { flex: 1; text-align: center; font-family: var(--uf); font-size: calc(14px * var(--ts)); color: #FDFCFA; }
/* Reset is last in the DOM and last visually. Where the panel has room it
   shares the final row with Background; otherwise it wraps beneath. No
   "order" property is involved either way. */
/* Background and Reset occupy a single grid cell so Reset cannot be
   auto-placed onto a new row under Font. It sits to the right of the
   Background choices where there is room and wraps beneath them otherwise. */
.prefs-bg-group {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px 18px; min-width: 0;
}
/* Two tracks wide where the panel has them, so Background and Reset sit side
   by side. Scoped above 640px: an unconditional span forces auto-fit to keep
   two columns even at 320px, which halved the chip width on a phone. Below
   that the pair is one column and Reset wraps beneath Background. */
@media (min-width: 641px) { .prefs-bg-group { grid-column: span 2; } }
.prefs-bg-group .prefs-cluster { flex: 1 1 200px; min-width: 0; }
.prefs-reset { display: flex; align-items: flex-end; justify-content: flex-start; flex: 0 0 auto; }
.step-value { min-width: 5ch; }
/* Native disabled state at the limits, exposed rather than merely inert. */
.step-btn:disabled { opacity: 0.45; cursor: default; }
/* Two columns need roughly 300px of panel column. Below that, or when the
   reader has enlarged the text enough that a chip label no longer fits, the
   grid goes to one column rather than the labels being shrunk. */
@media (max-width: 900px) { .font-grid { grid-template-columns: 1fr; } }

/* One H1 rendered as three visual lines. Block spans instead of <br> so
   NVDA browse mode encounters a single heading rather than three fragments. */
.h1-line { display: block; }

/* Decorative card numbers: hidden from AT (aria-hidden in the markup) but
   lifted to 4.5:1 against their card fills for sighted readers. */
.sev-list { list-style: none; margin: 0; padding: 0; display: flex; gap: 8px; flex-wrap: wrap; }
.res-grid, .res-grid--pair, .res-grid--trio { list-style: none; padding-left: 0; margin: 0; }

/* ONE list, 2 + 3 by layout. Six equal tracks: the first two cards take three
   tracks each (half-width row), the last three take two each (thirds row). The
   visual rhythm therefore comes from the grid, never from splitting the
   collection into separate lists. */
ul.res-grid--orgs {
  list-style: none; padding-left: 0; margin: 0;
  display: grid; gap: 16px; align-items: start;
  grid-template-columns: 1fr;
}
ul.res-grid--orgs > li { display: flex; min-width: 0; }
ul.res-grid--orgs > li > article { width: 100%; }
@media (min-width: 700px) and (max-width: 999px) {
  ul.res-grid--orgs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1000px) {
  ul.res-grid--orgs { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  ul.res-grid--orgs > li:nth-child(-n+2) { grid-column: span 3; }
  ul.res-grid--orgs > li:nth-child(n+3) { grid-column: span 2; }
}

@media (min-width: 700px) {
  ul
  
  
}
@media (min-width: 1000px) {
  
}
.res-grid > li, .res-grid--pair > li, .res-grid--trio > li { display: flex; min-width: 0; }
.res-grid > li > article, .res-grid--pair > li > article, .res-grid--trio > li > article { width: 100%; }
/* skip target must clear the sticky header when focused */
#main { scroll-margin-top: 120px; }
#main:focus { outline: none; }
#main:focus-visible { outline: 3px solid var(--focus-a); outline-offset: 4px; }

/* ---------- hero & carousel ---------- */
.hero {
  background: #4A3358; position: relative;
  width: 100%;
  /* min-height only — never max-height. A maximum pins the hero to a height
     unrelated to its content, and because the hero centres its child with
     overflow: hidden, a child taller than the box overflows symmetrically and
     BOTH halves get clipped. With the reader's text at 28px/2.4 the copy is
     ~2890px tall, so the eyebrow, the top of the headline, the end of the lede
     and both CTAs vanished entirely. The hero now grows with its content.
     Clipping lives on .hero-media instead, so the photograph is still cropped
     but the copy can never be cut off. */
  min-height: clamp(430px, 46vw, 660px);
  /* A COLUMN, not a row. The controls had to be absolutely positioned only
     because a row put them beside the copy, and absolute positioning is what
     made them able to overlap the headline. As a column the in-flow children
     are the control strip and then the copy, stacked and centred together, so
     the two occupy disjoint bands by construction rather than by clearance.
     The photo and scrim are absolute and unaffected. */
  display: flex; flex-direction: column; justify-content: center; align-items: stretch;
}

/* The desktop hero is driven by min-height: clamp(520px, 40vw, 700px). On a
   narrow screen 520px is most of the viewport, and because the photos are
   landscape, object-fit: cover then scales them up enormously and crops away
   most of the frame. Up to 1080px — the same breakpoint the navigation uses — the hero is an explicit 16:9
   band above the copy, matching the source almost exactly, so cover has
   essentially nothing to crop and the whole scene stays visible. */
/* Desktop hero: the band fills the hero behind the copy, and the controls sit
   at the top right — visually before the copy, matching their DOM position,
   with no "order" property anywhere. These sit above the responsive block
   below so that block can override them; declared after it, they won on
   source order and the band overlapped the copy. */
.hero-media { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
/* In normal flow in every mode: above the copy, which is where it comes in
   the DOM. Nothing is positioned, so nothing can overlap the headline. */
.carousel-controls-inline { position: relative; z-index: 2; padding: 0 40px 4px; justify-content: flex-start; }
/* Desktop copy is capped so it stays clear of the photograph behind it. */
.hero-copy { max-width: min(62%, 44rem); }

/* The band applies to narrow or squarish viewports only. A wide-but-short
   window (a 1024x640 or 900x540 laptop) is better served by the overlay: text
   and photograph share the same vertical space there, so the hero stays
   compact AND the image keeps a near-source ratio. Stacking them on such a
   window either crops the photo to fit or lets the band swallow the screen —
   the trade-off that a purely width-based breakpoint could not resolve. */
@media (max-width: 1080px) and (max-aspect-ratio: 13/10) {
  .hero { min-height: 0; max-height: none; display: block; }
  /* The source photographs are 1672x941, almost exactly 16:9. Matching the
     container to the source ratio means object-fit: cover has almost nothing
     to crop, so the whole scene stays visible instead of being enlarged to
     fill a taller box. The previous 4:3 band was 25% narrower than the source
     and cropped roughly a fifth off the sides while magnifying the subject.
     max-height keeps a short screen from being swallowed by the photo. */
  .hero-media {
    position: relative; inset: auto; width: 100%;
    aspect-ratio: 16 / 9; overflow: hidden;
    /* The cap needs a width-derived floor. Expressed purely in vh it becomes
       the PRIMARY driver on any short window and overrides the aspect ratio,
       so cover resumes cropping vertically: at 1024x640 a flat 58vh cut the
       band to 2.72:1 and hid 35% of the frame — the same failure as the
       earlier 38vh value, just at a different threshold. 46vw tracks the
       band's own width, so the ratio stays near the source 16:9 whatever the
       window height, while the vh term still limits tall-window layouts. */
    max-height: max(58vh, 46vw);
  }
  /* Controls flow directly beneath the band, where they read in the DOM. */
  .carousel-controls-inline {
    position: static; margin: 0; padding: 14px 40px 0;
    justify-content: flex-start; flex-wrap: wrap;
  }
  .hero-frames { position: absolute; inset: 0; }
  /* The scrim was hidden on mobile because the copy sat on plum below the
     photo. Now the headline is ON the photo, so it needs a dark ground: the
     scrim is scoped to the media cell and carries text-supporting darkness
     through roughly the left 58%, then fades out so the subject stays clear. */
  .hero-scrim {
    display: block; grid-area: media; position: relative; inset: auto;
    width: 100%; height: 100%; z-index: 2;
    background: linear-gradient(100deg,
      rgba(38,25,46,0.96) 0%,
      rgba(38,25,46,0.95) 40%,
      rgba(40,27,49,0.92) 58%,
      rgba(42,29,52,0.88) 70%,
      rgba(46,32,56,0.60) 82%,
      rgba(50,35,60,0.25) 92%,
      rgba(56,39,66,0) 100%);
  }
  /* Stays position: relative. Its inline z-index: 1 is what keeps the copy
     above the absolutely positioned scrim, and z-index does nothing on a
     static element — making it static dropped the text behind the scrim. */
  .hero-body { position: relative; padding: 34px 40px 40px; }
  /* The 62% cap exists to keep desktop copy clear of the photograph behind it.
     With the photo in its own band there is nothing to avoid, and at 320px the
     cap left a 167px text column — the single biggest cause of the tall
     mobile hero. */
  .hero .hero-copy { max-width: 100%; }
  .hero-copy { max-width: 100%; }
  /* At 16:9 the container matches the source, so cover has almost nothing to
     crop and object-position barely matters — centre is correct for all four.
     The source files are untouched; only framing changed. */
  .hero-frames img { object-position: center center; }
  /* The controls sit last in the copy column on desktop. On mobile the hero's
     height varies with text-size and line-spacing, so a strip at the bottom of
     the copy can end up far down the page. Flex order lifts it to the top of
     the copy, immediately under the image band — visually first, still last in
     the DOM and in tab order, and never over a face or the headline. */
  .hero-copy { display: flex; flex-direction: column; gap: 18px; }
  /* The headline's clamp floor of 27px was set for desktop. On a ~284px column
     that wraps to ten lines, so the floor drops for narrow screens. Still
     headline-scale, and it scales with the reader's text-size preference. */
  .hero .hero-copy h1 { font-size: calc(clamp(23px, 6.4vw, 32px) * var(--ts)); }
}
.hero-frames { position: absolute; inset: 0; z-index: 0; isolation: isolate; }

.hero-frames img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover;
  /* Above the mobile band the container is wider than the source 16:9, so the
     crop is vertical. "right center" pushed each subject to the edge and cut
     their equipment; these per-image values keep the person and the device
     they are using inside the frame. */
  object-position: 62% center;
  opacity: 0; transition: opacity 1600ms linear; will-change: opacity;
}
.hero-frames img[data-focal="couple"] { object-position: 58% 46%; }
.hero-frames img[data-focal="braille"] { object-position: 60% 30%; }
.hero-frames img[data-focal="editor"] { object-position: 66% 24%; }
.hero-frames img[data-focal="student"] { object-position: 56% 37%; }
.hero-frames img[data-active] { opacity: 1; }
.hero-scrim { position: absolute; inset: 0; z-index: 1; pointer-events: none; }

/* High contrast only: the rest of the theme is near-monochrome, so the hero's
   full-colour photography read as disconnected from it. A second scrim layer
   sits above the existing gradient and calms the imagery without touching the
   files — the photographs stay visible and their subjects recognisable, they
   simply stop competing with the restrained page. saturate() and contrast()
   act on the media layer only; no grayscale, so the images are not
   permanently desaturated and every other theme is unaffected. */
html[data-theme="contrast"] .hero-frames img {
  /* Near-natural: a slight desaturation only, no brightness reduction, so the
     photography reads clearly instead of disappearing behind a veil. */
  filter: saturate(0.9);
}
html[data-theme="contrast"] .hero-media::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  /* A very light uniform tint only — 10% — to settle the image into the theme
     without obscuring it. */
  background: rgba(0, 0, 0, 0.1);
}
html[data-theme="contrast"] .hero-scrim { display: none; }
html[data-theme="contrast"] .hero-statement > *,
html[data-theme="contrast"] .hero-headline .h1-line,
html[data-theme="contrast"] .hero-tail > .hero-copy > p {
  background: #150F1C;
  width: fit-content;
  max-width: 100%;
  padding: 2px 12px 2px 0;
  box-decoration-break: clone;
}
/* Was inline on the element, which meant the narrow-screen padding below never
   applied: at 320px the 40px side padding left a 240px text column, which is
   most of why the mobile hero was so tall. */
.hero-body { padding: 56px 40px 60px; }
.hero-body { position: relative; z-index: 2; width: 100%; max-width: 1180px; margin: 0 auto; padding: 56px 40px 60px; }

/* ---- hero: split copy, one H1, no CSS order ----
   Source order is statement -> H1 -> media -> controls -> paragraph -> CTAs.
   Desktop keeps the media and scrim absolute behind, with the three copy
   blocks in normal flow inside the 1180px measure. Mobile turns the hero into
   an explicit grid and places the headline in the SAME cell as the media, so
   it overlays the left of the photograph while still preceding it in the DOM —
   an overlay, not a reorder. */
.hero-statement, .hero-headline, .hero-tail, .carousel-controls-inline {
  position: relative; z-index: 2; box-sizing: border-box;
  width: 100%; max-width: 1180px; margin: 0 auto;
  padding-left: 40px; padding-right: 40px;
}
.hero-statement { padding-top: 56px; }
.hero-headline { padding-top: 18px; }
.hero-tail { padding-top: 20px; padding-bottom: 60px; }
.hero-statement > *, .hero-headline > h1 { max-width: min(52%, 36rem); }
@media (max-width: 820px) {
  .hero-statement, .hero-headline, .hero-tail { padding-left: 24px; padding-right: 24px; }
  .hero-statement { padding-top: 44px; }
  .hero-tail { padding-bottom: 48px; }
  .hero-statement > *, .hero-headline > h1 { max-width: 100%; }
}
.hero-copy { display: flex; flex-direction: column; gap: 24px; max-width: min(52%, 36rem); }
.hero-eyebrow { font-family: var(--uf); font-size: calc(11.5px * var(--ts)); letter-spacing: 0.16em; text-transform: uppercase; color: #86DCE4; }
.hero h1 {
  font-family: var(--hf); font-size: calc(clamp(27px, 2.8vw, 44px) * var(--ts)); font-weight: 400;
  line-height: calc(1.14 * var(--lhs)); letter-spacing: -0.022em; color: #FDFCFA; margin: 0; text-wrap: balance;
}
.hero p { font-family: var(--bf); font-size: calc(clamp(16px, 1.4vw, 20px) * var(--ts)); line-height: var(--bl); color: #FDFCFA; margin: 0; max-width: min(52ch, 100%); text-wrap: pretty; }
.hero-ctas { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; margin-top: 6px; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

.carousel-controls { position: absolute; z-index: 3; right: 40px; bottom: 26px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.carousel-btn {
  min-width: 44px; min-height: 44px; padding: 10px 14px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  background: rgba(26,22,32,0.55); border: 2px solid rgba(253,252,250,0.55);
  color: #FDFCFA; font-family: var(--bf); font-size: calc(14px * var(--ts)); font-weight: 600;
}
.dot-btn { width: 44px; height: 44px; padding: 0; cursor: pointer; background: none; border: 0; display: grid; place-items: center; }
.dot { width: 12px; height: 12px; border-radius: 6px; background: rgba(253,252,250,0.45); transition: width 200ms ease, background 200ms ease; }
.dot-btn[aria-current="true"] .dot { width: 30px; background: #5CC9D3; }

@media (prefers-reduced-motion: reduce) {
  .hero-frames img { transition: none; }
  .dot { transition: none; }
}

/* ---------- buttons & CTAs ---------- */
.btn-primary {
  display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
  min-height: 52px; padding: 15px 28px; background: #5CC9D3; color: #1A1620;
  font-family: var(--bf); font-size: calc(16.5px * var(--ts)); font-weight: 600; text-decoration: none;
}
.btn-quiet {
  display: inline-flex; align-items: center; box-sizing: border-box;
  min-height: 52px; padding: 15px 4px; color: #FDFCFA;
  font-family: var(--bf); font-size: calc(16.5px * var(--ts)); font-weight: 600; white-space: nowrap;
  text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 4px;
}
.btn-panel {
  align-self: flex-start; display: inline-flex; align-items: center; box-sizing: border-box;
  min-height: 50px; margin-top: 8px; padding: 14px 26px; background: #5CC9D3; color: #1A1620;
  font-family: var(--bf); font-size: calc(16px * var(--ts)); font-weight: 600; text-decoration: none;
}
.btn-outline {
  align-self: flex-start; display: inline-flex; align-items: center; box-sizing: border-box;
  min-height: 50px; margin-top: 10px; padding: 14px 26px;
  background: var(--brand-panel); border: 1px solid var(--brand-edge); color: #FDFCFA;
  font-family: var(--bf); font-size: calc(16px * var(--ts)); font-weight: 600; text-decoration: none;
}

/* ---------- shared content components ---------- */
.section { max-width: 1180px; margin: 0 auto; padding: 72px 40px 20px; }
.eyebrow { font-family: var(--uf); font-size: calc(11.5px * var(--ts)); letter-spacing: 0.16em; text-transform: uppercase; color: var(--label); }
.page-head { display: flex; flex-direction: column; gap: 20px; }
.page-head h1 { font-family: var(--hf); font-size: calc(50px * var(--ts)); font-weight: 400; line-height: calc(1.1 * var(--lhs)); letter-spacing: -0.025em; color: var(--fg); margin: 0; text-wrap: pretty; }
.lede { font-family: var(--bf); font-size: var(--bs); line-height: var(--bl); color: var(--fg); margin: 0; text-wrap: pretty; }

/* One parent-level gap for the whole card sequence. 46px matches the existing
   space between the last card and the pricing panel, which was the visual
   reference — no per-card margins. */
.svc-stack { display: flex; flex-direction: column; gap: 46px; }

.svc { display: flex; gap: 32px; flex-wrap: wrap; justify-content: space-between; background: var(--surface); border: 1px solid var(--line); padding: 26px 30px 28px; }
.svc { border-left: 4px solid var(--eyebrow); }
/* The pricing panel takes the same rule in plum, marking it as a note about
   all the services rather than one more service in the list. */
.pricing-panel {
  border: 1px solid var(--line); border-left: 4px solid var(--accent-plum);
  display: flex; gap: 32px; flex-wrap: wrap; justify-content: space-between;
  background: var(--surface); padding: 36px 40px;
}

/* The email CTA holds a long unbreakable address. Capping it at the container
   width and allowing the address to break keeps it inside a 320px viewport
   instead of forcing the whole page to scroll sideways. */
/* overflow-wrap: break-word does NOT reduce an element's intrinsic minimum
   width, so at the largest text sizes one long heading word could still force
   its container wider than a 320px viewport. "anywhere" does affect
   min-content, so headings reflow instead of pushing the page sideways. */
h1, h2, h3, .svc-shape { overflow-wrap: anywhere; }
/* At the largest text sizes the hero CTAs and the Text & display button were
   the last things that could not shrink: the CTAs had no width cap, and the
   button carries white-space: nowrap so it stays on one line in the desktop
   header. Below the desktop breakpoint that constraint is not needed. */
.hero-cta { max-width: 100%; overflow-wrap: anywhere; text-align: center; }
/* Service tags sit beside a heading in a wrapping flex row; cap them so a
   long tag cannot exceed the card at the largest text sizes. */
.svc-tag { max-width: 100%; box-sizing: border-box; overflow-wrap: anywhere; }
@media (max-width: 1199px) {
  /* Separators belong to the horizontal desktop row only; in the stacked
     disclosure nav a vertical rule between items makes no sense. Scoped to the
     breakpoint, not to [data-collapsible], which is present at every width. */
  .site-nav a + a::before { display: none; }
  .prefs-toggle { white-space: normal; max-width: 100%; }
}
/* The skip link is parked off-screen; cap it so large text cannot give it a
   width that influences layout. */
.skip { max-width: 90vw; }

/* One primary-action treatment, shared by every teal CTA. Teal on dark ink
   holds 8:1+ in all four themes because both colours are fixed rather than
   theme-derived, which is what "Start a project" already relied on. */
.btn-primary-teal {
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box; align-self: flex-start;
  max-width: 100%; min-height: 50px; padding: 14px 26px;
  background: #5CC9D3; color: #1A1620; border: 0;
  font-family: var(--bf); font-size: calc(16px * var(--ts)); font-weight: 600;
  text-decoration: none; overflow-wrap: anywhere;
}
.btn-primary-teal:hover { background: #7FDCE4; color: #1A1620; }

.email-cta {
  /* inline-block, not inline-flex: the address is a single text node, and in a
     flex container it becomes an anonymous item that will not wrap, so at the
     largest text sizes it overflowed its own button. A block context wraps it. */
  display: inline-block; box-sizing: border-box; align-self: flex-start;
  max-width: 100%; min-height: 52px; padding: 15px 28px;
  overflow-wrap: anywhere; text-align: center;
}
@media (max-width: 400px) { .email-cta { padding: 14px 16px; } }
.svc--lead { padding: 30px 32px 32px; }
.svc-main { display: flex; flex-direction: column; gap: 12px; flex: 1 1 100%; }
/* The qualifier now sits in the DOM right after the heading, so its
   wide-screen position on the right comes from ordinary flex placement in this
   row — no order property and nothing appears earlier than it does in source.
   When the row runs out of width it wraps beneath the heading, which is also
   its DOM position. */
/* Source order is heading -> tag -> qualifier, which is also the reading and
   stacking order. On wide cards the qualifier is pushed right by margin-left:
   auto, so the heading and its tag group at the left and nothing is reordered
   by CSS. Below 760px the row becomes a column and the visual sequence is the
   DOM sequence exactly. */
/* Source order is heading -> tag -> qualifier. The heading and its tag share a
   wrapper so a wrapping heading can never leave the tag stranded below the
   right-hand qualifier; the qualifier stays upper-right on wide cards and
   stacks third on narrow ones. No order property anywhere. */
.svc-head { display: flex; flex-direction: column; align-items: stretch; gap: 12px; }
.svc-title { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; min-width: 0; }
.svc-head > .svc-shape { min-width: 0; text-align: left; }
@media (max-width: 760px) {
  .svc-head { gap: 10px; }
  .svc-head > .svc-shape { text-align: left; }
}
.svc h2 { font-family: var(--hf); font-size: calc(27px * var(--ts)); font-weight: 400; line-height: calc(1.25 * var(--lhs)); letter-spacing: -0.018em; color: var(--fg); margin: 0; }
.svc-tag { font-family: var(--uf); font-size: calc(10.5px * var(--ts)); letter-spacing: 0.1em; text-transform: uppercase; color: #FDFCFA; background: #4A5D3A; padding: 4px 8px; }
/* Same badge system, but Pricing keeps its plum identity rather than the olive
   used on the service cards. The fill sits one step brighter than the card's
   accent border in each theme, so the badge reads as related but not identical.
   Text colour follows the fill: cream on the dark plums, ink on the light
   lavender the dark theme uses. */
.svc-tag--plum { background: #4A5D3A; }
.svc p { font-family: var(--bf); font-size: calc(16px * var(--ts)); line-height: calc(1.65 * var(--lhs)); color: var(--fg); margin: 0; max-width: min(800px, 100%); text-wrap: pretty; }
.svc-caveat { font-size: calc(14.5px * var(--ts)) !important; line-height: calc(1.6 * var(--lhs)) !important; color: var(--muted) !important; margin: 2px 0 0 !important; font-style: italic; }
/* The qualifier is a lead-in line, not a tag: plain text in the plum family so
   it outranks body copy without becoming a badge, and without borrowing teal,
   which this site reserves for interactive things. --accent-plum is already
   theme-aware — deep plum in Light/Warm, lavender in Dark. */
.svc-shape { font-family: var(--bf); font-size: calc(15px * var(--ts)); line-height: calc(1.55 * var(--lhs)); font-weight: 600; color: var(--accent-plum); min-width: 0; max-width: min(80ch, 100%); }

.sev { font-family: var(--uf); font-size: calc(11.5px * var(--ts)); letter-spacing: 0.08em; padding: 7px 12px; }
.sev--critical { background: #C1462F; color: #FDFCFA; }
.sev--high { background: #E8B33C; color: #1A1620; }
.sev--moderate { background: #4A5D3A; color: #FDFCFA; }
.sev--low { background: #1F5459; color: #FDFCFA; }

/* ---------- footer ---------- */
.site-footer { background: var(--brand-panel); border-top: 1px solid var(--brand-edge); margin-top: 90px; }
.footer-row { max-width: 1180px; margin: 0 auto; padding: 48px 40px 54px; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); gap: 34px; }
/* Eight links as 4 + 4 on desktop. Column flow with four explicit rows fills
   the first column top-to-bottom, then the second, so the visual order is the
   DOM order — no reordering property, no tabindex, one list. */
.footer-nav ul {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-auto-flow: column;
  grid-template-rows: repeat(4, auto); gap: 2px 26px;
}
/* One clean vertical list when two columns get cramped or the text is large. */
@media (max-width: 860px) {
  .footer-nav ul { grid-auto-flow: row; grid-template-rows: none; }
}
.footer-affil { font-family: var(--bf); font-size: calc(13px * var(--ts)); line-height: calc(1.55 * var(--lhs)); color: rgba(253,252,250,0.72); margin: 6px 0 0; max-width: min(34ch, 100%); overflow-wrap: anywhere; }
.footer-affil a,
.footer-link-teal { color: #5CC9D3; }
/* WCAG 2.2 SC 2.5.8. These are block-level links in a flex column, not links
   inside a sentence, so the inline exception does not apply and a 19px box was
   under the 24px minimum. Vertical padding lifts the target without changing
   the type or the column's rhythm. */
.footer-link-teal { display: inline-block; padding: 4px 0; }
/* Same reason: a resource card's heading link is the whole heading, so its
   inline content box (20px) was the whole target. */
.res-card h3 > a, .res-card h4 > a { display: inline-block; padding: 3px 0; }
/* The plum accent used on the Published work card, in its lavender tint. The
   token's light-theme value (#4A3358) is the footer's own background colour,
   so the literal value would render the address invisible; the lavender is the
   same accent in the dark-surface variant the footer actually needs. */
/* Non-interactive eyebrow/status labels use the brand olive; interactive text
   keeps the teal link treatment.
   .label-olive  — labels on page surfaces, theme-aware.
   .footer-label / .label-on-plum — labels on the footer and hero, which are
   plum in EVERY theme, so they need the sage tint rather than --label (whose
   High contrast value is near-black and would vanish on plum). */
.footer-label, .label-on-plum { color: #A9CE8A; }
.label-olive { color: var(--label); }
.footer-affil a:hover,
.footer-link-teal:hover { color: #7FDCE4; }

/* Service inquiry link: a plain anchor, deliberately quieter than the page's
   primary actions, with a 44px target from padding rather than a button fill. */
.svc-ask { margin: 10px 0 0; }
.svc-ask a {
  display: inline-flex; align-items: center; min-height: 44px; padding: 10px 0;
  max-width: 100%; flex-wrap: wrap;
  font-family: var(--bf); font-size: calc(15px * var(--ts)); font-weight: 600;
  color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 3px;
  overflow-wrap: anywhere;
}
.svc-ask a:hover { color: var(--fg); }

/* ---- Resources page ---- */
/* Restrained status note: modest border, the site's own surface and padding.
   No icon, no warning colour, no animation. */
.res-note {
  background: var(--surface); border: 1px solid var(--line);
  border-left: 4px solid var(--accent-plum);
  padding: 22px 26px; margin: 0 0 28px; max-width: min(74ch, 100%);
  display: flex; flex-direction: column; gap: 8px;
}
.res-note-title { font-family: var(--bf); font-size: calc(15.5px * var(--ts)); font-weight: 600; color: var(--fg); margin: 0; }
.res-note p:last-child { font-family: var(--bf); font-size: calc(15px * var(--ts)); line-height: calc(1.65 * var(--lhs)); color: var(--muted); margin: 0; }
.res-framing { font-family: var(--bf); font-size: calc(16.5px * var(--ts)); line-height: calc(1.6 * var(--lhs)); color: var(--fg); margin: 0 0 44px; max-width: min(74ch, 100%); }
.res-section { margin: 0 0 48px; }
.res-section > h2 {
  font-family: var(--hf); font-size: calc(28px * var(--ts)); font-weight: 400;
  line-height: calc(1.24 * var(--lhs)); letter-spacing: -0.018em; color: var(--fg);
  margin: 0 0 14px;
}
.res-disclaimer { font-family: var(--bf); font-size: calc(14.5px * var(--ts)); line-height: calc(1.6 * var(--lhs)); font-style: italic; color: var(--muted); margin: 0 0 26px; max-width: min(74ch, 100%); }
.res-region {
  font-family: var(--uf); font-size: calc(11.5px * var(--ts)); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--eyebrow); font-weight: 400;
  margin: 30px 0 14px;
}
/* Two columns where comfortable, one when constrained. Cards keep their own
   height — no stretch, so a short blurb does not inherit a tall neighbour's
   empty space. */
.res-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: 16px; align-items: start;
}
/* Oversized decorative quote marks as real elements, not generated content:
   aria-hidden can only be applied to an element, and CSS content is exposed as
   text by several screen readers. They sit outside the <blockquote>, so the
   quotation's own text stays clean for copy-paste and extraction too. */
/* Absolutely positioned so the marks add no height: as block-level flex
   children their margins were inflating the card. Padding on the card leaves
   room for them at the corners instead. */
/* Padding reserves the corner space the marks occupy, so they overlay
   without landing on the quotation or the attribution. */
.quote-card { position: relative; padding: 48px 48px 44px; }
.quote-card { gap: 14px; }
.quote-mark {
  position: absolute;
  font-family: var(--hf);
  font-size: calc(58px * var(--ts));
  line-height: 1;
  color: var(--eyebrow);
  opacity: 0.5;
  user-select: none;
  pointer-events: none;
}
.quote-mark--open { top: 6px; left: 32px; }
.quote-mark--close { bottom: -2px; right: 32px; }
@media (max-width: 560px) {
  .quote-card { padding: 38px 22px 36px; }
  .quote-mark { font-size: calc(44px * var(--ts)); }
  .quote-mark--open { top: 10px; left: 16px; }
  .quote-mark--close { bottom: 4px; right: 16px; }
}

/* A card that spans the content width, like the Published work card on About,
   rather than sitting in the two-column resource grid. */
.res-wide { max-width: none; width: 100%; }

/* The status note as a full-content-width anchor. The prose keeps a readable
   measure inside the wider card instead of stretching to the full line. */
.res-note--wide { max-width: none; }
.res-note--wide > * { max-width: min(74ch, 100%); }

/* Two explicit rows so the DOM order IS the visual order — no "order"
   property anywhere. Each row spans the content width; the pair splits it in
   two, the trio in three, and both collapse to fewer tracks when the cards
   would get cramped. */
.res-grid--pair,
.res-grid--trio { display: grid; gap: 16px; align-items: start; }
.res-grid--pair { grid-template-columns: 1fr; }
.res-grid--trio { grid-template-columns: 1fr; margin-top: 16px; }
@media (min-width: 700px) {
  .res-grid--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .res-grid--trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1000px) {
  .res-grid--trio { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.res-card {
  background: var(--surface-2); border: 1px solid var(--line);
  padding: 22px 24px 24px; min-width: 0;
  display: flex; flex-direction: column; gap: 9px;
}
.res-card h3, .res-card h4 {
  font-family: var(--hf); font-size: calc(20px * var(--ts)); font-weight: 400;
  line-height: calc(1.28 * var(--lhs)); letter-spacing: -0.012em; color: var(--fg);
  margin: 0; overflow-wrap: anywhere;
}
.res-card p {
  font-family: var(--bf); font-size: calc(15px * var(--ts));
  line-height: calc(1.62 * var(--lhs)); color: var(--fg); margin: 0;
}
/* Quiet centred closing section: a narrow measure, centred container, and a
   plain list of titles rather than six more cards. */
/* A card spanning the content width rather than a bare centred column. The
   inner measure stays narrow so the centred list keeps its rhythm. */
/* On About the card follows .page-split, which already carries 90px of bottom
   padding, so this wrapper only needs the closing space. */
.about-published { padding-bottom: 90px; }
.about-published .res-section { margin-bottom: 0; }

.res-published {
  background: var(--surface-2); border: 1px solid var(--line);
  /* Same plum accent language as the Pricing panel's left rule, mirrored on
     both edges. border-box sizing means the 4px does not shift the content. */
  border-left: 4px solid var(--accent-plum);
  border-right: 4px solid var(--accent-plum);
  box-sizing: border-box;
  padding: 34px 32px 36px; margin: 0; text-align: center;
}
.res-published > * { max-width: min(62ch, 100%); }
.res-published > h2,
.res-published > .res-published-lede,
.res-published > .pub-list { margin-left: auto; margin-right: auto; }
.res-published > h2 { text-align: center; }
.res-published-lede {
  font-family: var(--bf); font-size: calc(16px * var(--ts));
  line-height: calc(1.62 * var(--lhs)); color: var(--muted);
  margin: 0 0 22px;
}
.pub-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; align-items: center; gap: 16px; }
.pub-list li { display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0; max-width: 100%; text-align: center; }
.pub-list a {
  /* Public Sans, not the display serif: at these sizes the article titles read
     as a scannable index rather than as headings. */
  font-family: var(--bf); font-size: calc(18px * var(--ts));
  line-height: calc(1.34 * var(--lhs)); letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}
.pub-date { font-family: var(--uf); font-size: calc(12px * var(--ts)); letter-spacing: 0.06em; color: var(--muted); }

/* ---- WebAIM infographic: compact card + modal ---- */
.res-featured { gap: 12px; }
.res-featured-row { display: flex; align-items: stretch; gap: 16px; flex-wrap: wrap; }
/* The two controls stack into a column beside the tall thumbnail and share
   its height, so there is no empty gap under the primary action. */
.res-featured-actions {
  display: flex; flex-direction: column; justify-content: center;
  gap: 10px; flex: 1 1 200px; min-width: 0;
}
/* Two-class selectors, because .btn-primary-teal and .btn-secondary-teal are
   declared later in this file and would otherwise win at equal specificity.
   flex: 1 1 auto makes the pair share the column height, so they stay flush
   even when one label wraps to two lines at large text sizes. */
.res-featured-actions > .btn-primary-teal,
.res-featured-actions > .btn-secondary-teal {
  width: 100%; justify-content: center; text-align: center;
  flex: 1 1 auto;
  min-height: 50px; padding-top: 13px; padding-bottom: 13px;
  font-size: calc(16px * var(--ts));
  /* Matching 2px border on both. The secondary's outline was making it 3.7px
     taller than the filled button; a transparent border on the primary equalises
     the box metrics, and since background paints under the border area the
     filled button looks exactly as before. */
  border: 2px solid transparent;
}
.res-featured-actions > .btn-secondary-teal { border-color: var(--eyebrow); }

/* Secondary action: same shape and target as the primary, quieter fill, so
   the teal button stays clearly primary without this one becoming obscure. */
.btn-secondary-teal {
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box; min-height: 50px; padding: 14px 22px;
  background: transparent; border: 2px solid var(--eyebrow);
  color: var(--eyebrow); font-family: var(--bf);
  font-size: calc(15.5px * var(--ts)); font-weight: 600;
  text-decoration: none; overflow-wrap: anywhere;
}
.btn-secondary-teal:hover { background: var(--eyebrow); color: var(--bg); }
/* Proportional thumbnail: the poster is 378x864, so a 62px width keeps its
   ratio without the tall shape dominating the card. */
.ig-thumb {
  display: block; width: 62px; height: auto; flex: none;
  border: 1px solid var(--line); background: #fff;
}
.ig-open { min-height: 44px; }

.ig-dialog {
  padding: 0; border: 1px solid var(--line); background: var(--surface-2);
  color: var(--fg); max-width: min(94vw, 820px); width: 100%;
  max-height: 90vh; overflow: hidden;
  /* the dialog is a column so the body can scroll between fixed head and foot */
  display: none; flex-direction: column;
}
.ig-dialog[open] { display: flex; }
.ig-dialog::backdrop { background: rgba(26,22,32,0.72); }
.ig-dialog-head {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 18px 22px; border-bottom: 1px solid var(--line); flex: none;
}
.ig-dialog-head h2 {
  font-family: var(--hf); font-size: calc(22px * var(--ts)); font-weight: 400;
  line-height: calc(1.26 * var(--lhs)); letter-spacing: -0.014em; margin: 0;
  overflow-wrap: anywhere;
}
.ig-close {
  min-height: 44px; padding: 10px 16px; cursor: pointer; flex: none;
  background: transparent; border: 2px solid var(--eyebrow); color: var(--eyebrow);
  font-family: var(--bf); font-size: calc(15px * var(--ts)); font-weight: 600;
}
.ig-close:hover { background: var(--eyebrow); color: var(--bg); }
/* The poster is long; it keeps its full width and the body scrolls, rather
   than being shrunk to fit and becoming unreadable. */
.ig-dialog-body { overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; padding: 18px 22px; }
.ig-dialog-body img { display: block; width: 100%; height: auto; margin: 0 auto; background: #fff; }
/* The scrollable viewer is a focusable region; the shared :focus-visible rule
   gives it a ring, inset so it reads inside the dialog body. */
.ig-kbd-hint {
  font-family: var(--bf); font-size: calc(13.5px * var(--ts));
  line-height: calc(1.5 * var(--lhs)); color: var(--muted);
  margin: 0; padding: 10px 22px 0; flex: none;
}
.ig-dialog-body[tabindex="0"] { outline-offset: -3px; }

/* Text-version dialog: same shell, prose instead of an image. */
.txt-dialog-body h3 {
  font-family: var(--hf); font-size: calc(19px * var(--ts)); font-weight: 400;
  line-height: calc(1.3 * var(--lhs)); letter-spacing: -0.01em;
  color: var(--fg); margin: 22px 0 6px; overflow-wrap: anywhere;
}
.txt-dialog-body h3:first-child { margin-top: 0; }
.txt-dialog-body p {
  font-family: var(--bf); font-size: calc(15.5px * var(--ts));
  line-height: calc(1.62 * var(--lhs)); color: var(--fg);
  margin: 0; max-width: min(68ch, 100%);
}
.txt-attrib {
  margin-top: 28px; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: calc(13.5px * var(--ts)); color: var(--muted);
}

.ig-dialog-foot {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 16px 22px 18px; border-top: 1px solid var(--line); flex: none;
}
@media (max-width: 560px) {
  .ig-dialog { max-width: 100vw; max-height: 94vh; }
  .ig-dialog-head, .ig-dialog-body, .ig-dialog-foot { padding-left: 14px; padding-right: 14px; }
}
/* The footer email is a long unbreakable address; cap it and let it break so
   large text sizes cannot push the page sideways. */
.site-footer a[href^="mailto:"] { max-width: 100%; overflow-wrap: anywhere; }
.footer-nav { display: flex; flex-direction: column; gap: 2px; }
.footer-nav a { display: block; padding: 4px 0; font-family: var(--bf); font-size: calc(14.5px * var(--ts)); color: rgba(253,252,250,0.82); text-decoration: none; }
.footer-nav a:hover { color: #5CC9D3; text-decoration: underline; }

/* ============================================================
   Responsive pass
   Breakpoints are content-driven, not device-driven:
   1080px — the width at which brand + seven nav labels + the
            Text & display control stop fitting one row in the
            widest selectable font (OpenDyslexic / Monospace).
    820px — multi-column page layouts stop having room for two
            readable columns.
    560px — section padding and display type need to step down.
   ============================================================ */

/* ---- images ---- */
.brand img { width: 68px; height: 68px; }
.footer-logo { display: block; width: 44px; height: 44px; }
.portrait { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; }
@media (min-width: 900px) {
  .portrait { width: 88%; margin-left: auto; margin-right: auto; }
  /* The portrait is 88% wide and centred, so its left edge sits 6% into the
     card's content box. One shared inset puts both text blocks on that same
     line instead of per-line margins. Dropped below 900px, where the portrait
     fills the card and an inset would only cramp the text. */
  .about-inset { padding-left: 6%; }
}
.hero-frames picture { display: contents; }

/* ---- responsive navigation ----
   The toggle is injected by site.js and stays hidden until the nav actually
   needs collapsing, so keyboard order is brand -> toggle -> links. */
.nav-toggle { display: none; }
/* Both header controls are authored in the HTML now, so script never inserts a
   node into the banner. Without JavaScript they would be inert, so the
   pre-paint inline script removes .no-js and CSS hides them until it runs. */
html.no-js .nav-toggle, html.no-js .prefs-toggle { display: none !important; }
.nav-toggle {
  align-items: center; gap: 9px; min-height: 44px; padding: 11px 14px;
  cursor: pointer; white-space: nowrap; flex-shrink: 0;
  font-family: var(--bf); font-size: calc(15px * var(--ts)); font-weight: 600;
  border: 2px solid var(--eyebrow); background: transparent; color: var(--eyebrow);
}
.nav-toggle[aria-expanded="true"] { background: var(--eyebrow); color: #FDFCFA; }
html[data-theme="dark"] .nav-toggle[aria-expanded="true"] { color: #101018; }
.nav-bars { display: grid; gap: 3px; }
.nav-bars span { display: block; width: 16px; height: 2px; background: currentColor; }

@media (max-width: 1199px) {
  .header-row { gap: 16px; }
  .header-group { flex: 1 1 auto; min-width: 0; flex-wrap: wrap; justify-content: flex-end; }
  .nav-toggle { display: inline-flex; order: 1; }
  .prefs-toggle { order: 2; }
  .site-nav[data-collapsible] {
    flex-basis: 100%; width: 100%; order: 3; flex-direction: column; align-items: stretch;
    gap: 0; border-top: 1px solid var(--line); margin-top: 8px; padding-top: 4px;
  }
  .site-nav[data-collapsible][hidden] { display: none; }
  .site-nav[data-collapsible] a { min-height: 48px; padding: 13px 6px; font-size: calc(16px * var(--ts)); }
}

@media (min-width: 1200px) {
  /* The header deliberately runs wider than the 1180px body container so the
     brand sits farther out, the utility control farther right, and the nav
     gets the space between them. Outer margins stay comfortable because the
     box is capped, not edge-to-edge. */
  .header-row { flex-wrap: nowrap; max-width: 1280px; gap: 18px; }
  .header-group { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; justify-content: flex-end; gap: 12px; }
  .site-nav { flex: 1 1 auto; min-width: 0; flex-wrap: wrap; justify-content: center; gap: 4px; }
  .prefs-toggle { flex: 0 0 auto; }
  .site-nav a { padding-left: 6px; padding-right: 6px; }
  .site-nav a + a::before { left: -3px; }
}

@media (min-width: 1280px) {
  .header-row { gap: 22px; }
  .header-group { gap: 14px; }
  .site-nav { gap: 6px; }
  .site-nav a { padding-left: 9px; padding-right: 9px; }
  .site-nav a + a::before { left: -4px; }
}

/* From 1366px only the outer gaps grow. */
@media (min-width: 1366px) {
  .header-row { gap: 30px; }
  .header-group { gap: 18px; }
  .brand { flex: 0 0 auto; }
  .header-group { flex: 1 1 auto; min-width: 0; }
  /* No override needed here: syncToWidth() removes the hidden attribute above
     the breakpoint, so the attribute and the rendered state always agree. */
}

/* ---------- wide desktop (>= 1600px) ----------
   Nothing below 1600px is affected. On a very wide monitor the 1180px page
   shell and 1280px header left several hundred pixels of unused gutter either
   side, so the STRUCTURAL containers widen here — the page frame, not the
   reading measures. The H1, hero paragraph and body copy keep their own ch
   based max-widths, so line lengths are unchanged; only the left edge of the
   composition moves outward. */
@media (min-width: 1600px) {
  .header-row { max-width: 1540px; }
  .wrap,
  .section,
  .page-split,
  .footer-row { max-width: 1540px; }
  /* The hero rails share the shell so statement, H1, controls, paragraph and
     CTAs keep one common left edge. */
  .hero-statement,
  .hero-headline,
  .hero-tail,
  .carousel-controls-inline { max-width: 1540px; }
}

/* ---- narrow-viewport layout ---- */
@media (max-width: 820px) {
  .wrap, .section { padding-left: 24px; padding-right: 24px; }
  .header-row { padding: 14px 24px; }
  .prefs-grid { padding: 22px 24px 26px; grid-template-columns: 1fr; gap: 20px; }
  .hero-body { padding: 44px 24px 48px; }
  .hero-copy { max-width: 100%; }
  .page-head h1 { font-size: calc(clamp(30px, 7vw, 50px) * var(--ts)); }
  .footer-row { padding: 40px 24px 46px; grid-template-columns: 1fr; gap: 28px; }
  .svc { padding: 22px 22px 24px; gap: 18px; }
  .svc--lead { padding: 24px 24px 26px; }
  .svc-shape { min-width: 0; }
  /* every desktop two/three-column block collapses to one column; source
     order already reads correctly top to bottom */
  .cols-2, .cols-3, .cols-auto { grid-template-columns: 1fr !important; }
}

@media (max-width: 640px) {
  .hero-media { max-height: 40vh; }
  /* Explicit rows; the headline shares the media row so it sits over the
     photograph's protected left side. No order property is used. */
  .hero {
    display: grid;
    grid-template-areas: "statement" "media" "controls" "tail";
    grid-template-columns: 100%;
  }
  .hero-statement { grid-area: statement; padding-top: 22px; padding-bottom: 4px; }
  .hero-headline {
    grid-area: media; z-index: 3;
    margin-left: 0; margin-right: auto;
    padding-top: 0; padding-bottom: 0;
    max-width: 64%;
    display: flex; align-items: center;
  }
  .hero-headline > h1 {
    max-width: 100%;
    font-size: calc(clamp(18px, 5.2vw, 24px) * var(--ts));
    line-height: calc(1.18 * var(--lhs));
  }
  .hero-media { grid-area: media; height: 100%; }
  .carousel-controls-inline {
    grid-area: controls; gap: 14px; padding: 10px 18px 2px;
    justify-content: flex-start; align-items: center;
  }
  /* Mobile keeps one Play/Pause plus the four position indicators; Previous and
     Next are desktop-only, so the row stops eating vertical space. */
  [data-carousel-prev], [data-carousel-next] { display: none; }
  .dot-btn { min-width: 44px; min-height: 44px; }
  .hero-tail { grid-area: tail; padding-top: 14px; padding-bottom: 34px; }
  /* narrow-screen paddings for the hero band's neighbours */
  .carousel-controls-inline { padding: 12px 18px 0; }
  .hero-body { padding: 30px 18px 36px; }
  /* Mobile header: a full-bleed brand banner, then the two controls on their
     own row beneath it. The row itself drops its horizontal padding so the
     brand band can reach both viewport edges; each child re-applies its own
     padding, which avoids a 100vw hack and cannot create a scrollbar. */
  .header-row { flex-direction: column; align-items: stretch; gap: 0; padding: 0; max-width: none; }
  .brand {
    align-self: stretch; width: 100%; box-sizing: border-box; margin: 0;
    display: flex; justify-content: center; align-items: center;
    gap: 16px; text-align: left;
    padding: 20px 20px 18px;
    border-bottom: 1px solid var(--line);
  }
  /* Brand scale: the lockup carries the top of the site on mobile, so the mark
     and wordmark step up. Artwork, wording and aspect ratio are untouched. */
  .brand img { width: 64px; height: 64px; }
  .wordmark { font-size: calc(19px * var(--ts)); }
  .wordmark-tag { font-size: calc(15.5px * var(--ts)); }
  .header-group {
    flex: 1 1 100%; width: 100%; margin-left: 0; box-sizing: border-box;
    justify-content: space-between; align-items: flex-start; gap: 12px;
    padding: 14px 20px;
  }
  .nav-toggle { order: 1; }
  .prefs-toggle { order: 2; }
  /* Wide row-style links: the hit area spans the menu's content width rather
     than hugging the label, with restrained block padding so eight
     destinations stay reachable on a short screen. */
  .site-nav { order: 3; flex: 1 1 100%; width: 100%; align-items: stretch; box-sizing: border-box; padding: 0 20px 16px; gap: 2px; }
  .site-nav[data-collapsible] a, .site-nav a {
    width: 100%; box-sizing: border-box; justify-content: flex-start;
    text-align: left; font-size: calc(18px * var(--ts));
    padding: 11px 14px; min-height: 44px; white-space: normal;
  }
  .site-nav a[aria-current="page"] { text-underline-offset: 4px; }
  .prefs-panel { padding-left: 20px; padding-right: 20px; }
}

/* Panels are ordinary disclosures, but when one is open the page behind it is
   locked, so a panel taller than the viewport must scroll itself. */
/* Sized from the space genuinely left below the panel, not from the top of the
   viewport: --prefs-top is the panel's measured offset, published by site.js on
   open, resize and orientation change. dvh follows mobile browser chrome as it
   collapses, and the safe-area padding keeps the last control clear of a home
   indicator. The fallback offset only applies before the first measurement. */
.prefs-panel {
  max-height: calc(100dvh - var(--prefs-top, 8rem));
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}
@supports not (height: 100dvh) {
  .prefs-panel { max-height: calc(100vh - var(--prefs-top, 8rem)); }
}
body[data-scroll-locked] { overflow: hidden; }

/* Mobile footer: the major groups centre as blocks. Source order and keyboard
   order are untouched, so the visual order still matches the DOM. */
@media (max-width: 640px) {
  .footer-row { grid-template-columns: 1fr; gap: 30px; text-align: center; justify-items: center; }
  .footer-row > * { align-items: center; }
  .footer-brand { justify-content: center; }
  .footer-nav, .footer-nav ul { align-items: center; }
  .footer-nav a { text-align: center; }
}

@media (max-width: 560px) {
  .wrap, .section { padding-left: 18px; padding-right: 18px; }
  /* header-row padding intentionally omitted: the mobile brand band is
     full-bleed and its children own their padding (see the 640px block). */
  .brand { padding-left: 18px; padding-right: 18px; }
  .header-group { padding-left: 18px; padding-right: 18px; }
  .site-nav { padding-left: 18px; padding-right: 18px; }
  .prefs-grid { padding: 18px 18px 22px; }
  .hero-body { padding: 36px 18px 40px; }
  .footer-row { padding: 34px 18px 40px; }
  .section { padding-top: 48px; }
  .page-head h1 { font-size: calc(clamp(27px, 8vw, 40px) * var(--ts)); }
  .font-grid { grid-template-columns: 1fr; }
  .theme-grid { grid-template-columns: 1fr; }
  .svc { padding: 18px 16px 20px; }
  .svc--lead { padding: 20px 18px 22px; border-left-width: 4px; }
  .svc h2 { font-size: calc(23px * var(--ts)); }
}

/* ---- long strings must never force the page sideways ---- */
a[href^="mailto:"], .svc-shape, .wordmark, code { overflow-wrap: break-word; }
main { overflow-wrap: break-word; }
/* code fragments may scroll locally rather than widen the page */
pre, .code-block { max-width: 100%; overflow-x: auto; }

/* ---- touch targets ---- */
@media (pointer: coarse) {
  .site-nav a { min-height: 48px; }
  .dot-btn { width: 48px; height: 48px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}

/* ---- layout-critical declarations extracted from inline styles ----
   These four were fixed two-column grids that could not collapse. Moved here
   so the narrow-width rules can override them without !important. */
.page-split {
  max-width: 1180px; margin: 0 auto; padding: 72px 40px 90px;
  display: grid; grid-template-columns: 1.3fr 1fr; gap: 56px; align-items: start;
}
.page-split--even { grid-template-columns: 1fr 1fr; }
.panel-split { display: grid; grid-template-columns: 1.2fr 1fr; gap: 36px; align-items: center; }
.feature-split { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; align-items: start; }
/* Grid and flex children default to min-width:auto, so a column refuses to
   shrink below its content. At large text sizes that pushed tracks wider than
   the viewport. min-width:0 lets them reflow instead. */
.page-split > *, .panel-split > *, .feature-split > * { min-width: 0; }
.svc, .svc-main, .hero-copy, .footer-row > * { min-width: 0; }

@media (max-width: 960px) {
  .page-split, .page-split--even { grid-template-columns: 1fr; gap: 36px; }
  .portrait, .about-inset { width: 100%; max-width: 420px; margin-left: auto; margin-right: auto; }
  .about-inset { padding-left: 0; }
  .feature-split { grid-template-columns: 1fr; gap: 32px; }
  .panel-split { grid-template-columns: 1fr; gap: 26px; align-items: start; }
}
@media (max-width: 820px) {
  .page-split, .page-split--even { padding: 56px 24px 72px; }
}
@media (max-width: 560px) {
  .page-split, .page-split--even { padding: 44px 18px 60px; }
}

/* The collapsed nav needs the same scroll treatment as the Text & display
   panel, but it must NOT apply while the desktop row is showing: the shared
   rule matched .site-nav[data-collapsible] at every width and its 24px bottom
   padding inflated the desktop nav box by 15px, making the header 119px
   instead of 104px. Scoped to the disclosure breakpoint only. */
@media (max-width: 1199px) {
  .site-nav[data-collapsible] {
    max-height: calc(100dvh - var(--prefs-top, 8rem));
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  }
}
@supports not (height: 100dvh) {
  @media (max-width: 1199px) {
    .site-nav[data-collapsible] { max-height: calc(100vh - var(--prefs-top, 8rem)); }
  }
}
