/* Self-hosted, all three SIL Open Font License 1.1. Licences ship alongside
   the files in assets/fonts/. No third-party font request, so nothing external
   blocks the first paint.

   Geist stands in for the sans Mohamed liked — that face is credited
   "BSPK x Geist x Anthropic", so this is its open ancestor rather than a
   lookalike. Paper Mono is the one he picked, used as picked. */
@font-face {
  font-family: Geist;
  src: url('/assets/fonts/Geist-var.woff2') format('woff2-variations');
  font-weight: 100 900; font-style: normal; font-display: swap;
}
/* Two faces, because Newsreader has no italic axis: without the second one an
   `em` or a standfirst gets a mechanically skewed roman rather than a true
   italic — no single-storey a, no cursive e.

   Both are pinned to `opsz` 16 rather than shipping the 6-72 optical axis.
   That axis doubled the file (175KB → 80KB once pinned) to carry sizes this
   site never sets, and 16 is the cut wanted for body text anyway: sturdier
   hairlines, lower stroke contrast, marginally more open spacing.

   The weight range is the file's real one. It was declared 300-700 against a
   font that goes 200-800, so the ends were being clamped. */
@font-face {
  font-family: Newsreader;
  src: url('/assets/fonts/Newsreader-var.woff2') format('woff2-variations');
  font-weight: 200 800; font-style: normal; font-display: swap;
}
@font-face {
  font-family: Newsreader;
  src: url('/assets/fonts/Newsreader-Italic-var.woff2') format('woff2-variations');
  font-weight: 200 800; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'Paper Mono';
  src: url('/assets/fonts/PaperMono-var.woff2') format('woff2-variations');
  font-weight: 100 800; font-style: normal; font-display: swap;
}

/* mr-eyes.com — a quiet scientific editorial system. The near-black and paper
   surfaces are warm rather than neutral, reducing glare without turning muddy. */

:root {
  color-scheme: dark;
  --bg:        #111512;
  --ink:       #eceee9;
  --body:      #d5d9d3;
  --muted:     #bac3bb;
  --soft:      #d3d8d2;
  --dim:       #96a199;
  --rule:      #29302b;
  --border:    #343c36;
  --panel:     #181d19;
  --nav-off:   #a6b0a8;
  --nav-hover: #e3e7e1;
  --accent:    #ef765a;
  --wash:      rgba(239, 118, 90, .075);
  --hover:     rgba(236, 238, 233, .035);
  --shadow:    rgba(0, 0, 0, .45);

  /* figure ink and series, stepped for the dark surface */
  --fig-ink: #eceee9; --fig-muted: #a3ada5; --fig-dim: #68736b;
  --fig-rule: #29302b; --fig-axis: #39423c; --fig-baseline: #505a53;
  --s1: #3987e5; --s2: #d95926;

  --sans:  Geist, system-ui, -apple-system, "Segoe UI", sans-serif;
  --serif: Newsreader, Georgia, "Iowan Old Style", serif;
  --mono:  "Paper Mono", ui-monospace, SFMono-Regular, monospace;
  --gutter: clamp(1.25rem, 4.5vw, 3.25rem);
  --shell: 1160px;
  --measure: 70ch;
  --lede: 48ch;
  --col: 6.75rem;
  --col-gap: 1.35rem;
  --mini-h: 58px;        /* the compact bar; .menu hangs off its lower edge */
  /* One spacing scale for the whole site. It lived on `.home` until the
     shared footer started using it, at which point every other page lost
     its footer gaps to an undefined variable. */
  --s3: 12px; --s4: 16px; --s6: 24px; --s8: 32px;
  --s12: 48px; --s16: 64px; --s20: 80px; --s24: 96px;
  /* A photograph shot on a cool studio backdrop needs different handling on
     each surface: near-black takes it as it is, warm paper does not. */
  --photo-grade: saturate(.96);
  --photo-mono: grayscale(1) sepia(.28) saturate(1.1) brightness(.76) contrast(1.08);
  /* the accent is the brand mark; a link inside prose needs to be darker
     than the text around it, not lighter */
  --link: #f0907a;
  /* antialiased suppresses the halation of light-on-dark; light theme wants
     the default, or the serif goes spindly */
  --smoothing: antialiased;
}

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

html { scroll-behavior: smooth; background: var(--bg); }

body {
  min-height: 100vh; margin: 0;
  background: radial-gradient(circle at 9% -8%, var(--wash), transparent 32rem), var(--bg);
  color: var(--ink);
  font: 400 17px/1.65 var(--sans);
  -webkit-font-smoothing: var(--smoothing);
}

a { color: inherit; }
::selection { color: var(--bg); background: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.skip {
  position: absolute; left: -9999px;
  background: var(--accent); color: var(--bg); padding: .7rem 1rem;
  font: 600 .8rem/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
}
.skip:focus { left: 1rem; top: 1rem; z-index: 20; }

/* ─────────────────────────────────────────── the shell

   One horizontal header, sticky, fixed height. It never resizes, so it can
   never reflow the page under a scroll — which is what the earlier two-element
   morph was working around. What changes on scroll is opacity only: the page
   title fades in, a shadow appears, the read line shows.
   ─────────────────────────────────────────── */

.shell { position: relative; max-width: var(--shell); margin-inline: auto; }
.sentinel { height: 1px; margin-bottom: -1px; pointer-events: none; }

.top {
  position: sticky; top: 0; z-index: 6;
  background: transparent; border-bottom: 1px solid transparent;
  transition: background-color .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.is-pinned .top {
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  border-bottom-color: var(--rule); box-shadow: 0 14px 24px -20px var(--shadow);
  backdrop-filter: blur(16px);
}
.top__in {
  display: flex; align-items: center; gap: 1rem;
  height: 68px; padding-inline: var(--gutter);
}
.mark { display: flex; color: var(--ink); flex: 0 0 auto; }
.mark svg { width: 56px; height: auto; }
.top__t {
  flex: 0 1 auto; min-width: 0; font: 600 .88rem/1 var(--sans); letter-spacing: -.014em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  opacity: 0; transform: translateY(4px);
  transition: opacity .18s ease, transform .18s ease;
}
.is-pinned .top__t { opacity: 1; transform: none; }

.nv {
  margin-left: auto; flex: 0 0 auto; white-space: nowrap;
  font: 540 .94rem/1 var(--sans); letter-spacing: -.006em; color: var(--nav-off);
}
.nv a { display: inline-block; text-decoration: none; margin-left: .7rem; padding: .55rem .28rem; }
.nv a:hover { color: var(--nav-hover); }
.nv a.on { color: var(--ink); }
.nv a.on::after {
  content: ''; display: inline-block; width: 4px; height: 4px; border-radius: 50%;
  background: var(--accent); margin-left: .38rem; vertical-align: .16em;
}
/* The dot alone is easy to miss at this size. A short underline joins it and
   reads as "you are here" without adding another shape to the header. */
.nv a.on { position: relative; }
.nv a.on::before {
  content: ''; position: absolute; left: .28rem; right: .28rem; bottom: .3rem;
  height: 1.5px; border-radius: 1px; background: var(--accent);
}
/* the focus ring must still outrank the underline — keyboard users first */
.nv a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The switch belongs to the nav rather than sitting on top of it: same idiom
   as the links — inactive recedes, the current one is marked in the accent.
   No box and no fill, so it does not read as a widget bolted to the header.
   A hairline separates it from the links, as rules separate everything here. */
.themeswitch {
  display: inline-flex; align-items: center; gap: 2px; flex: 0 0 auto;
  margin-left: .65rem; padding: 2px; border: 1px solid var(--border);
  border-radius: 999px; background: var(--panel);
}
.themeswitch button {
  display: grid; place-items: center; width: 30px; height: 30px; padding: 0;
  background: none; border: 0; border-radius: 50%; cursor: pointer; color: var(--nav-off);
  transition: color .14s ease, background-color .14s ease;
}
.themeswitch button:hover { color: var(--nav-hover); }
.themeswitch button[aria-pressed="true"] { color: var(--accent); background: var(--wash); }
.themeswitch button:focus-visible { outline-offset: 2px; }
.themeswitch svg { display: block; }

.prog { position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: transparent; opacity: 0;
        transition: opacity .18s ease; }
.is-scrollable.is-pinned .prog { opacity: 1; }
.prog i { display: block; height: 100%; width: 0; background: var(--accent); }

/* The phone header. Off entirely up here, switched on in the narrow block at
   the end of the file, so no desktop rule, measurement or stacking order is
   touched by any of it. */
.mini, .scrim, .menu { display: none; }

.pad { padding: clamp(2.4rem, 5vw, 4.6rem) var(--gutter) 4.5rem; }

.foot {
  display: flex; align-items: center;
  margin: 0 var(--gutter) 2.5rem;
  padding-top: 1.3rem; border-top: 1px solid var(--rule);
  font: 500 .88rem/1 var(--sans);
}
.foot__meta { margin-left: auto; color: var(--dim); font: 400 .82rem/1 var(--sans); }
/* --link, not --accent: clickable text needs the darker rust (6.10:1 on
   paper); --accent is 4.59:1 and belongs on marks, not words. */
.foot__links { display: flex; gap: 1.3rem; flex-wrap: wrap; color: var(--link); }
.foot a { text-decoration: none; }
.foot a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ─────────────────────────────────────────── light theme
   Selected, not flipped: each value is chosen for the light surface and the
   series colours are validated against it separately. `data-theme` wins over
   the OS setting in both directions. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;
    --bg: #f3f1ea; --ink: #1b211d; --body: #262d29; --muted: #49544d;
    --soft: #363f39; --dim: #566159; --rule: #d9d8d0; --panel: #ebe9e2;
    --accent: #b64b35; --wash: rgba(182, 75, 53, .075); --hover: rgba(27, 33, 29, .035);
    --shadow: rgba(55, 50, 42, .18);
    --photo-grade: brightness(1.11) saturate(.70) sepia(.20) contrast(.93);
  --link: #9c3a26; --smoothing: auto;
    --link: #9c3a26; --smoothing: auto;
  --photo-mono: grayscale(1) sepia(.34) saturate(1.3) brightness(1.07) contrast(.95);
    --photo-mono: grayscale(1) sepia(.34) saturate(1.3) brightness(1.07) contrast(.95);
    --fig-ink: #1b211d; --fig-muted: #59645d; --fig-dim: #657068;
    --fig-rule: #d9d8d0; --fig-axis: #c2c4ba; --fig-baseline: #b7bcb5;
    --s1: #2a78d6; --s2: #d95926;
    --nav-off: #657068; --nav-hover: #242c27; --border: #cdd0c8;
  }
}
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f3f1ea; --ink: #1b211d; --body: #262d29; --muted: #49544d;
  --soft: #363f39; --dim: #566159; --rule: #d9d8d0; --panel: #ebe9e2;
  --accent: #b64b35; --wash: rgba(182, 75, 53, .075); --hover: rgba(27, 33, 29, .035);
  --shadow: rgba(55, 50, 42, .18);
  --photo-grade: brightness(1.11) saturate(.70) sepia(.20) contrast(.93);
  --fig-ink: #1b211d; --fig-muted: #59645d; --fig-dim: #657068;
  --fig-rule: #d9d8d0; --fig-axis: #c2c4ba; --fig-baseline: #b7bcb5;
  --s1: #2a78d6; --s2: #d95926;
  --nav-off: #657068; --nav-hover: #242c27; --border: #cdd0c8;
}

/* ─────────────────────────────────────────── inner pages */

.pt {
  /* min() around the clamp: a clamp minimum is a floor in rem, so at 200% text
     this became 75px and a single word could not wrap below its own width. */
  max-width: 18ch; font: 760 min(clamp(2.35rem, 5vw, 3.25rem), 13vw)/.98 var(--sans);
  letter-spacing: -.042em; margin: .55rem 0 0;
  overflow-wrap: break-word;
}
.grp {
  font: 620 .7rem/1 var(--sans); letter-spacing: .17em; text-transform: uppercase;
  color: var(--muted); margin: 2.8rem 0 0;
  padding-bottom: .7rem; border-bottom: 1px solid var(--rule);
}
.fine { color: var(--dim); font-size: .88rem; font-style: italic; margin-top: 1.7rem; }
.gap { color: var(--dim); }


.swr { padding: 1.25rem 0; border-bottom: 1px solid var(--rule); }
.swr:last-of-type { border-bottom: 0; }
.swr .hd { display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap; }
.swr .nm { font: 600 1.15rem/1 var(--sans); letter-spacing: -.018em; text-decoration: none; }
a.nm:hover { color: var(--accent); }
.swr .lg { font: 500 .76rem/1 var(--sans); letter-spacing: .11em; text-transform: uppercase; color: var(--dim); }
.swr .st { margin-left: auto; font: 500 .8rem/1 var(--sans); color: var(--dim); }
.swr .x { font-size: .98rem; line-height: 1.65; color: var(--body); margin: .45rem 0 0; max-width: var(--measure); }
.swr code {
  display: inline-block; margin-top: .55rem;
  font: 400 .84rem/1 var(--mono); color: var(--muted);
  background: var(--panel); border: 1px solid var(--border); padding: .35rem .55rem;
}

.cvr { display: grid; grid-template-columns: var(--col) 1fr; gap: var(--col-gap); padding: .9rem 0; border-bottom: 1px solid var(--rule); }
.cvr .when { font: 500 .78rem/1 var(--mono); color: var(--dim); padding-top: .3rem; }
.cvr .r1 { font: 600 1.04rem/1.35 var(--sans); }
.cvr .r2 { font-size: .94rem; line-height: 1.6; color: var(--soft); margin-top: .2rem; }
.skills { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .75rem; }
.skills span { font: 520 .84rem/1 var(--sans); color: var(--muted); background: var(--panel); border: 1px solid var(--border); border-radius: 999px; padding: .42rem .65rem; }
.dl {
  display: inline-flex; align-items: center; margin-top: 1.35rem;
  font: 650 .75rem/1 var(--sans); letter-spacing: .12em; text-transform: uppercase;
  color: var(--bg); background: var(--accent); border-radius: 999px;
  padding: .78rem 1.05rem; text-decoration: none; transition: transform .16s ease, filter .16s ease;
}
.dl:hover { filter: brightness(1.07); transform: translateY(-1px); }

.post { display: grid; grid-template-columns: var(--col) 1fr; gap: var(--col-gap); padding: 1.15rem 0; border-bottom: 1px solid var(--rule); text-decoration: none; }
.post .dt { font: 500 .78rem/1 var(--mono); color: var(--dim); padding-top: .38rem; }
.post .h { font: 600 1.16rem/1.3 var(--sans); letter-spacing: -.018em; }
.post:hover .h { color: var(--accent); }
.post .x { font-size: .98rem; line-height: 1.65; color: var(--body); margin-top: .4rem; max-width: var(--measure); }
.tags { margin-top: .5rem; display: flex; gap: .45rem; flex-wrap: wrap; }
.tg { font: 600 .66rem/1 var(--sans); letter-spacing: .09em; text-transform: uppercase; color: var(--dim); background: var(--panel); border: 1px solid var(--border); border-radius: 999px; padding: .28rem .5rem; }

/* ─────────────────────────────────────────── article */

.art { max-width: none; }
.art p, .art h2, .art blockquote, .art ul, .art ol { max-width: var(--measure); }
.art h1 { max-width: 20ch; }
.art .kicker { font: 600 .72rem/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; color: var(--dim); margin-top: 1.7rem; }
.art h1 { font: 800 clamp(2rem, 5vw, 2.5rem)/1.05 var(--sans); letter-spacing: -.032em; margin: .6rem 0 0; }
.art .meta { margin: .85rem 0 0; }
.art p { color: var(--body); margin: 1.35rem 0 0;
         font: 400 1.18rem/1.7 var(--serif); }
.art h2 { font: 700 1.3rem/1.3 var(--sans); letter-spacing: -.022em; margin: 2.3rem 0 0; }
.art a { color: var(--accent); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--accent) 38%, transparent); }
.art pre { background: var(--panel); border: 1px solid var(--border); padding: .85rem 1rem; margin: 1.25rem 0 0; overflow-x: auto; }
.art pre code { font: 400 .88rem/1.7 var(--mono); color: var(--muted); }
.art blockquote { margin: 1.5rem 0 0; padding-left: 1rem; border-left: 2px solid var(--accent);
                  color: var(--ink); font: 400 1.22rem/1.6 var(--serif); }
.art figure { margin: 2.1rem 0 0; }
.art figcaption { font-size: .9rem; line-height: 1.65; color: var(--dim); margin-top: .8rem; }
.art svg { display: block; width: 100%; height: auto; }

/* ─────────────────────────────────────────── motion
   The phone layout lives at the foot of this file — see "narrow". Overrides
   have to come after the rules they override, and several of those are defined
   further down. */

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

@media print {
  .top, .foot { display: none; }
  body { background: #fff; color: #000; }
}

/* ─────────────────────────────────────────── news */

.nwl { margin-top: .3rem; }
.nw { display: grid; grid-template-columns: var(--col) 1fr; gap: var(--col-gap); padding: .95rem 0; border-bottom: 1px solid var(--rule); }
.nw:last-of-type { border-bottom: 0; }
.nw .d { font: 500 .78rem/1.5 var(--mono); color: var(--dim); letter-spacing: .04em; }
.nw .tx { font-size: 1rem; line-height: 1.68; color: var(--body); }
.nw .tx em { color: var(--ink); font-style: italic; }
.nw .go { color: var(--accent); text-decoration: none; font-weight: 600; }
.nw--tight { padding: .6rem 0; }
.k {
  display: inline-block; margin-right: .55rem; vertical-align: 1px;
  font: 600 .66rem/1 var(--sans); letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim); border: 1px solid var(--border); padding: .2rem .4rem;
}
.k--paper, .k--preprint { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* ─────────────────────────────────────────── figures */

/* the drawing is authored at 860; never let it scale above 1:1 or its type
   outgrows the body copy */
.fig { margin: 2.3rem 0 0; max-width: 860px; }
.fig .ftop { display: flex; align-items: baseline; gap: .65rem; flex-wrap: wrap; margin-bottom: .8rem; }
.fig .fno { font: 600 .7rem/1 var(--sans); letter-spacing: .16em; text-transform: uppercase; color: var(--accent); }
.fig .fttl { font: 600 1rem/1.3 var(--sans); letter-spacing: -.012em; }
.fig .chip {
  margin-left: auto; font: 600 .66rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--soft); border: 1px solid var(--border); padding: .22rem .45rem;
}
.fig .legend { display: flex; gap: 1.1rem; margin-bottom: .7rem; font: 500 .84rem/1 var(--sans); color: var(--body); }
.fig .legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: .35rem; vertical-align: -1px; }
.tv { margin-top: .6rem; }
.tv summary {
  font: 600 .7rem/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); cursor: pointer;
}
.tv summary:hover { color: var(--ink); }
.tv table { border-collapse: collapse; margin-top: .7rem; font: 400 .86rem/1 var(--mono); color: var(--body); }
.tv th, .tv td { text-align: right; padding: .35rem .9rem .35rem 0; border-bottom: 1px solid var(--rule); font-variant-numeric: tabular-nums; }
.tv th:first-child, .tv td:first-child { text-align: left; }
.tv th { color: var(--dim); font-weight: 500; }


/* ─────────────────────────────────────────── rich article elements */

/* tables — each one scrolls inside its own box, so a wide table never sets the
   width of the page. `width: auto` lets it shrink to fit when it can. */
.tw { overflow-x: auto; max-width: 860px; margin: 1.8rem 0 0; }
.tw table { margin: 0; width: auto; min-width: 100%; }
.art table { border-collapse: collapse; width: 100%; max-width: 860px; margin: 1.8rem 0 0; font-size: .95rem; }
.art th, .art td { padding: .6rem .9rem .6rem 0; border-bottom: 1px solid var(--rule); text-align: left; font-variant-numeric: tabular-nums; }
.art thead th { font: 500 .78rem/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--dim); padding-bottom: .7rem; }
.art tbody tr:last-child td { border-bottom: 0; }
.art td[style*="right"], .art th[style*="right"] { padding-right: 0; }
.art tbody tr:hover td { background: var(--panel); }

/* Mono is for things that are typed, named or measured — code, filenames,
   accessions, keys, and numeric table columns. Prose is serif, structure is
   sans; anything a machine would read is mono. */
.art :not(pre) > code {
  font: 400 .88em/1.4 var(--mono);
  background: var(--panel); border: 1px solid var(--border);
  padding: .1em .34em; border-radius: 2px;
  overflow-wrap: break-word;
}
.art a > code { color: inherit; }
.art kbd {
  font: 500 .82em/1 var(--mono);
  background: var(--panel); border: 1px solid var(--border);
  border-bottom-width: 2px; border-radius: 3px; padding: .2em .42em;
  white-space: nowrap;
}
.art samp, .art var { font-family: var(--mono); font-style: normal; font-size: .9em; }
/* right-aligned columns are the numeric ones; mono keeps the digits in step.
   markdown-it writes these as an inline style rather than an align attribute. */
.art td[style*="right"] { font: 400 .94rem/1.5 var(--mono); }
.art dt { font-family: var(--sans); }
.art dt code { font-size: .92em; }

/* equations — MathML, rendered at build time, no runtime typesetter */
.eqn { margin: 1.6rem 0 0; overflow-x: auto; padding: .3rem 0; }
.eqn math { font-size: 1.25rem; color: var(--ink); }
/* MathML resolves to the `math` generic family, and a machine without a math
   font installed drops straight to tofu on Greek. Name real fallbacks after it
   so an alpha always has somewhere to land. */
.eqn math, .eqn-i math { font-family: math, Newsreader, Georgia, "DejaVu Serif", serif; }
.eqn-i math { font-size: 1.02em; color: var(--ink); }
.tex-error { color: var(--accent); }

/* callouts */
.cal { margin: 1.7rem 0 0; max-width: var(--measure); padding: .95rem 1.1rem; background: var(--panel); border-left: 3px solid var(--dim); }
.cal__k { font: 600 .68rem/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; color: var(--dim); margin-bottom: .5rem; }
.cal__b > :first-child { margin-top: 0; }
.cal__b p { font: 400 1.04rem/1.65 var(--serif); margin-top: .7rem; }
.cal--note { border-left-color: var(--s1); }
.cal--note .cal__k { color: var(--s1); }
.cal--warn { border-left-color: var(--accent); }
.cal--warn .cal__k { color: var(--accent); }
.cal--aside { border-left-color: var(--dim); font-style: italic; }

/* code blocks with a copy button */
.code { margin: 1.4rem 0 0; max-width: 860px; border: 1px solid var(--border); background: var(--panel); }
.code__bar { display: flex; align-items: center; gap: .8rem; padding: .45rem .8rem; border-bottom: 1px solid var(--border); }
.code__t { font: 400 .78rem/1 var(--mono); letter-spacing: .08em; color: var(--dim); }
.code__c {
  margin-left: auto; cursor: pointer; background: none; color: var(--dim);
  border: 1px solid var(--border); padding: .25rem .5rem;
  font: 600 .68rem/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
}
.code__c:hover { color: var(--ink); border-color: var(--ink); }
.code__c.ok { color: var(--bg); background: var(--accent); border-color: var(--accent); }
.code pre { margin: 0; border: 0; background: none; padding: .9rem 1rem; overflow-x: auto; }

/* images */
.img { margin: 2rem 0 0; max-width: 860px; }
.img img { width: 100%; height: auto; display: block; border: 1px solid var(--rule); }
.img--pair { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; }
.img--pair figcaption { grid-column: 1 / -1; }

/* gallery + lightbox */
.gal { margin: 2rem 0 0; max-width: 860px; }
.gal__g { display: grid; grid-template-columns: repeat(var(--cols, 3), 1fr); gap: .7rem; }
.gal__i { padding: 0; border: 1px solid var(--rule); background: none; cursor: zoom-in; display: block; overflow: hidden; }
.gal__i img { width: 100%; height: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; transition: transform .25s cubic-bezier(.4, 0, .2, 1); }
.gal__i:hover img { transform: scale(1.04); }
.gal__i:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.lb { position: fixed; inset: 0; z-index: 40; display: none; place-items: center; padding: 3vmin;
      background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: blur(3px); }
.lb[open] { display: grid; }
.lb__f { max-width: min(1200px, 94vw); max-height: 88vh; margin: 0; }
.lb__f img { max-width: 100%; max-height: 78vh; display: block; margin-inline: auto; border: 1px solid var(--rule); }
.lb__c { margin-top: .8rem; text-align: center; font-size: .9rem; color: var(--muted); }
.lb__x { position: absolute; top: 1.2rem; right: 1.4rem; background: none; border: 1px solid var(--border);
         color: var(--muted); cursor: pointer; font: 500 .78rem/1 var(--mono); padding: .45rem .6rem; letter-spacing: .1em; }
.lb__x:hover { color: var(--ink); border-color: var(--ink); }
.lb__nav { position: absolute; inset-inline: 1.2rem; top: 50%; display: flex; justify-content: space-between; pointer-events: none; }
.lb__nav button { pointer-events: auto; background: none; border: 1px solid var(--border); color: var(--muted);
                  cursor: pointer; font: 500 1rem/1 var(--mono); padding: .6rem .75rem; }
.lb__nav button:hover { color: var(--ink); border-color: var(--ink); }

/* footnotes */
.footnotes { max-width: var(--measure); margin-top: 2.6rem; padding-top: 1.2rem; border-top: 1px solid var(--rule); font-size: .92rem; color: var(--soft); }
.footnotes ol { padding-left: 1.1rem; }
.footnotes li { margin-bottom: .5rem; }
.footnote-ref a { color: var(--accent); text-decoration: none; font-size: .8em; vertical-align: super; }
.footnote-backref { color: var(--accent); text-decoration: none; }

/* definition lists */
.art dl { max-width: var(--measure); margin: 1.6rem 0 0; }
.art dt { font: 600 1rem/1.4 var(--sans); margin-top: 1rem; }
.art dd { margin: .3rem 0 0; color: var(--soft); font: 400 1.04rem/1.65 var(--serif); }

/* collapsibles */
.art details { max-width: var(--measure); margin: 1.6rem 0 0; border: 1px solid var(--border); background: var(--panel); }
.art details > summary { padding: .8rem 1rem; cursor: pointer; font: 600 .95rem/1.3 var(--sans); list-style: none; }
.art details > summary::-webkit-details-marker { display: none; }
.art details > summary::before { content: '+'; display: inline-block; width: 1.1em; color: var(--accent); font-family: var(--mono); }
.art details[open] > summary::before { content: '–'; }
.art details[open] > summary { border-bottom: 1px solid var(--border); }
.art details > :not(summary) { margin-inline: 1rem; }
.art details > :last-child { margin-bottom: 1rem; }
.art details .tv, .fig details { border: 0; background: none; margin-inline: 0; }
.art .fig details > summary { padding: 0; font: 500 .76rem/1 var(--mono); }
.art .fig details > summary::before { content: ''; width: 0; }

@media (max-width: 720px) {
  .img--pair, .gal__g { grid-template-columns: 1fr 1fr; }
  .art table { font-size: .88rem; }
}

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

/* The UA rule for [hidden] is a bare `display:none` and loses to any author
   rule that sets display. Anything hidden here must stay hidden. */
[hidden] { display: none !important; }

/* the post's date and tags: a value and a set of chips, not a run-on list */
.art .meta { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.art .meta .when { font: 400 .8rem/1 var(--mono); color: var(--dim); letter-spacing: .02em; }
.art .meta .tags { display: flex; gap: .4rem; }

/* ─────────────────────────────────────────── contact

   The footer email opens this instead of firing a mail client. The address
   itself is assembled by script — it is not in the HTML — so the link is hidden
   until that has run. */

.rx {
  width: min(30rem, calc(100vw - 2.5rem)); padding: 1.9rem 1.9rem 1.7rem;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 12px; box-shadow: 0 24px 60px -28px rgba(0, 0, 0, .8);
}
.rx::backdrop { background: color-mix(in srgb, var(--bg) 82%, transparent); backdrop-filter: blur(3px); }
.rx__x {
  position: absolute; top: .7rem; right: .85rem; padding: .2rem .3rem;
  background: none; border: 0; cursor: pointer; color: var(--dim);
  font: 400 1.4rem/1 var(--sans);
}
.rx__x:hover { color: var(--ink); }
.rx__h { font: 700 1.5rem/1.1 var(--sans); letter-spacing: -.025em; margin: 0; }
.rx__p { font: 400 1.02rem/1.6 var(--serif); color: var(--muted); margin: .7rem 0 0; }

.rx__field {
  display: flex; align-items: center; gap: .7rem; margin-top: 1.3rem;
  border: 1px solid var(--border); border-radius: 7px; background: var(--panel); padding: .7rem .8rem;
}
.rx__field code {
  flex: 1 1 auto; min-width: 0; overflow-x: auto; white-space: nowrap;
  font: 400 .95rem/1.4 var(--mono); background: none; border: 0; padding: 0; color: var(--ink);
}
.rx__copy {
  flex: 0 0 auto; cursor: pointer; background: none; color: var(--dim);
  border: 1px solid var(--border); padding: .3rem .5rem;
  font: 600 .68rem/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
}
.rx__copy:hover { color: var(--ink); border-color: var(--ink); }
.rx__copy.ok { background: var(--accent); border-color: var(--accent); color: var(--bg); }

.rx__send {
  display: inline-flex; align-items: center; gap: .55rem; margin-top: 1.1rem;
  padding: .75rem 1rem; border-radius: 999px; text-decoration: none;
  background: var(--accent); color: var(--bg);
  font: 600 .82rem/1 var(--sans); letter-spacing: .03em;
}
.rx__send:hover { filter: brightness(1.08); }
.rx__send:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ── the portrait ─────────────────────────────────────────────────────────
   Shared by the front page and the bio. The image is graded toward neutral in
   the file; the circle and the toning do the rest — see below. */
.frame { position: relative; display: block; }
.frame img { display: block; width: 100%; height: auto; filter: var(--photo-grade); }

/* ── the framed portrait ──────────────────────────────────────────────────
   A different answer to the same problem, used by /preview/4/.

   Feathering the edge hides where the photograph stops, but not what is inside
   it: a cool grey studio backdrop still rings the head, and on warm paper that
   ring is the thing that reads wrong. No amount of edge treatment fixes a
   temperature clash in the middle of the image.

   Two moves instead. The circle crops most of the backdrop away and turns what
   is left into a deliberate shape rather than a rectangle that failed to blend.
   And the toning takes the colour out of the argument entirely — greyscale,
   then warmed back toward the page — so there is no cool grey left to clash.
   The rings are hairlines with a gap of page colour between them, the same
   mat-and-frame idiom the rules elsewhere already use. */
.frame--round { border-radius: 50%; overflow: hidden; }
.frame--round img {
  aspect-ratio: 1; object-fit: cover; object-position: 50% 34%;
  transform: scale(1.1);
  filter: var(--photo-mono);
}
.frame--round::after {
  border-radius: 50%;
  background: none;                       /* the circle is the edge now */
  box-shadow: inset 0 0 0 1px var(--border);
}
/* The ring, shared by every portrait on the site. The figure's own padding is
   what reserves room for it, so the rings sit inside the grid track instead of
   overhanging it. Sized down for the small portrait in the third layout, where
   an 8px band around a 6rem circle would be a frame around a stamp. */
/* The ring. The figure's own padding reserves room for it, so it sits inside
   the grid track instead of overhanging. */
.hero__pic .frame, .about__pic .frame {
  border-radius: 50%;
  box-shadow: 0 0 0 8px var(--bg), 0 0 0 9px var(--rule);
}

/* ── the bio page ─────────────────────────────────────────────────────────
   Two columns for the whole article. The portrait hangs in the left margin;
   the name, intro, position line, rule and every section share one text column
   with a single left edge. The column is capped rather than the paragraphs, so
   the rule stays the width of the prose it closes — set on the article it
   would run past the text and read as a table header.

   The section heads carry no rule. Each one used to sit closer to its rule
   than to its own first line, so the heading was bound upward instead of down
   to the text it introduces, and each rule ran the full shell width over a
   column half that wide. Nothing replaced them: the heading is separated by
   size, weight and ink, the sections by space alone — none inside, a great
   deal between. */
.about {
  display: grid; grid-template-columns: 12rem minmax(0, 1fr);
  align-items: start; gap: 0 clamp(1.8rem, 3.5vw, 3rem);
  /* the article's own cap governs the grid before the column's does:
     12rem portrait + 2.8rem gap + 38rem column */
  max-width: 53rem;
  margin-inline-start: clamp(0px, 2.2vw, 28px);
}
.about__pic { grid-column: 1; margin: 0; padding: 8px; }
.about__pic .frame {
  border-radius: 50%;
  box-shadow: 0 0 0 7px var(--bg), 0 0 0 8px var(--rule);
}
/* one measure for everything in the column, the rule included */
.about__col { grid-column: 2; min-width: 0; max-width: 38rem; }

.about__head {
  padding-bottom: clamp(1.5rem, 3.5vw, 2.1rem);
  border-bottom: 1px solid var(--rule);
}
.about__name {
  margin: 0; color: var(--ink);
  font: 380 clamp(2.1rem, 4.2vw, 3rem)/1.08 var(--serif); letter-spacing: -.018em;
}
/* the second-most-read text on the page; --muted recedes further than its job
   warrants, so it takes --soft */
.about__sub {
  margin: 1.05rem 0 0; color: var(--soft);
  font: italic 400 clamp(1.06rem, 1.6vw, 1.18rem)/1.66 var(--serif);
  -webkit-font-smoothing: var(--smoothing);
}
.about__meta {
  margin: 1.15rem 0 0; color: var(--dim);
  font: 560 .7rem/1.6 var(--sans); letter-spacing: .14em; text-transform: uppercase;
}

/* a crosshead, not a label: sentence case in the body face, bound to the
   paragraph beneath it. Uppercase letterspaced sans has no ascender profile,
   so the eye spells it rather than recognising it — right for the metadata
   above, wrong for introducing prose. */
.about__h {
  margin: 0 0 .5rem; color: var(--ink);
  font: 600 clamp(1.3rem, 1.7vw, 1.44rem)/1.3 var(--serif); letter-spacing: -.006em;
}

.about__s:first-of-type { margin-top: clamp(2rem, 4vw, 2.7rem); }
.about__s + .about__s { margin-top: clamp(2.9rem, 5.2vw, 3.95rem); }

/* The measure is in em, not ch. `ch` is the advance of `0`; when the serif was
   a broken subset with no `0` the browser fell back to 0.5em, and `64ch` meant
   32em — about 78 characters rather than the 64 it appears to say. Counted on
   screen, this lands at 67. */
.about__s p {
  margin: 0; color: var(--body);
  font: 400 1.265rem/1.62 var(--serif);
  -webkit-font-smoothing: var(--smoothing);
  text-wrap: pretty;
}
/* An indent and a gap solve the same problem, so the page gets one of them.
   The indent was right while crossheads parted the sections; now that the page
   runs as one continuous piece it is the only paragraph signal in 2000px of
   scroll, which on screen reads as a wall. Just under two-thirds of a line:
   enough to separate, not so much that the paragraphs stop being one piece. */
.about__s p + p { margin-top: 1em; }

/* ── the front page ──────────────────────────────────────────────────────────
   Identity, then what the work is, then the evidence, then what is current.

   One spacing scale governs the page — 4 8 12 16 24 32 48 64 80 96 — rather
   than a value invented per section. The `home` body class carries the two
   measurements that differ from the rest of the site. */
.home .shell { max-width: 1100px; }
.home .pad { padding-top: clamp(var(--s12), 7vw, var(--s24)); }

/* hero. No min-height:100vh — on a 1366x768 laptop that would push the work
   entirely below the fold, and the work is the point of the page. */
.hero {
  display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: center; gap: var(--s8) clamp(var(--s12), 5vw, 72px);
}
/* down ~6%: the portrait had marginally more weight than the type it sits with */
.hero__pic { margin: 0; width: min(clamp(15rem, 27vw, 20.5rem), 58vw); padding: 9px; }
.hero__pic .frame {
  border-radius: 50%;
  box-shadow: 0 0 0 8px var(--bg), 0 0 0 9px var(--rule);
}
.name {
  margin: 0; color: var(--ink);
  font: 780 clamp(2.8rem, 4.6vw, 4rem)/.98 var(--sans); letter-spacing: -.045em;
}
.hero__say { min-width: 0; max-width: 100%; }
.lede {
  margin: var(--s6) 0 0; max-width: min(38rem, 100%); color: var(--body);
  font: 400 clamp(1.2rem, 1.7vw, 1.45rem)/1.48 var(--serif);
  text-wrap: pretty;
}
.role {
  margin: var(--s6) 0 0; color: var(--muted);
  font: 500 .88rem/1.7 var(--sans); letter-spacing: .01em;
}
.role--sub { margin-top: 2px; color: var(--dim); }
.role__sep { color: var(--dim); }
.role a { color: var(--muted); text-decoration: none; border-bottom: 1px solid var(--border); }
.role a:hover { color: var(--ink); border-bottom-color: var(--accent); }

/* a text link, not a button — but unmistakably a link */
.go {
  display: inline-flex; align-items: center; flex-wrap: wrap; gap: .4rem;
  max-width: 100%;
  margin-top: var(--s6); min-height: 42px;
  font: 500 .95rem/1 var(--sans); color: var(--link); text-decoration: none;
}
.go i { font-style: normal; transition: transform .16s ease; }
.go:hover { text-decoration: underline; text-underline-offset: 4px; }
.go:hover i { transform: translateX(2px); }

/* section label: heading, rule, and a destination on the same line */
/* one gap between blocks, not two stacked paddings: the hero's own bottom
   padding plus this one came to ~150px where 64-80 was wanted */
.sec { padding-top: clamp(var(--s16), 5.5vw, var(--s20)); }
.lab {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s4);
  margin: 0 0 var(--s6);
  font: 620 .72rem/1 var(--sans); letter-spacing: .17em; text-transform: uppercase;
  color: var(--muted);
}
.lab__rule { flex: 1 1 2rem; min-width: 1rem; height: 1px; background: var(--rule); }
.lab__more {
  display: inline-flex; align-items: center; gap: .35rem; min-height: 42px;
  font: 620 .72rem/1 var(--sans); letter-spacing: .12em; color: var(--link);
  text-decoration: none; text-transform: none;
}
.lab__more i { font-style: normal; transition: transform .16s ease; }
.lab__more:hover { text-decoration: underline; text-underline-offset: 4px; }
.lab__more:hover i { transform: translateX(2px); }

/* the work: a 2x2 editorial grid, open. A rule above each block rather than a
   border around it — no cards, no shadows, no radius, no fills. */
.work {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(var(--s8), 5vw, 72px);
}
.w > a {
  display: block; height: 100%; padding: var(--s6) 0 var(--s8);
  border-top: 1px solid var(--rule); text-decoration: none;
  transition: border-color .16s ease;
}
.w > a:hover { border-top-color: var(--border); }
.w__t {
  display: block; color: var(--ink);
  font: 630 1.28rem/1.25 var(--sans); letter-spacing: -.025em;
  transition: color .14s ease;
}
.w__d {
  display: block; margin-top: .5rem; max-width: 42ch; color: var(--body);
  font: 400 1rem/1.6 var(--sans);
}
.w__m {
  display: block; margin-top: var(--s3); color: var(--dim);
  font: 500 .82rem/1 var(--sans); letter-spacing: .01em;
}
.w__a { font-style: normal; display: inline-block; transition: transform .16s ease; }
.w > a:hover .w__t { color: var(--link); }
.w > a:hover .w__m { color: var(--muted); }
.w > a:hover .w__a { transform: translateX(2px); }

/* updates: the whole row is the target, not just the title */
.upd { list-style: none; margin: 0; padding: 0; }
.u > a, .u > div {
  display: grid; grid-template-columns: 11rem minmax(0, 1fr); gap: var(--s4);
  padding: var(--s4) 0; border-top: 1px solid var(--rule);
  text-decoration: none; transition: background-color .14s ease, border-color .14s ease;
}
.u > a { margin-inline: -.6rem; padding-inline: .6rem; }
.u > a:hover { background: var(--hover); border-top-color: var(--border); }
.u:last-child > a, .u:last-child > div { border-bottom: 1px solid var(--rule); }
.u__d { font: 500 .78rem/1.6 var(--mono); letter-spacing: .04em; color: var(--dim); }
.u__d b { color: var(--link); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
.u__t { color: var(--body); font: 400 1rem/1.62 var(--sans); }
.u__t em { color: var(--ink); font-style: italic; }
.u__a { font-style: normal; color: var(--dim); display: inline-block; transition: transform .16s ease, color .14s ease; }
.u > a:hover .u__a { transform: translateX(2px); color: var(--link); }

/* ── header, scoped to this page by the body class ───────────────────────── */
/* current page carries weight as well as the accent mark — colour must never
   be the only signal */
.nv a.on { font-weight: 640; }
/* the row stays visually delicate while every link gets a real hit area */
.nv a { display: inline-flex; align-items: center; min-height: 42px; padding-inline: 4px; }
/* both theme positions read the same when selected; the sun was louder */
.themeswitch button[aria-pressed="true"] {
  color: var(--accent); background: var(--wash); box-shadow: inset 0 0 0 1px var(--border);
}
.foot__links { gap: var(--s4) var(--s6); }
.foot a { min-height: 42px; display: inline-flex; align-items: center; }

/* ── the reviewed mobile layout, declared rather than inherited ──────────── */
@media (max-width: 760px) {
  .hero { grid-template-columns: minmax(0, 1fr); text-align: center; gap: var(--s6); }
  .hero__pic { margin-inline: auto; }
  .hero__pic { width: min(11rem, 58vw); }
  /* min() around the clamp, not just the clamp. A clamp minimum is a floor in
     rem, so at 200% text it became 83px and "Abuelanin" — one word — could not
     wrap below its own width, forcing a horizontal scrollbar at 320px. The vw
     cap cannot outgrow the viewport. At normal text size this is identical:
     12vw on a 390px phone is the 46.8px it already was. */
  .name { font-size: min(clamp(2.6rem, 12vw, 3rem), 12vw); }
  .lede, .role { text-align: center; }
  /* The non-breaking hyphen in "chromosome-scale" keeps that compound on one
     line, which is what it is for — but at 200% text it becomes a single
     ~300px token that cannot wrap, and it was the last thing forcing a
     horizontal scrollbar at 320px. break-word only engages when a word truly
     will not fit, so normal rendering is untouched; the vw cap stops the type
     outgrowing the viewport in the first place. */
  .lede { font-size: min(1.18rem, 6vw); overflow-wrap: break-word; }
  .go { margin-inline: auto; }
  .work { grid-template-columns: 1fr; }
  /* the date moves above its title rather than squeezing a two-column row */
  .u > a, .u > div { grid-template-columns: 1fr; gap: 6px; }
  .foot__meta { margin-left: 0; width: 100%; }
}

/* ── front page: interaction and contrast ──────────────────────────────────────────────────────────
   A second review pass over /preview/4/. Everything here is state, contrast
   and interaction; the architecture is untouched. */

/* the whole block is the target, and the focus ring frames all of it rather
   than just the line that happens to hold the anchor */
.w > a { position: relative; cursor: pointer; }
.w > a:focus-visible { outline: 2px solid var(--link); outline-offset: 6px; border-radius: 2px; }
.u > a:focus-visible { outline: 2px solid var(--link); outline-offset: 3px; border-radius: 2px; }
.w > a:hover { border-top-color: var(--muted); }

/* institution links: neutral by default with a quiet accent underline, orange
   only on hover — two permanently orange institutions would compete with the
   one call to action in this block */
.role__link {
  color: var(--muted); text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  transition: color .14s ease, border-color .14s ease;
}
.role__link:hover { color: var(--link); border-bottom-color: var(--link); }
.role__link:focus-visible { outline: 2px solid var(--link); outline-offset: 3px; border-radius: 2px; }

/* one construction for both themes: tinted circular fill plus accent icon,
   inside a neutral hairline container. The light sun previously carried a ring
   the dark moon did not, so they read as two different components. */
.themeswitch { border-color: var(--border); background: transparent; }
.themeswitch button { width: 32px; height: 32px; }
.themeswitch button[aria-pressed="true"] {
  color: var(--accent); background: var(--wash); box-shadow: none;
}

/* the current page: weight and a mark, never colour alone */
.nv a.on { font-weight: 660; color: var(--ink); }

/* ── front page: final polish ──────────────────────────────────────────────────────────
   Polish over /preview/5/. The accent text is deliberately unchanged: the
   flagged links sample at #9c3a26 on #f3f1ea, which is 6.10:1. The suggested
   #b64b35 measures 4.59:1, so "darkening" them would have cut contrast by a
   quarter. */

/* three signals for the current page — weight, dot and underline — is one more
   than a page this quiet needs. The dot goes with the eyes; the weight is the
   non-colour signal WCAG asks for. The underline is the one to drop. */
.nv a.on::before { content: none; }
/* it returns on hover, where it is an affordance rather than a state */
.nv a:hover { text-decoration: underline; text-underline-offset: 6px;
                  text-decoration-color: color-mix(in srgb, var(--accent) 55%, transparent); }

/* the bio link belonged to the block above it, not to the space below */
.go { margin-top: 14px; }

/* One column, and the portrait moves above the name: at this width a photograph
   beside a 3rem name leaves neither enough room. It shrinks to a token rather
   than stretching edge to edge, which would make the page a profile card. */
@media (max-width: 760px) {
  .ph, .ph2 { grid-template-columns: 1fr; gap: 1.5rem; align-items: start; }
  .ph__pic .pcap { text-align: left; margin-top: 1.1rem; }

  .about__top { grid-template-columns: 1fr; gap: 1.2rem; }
  /* the margin column collapses; the portrait goes on top */
  /* These targeted .bio3/.bio4/.bio5 — the class names the page carried while
     it was still a preview. Consolidating to .about left them matching nothing,
     so the two-column grid survived onto phones and squeezed the text column
     to 52px at 320. The margin column collapses; the portrait goes on top. */
  .about { grid-template-columns: 1fr; gap: 1.3rem; margin-inline-start: 0; }
  .about__pic { grid-column: 1; width: min(7rem, 40vw); }
  .about__col { grid-column: 1; max-width: none; }
  /* a clamp minimum is a floor in rem, so at 200% text the name could not wrap
     below its own width; and the non-breaking hyphen in "chromosome-scale"
     makes one ~300px token. Both capped against the viewport instead. */
  .about__name { font-size: min(clamp(2.1rem, 4.2vw, 3rem), 11vw); }
  .about__sub, .about__s p { overflow-wrap: break-word; }
  .about__pic { width: 7rem; }
  /* two lines of titles, then ellipsis — enough to show what is inside without
     the shut state growing taller than the section it stands for */
}

/* ── refinements ──────────────────────────────────────────────────────────
   The accent is deliberately NOT changed here. The links measure #9c3a26 —
   6.10:1 on paper — and the #b64b35 proposed in review measures 4.59:1, so
   adopting it would have cut contrast by a quarter to fix a paleness that was
   never on the page. The pale #f0907a it was mistaken for is the dark-theme
   value and never renders in light. */

/* The read line: a hairline, not a bar. Three passes to land it. The accent at
   2px was a fifth use of the orange; --muted at 2px read as a long dark
   underline beneath the header; --border at 1px measured 1.38 against the light
   background and 1.62 against the dark, which is below the point where a line
   reads as a line at all. Thinness was the fix, not colour, so the height stays
   halved and the base rule's accent is left alone. */
.prog { height: 1px; }

/* Anchors and focused elements were sliding under the sticky bar. */
html { scroll-padding-top: calc(var(--mini-h) + 1rem); }
:target, :focus-visible { scroll-margin-top: calc(var(--mini-h) + 1rem); }

/* An external arrow that wraps onto its own line reads as a stray character;
   below 360px the row cannot spare the width for it at all. */
.w__a, .u__a { white-space: nowrap; }
@media (max-width: 360px) { .w__a, .u__a { display: none; } }

/* ── /preview/publications/ ───────────────────────────────────────────────
   Same record, same order, same editorial character. What changes is that
   every entry has one shape, the title is the thing you click, and the author
   line stops carrying the journal at its end. */

.pvh { padding-bottom: var(--s6); }
.pvh__sub {
  margin: .7rem 0 0; max-width: 60ch; color: var(--body);
  font: 400 1.02rem/1.6 var(--sans);
}
.pvh__util {
  display: flex; flex-wrap: wrap; gap: .4rem var(--s6); margin: var(--s4) 0 0;
  font: 500 .86rem/1 var(--sans);
}
.pvh__util a {
  display: inline-flex; align-items: center; gap: .3rem; min-height: 42px;
  color: var(--link); text-decoration: none;
}
.pvh__util i { font-style: normal; transition: transform .16s ease; }
.pvh__util a:hover { text-decoration: underline; text-underline-offset: 4px; }
.pvh__util a:hover i { transform: translate(1px, -1px); }

.pvg + .pvg { margin-top: var(--s8); }

.pv2 {
  display: grid; grid-template-columns: var(--col) minmax(0, 1fr); gap: var(--col-gap);
  padding: var(--s6) 0; border-top: 1px solid var(--rule);
}
.pv2__y { font: 500 .78rem/1.5 var(--mono); letter-spacing: .04em; color: var(--dim); }

/* the title carries the link, and the arrow only appears once there is
   somewhere to go */
.pv2__t { margin: 0; font: 590 1.08rem/1.45 var(--sans); letter-spacing: -.015em;
          color: var(--ink); overflow-wrap: break-word; }
.pv2__t a { color: inherit; text-decoration: none; }
.pv2__t a:hover { color: var(--link); }
.pv2__t a:focus-visible { outline: 2px solid var(--link); outline-offset: 4px; border-radius: 2px; }
.pv2__x { font-style: normal; margin-left: .35rem; color: var(--dim); display: inline-block;
          transition: transform .16s ease, color .14s ease; }
.pv2__t a:hover .pv2__x { color: var(--link); transform: translate(2px, -2px); }

/* Author line and venue separated. The journal used to sit at the end of a
   fifteen-name author string, which is exactly where the eye stops looking. */
.pv2__au {
  margin: .45rem 0 0; max-width: 90ch; color: var(--soft);
  font: 400 .94rem/1.65 var(--sans);
}
/* the name is emphasis, not a link: an underline in the accent said "click me"
   about a person's own name */
.pv2__au .me { font-weight: 640; color: var(--ink); border: 0; text-decoration: none; }
.pv2__v { margin: .2rem 0 0; max-width: 90ch; color: var(--muted); font: 400 .94rem/1.6 var(--sans); }
.pv2__art { color: var(--dim); }

/* one action row per entry, whatever resources happen to exist */
.pv2__act { display: flex; flex-wrap: wrap; gap: .2rem var(--s4); margin: .7rem 0 0; }
.pv2__act a {
  display: inline-flex; align-items: center; gap: .28rem; min-height: 40px;
  font: 500 .84rem/1 var(--sans); color: var(--link); text-decoration: none;
}
.pv2__act i { font-style: normal; transition: transform .16s ease; }
.pv2__act a:hover { text-decoration: underline; text-underline-offset: 4px; }
.pv2__act a:hover i { transform: translateX(2px); }
.pv2__act a:focus-visible { outline: 2px solid var(--link); outline-offset: 3px; border-radius: 2px; }

@media (max-width: 760px) {
  /* the year moves above the title rather than holding a rail of its own */
  .pv2 { grid-template-columns: 1fr; gap: .35rem; padding: var(--s6) 0; }
  .pv2__t { font-size: 1.12rem; }
  .pv2__au, .pv2__v { font-size: .95rem; }
  .pvh__util { gap: .2rem var(--s4); }
}

/* ── 404 ──────────────────────────────────────────────────────────────────
   The code carries the page. The first version led with the joke and set
   "404" in 11px of dim mono, which made the reader work out what had gone
   wrong — the one thing an error page must not do. Playful, but second. */
.nf { min-height: 46vh; display: grid; align-content: center; max-width: 34rem; }
.nf__code {
  margin: 0; color: var(--ink); line-height: .85;
  font: 780 min(clamp(4rem, 12vw, 7rem), 22vw) var(--sans); letter-spacing: -.055em;
}
.nf__h {
  margin: var(--s4) 0 0; color: var(--ink);
  font: 620 clamp(1.15rem, 2vw, 1.4rem)/1.2 var(--sans); letter-spacing: -.02em;
}
/* the one flourish, and it recedes */
.nf__joke {
  margin: .5rem 0 0; color: var(--muted);
  font: italic 400 clamp(1rem, 1.6vw, 1.1rem)/1.5 var(--serif);
  -webkit-font-smoothing: var(--smoothing);
}
.nf__back {
  display: inline-flex; align-items: center; gap: .35rem; width: fit-content;
  margin-top: var(--s6); min-height: 42px;
  font: 500 .92rem/1 var(--sans); color: var(--link); text-decoration: none;
}
.nf__back i { font-style: normal; transition: transform .16s ease; }
.nf__back:hover { text-decoration: underline; text-underline-offset: 4px; }
.nf__back:hover i { transform: translateX(2px); }
.nf__back:focus-visible { outline: 2px solid var(--link); outline-offset: 3px; border-radius: 2px; }

/* ─────────────────────────────────────────── narrow

   Last in the file on purpose: these are overrides, and an override that sits
   above the rule it overrides silently loses at equal specificity.

   Below 700 the six nav links cannot share a line with the mark and the theme
   switch — that row is about 720px at its tightest, which put a horizontal
   scrollbar on every phone. So the header becomes two rows and scrolls away
   with the page instead of sticking.

   What comes back in its place is .mini: one row — mark, title, ☰ — which fits
   any phone, sliding down over the header once the sentinel leaves. The links
   move into .menu, a panel hanging off the bar's lower edge. Nothing morphs;
   the two are separate elements, because a header that animates its own height
   reflows the document under the scroll. See the pinned-bar block below. */

@media (max-width: 900px) {
  .hero { grid-template-columns: 1fr; align-items: start; gap: 1.5rem; }
  }

@media (max-width: 820px) {
  .top { position: static; }
  .top__in {
    display: grid; grid-template-columns: auto auto;
    height: auto; padding-block: .9rem 0; row-gap: .35rem;
  }
  .mark { grid-area: 1 / 1; }
  .themeswitch { grid-area: 1 / 2; justify-self: end; margin-left: 0; padding-left: 0; border-left: 0; }
  .top__t { display: none; }        /* the title it fades in is the <h1> just below */

  /* Flex-wrap rather than a fixed three-column grid. The grid could not
     reflow: at 200% text on a 320px viewport its three max-content columns
     overflowed the row and pushed the theme switch off the page, which fails
     the reflow and resize-text criteria. Wrapping costs nothing and holds at
     any text size. */
  .nv {
    grid-area: 2 / 1 / 3 / 3; margin-left: 0; white-space: normal;
    display: flex; flex-wrap: wrap; justify-content: start;
    column-gap: clamp(1.15rem, 7vw, 2rem); row-gap: .1rem;
    padding-bottom: .65rem; border-bottom: 1px solid var(--rule);
  }
  .nv a { margin: 0 -.3rem; padding: .58rem .3rem; }
  .themeswitch { margin-left: 0; }
  .themeswitch button { width: 38px; height: 38px; }

  /* This header overlays nothing — it sits in the flow and scrolls away — so
     the pinned treatment has no work to do, and the frosting cannot stay in
     any case. A backdrop-filter other than `none` makes an element the
     containing block for its fixed descendants; the same is true of filter,
     transform and will-change. None of them may go on .top at this width. */
  .is-pinned .top {
    background: transparent; border-bottom-color: transparent;
    box-shadow: none; backdrop-filter: none;
  }
  .top .prog { display: none; }     /* the read line rides .mini instead */
  html { scroll-padding-top: calc(var(--mini-h) + .5rem); }   /* anchors clear the bar */

  /* ── the pinned bar ──────────────────────────────────────────────────────
     Slides down over the header once the sentinel leaves. Transform only: the
     document's height never changes, so nothing reflows under the scroll. */
  .mini {
    display: block; position: fixed; top: 0; left: 0; right: 0; z-index: 9;
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--rule);
    transform: translateY(-100%);
    transition: transform .24s cubic-bezier(.4, 0, .2, 1);
  }
  .is-pinned .mini { transform: none; }
  .mini__in {
    display: flex; align-items: center; gap: .75rem;
    height: var(--mini-h); padding-inline: var(--gutter);
  }
  .mini .mark { color: var(--ink); }
  .mini .mark svg { width: 44px; }
  .mini__t {
    flex: 1 1 auto; min-width: 0; font: 600 .9rem/1 var(--sans); letter-spacing: -.014em;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--ink);
  }

  /* three dashes that fold into a cross — two rotations and a fade, no layout */
  /* The bars are the <i> elements inside .burger__b. Targeting `.burger span`
     here would style the wrapper itself — which is what happened, and drew a
     single 22x2 line where three belong. */
  .burger {
    flex: 0 0 auto; display: grid; place-content: center;
    width: 44px; height: 44px; margin-right: -.6rem;
    padding: 0; border: 0; background: none; color: var(--ink); cursor: pointer;
  }
  .burger__b { display: grid; gap: 5px; }
  .burger i {
    display: block; width: 22px; height: 2px; border-radius: 1px;
    background: currentColor;
    transition: transform .22s cubic-bezier(.4, 0, .2, 1), opacity .16s ease;
  }
  .is-menu-open .burger i:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .is-menu-open .burger i:nth-child(2) { opacity: 0; }
  .is-menu-open .burger i:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* ── the panel ───────────────────────────────────────────────────────────
     A sibling of .mini, never a child: .mini is transformed, and a transformed
     element is the containing block for its fixed descendants — nested, this
     would anchor to the bar rather than the viewport.

     Closed is `visibility: hidden` rather than `display: none` so both
     directions can animate; it also keeps the links out of the tab order and
     the accessibility tree while shut. */
  .menu {
    display: block; position: fixed; top: var(--mini-h); left: 0; right: 0; z-index: 8;
    padding: .35rem var(--gutter) 1.15rem;
    background: var(--bg); border-bottom: 1px solid var(--rule);
    box-shadow: 0 18px 28px -24px var(--shadow);
    opacity: 0; visibility: hidden; transform: translateY(-8px);
    transition: opacity .18s ease, transform .22s cubic-bezier(.4, 0, .2, 1), visibility .22s;
  }
  .is-menu-open .menu { opacity: 1; visibility: visible; transform: none; }
  .menu a {
    display: block; padding: .95rem 0; text-decoration: none;
    border-bottom: 1px solid var(--rule);
    font: 540 1.05rem/1 var(--sans); letter-spacing: -.01em; color: var(--nav-off);
  }
  .menu a.on { color: var(--ink); }
  .menu a.on::after {
    content: ''; display: inline-block; width: 4px; height: 4px; border-radius: 50%;
    background: var(--accent); margin-left: .4rem; vertical-align: .16em;
  }
  .menu__foot { padding-top: .9rem; }

  .scrim {
    display: block; position: fixed; inset: 0; z-index: 7;
    background: color-mix(in srgb, var(--bg) 62%, transparent);
    opacity: 0; visibility: hidden;
    transition: opacity .2s ease, visibility .2s;
  }
  .is-menu-open .scrim { opacity: 1; visibility: visible; }

  .pad { padding-inline: 22px; padding-top: 60px; padding-bottom: 3.5rem; }
  .home .sec { padding-top: 60px; }
  .home .hero { gap: 24px; }
  .foot { margin-inline: 22px; }
  .hero { padding-top: .15rem; padding-bottom: 2.2rem; }
  /* capped against the viewport: a clamp minimum is a floor in rem, so at 200%
     text it became 96px and the name could not wrap below its own width */
  .name { font-size: min(clamp(3rem, 14vw, 4rem), 14vw); }
  .lede, .aff, .w .d, .nw .tx { overflow-wrap: break-word; }
  .lede { font-size: 1.25rem; }
  .post, .cvr, .pr, .nw { grid-template-columns: 1fr; gap: .35rem; }
  .post .dt, .cvr .when, .nw .d { padding-top: 0; }
  .pr, .post, .cvr, .nw { padding-block: 1.05rem; }
  .swr .st { margin-left: 0; }           /* role drops under the name instead of pushing the row */

  /* Touch targets. Every standalone control gets to roughly 40px; where the
     padding would move something, a negative margin pulls it back. Links inside
     prose are left alone — a word mid-sentence cannot be padded without
     breaking the line, and WCAG exempts them for exactly that reason. */
  .mark { padding: .55rem .45rem; margin: -.55rem -.45rem; }
  /* two columns, and the notice on its own full-width line beneath */
  .foot { display: block; }
  .foot__links { display: grid; grid-template-columns: 1fr 1fr; gap: .55rem 1.2rem; }
  .foot__meta { display: block; margin: 1.1rem 0 0; text-align: left; }
  .foot a { padding: .55rem 0; }
  .swr .nm { display: inline-block; padding: .3rem 0; }
  .nw .go { display: inline-block; padding: .35rem .55rem; margin: -.35rem -.55rem; }
  .code__c { padding: .55rem .8rem; }
  .gal__i { min-height: 44px; }
}

@media (max-width: 390px) {
  .nv { column-gap: 1rem; }
  .name { font-size: min(2.8rem, 14vw); }   /* capped: see the 820 block */
}
