/* ==========================================================================
   Exemplar PDC Project — shared design system
   Modern CSS: cascade layers, tiered design tokens, light-dark(), container
   queries, :has(), logical properties, forced-colors.
   No dependencies. Static multi-page site.
   ========================================================================== */

@layer reset, base, theme, layout, components, utilities;

/* ---------------------------------------------------------------- reset -- */
@layer reset {
  :where(html) { -webkit-text-size-adjust: 100%; }
  :where(body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, ul, ol) {
    margin-block: 0;
  }
  :where(ul[role="list"], ol[role="list"]) { list-style: none; padding-inline-start: 0; }
  :where(img, svg, video) { display: block; max-inline-size: 100%; height: auto; }
  :where(button, input, select, textarea) { font: inherit; color: inherit; }
  :where(a) { color: inherit; }
}

/* ----------------------------------------------------------------- base -- */
@layer base {
  :root {
    color-scheme: light dark;
    accent-color: var(--accent);

    /* Tier 1 — literal tokens ------------------------------------------- */
    --font-display: "Iowan Old Style", "Palatino Linotype", "Source Serif 4",
                    Palatino, Georgia, ui-serif, serif;
    --font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
                 "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

    --ink-95: oklch(0.16 0.02 260);
    --ink-70: oklch(0.36 0.02 260);
    --ink-50: oklch(0.52 0.015 260);
    --paper-00: oklch(1 0 0);
    --paper-02: oklch(0.985 0.004 260);
    --paper-05: oklch(0.965 0.006 260);
    --paper-08: oklch(0.935 0.008 260);

    --night-95: oklch(0.17 0.018 260);
    --night-90: oklch(0.21 0.02 260);
    --night-85: oklch(0.25 0.02 260);
    --night-80: oklch(0.30 0.022 260);
    --mist-95: oklch(0.95 0.01 260);
    --mist-75: oklch(0.78 0.015 260);
    --mist-60: oklch(0.66 0.015 260);

    /* Rhythm */
    --step--1: clamp(0.82rem, 0.79rem + 0.14vw, 0.9rem);
    --step-0:  clamp(1rem, 0.96rem + 0.2vw, 1.09rem);
    --step-1:  clamp(1.2rem, 1.12rem + 0.4vw, 1.42rem);
    --step-2:  clamp(1.45rem, 1.3rem + 0.72vw, 1.9rem);
    --step-3:  clamp(1.75rem, 1.48rem + 1.3vw, 2.6rem);
    --step-4:  clamp(2.1rem, 1.6rem + 2.4vw, 3.6rem);

    --sp-1: 0.375rem; --sp-2: 0.625rem; --sp-3: 1rem;
    --sp-4: 1.5rem;   --sp-5: 2.25rem;  --sp-6: 3.5rem; --sp-7: 5.5rem;

    --radius-s: 0.4rem; --radius-m: 0.75rem; --radius-l: 1.15rem; --radius-pill: 999px;
    --measure: 68ch;
    --shell: 78rem;

    --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
    --dur: 240ms;

    /* Tier 2 — semantic tokens ------------------------------------------ */
    --brand: light-dark(oklch(0.53 0.20 10), oklch(0.78 0.15 8));
    --brand-deep: light-dark(oklch(0.40 0.17 12), oklch(0.88 0.10 8));
    --brand-tint: light-dark(oklch(0.965 0.022 18), oklch(0.28 0.07 10));
    --accent: light-dark(oklch(0.64 0.17 48), oklch(0.80 0.15 55));
    /* --accent is tuned for borders and fills; as small text on a light
       background it only reaches 3.44:1. This darker mix clears 4.5:1 against
       every surface it lands on — the worst being --brand-tint behind an open
       timeline row, where it measures 4.60:1. Dark mode is already at 9.2:1. */
    --accent-ink: light-dark(oklch(0.55 0.17 48), oklch(0.80 0.15 55));
    /* Foreground for anything sitting ON --brand. In dark mode --brand is a
       light rose, so white-on-brand would fail contrast — flip to near-black. */
    --on-brand: light-dark(oklch(0.99 0 0), oklch(0.16 0.02 260));

    --bg: light-dark(var(--paper-02), var(--night-95));
    --bg-raised: light-dark(var(--paper-00), var(--night-90));
    --bg-sunken: light-dark(var(--paper-05), oklch(0.14 0.015 260));
    --bg-subtle: light-dark(var(--paper-08), var(--night-85));

    --fg: light-dark(var(--ink-95), var(--mist-95));
    --fg-muted: light-dark(var(--ink-70), var(--mist-75));
    --fg-faint: light-dark(var(--ink-50), var(--mist-60));

    --line: light-dark(oklch(0.89 0.008 260), oklch(0.32 0.02 260));
    --line-strong: light-dark(oklch(0.80 0.012 260), oklch(0.42 0.022 260));

    --shadow-s: 0 1px 2px light-dark(oklch(0.2 0.02 260 / 0.06), oklch(0 0 0 / 0.4));
    --shadow-m: 0 1px 2px light-dark(oklch(0.2 0.02 260 / 0.05), oklch(0 0 0 / 0.35)),
                0 8px 24px -8px light-dark(oklch(0.2 0.02 260 / 0.12), oklch(0 0 0 / 0.55));
    --shadow-l: 0 2px 4px light-dark(oklch(0.2 0.02 260 / 0.05), oklch(0 0 0 / 0.4)),
                0 20px 48px -16px light-dark(oklch(0.2 0.02 260 / 0.20), oklch(0 0 0 / 0.7));

    /* Flag Maker palette (flag of Mauritius) */
    --flag-1: oklch(0.58 0.20 26);
    --flag-2: oklch(0.44 0.15 258);
    --flag-3: oklch(0.83 0.16 92);
    --flag-4: oklch(0.55 0.13 152);
  }

  html { scroll-behavior: smooth; scroll-padding-block-start: 5.5rem; }
  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after {
      animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
    }
  }

  body {
    font-family: var(--font-body);
    font-size: var(--step-0);
    line-height: 1.62;
    color: var(--fg);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  h1, h2, h3, h4 {
    font-family: var(--font-display);
    font-weight: 600;
    line-height: 1.14;
    letter-spacing: -0.012em;
    text-wrap: balance;
    color: var(--fg);
  }
  h1 { font-size: var(--step-4); }
  h2 { font-size: var(--step-3); }
  h3 { font-size: var(--step-2); }
  h4 { font-size: var(--step-1); }
  p, li { text-wrap: pretty; overflow-wrap: break-word; }

  a:any-link { color: var(--brand); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
  a:any-link:hover { color: var(--brand-deep); }

  :where(a, button, summary, input, select, [tabindex]):focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 3px;
    border-radius: var(--radius-s);
  }

  ::selection { background: var(--brand-tint); color: var(--brand-deep); }

  code, kbd { font-family: var(--font-mono); font-size: 0.9em; }
  kbd {
    padding: 0.1em 0.4em; border: 1px solid var(--line-strong);
    border-radius: var(--radius-s); background: var(--bg-subtle);
  }

  @media (forced-colors: active) {
    .card, .panel, .chip, .pill { border: 1px solid CanvasText; }
    .stat-value, .hero h1 { color: CanvasText; }
  }
}

/* --------------------------------------------------------------- layout -- */
@layer layout {
  .shell { inline-size: min(100% - 2.5rem, var(--shell)); margin-inline: auto; }
  .stack > * + * { margin-block-start: var(--flow, var(--sp-3)); }
  .prose { max-inline-size: var(--measure); }
  .prose > * + * { margin-block-start: var(--sp-3); }

  .section { padding-block: var(--sp-6); }
  .section--tight { padding-block: var(--sp-5); }

  /* alternating page bands, so the page has rhythm instead of one flat field */
  .band { background: var(--bg); }
  .band--sunken { background: var(--bg-sunken); border-block: 1px solid var(--line); }
  .band--tint {
    background: linear-gradient(180deg, var(--brand-tint), transparent 85%), var(--bg);
    border-block: 1px solid var(--line);
  }
  /* Feature band — warmer and more saturated than --band--tint, for the two
     sections that should read as the page's offer: the volume and its use. */
  .band--feature {
    position: relative;
    background:
      radial-gradient(90% 120% at 88% 0%,
        color-mix(in oklab, var(--brand) 22%, transparent) 0%, transparent 60%),
      radial-gradient(80% 110% at 4% 100%,
        color-mix(in oklab, var(--accent) 20%, transparent) 0%, transparent 58%),
      linear-gradient(165deg,
        color-mix(in oklab, var(--brand) 10%, var(--bg-raised)) 0%,
        color-mix(in oklab, var(--accent) 6%, var(--bg-raised)) 100%);
    border-block: 1px solid color-mix(in oklab, var(--brand) 20%, var(--line));
  }
  /* a hairline of brand colour along the top edge, so the band starts crisply */
  .band--feature::before {
    content: ""; position: absolute; inset-inline: 0; inset-block-start: 0; block-size: 2px;
    background: linear-gradient(90deg, var(--brand), var(--accent) 60%, var(--brand));
  }

  .grid { display: grid; gap: var(--sp-4); }
  .grid--auto { grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr)); }
  .grid--three { grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); }
  .grid--wide { grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr)); }
  .grid--halves { grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr)); }
  /* narrow heading column beside a long list, so the list gets the room */
  .grid--aside { grid-template-columns: minmax(0, 19rem) minmax(0, 1fr); gap: var(--sp-6); }
  @media (width < 58rem) { .grid--aside { grid-template-columns: 1fr; gap: var(--sp-4); } }

  .cluster { display: flex; flex-flow: row wrap; gap: var(--sp-2); align-items: safe center; }
  .split { display: flex; flex-flow: row wrap; gap: var(--sp-4); align-items: safe center; }
  .split > .push { margin-inline-start: auto; }
}

/* ----------------------------------------------------------- components -- */
@layer components {

  /* --- skip link --- */
  .skip-link {
    position: fixed; inset-block-start: 0.5rem; inset-inline-start: 0.5rem;
    z-index: 100; padding: var(--sp-2) var(--sp-3);
    background: var(--brand); color: var(--on-brand);
    border-radius: var(--radius-s); font-weight: 600;
    transform: translateY(-160%); transition: transform var(--dur) var(--ease);
  }
  .skip-link:focus-visible { transform: none; }

  /* --- site header --- */
  .site-header {
    position: sticky; inset-block-start: 0; z-index: 40;
    background: light-dark(oklch(1 0 0 / 0.82), oklch(0.17 0.018 260 / 0.82));
    border-block-end: 1px solid var(--line);
    backdrop-filter: blur(14px) saturate(1.4);
  }
  @supports not (backdrop-filter: blur(1px)) { .site-header { background: var(--bg-raised); } }

  /* The header opens tall so the project can be named at full size, then
     compacts on the first scroll. site.js flips [data-scrolled]. */
  .masthead {
    display: flex; align-items: center; gap: var(--sp-3);
    min-block-size: 6.5rem;
    transition: min-block-size 320ms var(--ease);
  }
  .site-header[data-scrolled] .masthead { min-block-size: 4.25rem; }

  .brandmark { display: flex; align-items: center; gap: 0.85rem; text-decoration: none; min-inline-size: 0; }
  .brandmark .glyph {
    inline-size: 3rem; block-size: 3rem; flex: none;
    border-radius: 0.8rem; display: grid; place-items: center;
    background: linear-gradient(150deg, var(--brand), var(--brand-deep));
    color: var(--on-brand); box-shadow: var(--shadow-s);
    transition: inline-size 320ms var(--ease), block-size 320ms var(--ease),
                border-radius 320ms var(--ease);
  }
  .site-header[data-scrolled] .brandmark .glyph {
    inline-size: 2.4rem; block-size: 2.4rem; border-radius: 0.65rem;
  }
  .brandmark .glyph svg { inline-size: 62%; block-size: 62%; }
  .brandmark .name {
    font-family: var(--font-display); font-weight: 600;
    font-size: 1.5rem; line-height: 1.15; letter-spacing: -0.015em;
    transition: font-size 320ms var(--ease);
  }
  .site-header[data-scrolled] .brandmark .name { font-size: 1.16rem; }
  .brandmark .tag {
    display: block; margin-block-start: 0.34rem;
    font-family: var(--font-body); font-size: 0.7rem; line-height: 1.3;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-faint); font-weight: 500;
    max-inline-size: 62ch; max-block-size: 4em; opacity: 1; overflow: hidden;
    transition: max-block-size 320ms var(--ease), opacity 220ms var(--ease),
                margin-block-start 320ms var(--ease);
  }
  /* Compact state: the full course-sequence sentence would dominate the bar. */
  .site-header[data-scrolled] .brandmark .tag {
    max-block-size: 0; opacity: 0; margin-block-start: 0;
  }
  /* Hidden below 48rem (no room at all) and again from 60rem to 75rem, where
     the horizontal nav squeezes the brand column enough that the tagline
     wraps past its max-block-size and gets clipped mid-sentence. */
  @media (width < 48rem), (60rem <= width < 75rem) {
    .brandmark .tag { display: none; }
  }

  /* --- primary nav --- */
  .primary-nav { margin-inline-start: auto; }
  .primary-nav > ul { display: flex; gap: 0.15rem; list-style: none; padding: 0; margin: 0; }
  .nav-link {
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.5rem 0.72rem; border-radius: var(--radius-s);
    font-size: 0.94rem; font-weight: 500; color: var(--fg-muted);
    text-decoration: none; white-space: nowrap;
    min-block-size: 2.5rem;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
  }
  .nav-link:hover:not([aria-current]) { background: var(--bg-subtle); color: var(--fg); }
  .nav-link[aria-current="page"] { color: var(--brand-deep); background: var(--brand-tint); font-weight: 600; }

  /* leaves the site, so it is styled apart from the page links */
  .nav-out { margin-inline-start: 0.45rem; padding-inline-start: 0.45rem; border-inline-start: 1px solid var(--line); }
  .nav-link--cder {
    gap: 0.45rem;
    border: 1px solid color-mix(in oklab, var(--accent) 45%, var(--line));
    background: color-mix(in oklab, var(--accent) 12%, transparent);
    color: var(--fg);
  }
  .nav-link--cder:hover:not([aria-current]) {
    background: color-mix(in oklab, var(--accent) 22%, transparent);
    border-color: var(--accent);
    color: var(--fg);
  }
  .nav-link--cder img { block-size: 1rem; inline-size: auto; max-inline-size: none; }
  .nav-link--cder svg { inline-size: 0.8rem; block-size: 0.8rem; opacity: 0.7; }
  @media (width < 62rem) {
    .nav-out { margin-inline-start: 0; padding-inline-start: 0; border-inline-start: 0;
               margin-block-start: 0.4rem; padding-block-start: 0.4rem;
               border-block-start: 1px solid var(--line); }
  }

  .nav-toggle { display: none; }

  @media (width < 62rem) {
    .nav-toggle {
      display: inline-flex; align-items: center; gap: 0.45rem;
      margin-inline-start: auto;
      padding: 0.5rem 0.8rem; min-block-size: 2.75rem;
      background: var(--bg-subtle); border: 1px solid var(--line);
      border-radius: var(--radius-s); font-weight: 600; font-size: 0.9rem; cursor: pointer;
    }
    .primary-nav {
      position: absolute; inset-inline: 0; inset-block-start: 100%;
      background: var(--bg-raised); border-block-end: 1px solid var(--line);
      box-shadow: var(--shadow-m); padding: var(--sp-3);
      display: none;
    }
    .primary-nav[data-open] { display: block; }
    .primary-nav > ul { flex-direction: column; gap: 0.15rem; }
    .nav-link { inline-size: 100%; }
  }

  .icon-btn {
    display: inline-grid; place-items: center;
    inline-size: 2.5rem; block-size: 2.5rem; flex: none;
    border-radius: var(--radius-s); border: 1px solid var(--line);
    background: var(--bg-raised); color: var(--fg-muted); cursor: pointer;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
  }
  .icon-btn:hover { background: var(--bg-subtle); color: var(--fg); }
  .icon-btn svg { inline-size: 1.15rem; block-size: 1.15rem; }
  :root:not(.dark) .icon-btn .moon, :root.dark .icon-btn .sun { display: none; }

  /* --- buttons --- */
  .btn {
    display: inline-flex; align-items: center; gap: 0.5rem;
    padding: 0.68rem 1.15rem; min-block-size: 2.75rem;
    border-radius: var(--radius-pill); border: 1px solid transparent;
    font-size: 0.95rem; font-weight: 600; text-decoration: none; cursor: pointer;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
                transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
  }
  .btn svg { inline-size: 1.05rem; block-size: 1.05rem; flex: none; }
  .btn:hover:not(:disabled) { transform: translateY(-1px); }
  .btn:active:not(:disabled) { transform: none; }
  .btn--primary { background: var(--brand); color: var(--on-brand); box-shadow: var(--shadow-s); }
  .btn--primary:any-link { color: var(--on-brand); }
  .btn--primary:hover:not(:disabled) { background: var(--brand-deep); box-shadow: var(--shadow-m); }
  .btn--ghost { background: var(--bg-raised); border-color: var(--line-strong); color: var(--fg); }
  .btn--ghost:any-link { color: var(--fg); }
  .btn--ghost:hover:not(:disabled) { background: var(--bg-subtle); }
  .btn--sm { padding: 0.42rem 0.85rem; min-block-size: 2.25rem; font-size: 0.86rem; }
  /* Quieter than --ghost: a barely-there surface for controls that are about
     the document rather than actions on it. Lighter, not darker — a darker
     chip would pull the eye to the citation ahead of the download below it. */
  .btn--quiet {
    background: light-dark(oklch(1 0 0 / 0.62), oklch(1 0 0 / 0.05));
    border-color: var(--line); color: var(--fg-muted); box-shadow: none;
  }
  .btn--quiet:any-link { color: var(--fg-muted); }
  .btn--quiet:hover:not(:disabled) { background: var(--bg-raised); color: var(--fg); }

  /* --- eyebrow / labels --- */
  .eyebrow {
    display: inline-flex; align-items: center; gap: 0.45rem;
    font-size: 0.74rem; font-weight: 700; letter-spacing: 0.13em;
    text-transform: uppercase; color: var(--brand);
  }
  .eyebrow::before { content: ""; inline-size: 1.6rem; block-size: 2px; background: var(--accent); border-radius: 2px; }

  .lede { font-size: var(--step-1); line-height: 1.5; color: var(--fg-muted); max-inline-size: 58ch; text-wrap: pretty; }

  /* --- hero --- */
  /* Distinctly shaded surface, so the header reads as its own band rather
     than fading into the page. Deeper teal wash + warm accent bloom. */
  .hero {
    position: relative; overflow: clip;
    padding-block: clamp(2.5rem, 1.5rem + 6vw, 5.5rem);
    background:
      radial-gradient(105% 85% at 82% -12%,
        color-mix(in oklab, var(--brand) 30%, transparent) 0%, transparent 58%),
      radial-gradient(75% 95% at 4% 105%,
        color-mix(in oklab, var(--accent) 22%, transparent) 0%, transparent 55%),
      linear-gradient(168deg,
        color-mix(in oklab, var(--brand) 13%, var(--bg-raised)) 0%,
        color-mix(in oklab, var(--brand) 6%, var(--bg-raised)) 58%,
        var(--bg-raised) 100%);
    border-block-end: 1px solid color-mix(in oklab, var(--brand) 22%, var(--line));
  }
  .hero::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background-image:
      linear-gradient(to right, color-mix(in oklab, var(--brand) 26%, transparent) 1px, transparent 1px),
      linear-gradient(to bottom, color-mix(in oklab, var(--brand) 26%, transparent) 1px, transparent 1px);
    background-size: 3.5rem 3.5rem;
    mask-image: radial-gradient(115% 85% at 50% 0%, black, transparent 72%);
    opacity: 0.55;
  }
  .hero > * { position: relative; z-index: 1; }
  .hero-actions { margin-block-start: var(--sp-4); }
  /* Two rows: what identifies the volume, then what you can do with it. The
     meta row is set quiet so the download call to action still leads, and the
     badge is stretched to the small-button height so the row lines up. */
  .hero-meta { gap: 0.55rem; margin-block-start: var(--sp-4); }
  .hero-meta + .hero-actions { margin-block-start: var(--sp-3); }
  .hero-meta .doi { min-block-size: 2.25rem; }

  /* Release status. The badge is all that shows; the sentence is a tooltip on
     hover and on keyboard focus, so it is reachable without a pointer. The
     accessible name carries the same text, so it is never hover-only for
     assistive tech. */
  .release-note { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin: 0; }
  [data-tip] { position: relative; cursor: help; }
  [data-tip]::after {
    content: attr(data-tip); position: absolute; z-index: 20;
    inset-block-start: calc(100% + 0.5rem); inset-inline-start: 0;
    inline-size: max-content; max-inline-size: min(40ch, 80vw);
    padding: 0.5rem 0.7rem; border-radius: var(--radius-s);
    background: var(--fg); color: var(--bg);
    font-family: var(--font-body); font-size: 0.8rem; font-weight: 500;
    line-height: 1.45; text-transform: none; letter-spacing: normal;
    white-space: normal; text-align: start; pointer-events: none;
    opacity: 0; translate: 0 -4px;
    transition: opacity 140ms var(--ease), translate 140ms var(--ease);
  }
  [data-tip]:hover::after, [data-tip]:focus-visible::after { opacity: 1; translate: 0 0; }
  @media (prefers-reduced-motion: reduce) { [data-tip]::after { transition: none; } }
  /* A tooltip anchored to the inline start would run off a narrow screen. */
  @media (width < 34rem) { [data-tip]::after { max-inline-size: 74vw; } }

  /* "Which path should I take?" — a want on the left, where it leads on the
     right. Two columns where there is room, stacked pairs where there is not. */
  .pathlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
  .pathlist > li {
    display: grid; gap: 0.35rem var(--sp-5); align-items: baseline;
    grid-template-columns: minmax(0, 22ch) minmax(0, 1fr);
    padding-block: var(--sp-3);
    border-block-start: 1px solid var(--line);
  }
  .pathlist > li:last-child { border-block-end: 1px solid var(--line); }
  .pathlist-want { font-weight: 600; color: var(--fg); }
  .pathlist-want::before { content: "\201c"; }
  .pathlist-want::after  { content: "\201d"; }
  .pathlist-go { font-weight: 600; text-decoration: none; }
  .pathlist-go:hover { text-decoration: underline; }
  .pathlist-go .tiny { display: block; font-weight: 400; }
  @media (width < 40rem) {
    .pathlist > li { grid-template-columns: 1fr; }
  }

  /* Landing hero: two columns so the headline never leaves half the fold empty */
  .hero--split .hero-grid {
    display: grid; gap: var(--sp-6);
    /* grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr); */
    grid-template-columns: minmax(0, 2.00fr) minmax(0, 0.98fr);
    align-items: center;
  }
  .hero--split h1 { max-inline-size: 15ch; }
  .hero--split .lede { max-inline-size: 46ch; }
  @media (width < 64rem) {
    .hero--split .hero-grid { grid-template-columns: 1fr; gap: var(--sp-5); }
    .hero--split h1, .hero--split .lede { max-inline-size: 30ch; }
    .hero--split .lede { max-inline-size: 54ch; }
  }
  /* With the showcase slides commented out of index.php the second column has
     nothing in it, which left the headline boxed into the left half of a full
     width bar. Detecting the track rather than a modifier class means the two
     column layout comes back on its own the moment the markup is restored. */
  .hero--split .hero-grid:not(:has(.showcase-track)) {
    grid-template-columns: 1fr;
  }
  /* 34ch sets the headline on two lines here; tighter measures push it to
     three and it stops reading like a full width hero. */
  .hero--split .hero-grid:not(:has(.showcase-track)) h1 { max-inline-size: 34ch; }
  /* Wide, but still short of the full bar — a lede running the whole width of
     a 1400px viewport is a genuinely hard line to read back. */
  .hero--split .hero-grid:not(:has(.showcase-track)) .lede { max-inline-size: 72ch; }
  /* the dots and the caption have nothing left to describe */
  .hero-grid:not(:has(.showcase-track)) .showcase { display: none; }
  /* Interior page heroes stay single-column and shallower */
  .hero--page { padding-block: clamp(2.25rem, 1.5rem + 4vw, 4rem); }
  /* Interior headings ran to 20ch — under half the shell — which pushed every
     one of them onto three lines and wasted the vertical space. Matched to the
     home page's 34ch, with the lede widened to suit. */
  .hero--page h1 { max-inline-size: 34ch; }
  .hero--page .lede { max-inline-size: 72ch; }

  /* --- Flag Maker hero visual ------------------------------------------
     A 12x8 grid standing in for the flag of Mauritius. Cells fill in the
     order a given number of "processors" would colour them. Static and
     complete without JS; animated by assets/site.js when allowed.         */
  .flagmaker {
    max-inline-size: 30rem; margin-inline: auto;
    background: var(--bg-raised); border: 1px solid var(--line);
    border-radius: var(--radius-l); padding: var(--sp-3) var(--sp-4);
    box-shadow: var(--shadow-m);
  }
  .flagmaker-head { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: baseline; margin-block-end: var(--sp-3); }
  .flagmaker-head .fm-title { font-family: var(--font-display); font-weight: 600; font-size: 1.02rem; }
  .flagmaker-head .fm-sub { font-size: 0.8rem; color: var(--fg-faint); }
  /* --- showcase carousel -------------------------------------------------
     Two animated exemplars from the volume, crossfading, with dots.        */
  .showcase { position: relative; }
  .showcase-track { display: grid; }
  .showcase-slide {
    grid-area: 1 / 1; margin: 0;
    opacity: 0; visibility: hidden;
    transition: opacity 620ms var(--ease);
  }
  .showcase-slide[data-active] { opacity: 1; visibility: visible; }
  /* shared pager dots — used by the hero carousel and the news list */
  .dots { display: flex; align-items: center; gap: 0.5rem; }
  .dots--center { justify-content: center; }
  .dot {
    inline-size: 2.25rem; block-size: 0.4rem; cursor: pointer;
    border: 0; border-radius: var(--radius-pill);
    background: var(--line-strong);
    /* WCAG 2.5.8 wants a 24px target. The visible pill stays 6px tall; the
       padding enlarges the hit area and is clipped out of the paint. */
    padding-block: 0.6rem; padding-inline: 0; box-sizing: content-box;
    background-clip: content-box;
    transition: background var(--dur) var(--ease), inline-size var(--dur) var(--ease);
  }
  .dot:hover { background-color: color-mix(in oklab, var(--brand) 55%, var(--line-strong)); }
  .dot[aria-selected="true"] { background-color: var(--brand); inline-size: 3.25rem; }
  .showcase-dots { display: flex; justify-content: center; align-items: center; gap: 0.5rem; margin-block-start: var(--sp-3); }
  .showcase-label {
    text-align: center; font-size: 0.78rem; color: var(--fg-faint);
    margin-block-start: 0.5rem;
  }
  @media (forced-colors: active) {
    .dot[aria-selected="true"] { background-color: Highlight; }
  }

  /* --- Penny Sorting stage ----------------------------------------------
     Four worker lanes sorting a fixed pile of coins. The imbalanced case is
     the point: total work is unchanged but the slowest lane sets the pace. */
  .penny-stage {
    display: grid; gap: 0.55rem; align-content: center;
    aspect-ratio: 16 / 10; padding: var(--sp-3);
    border-radius: var(--radius-s); outline: 1px solid var(--line);
    background: var(--bg-subtle);
  }
  .penny-lane { display: flex; align-items: center; gap: 0.5rem; min-block-size: 1.6rem; }
  .penny-lane .who {
    inline-size: 2.6rem; flex: none;
    font-family: var(--font-mono); font-size: 0.68rem; color: var(--fg-faint);
  }
  .penny-coins { display: flex; flex-wrap: nowrap; gap: 0.2rem; min-inline-size: 0; }
  .penny-coin {
    inline-size: 0.8rem; block-size: 0.8rem; border-radius: 50%; flex: none;
    background: var(--line-strong);
    transition: background 320ms var(--ease);
  }
  .penny-coin[data-done] { background: var(--flag-3); }
  .penny-lane[data-idle] .who { opacity: 0.4; }
  .penny-note {
    grid-column: 1 / -1; margin-block-start: 0.2rem;
    font-size: 0.76rem; color: var(--fg-muted); text-align: center; min-block-size: 1.2em;
  }

  .fm-stage { position: relative; aspect-ratio: 16 / 10; border-radius: var(--radius-s); overflow: clip; outline: 1px solid var(--line); }
  .fm-grid {
    display: grid; grid-template-columns: repeat(12, 1fr); grid-template-rows: repeat(8, 1fr);
    gap: 2px; block-size: 100%; background: var(--line);
  }
  .fm-cell { background: var(--bg-subtle); transition: background 320ms var(--ease); }
  .fm-cell[data-on="1"] { background: var(--flag-1); }
  .fm-cell[data-on="2"] { background: var(--flag-2); }
  .fm-cell[data-on="3"] { background: var(--flag-3); }
  .fm-cell[data-on="4"] { background: var(--flag-4); }
  /* partition boundaries between processors */
  .fm-split {
    position: absolute; inset-inline: 0; block-size: 0;
    border-block-start: 2px solid oklch(1 0 0 / 0.55);
    pointer-events: none;
  }
  .fm-fallback { block-size: 100%; }

  .fm-legend {
    display: flex; flex-wrap: wrap; gap: var(--sp-3);
    align-items: center; justify-content: space-between;
    margin-block-start: var(--sp-3); padding-block-start: var(--sp-3);
    border-block-start: 1px solid var(--line);
  }
  .fm-modes { display: flex; gap: 0.3rem; flex-wrap: wrap; }
  .fm-mode {
    padding: 0.3rem 0.7rem; min-block-size: 2rem;
    border-radius: var(--radius-pill); border: 1px solid var(--line-strong);
    background: var(--bg-raised); color: var(--fg-muted);
    font-size: 0.78rem; font-weight: 600; cursor: pointer;
    font-variant-numeric: tabular-nums;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
  }
  .fm-mode:hover { background: var(--bg-subtle); color: var(--fg); }
  .fm-mode[aria-pressed="true"] { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
  .fm-readout { font-family: var(--font-mono); font-size: 0.78rem; color: var(--fg-faint); font-variant-numeric: tabular-nums; }
  .fm-readout b { color: var(--brand); font-size: 0.95rem; }

  /* --- metrics ------------------------------------------------------------
     Borderless figures on a raised card that overlaps the hero's lower edge,
     rather than a ruled grid that reads as a table.                        */
  .metrics-wrap { position: relative; z-index: 2; margin-block-start: clamp(-3.5rem, -2rem - 2vw, -2rem); }
  .metrics {
    display: grid; gap: var(--sp-5) var(--sp-3);
    grid-template-columns: repeat(auto-fit, minmax(min(8.5rem, 100%), 1fr));
    padding: var(--sp-5) var(--sp-4);
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-l);
  }
  .metric { position: relative; text-align: center; padding-inline: var(--sp-3); }
  /* hairline separators only between items on the same row */
  .metric + .metric::before {
    content: ""; position: absolute; inset-block: 12%; inset-inline-start: 0;
    inline-size: 1px; background: var(--line);
  }
  @media (width < 34rem) { .metric + .metric::before { display: none; } }
  /* Rose and the warm accent are adjacent on the hue wheel, so the gradient's
     midpoint stays warm instead of detouring through grey. */
  .metric-value {
    --m-from: light-dark(oklch(0.55 0.20 12), oklch(0.84 0.15 8));
    --m-to:   light-dark(oklch(0.63 0.17 48), oklch(0.86 0.14 52));

    display: block; font-family: var(--font-display); font-weight: 600; line-height: 0.95;
    font-size: clamp(2.1rem, 1.5rem + 2vw, 3rem);
    letter-spacing: -0.03em;
    color: var(--m-from);
    background: linear-gradient(140deg in oklch, var(--m-from), var(--m-to));
    -webkit-background-clip: text; background-clip: text;
  }
  @supports (background-clip: text) or (-webkit-background-clip: text) {
    .metric-value { color: transparent; }
  }
  .metric-label {
    display: block; margin-block-start: 0.55rem;
    font-size: 0.74rem; font-weight: 600; letter-spacing: 0.09em;
    text-transform: uppercase; color: var(--fg-faint);
  }
  @media (forced-colors: active) { .metric-value { color: CanvasText; background: none; } }

  /* --- usage figures (live counters) ------------------------------------ */
  .usage {
    display: grid; gap: var(--sp-4);
    grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
    align-items: center;
    padding: var(--sp-4) var(--sp-5);
    background: color-mix(in oklab, var(--bg-raised) 88%, transparent);
    border: 1px solid color-mix(in oklab, var(--brand) 18%, var(--line));
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-s);
    backdrop-filter: blur(6px);
  }
  @media (width < 56rem) { .usage { grid-template-columns: 1fr; } }
  .usage-head .tiny { margin-block-start: 0.4rem; max-inline-size: 30ch; }
  .usage-figures {
    display: grid; gap: var(--sp-3);
    grid-template-columns: repeat(auto-fit, minmax(min(7.5rem, 100%), 1fr));
  }
  /* smaller than the headline metrics — these are supporting numbers */
  .usage .metric-value { font-size: clamp(1.6rem, 1.2rem + 1.2vw, 2.1rem); }
  .usage .metric + .metric::before { inset-block: 8%; }
  .usage-foot { grid-column: 1 / -1; margin: 0; font-size: 0.85rem; }
  .usage-foot a { font-weight: 600; text-decoration: none; }
  .usage-foot a:hover { text-decoration: underline; }

  /* --- cards --- */
  .card {
    container-type: inline-size;
    position: relative; display: flex; flex-direction: column; gap: var(--sp-2);
    padding: var(--sp-4);
    background: var(--bg-raised); border: 1px solid var(--line);
    border-radius: var(--radius-m);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
  }
  .card:has(a:hover), .card:has(a:focus-visible) {
    border-color: color-mix(in oklab, var(--brand) 45%, var(--line));
    box-shadow: var(--shadow-m); transform: translateY(-2px);
  }
  /* the card is a column flex container, so bare pills would stretch full width */
  .card > .pill, .card > .card-icon { align-self: start; }
  .card h3, .card h4 { font-size: var(--step-1); margin: 0; }
  .card p { color: var(--fg-muted); font-size: 0.95rem; margin: 0; }
  .card .stretch::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
  .card a { text-decoration: none; }
  .card a:hover { text-decoration: underline; }
  .card-icon {
    inline-size: 2.6rem; block-size: 2.6rem; border-radius: var(--radius-s);
    display: grid; place-items: center; flex: none;
    background: var(--brand-tint); color: var(--brand-deep);
  }
  .card-icon svg { inline-size: 1.3rem; block-size: 1.3rem; }
  /* These links sit inline among other text, so colour alone cannot carry
     the distinction — the brand pink is only 1.85:1 against the muted grey
     beside it. An underline satisfies 1.4.1 without a colour change. */
  .card-foot a { text-decoration: underline; text-underline-offset: 0.18em;
                 text-decoration-thickness: from-font; }
  .card-foot { margin-block-start: auto; padding-block-start: var(--sp-2); font-size: 0.86rem; line-height: 1.7; color: var(--fg-faint); }

  .card--feature { background: linear-gradient(160deg, var(--brand-tint), var(--bg-raised) 62%); }

  /* compact 3-up for the adoption paths — orientation, not a headline act */
  .path-strip { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); }
  .path {
    display: grid; gap: 0.35rem; padding: var(--sp-3) var(--sp-4);
    background: var(--bg-raised); border: 1px solid var(--line);
    border-radius: var(--radius-m);
    border-inline-start: 3px solid var(--brand);
  }
  .path .path-effort { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent-ink); }
  .path h3 { font-size: 1.1rem; }
  .path p { font-size: 0.9rem; color: var(--fg-muted); margin: 0; }
  .path a { font-size: 0.88rem; font-weight: 600; text-decoration: none;
            display: inline-flex; align-items: center; min-block-size: 1.5rem; }
  .path a:hover { text-decoration: underline; }

  /* --- featured e-book band --- */
  .feature-book { display: grid; gap: var(--sp-6); grid-template-columns: 20rem minmax(0, 1fr); align-items: start; }
  @media (width < 58rem) { .feature-book { grid-template-columns: 1fr; gap: var(--sp-4); } }
  /* The artwork carries the title, subtitle, author list and NSF seal itself,
     so the frame around it stays out of the way: a radius, a shadow, and a
     spine down the binding edge so it reads as a book rather than a picture.
     Capped at the width of its own grid column, so the single-column layout
     below 58rem does not blow it up to full width. */
  .book-cover { margin: 0; display: grid; gap: var(--sp-3); justify-items: center; }
  .book-cover-link {
    position: relative; display: block; inline-size: 100%; max-inline-size: 20rem;
    border-radius: var(--radius-m); overflow: clip; box-shadow: var(--shadow-l);
    transition: translate var(--dur) var(--ease);
  }
  .book-cover-link:hover { translate: 0 -4px; }
  .book-cover img { display: block; inline-size: 100%; block-size: auto; }
  /* The spine, plus a hairline ring — the cover is dark, and without it the
     edge disappears into the band in dark mode. */
  .book-cover-link::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    border-radius: inherit;
    background: linear-gradient(90deg, oklch(0 0 0 / 0.40) 0, oklch(0 0 0 / 0.08) 0.5rem, transparent 0.5rem);
    box-shadow: inset 0 0 0 1px oklch(1 0 0 / 0.16);
  }
  .book-cover figcaption {
    font-size: 0.76rem; font-family: var(--font-mono); color: var(--fg-faint);
  }
  @media (prefers-reduced-motion: reduce) {
    .book-cover-link { transition: none; }
    .book-cover-link:hover { translate: none; }
  }

  /* --- goals ---------------------------------------------------------------
     Five cards were taller than the prose beside them. A numbered list keeps
     the same information at roughly the height of the column it sits next to. */
  .goal-list {
    list-style: none; margin: 0; padding: 0;
    display: grid; gap: 0; align-content: start;
    border: 1px solid var(--line); border-radius: var(--radius-m);
    background: var(--bg-raised);
    overflow: clip;
  }
  .goal-list > li {
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-3);
    align-items: start;
    padding: 0.8rem var(--sp-4);
    font-size: 0.94rem; color: var(--fg-muted); line-height: 1.5;
  }
  .goal-list > li + li { border-block-start: 1px solid var(--line); }
  .goal-list b { color: var(--fg); font-weight: 600; }
  .goal-n {
    inline-size: 1.55rem; block-size: 1.55rem; flex: none;
    display: grid; place-items: center; border-radius: 50%;
    background: var(--brand-tint); color: var(--brand-deep);
    font-size: 0.78rem; font-weight: 700; font-variant-numeric: tabular-nums;
  }

  /* --- citation ------------------------------------------------------------ */
  .cite-card {
    display: grid; gap: var(--sp-4);
    grid-template-columns: minmax(0, 16rem) minmax(0, 1fr);
    align-items: start;
    padding: var(--sp-4) var(--sp-5);
    background: var(--bg-raised); border: 1px solid var(--line);
    border-radius: var(--radius-l);
  }
  @media (width < 56rem) { .cite-card { grid-template-columns: minmax(0, 1fr); } }
  .cite-label {
    margin: 0 0 var(--sp-2); font-size: 0.72rem; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--fg-faint);
  }
  /* BibTeX must not be re-wrapped — the entry is copied verbatim, so it
     scrolls sideways in its own box rather than reflowing. */
  .cite-code {
    margin: 0; padding: var(--sp-3); overflow-x: auto;
    background: var(--bg-sunken); border: 1px solid var(--line);
    border-radius: var(--radius-s);
    font-family: var(--font-mono); font-size: 0.76rem; line-height: 1.55;
    color: var(--fg); tab-size: 2;
  }
  .cite-code code { font: inherit; color: inherit; background: none; padding: 0; }
  .cite-text {
    margin: 0; padding: var(--sp-3);
    background: var(--bg-sunken); border: 1px solid var(--line);
    border-radius: var(--radius-s);
    font-size: 0.88rem; line-height: 1.6; color: var(--fg);
    /* hanging indent, the way a reference list sets */
    padding-inline-start: calc(var(--sp-3) + 1.4rem); text-indent: -1.4rem;
    overflow-wrap: anywhere;
  }

  /* --- DOI badge -----------------------------------------------------------
     Two segments in the Zenodo/shields idiom: a neutral label, then the
     identifier. Tinted rather than filled with brand — the badge says what the
     volume is, it is not the call to action, and a solid brand pill here would
     carry the same weight as the Download button beside it. Pill radius so it
     belongs in a row of buttons. The wrapper draws no clip, so the "Copied"
     bubble can sit outside the badge without being cut off. */
  .doi { display: inline-flex; align-items: stretch; vertical-align: middle; }
  /* the sentence that introduces the badge on the home page */
  .doi-line { display: flex; flex-wrap: wrap; align-items: center; gap: 0.55rem;
              font-size: 0.86rem; color: var(--fg-muted); }
  .doi-badge, .doi-open {
    display: inline-flex; align-items: center; gap: 0.4rem;
    border: 0; 
    /* padding: 0.42rem 0.6rem;  */
    /* Block padding is commented out above to keep the badge tight. Without it
       the control is 16px tall, under the 24px WCAG 2.5.8 floor, so the height
       is set as a minimum instead — same look, legal target. */
    min-block-size: 1.5rem; padding-inline: 0.6rem;
    cursor: pointer;
    font-family: var(--font-mono); font-size: 0.76rem; line-height: 1.15;
    background: var(--brand-tint); color: var(--brand-deep);
    box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--brand) 24%, transparent);
    transition: background var(--dur) var(--ease);
  }
  .doi-badge {
    position: relative; padding-inline-start: 0;
    border-start-start-radius: var(--radius-pill); border-end-start-radius: var(--radius-pill);
  }
  .doi-label {
    align-self: stretch; display: inline-flex; align-items: center;
    padding: 0 0.6rem 0 0.75rem; margin-inline-end: 0.45rem;
    background: light-dark(oklch(0.33 0.012 260), oklch(0.26 0.012 260));
    color: oklch(1 0 0); font-weight: 700; letter-spacing: 0.1em;
    border-start-start-radius: var(--radius-pill); border-end-start-radius: var(--radius-pill);
  }
  /* Nothing here may shrink: a squeezed identifier is unreadable and a
     squeezed arrow drops under the 24px target floor. */
  .doi-label, .doi-open, .doi-ico { flex: none; }
  .doi-value { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 600;
               white-space: nowrap; }
  .doi-badge:hover, .doi-open:hover { background: color-mix(in oklab, var(--brand) 16%, var(--bg-raised)); }
  .doi-open {
    text-decoration: none;
    border-start-end-radius: var(--radius-pill); border-end-end-radius: var(--radius-pill);
    padding-inline: 0.5rem 0.7rem; min-inline-size: 1.9rem; justify-content: center;
  }
  .doi-open:any-link { color: var(--brand-deep); }
  .doi-ico { inline-size: 0.86rem; block-size: 0.86rem; flex: none; opacity: 0.75; }
  .doi-open svg { inline-size: 0.86rem; block-size: 0.86rem; }
  /* the tick replaces the copy glyph, so the identifier itself never moves */
  .doi-ico--done, .doi-badge[data-copied] .doi-ico--copy { display: none; }
  .doi-badge[data-copied] .doi-ico--done { display: block; }
  .doi-badge[data-copied]::after {
    content: "Copied"; position: absolute; z-index: 5;
    inset-block-end: calc(100% + 0.45rem); inset-inline-start: 50%; translate: -50% 0;
    padding: 0.24rem 0.5rem; border-radius: var(--radius-s);
    background: var(--fg); color: var(--bg);
    font-family: var(--font-body); font-size: 0.7rem; font-weight: 600;
    white-space: nowrap; pointer-events: none;
    animation: doi-pop 160ms var(--ease) both;
  }
  @keyframes doi-pop { from { opacity: 0; translate: -50% 4px; } }
  /* Below ~416px the full pill no longer fits the card it sits in, so it
     steps down a size rather than wrapping or clipping the identifier. */
  @media (width < 26rem) {
    .doi-badge, .doi-open { font-size: 0.7rem; padding-block: 0.4rem; }
    .doi-label { padding-inline: 0.45rem; letter-spacing: 0.06em; }
  }
  @media (prefers-reduced-motion: reduce) {
    .doi-badge[data-copied]::after { animation: none; }
  }

  /* --- copy button ---------------------------------------------------------
     Swaps to a tick for a moment after a successful copy.                   */
  .btn--icon { padding-inline: 0.7rem; }
  .btn[data-copy][data-copied] {
    background: color-mix(in oklab, var(--flag-4) 22%, var(--bg-raised));
    border-color: color-mix(in oklab, var(--flag-4) 55%, var(--line));
    color: var(--fg);
  }
  .btn[data-copy][data-copied] svg { color: var(--flag-4); }

  /* --- panel --- */
  .panel {
    background: var(--bg-raised); border: 1px solid var(--line);
    border-radius: var(--radius-l); padding: var(--sp-5);
  }
  .panel--sunken { background: var(--bg-sunken); }

  /* --- chips / pills --- */
  .pill {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.22rem 0.62rem; border-radius: var(--radius-pill);
    font-size: 0.74rem; font-weight: 600; letter-spacing: 0.02em;
    background: var(--bg-subtle); color: var(--fg-muted);
    border: 1px solid var(--line);
  }
  .pill--brand { background: var(--brand-tint); color: var(--brand-deep); border-color: color-mix(in oklab, var(--brand) 25%, transparent); }
  .pill--accent { background: color-mix(in oklab, var(--accent) 16%, transparent); color: light-dark(oklch(0.38 0.11 55), oklch(0.86 0.1 60)); border-color: color-mix(in oklab, var(--accent) 32%, transparent); }

  /* --- filter chips (checkbox-driven, styled with :has()) --- */
  .facet { border: 0; padding: 0; margin: 0; min-inline-size: 0; }
  .facet legend {
    font-size: 0.74rem; font-weight: 700; letter-spacing: 0.11em;
    text-transform: uppercase; color: var(--fg-faint); padding: 0;
    margin-block-end: 0.55rem;
  }
  .chip {
    display: inline-flex; align-items: center; gap: 0.4rem;
    padding: 0.4rem 0.8rem; min-block-size: 2.25rem;
    border-radius: var(--radius-pill); border: 1px solid var(--line-strong);
    background: var(--bg-raised); color: var(--fg-muted);
    font-size: 0.85rem; font-weight: 500; cursor: pointer; user-select: none;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
  }
  .chip:hover { background: var(--bg-subtle); color: var(--fg); }
  .chip input { position: absolute; opacity: 0; pointer-events: none; }
  .chip:has(input:checked) {
    background: var(--brand); border-color: var(--brand); color: var(--on-brand); font-weight: 600;
  }
  .chip:has(input:focus-visible) { outline: 3px solid var(--accent); outline-offset: 3px; }
  .chip .count { font-size: 0.76rem; opacity: 0.75; font-variant-numeric: tabular-nums; }
  @media (forced-colors: active) {
    .chip:has(input:checked) { background: Highlight; color: HighlightText; }
  }

  /* --- browse-mode switch (institutions | activities) --- */
  .modeswitch {
    display: inline-flex; gap: 0.2rem; padding: 0.25rem;
    background: var(--bg-subtle); border: 1px solid var(--line);
    border-radius: var(--radius-pill);
  }
  .modeswitch button {
    border: 0; background: transparent; cursor: pointer;
    padding: 0.45rem 1rem; min-block-size: 2.4rem;
    border-radius: var(--radius-pill);
    font-size: 0.88rem; font-weight: 600; color: var(--fg-muted);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
  }
  .modeswitch button:hover { color: var(--fg); }
  .modeswitch button[aria-selected="true"] { background: var(--bg-raised); color: var(--brand-deep); box-shadow: var(--shadow-s); }

  /* --- search field --- */
  .searchbar { position: relative; flex: 1 1 18rem; min-inline-size: 0; }
  .searchbar svg {
    position: absolute; inset-inline-start: 0.9rem; inset-block-start: 50%;
    translate: 0 -50%; inline-size: 1.1rem; block-size: 1.1rem;
    color: var(--fg-faint); pointer-events: none;
  }
  .searchbar input {
    inline-size: 100%; padding: 0.75rem 1rem 0.75rem 2.7rem; min-block-size: 2.9rem;
    background: var(--bg-raised); border: 1px solid var(--line-strong);
    border-radius: var(--radius-pill); color: var(--fg);
  }
  .searchbar input::placeholder { color: var(--fg-faint); }
  .searchbar input:focus-visible { border-color: var(--brand); }

  /* --- results --- */
  .result-meta {
    display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: baseline;
    font-size: 0.9rem; color: var(--fg-faint);
    padding-block: var(--sp-3); border-block-end: 1px solid var(--line);
    margin-block-end: var(--sp-4);
  }
  .result-meta strong { color: var(--fg); font-variant-numeric: tabular-nums; }

  .empty {
    text-align: center; padding: var(--sp-6) var(--sp-3);
    border: 1px dashed var(--line-strong); border-radius: var(--radius-l);
    color: var(--fg-faint);
  }

  /* --- definition rows used inside activity cards & dialogs --- */
  .deflist { display: grid; gap: 0.55rem; margin: 0; container-type: inline-size; }
  .deflist > div { display: grid; grid-template-columns: 8.5rem minmax(0, 1fr); gap: var(--sp-3); }
  @container (inline-size < 24rem) { .deflist > div { grid-template-columns: 1fr; gap: 0.15rem; } }
  .deflist dt { font-size: 0.74rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--fg-faint); padding-block-start: 0.12rem; }
  .deflist dd { margin: 0; font-size: 0.9rem; color: var(--fg-muted); }

  /* --- timelines ---------------------------------------------------------
     Two flavours. `.timeline--rail` is the project's own arc: a horizontal
     rail with a dot per phase, given room because it is the page's spine.
     `.timeline--compact` is the CDER back-story: one line per era, expanding
     on click so it costs almost no height until someone wants it.           */
  .timeline { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--sp-4); }
  .timeline > li {
    position: relative; padding-inline-start: 2.6rem;
    border-inline-start: 2px solid var(--line);
    padding-block-end: var(--sp-2);
  }
  .timeline > li::before {
    content: ""; position: absolute; inset-inline-start: -0.47rem; inset-block-start: 0.42rem;
    inline-size: 0.8rem; block-size: 0.8rem; border-radius: 50%;
    background: var(--brand); border: 3px solid var(--bg);
  }
  .timeline .year {
    font-family: var(--font-mono); font-size: 0.8rem; font-weight: 600;
    color: var(--accent-ink); letter-spacing: 0.04em;
  }
  .timeline h4 { margin-block: 0.15rem 0.3rem; font-size: 1.06rem; }
  .timeline p { font-size: 0.93rem; color: var(--fg-muted); margin: 0; }

  /* --- rail: the project's own timeline -----------------------------------
     A continuous horizontal rail with a dot per phase. Reads as a timeline at
     a glance, and costs about half the height the card layout did. Collapses
     to the classic vertical line-and-dot on narrow screens.                 */
  .timeline--rail {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--sp-5);
    padding-block-start: 0.35rem;
  }
  .timeline--rail > li {
    position: relative;
    padding: 2.4rem 0 0 0;
    border: 0;
  }
  /* the rail: a segment from this dot to the next */
  .timeline--rail > li::before {
    content: ""; position: absolute;
    inset-block-start: 0.62rem; inset-inline-start: 0.55rem;
    inline-size: calc(100% + var(--sp-5) - 0.55rem); block-size: 2px;
    background: linear-gradient(90deg,
      color-mix(in oklab, var(--brand) 55%, var(--line)),
      color-mix(in oklab, var(--brand) 22%, var(--line)));
    border-radius: 2px;
  }
  /* the rail stops at the last dot rather than trailing off */
  .timeline--rail > li:last-child::before { display: none; }
  /* the dot */
  .timeline--rail > li::after {
    content: ""; position: absolute;
    inset-block-start: 0.25rem; inset-inline-start: 0;
    inline-size: 1.1rem; block-size: 1.1rem; border-radius: 50%;
    background: var(--brand);
    box-shadow: 0 0 0 4px var(--bg), 0 0 0 5px color-mix(in oklab, var(--brand) 30%, transparent);
    transition: scale var(--dur) var(--ease);
  }
  .timeline--rail > li:hover::after { scale: 1.25; }
  .timeline--rail .year {
    display: block; font-size: 0.98rem; font-weight: 700; letter-spacing: 0.02em;
    color: var(--brand); font-family: var(--font-mono);
  }
  .timeline--rail h4 { font-size: 1.04rem; margin-block: 0.3rem 0.35rem; text-wrap: balance; }
  .timeline--rail p { font-size: 0.9rem; }

  @media (width < 62rem) {
    /* back to the vertical line-and-dot, which is what stacking wants anyway */
    .timeline--rail { grid-template-columns: 1fr; gap: var(--sp-4); padding-block-start: 0; }
    .timeline--rail > li {
      padding: 0 0 var(--sp-2) 2.6rem;
      border-inline-start: 2px solid var(--line);
    }
    .timeline--rail > li::before { display: none; }
    .timeline--rail > li::after {
      inset-block-start: 0.3rem; inset-inline-start: -0.6rem;
      inline-size: 1rem; block-size: 1rem;
      box-shadow: 0 0 0 3px var(--bg);
    }
  }

  /* --- compact: the CDER back-story, expanding on demand --- */
  .timeline--compact { gap: 0; }
  .timeline--compact > li {
    padding: 0; border-inline-start: 0;
    border-block-end: 1px solid var(--line);
  }
  .timeline--compact > li::before { display: none; }
  .timeline--compact > li:first-child { border-block-start: 1px solid var(--line); }
  .timeline--compact .era {
    display: grid; grid-template-columns: 9rem minmax(0, 1fr) auto;
    gap: var(--sp-3); align-items: baseline;
    inline-size: 100%; text-align: start;
    padding: 0.85rem 0.6rem; border: 0; background: none; cursor: pointer;
    transition: background var(--dur) var(--ease);
  }
  .timeline--compact .era:hover { background: color-mix(in oklab, var(--brand) 6%, transparent); }
  .timeline--compact > li[data-open] .era { background: var(--brand-tint); }
  .timeline--compact .era .year { color: var(--accent-ink); }
  /* a <button> may only contain phrasing content, so this is a span styled
     to match the surrounding headings */
  .timeline--compact .era-title {
    font-family: var(--font-display); font-weight: 600; font-size: 1rem;
    line-height: 1.2; color: var(--fg);
  }
  /* a chevron that turns to point down when the row is open */
  .timeline--compact .era .chev {
    inline-size: 1.05rem; block-size: 1.05rem; flex: none;
    color: var(--brand); transition: rotate 320ms var(--ease);
  }
  .timeline--compact > li[data-open] .era .chev { rotate: 90deg; }
  .timeline--compact .detail {
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows 320ms var(--ease);
  }
  .timeline--compact .detail > div { overflow: hidden; }
  @media (prefers-reduced-motion: reduce) { .timeline--compact .detail { transition: none; } }
  .timeline--compact .detail p {
    margin: 0; padding: 0 0.6rem 1rem 9.6rem;
    font-size: 0.92rem; color: var(--fg-muted);
  }
  /* opens on click only — hovering past a row no longer expands it */
  .timeline--compact > li[data-open] .detail { grid-template-rows: 1fr; }
  @media (width < 46rem) {
    .timeline--compact .era { grid-template-columns: 1fr auto; }
    .timeline--compact .era .year { grid-column: 1 / -1; }
    .timeline--compact .detail p { padding-inline-start: 0.6rem; }
  }
  @media (prefers-reduced-motion: reduce) {
    .timeline--compact .detail { transition: none; }
  }

  /* --- people --- */
  .person { display: flex; gap: var(--sp-3); align-items: center; }
  /* .card sets flex-direction:column, which would stack the avatar on top */
  .card.person { flex-direction: row; }
  .avatar {
    inline-size: 2.9rem; block-size: 2.9rem; flex: none; border-radius: 50%;
    display: grid; place-items: center;
    background: var(--brand-tint); color: var(--brand-deep);
    font-family: var(--font-display); font-weight: 600; font-size: 1rem;
  }
  .person .who { display: block; font-weight: 600; line-height: 1.3; }
  .person .where { display: block; font-size: 0.85rem; line-height: 1.35; color: var(--fg-faint); }

  /* --- table --- */
  .table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius-m); background: var(--bg-raised); scrollbar-gutter: stable; }
  table.data { inline-size: 100%; border-collapse: collapse; font-size: 0.9rem; }
  table.data caption { text-align: start; padding: var(--sp-3) var(--sp-4); color: var(--fg-faint); font-size: 0.85rem; border-block-end: 1px solid var(--line); }
  table.data :is(th, td) { padding: 0.55rem 1rem; text-align: start; vertical-align: top; border-block-start: 1px solid var(--line); }
  table.data thead th { background: var(--bg-sunken); font-size: 0.76rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-faint); border-block-start: 0; position: sticky; inset-block-start: 0; }
  table.data tbody tr:hover { background: var(--brand-tint); }
  /* the detail row is content, not a target — it should not light up */
  table.data tbody tr.ch-detail-row:hover { background: var(--bg-sunken); }

  /* Chapter/institution column must never wrap; the prose columns give way. */
  table.data--glance th[scope="row"] { white-space: nowrap; font-weight: 600; }
  table.data--glance :is(th, td) { padding-inline: 0.85rem; }
  /* 1% + nowrap makes a column shrink to its content under auto table layout,
     so column one is exactly as wide as it needs to be and never wraps. */
  table.data--glance .col-ch    { inline-size: 1%; }
  table.data--glance .col-lang  { inline-size: 1%; }
  table.data--glance .col-ctx   { inline-size: 26%; }
  table.data--glance .col-style { inline-size: 40%; }
  table.data--glance .col-start { inline-size: 34%; }
  table.data--glance thead th:nth-child(3) { white-space: nowrap; }

  /* --- chapter table detail ------------------------------------------------
     The contents of a chapter, revealed in a row beneath it. Kept dense on
     purpose: this is a reference list, not prose.                          */
  /* inline-flex so it shrink-wraps and sits at the start of the cell rather
     than filling it and centring on the button's default text-align */
  /* Deliberately not a .disclose-btn: that component tints itself on hover and
     when open, which would highlight column one on top of the row highlight.
     The row is the only hover cue here, so this button stays transparent. */
  .ch-toggle {
    display: inline-flex; inline-size: auto; justify-content: flex-start;
    align-items: center; gap: 0.45rem;
    padding: 0; min-block-size: 1.5rem; border: 0; background: none; cursor: pointer;
    font-weight: 600; font-size: inherit; line-height: 1.35; text-align: start;
    color: var(--fg);
  }
  .data--chapters :is(th, td) { padding-block: 0.42rem; }
  .data--chapters .btn--sm { min-block-size: 2rem; padding-block: 0.28rem; }
  /* no hover styling at all — the row highlight does the work */
  /* The shared .disclose-btn pushes its chevron right with margin-inline-start:
     auto, and is declared later. Here the chevron leads, so that auto margin
     has to be cancelled with enough specificity to win. */
  .ch-row .ch-toggle .chev {
    margin-inline-start: 0; inline-size: 0.9rem; block-size: 0.9rem;
  }
  [data-open] .ch-toggle .chev { rotate: 90deg; }
  .ch-row[data-open] { background: var(--brand-tint); }
  .ch-plain { color: var(--fg-muted); }

  /* Part and pages, shown only once those columns drop out. Authors are not
     repeated here — they read as clutter beside the titles, and the full list
     with institution is in the expanded detail. */
  .ch-meta {
    display: none; color: var(--fg-faint);
    margin-block-start: 0.1rem; padding-inline-start: 1.35rem;
    font-size: 0.78rem; line-height: 1.35;
  }
  tr:has(.ch-plain) .ch-meta { padding-inline-start: 0; }

  /* --- responsive ---------------------------------------------------------
     Below 56rem the Part and Pages columns are dropped and folded into the
     line under the title, so the table never needs sideways scrolling.     */
  @media (width < 56rem) {
    .data--chapters thead th:nth-child(2),
    .data--chapters thead th:nth-child(3),
    .data--chapters tbody td:nth-child(2),
    .data--chapters tbody td:nth-child(3) { display: none; }
    .data--chapters .ch-meta { display: block; }
    .data--chapters :is(th, td) { padding-inline: 0.7rem; }
    .data--chapters .ch-title { font-size: 0.94rem; }
  }
  /* narrower still: the count moves into the same line and only the button stays */
  @media (width < 34rem) {
    .data--chapters thead th:nth-child(4),
    .data--chapters tbody td:nth-child(4) { display: none; }
    .data--chapters .btn--sm { padding-inline: 0.6rem; }
  }

  .data--chapters .btn--sm { white-space: nowrap; }

  .ch-detail-row > td { padding: 0 1rem 0.9rem; background: var(--bg-sunken); }
  .ch-detail-row[hidden] { display: none; }
  .ch-authors {
    margin: 0 0 0.6rem; font-size: 0.85rem; color: var(--fg-muted);
    display: flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; align-items: baseline;
  }
  .ch-authors .ch-inst { color: var(--fg-faint); font-size: 0.8rem; }
  .ch-authors .ch-inst::before { content: "— "; }

  /* one section per line, single column */
  /* The chapter abstract, lifted from the volume so readers can judge a
     chapter without opening it. */
  .ch-excerpt {
    margin: 0 0 var(--sp-3); max-inline-size: 78ch;
    font-size: 0.9rem; line-height: 1.6; color: var(--fg-muted);
    padding-inline-start: var(--sp-3);
    border-inline-start: 2px solid color-mix(in oklab, var(--brand) 35%, transparent);
  }
  .ch-links {
    display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
    margin-block-start: var(--sp-3); font-size: 0.86rem; font-weight: 600;
  }
  /* The chapter's subtitle, under its title in the first column. */
  .ch-sub { display: block; margin-block-start: 0.15rem; font-size: 0.78rem; font-weight: 400; }
  /* A nested disclosure indents one step per level so the depth is legible. */
  .ch-sections .sec-subs .sec-subs { padding-inline-start: 1.9rem; }

  .ch-sections { list-style: none; margin: 0; padding: 0; }
  .ch-sections .sec { border-block-start: 1px solid color-mix(in oklab, var(--line) 60%, transparent); }
  .ch-sections .sec:first-child { border-block-start: 0; }

  /* a section row, whether it is a button or a plain line, lines up the same */
  .sec-btn, .sec--leaf {
    inline-size: 100%; text-align: start;
    display: grid; grid-template-columns: 1rem 2.9rem minmax(0, 1fr); gap: 0.5rem;
    align-items: baseline;
    padding: 0.32rem 0; border: 0; background: none;
    font-size: 0.85rem; line-height: 1.45; font-weight: 400; color: var(--fg);
  }
  .sec--leaf { padding-inline-start: 0; }
  .sec--leaf .sec-n { grid-column: 2; }
  .sec--leaf .sec-t { grid-column: 3; }
  .sec-btn { cursor: pointer; transition: color var(--dur) var(--ease); }
  .sec-btn:hover { color: var(--brand-deep); }
  .ch-sections .sec-btn .chev {
    inline-size: 0.8rem; block-size: 0.8rem; margin-inline-start: 0;
    color: var(--brand); align-self: center;
    transition: rotate 260ms var(--ease);
  }
  .ch-sections .sec[data-open] > .sec-btn { color: var(--brand-deep); font-weight: 600; }
  .ch-sections .sec[data-open] > .sec-btn .chev { rotate: 90deg; }
  .ch-sections .sec-n {
    font-family: var(--font-mono); font-size: 0.76rem; color: var(--brand); text-align: end;
  }

  /* Subsections, one per line under their section. Every child here is either
     a leaf (which lays itself out) or a wrapper holding a button and a panel —
     so the list must NOT impose a grid of its own, or the wrapper's button is
     crushed into the first 1rem column. */
  .sec-subs { list-style: none; margin: 0; padding: 0 0 0.35rem 0; }
  /* Only the wrappers, not the leaves — a leaf carries both classes and this
     selector outranks the grid rule below, which flattened it to inline text. */
  .sec-subs > li.sec:not(.sec--leaf) { display: block; }
  .sec-subs .sec--leaf,
  .sec-subs > li:not(.sec) {
    display: grid; grid-template-columns: 1rem 3.6rem minmax(0, 1fr); gap: 0.5rem;
    align-items: baseline;
    padding-block: 0.22rem;
    font-size: 0.82rem; line-height: 1.4; color: var(--fg-muted);
  }
  .sec-subs .sec-n { font-size: 0.72rem; color: var(--fg-faint); }
  /* deeper numbers are longer, so the number column widens with the level */
  .sec-subs .sec-subs .sec--leaf { grid-template-columns: 1rem 5.3rem minmax(0, 1fr); }
  .sec-subs .sec-btn { font-size: 0.82rem; }

  @media (width < 46rem) {
    .sec-btn, .sec--leaf { grid-template-columns: 1rem 2.5rem minmax(0, 1fr); }
    .sec-subs .sec--leaf, .sec-subs > li:not(.sec) { grid-template-columns: 1rem 3rem minmax(0, 1fr); }
    .sec-subs .sec-subs .sec--leaf { grid-template-columns: 1rem 4.4rem minmax(0, 1fr); }
  }

  /* --- publications ------------------------------------------------------
     One line each: authors, title, venue. Three stacked blocks per entry with
     a bold title made a nine-item list far taller than it needed to be.     */
  .pubs { list-style: none; padding: 0; margin: 0; counter-reset: pub; }
  /* Not a grid: the three spans must stay on one flowing line, so the counter
     is positioned instead of taking a column of its own. */
  .pubs > li {
    position: relative;
    padding: 0.5rem 0 0.5rem 2.4rem;
    border-block-end: 1px solid var(--line);
    font-size: 0.92rem; line-height: 1.5;
    text-wrap: pretty;
  }
  .pubs > li::before {
    counter-increment: pub; content: counter(pub) ".";
    position: absolute; inset-inline-start: 0; inset-block-start: 0.62rem;
    inline-size: 1.8rem; text-align: end;
    font-family: var(--font-mono); font-size: 0.78rem; color: var(--fg-faint);
  }
  .pubs .pub-authors { color: var(--fg-muted); }
  .pubs .pub-title { color: var(--fg); }
  .pubs .pub-venue { color: var(--fg-faint); }

  /* --- repository list --- */
  .repo-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0; }
  .repo-list > li {
    display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); align-items: baseline;
    padding-block: 0.7rem; border-block-end: 1px solid var(--line);
  }
  .repo-list .repo-who { font-weight: 600; min-inline-size: 9rem; }
  .repo-list a { font-family: var(--font-mono); font-size: 0.84rem; overflow-wrap: anywhere; }

  /* --- disclosure ----------------------------------------------------------
     One mechanism for the prior-work rows and the FAQ. <details> cannot
     animate its own height (its content is not rendered while closed), so
     this is a button plus a 0fr -> 1fr grid, which can.                    */
  .faq-list {
    border: 1px solid var(--line); border-radius: var(--radius-m);
    background: var(--bg-raised); overflow: clip;
  }
  .faq { border: 0; background: none; }
  .faq + .faq, .faq-more .faq:first-child { border-block-start: 1px solid var(--line); }
  .faq-more { display: contents; }
  .faq-more[hidden] { display: none; }

  .disclose-btn {
    inline-size: 100%; text-align: start; cursor: pointer;
    display: flex; gap: var(--sp-3); align-items: center;
    padding: 0.7rem var(--sp-4); min-block-size: 2.6rem;
    border: 0; background: none;
    font-weight: 600; font-size: 0.96rem; color: var(--fg);
    transition: background var(--dur) var(--ease);
  }
  .disclose-btn:hover { background: color-mix(in oklab, var(--brand) 6%, transparent); }
  [data-open] > .disclose-btn { background: var(--brand-tint); }
  .disclose-btn .chev {
    inline-size: 1.05rem; block-size: 1.05rem; flex: none; margin-inline-start: auto;
    color: var(--brand); transition: rotate 320ms var(--ease);
  }
  [data-open] > .disclose-btn .chev { rotate: 90deg; }

  .disclose-panel {
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows 320ms var(--ease);
  }
  .disclose-panel > div { overflow: hidden; }
  [data-open] > .disclose-panel { grid-template-rows: 1fr; }
  .disclose-panel .inner { padding: 0 var(--sp-4) var(--sp-3); color: var(--fg-muted); font-size: 0.93rem; }
  .disclose-panel .inner > * + * { margin-block-start: var(--sp-2); }
  @media (prefers-reduced-motion: reduce) { .disclose-panel { transition: none; } }

  /* --- notice / callout --- */
  .callout {
    border-inline-start: 3px solid var(--accent);
    background: color-mix(in oklab, var(--accent) 9%, var(--bg-raised));
    padding: var(--sp-3) var(--sp-4); border-radius: 0 var(--radius-m) var(--radius-m) 0;
    font-size: 0.94rem;
  }
  .callout strong { color: var(--fg); }
  .callout--info { border-inline-start-color: var(--brand); background: color-mix(in oklab, var(--brand) 8%, var(--bg-raised)); }

  /* --- news -------------------------------------------------------------
     A fixed-height window whose contents scroll continuously, so every item
     comes past without the section growing without limit.                  */
  .news-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0; }
  .news-list > li { padding-block: var(--sp-3); border-block-end: 1px solid var(--line); display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: baseline; }
  .news-list time { font-family: var(--font-mono); font-size: 0.8rem; color: var(--fg-faint); flex: none; min-inline-size: 7rem; }
  .news-list .headline { font-weight: 600; }
  .news-list p { flex-basis: 100%; margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--fg-muted); }
  .news-list .news-more { flex-basis: 100%; font-size: 0.85rem; font-weight: 600; }

  .news-window { position: relative; overflow: hidden; }
  .news-reel {
    display: flex; flex-direction: column;
    transition: translate 480ms var(--ease);
  }
  .news-page { flex: 0 0 auto; }
  .news-page .news-list > li:last-child { border-block-end: 0; }
  /* Until JS pages it, the list is just a list — nothing is hidden. */
  .news-window:not([data-paged]) { overflow: visible; }

  .news-pager {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
    margin-block-start: var(--sp-3); padding-block-start: var(--sp-3);
    border-block-start: 1px solid var(--line);
  }
  .news-count {
    margin-inline-start: auto;
    font-family: var(--font-mono); font-size: 0.78rem; color: var(--fg-faint);
    font-variant-numeric: tabular-nums;
  }
  .news-pager[hidden] { display: none; }

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

  /* --- institution cards -------------------------------------------------
     Was a ruled roll that read as a table. Now a card each, with room for a
     logo (or a monogram until one is supplied).                            */
  .inst-grid {
    display: grid; gap: var(--sp-3);
    grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  }
  .inst-card {
    position: relative;
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-3);
    align-items: center;
    padding: var(--sp-3) var(--sp-4);
    background: var(--bg-raised); border: 1px solid var(--line);
    border-radius: var(--radius-m); text-decoration: none;
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
  }
  .inst-card:hover, .inst-card:focus-visible {
    border-color: color-mix(in oklab, var(--brand) 45%, var(--line));
    box-shadow: var(--shadow-m); transform: translateY(-2px);
  }
  .inst-logo {
    inline-size: 3.25rem; block-size: 3.25rem; flex: none;
    display: grid; place-items: center; overflow: hidden;
    border-radius: var(--radius-s);
    background: var(--brand-tint);
  }
  .inst-logo img { inline-size: 100%; block-size: 100%; object-fit: contain; padding: 0.3rem; }
  .inst-logo .mono {
    font-family: var(--font-display); font-weight: 600; font-size: 0.98rem;
    letter-spacing: -0.01em; color: var(--brand-deep);
  }
  .inst-body { display: grid; gap: 0.15rem; min-inline-size: 0; }
  .inst-card .inst-name { font-weight: 600; font-size: 0.98rem; line-height: 1.25; color: var(--fg); }
  .inst-card .inst-meta { font-size: 0.8rem; color: var(--fg-faint); }
  .inst-card .inst-role {
    justify-self: start; margin-block-start: 0.2rem;
    font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--brand);
  }
  .inst-card[data-role="Testing team"] .inst-role { color: var(--fg-faint); }

  /* --- dialog --- */
  dialog.sheet {
    max-inline-size: min(56rem, 92vw); inline-size: 100%;
    border: 1px solid var(--line); border-radius: var(--radius-l); padding: 0;
    background: var(--bg-raised); color: var(--fg); box-shadow: var(--shadow-l);
  }
  dialog.sheet::backdrop { background: oklch(0.15 0.02 260 / 0.55); backdrop-filter: blur(2px); }
  dialog.sheet .sheet-head {
    position: sticky; inset-block-start: 0; z-index: 2;
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-block-end: 1px solid var(--line); background: inherit;
  }
  dialog.sheet .sheet-head strong { font-family: var(--font-display); font-size: 1.1rem; }
  dialog.sheet .sheet-body { padding: var(--sp-4) var(--sp-5) var(--sp-5); }

  /* Subtle reveal, motion-safe only.
     This used `animation-timeline: view()`, which is bidirectional — scrolling
     back up replayed it in reverse and elements visibly faded out again. It is
     now a one-shot transition driven by an observer that unobserves after
     firing, so an element reveals once and stays put.
     Gated on .js so nothing is ever left invisible if the script fails. */
  @media (prefers-reduced-motion: no-preference) {
    .js .reveal {
      opacity: 0; translate: 0 1.25rem;
      transition: opacity 520ms var(--ease), translate 520ms var(--ease);
    }
    .js .reveal[data-shown] { opacity: 1; translate: 0 0; }
  }

  /* --- footer -------------------------------------------------------------
     Deliberately bold: a deep rose-to-ink gradient that closes the page.
     Its own light-on-dark scale, independent of the page theme.            */
  .site-footer {
    --ft-fg: oklch(0.97 0.012 24);
    --ft-muted: oklch(0.89 0.022 24 / 0.9);
    --ft-faint: oklch(0.87 0.022 24 / 0.68);
    --ft-line: oklch(1 0 0 / 0.16);

    /* Rebind the semantic tokens rather than fighting the utilities layer —
       `.muted`/`.faint` live in @layer utilities and would otherwise win on
       layer order no matter how specific a .site-footer rule is. */
    --fg: var(--ft-fg);
    --fg-muted: var(--ft-muted);
    --fg-faint: var(--ft-faint);
    --line: var(--ft-line);
    --line-strong: oklch(1 0 0 / 0.28);
    --brand: oklch(0.88 0.10 10);
    --brand-deep: oklch(0.96 0.05 10);

    position: relative; isolation: isolate;
    margin-block-start: var(--sp-6);
    padding-block: var(--sp-6) var(--sp-4);
    color: var(--ft-fg);
    background:
      radial-gradient(80% 120% at 88% 0%, oklch(0.46 0.17 12 / 0.85) 0%, transparent 62%),
      radial-gradient(70% 100% at 2% 100%, oklch(0.58 0.16 55 / 0.45) 0%, transparent 58%),
      linear-gradient(158deg, oklch(0.34 0.13 14) 0%, oklch(0.24 0.09 20) 52%, oklch(0.17 0.045 28) 100%);
  }
  .site-footer::before {
    content: ""; position: absolute; inset-inline: 0; inset-block-start: 0; block-size: 3px; z-index: -1;
    background: linear-gradient(90deg, var(--brand), var(--accent) 55%, var(--brand));
  }
  .site-footer .brandmark .name { color: var(--ft-fg); }
  .site-footer .brandmark .tag { color: var(--ft-faint); }
  .site-footer .muted, .site-footer .small.muted { color: var(--ft-muted); }
  .site-footer a:any-link { color: var(--ft-fg); }
  .site-footer a:any-link:hover { color: oklch(1 0 0); }

  .footer-grid { display: grid; gap: var(--sp-5); grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr)); }
  /* wider brand column so the contact address sits on one line */
  @media (width >= 68rem) { .footer-grid { grid-template-columns: 21rem repeat(4, 1fr); } }
  .footer-contact { font-size: 0.86rem; overflow-wrap: normal; }
  .footer-grid h2 { font-size: 0.76rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ft-faint); margin: 0 0 var(--sp-2); font-weight: 700; }
  .footer-grid ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.42rem; }
  /* a column of links is not a sentence, so WCAG 2.5.8 applies in full */
  .footer-grid li a { display: inline-flex; align-items: center; min-block-size: 1.5rem; }
  .footer-grid a { font-size: 0.92rem; color: var(--ft-muted); text-decoration: none; }
  .footer-grid a:hover { color: oklch(1 0 0); text-decoration: underline; }

  /* --- supported-by logo lockups --- */
  .sponsors {
    display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-6); align-items: center;
    margin-block-start: var(--sp-5); padding-block-start: var(--sp-5);
    border-block-start: 1px solid var(--ft-line);
  }
  .sponsors-label {
    font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase;
    font-weight: 700; color: var(--ft-faint); flex: none;
  }
  .sponsor-logos { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4) var(--sp-6); list-style: none; margin: 0; padding: 0; }
  /* Each mark sits on a light plate. The footer is dark in both themes, and
     these logos are not all light-on-transparent — the TCPP mark is mostly
     dark ink and the NSF seal has a dark blue globe, so on the gradient they
     sank into it. A plate is also future-proof: any logo dropped in later
     reads correctly without needing a special variant. */
  .sponsor {
    display: inline-flex; align-items: center;
    text-decoration: none;
    transition: transform var(--dur) var(--ease);
  }
  .sponsor:hover { transform: translateY(-1px); }
  .plate {
    display: grid; place-items: center;
    block-size: 3.2rem;                 /* one height, so the row lines up */
    padding-inline: 0.8rem;
    background: oklch(0.99 0 0);
    border-radius: var(--radius-s);
    box-shadow: 0 1px 0 oklch(1 0 0 / 0.35), 0 2px 8px oklch(0 0 0 / 0.28);
    transition: box-shadow var(--dur) var(--ease);
  }
  .sponsor:hover .plate { box-shadow: 0 1px 0 oklch(1 0 0 / 0.5), 0 4px 14px oklch(0 0 0 / 0.36); }
  .plate img { display: block; block-size: 1.85rem; inline-size: auto; max-inline-size: none; }
  /* a square seal needs to be taller than a wordmark to look the same weight */
  .plate--seal { padding-inline: 0.55rem; }
  .plate--seal img { block-size: 2.4rem; }
  @media (forced-colors: active) { .sponsor { forced-color-adjust: none; color: LinkText; } }

  .colophon { margin-block-start: var(--sp-5); font-size: 0.82rem; color: var(--ft-faint); display: flex; flex-wrap: wrap; gap: var(--sp-3); }

  /* --- community icon links ---
     Scoped to .site-footer so it outranks `.footer-grid ul { display: grid }`. */
  .site-footer .social {
    display: flex; flex-flow: row wrap; gap: var(--sp-2);
    list-style: none; margin: 0; padding: 0;
  }
  .social a {
    display: inline-flex; align-items: center; gap: 0.45rem;
    padding: 0.4rem 0.8rem; min-block-size: 2.25rem;
    border: 1px solid var(--ft-line); border-radius: var(--radius-pill);
    font-size: 0.84rem; font-weight: 600; text-decoration: none;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
                transform var(--dur) var(--ease);
  }
  .social a:hover { background: oklch(1 0 0 / 0.1); border-color: oklch(1 0 0 / 0.34); transform: translateY(-1px); }
  .social svg { inline-size: 1.1rem; block-size: 1.1rem; flex: none; }

  /* --- back to top ------------------------------------------------------
     Fixed bottom-right, revealed once the page has been scrolled.         */
  .to-top {
    position: fixed; inset-block-end: 1.5rem; inset-inline-end: 1.5rem; z-index: 45;
    inline-size: 3rem; block-size: 3rem; padding: 0;
    display: grid; place-items: center; cursor: pointer;
    border: 1px solid color-mix(in oklab, var(--brand) 40%, var(--line));
    border-radius: 50%;
    background: var(--bg-raised); color: var(--brand-deep);
    box-shadow: var(--shadow-m);

    opacity: 0; translate: 0 0.9rem; scale: 0.85;
    transition: opacity 260ms var(--ease), translate 260ms var(--ease),
                scale 260ms var(--ease), background var(--dur) var(--ease),
                color var(--dur) var(--ease);
  }
  .to-top[hidden] { display: grid; }        /* animate out rather than vanish */
  .to-top[data-show] { opacity: 1; translate: 0 0; scale: 1; }
  .to-top:not([data-show]) { pointer-events: none; }
  .to-top svg { inline-size: 1.35rem; block-size: 1.35rem; }
  .to-top:hover { background: var(--brand); color: var(--on-brand); border-color: var(--brand); }
  /* the arrow lifts on hover, and nudges once each time the button appears */
  .to-top:hover svg { animation: to-top-nudge 620ms var(--ease) infinite; }
  @keyframes to-top-nudge {
    0%, 100% { translate: 0 0; }
    45%      { translate: 0 -0.22rem; }
  }
  @media (width < 40rem) {
    .to-top { inset-block-end: 1rem; inset-inline-end: 1rem; inline-size: 2.75rem; block-size: 2.75rem; }
  }
  @media (prefers-reduced-motion: reduce) {
    .to-top { transition: opacity 0.01ms; translate: none; scale: none; }
    .to-top:hover svg { animation: none; }
  }
  /* At-a-glance facts on an activity card: how long, what setup, which
     language. Quieter than a .pill, which marks the activity's type. */
  .meta-tag {
    display: inline-flex; align-items: center; padding: 0.15rem 0.5rem;
    font-size: 0.72rem; font-weight: 600; color: var(--fg-muted);
    background: var(--bg-sunken); border: 1px solid var(--line);
    border-radius: var(--radius-pill); white-space: nowrap;
  }

  /* Seventeen authors, still as person cards so the page keeps one language
     for naming people — just tighter, two to a row, with the tail folded away.
     display:contents on the wrapper so the hidden cards join the same grid. */
  .authorgrid { display: grid; gap: 0.5rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  @media (width < 46rem) { .authorgrid { grid-template-columns: 1fr; } }
  .author-more { display: contents; }
  .author-more[hidden] { display: none; }
  .person--sm { gap: 0.6rem; }
  .card.person--sm { padding: 0.5rem 0.7rem; }
  .person--sm .avatar { inline-size: 2.1rem; block-size: 2.1rem; font-size: 0.78rem; }
  .person--sm .who { font-size: 0.88rem; }
  .person--sm .where { font-size: 0.76rem; }

  /* The chapter's own subtitle. Printed in full these run to two lines each and
     read as body copy; a short label carries the sense and the full wording is
     one hover or one tab away. */
  .card-sub { margin: 0.4rem 0 0.75rem; font-size: 0.82rem; line-height: 1.45; }
  .sub-short {
    color: var(--fg-muted); font-weight: 600;
    /* Focusable, so it is a target under WCAG 2.5.8 and needs 24px. Padding on
       an inline box widens the hit area without moving anything around it. */
    padding-block: 0.25rem;
    text-decoration: underline dotted; text-underline-offset: 0.25em;
    text-decoration-color: color-mix(in oklab, var(--brand) 45%, transparent);
    text-decoration-thickness: 1px;
  }
  .sub-short:hover, .sub-short:focus-visible { color: var(--fg); }

  /* --- adoption / contact form ---------------------------------------------
     Plain stacked fields. Nothing here is JavaScript-dependent: the form posts
     and re-renders server-side, errors and all. */
  .form { display: grid; gap: var(--sp-4); }
  .field { display: grid; gap: 0.35rem; border: 0; padding: 0; margin: 0; min-inline-size: 0; }
  .field > label, .field > legend { font-size: 0.86rem; font-weight: 600; color: var(--fg); padding: 0; }
  .field .req { color: var(--brand); }
  .field input[type="text"], .field input[type="email"], .field textarea {
    inline-size: 100%; padding: 0.6rem 0.75rem;
    font: inherit; font-size: 0.92rem; color: var(--fg);
    background: var(--bg); border: 1px solid var(--line-strong);
    border-radius: var(--radius-s);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
  }
  .field textarea { resize: vertical; min-block-size: 5.5rem; }
  .field :is(input, textarea):focus-visible {
    border-color: var(--brand);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand) 22%, transparent);
    outline: none;
  }
  .field :is(input, textarea)[aria-invalid="true"] { border-color: var(--brand-deep); }
  .field-err { margin: 0; font-size: 0.82rem; font-weight: 600; color: var(--brand-deep); }
  .field-row { display: grid; gap: var(--sp-4); grid-template-columns: repeat(2, minmax(0, 1fr)); }
  @media (width < 34rem) { .field-row { grid-template-columns: 1fr; } }
  .checks { display: flex; flex-wrap: wrap; gap: 0.5rem; }
  /* The label is the target for the checkbox inside it, so it carries the
     24px minimum — a single line of 0.9rem text is only 22px. */
  .check { display: flex; gap: 0.55rem; align-items: start; font-size: 0.9rem;
           color: var(--fg-muted); min-block-size: 1.5rem; padding-block: 0.1rem; }
  .check input { margin-block-start: 0.15rem; inline-size: 1rem; block-size: 1rem; flex: none; }
  /* The honeypot must be reachable by a bot parsing the DOM but invisible and
     unfocusable for everyone else — so it is moved off-screen, not display:none,
     which some bots detect and skip. */
  .hp { position: absolute; inset-inline-start: -9999px; inline-size: 1px; block-size: 1px; overflow: hidden; }

  @media print { .to-top { display: none !important; } }

  /* --- target size (WCAG 2.5.8) ---------------------------------------------
     These links stand alone in their block rather than sitting inside a
     sentence, so the "inline" exception does not cover them and they need a
     24px target. An inline box is only as tall as its font metrics — raising
     line-height leaves the hit area untouched — so they take real padding.
     Padding on an inline box does not push the surrounding lines apart, which
     is why the enclosing line-height is set loose enough to absorb it. */
  :is(.card-foot, .callout, .usage-foot, .footer-contact) a { padding-block: 0.18rem; }
  /* .eyebrow is a flex container, so its link is a block-level flex item and
     takes a minimum height rather than padding. */
  .eyebrow a { display: inline-flex; align-items: center; min-block-size: 1.5rem; }
  /* A link that is the entire content of its paragraph or heading is a
     standalone target too. Padding on an inline box changes nothing
     visually — there is no background to grow — it only widens the hit
     area, so this can be stated broadly without risk. */
  :is(p, dd, figcaption, h2, h3, h4, h5, h6) > a:only-child { padding-block: 0.2rem; }
}

/* ------------------------------------------------------------ utilities -- */
@layer utilities {
  .visually-hidden:where(:not(:focus-within, :active)) {
    position: absolute !important; clip-path: inset(50%) !important;
    inline-size: 1px !important; block-size: 1px !important;
    overflow: hidden !important; white-space: nowrap !important;
    margin: -1px !important; padding: 0 !important; border: 0 !important;
  }
  .muted { color: var(--fg-muted); }
  .faint { color: var(--fg-faint); }
  .small { font-size: 0.88rem; }
  .tiny { font-size: 0.78rem; }
  .center { text-align: center; }
  .mt-2 { margin-block-start: var(--sp-2); }
  .mt-3 { margin-block-start: var(--sp-3); }
  .mt-4 { margin-block-start: var(--sp-4); }
  .mt-5 { margin-block-start: var(--sp-5); }
  .mt-6 { margin-block-start: var(--sp-6); }
  .mb-2 { margin-block-end: var(--sp-2); }
  .mb-3 { margin-block-end: var(--sp-3); }
  .mb-4 { margin-block-end: var(--sp-4); }
  .nowrap { white-space: nowrap; }
  .full { inline-size: 100%; }
  .measure { max-inline-size: var(--measure); }
}

/* ---------------------------------------------------------------- print -- */
@media print {
  .site-header, .site-footer, .skip-link, .nav-toggle, .icon-btn,
  .flagmaker, .modeswitch, #ex-form, .fm-scenarios { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .hero { padding-block: 0 1rem; background: none; border: 0; }
  .hero::after { display: none; }
  .section, .section--tight { padding-block: 0.75rem; }
  .band--sunken, .band--tint { background: none; border: 0; }
  .card, .panel, .table-wrap { border: 1px solid #999; box-shadow: none; break-inside: avoid; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
  h1, h2, h3 { break-after: avoid; }
}
