/* ============================================================
   laurenceavent.com — design system
   Zero external requests: fonts are self-hosted (SIL OFL).
   Light/dark via prefers-color-scheme + explicit [data-theme].
   ============================================================ */

/* ---------- self-hosted variable fonts ----------
   Subset by tools/make_fonts.py; URLs are content-fingerprinted by
   tools/build.py. Deliberately no `unicode-range`: its only effect is to stop
   the browser using the font for a codepoint, and there is exactly one file per
   family here, so a range that is narrower than the subset silently drops
   glyphs to a system fallback. (The stock Google "latin" range does precisely
   that to U+2192 →, which every Lean statement on this site contains.) */
@font-face {
  font-family: "Inter var";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("/fonts/inter-var-latin.8e6bad39b5.woff2") format("woff2-variations");
}
@font-face {
  font-family: "JetBrains Mono var";
  font-style: normal;
  font-display: swap;
  font-weight: 100 800;
  src: url("/fonts/jetbrains-mono-var-latin.3d6669f04a.woff2") format("woff2-variations");
}

:root {
  --bg:        #ffffff;
  --bg-subtle: #f7f8fa;
  --bg-sunken: #f1f3f6;
  --surface:   #ffffff;
  --border:    #e6e8ee;
  --border-strong: #d3d8e0;
  /* Boundary of a control whose outline is its only affordance (WCAG 1.4.11
     wants 3:1 against the adjacent surface). --border/--border-strong are
     decorative separators and are deliberately quieter than this. */
  --border-control: #828a99;
  --text:      #12151a;
  --text-muted:#565e6a;
  --text-faint:#646c78;
  --accent:    #2b50e2;
  --accent-ink:#1c37b0;
  --accent-soft: rgba(43, 80, 226, 0.075);
  --accent-line: rgba(43, 80, 226, 0.22);
  --verified:  #10693c;
  --verified-soft: rgba(16, 105, 60, 0.10);
  --warn:      #b3261e;
  --dot:       rgba(18, 21, 26, 0.055);
  --radius:    13px;
  --radius-sm: 8px;
  --maxw:      1080px;
  --readw:     720px;
  --header-h:  62px;
  --sans: "Inter var", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: "JetBrains Mono var", ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
  --shadow: 0 1px 2px rgba(16,18,22,0.04), 0 8px 24px rgba(16,18,22,0.05);
  --shadow-hover: 0 2px 4px rgba(16,18,22,0.06), 0 14px 40px rgba(16,18,22,0.09);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:        #0a0b0d;
    --bg-subtle: #101216;
    --bg-sunken: #0d0f12;
    --surface:   #111318;
    --border:    #262b36;
    --border-strong: #313640;
    --border-control: #606877;
    --text:      #edeff3;
    --text-muted:#9ba3ae;
    --text-faint:#7c8592;
    --accent:    #8aa9ff;
    --accent-ink:#b3c7ff;
    --accent-soft: rgba(138, 169, 255, 0.11);
    --accent-line: rgba(138, 169, 255, 0.26);
    --verified:  #4ecb92;
    --verified-soft: rgba(78, 203, 146, 0.13);
    --warn:      #f4837c;
    --dot:       rgba(237, 239, 243, 0.055);
    --shadow: 0 1px 2px rgba(0,0,0,0.4), 0 10px 30px rgba(0,0,0,0.35);
    --shadow-hover: 0 2px 6px rgba(0,0,0,0.5), 0 18px 48px rgba(0,0,0,0.5);
  }
}

[data-theme="dark"] {
  --bg:        #0a0b0d;
  --bg-subtle: #101216;
  --bg-sunken: #0d0f12;
  --surface:   #111318;
  --border:    #262b36;
  --border-strong: #313640;
  --border-control: #606877;
  --text:      #edeff3;
  --text-muted:#9ba3ae;
  --text-faint:#7c8592;
  --accent:    #8aa9ff;
  --accent-ink:#b3c7ff;
  --accent-soft: rgba(138, 169, 255, 0.11);
  --accent-line: rgba(138, 169, 255, 0.26);
  --verified:  #4ecb92;
  --verified-soft: rgba(78, 203, 146, 0.13);
  --warn:      #f4837c;
  --dot:       rgba(237, 239, 243, 0.055);
  --shadow: 0 1px 2px rgba(0,0,0,0.4), 0 10px 30px rgba(0,0,0,0.35);
  --shadow-hover: 0 2px 6px rgba(0,0,0,0.5), 0 18px 48px rgba(0,0,0,0.5);
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16.5px;
  line-height: 1.65;
  font-feature-settings: "kern", "liga", "calt";
  /* Inter's subset keeps its opsz axis (14-32); this is the browser default,
     stated so it is obvious the display cut on the hero h1 is intentional. */
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Announced by screen readers, invisible on screen. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-ink); }

h1, h2, h3, h4 { line-height: 1.15; letter-spacing: -0.021em; font-weight: 640; margin: 0; }
p { margin: 0 0 1rem; }

/* Anything that can be jumped to clears the sticky header. */
[id] { scroll-margin-top: calc(var(--header-h) + 22px); }

/* Consistent, visible keyboard focus everywhere. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}
:focus:not(:focus-visible) { outline: none; }

.container { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
/* A reading-measure container. It caps the line length *without* re-centring:
   the left margin is exactly where a full-width .container's left edge falls,
   so the text stays on the same vertical rule as the header brand and the
   footer, instead of floating inboard of everything else on the page. */
.reading {
  max-width: calc(var(--readw) + 48px);
  margin-left: max(0px, calc((100% - var(--maxw)) / 2));
  margin-right: auto;
}

/* ---------- eyebrow / labels ---------- */
.eyebrow {
  display: block;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: 500;
}

/* ---------- header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--border);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header { background: var(--bg); }
}
.site-header .container {
  display: flex; align-items: center; justify-content: space-between;
  height: var(--header-h);
}
.brand {
  display: inline-flex; align-items: center; gap: 9px;
  font-weight: 640; letter-spacing: -0.02em; color: var(--text); font-size: 15px;
}
.brand:hover { color: var(--text); }
.brand svg { width: 5px; height: 19px; flex: none; color: var(--accent); }
.brand-name { white-space: nowrap; }
.header-actions { display: flex; align-items: center; }

.nav { display: flex; align-items: center; gap: 2px; }
.nav a {
  color: var(--text-muted); font-size: 14.5px; font-weight: 500;
  padding: 7px 11px; border-radius: var(--radius-sm);
  transition: color .15s ease, background .15s ease;
}
.nav a:hover { color: var(--text); background: var(--bg-subtle); }
.nav a.active { color: var(--text); }
.nav a.active::after {
  content: ""; display: block; height: 2px; margin-top: 3px;
  background: var(--accent); border-radius: 2px;
}
.icon-btn {
  display: inline-grid; place-items: center;
  width: 34px; height: 34px; margin-left: 8px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-muted); cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.icon-btn:hover { color: var(--text); border-color: var(--border-strong); background: var(--bg-subtle); }
.icon-btn svg { width: 17px; height: 17px; }
.theme-icon-dark { display: none; }
[data-theme="dark"] .theme-icon-light { display: none; }
[data-theme="dark"] .theme-icon-dark { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-icon-light { display: none; }
  :root:not([data-theme="light"]) .theme-icon-dark { display: block; }
}

/* Hamburger is mobile-only. */
[data-nav-toggle] { display: none; }

/* ---------- hero ---------- */
.hero { padding: 96px 0 62px; position: relative; overflow: hidden; }
.hero::before {
  content: ""; position: absolute; inset: -46% 0 auto 0; height: 540px;
  background: radial-gradient(58% 58% at 28% 0%, var(--accent-soft), transparent 70%);
  pointer-events: none; z-index: 0;
}
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: radial-gradient(var(--dot) 1px, transparent 1px);
  background-size: 22px 22px;
  /* Fades all the way to the element's own bottom edge. Stopping short (e.g.
     `transparent 72%`) leaves a visible horizontal seam on the short sub-page
     heroes, where 72% of the box is only ~110px down. */
  -webkit-mask-image: linear-gradient(180deg, rgba(0,0,0,.9), rgba(0,0,0,.45) 46%, transparent);
  mask-image: linear-gradient(180deg, rgba(0,0,0,.9), rgba(0,0,0,.45) 46%, transparent);
}
.hero .container { position: relative; z-index: 1; }
.hero h1 {
  font-size: clamp(2.4rem, 5.4vw, 3.55rem);
  letter-spacing: -0.036em; font-weight: 680; margin: 14px 0 0;
}
.hero-sub h1 { font-size: clamp(2rem, 4.4vw, 2.85rem); }
.hero .lede {
  font-size: clamp(1.05rem, 2.1vw, 1.27rem);
  color: var(--text-muted); max-width: 640px; margin: 18px 0 0; line-height: 1.55;
}
.hero-pb-sm { padding-bottom: 22px; }
.hero-pb-md { padding-bottom: 34px; }

/* Two-column hero: copy on the left, the shipped theorem on the right. */
.hero-grid { display: grid; gap: 44px; align-items: center; }
@media (min-width: 940px) {
  .hero-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 25rem); gap: 56px; }
  .hero-grid .lede { max-width: 34rem; }
}

/* ---------- proof card ---------- */
.proofcard {
  margin: 0; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow-hover); overflow: hidden;
  transition: border-color .18s ease, box-shadow .18s ease;
}
.proofcard:hover { border-color: var(--border-strong); }
.pc-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 15px; border-bottom: 1px solid var(--border);
  background: var(--bg-subtle);
  font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.02em;
}
.pc-file { color: var(--text-muted); }
.pc-state {
  color: var(--verified); background: var(--verified-soft);
  padding: 2px 8px; border-radius: 999px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; font-size: 10.5px;
}
/* The UA stylesheet sets `font-family: monospace` on code/kbd/samp/pre
   directly, and a direct rule beats an inherited one — so a <code> inside a
   styled <pre> quietly reverts to the platform monospace font while everything
   around it uses ours. `font: inherit` is the only reliable fix; it also
   disposes of the legacy "monospace shrinks to the fixed-width default size"
   quirk in the same stroke. */
code, kbd, samp, pre { font: inherit; }

.pc-body {
  margin: 0; padding: 17px 16px; overflow-x: auto;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.72;
  color: var(--text); white-space: pre; tab-size: 2;
}
.pc-body .kw { color: var(--accent); font-weight: 600; }
.pc-body .id { color: var(--text); }
.pc-body .cm { color: var(--text-faint); }
/* A shell prompt is not part of the command. Excluding it from selection means
   dragging across the whole block and pasting into a terminal actually runs —
   the `#` lines survive the trip as ordinary shell comments, a bare `$` would
   not. */
.pc-body .pr { color: var(--accent); user-select: none; -webkit-user-select: none; }
/* Command lines scroll sideways on a narrow screen; the digest they produce must
   not. It is the one string on the card the reader compares character by
   character against their own terminal, and a 64-character hash clipped at the
   card edge looks exactly like a whole one. Same reasoning as `.hashrow .hx`
   below. Wide enough to fit, this rule does nothing. */
.pc-body .out { white-space: pre-wrap; overflow-wrap: anywhere; }
.pc-foot {
  display: flex; flex-wrap: wrap; gap: 6px 14px;
  padding: 11px 16px; border-top: 1px solid var(--border);
  background: var(--bg-subtle);
  font-family: var(--mono); font-size: 11px; color: var(--text-faint);
}
.pc-foot span { display: inline-flex; align-items: center; gap: 6px; }
.pc-foot span + span::before {
  content: ""; width: 3px; height: 3px; border-radius: 50%;
  background: var(--text-faint); opacity: .6;
}

.status-line {
  display: inline-flex; align-items: center; gap: 10px;
  margin-top: 28px; padding: 8px 15px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); font-family: var(--mono); font-size: 12.5px;
  color: var(--text-muted); box-shadow: var(--shadow);
}
.status-line .chip-verified { color: var(--verified); font-weight: 600; }
.status-line .mono { color: var(--text); font-weight: 600; }
.hero-actions { margin-top: 30px; display: flex; flex-wrap: wrap; gap: 12px; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 17px; border-radius: var(--radius-sm);
  font-size: 14.5px; font-weight: 550; cursor: pointer;
  font-family: inherit;
  border: 1px solid transparent;
  transition: background .16s ease, border-color .16s ease, color .16s ease, transform .16s ease, box-shadow .16s ease;
  white-space: nowrap;
}
.btn svg { width: 15px; height: 15px; flex: none; }
.btn-primary { background: var(--accent); color: #fff; border-color: var(--accent); }
[data-theme="dark"] .btn-primary { color: #0a0b0d; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .btn-primary { color: #0a0b0d; } }
.btn-primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); color: #fff; transform: translateY(-1px); }
[data-theme="dark"] .btn-primary:hover { color: #0a0b0d; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .btn-primary:hover { color: #0a0b0d; } }
.btn-ghost { background: var(--surface); color: var(--text); border-color: var(--border-control); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); transform: translateY(-1px); }
.btn-sm { padding: 6px 12px; font-size: 13px; }

/* ---------- section ---------- */
section.block { padding: 52px 0; }
.section-head { margin-bottom: 26px; }
.section-head h2 { font-size: clamp(1.5rem, 3vw, 1.95rem); margin-top: 8px; }
.section-head .sub { color: var(--text-muted); margin-top: 9px; max-width: 640px; }
.divider { border: 0; border-top: 1px solid var(--border); margin: 0; }

/* heading anchor links */
.anchor {
  margin-left: 10px; font-family: var(--mono); font-weight: 400;
  color: var(--text-faint); opacity: 0; font-size: .72em;
  transition: opacity .15s ease, color .15s ease;
}
h2:hover .anchor, h3:hover .anchor, .anchor:focus-visible { opacity: 1; }
.anchor:hover { color: var(--accent); }

/* ---------- cards ---------- */
.card-grid { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(272px, 1fr)); }
.card {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); padding: 22px; box-shadow: var(--shadow);
  transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
  display: flex; flex-direction: column;
}
.card:hover { transform: translateY(-3px); border-color: var(--border-strong); box-shadow: var(--shadow-hover); }
.card h3 { font-size: 1.13rem; }
.card .card-eyebrow { margin-bottom: 12px; }
.card p { color: var(--text-muted); font-size: 15px; margin: 10px 0 0; }
.card .card-links { margin-top: auto; padding-top: 16px; display: flex; flex-wrap: wrap; gap: 14px; }
.card .card-links a { font-size: 14px; font-weight: 550; display: inline-flex; align-items: center; gap: 5px; }
.card .card-links a svg { width: 12px; height: 12px; flex: none; }

/* ---------- chips / tags ---------- */
.tags { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 14px; align-items: center; }
.tag {
  font-size: 11.5px; font-family: var(--mono); letter-spacing: 0.02em;
  padding: 3px 9px; border-radius: 6px;
  background: var(--bg-subtle); color: var(--text-muted); border: 1px solid var(--border);
}
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; font-family: var(--mono);
  padding: 4px 11px; border-radius: 999px;
}
/* Requires .chip for the pill padding and radius — a bare .chip-verified (the
   one in the hero status line) must stay plain text, or the tint paints a hard
   rectangle inside the rounded strip. */
.chip.chip-verified { background: var(--verified-soft); color: var(--verified); }
.chip svg { width: 12px; height: 12px; flex: none; }

/* ---------- stat strip ---------- */
.statstrip {
  display: grid; gap: 1px; margin: 22px 0 0;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  background: var(--border); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
.statstrip .stat { background: var(--surface); padding: 18px 18px 16px; }
.statstrip .stat .n {
  font-family: var(--mono); font-size: 1.8rem; font-weight: 600;
  color: var(--text); letter-spacing: -0.035em; display: block;
  line-height: 1.1; font-variant-numeric: tabular-nums;
}
.statstrip .stat .n.ok { color: var(--verified); }
.statstrip .stat .l {
  display: block; margin-top: 3px; font-size: 12.5px; color: var(--text-faint);
  line-height: 1.4; text-wrap: balance;
}
/* Keep the strip's baseline even when one label wraps to two lines. */
@media (min-width: 620px) { .statstrip .stat .l { min-height: 2.8em; } }

/* ---------- research: result banner ---------- */
.result-banner {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: linear-gradient(180deg, var(--accent-soft), transparent 58%), var(--surface);
  padding: 26px 26px 24px; box-shadow: var(--shadow); margin-bottom: 34px;
}
.result-banner .theorem {
  font-family: var(--mono); font-size: 13.5px; line-height: 1.75;
  background: var(--bg-sunken); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 15px 17px; margin: 16px 0 0;
  overflow-x: auto; color: var(--text); white-space: pre;
}
.result-banner .theorem .kw { color: var(--accent); font-weight: 600; }
.result-banner .theorem .id { color: var(--text); }
.result-banner .theorem .cm { color: var(--text-faint); }
.result-banner h2 { font-size: clamp(1.42rem, 2.6vw, 1.78rem); margin-top: 12px; max-width: 34ch; }
.result-banner h2.wide { max-width: 42ch; }
.result-banner .paper-links { margin-top: 20px; }

/* ---------- paper list ---------- */
.paper {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); padding: 24px; box-shadow: var(--shadow);
  margin-bottom: 18px; transition: border-color .18s ease, box-shadow .18s ease;
}
.paper:hover { border-color: var(--border-strong); box-shadow: var(--shadow-hover); }
.paper .paper-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
/* h2 on /projects/ (top-level sections of that page), h3 on /research/ (they
   sit under the "Three reports" heading). Same object, same weight either way. */
.paper h2, .paper h3 { font-size: 1.26rem; margin-top: 10px; max-width: 58ch; }
.paper .authors { font-family: var(--mono); font-size: 12px; color: var(--text-faint); white-space: nowrap; }
.paper .abstract { color: var(--text-muted); font-size: 15px; margin-top: 14px; max-width: 74ch; }
/* Unscoped: the research page's result banner uses the same action row. */
.paper-links { margin-top: 18px; display: flex; flex-wrap: wrap; gap: 10px; }

/* Wide screens: pull the actions into a right rail so the abstract's
   reading measure doesn't leave a dead gutter inside the card. */
@media (min-width: 900px) {
  .paper {
    display: grid; grid-template-columns: minmax(0, 1fr) 14.5rem;
    column-gap: 30px; row-gap: 0;
  }
  .paper .paper-head,
  .paper .abstract,
  .paper .tags,
  .paper ul { grid-column: 1; }
  .paper .paper-head { display: block; }
  .paper .paper-head .authors { display: none; }
  /* `grid-row: 1 / -1` would resolve against the *explicit* grid (which has no
     rows here) and collapse to row 1, inflating it. Span past the last real row
     instead; the extra implicit rows are zero-height because row-gap is 0. */
  .paper .paper-links {
    grid-column: 2; grid-row: 1 / span 12; align-self: stretch;
    margin-top: 2px; flex-direction: column; align-items: stretch;
    justify-content: flex-start;
    padding-left: 30px; border-left: 1px solid var(--border);
  }
  .paper .paper-links .rail-meta {
    font-family: var(--mono); font-size: 12px; color: var(--text-faint);
    margin-bottom: 4px;
  }
  /* The rail content box is ~202px. `white-space: nowrap` from .btn would push
     the longer labels straight through the card edge, so let them wrap here. */
  .paper .paper-links .btn {
    justify-content: center; text-align: center;
    white-space: normal; line-height: 1.3; padding: 9px 14px;
  }
  /* The rail is a *column*, where `flex-basis` resolves against the container's
     height — the row-layout `100%` below would leave the note inline and running
     out through the card edge. It is already last in DOM order, so plain
     auto-sizing puts it under the buttons where it belongs. */
  .paper .paper-links .rail-note { flex: 0 0 auto; margin-top: 6px; }
}
.paper .paper-links .rail-meta { display: none; }
@media (min-width: 900px) { .paper .paper-links .rail-meta { display: block; } }
/* Page count and file size, measured from the PDF that is actually served.
   Below the rail breakpoint `.paper-links` is a wrapping row, and `flex: 0 0 100%`
   is what drops the note onto its own line under the buttons — on a phone,
   knowing the download size before tapping it matters more, not less. */
.paper-links .rail-note {
  flex: 0 0 100%; margin: 2px 0 0;
  font-family: var(--mono); font-size: 11.5px; color: var(--text-faint);
}
.paper ul { margin: 14px 0 0; padding-left: 20px; color: var(--text-muted); font-size: 15px; }
.paper ul li { margin: 4px 0; }

/* ---------- hash rows ---------- */
.hashes {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-subtle); overflow: hidden;
  /* The filenames differ in length, so per-row grids would start every hash at
     a different x. Track sizing lives on the list and each row adopts it with
     `subgrid`, which puts all three columns on one set of rules. The plain
     declaration above it is the fallback: a browser without subgrid ignores
     the second line and keeps independent (but still correct) rows. */
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
}
/* Filename first, then the hash, then the button hard against the right edge.
   The DOM order is hash-then-filename (it is the hash that is the datum), so
   `order` does the swap without a second markup path for the mobile stack. */
.hashrow {
  display: grid; grid-column: 1 / -1;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-columns: subgrid;
  align-items: center; gap: 14px;
  padding: 11px 16px; border-top: 1px solid var(--border);
  font-family: var(--mono); font-size: 12.5px;
}
.hashrow:first-child { border-top: 0; }
.hashrow .fn { order: 1; color: var(--text); white-space: nowrap; }
.hashrow .hx { order: 2; color: var(--text-muted); overflow-x: auto; white-space: nowrap; }
.hashrow .copy-btn { order: 3; }
.copy-btn {
  border: 1px solid var(--border-control); background: var(--surface);
  color: var(--text-muted); border-radius: 6px; cursor: pointer;
  font-family: var(--mono); font-size: 11px; padding: 0 10px;
  transition: color .15s ease, border-color .15s ease;
  /* WCAG 2.5.8 target size. */
  min-width: 64px; min-height: 26px;
  display: inline-flex; align-items: center; justify-content: center;
}
.copy-btn:hover { color: var(--accent); border-color: var(--accent); }
.copy-btn[data-copied="1"] { color: var(--verified); border-color: var(--verified); }
/* Clipboard writes can be refused (no permission, insecure context, no API at
   all). Say so on the button as well as in the live region — colour is not the
   only difference, the word changes to "Failed" too. */
.copy-btn[data-copied="0"] { color: var(--warn); border-color: var(--warn); }
.copy-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 14px; }

/* ---------- prose (cv, generic) ---------- */
.prose h2 { font-size: 1.45rem; margin: 44px 0 6px; }
.prose h3 { font-size: 1.1rem; margin: 26px 0 2px; }
.prose ul { margin: 8px 0 16px; padding-left: 20px; color: var(--text-muted); }
.prose li { margin: 5px 0; }
.prose .lead { font-size: 1.1rem; color: var(--text-muted); }

/* timeline entries */
.entry { padding: 20px 0; border-top: 1px solid var(--border); }
/* Every group under a heading starts clean, not just the first group on the
   page — `:first-of-type` alone leaves a rule butting up under each later h2. */
.entry:first-of-type, h2 + .entry { border-top: 0; }
.entry .entry-top { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; align-items: baseline; }
.entry h3 { font-size: 1.08rem; margin: 0; }
.entry .org { color: var(--accent); font-weight: 600; }
.entry .when { font-family: var(--mono); font-size: 12px; color: var(--text-faint); white-space: nowrap; }
.entry p { margin: 8px 0 0; color: var(--text-muted); font-size: 15px; }
.entry .fw-normal { font-weight: 400; }

/* skills grid */
.kv { display: grid; grid-template-columns: 168px 1fr; gap: 8px 20px; margin-top: 10px; }
.kv dt { font-family: var(--mono); font-size: 12.5px; color: var(--text-faint); }
.kv dd { margin: 0; color: var(--text-muted); font-size: 15px; }

/* ---------- disclosures ---------- */
.disc { border-top: 1px solid var(--border); padding: 22px 0; }
.disc:first-of-type, h2 + .disc { border-top: 0; }
.disc .disc-top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.disc h2, .disc h3 { font-size: 1.1rem; }
.disc p { color: var(--text-muted); font-size: 15px; margin: 10px 0 0; max-width: 74ch; }
.chip-status {
  font-family: var(--mono); font-size: 11.5px; padding: 3px 10px; border-radius: 999px;
  background: var(--bg-subtle); color: var(--text-muted); border: 1px solid var(--border);
  white-space: nowrap;
}
.chip-status.ack { background: var(--verified-soft); color: var(--verified); border-color: transparent; }

/* ---------- contact ---------- */
.contact-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.contact-card {
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
  padding: 16px 18px; display: flex; align-items: center; gap: 13px;
  transition: border-color .18s ease, transform .18s ease;
}
.contact-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.contact-card svg { width: 17px; height: 17px; flex: none; color: var(--text-faint); }
.contact-card:hover svg { color: var(--accent); }
.contact-card .cc-t { display: block; color: var(--text); font-weight: 550; font-size: 14.5px; }
.contact-card .cc-s { display: block; color: var(--text-faint); font-size: 12.5px; font-family: var(--mono); margin-top: 1px; }

/* ---------- footer ---------- */
.site-footer { border-top: 1px solid var(--border); margin-top: 48px; padding: 40px 0 34px; color: var(--text-muted); }
.site-footer .foot-top { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; align-items: flex-start; }
.site-footer .foot-links { display: flex; gap: 18px; flex-wrap: wrap; }
.site-footer a { color: var(--text-muted); font-size: 14px; }
.site-footer a:hover { color: var(--text); }
.site-footer .fine { font-size: 13px; color: var(--text-faint); max-width: 46ch; }
.site-footer .foot-bottom {
  margin-top: 28px; padding-top: 18px; border-top: 1px solid var(--border);
  display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  font-family: var(--mono); font-size: 11.5px; color: var(--text-faint);
}

/* ---------- misc ---------- */
.list-plain { list-style: none; padding: 0; margin: 0; }
.mono { font-family: var(--mono); }
.muted { color: var(--text-muted); }
.faint { color: var(--text-faint); }
.fs-sm { font-size: 14px; }
.max-70 { max-width: 70ch; }
.mt-xs { margin-top: 6px; } .mt-sm { margin-top: 10px; } .mt { margin-top: 22px; } .mt-lg { margin-top: 44px; }
.pb { padding-bottom: 26px; }
.center { text-align: center; }
.skip {
  position: absolute; left: -9999px; top: 0; background: var(--accent); color: #fff;
  padding: 9px 15px; border-radius: 0 0 8px 0; z-index: 100; font-size: 14px;
  font-weight: 550;
}
.skip:focus { left: 0; color: #fff; }
/* The dark accent is a light blue; white-on-it is 2.0:1. Mirror .btn-primary
   and put the dark ink on top instead (8.6:1). Both selectors are needed —
   `.skip:focus` re-declares the colour at equal specificity. */
[data-theme="dark"] .skip, [data-theme="dark"] .skip:focus { color: #0a0b0d; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .skip,
  :root:not([data-theme="light"]) .skip:focus { color: #0a0b0d; }
}

/* ---------- responsive ---------- */
@media (max-width: 760px) {
  body { font-size: 16px; }
  .hero { padding: 60px 0 42px; }
  section.block { padding: 40px 0; }
  .nav {
    position: fixed; inset: var(--header-h) 0 auto 0; flex-direction: column; align-items: stretch;
    background: var(--bg); border-bottom: 1px solid var(--border); padding: 8px 16px 16px;
    gap: 2px; transform: translateY(-8px); opacity: 0;
    visibility: hidden; pointer-events: none;
    transition: opacity .16s ease, transform .16s ease, visibility 0s linear .16s;
  }
  .nav.open {
    opacity: 1; transform: none; visibility: visible; pointer-events: auto;
    transition: opacity .16s ease, transform .16s ease, visibility 0s;
  }
  .nav a { padding: 11px 12px; font-size: 15px; }
  .nav a.active::after { display: none; }
  .nav a.active { background: var(--bg-subtle); }
  [data-nav-toggle] { display: inline-grid; }
  .kv { grid-template-columns: 1fr; gap: 2px 0; }
  .kv dd { margin-bottom: 12px; }
  /* Narrow enough that the hash needs its own line: drop the shared column
     track and let each row be an ordinary two-column grid again. `order: 4`,
     not 3 — the copy button already holds 3, and grid ties break by DOM order,
     which would put the hash first. */
  .hashes { grid-template-columns: 1fr; }
  .hashrow { grid-template-columns: 1fr auto; row-gap: 6px; }
  /* Let the hash wrap rather than run off the card. `nowrap` plus a scroll
     container is right on desktop, where 64 hex characters fit on one line, but
     at this width it clips mid-digest with nothing to say it was clipped — a
     truncated hash that looks whole is worse than one on two lines. */
  .hashrow .hx {
    grid-column: 1 / -1; order: 4;
    white-space: normal; overflow-wrap: anywhere; overflow-x: visible;
  }
  .statstrip .stat .n { font-size: 1.55rem; }
  .anchor { display: none; }
}

@media (max-width: 560px) {
  /* Phone widths are where these rows start wrapping, and a wrapped row of
     shrink-to-fit buttons leaves a ragged right edge with a different width on
     every line. `flex: 1 1 auto` makes each line's buttons share it instead: two
     that still fit side by side split the row, and one that had to wrap fills
     it. Tap targets get bigger in the same stroke. */
  .hero-actions .btn,
  .paper-links .btn,
  .copy-row .btn { flex: 1 1 auto; justify-content: center; }

  /* Wrap the shell block rather than let it scroll sideways. A horizontal
     scroller inside a vertically scrolling page is easy to miss entirely, and a
     command clipped at the card edge reads as the whole command — on the one
     card whose entire purpose is that the reader runs what it says, that is the
     worst possible failure. Real terminals wrap at the window edge too, and
     every command line here is introduced by a `$`, so a line without one is
     plainly a continuation. Above this width the block fits and this is moot. */
  .pc-body { white-space: pre-wrap; }
}

@media (max-width: 430px) {
  /* The name stays. Hiding it left a 5x19px tap target for the home link, and
     there is room for it here: brand + two 34px icon buttons is well under
     390px once the nav collapses. */
  .brand { font-size: 14px; gap: 8px; }
  .result-banner { padding: 20px 18px; }
  .paper { padding: 20px 18px; }
  .result-banner .theorem, .pc-body { font-size: 11.5px; }
  .hero .lede, .prose .lead { text-wrap: pretty; }
}

/* ---------- print (CV in particular) ---------- */
@media print {
  :root {
    --bg: #fff; --surface: #fff; --bg-subtle: #fff; --bg-sunken: #fff;
    --text: #000; --text-muted: #333; --text-faint: #555;
    --border: #ccc; --border-strong: #999; --border-control: #666;
    --accent: #000; --shadow: none;
  }
  .site-header, .site-footer, .hero-actions, .skip, .anchor, .copy-btn, .contact-grid { display: none !important; }
  body { font-size: 11pt; line-height: 1.45; background: #fff; color: #000; }
  .hero { padding: 0 0 12pt; }
  .hero::before, .hero::after { display: none; }
  .hero h1 { font-size: 22pt; }
  .hero .lede { font-size: 11pt; color: #333; max-width: none; }
  .container { max-width: none; padding: 0; }
  .reading { max-width: none; margin-left: 0; }
  .prose h2 { margin: 16pt 0 4pt; font-size: 13pt; }
  .entry { padding: 8pt 0; break-inside: avoid; }
  .paper, .card, .result-banner { box-shadow: none; break-inside: avoid; }
  a { color: #000; text-decoration: underline; }
}
