:root{
  /* Sampled from the live site: accent rgb(208,36,76), body font Lora.
     Neutrals carry a slight warm bias so they sit with the crimson
     rather than fighting it. */
  --ground:#FCFCFD;
  --band:#F1F1F2;
  --band-line:#E4E4E7;
  --raised:#F5F6F8;
  --ink:#191C24;
  --ink-mid:#525869;
  --ink-soft:#6B7180;
  --rule:#DEE1E8;
  --rule-soft:#EAECF1;
  --accent:#2F52AD;
  --accent-soft:#E5EAF7;
  --flag:#8A6414;
  --flag-soft:#F7F0DF;
  --band-img-opacity:1;
  --band-title-shadow:0 1px 0 rgba(255,255,255,.6);
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
    --ground:#0F1116;
    --band:#1A1D25;
    --band-line:#262A34;
    --raised:#171A22;
    --ink:#E8EAF0;
    --ink-mid:#A7ADBD;
    --ink-soft:#7E8496;
    --rule:#272B36;
    --rule-soft:#1E222C;
    --accent:#93A9EE;
    --accent-soft:#1A2138;
    --flag:#D7B369;
    --flag-soft:#2A2416;
    --band-img-opacity:.30;
    --band-title-shadow:none;
  }
}:root[data-theme="dark"]{
  --ground:#0F1116;
  --band:#1A1D25;
  --band-line:#262A34;
  --raised:#171A22;
  --ink:#E8EAF0;
  --ink-mid:#A7ADBD;
  --ink-soft:#7E8496;
  --rule:#272B36;
  --rule-soft:#1E222C;
  --accent:#93A9EE;
  --accent-soft:#1A2138;
  --flag:#D7B369;
  --flag-soft:#2A2416;
  --band-img-opacity:.30;
  --band-title-shadow:none;
}/* ---------- alternate accent: crimson, your current colour ----------
   Blue is now the default (Jake, 2026-09-04: "more authoritative").
   The neutrals shift with the accent — greys biased toward it read as
   chosen; cool greys under a crimson accent read as a mistake. */
:root[data-accent="crimson"]{
  --ground:#FDFCFC;--band:#F1F1F1;--band-line:#E5E5E5;--raised:#F7F5F6;
  --ink:#22191C;--ink-mid:#5E5257;--ink-soft:#767077;
  --rule:#E2DDDF;--rule-soft:#EDE9EB;
  --accent:#D0244C;--accent-soft:#FAE8ED;
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"])[data-accent="crimson"]{
    --ground:#141013;--band:#1D171A;--band-line:#282024;--raised:#1B1518;
    --ink:#EFE9EB;--ink-mid:#B2A6AB;--ink-soft:#8A7E83;
    --rule:#2E262A;--rule-soft:#241D21;
    --accent:#F26A8A;--accent-soft:#2C171E;
  }
}:root[data-theme="dark"][data-accent="crimson"]{
  --ground:#141013;--band:#1D171A;--band-line:#282024;--raised:#1B1518;
  --ink:#EFE9EB;--ink-mid:#B2A6AB;--ink-soft:#8A7E83;
  --rule:#2E262A;--rule-soft:#241D21;
  --accent:#F26A8A;--accent-soft:#2C171E;
}*{box-sizing:border-box}body{
  margin:0;background:var(--ground);color:var(--ink);
  font-family:Lora,Georgia,'Times New Roman',serif;
  font-size:17px;line-height:1.68;-webkit-font-smoothing:antialiased;
}.sans{font-family:Karla,system-ui,-apple-system,sans-serif}.mono{font-family:'IBM Plex Mono','SF Mono',Menlo,monospace}.wrap{max-width:840px;margin:0 auto;padding:0 30px}/* ---------- masthead + nav ---------- */
.masthead{border-bottom:1px solid var(--rule-soft)}.masthead-in{
  max-width:840px;margin:0 auto;padding:26px 30px 24px;
  display:flex;align-items:center;gap:20px;flex-wrap:wrap;
}.wordmark{display:flex;align-items:center;gap:14px;text-decoration:none;color:inherit;border:0}.wordmark h1{
  font-family:Karla,system-ui,-apple-system,sans-serif;font-size:24px;font-weight:600;
  letter-spacing:-.014em;line-height:1.15;margin:0;
}.wordmark p{margin:2px 0 0;font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:.02em;color:var(--ink-soft)}nav.site{margin-left:auto;display:flex;gap:0;flex-wrap:wrap;align-items:center}nav.site a, nav.site a.cv{
  appearance:none;background:none;border:0;cursor:pointer;
  font-family:Karla,system-ui,sans-serif;
  font-size:11.5px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-mid);padding:8px 13px;text-decoration:none;position:relative;
}nav.site a:hover, nav.site a.cv:hover{color:var(--ink)}nav.site a[aria-current="page"]{color:var(--accent)}nav.site a[aria-current="page"]::after{
  content:"";position:absolute;left:13px;right:13px;bottom:1px;height:1.5px;background:var(--accent);
}nav.site a:focus-visible, nav.site a.cv:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-radius:2px}/* ---------- the banner band ----------
   YOUR photograph, kept. It is layered over a CSS stand-in that draws the
   same pleats, so the band still reads correctly wherever the image can't
   load — which includes this published preview, since a hosted page may not
   pull images off jakechandler.net. Open this same file from the Cowork
   folder and the real photograph appears.

   The image is served here at its live URL. On the real rebuild it would be
   resized: the original is 3456px wide and 287 KB, filling a strip about
   200px tall. Same picture at a sensible size is roughly a tenth of that. */
.band{
  background:var(--band);
  border-bottom:1px solid var(--band-line);
  position:relative;overflow:hidden;
}.band::before{
  content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(107deg,
    transparent 0 22px,
    color-mix(in srgb,var(--band-line) 62%,transparent) 22px 23px,
    transparent 23px 46px);
  mask-image:linear-gradient(100deg,transparent 8%,#000 62%,#000 100%);
  -webkit-mask-image:linear-gradient(100deg,transparent 8%,#000 62%,#000 100%);
}.band::after{
  content:"";position:absolute;inset:0;
  background-image:url("/assets/jakechandler-banner2-2-1800x738.jpg");
  background-size:cover;background-position:center 62%;
}.band-in{
  max-width:840px;margin:0 auto;padding:38px 30px 36px;
  position:relative;z-index:1;
}.band::after{opacity:var(--band-img-opacity)}.band h2{
  margin:0;font-family:Lora,Georgia,serif;font-weight:500;
  font-size:28px;line-height:1.2;letter-spacing:-.004em;color:var(--accent);
  text-wrap:balance;text-shadow:var(--band-title-shadow);
}/* ---------- panels ---------- */
main{padding:40px 0 88px}.panel{display:flex;flex-direction:column;gap:32px}p{margin:0}.prose{display:flex;flex-direction:column;gap:16px;max-width:66ch}a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--accent-soft)}a:hover{border-bottom-color:var(--accent)}a:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.eyebrow{
  font-family:Karla,system-ui,sans-serif;
  font-size:10.5px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-soft);margin:0 0 13px;
}h3.sub{font-family:Lora,Georgia,serif;font-size:16.5px;font-weight:600;margin:0 0 4px}/* ---------- home: portrait in the margin, one text column ----------
   The portrait sits in a fixed gutter and ALL the bio prose sits in the
   column beside it. Every paragraph then shares one left edge and one line
   length. The earlier version put only the first two paragraphs beside the
   photo and let the rest run full width, which gave the page two different
   measures and a left edge that jumped — visible at any width, worse while
   resizing. */
.bio{display:grid;grid-template-columns:124px minmax(0,1fr);gap:0 26px;align-items:start}.portrait{
  width:124px;height:auto;display:block;
  background:var(--raised);
  font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:var(--ink-soft);
}.bio .prose{max-width:none}
@media (max-width:620px){.bio{grid-template-columns:1fr;gap:20px}.portrait{width:108px}}/* ---------- ancestry chain ---------- */
/* ---------- the ancestry chain ----------
   Built from list items rather than a fixed-width SVG. The SVG version had a
   640px minimum inside a sideways-scrolling strip, which on a phone cut the
   chain off after "Moore" with no cue that it scrolled — hiding the one node
   the diagram exists to reach. Laid out with flex it simply turns vertical. */
.lineage{border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:22px 0 18px}.chain{
  list-style:none;margin:0;padding:0;
  display:flex;align-items:flex-start;
  font-family:Karla,system-ui,sans-serif;font-size:12px;color:var(--ink-mid);
}.chain li{flex:1;position:relative;padding-top:19px;text-align:center;min-width:0}.chain li::before{
  content:"";position:absolute;top:4px;left:50%;transform:translateX(-50%);
  width:7px;height:7px;border-radius:50%;background:var(--accent);z-index:1;
}.chain li::after{
  content:"";position:absolute;top:7px;left:50%;width:100%;height:1px;background:var(--rule);
}.chain li:last-child::after{display:none}.chain .me{color:var(--accent);font-weight:600}.chain .me::before{
  width:11px;height:11px;top:2px;background:var(--ground);
  border:2px solid var(--accent);
}/* ---------- bibliography ---------- */
.biblio{display:flex;flex-direction:column}.entry{display:grid;grid-template-columns:56px 1fr;gap:0 18px;padding:12px 0;border-bottom:1px solid var(--rule-soft);align-items:baseline}.entry:first-child{border-top:1px solid var(--rule)}.yr{font-family:'IBM Plex Mono',monospace;font-size:12px;font-variant-numeric:tabular-nums;color:var(--ink-soft)}.entry .t{font-size:16px;line-height:1.5}.entry .v{display:block;margin-top:2px;font-family:Karla,system-ui,sans-serif;font-size:12.5px;color:var(--ink-soft)}.pdf{font-family:'IBM Plex Mono',monospace;font-size:10.5px;border:0;color:var(--accent)}.pdf::before{content:"["}.pdf::after{content:"]"}/* ---------- teaching ---------- */
.courses{display:flex;flex-direction:column}.course{display:grid;grid-template-columns:1fr auto;gap:3px 20px;padding:15px 0;border-bottom:1px solid var(--rule-soft)}.course:first-child{border-top:1px solid var(--rule)}.course p{grid-column:1/-1;font-size:15px;color:var(--ink-mid);line-height:1.6;max-width:64ch}.where{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--ink-soft);white-space:nowrap;font-variant-numeric:tabular-nums}.files{grid-column:1/-1;margin-top:6px;font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--ink-soft)}/* ---------- contact ---------- */
/* One column. The auto-fit grid split six items across two columns and the
   label/value pairs went ragged against each other — vertical is right. */
/* Widened 520 -> 680, 2026-09-09, on Jake's instruction ("change the text
   wrapping, so that each link fit on one line"). The value column is
   max-width minus the 124px label and the 16px gap, so this buys it roughly
   540px instead of 380px — enough for the longest row on the page (Scopus's
   URL, ~50 characters) at 16px without wrapping on desktop. Still short of
   680 + the .wrap padding, so it never crowds the page's own 780px measure.
   ⚠️ Below the 620px breakpoint .links li stacks (label above value) and the
   value gets the full row width regardless of this number — this change is a
   desktop-only fix. A very long value on a narrow phone can still wrap;
   overflow-wrap:anywhere below is what keeps that safe rather than broken. */
/* ⛔ border-top DROPPED 2026-09-09. The page went from one `.links` list to
   three (Identifiers / Indexes / Elsewhere), each one now sitting right
   below an `h2.eyebrow` — and `h2.eyebrow` already closes with its own
   hairline (`border-bottom:1px solid var(--rule);padding-bottom:9px`, see
   build_site.py). This rule's own top border then drew a SECOND line right
   under that one, on every group — the "two lines after the sections" Jake
   flagged as inconsistent with how a section heading reads everywhere else
   on the site (/research/'s Overview/Publications get exactly one hairline
   each, from the heading alone). One rule should own the boundary, not two, so this list no longer draws its own — the heading's hairline is enough. */
.links{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;max-width:680px}/* ⛔ border-bottom DROPPED 2026-09-09, on Jake's instruction ("remove the
   lines in between the sites linked"), same session as dropping .links's own
   top border above. Padding alone now carries the rhythm between rows within
   a group; the h2.eyebrow hairline is the only rule left on the page, marking
   where one group ends and the next begins rather than every single row. */
.links li{padding:12px 0;display:flex;gap:16px;align-items:baseline}/* ⛔ ADDED 2026-09-09, on Jake's instruction ("the spacing after the section
   headings is too large"). `.panel{gap:32px}` is the site-wide rhythm between
   ANY two panel children, h2.eyebrow's own margin-bottom is already zeroed
   (site-wide, so Research/Teaching keep their 32px), and neither can be
   overridden per-pair without touching those other pages too. This selector
   is scoped to exactly the shape only the contact page has — an h2.eyebrow
   immediately followed by a .links list — so pulling it up 16px (32 -> 16)
   tightens ONLY the heading-to-its-own-list gap here, leaving Research and
   Teaching's heading spacing untouched. Matches the standing principle (see
   h2.eyebrow's own comment): a heading should sit closer to its own content
   than to the next section — group-to-group is still the full 32px. */
.panel>h2.eyebrow+.links{margin-top:-16px}.links .k{font-family:Karla,system-ui,sans-serif;font-size:11px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-soft);width:124px;flex:none}.links .val{font-size:16px;min-width:0;overflow-wrap:anywhere}.note{
  background:var(--flag-soft);border-left:2px solid var(--flag);
  padding:12px 16px;font-family:Karla,system-ui,sans-serif;
  font-size:13px;line-height:1.6;color:var(--ink-mid);max-width:66ch;
}.note b{color:var(--ink)}.note .mono{font-size:12px}footer.site{border-top:1px solid var(--rule);padding:20px 0 60px;font-family:Karla,system-ui,sans-serif;font-size:12px;color:var(--ink-soft);display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap}

@media (max-width:620px){body{font-size:16.5px}.wrap, .masthead-in, .band-in{padding-left:20px;padding-right:20px}.band h2{font-size:23px}.band-in{padding-top:28px;padding-bottom:26px}/* nav drops under the wordmark and spans the full width, so the five
     items wrap predictably instead of squeezing */
  nav.site{margin-left:0;width:100%;margin-top:4px;gap:0 2px}nav.site a, nav.site a.cv{padding:7px 10px 7px 0;font-size:11px;letter-spacing:.12em}nav.site a[aria-current="page"]::after{left:0;right:10px}nav.site a.cv{margin-left:auto;padding-right:0}.wordmark h1{font-size:22px}.wordmark{gap:12px}.masthead-in{padding:18px 20px 14px;gap:10px}/* the chain turns vertical rather than scrolling sideways */
  .chain{flex-direction:column;font-size:13px}.chain li{flex:none;text-align:left;padding:0 0 15px 24px}.chain li:last-child{padding-bottom:0}.chain li::before{top:4px;left:3px;transform:none}.chain li::after{top:9px;left:6px;width:1px;height:100%}.chain .me::before{top:1px;left:1px}.note{font-size:12.5px}.entry{grid-template-columns:44px 1fr;gap:0 12px}.links li{flex-direction:column;gap:3px}.links .k{width:auto}.course{grid-template-columns:1fr}.where{white-space:normal}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ---------- archive banner ----------
   Added 2026-09-04. The two workshop pages are preserved exactly as they
   stood; this strip is what makes that legible, so a reader does not take
   a 2019 job title or a live fellowship credit as a current claim. */
.archived{
  background:var(--flag-soft);border:1px solid color-mix(in srgb,var(--flag) 30%,transparent);
  border-left:3px solid var(--flag);border-radius:2px;
  padding:13px 17px;margin:0 0 30px;max-width:66ch;
  font-family:Karla,system-ui,sans-serif;font-size:13px;line-height:1.6;color:var(--ink-mid);
}
.archived b{color:var(--flag);font-weight:600}
.talks{list-style:none;margin:0;padding:0}
.talks li{padding:13px 0;border-top:1px solid var(--rule-soft)}
.talks li:first-child{border-top:0}
.talks .t{display:block;color:var(--ink)}

/* ---------- the DOI on a publication row ----------
   Added 2026-09-06 (WEB C6), when 19 of 27 entries gained the DOI they should
   always have carried. Deliberately NOT `.pdf`: that class brackets its label
   and paints it in --accent, which says "open this file". A DOI is the entry's
   identifier, and on the five publisher-open entries it is also the full text,
   so it must read as apparatus that happens to be clickable rather than as a
   second download button competing with the first.
   ⚠️ Same 10.5px monospace as .pdf so the row stays one typographic object;
   --ink-soft rather than --accent is the whole difference, plus a rule under it
   so it is still visibly a link. `overflow-wrap` because a long Oxford DOI
   (10.1093/acprof:oso/9780199604760.003.0001) is 44 characters with no space in
   it and WILL overflow the 620px column on a phone otherwise. */
.doi{
  font-family:'IBM Plex Mono',monospace;font-size:10.5px;
  color:var(--ink-soft);border-bottom:1px solid var(--rule);
  overflow-wrap:anywhere;
}
.doi:hover{color:var(--accent);border-bottom-color:var(--accent)}

/* ---------- the DOI as TEXT, inside the reference (C7) ----------
   Jake, 2026-09-06: print the DOI rather than link it, "since they do not
   discriminate between paywalled and open access". So it must NOT look like a
   link: no rule under it, no hover, and it sits in the citation line rather
   than in the row of chips.
   ⚠️ `overflow-wrap:anywhere` is kept from the old `.doi` rule for the same
   reason it was there: the Oxford DOI is 44 characters with no space in it and
   overflows a 390px column otherwise. Losing the link must not lose that.
   ⚠️ Sized to `.v`, not to `.pdf`. It is part of the reference now, and at
   10.5px against the citation's 15px it read as a caption that had slipped. */
.doi-text{
  font-family:'IBM Plex Mono',monospace;font-size:.82em;
  color:var(--ink-soft);overflow-wrap:anywhere;
}

/* ---------- the publisher link ----------
   ⭐ Refinement A (later C7). One chip, "publisher (free|paywalled)", styled
   and BRACKETED exactly like .pdf — Jake: "either everything gets them
   (preferable, I think) or nothing does". The access word lives inside the
   link text in parentheses, so it can never be orphaned from its link, and
   the em.access element this replaced is gone from the markup entirely. */
.pub{
  font-family:'IBM Plex Mono',monospace;font-size:10.5px;
  border:0;color:var(--accent);
}
.pub::before{content:"["}
.pub::after{content:"]"}
/* The chip and its leading separator never break apart. See the renderer. */
.pubwrap{white-space:nowrap}

/* ---------- the lattice mark's top element ----------
   The original icon paints the maximum crimson (208,36,76) — the site's former
   accent — and everything else black. The mark is now blue with that one node
   crimson, on Jake's call 2026-09-05. Its own token because it is the only
   crimson left in the default palette, and a decorative mark should not borrow
   a text colour. Lightened in dark mode, where #D0244C goes muddy on #0F1116. */
:root{--mark-top:#D0244C}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--mark-top:#F26A8A}}
:root[data-theme="dark"]{--mark-top:#F26A8A}

/* ---------- bylines ----------
   Added 2026-09-04. Joint entries carry the published author list instead of
   "(with X)", which erased author order — the one thing carrying the signal.
   Set at citation-apparatus size, with the site's owner emphasised so a reader
   scanning the column sees at a glance where he stands in each byline.
   ⚠️ Sole-authored entries carry NO byline line: 14 of 26 papers are solo, and
   the absence marks them without a tally, which would read as defensive. */
.byline{
  display:block;margin-top:2px;
  font-family:Karla,system-ui,sans-serif;font-size:12.5px;color:var(--ink-soft);
}
.byline b{color:var(--ink-mid);font-weight:600}

/* ---------- the convention note ----------
   ⛔ States the CONVENTION, never a contribution SHARE. Jake, 2026-09-04:
   "I don't want a precise contribution percentage when I am more than 50/50:
   it could invite complaints from Booth." That constraint is unchanged and a
   check still enforces it: no number, no percentage, no ratio.

   🔁 WORDING CHANGED 2026-09-07 (C10), on Jake's instruction: "the order the
   authors agreed" -> "the order of relative contribution".

   ⚠️ THIS IS A STRONGER CLAIM THAN THE ONE IT REPLACES, AND THE TRADE-OFF WAS
   PUT TO HIM. The old phrasing attributed the decision to both parties and so
   asserted nothing a co-author could dispute; the new one states what the order
   MEANS, which is a claim about contribution. Jake's call, and it is his record.

   ✅ It is also the more accurate sentence, and that was checked rather than
   assumed. Career V-41 is closed on exactly this point —
   Career/state/VERIFICATION_QUEUE.md:646, "Alphabetical = equal contribution;
   departures mark Jake's lead. Five equal, five Jake-led, none Booth-led" —
   and the long-form record spells the same convention out for a committee
   (Career/output/academic/dossiers/long-form-record/LONG_FORM_RECORD_2026-09-01.md:464).
   📌 So the site now says what Career already says, rather than a weaker
   paraphrase of it. Opening Career first is the standing rule; the last time
   this project drafted an authorship sentence from first principles, Career's
   version turned out to be the stronger one. */
.note{
  font-family:Karla,system-ui,sans-serif;font-size:13px;line-height:1.6;
  color:var(--ink-mid);margin:-4px 0 6px;max-width:60ch;
}

/* ---------- space between stacked panels ----------
   Added 2026-09-04, found at 390px. The preview had exactly ONE .panel
   visible at a time — it drove the four sections with tabs, and the three
   inactive ones were `hidden`. So the gap BETWEEN panels was never designed,
   because it never existed. The real /research/ page stacks four of them on
   one page, and `main` has no gap and `section` has no margin: the section
   breaks rendered at 0px while the gaps INSIDE a section are 32px, so the
   hierarchy read upside down — a new section looked like a tighter break
   than a new paragraph.

   Only /research/ has more than one panel, so this rule touches one page.
   52px, comfortably clear of the 32px intra-panel gap. */
.panel + .panel{margin-top:52px}

/* ---------- section headings ----------
   Added 2026-09-04. `h3.sub` came out of the preview at 16.5px against a 17px
   body: the section headings — "Book chapters", "Edited volumes", and every
   course title — were SMALLER than the text they head. Jake caught it.

   It is the same defect § 8 already recorded once, when theme headings arrived
   as <h6>. That instance was fixed and this one was never checked, because in
   the preview these headings sat in a panel of their own where nothing
   contradicted them. A heading is only a heading relative to its body text.

   The scale is now: band title 28 / section 21 / body 17 / meta 11.
   Mobile: 23 / 20 / 16.5 / 11. */
/* Weight 500, not the preview's 600 — Jake, 2026-09-04: "'Articles in
   peer-reviewed conference proceedings' and similar on that page probably
   shouldn't be bold." At 18.5px against 17px body, bold was doing the work
   that size and position already do, and five bold category headings down one
   page read as emphasis rather than structure.

   ⭐ It also sharpens the level above it. The section heading stays at 600, so
   the two ranks now differ in weight as well as size — which matters, because
   the scale is deliberately narrow (20 / 18.5 / 17) and size alone was
   carrying almost none of the distinction.

   ⚠️ This is `h3.sub`, so it also un-bolds the twelve course titles on
   /teaching/ and any heading inside the archived workshop page. That is the
   same element doing the same job, and consistent is right — but if you want
   it scoped to /research/ only, the selector is `.biblio + h3.sub` territory
   and it is one line. */
h3.sub{font-size:18.5px;font-weight:500;line-height:1.35;letter-spacing:-.004em;margin:0 0 8px}
/* 🔁 Superseded 2026-09-06 by the proximity fix at the foot of this stylesheet,
   which sets this to 0 so the course title and its meta line read as one unit.
   Left in place as the record of what the value was. */
.course h3.sub{margin-bottom:5px}

/* ---------- .eyebrow is the SECTION heading, and was a third the size ----
   ⛔ The third instance of the same fault, and the worst. Jake, 2026-09-04,
   pointing at "OVERVIEW" and "PAST PROJECTS": "the section headings are still
   way too small."

   He was pointing at a different element from the one I had just fixed. On
   /research/ the hierarchy is:

       .eyebrow   OVERVIEW · PAST PROJECTS · PUBLICATIONS · TALKS & HANDOUTS
       h3.sub       Articles in journals · Book chapters · Edited volumes ...
       body         the entries themselves

   `.eyebrow` was **10.5px** and `h3.sub` is 21px. **The level-one heading was
   half the size of the level-two heading nested inside it**, and a third the
   size of the body text it introduced. Raising h3.sub to 21 had made the
   inversion worse, not better.

   ⚠️ And they were not headings at all — `<p class="eyebrow">`. The page's
   outline ran h1 → h3 with the whole section structure missing from it, so a
   screen reader could not navigate the page by section. Now `<h2>`.

   The 10.5px uppercase label is a real device, and it was right in the preview,
   where each of these sat alone at the top of its own tab panel with nothing
   below it to be smaller than. Stacked on one page it stops being an eyebrow
   and becomes a section heading, which has to out-rank what it contains.

   ⚠️ SIZED DOWN AGAIN, 2026-09-04 — Jake: "with the font changed, they are now
   a bit too big. I just want them a size up from the main text." Body is 17, so
   the section heading is **20**. That forced `h3.sub` down with it: a
   subsection heading cannot sit at 21 under a 20px section heading without
   re-inverting the thing this rule exists to fix, so it goes to **18.5**.

   📌 The three levels are now two points apart, which is a narrow band, so the
   rank is carried by more than size: the 52px gap above each section, the
   weight, and the fact that a section heading always opens a panel. **Size is
   the cheapest rank signal and it is not the only one** — which is what makes
   a tight scale workable here.

   Scale, top to bottom: name 34 / section 20 / subsection 18.5 / body 17 / meta 11.
   Mobile: 27 / 19 / 17.5 / 16.5 / 11. */
h2.eyebrow{
  font-family:Lora,Georgia,serif;
  font-size:20px;font-weight:600;line-height:1.3;
  letter-spacing:-.005em;text-transform:none;
  color:var(--ink);margin:0 0 16px;
  /* ⭐ A hairline under the section heading, added 2026-09-04. Jake, on the
     course titles: "it clashes with the section headings."

     He is right, and the cause is that the scale he asked for is narrow.
     Section 20 and subsection 18.5 are 1.5px apart — at that distance size
     cannot carry the rank on its own, and on /teaching/ the problem was worst:
     ONE section heading ("Courses taught") sitting above TWELVE course titles
     that looked almost identical to it.

     🔁 That heading was removed on 2026-09-06 — it named the only thing on the
     page, so it divided nothing — which means /teaching/ no longer exercises
     this rule at all. The rule stays because /research/ stacks four sections
     and needs it, and because the ambiguity it fixes is a property of the
     narrow scale, not of the page it was found on.

     The rule fixes it by changing the KIND rather than the size. A section
     heading now spans the measure and closes with a line; a subsection heading
     is just words. That reads instantly and costs no extra size, which is what
     Jake asked to keep down. It also means the two levels no longer depend on
     the reader noticing 1.5px and a weight step. */
  border-bottom:1px solid var(--rule);padding-bottom:9px;
}

/* ---------- the band ----------
   ⛔ Reversed 2026-09-04, on Jake's instruction: "I am backpedalling on the
   banner: it looks cheap; you were right." That reinstates G5 — draw the band
   in CSS, do not use the photograph — and supersedes F4 and G6.

   🔁 ONE LINE TO UNDO. Delete the rule below and the photograph returns; the
   file is still on disk at wp-content/uploads/2017/12/, still resized, and
   `.band::after` still carries the image and the opacity token. Nothing else
   has to change. */
.band::after{content:none}

/* ---------- masthead, enlarged ----------
   Added 2026-09-04, with the band's removal above. Jake: "increase the font
   size of that bar and just use that (increasing, correspondingly the size of
   my name and the subtitle)."

   The reasoning is structural, not decorative. The band used to be the loudest
   thing on the page at 28px, and the masthead was sized to sit under it. With
   the band gone from the four nav pages, the masthead IS the top of the page —
   it now has to do the work of orienting a reader on its own, so the three
   things it holds all step up together and keep their relative order:

     name 24 -> 30   the page's one piece of identity
     nav   11.5 -> 13.5   now the only page indicator, so it must read as one
     sub   11 -> 13   still the quietest of the three

   Section headings stay at 21. The band title was 28; nothing on the page is
   28 any more, and that is the point — one fewer level in the scale.

   ⭐ And the nav moves BELOW the name and subtitle, at every width — Jake,
   2026-09-04: "to allow for bigger fonts we could put the nav bar under the
   name and subtitle." This is not a new layout: it is what the masthead
   already did under 620px, promoted to the only layout. Side by side, the name
   and the nav compete for one line and both stay small to fit; stacked,
   neither is constrained by the other, which is what buys the size.

     name 24 -> 34
     nav   11.5 -> 15   the only page indicator now, so it must read as one
     sub   11 -> 14

   Nothing on the page is 28 any more — that was the band title. One fewer
   level in the scale. */
.masthead-in{
  display:block;
  padding:30px 30px 22px;
}
/* The lattice mark: larger, and standing further off the name — Jake,
   2026-09-04. The mark is a Hasse diagram, which is a picture of the thing the
   site is about, so it can carry more weight than a decorative logo would.
   46×51 in the markup; the gap goes 14 → 22, because a bigger mark set at the
   old distance reads as crowding the name rather than accompanying it.
   `align-items:center` keeps it optically centred on the name-plus-subtitle
   block rather than on the name alone.

   ⭐ THE NAME NOW STARTS WHERE "RESEARCH" DOES. Jake, 2026-09-06 (C7): "move
   my name and subtitle a bit further to the right of the logo. maybe so that
   it lines up with 'research' below it".

   ⛔ NOT A GAP — A FIXED FIRST COLUMN, and the difference is the whole point.
   A gap positions the name relative to the MARK'S RIGHT EDGE, and the mark is
   fluid: measured across widths it renders 37px down to 23px at 320px. Any
   fixed gap therefore aligns at exactly one viewport and drifts everywhere
   else — a first attempt at `gap:35px` was exact from 700px up and out by
   +6px at 480–620px and −1px at 390px.

   📌 A grid column instead makes the name's position independent of the mark
   entirely. And the column width is not a magic number: `.wordmark` and the
   nav share a left edge, so the name lines up with the second nav item exactly
   when the first column equals THE FIRST NAV ITEM'S WIDTH. Measured, that is
   a constant at every viewport — "Research".left minus "Home".left is 72px
   from 621px up and 57px below it, whatever the mark is doing.

   ⚠️ STILL HOSTAGE TO THE WORD "Home", but now visibly so: 72 and 57 ARE the
   width of "Home" plus its padding. Rename that nav item and the alignment
   silently goes — nothing in CSS can measure a sibling. A check pins both
   numbers so they cannot drift unnoticed; no check can see the intent. If the
   nav ever changes, re-measure and reset both.

   `align-items:center` is kept from the flex version: it centres the mark on
   the name-plus-subtitle block rather than on the name alone.

   ⛔ `column-gap:0` IS LOAD-BEARING, and leaving it out cost a measuring pass.
   The preview declares a 14px gap on this element, and gap applies to grid
   exactly as it does to flex — so the first grid version placed the name at
   column-width PLUS 14px and was out by that at every width. The column is the
   whole position now; nothing may be added to it.
   📌 Same shape as F32 earlier today: a preview rule surviving underneath this
   project's override and quietly changing the result.

   ⚠️ AND WRITING THIS NOTE BROKE THE PAGE TWICE, WHICH IS THE RECORD.
   First: the note quoted the preview's declaration verbatim in running prose,
   after a comment terminator that had been typed a paragraph too early. The
   quoted rule was then read as a REAL rule — reinstating flex and the 14px
   gap, and beating the actual rule below it.
   Second, fixing that: this paragraph described the terminator by TYPING it,
   which closed the comment at that word and stranded everything after.
   ⛔ TWO RULES FOLLOW, both learned the hard way in five minutes:
     1. never write a CSS declaration inside a CSS comment in a form that
        would parse if the comment ended early — describe it in words;
     2. never type a comment terminator inside a comment, not even to name it.
   ✅ Both were caught by verify_diff's balance and stray-prose checks, which
   is the whole argument for the crude well-formedness checks added in C4. */
.wordmark{display:inline-grid;grid-template-columns:72px 1fr;column-gap:0;align-items:center}
/* ⛔ The mark must not stretch to fill its column — it is a fixed-size glyph,
   and a stretched Hasse diagram would distort the thing the site is about.
   ⚠️ This also stops it SHRINKING, which flex was doing: squeezed in a flex
   row the mark fell to 23px at 320px. That was incidental, not designed — the
   markup asks for 37×51 — and it now holds that size at every width, which
   fits comfortably inside even the 57px mobile column. */
.wordmark > svg{justify-self:start;flex-shrink:0}
/* ---------- the name: Karla, on the subtitle's tracking ----------
   Two instructions from Jake, 2026-09-04, and they came apart.

   First: "make my name the same font as the subtitle ... I suspect we have
   extra spacing between letters in the subtitle and nav bar. If so, apply the
   same settings to my name." ⭐ **He was right to suspect the tracking, and the
   direction was the surprise:** the subtitle carries +.02em and the nav +.11em,
   while the name was on **-.014em** — negative. It was not merely un-tracked,
   it was tracked the opposite way from everything beside it.

   Then, seeing it in mono: "the original font for the name was actually nice.
   Can we revert? Keep the spacing though."

   🔁 So the FACE is back to Karla and the TRACKING stays at the subtitle's
   +.02em. That is the whole point of the two-part request — the suspicion about
   the spacing was correct even though the conclusion he drew from it, changing
   the family, was not. Size back to 34/27, which Karla fits comfortably;
   monospace had needed 32/25 because "Jake Chandler" is 13 characters at a
   fixed advance. IBM Plex Mono 600 dropped from the font request with it. */
.wordmark h1{
  font-family:Karla,system-ui,-apple-system,sans-serif;
  font-size:34px;letter-spacing:.02em;line-height:1.12;
}
.wordmark p{font-size:14px;margin-top:2px}
/* The nav spans the full width on its own row and starts flush with the left
   edge of the masthead, so it lines up with the wordmark rather than sitting
   at a 13px indent. CV stays pushed to the far right. */
/* The 14px this used to carry was set before the nav grew to 15px and moved
   onto its own row, and it left the nav crowded up under the subtitle. Jake,
   2026-09-06: "a touch more space ... harmonious with the other vertical
   spacing."
   ⭐ The figure is not free-chosen — it is read off what is already below the
   nav. The masthead closes with 24px of padding and each link carries 8px of
   its own, so there are 32px between the nav's text and the rule beneath it.
   Above it: this margin plus the same 8px. At 20px that is 28 against 32 —
   near-even, so the nav sits in its own band rather than hanging off the
   wordmark, and still a shade tighter above than below, which keeps it
   reading as part of the masthead instead of floating free of it. */
nav.site{margin-left:0;width:100%;margin-top:20px;gap:0 4px}
nav.site a,nav.site a.cv{
  font-size:15px;letter-spacing:.11em;padding:8px 16px 8px 0;
}
nav.site a[aria-current="page"]::after{left:0;right:16px}
/* 🔁 `margin-left:auto` after all — but for a different reason than before, and
   the difference is the point.

   Jake first said the CV link was "too far to the right". It was: pushed to the
   end of a **780px** nav row, it sat half a page from the item before it and
   read as a stray link. Then, once the column came down to 620px: "could we
   have the CV link placed at the point where the rest of the text below wraps?"

   ⭐ Same declaration, opposite result, because what it aligns to changed.
   Flush right now means flush with the single measure — the CV link's right
   edge lands on the same pixel as the prose, the publication rows, the heading
   rules and the ancestry chain. It is no longer stranded at an edge nothing
   else uses; it is the top item in the column's right margin.

   📌 Worth keeping as a rule of thumb: `margin-left:auto` is only as good as
   the box it flushes against. The declaration was never the problem. */
nav.site a.cv{margin-left:auto;padding-right:0}
nav.site a.cv[aria-current="page"]::after{right:0}
/* Without a band beneath it, the masthead's rule is the only thing between the
   nav and the content, so the content needs more room under it than the 40px
   that used to sit below a 36px-padded band. */
main{padding:56px 0 88px}

/* ================= THE MEASURE ==============================================
   Rewritten 2026-09-06, after Jake asked for the proportions to be checked
   against design principles. Every figure below was MEASURED in a browser with
   the real webfonts loaded, not computed from the CSS — see § How to rebuild
   in state/PROPORTIONS_AUDIT_2026-09-06.md for the script.

   ⛔ THE FAULT: `ch` DOES NOT MEAN "CHARACTERS" IN THIS TYPEFACE.
   `1ch` is the advance width of the digit "0". In Lora that is 0.62em, while
   the average character in running English prose is about 0.45em — so `66ch`
   asks for 66 characters and delivers 91. Measured, before this change:

       .prose      max-width:66ch  →  697px  →  91.5 characters per line
       .course p   max-width:64ch  →  596px  →  88.7
       .bio prose  (grid column)   →  630px  →  82.7
       .entry .v   (uncapped)      →  706px  →  127

   The conventional comfortable range for a single column is 45–75 characters
   and every one of those was outside it. ⭐ This is a CORRECTION, not a
   redesign: the stylesheet was already asking for a 66-character measure and
   was being answered in the wrong unit. It is also the same mistake, in the
   same unit, that made the lecture-slide lists overhang their paragraphs
   earlier today — that was its visible symptom, this is the cause.

   THE FIX: one scale, stated in pixels, calibrated to characters. 72 is the
   top of the comfortable band rather than the 66 ideal — deliberately, so the
   correction is the smallest one that reaches the standard and the page still
   looks like the page Jake approved over thirteen revisions.

   ⚠️ Pixels, not `ch`, for a second reason: if Lora fails to load the fallback
   is Georgia, whose digits are much narrower, so a `ch` cap would silently
   change measure during every slow font load.

   ═══ REVISED 2026-09-06, second pass ═══════════════════════════════════════
   Jake, on the 548px result: "the text width is rather small, no? It would
   also be nice to have all the text wrap at the same point. This is not the
   case in research, where the modules wrap way further to the right than the
   main text. We also need to make sure these values are the same between
   pages."

   ⭐ THOSE ARE THREE SYMPTOMS OF ONE THING, and so is the CV link sitting too
   far right. **The page column was 780px wide and the text inside it was
   548** — so 232px of every page was dead space on the right, the prose
   stopped in the middle of nowhere, the publication rows and the nav ran on to
   the true edge, and nothing shared a right margin with anything.

   THE FIX IS TO STOP HAVING TWO WIDTHS. The content column IS the measure now:
   `.wrap`, `.masthead-in` and `.band-in` are 680px, which is 620 of content
   inside 30px of padding either side. Everything then wraps at the same point
   **by construction rather than by being told to** — prose, publication rows,
   course descriptions, slide lists, the nav, the ancestry chain — on every
   page, because they are all bounded by the same column.

   ⚠️ 620px is 81 characters at 17px, above the 45–75 band. It is a deliberate
   compromise, stated so nobody "fixes" it: the design Jake approved over
   thirteen revisions ran at 91.5, my correction to 72 he found too narrow, and
   620 is the midpoint. With 1.68 line-height it holds up; without the generous
   leading it would not.

   ⚠️ AND ONE REAL COST. Course descriptions are 15px, so at this width they
   run to ~92 characters — the longest lines on the site. "Everything wraps at
   the same point" and "every block has a comfortable line length" cannot both
   be true when the type sizes differ, and Jake asked for the first. Recorded
   as a trade, not an oversight. */
:root{
  --measure:620px;      /* the content column. ≈81 characters at 17px Lora */
}
/* ⛔ These are the OLD per-size caps, deliberately not restored. They gave each
   block its own comfortable measure and therefore its own right edge, which is
   exactly what Jake objected to. If a future pass wants them back it is
   reversing an explicit instruction, not fixing an omission. */
.prose{max-width:none}
.course p{max-width:none}
.files{max-width:none}
.note{max-width:none}
.archived{max-width:none}

/* ---------- the home portrait moves to the RIGHT margin --------------------
   Jake, 2026-09-06: "I'm finding the landing page with the photo quite
   jarring ... are there alternative standard options?" Four placements were
   built and rendered side by side — `scripts/portrait_compare.py`.

   🔁 **B was tried and REVERTED, on Jake's look: "That looks bad."** The
   portrait stays in the LEFT margin, as it always was. Recorded here rather
   than deleted, because the argument for moving it was measured and correct
   and someone will make it again:

     - With the portrait on the left, the home page's text begins **190px right
       of where text begins on every other page**, and right of the nav
       directly above it. Home is the page a visitor meets first and it is the
       one page whose left edge does not line up.
     - The portrait fills 155px of a 574px column, so **73% of that column is
       empty**.

   ⛔ **Both are true and neither settles it.** Seen at full size, a right-hand
   portrait reads worse than the misalignment it fixes. **This is what the
   comparison page is for** — the argument was sound and the result was wrong,
   which is not something a measurement can tell you.

   ⚠️ C — filling the column with affiliations under the photograph — was the
   option Jake liked in principle and it failed on content, not layout: *"the
   text below makes no sense and is a bit silly."* Correct. Everything I could
   put there (La Trobe, Melbourne; MCMP, Munich) is **already in the first
   paragraph**, so the column would repeat the sentence beside it. There is no
   non-redundant material for that column, and that is the real reason the
   photograph sits alone.

   ⛔ Floating the image and letting the text wrap under it is the textbook fix
   for a long void, and it is NOT available here: this project tried it and
   rejected it — site.css § home, "two different measures and a left edge that
   jumped". Checked before proposing, per the standing rule.

   🔁 And the cap-height nudge went with it. I had pushed the photograph down
   14px so its top edge met the CAP of the first line rather than the top of
   the line's box. Jake, looking at it: "The top of the pic should be aligned
   with the top of the text, which it is not." ⭐ **Top-of-box is what he reads
   as aligned, and he is the reader** — so the photograph is back on
   `align-items:start` with no nudge, exactly as it was through all thirteen
   revisions. (For the record, the nudge was also 2px over: measured from real
   font metrics, Lora's cap sits 11.9px below the line-box top, not 14.)

   ⚠️ The gutter is 32px, up from 26. Jake asked for it to "revert to what it
   was" — but it never changed: 26px, declared and rendered, before and after.
   What changed is the column BESIDE it. The measure fix narrowed the text from
   630px to 548, so the same 26px now sits against a smaller block with 82px of
   space on its other side, and reads tighter than it did. **The perception was
   right and the diagnosis was wrong**, which is worth recording, because
   "revert it" would have changed nothing and looked like ignoring him.
   32px is the site's existing intra-section gap, so this is the scale it
   already uses rather than a number chosen to taste. */
/* The bio keeps its portrait gutter, and `1fr` lets the prose run to the same
   right edge as everything else — the indent is on the LEFT only. */
.bio{grid-template-columns:124px minmax(0,1fr);gap:0 32px}

/* ═══ ONE COLUMN, AND IT IS THE MEASURE ═══════════════════════════════════
   This is what makes every block on every page wrap at the same point. The
   cap is on the CONTAINER, so nothing inside needs its own — and nothing
   inside can drift out of line by being forgotten. `.band-in` is included so
   the two workshop pages' banner matches the four nav pages' masthead. */
.wrap,.masthead-in,.band-in{max-width:calc(var(--measure) + 60px)}

/* ---------- a heading must sit closer to its own text than to the next ------
   Measured, before this change: 48px between a section heading and its first
   paragraph, against 52px between one whole section and the next. Those are
   the same distance to any reader, so the headings floated between two
   sections instead of belonging to one.

   ⛔ Nobody wrote 48. `.panel` is a flex column with `gap:32px` and the
   heading carried its own `margin-bottom:16px`, and the two ADD. That is why
   it survived review: neither number looks wrong on its own.

   Dropping the margin leaves the gap to do the work alone — 32px inside a
   section against 52px between them, a ratio of 1:1.6 that reads instantly. */
/* ⚠️ Scoped to a DIRECT child of the panel — `.panel>h2.eyebrow`, not a bare
   `h2.eyebrow` — because the flex gap that replaces this margin only exists
   for the panel's own children. See the exception immediately below, which is
   what a bare selector broke. */
.panel>h2.eyebrow{margin-bottom:0}
/* ⛔ EXCEPT on the home page, where the heading is NOT a child of the panel.
   Jake, 2026-09-06: "the Philosophical ancestry diagram is a bit close to the
   section title/hairline." He is right, and it is this rule that did it.

   "Philosophical ancestry" sits INSIDE `.lineage`, a plain bordered div — not
   in the flex column — so there is no 32px gap standing behind it. Removing
   the margin left the chain sitting directly under the heading's own hairline
   with nothing between them at all.

   ⚠️ The lesson generalises past this one element: `margin-bottom:0` is only
   safe where a flex `gap` is already supplying the space, and a selector that
   does not say so will find the one place that is not a flex child. Any future
   heading placed outside `.panel` needs its own margin, as this one does.

   22px, matching `.lineage`'s own top padding, so the block reads as evenly
   inset above and below its heading. */
.lineage h2.eyebrow{margin-bottom:22px}

/* ---------- the course meta line was grouped with the wrong thing ----------
   Measured: 8px above "Leuven 2012; Undergrad" and 3px below it. Space is what
   tells a reader what belongs to what, and that spacing said the line belongs
   to the paragraph under it rather than the course title it annotates —
   exactly backwards.

   Reversed, and the whole block now reads as two groups rather than four
   loose lines: title+meta at 3px, a 12px break, then description+slides at
   8px, with 30px and a hairline to the next course. 3 < 8 < 12 < 30. */
.course h3.sub{margin-bottom:0}
.course p:not(.where){margin-top:9px}

/* ---------- the course meta line was never getting its own rule ------------
   Jake, 2026-09-06: "the font below the teaching subject titles ... is a touch
   too large."

   ⛔ It was rendering at 15px in --ink-mid. Its own rule asks for 11px in
   --ink-soft. **`.where` is (0,1,0) and `.course p` is (0,1,1)** — a class
   plus an element beats a class alone, so the generic paragraph rule was
   quietly overriding the specific one. Only the properties `.course p` does
   not mention — the monospace face, the tabular figures, the nowrap — were
   getting through, which is exactly why it looked deliberate.

   ⭐ The tell was on another page. The SAME class renders at 11px on
   /kr-conventicle-pricai2019/, where there is no `.course p` to lose to. One
   class, two sizes, two pages — which is the cross-page inconsistency Jake
   asked about in the same breath, showing up before anyone went looking.

   11px also puts it back in rank with `.files` directly beneath it, which is
   the same kind of thing — meta, mono, quiet — and has been 11px all along.
   At 15 against 11 they read as two ranks for no reason. */
.course p.where{font-size:11px;color:var(--ink-soft);line-height:1.68}
.files{margin-top:5px}

@media (max-width:620px){
  /* ⚠️ Size only. The margin-bottom that used to sit here is deleted rather
     than zeroed: this block comes AFTER the proximity fix in source order and
     is more specific than nothing, so any value left here would govern at
     phone width alone — 12px would have quietly reinstated the double gap
     (12 + the panel's 32 = 44 against 52) where nobody would look for it, and
     0px would have overridden the ancestry heading's exception the same way.
     Letting the two rules above apply at every width is the safe shape. */
  h2.eyebrow{font-size:19px}
  h3.sub{font-size:17.5px}
  /* ⭐ 57px — the first nav item's width at this size, re-measured rather than
     scaled from the desktop 72. The nav drops to 12.5px here, so "Home" plus
     its padding is narrower; "Research".left is a flat 77 against a nav left
     edge of 20 at every width from 320 to 620px.
     ⛔ Using 72 here would push the name 15px past "Research". */
  .wordmark{grid-template-columns:57px 1fr}
  .wordmark h1{font-size:27px}
  .wordmark p{font-size:12.5px;margin-top:2px}
  /* Same reading at 390px, against a shallower masthead: 14px of padding
     plus the link's 7px is 21px below the nav, so 16 + 7 = 23 above it. */
  nav.site{margin-top:16px}
  nav.site a,nav.site a.cv{font-size:12.5px;letter-spacing:.08em;padding:7px 11px 7px 0}
  nav.site a[aria-current="page"]::after{left:0;right:11px}
  /* ⛔ No `margin-left:auto` on CV at this width. The larger nav no longer
     fits five items on one line at 390px, and an auto margin on a wrapped
     item does not keep it beside the others — it strands it alone at the far
     right of a second row, reading as a stray link rather than part of the
     nav. In normal flow it simply follows CONTACT and shares the left edge. */
  /* ⛔ Still NOT `auto` at 390px, and this is the case that shows why the
     desktop rule is about the box rather than the declaration: here the nav
     wraps to two lines, and an auto margin on a WRAPPED item does not keep it
     beside the others — it strands it alone at the far right of the second
     row. In flow, with its own gap, it simply follows CONTACT. */
  nav.site a.cv{margin-left:26px;padding-right:0}
  .masthead-in{padding:20px 20px 14px}
  main{padding:38px 0 64px}

  /* ---------- F32: the portrait stops eating the measure on a phone --------
     Found 2026-09-06 (C7) by RENDERING the home page at 390px and reading it.
     On a 390px screen the 124px portrait gutter plus its 32px gap took 156px
     of a 350px content column, leaving the prose 194px — about 23 characters
     per line, against ~41 on every other page.

     ⛔ AND THE FIRST DIAGNOSIS WAS WRONG, WHICH IS THE PART WORTH KEEPING.
     It was written up as "the portrait's column has no rule telling it to
     stop". There IS such a rule, and it has been in the stylesheet all along:
     the preview carries `@media (max-width:620px){.bio{grid-template-columns:
     1fr;gap:20px}.portrait{width:108px}}`. The preview always meant the
     portrait to stack on a phone.

     📌 IT LOST THE CASCADE. `site_css()` lifts the preview's CSS and this file
     then appends its refinements after it — including the unconditioned
     `.bio{grid-template-columns:124px minmax(0,1fr);gap:0 32px}` above, which
     re-declares `grid-template-columns` at EVERY width and so beats the media
     query on source order.

     ⚠️ And it leaked exactly the way the root CLAUDE.md says: "a CSS rule that
     loses the cascade does not vanish — it leaks whichever properties the
     winner does not declare, and the result looks designed." Nothing
     re-declared `.portrait{width:108px}`, so the portrait DID shrink to 108px
     at phone width. A smaller photograph in a margin column reads as a
     considered mobile treatment. That is why five sessions of CSS review
     walked past it: the page did not look broken, it looked deliberate.

     📌 Same root cause as findings F16–F31 — "the preview was never a
     specification of the built pages" — and the ninth instance of it. The
     other eight were transformations that changed meaning; this one is an
     override that silently un-did a rule the preview had got right.

     ⚠️ 23 is the OPTIMISTIC figure. It was measured with fallback faces,
     because the measuring environment could not fetch the webfonts, and
     PROPORTIONS_AUDIT_2026-09-06.md established Lora is ~22% wider than an
     average character. The real count is lower.

     📌 The irony is worth keeping: PROPORTIONS_AUDIT was a measured hunt, the
     same week, for a measure that was too WIDE — 91 characters, site-wide. The
     home page at phone width was wrong by more than that margin in the other
     direction, and was missed because it was the one page nobody re-opened.

     ⛔ THIS DOES NOT TOUCH THE DESKTOP DECISION. Jake chose the LEFT margin on
     2026-09-06 after four placements were built and rendered side by side
     (scripts/portrait_compare.py), having reverted option B on sight. Above
     620px this block does not apply and .bio keeps `124px minmax(0,1fr)`
     exactly as it was. Verified by re-measuring at 1200px, not by reasoning.

     ✅ THE FIX RESTORES THE PREVIEW'S OWN INTENT rather than inventing a
     treatment: one column, and the preview's 20px gap, not a number chosen to
     taste. `.portrait{width:108px}` is left to the preview rule that already
     sets it and has been working the whole time.

     🔁 The dead rule in the preview block is deliberately NOT deleted. It is
     the record of what was agreed, and it becomes live again for anyone who
     removes the override above. Deleting it would leave the phone layout
     resting on this one line alone.

     `justify-self:start` is stated rather than assumed. A replaced element's
     stretch behaviour in a one-column grid is precisely the sort of thing that
     varies between engines, and the failure mode would be a 124px portrait
     blown up to the full column width — loud, but only if someone looks. */
  .bio{grid-template-columns:minmax(0,1fr);gap:20px 0}
  .bio .portrait{justify-self:start}
}

/* ---- the portrait's hairline (2026-09-06, C8) --------------------------- */
/* Jake's call, after seeing the near-white backdrop against the page ground:
   "a hairline border would work".

   ⛔ WHY IT IS NEEDED NOW AND WAS NOT BEFORE. The photograph's backdrop used to
   be studio grey, ~#7F. It is now near-white, and the page ground is #FCFCFD,
   so in LIGHT MODE the image's own edge had nothing to define it — the portrait
   dissolved into the page. That reads well as a cut-out and badly as a
   rectangle whose corners you can only just see.

   ⭐ BLUE, NOT GREY — Jake, 2026-09-06: "maybe using the blue colour?" It is
   `--accent`, the same token as the links and as the lattice mark's minimum,
   so the three blue things on the page are one decision rather than three.
   Four options were rendered before choosing (grey `--rule`, `--accent`,
   `--accent-soft`, and none): build/portrait_hairline_options.html.

   ⭐ AND IT INVERTS PROPERLY, which grey did not. `--accent` is #2F52AD on the
   light ground and LIGHTENS to #93A9EE on the dark one, so the line stays
   visible in both. `--rule` goes to #272B36 in dark mode, where it would have
   all but vanished against a near-white photograph — the hairline would have
   existed only in light mode while looking, in the source, like it applied to
   both.

   ⚠️ It is a stronger mark than a neutral rule: at 1px it reads as deliberate
   framing rather than as an edge. 📌 If the dark-mode block ever needs
   softening as well, that is a separate change — a radius or an inset shadow —
   not a darker line.

   ⛔ box-sizing is stated. The site sets `box-sizing:border-box` once, globally,
   and this rule depends on it: without it the border would make the element
   126px inside a 124px grid column. Asserting it here means the dependency is
   visible at the point of use rather than inferred three files away. */
.portrait{border:1px solid var(--accent);box-sizing:border-box}

/* ⭐ A TOUCH THINNER ON HI-DPI, Jake 2026-09-06: "make the line a touch thinner
   though". A true half-pixel hairline — which is what a retina screen can
   actually draw and what he is looking at.

   ⛔ NOT DECLARED AS A FLAT `0.5px`, and the media query is the whole point.
   Sub-pixel border widths are rendered inconsistently at 1x: some engines round
   0.5 up to a full pixel, some round it DOWN TO ZERO and the border disappears
   entirely. ⚠️ On his own machine either behaviour looks identical to success,
   because it is retina — the failure would only ever appear on someone else's
   1x display, which is the class of defect this project has been bitten by
   repeatedly (it is F16/F20/F32's shape: correct where it was checked, wrong
   where it was not).

   ✅ So 1px stays the floor everywhere, and only a display that can resolve
   half a pixel is offered one. */
@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:1.5dppx){
  .portrait{border-width:.5px}
}

/* ---- teaching: the three current subjects (2026-09-06) ------------------ */
/* ⛔ Added WITH the markup that uses them. Three new classes went into the
   build before this block existed and every one of them rendered unstyled —
   caught by grepping the built CSS for each new class name, not by reading
   the template. `.week` was then folded into `.course`; these two remain. */

/* ⛔ `.course p.more` lived here for one afternoon and is gone with the
   per-subject pages it linked to. Left as a note rather than as a rule: dead
   CSS is what a later session mistakes for a styling that is in use. */

/* A slide deck or video that EXISTS but has nowhere to live yet
   (state/VIDEO_HOSTING.md). It sits inside .files, so it already inherits the
   11px monospace and --ink-soft; all this adds is the dotted underline that
   says "not a link, and not an oversight". ⚠️ Deliberately NOT styled to look
   disabled or broken — the material is real, the URL is pending. */
.files .pending{border-bottom:1px dotted var(--rule);opacity:.75}