/* ============================================================================
   Blade Runner title-card theme.

   The 1982 main titles are set entirely in Goudy Oldstyle on black. Three
   devices carry the whole design, and this stylesheet implements exactly those
   three and nothing else:

     1. ALL CAPS, letterspaced, for a card that introduces a proper name.
     2. Small caps for a proper name introduced inside running text.
     3. Red, once, for the thing being named — and never again.

   The crawl itself is set like a book: sentence case, first-line indents, no
   space between paragraphs, generous word spacing, ragged right.

   Letterspacing is applied to capitals and small capitals only. Never to
   lowercase. (Goudy himself is supposed to have had opinions about the men who
   do that to lowercase.) Every tracked rule below therefore also sets
   `text-transform: uppercase` or `font-variant-caps`, so the two travel
   together and a future edit cannot separate them.
   ========================================================================= */

/* --- Typeface -------------------------------------------------------------
   Self-hosted, not Google-served, for one load-bearing reason: Google's
   subsetter strips the OpenType layout tables down to
   `liga/locl/lnum/frac/numr/dnom` and the small-caps feature (`smcp`, `c2sc`)
   does not survive. `font-variant-caps` against that subset falls back to
   browser-synthesised small caps — scaled-down capitals, too light against the
   lowercase, which is the one thing this design cannot afford. The upstream
   OFL font carries 121 real small-cap glyphs. Both faces together are ~72 KB,
   less than the two Google families they replace, and cost no third-party
   request. Licence travels with them in assets/fonts/OFL.txt.

   Note: upstream ships `smcp` on the roman only. The italic has no small caps,
   so `.sc` nested inside italic text will synthesise. Avoid that combination. */
@font-face {
  font-family: 'Sorts Mill Goudy';
  src: url('/assets/fonts/SortsMillGoudy-Regular.woff2') format('woff2');
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Sorts Mill Goudy';
  src: url('/assets/fonts/SortsMillGoudy-Italic.woff2') format('woff2');
  font-style: italic;
  font-weight: 400;
  font-display: swap;
}

:root {
  /* Two measures, and only two. Both are in px on purpose: `ex` is font-relative,
     so the same `85ex` resolves to a different pixel width on every element that
     changes font-size — which is how the essay pages ended up with six different
     right edges. Text blocks get --measure; figures and data tables get the
     deliberate breakout at --measure-wide. Nothing else sets its own width. */
  --measure: 730px;
  --measure-wide: 1100px;
  /* Sidebar is 20% of the wrapper, content 80%, so this caps the content
     column at --measure-wide and keeps the nav from drifting away from the
     text on wide monitors. */
  --wrapper-max: calc(var(--measure-wide) / 0.8);

  /* --- Palette ----------------------------------------------------------
     Sampled from the title cards themselves rather than invented. The film's
     white is #ececec — very slightly off pure white, and neutral, not warm.
     The film's red is #980000, a genuinely dark blood red.

     That red is 2.34:1 against black. It fails WCAG at every size, so it is
     not usable as-is for anything that has to be read. Two reds resolve this
     without abandoning the look:

       --bleed  #b81419  3.15:1 — clears AA for large text (>=24px) only.
                Display use: the h1 marquee, and the debut of a `.term`, where
                the italic carries the emphasis and the colour is decoration.
       --ember  #e04a42  5.23:1 — clears AA for body text. Anything at reading
                size that signals through colour: links on hover, table
                accents, notes.

     Ratios are recomputed by hand if these ever change; see the typography
     conventions note. Do not reach for the authentic #980000 on live text. */
  --void:      #000000;
  --ink:       #e8e8e8;   /* 17.1:1 — body */
  /* Secondary text is WARM, not grey. The old --ink-dim was #9a9a9a: a neutral
     grey at 7.46:1 that read as switched-off rather than as a quieter voice,
     and was genuinely hard to read at caption size. #cbb8a9 is 10.97:1, half a
     stop brighter, and warm enough to be obviously a different voice from the
     neutral body ink rather than a dimmer copy of it. It is also the tint the
     figures use for their apparatus, so page and chart speak once. */
  --ink-2:     #cbb8a9;   /* 11.0:1 — captions, meta, secondary nav */
  --ink-dim:   #cbb8a9;   /* deprecated alias; use --ink-2 */
  /* Rules only, never text. Warm to match, so no grey survives anywhere. */
  --ink-faint: #4a4038;
  --bleed:     #b81419;   /* display red   */
  --ember:     #e04a42;   /* text-size red */

  /* The single tracking token. If a rule sets letter-spacing, it is this
     value, and the run it applies to is capitals. */
  --track: 0.14em;
}

/* Border-box everywhere, so padding and borders count *inside* a width rather
   than being added to it. Without this, blockquote's 1.4em indent, a list's
   40px marker gutter and the sidebar's 3% padding each push their element past
   the measure by exactly their own padding — three more ragged edges. */
*, *::before, *::after { box-sizing: border-box; }

html {
  font-family: 'Sorts Mill Goudy', 'Iowan Old Style', Georgia, serif;
}

body {
  margin: 0;
  /* 19px, not the 18px of the previous theme: Goudy Oldstyle has a notably
     small x-height, so it reads a size smaller than Spectral did at the same
     nominal font-size. 20px was tried first and read slightly loud on a wide
     monitor. */
  font-size: 19px;
  color: var(--ink);
  background-color: var(--void);
  padding-left: 10%;
  padding-right: 10%;
  padding-bottom: 5%;
}

/* Layout */
.site-wrapper {
  display: flex;
  align-items: flex-start;
  max-width: var(--wrapper-max);
  margin: 0 auto;          /* drop this line to keep the page flush left */
}

.sidebar {
  width: 20%;
  flex-shrink: 0;
  text-align: right;
  padding-right: 3%;
  padding-top: 4%;
  font-size: 0.8em;
}

/* The site name is a credit card: a person's name, in caps, in white —
   "HARRISON FORD", not "BLADE RUNNER". */
.site-name {
  margin-bottom: 2.4em;
  text-transform: uppercase;
  letter-spacing: var(--track);
  color: var(--ink);
}

/* Tracking adds space *after* the final letter too, which pushes right-aligned
   type away from its own right edge. Pull the column back by one track so the
   nav aligns optically rather than metrically. */
.sidebar { margin-right: calc(-1 * var(--track)); }

.sidebar a {
  /* Flex, not block, so the word and its mark are one row that ends flush
     right. The word is an anonymous flex item; it stays ragged-left while the
     marks form a true column down the right edge. */
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.55em;
  margin-bottom: 1.4em;
  /* Cancels the sidebar's optical pull, for these rows only. That pull
     compensates for tracking added after the final LETTER; a row ending in a
     mark has no trailing track to absorb, so without this the icon column
     would hang 0.14em past the edge the site name aligns to. */
  margin-right: var(--track);
  text-transform: uppercase;
  letter-spacing: var(--track);
  color: var(--ink-2);
  text-decoration: none;
}
.sidebar a:hover { color: var(--ink); }

/* You-are-here. --ember, not --bleed: this is 15px text, and only --ember
   clears AA at reading size (5.23:1 against the void; the display red is
   3.15:1 and is reserved for the h1 and for rules). It travels to --bleed on
   hover, the same direction the subscribe label and the footnote marker take.

   The mark inherits it for free -- every stroke in these drawings is
   currentColor -- so the whole row changes state as one object.

   Selecting on the attribute rather than a class means the visual state and
   the state announced to a screen reader cannot drift apart: there is only one
   thing to set. It outranks the :hover rule above on specificity, so a hover
   over the current page does not knock it back to plain ink. */
.sidebar a[aria-current="page"] { color: var(--ember); }
.sidebar a[aria-current="page"]:hover,
.sidebar a[aria-current="page"]:focus { color: var(--bleed); }

/* Nav marks. Drawn, not typed: Sorts Mill Goudy has no geometric shapes, so a
   glyph would silently fall back to a different face -- the same reason the
   subscribe block draws its own RSS mark rather than setting one.

   Every stroke is `currentColor`, so a mark takes its link's colour and travels
   with it on hover. There is no second hover rule to keep in sync.

   Sized in `em`, so they scale with the sidebar's 0.8em type rather than being
   pinned to a pixel size that only looks right at one zoom level. */
.navicon {
  flex: 0 0 auto;
  width: 1.15em;
  height: 1.15em;
  /* Optical centring. `align-items: center` centres the mark on the LINE BOX,
     but the visual mass of tracked capitals sits above that centre, so the mark
     reads a hair low without this. */
  position: relative;
  top: -0.05em;
}

/* --- The crawl ------------------------------------------------------------
   Set like a book. Ragged right, not justified: the crawl in the film is
   ragged, and justification plus the wide word spacing below would fight each
   other and open rivers. Hyphenation off for the same reason — a book of this
   period would break words, but a browser's hyphenation dictionary breaks them
   badly and the ragged edge is the point. */
.content {
  width: 80%;
  padding-top: 4%;
  line-height: 1.75;
  text-align: left;
  hyphens: none;
  word-spacing: 0.12em;
  counter-reset: footnote;
}

/* First-line indents carry the paragraph break, so the paragraphs close up.
   Scoped to `.content >` so that .dek, figcaption, blockquote and the
   generated resource pages keep their own spacing. The first paragraph after
   any heading is not indented — that is the book convention, and `p + p` gives
   it for free by only indenting a paragraph that follows another paragraph. */
/* Every paragraph in the column obeys the measure — that part is universal. */
.content > p { max-width: var(--measure); }

/* Book setting applies to PLAIN paragraphs only, and the `:not([class])` is
   load-bearing. `.content > p` is specificity 0-1-1, which beats a component's
   own `.dek { margin: … }` at 0-1-0 — so a blanket `margin: 0` here silently
   stripped the spacing from every deck, attribution and caption on the site and
   collapsed them against whatever followed. Matching only unclassed paragraphs
   means a component keeps its own margins by default, and nothing has to be
   listed here when a new one is added.

   The same restriction on both sides of the sibling selector gives the book
   rule for free: consecutive body paragraphs indent, and the first paragraph
   after a heading, deck or figure does not — because its predecessor is not a
   plain <p>. */
.content > p:not([class]) { margin: 0; }
.content > p:not([class]) + p:not([class]) { text-indent: 1.6em; }

/* Links: underlined always, so the signal never depends on colour alone. The
   resting rule is grey, not red — a dense link index (the Projects page runs to
   eleven) turned a red underline into the loudest thing on the page, which is
   the opposite of an accent used sparingly. Red is now spent only on hover, so
   it marks interaction rather than decoration. */
a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--ink-2);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
a:visited { color: var(--ink-2); }
a:hover, a:focus {
  color: var(--ember);
  text-decoration-color: var(--ember);
}

/* --- Title cards ---------------------------------------------------------- */

/* h1 is the marquee — the title of the work, in red, like "BLADE RUNNER". */
h1 {
  font-size: 2.4em;
  line-height: 1.15;
  margin: 0 0 0.8em 0;
  text-transform: uppercase;
  letter-spacing: var(--track);
  color: var(--bleed);
  font-weight: 400;
  word-spacing: normal;
}

/* h2 and h3 are credit cards — white caps. h2 takes full capitals; h3 takes
   small capitals, so the two ranks differ by case as well as size and the
   page never shows three sizes of the same shouting. */
h2 {
  font-size: 1.5em;
  line-height: 1.25;
  margin: 2em 0 0.6em 0;
  text-transform: uppercase;
  letter-spacing: var(--track);
  color: var(--ink);
  font-weight: 400;
  word-spacing: normal;
}

h3 {
  font-size: 1.15em;
  line-height: 1.3;
  margin: 2em 0 0.5em 0;
  font-variant-caps: all-small-caps;
  -webkit-font-variant-caps: all-small-caps;
  font-feature-settings: "smcp" 1, "c2sc" 1;
  letter-spacing: var(--track);
  color: var(--ink);
  font-weight: 400;
  word-spacing: normal;
}

h1, h2, h3 { max-width: var(--measure); }

/* --- The two emphasis devices ---------------------------------------------

   .sc    a proper name being introduced into running text. Real small caps
          from the font, tracked, same colour as the body. Use on FIRST
          mention only; afterwards the name is set plainly.

   .term  a term of art, italic on every appearance.
   .term.debut  its first appearance, additionally in red.

   This is the film's exact logic for "Replicant": italicised every time,
   red only on its debut. The italic is what carries the emphasis; the red is
   decoration on top of it, which is also why the low-contrast --bleed is
   acceptable here — remove the colour and nothing is lost. */
.sc {
  font-variant-caps: all-small-caps;
  -webkit-font-variant-caps: all-small-caps;
  font-feature-settings: "smcp" 1, "c2sc" 1;
  letter-spacing: var(--track);
}

.term { font-style: italic; }
.term.debut { color: var(--bleed); }

/* .uline  a clause the essay wants the reader to leave with, marked by a rule
   UNDER it rather than by red type. The type stays --ink; only the rule is red,
   which is the same licence blockquote's border already takes — a graphic may
   use the display red, readable text may not.

   The essays carry no links in running prose — a source is a superscript note,
   never an underlined phrase — so a rule under a clause can only mean emphasis.
   That policy is what makes this device readable, and it is worth keeping.

   The rule is nonetheless a BORDER rather than a text-decoration, so the two
   never share a channel even where they do meet: a link is drawn with
   text-decoration, and border and decoration stack instead of overwriting. On a
   clause that is somehow both, the reader sees two rules — the link's thin one
   tight under the baseline, the emphasis's thick red one below it — rather than
   one ambiguous mark. `padding-bottom` sets that gap; on an inline box it moves
   the border without moving the line, so nothing reflows.

   Applied to an <a>, the visited colour is suppressed: an emphasised clause
   should not go dim once read. */
.uline {
  border-bottom: 2px solid var(--bleed);
  /* Low enough to read as a mark rather than as an underline, not so low that
     the rule crowds the cap-height of the line beneath it. */
  padding-bottom: 0.14em;
}
a.uline, a.uline:visited { color: var(--ink); }

/* A run of full capitals set inline — a card-style name in body copy. */
.caps {
  text-transform: uppercase;
  letter-spacing: var(--track);
  font-size: 0.92em;   /* full caps at body size read too large next to Goudy's
                          small x-height; a hair down evens the colour */
}

/* Plain emphasis stays plain. In a single-typeface design with no bold weight
   available, italic is the only emphasis, and it should not also change
   colour — that was a habit from the two-typeface theme. */
em, i, dfn, var, cite { font-style: italic; color: inherit; }
strong, b { font-weight: 400; font-style: italic; color: var(--ink); }

ul, ol { max-width: var(--measure); }
li { margin: 10px 0; }

/* --- Numerals -------------------------------------------------------------
   Goudy Oldstyle's default figures are oldstyle (ranging) and proportional,
   which is correct in prose. In a data table they are neither aligned nor
   scannable, so tables switch to lining figures — which in this font are all
   exactly 527 units wide, i.e. already tabular. `tabular-nums` below is
   therefore belt-and-braces; the font has no `tnum` feature and does not need
   one. */
:is(table.data, .tablewrap table), .num, time { font-variant-numeric: lining-nums tabular-nums; }

/* Code */
pre, code, samp, tt, kbd {
  font-family: ui-monospace, 'DejaVu Sans Mono', monospace;
  font-size: 0.85em;
  color: var(--ink-2);
  word-spacing: normal;
}
pre { overflow-x: auto; max-width: var(--measure-wide); }

/* Custom elements retained for _ideas/post-template.md. Rethemed off the old
   #202080 blue, which sat at about 1.4:1 on black and was unreadable. */
rw  { font-style: italic; color: var(--ember); }
eqn { font-style: italic; color: var(--ink-2); }
lambda, biglambda { color: var(--bleed); }
biglambda { font-size: 42px; }

div.equation, span.equation { font-style: italic; color: var(--ink-2); }
div.equation { margin: 1.2em 0; }
div.equation em, span.equation em { color: var(--ember); font-style: normal; }
span.program { font-family: ui-monospace, monospace; }
span.rw { font-family: ui-monospace, monospace; font-style: italic; }

/* --- Essay / analysis components (long-form pieces with figures + tables) --- */
.dek {
  font-size: 1.2em;
  line-height: 1.5;
  color: var(--ink);
  hyphens: none;
  max-width: var(--measure);
  margin: 0 0 1.2em 0;
  text-indent: 0;
}
.meta {
  font-size: 0.8em;
  color: var(--ink-2);
  font-style: italic;
  max-width: var(--measure);
  margin-bottom: 2.4em;
  text-indent: 0;
}
.meta a, .dek a { color: inherit; }

/* One panel per row means the figures are portrait now, so they sit at the
   TEXT measure and share the prose's left and right edges. Only tables still
   break out to --measure-wide, which reads as a deliberate exception rather
   than as everything being a different width. */
/* A figure or a table is an interruption in the reading, and whitespace alone
   was not saying so: at this line-height the gap above a figure reads as a
   slightly generous paragraph break rather than as a change of register.
   A hairline above and below closes the block off, which is the book
   convention for setting a table into running text.

   The rules are --ink-faint, which the palette note reserves for rules and
   borders and forbids for text. They are the quietest mark available, and that
   is the point: they should register as structure, not as decoration competing
   with the red the page spends elsewhere. Padding, not margin, so the rule sits
   clear of the content it encloses. */
:is(figure, .tablewrap) {
  border-top: 1px solid var(--ink-faint);
  border-bottom: 1px solid var(--ink-faint);
  padding-top: 1.5em;
  padding-bottom: 1.5em;
}

figure { margin: 2.4em 0; max-width: var(--measure); }
figure img, figure svg { width: 100%; height: auto; display: block; }
/* A caption carries three things and used to set each at its own size: a
   small-caps finding, a paragraph of method, and the provenance line. Three
   sizes inside one block read as three unrelated things. Now there is ONE
   caption size; the finding is marked by small caps and the provenance by
   colour and position, not by shrinking the type further. */
figcaption {
  font-size: 0.86em;
  line-height: 1.6;
  color: var(--ink-2);
  hyphens: none;
  word-spacing: normal;
  margin-top: 0.9em;
  max-width: var(--measure);
}
figcaption strong {
  color: var(--ink);
  font-style: normal;
  font-variant-caps: all-small-caps;
  -webkit-font-variant-caps: all-small-caps;
  font-feature-settings: "smcp" 1, "c2sc" 1;
  letter-spacing: var(--track);
}

/* Shrink-to-fit, not fill. `width: 100%` on the table made every table 1088px
   wide whatever it held, so a three-column table of short values was spread
   across the full breakout width and read as a layout accident. Now the table
   sizes to its content between two bounds: never narrower than the prose
   measure, so a small table shares the text column's edges, and never wider
   than the breakout, past which the wrapper scrolls. */
.tablewrap { overflow-x: auto; margin: 2.4em 0; width: fit-content; max-width: var(--measure-wide); -webkit-overflow-scrolling: touch; }
/* The wrapper now draws the closing rule, so the last row must not draw one
   too — two hairlines a few pixels apart read as a mistake, not a border. */
:is(table.data, .tablewrap table) tbody tr:last-child td { border-bottom: 0; }
/* Kramdown emits a bare <table> for a markdown table, so a post written in
   Obsidian has no class to hang styling on. Matching `.tablewrap table` as well
   as `table.data` means an ordinary markdown table inside a .tablewrap div gets
   the full treatment with no extra markup — which is the difference between
   writing a post and fighting one. */
:is(table.data, .tablewrap table) {
  border-collapse: collapse;
  width: auto;
  font-size: 0.85em;
  line-height: 1.45;
  text-align: left;
  hyphens: none;
  word-spacing: normal;
}
:is(table.data, .tablewrap table) caption {
  text-align: left;
  /* Held to the prose measure so a long caption cannot be the thing that widens
     a narrow table. A `table-caption` box is then stretched back out to whatever
     width the table settles at, which is what we want: a caption spans its own
     table. Do NOT set `display: block` here — that makes the caption a normal
     block, which fills the wrapper and defeats the table's fit-content sizing. */
  max-width: var(--measure);
  color: var(--ink-2);
  font-size: 0.88em;
  line-height: 1.5;
  padding-bottom: 0.8em;
}
:is(table.data, .tablewrap table) th, :is(table.data, .tablewrap table) td { padding: 8px 14px; border-bottom: 1px solid var(--ink-faint); }
/* Column heads get the small-caps treatment rather than a colour or a weight —
   the font has no bold, and the accent is spent elsewhere. */
:is(table.data, .tablewrap table) thead th {
  color: var(--ink);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  -webkit-font-variant-caps: all-small-caps;
  font-feature-settings: "smcp" 1, "c2sc" 1;
  letter-spacing: var(--track);
  border-bottom: 1px solid #666;
}
:is(table.data, .tablewrap table) td.num, :is(table.data, .tablewrap table) th.num { text-align: right; }
/* For short label columns — dates, codes — that must not break mid-value. */
:is(table.data, .tablewrap table) td.nowrap, :is(table.data, .tablewrap table) th.nowrap { white-space: nowrap; }
:is(table.data, .tablewrap table) .hi  { color: var(--ember); }
:is(table.data, .tablewrap table) .neg { color: var(--ember); font-style: italic; }
:is(table.data, .tablewrap table) tbody tr:hover { background: #17120f; }  /* warm, to match the palette */

.note {
  border-left: 2px solid var(--bleed);
  padding: 0.2em 0 0.2em 1.2em;
  margin: 2em 0;
  color: var(--ink);
  hyphens: none;
  max-width: var(--measure);
  text-indent: 0;
}
.note p { text-indent: 0; }
.note strong {
  color: var(--ember);
  font-style: normal;
  font-variant-caps: all-small-caps;
  -webkit-font-variant-caps: all-small-caps;
  font-feature-settings: "smcp" 1, "c2sc" 1;
  letter-spacing: var(--track);
}

/* --- Post index (/posts/) ---------------------------------------------------
   A dated list, not prose: no first-line indent, no extra word spacing, and
   lining figures so the date column scans. The date is set in small capitals'
   register — dim and tracked — so the title is the only thing competing for
   attention on each row. */
.postlist {
  list-style: none;
  padding: 0;
  margin: 2em 0 0 0;
  max-width: var(--measure);
}
.postlist li {
  margin: 0 0 1.6em 0;
  word-spacing: normal;
}
.postlist time {
  display: block;
  font-size: 0.72em;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: 0.2em;
}
.postlist a { font-size: 1.05em; }
.postnote {
  display: block;
  font-size: 0.85em;
  color: var(--ink-2);
  margin-top: 0.15em;
}

/* A dateline for a page rather than for an item. Italic and small so it reads
   as apparatus rather than as the first sentence of the page — it is the kind
   of thing a reader consults, not something they read on the way in. */
.updated {
  font-style: italic;
  font-size: 0.82em;
  color: var(--ink-2);
  max-width: var(--measure);
  margin-top: -0.4em;
}

/* --- Project list ---------------------------------------------------------
   The posts index in a wider register: same date/title/blurb stack, plus a
   mark in a left gutter. The gutter is what makes it a different object rather
   than a longer version of the same one — ten rows of pure type read as a
   directory, and these are finished pieces of work.

   Grid, not float or flex: the marks need to hold ONE column whatever the
   titles do, and only a grid track guarantees that when a title wraps to two
   lines. 40px is the drawing's natural size, so the SVG is never resampled. */
.projectlist {
  list-style: none;
  padding: 0;
  margin: 2em 0 0 0;
  max-width: var(--measure);
}
.projectlist li {
  display: grid;
  grid-template-columns: 40px 1fr;
  column-gap: 1.4em;
  align-items: start;
  margin: 0 0 2.1em 0;
  word-spacing: normal;
}
/* Nudged to sit against the date rather than the top of the line box, which is
   taller than the tracked capitals inside it. */
.projectmark { display: block; padding-top: 0.15em; }
.projectmark svg {
  display: block;
  width: 40px;
  height: 40px;
  /* Quieter than the title at rest: the title is what you are choosing
     between, and ten marks at full ink would read as the loudest thing on the
     page. Same subordination the home page marks use. */
  color: var(--ink-2);
}
/* The mark is aria-hidden and sits outside the anchor, so it cannot inherit
   the link's own :hover. Driving it from the row restores the connection --
   :focus-within as well as :hover, or the mark would stay dim for anyone
   arriving by keyboard, which is precisely when a "you are here" cue helps. */
.projectlist li:hover .projectmark svg,
.projectlist li:focus-within .projectmark svg { color: var(--ember); }
.projectlist time {
  display: block;
  font-size: 0.72em;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: 0.2em;
}
.projectlist a { font-size: 1.05em; }

/* --- Stat row -------------------------------------------------------------
   A handful of headline numbers is a KPI row, not a bar chart — and when the
   ratio between them is 187,500:1 there is no honest way to draw a bar anyway.
   The numbers carry it; one accent marks the punchline and nothing else. */
.statrow {
  display: flex;
  flex-wrap: wrap;
  gap: 2.4em;
  margin: 2.4em 0 0.8em 0;
  max-width: var(--measure);
  word-spacing: normal;
}
.stat { flex: 1 1 8em; }
.statvalue {
  display: block;
  font-size: 2.1em;
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: lining-nums tabular-nums;
}
.statvalue.accent { color: var(--ember); }
.statlabel {
  display: block;
  font-size: 0.78em;
  line-height: 1.35;
  color: var(--ink-2);
  letter-spacing: var(--track);
  text-transform: uppercase;
  margin-top: 0.4em;
}
.statnote {
  font-size: 0.85em;
  color: var(--ink-2);
  max-width: var(--measure);
  margin: 0 0 1.6em 0;
}
.content > .statnote { text-indent: 0; }

/* --- Staged sequence -------------------------------------------------------
   An ordered process, drawn as one. The rule down the left is a single
   ::before on the list rather than a border per item, so it reads as one
   continuous line the numbered nodes sit on; the last item masks the tail of
   it so the line stops at the final node instead of running past it. Built
   from a real <ol> so the order is in the markup, not only in the paint. */
.stages {
  position: relative;
  list-style: none;
  counter-reset: stage;
  padding: 0 0 0 3.2em;
  margin: 2em 0;
  max-width: var(--measure);
  word-spacing: normal;
}
.stages::before {
  content: "";
  position: absolute;
  left: 0.85em;
  top: 0.55em;
  bottom: 0;
  width: 1px;
  background: var(--ink-faint);
}
.stages li {
  position: relative;
  margin: 0 0 1.9em 0;
  counter-increment: stage;
}
.stages li:last-child { margin-bottom: 0; }
/* Cover the rule below the final node. */
.stages li:last-child::after {
  content: "";
  position: absolute;
  left: -2.35em;
  top: 1.5em;
  bottom: -2em;
  width: 3px;
  background: var(--void);
}
.stages li::before {
  content: counter(stage);
  position: absolute;
  left: -3.2em;
  top: -0.1em;
  width: 1.7em;
  height: 1.7em;
  line-height: 1.6em;
  text-align: center;
  border: 1px solid var(--ink-faint);
  border-radius: 50%;
  background: var(--void);
  color: var(--ink-2);
  font-size: 0.8em;
  font-variant-numeric: lining-nums;
}
.stage-name {
  display: block;
  font-variant-caps: all-small-caps;
  -webkit-font-variant-caps: all-small-caps;
  font-feature-settings: "smcp" 1, "c2sc" 1;
  letter-spacing: var(--track);
  color: var(--ink);
}
.stage-q {
  display: block;
  color: var(--ink-2);
  font-style: italic;
  margin-top: 0.1em;
}

/* --- Image comparison (swipe) ---------------------------------------------
   Two images of identical extent stacked; the top one is clipped by width. The
   range input is the actual control — keyboard- and AT-operable — and the
   pointer handlers only write to it. */
.swipe {
  position: relative;
  width: 100%;
  max-width: var(--measure-wide);
  aspect-ratio: 1047 / 654;
  overflow: hidden;
  touch-action: none;
  cursor: ew-resize;
  user-select: none;
}
.swipe img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Both layers are full-size and identically positioned; only the clip moves.
   Sizing the top layer by width instead would make its image a percentage of a
   percentage — wider than the base whenever the column is narrower than
   --measure-wide — and the two would silently fall out of register. */
.swipe-top {
  position: absolute;
  inset: 0;
  clip-path: inset(0 50% 0 0);
}
.swipe-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  margin-left: -1px;
  background: var(--bleed);
  pointer-events: none;
}
.swipe-handle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 2px solid var(--bleed);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
}
/* The control itself sits across the figure, invisible but focusable. A visible
   focus ring appears on the handle when it is keyboard-focused. */
.swipe-range {
  position: absolute;
  left: 0;
  bottom: 0.6em;
  width: 100%;
  margin: 0;
  opacity: 0;
  height: 2.2em;
  cursor: ew-resize;
}
.swipe-range:focus-visible { opacity: 1; }

/* --- Figure provenance ----------------------------------------------------
   The last line of a figcaption, naming the file (and where useful the key)
   the numbers came from. Set a step below the caption so it reads as apparatus
   rather than as part of the finding. */
.prov {
  display: block;
  margin-top: 0.7em;
  /* --ink-dim, not --ink-faint: this is text, and the palette note above is
     explicit that --ink-faint is for rules and borders only. */
  color: var(--ink-2);
  font-size: 1em;          /* same size as the caption it closes */
  opacity: 0.82;           /* set back by tone, not by another size step */
}

.prov code {
  font-size: 0.95em;
  color: var(--ink);
}

.prov a { color: var(--ink-2); }
.prov a:hover { color: var(--ember); }

/* --- Annotated source list ------------------------------------------------
   A run of 26 bare links tells a reader nothing about which one answers their
   question. Each entry names the source and then what it is FOR, so the list
   works as an index into the argument rather than a bibliography. */
.sources {
  max-width: var(--measure);
  padding-left: 1.2em;
  color: var(--ink-2);
  font-size: 0.92em;
  line-height: 1.6;
}

.sources li { margin-bottom: 0.75em; }
.sources li::marker { color: var(--ink-2); }
/* The source's name carries the colour; the gloss after the colon stays dim. */
.sources a { color: var(--ink); }
.sources a:hover { color: var(--ember); }

/* --- Contents list --------------------------------------------------------
   Only on long reference pages (the methodology note), never on the essays:
   an essay that needs a table of contents to be navigable has a structure
   problem a nav cannot fix. Numbers come from the source order, so the list
   cannot drift out of step with the headings it points at. */
.toc {
  max-width: var(--measure);
  margin: 2em 0;
  padding-left: 1.1em;
  border-left: 1px solid var(--ink-faint);
}

.toc-head {
  margin: 0 0 0.5em;
  text-transform: uppercase;
  letter-spacing: var(--track);
  font-size: 0.78em;
  color: var(--ink-2);
}

.toc ol {
  margin: 0;
  padding-left: 1.3em;
  font-size: 0.94em;
}

.toc li { margin-bottom: 0.25em; }
.toc li::marker { color: var(--ink-2); font-variant-numeric: lining-nums; }
.toc a { color: var(--ink-2); text-decoration: none; }
.toc a:hover { color: var(--ember); }

/* A deep link drops the reader at the heading's very top edge, hard against the
   viewport. Give every anchorable heading room to breathe above it. */
:is(h2, h3)[id] { scroll-margin-top: 1.5em; }

/* --- Footnotes -------------------------------------------------------------
   One source of truth. The note is authored inline, next to the sentence it
   qualifies:

     the 1985 Supreme Court decision<span class="fn"><span class="fn-note">
     Metropolitan Life v. Massachusetts.</span></span> putting self-funded...

   The marker number is a CSS counter, so notes renumber themselves when one is
   added, moved or cut; there is nothing to keep in step by hand. Hover or focus
   opens the note in place. A script in the layout mirrors every note into a
   list at the foot of the page, so the set can be scanned without hunting
   through the prose.

   Why not title="": tooltips cannot be styled, appear on a delay the reader
   does not control, and do not exist on touch. Why not <details>: it is a block
   element and cannot sit inside a sentence.

   The note's containing block is the PARAGRAPH, not the marker: `.fn` is
   deliberately `position: static` so that `left` is measured from the text
   column's edge and every note lands at the same x. Anchoring to the marker
   instead put a note raised near the end of a line off the right of the
   viewport. `top` stays `auto`, which resolves to the abspos static position —
   the marker's own line — so the note still opens next to its sentence.

   Do not put a footnote inside .tablewrap: that scroll container would clip it. */

.fn {
  counter-increment: footnote;
  cursor: help;
}

/* The blocks a note can be raised from. Each becomes the note's containing
   block; without this the note would position against the page. */
.content :is(p, blockquote, figcaption, .note, ul, ol) { position: relative; }

.fn::before {
  content: counter(footnote);
  font-size: 0.68em;
  vertical-align: super;
  line-height: 0;
  color: var(--ember);
  font-variant-numeric: lining-nums;
  /* The marker follows the punctuation it closes, per Chicago, and sits tight
     against it. */
  margin-left: 0.06em;
}

.fn:hover::before,
.fn:focus::before { color: var(--bleed); }

.fn-note {
  display: none;
  position: absolute;
  left: 0;
  top: auto;
  margin-top: 0.6em;   /* clear of the line the marker sits on */
  z-index: 30;
  /* Never wider than the measure it hangs under, so it cannot leave the column. */
  width: 100%;
  max-width: var(--measure);
  padding: 0.7em 0.9em;
  background: var(--void);
  border: 1px solid var(--ink-faint);
  border-left: 2px solid var(--ember);
  color: var(--ink-2);
  font-size: 0.86em;
  line-height: 1.55;
  text-indent: 0;
  word-spacing: normal;
  cursor: auto;
}

.fn:hover .fn-note,
.fn:focus .fn-note,
.fn:focus-within .fn-note { display: block; }

/* --- The same note, in the margin -----------------------------------------
   Above 1500px there is room to the right of the 730px measure for the note to
   open BESIDE the sentence it qualifies instead of on top of the paragraph
   below it. Nothing about the markup changes; only where the box lands.

   Only two properties change. `left` becomes the measure plus a gutter — stated
   in px off the paragraph's own left edge, not as `100%`, because a
   blockquote's paragraph is narrower than a plain one by its indent and the
   notes would not line up. And the box loses its opaque backdrop and frame:
   nothing is being covered out there, so a red left rule is enough, which is
   what `.note` and blockquote already use for an aside.

   The note is allowed to overhang the content column into the page's outer 10%
   padding, the way a sidenote overhangs a book's trim. Nothing in the chain
   sets `overflow: hidden`, so it is not clipped. Below the gate the note opens
   as a panel under its own line instead.

   GATE LOWERED 1500 -> 1440 (2026-08-29), and the note narrowed 300 -> 280 to
   pay for it. Reason: this laptop's panel is 1920x1200 at scale 1.25, so its
   CSS viewport is 1536px — it cleared the old gate by 36px, which meant the
   margin note appeared only in a maximized window and silently fell back to the
   inline panel if the window was narrowed at all or the page zoomed. That reads
   as a bug rather than as a threshold.

   Measured in a headless browser against the longest note on the OneCare page,
   which is the worst case on the site (clearance = viewport minus the note's
   right edge, at clientWidth so the scrollbar is already excluded):

       width/gutter    1366px   1400px   1440px   1500px
       300 / 28         -45      -20      +10      +54     <- old
       280 / 24         -21       +4      +34      +78     <- now
       260 / 24          -1      +24      +54      +98

   280/24 at a 1440 gate keeps 34px of clearance where the old pair had 10, and
   costs 31px of note height on that worst-case note. 260px was rejected: it
   buys another 20px of clearance and makes the tallest note 729px, most of a
   laptop screen. Re-measure before changing either number — the two trade
   against each other and the gate is only correct for the pair below it. */
@media (min-width: 1440px) {
  .fn-note {
    left: calc(var(--measure) + 24px);
    width: 280px;
    max-width: none;
    margin-top: 0;
    background: none;
    border: 0;
    border-left: 2px solid var(--ember);
    padding: 0 0 0 0.9em;
  }
}

/* The mirrored list. Its numbers come from the same document order the counter
   walks, so list item N is marker N. */
.footnotes {
  max-width: var(--measure);
  margin-top: 1.2em;
  padding-left: 1.4em;
  color: var(--ink-2);
  font-size: 0.92em;
}

.footnotes li {
  margin-bottom: 0.7em;
  text-indent: 0;
}

.footnotes li::marker {
  color: var(--ember);
  font-variant-numeric: lining-nums;
}

.fn-back {
  text-decoration: none;
  color: var(--ink-2);
}
.fn-back:hover { color: var(--ember); }

@media (max-width: 600px) {
  .statrow { gap: 1.4em; }
  .statvalue { font-size: 1.7em; }
}

/* Quoted testimony / transcript extracts — deliberately recessive against .note,
   which uses the accent border for the essay's own asides. */
blockquote {
  /* The rule is red, not grey. It is a graphic, so the display red is allowed
     here even though it fails contrast for text; the quotation itself is set in
     the warm secondary ink and carries its own contrast. */
  border-left: 2px solid var(--bleed);
  padding: 0.2em 0 0.2em 1.4em;
  margin: 1.8em 0;
  color: var(--ink-2);
  font-size: 0.95em;
  hyphens: none;
  max-width: var(--measure);
}
blockquote p { margin: 0 0 0.8em 0; text-indent: 0; }
blockquote p:last-child { margin-bottom: 0; }
blockquote strong { color: var(--ink); font-style: normal; }

.pagenav {
  margin-top: 3em;
  padding-top: 1.4em;
  border-top: 1px solid var(--ink-faint);
  font-size: 0.9em;
  max-width: var(--measure);
  text-indent: 0;
}

/* Misc */
hr {
  border: 0;
  border-top: 1px solid var(--ink-faint);
  margin: 2.6em 0;
  /* A section rule belongs to the text it divides, not to the figure column. */
  max-width: var(--measure);
}

footer {
  font-size: 0.7em;
  font-style: italic;
  color: var(--ink-2);
}

#pdfdisplay {
  height: 70vh;
}

/* --- Career figure (index page) --------------------------------------------
   A span chart of five roles, drawn as INLINE svg rather than shipped as a
   file. Three reasons, all of them load-bearing:
     * it inherits Sorts Mill Goudy from `html`, so the figure is set in the
       page's own face without embedding or glyph-pathing anything;
     * `var(--ink)` and friends resolve inside it, so the figure tracks the
       palette instead of freezing a copy of it in hex;
     * there is no build step to forget. The project figures are matplotlib
       output; this one is five rectangles and does not earn a pipeline.

   NOTE for check-site.sh: its glyph scan strips <svg> blocks, so nothing here
   is covered by it. Keep the text to letters, digits and the middot -- no
   arrows, which this font does not have at all.

   Colour does no identity work. Every bar is labelled directly, and the only
   encoded distinction is state: a role that ended is --ink-2, the current one
   is --ink and fades out at the right edge rather than stopping. That is a
   lightness difference between two near-neutrals, so it survives every form of
   colour blindness untouched, and the red stays spent on the h1. */
.cvfig { margin: 2.4em 0 2.8em 0; max-width: var(--measure); }
/* Directly under its heading the figure is the section's opening, not an
   interruption in it, so it closes up. The generic figure margin assumes prose
   above and below. */
h3 + .cvfig { margin-top: 1.2em; }
/* Below the measure the label gutter would crush the bars, so the figure
   scrolls in place instead of reflowing -- same escape hatch as .tablewrap. */
.cvfig .cvscroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.cvfig svg { display: block; width: 100%; min-width: 620px; height: auto; }
.cv-grid { stroke: var(--ink-faint); stroke-width: 1; }
.cv-bar  { fill: var(--ink-2); }
.cv-bar-now { fill: url(#cv-now); }
#cv-now stop { stop-color: var(--ink); }
/* `word-spacing: 0.12em` is set on .content for the book setting, and it
   INHERITS into inline SVG. The gutter was sized from the font's own advance
   widths (fontTools, 276px for the longest label at 12px), which do not include
   it; the seven spaces in "Expert Business Intelligence Analyst - Burlington,
   VT" added ~10px and pushed that label off the left edge of the viewBox, where
   the SVG clipped it mid-letter. Any measurement taken from font metrics has to
   turn off the CSS that font metrics cannot see. */
.cvfig text { word-spacing: normal; }
.cv-org  { fill: var(--ink);   font-size: 12px; }
.cv-role { fill: var(--ink-2); font-size: 12px; }
.cv-year { fill: var(--ink-2); font-size: 11px; letter-spacing: 0.04em; }

/* --- Selected work marks (index page) ---------------------------------------
   Four links to project pages, each carrying a miniature of the FORM its
   figures take -- paired pyramids, a slopegraph, a divergence, a series with a
   cliff in it.

   Deliberately drawn rather than borrowed. Rendering the real figures at this
   size was tried first: at 250px the type is unreadable, so a thumbnail can
   only ever communicate a shape, and the two metro figures additionally drag
   their blue/orange/green categorical palettes onto a page that spends exactly
   one red. A drawn mark says the same thing the shrunk chart could say, in the
   page's own two inks.

   Strokes are `currentColor`, so the whole mark takes the link's colour and
   turns --ember on hover with no rule of its own. */
.marks {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.6em;
  margin: 1.8em 0 2.6em 0;
  max-width: var(--measure);
  word-spacing: normal;
}
.mark {
  display: block;
  color: var(--ink-2);
  text-decoration: none;
  border-top: 1px solid var(--ink-faint);
  padding-top: 0.9em;
}
.mark:visited { color: var(--ink-2); }
.mark:hover, .mark:focus { color: var(--ember); }
.mark svg { display: block; width: 100%; height: auto; overflow: visible; }
.mark-label {
  display: block;
  margin-top: 0.7em;
  font-size: 0.78em;
  line-height: 1.35;
  letter-spacing: var(--track);
  text-transform: uppercase;
}
/* The mark is decoration for a link whose text already names the target, so it
   is hidden from assistive tech rather than described twice. */

/* --- Subscribe block --------------------------------------------------------
   The feed existed from the first build -- jekyll-feed writes /feed.xml and the
   layout emits {% feed_meta %} for autodiscovery -- but the only visible link
   to it was one line on /posts/. Autodiscovery is a browser affordance most
   browsers stopped surfacing years ago, so in practice the feed was invisible.

   The icon is inline SVG, not a character: Sorts Mill Goudy has no geometric
   shapes, so a glyph would silently fall back to another face. */
.subscribe {
  border-top: 1px solid var(--ink-faint);
  border-bottom: 1px solid var(--ink-faint);
  padding: 1.4em 0;
  margin: 2.8em 0 0 0;
  max-width: var(--measure);
  display: flex;
  align-items: baseline;
  gap: 1.1em;
  text-indent: 0;
  word-spacing: normal;
}
.subscribe .rss {
  flex: 0 0 auto;
  width: 1.5em;
  height: 1.5em;
  /* Baseline alignment on a flex item aligns its bottom margin edge; nudge the
     mark down so it sits on the text baseline rather than above it. */
  position: relative;
  top: 0.28em;
  /* Matches the label rather than the surrounding secondary ink, so the mark
     and the word read as one control. A graphic may take the display red, but
     --ember keeps it identical to the text it sits beside. */
  color: var(--ember);
}
/* The icon link has no text, so an underline would be a stray rule under a
   picture. The colour shift on hover carries the affordance instead, and the
   adjacent worded link is the accessible route regardless. */
.rss-link { text-decoration: none; }
.subscribe a:hover .rss, .subscribe a:focus .rss { color: var(--bleed); }
.subscribe-body { flex: 1 1 auto; }
/* The one place on the site where red is the RESTING colour of a piece of text
   rather than a hover state. It is --ember, not --bleed: at 5.23:1 it clears AA
   for body text, which the display red does not, and this is set at reading
   size. No underline -- the small caps and the mark beside it already say
   "link", and a rule under tracked capitals reads as a mistake. Hover moves it
   to --bleed, which is the same direction the footnote marker travels. */
.subscribe-label {
  display: block;
  font-variant-caps: all-small-caps;
  -webkit-font-variant-caps: all-small-caps;
  font-feature-settings: "smcp" 1, "c2sc" 1;
  letter-spacing: var(--track);
  color: var(--ember);
  text-decoration: none;
}
.subscribe-label:visited { color: var(--ember); }
.subscribe-label:hover, .subscribe-label:focus { color: var(--bleed); }
.subscribe-note {
  display: block;
  font-size: 0.85em;
  line-height: 1.5;
  color: var(--ink-2);
  margin-top: 0.25em;
}

/* Mobile */
@media (max-width: 600px) {
  body {
    font-size: 18px;
    padding-left: 5%;
    padding-right: 5%;
  }

  .site-wrapper {
    flex-direction: column;
  }

  .sidebar {
    width: 100%;
    text-align: left;
    padding-right: 0;
    padding-top: 2%;
    padding-bottom: 2%;
    border-bottom: 1px solid var(--ink-faint);
  }

  .site-name { margin-bottom: 0.8em; }

  /* Here the nav is a wrapped horizontal row, so vertical space between the
     lines is the CONTAINER's line-height -- vertical margins on an inline-level
     box do not open up a line box. 1.9 rather than the inherited value because
     each link now carries a mark as well as a word, and at the default the two
     wrapped lines met with no gap at all. */
  .sidebar { line-height: 1.9; }

  /* inline-FLEX, not inline: the desktop rule above makes each link a flex row
     of word + mark, and plain `inline` would drop the mark to its own line. */
  .sidebar a {
    display: inline-flex;
    margin-right: 1.4em;
    margin-bottom: 0;
  }

  .content {
    width: 100%;
    padding-top: 4%;
  }

  h1 { font-size: 1.9em; }
  h2 { font-size: 1.35em; }
  h3 { font-size: 1.1em; }

  /* Four marks across is 90px each at this width -- below the point where the
     drawn form is legible. Two up, two down. */
  .marks { grid-template-columns: repeat(2, 1fr); gap: 1.4em; }
}
