/* ============================================================================
   TOKENS — the coffee system.
   Matte black with a warm roast undertone; crema for ink; brass for emphasis.
   Dark is the primary design. Light is a considered second cut, not an
   inversion — the paper warms rather than bleaching to white.
   ========================================================================= */

:root {
  /* — surface, darkest to lightest — */
  --void:        #0A0807;  /* the matte black. warm, never blue. */
  --bed:         #100C0A;  /* page ground */
  --raise:       #171210;  /* cards, callouts, code */
  --raise-2:     #1F1815;  /* nested surfaces */
  --edge:        #251D19;  /* hairlines */
  --edge-strong: #362B24;

  /* — ink — */
  --crema:       #EDE4D8;  /* body text: warm bone, not white */
  --crema-dim:   #A2907E;  /* secondary, captions */
  --crema-faint: #6E6055;  /* tertiary, numerals, furniture */

  /* — accent: brass, used sparingly and never decoratively — */
  --brass:        #C08A4E;
  --brass-bright: #DDA968;
  --brass-sunk:   #8A6236;
  --brass-wash:   rgba(192, 138, 78, 0.10);

  /* — signal — */
  --oxblood:  #8A4038;  /* gated / restricted */
  --verdigris:#4E7C6B;  /* verified / resolved */

  --selection: rgba(192, 138, 78, 0.24);

  /* — type — */
  --serif: "Source Serif 4", "Source Serif 4 Subset", Charter, "Bitstream Charter",
           "Sitka Text", Cambria, Georgia, serif;
  --mono:  "JetBrains Mono", "JetBrains Mono Subset", "Cascadia Code",
           ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Modular scale, 1.200 (minor third) — restrained. Luxury is not loud. */
  --t--2: 0.694rem;
  --t--1: 0.833rem;
  --t-0:  1rem;
  --t-1:  1.200rem;
  --t-2:  1.440rem;
  --t-3:  1.728rem;
  --t-4:  2.074rem;
  --t-5:  2.488rem;

  /* Furniture sizes, in whole pixels rather than off the fluid scale.
     Small caps at a fractional cap height grid-fit unevenly — see the note in
     10-type.css. These are the sizes that are set in small caps and sit
     outside the prose. */
  --f-mark: 18px;   /* the wordmark */
  --f-nav:  15px;   /* nav, account menu, buttons */
  --f-meta: 13px;   /* dates, tags, the gate label */
  --f-fine: 12px;   /* the smallest labels */

  /* Vertical rhythm. Every vertical metric is a multiple of --lead. */
  --lead: 1.62;
  --rhythm: calc(var(--t-0) * var(--lead));

  /* Measure: 66 characters. The single most consequential number here. */
  --measure: 66ch;
  --margin-col: 12rem;   /* sidenote column: narrow, so the prose sits closer
                            to the centre of the page than to its left edge */
  --gutter: 2.5rem;
  /* How far a code block may reach past the measure on each side. Code is not
     prose: it is read in lines that do not wrap, and a little more room costs
     the measure nothing because nothing else sits there. */
  --code-bleed: 1.5rem;

  --rule: 1px;
  --radius: 2px;         /* almost none. matte, machined, not soft. */

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Light: warm paper. Roles swap consistently; nothing becomes pure #FFF. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --void:        #EDE6DA;
    --bed:         #F5F0E8;
    --raise:       #FBF8F2;
    --raise-2:     #FFFDF9;
    --edge:        #DED3C2;
    --edge-strong: #C4B49E;

    --crema:       #1A1310;
    --crema-dim:   #574A40;
    --crema-faint: #857463;

    --brass:        #8A5A20;
    --brass-bright: #6E4718;
    --brass-sunk:   #B08A55;
    --brass-wash:   rgba(138, 90, 32, 0.08);

    --oxblood:   #8A3028;
    --verdigris: #2F5A4A;

    --selection: rgba(138, 90, 32, 0.18);
  }
}

:root[data-theme="light"] {
  --void:        #EDE6DA;
  --bed:         #F5F0E8;
  --raise:       #FBF8F2;
  --raise-2:     #FFFDF9;
  --edge:        #DED3C2;
  --edge-strong: #C4B49E;

  --crema:       #1A1310;
  --crema-dim:   #574A40;
  --crema-faint: #857463;

  --brass:        #8A5A20;
  --brass-bright: #6E4718;
  --brass-sunk:   #B08A55;
  --brass-wash:   rgba(138, 90, 32, 0.08);

  --oxblood:   #8A3028;
  --verdigris: #2F5A4A;

  --selection: rgba(138, 90, 32, 0.18);
}

:root[data-theme="dark"] {
  --void: #0A0807; --bed: #100C0A; --raise: #171210; --raise-2: #1F1815;
  --edge: #251D19; --edge-strong: #362B24;
  --crema: #EDE4D8; --crema-dim: #A2907E; --crema-faint: #6E6055;
  --brass: #C08A4E; --brass-bright: #DDA968; --brass-sunk: #8A6236;
  --brass-wash: rgba(192, 138, 78, 0.10);
  --oxblood: #8A4038; --verdigris: #4E7C6B;
  --selection: rgba(192, 138, 78, 0.24);
}

/* ============================================================================
   THE TYPOGRAPHY ENGINE
   Everything here exists to make justified text on a screen approach what
   Typst or TeX gets on a page. CSS provides the floor; Justif (30-justif.js)
   provides the Knuth–Plass ceiling as progressive enhancement.
   ========================================================================= */

/* — faces. Variable, so one file per style covers the whole weight range. — */

@font-face {
  font-family: "Source Serif 4";
  src: url("/assets/fonts/SourceSerif4-Roman.subset.80818c5642.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-stretch: normal;
  font-style: normal;
  font-display: swap;
  /* opsz is exposed to the browser, which drives it from the computed size. */
}

@font-face {
  font-family: "Source Serif 4";
  src: url("/assets/fonts/SourceSerif4-Italic.subset.5a0a9a49ec.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("/assets/fonts/JetBrainsMono.subset.91ee592466.woff2") format("woff2-variations");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("/assets/fonts/JetBrainsMono-Italic.subset.9c29d5b0ce.woff2") format("woff2-variations");
  font-weight: 100 800;
  font-style: italic;
  font-display: swap;
}

/* — the base — */

html {
  font-size: clamp(16.5px, 0.94rem + 0.28vw, 20px);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  background: var(--bed);
  color: var(--crema);
  font-family: var(--serif);
  font-size: var(--t-0);
  line-height: var(--lead);
  font-weight: 400;

  /* Let the browser drive the opsz axis from the computed size. This is the
     whole point of a variable text face: 9pt captions get a sturdier, more
     open cut than 36pt display, automatically. */
  font-optical-sizing: auto;

  /* Old-style figures in running prose — numerals should sit in the text,
     not stand above it shouting. Tabular lining figures are switched back on
     locally wherever numbers need to align (tables, metrics). */
  font-variant-numeric: oldstyle-nums proportional-nums;
  font-variant-ligatures: common-ligatures contextual;

  /* Kerning on, always. */
  font-kerning: normal;

  /* `optimizeLegibility` was here and did nothing: kerning and ligatures are
     already switched on by name above, which is all it would have bought. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Furniture is set in whole pixels.
 *
 * The reading sizes are a fluid scale off a `clamp()`ed root, so they land on
 * fractional pixels — 0.833rem of 18.62px is 15.51px. A rasteriser rounds each
 * glyph's outline on its own, and at a fractional cap height they do not all
 * round the same way: one letter comes out a pixel short of the ones beside
 * it, which in a letter-spaced small-caps run is very easy to see. Whole
 * pixels rounds every glyph identically.
 *
 * Only the furniture. Small caps *inside* prose — `.sc`, `abbr`, `h4`, the
 * lede's opening phrase — stay proportional to the text around them, because
 * a fixed size next to fluid text is the worse fault.
 *
 * `text-rendering` is left alone. `geometricPrecision` is what produced the
 * uneven run: it turns off the hinting that keeps a whole word on one set of
 * pixel rows. Measured in Chromium at several sizes; `auto` is uniform and
 * `geometricPrecision` is not. */

/* — prose — */

.prose {
  max-width: var(--measure);
  /* Hanging punctuation is real optical margin alignment where supported
     (Safari). Justif does protrusion for everyone else. */
  hanging-punctuation: first allow-end last;
}

.prose p {
  margin: 0 0 var(--rhythm);
  text-align: justify;
  text-justify: inter-word;
  hyphens: auto;
  -webkit-hyphens: auto;
  hyphenate-limit-chars: 6 3 3;   /* don't hyphenate short words or leave stubs */
  hyphenate-limit-lines: 2;       /* at most 2 consecutive hyphenated lines */
  /* Browser's own paragraph-aware breaker. Justif supersedes it when it runs,
     but this is a genuinely good fallback on its own. */
  text-wrap: pretty;
  orphans: 2;
  widows: 2;
}

/* The opening paragraph of a main page: no indent, and small caps for the
   first few words — a considered entrance rather than a drop cap, which at
   screen sizes usually just looks like a mistake. */
.prose > p.lede {
  text-indent: 0;
  font-size: var(--t-1);
  line-height: 1.5;
  color: var(--crema);
}

/* The opening phrase in small caps.
 *
 * A real element, not `::first-line`. The pseudo-element version is what
 * produced `NOTESKEPTINTHEOPEN`: Justif measures a paragraph and writes a
 * fitted `word-spacing` onto each line, no API exposes the metrics of a
 * `::first-line`, so it measured a line it could not see and closed every gap
 * in it. A span is measured like any other run — the same way inline code and
 * inline maths are, both of which come out right — so the lede is justified
 * with everything else.
 *
 * It also stops the phrase changing when the window is resized, which is what
 * "whatever fits on the first line" meant in practice. */
/* Ragged, and outside Justif's selector: it measures a real small-caps run
   much wider than it renders, and the line then stretches to fill. See
   assets/js/justify.js, where that is localised property by property. */
.prose > p.lede { text-align: left; }

.lede__in {
  font-variant-caps: small-caps;
  font-variant-numeric: oldstyle-nums;
  letter-spacing: 0.02em;
}

/* Continental paragraph indentation: indent every paragraph *except* the one
   that starts a section. Indentation and vertical space are redundant, so
   pages set in `.prose--indented` drop the space and use the indent. */
.prose--indented p { margin-bottom: 0; text-indent: 1.4em; }
.prose--indented p.lede,
.prose--indented :is(h1,h2,h3,h4,figure,blockquote,pre) + p { text-indent: 0; }
.prose--indented :is(h1,h2,h3,h4,figure,blockquote,pre) + p::first-letter { }

/* — headings. Tight, brass-free, weight and tracking do the work. — */

h1, h2, h3, h4, h5, h6 {
  font-family: var(--serif);
  font-optical-sizing: auto;
  line-height: 1.12;
  text-wrap: balance;      /* headings get balanced rags, not pretty ones */
  hyphens: none;
  margin: 0;
  color: var(--crema);
}

h1 {
  font-size: var(--t-5);
  font-weight: 600;
  letter-spacing: -0.021em;   /* optical tightening at display size */
  margin-block: 0 calc(var(--rhythm) * 0.75);
}

h2 {
  font-size: var(--t-3);
  font-weight: 600;
  letter-spacing: -0.012em;
  margin-block: calc(var(--rhythm) * 2) calc(var(--rhythm) * 0.5);
}

h3 {
  font-size: var(--t-1);
  font-weight: 600;
  letter-spacing: -0.004em;
  margin-block: calc(var(--rhythm) * 1.5) calc(var(--rhythm) * 0.25);
}

/* h4 as a run-in sidehead, small caps — the LaTeX \paragraph move. */
h4 {
  font-size: var(--t-0);
  font-weight: 600;
  font-variant-caps: small-caps;
  letter-spacing: 0.045em;
  color: var(--crema-dim);
  margin-block: var(--rhythm) 0;
}

/* — inline — */

em { font-style: italic; }
strong { font-weight: 650; }        /* variable axis: not a jump to 700 */

/* Nested emphasis flips, as it should. */
em em, i i { font-style: normal; }

/* True small caps, never faked by scaling. */
.sc {
  font-variant-caps: small-caps;
  font-variant-numeric: oldstyle-nums;
  letter-spacing: 0.045em;
}

/* Abbreviations and acronyms get small caps automatically — an all-caps run
   in justified serif text is a hole in the page otherwise. */
abbr, .caps {
  font-variant-caps: all-small-caps;
  letter-spacing: 0.05em;
  text-decoration: none;
}

/* Numerals that must align: tables, metrics, line numbers. */
table td.num {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "zero" 1;   /* slashed zero where the face has it */
}

/* Proper fractions via the frac feature rather than sup/sub hacks. */
.frac { font-variant-numeric: diagonal-fractions; }

a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--brass-sunk);
  text-decoration-thickness: 0.055em;
  text-underline-offset: 0.16em;
  /* Skip descenders — the single cheapest way to make links look considered. */
  text-decoration-skip-ink: auto;
  transition: text-decoration-color 120ms var(--ease), color 120ms var(--ease);
}

a:hover {
  color: var(--brass-bright);
  text-decoration-color: var(--brass);
}

a:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
  border-radius: var(--radius);
}

blockquote {
  margin: var(--rhythm) 0;
  padding-left: var(--gutter);
  border-left: var(--rule) solid var(--edge-strong);
  color: var(--crema-dim);
  font-style: italic;
}

blockquote p { text-align: justify; }

hr {
  border: 0;
  height: var(--rule);
  background: var(--edge);
  margin: calc(var(--rhythm) * 2) 0;
}

/* A typographic break rather than a rule: three brass asterisks. */

/* — reduced motion — */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================================
   LAYOUT — measure plus a margin column.
   Sidenotes float into the margin from inside the paragraph they belong to,
   which is the only way to keep them vertically anchored to their reference.
   ========================================================================= */

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

body { margin: 0; }

.page {
  max-width: calc(var(--measure) + var(--gutter) + var(--margin-col));
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: calc(var(--rhythm) * 2);
}

.prose,
.masthead,
.article__header,
.page > p,
.page > h1, .page > h2, .page > h3, .page > h4,
.page > ul, .page > ol, .page > pre, .page > blockquote {
  max-width: var(--measure);
}

/* Deliberate escapes from the measure. */
.wide { max-width: calc(var(--measure) + var(--gutter) + var(--margin-col)); }
.full { max-width: none; }

/* — masthead — */

.masthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gutter);
  padding-bottom: var(--rhythm);
  margin-bottom: calc(var(--rhythm) * 2);
  border-bottom: var(--rule) solid var(--edge);
  max-width: none;
}

.masthead__mark {
  font-weight: 600;
  font-size: var(--f-mark);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.14em;
  text-decoration: none;
  color: var(--crema);
  white-space: nowrap;
}

.masthead__mark::before {
  content: "";
  display: inline-block;
  width: 0.44em; height: 0.44em;
  margin-right: 0.62em;
  background: var(--brass);
  transform: rotate(45deg) translateY(-0.06em);
}

.masthead nav {
  display: flex;
  /* Three different elements — a link, a <details>, a <button> — so their box
     heights differ. Aligned on the text baseline rather than the boxes, or
     they sit on three slightly different lines. */
  align-items: baseline;
  gap: 1.5rem;
  font-size: var(--f-nav);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--crema-dim);
}

.masthead nav a { text-decoration: none; }
.masthead nav a:hover { color: var(--brass-bright); }

/* — the account corner —
 *
 * Anonymous, this is one link and looks like the one beside it. Signed in,
 * auth.js swaps in a <details>: the summary is your name, still set like the
 * nav, and the panel under it holds the controls. A native disclosure, so it
 * is keyboard-operable and closes on Escape without any script. */

.account { position: relative; }

/* One row, one size, one colour. The nav holds three things — a link, your
   account, and the colour scheme — and they are three different elements, so
   nothing about their type is left to what a <button> or a <summary> happens
   to inherit. Every value here is set, not inherited. */
.masthead nav > a,
.masthead nav .theme-toggle,
.account__menu > summary {
  font-family: var(--serif);
  font-size: var(--f-nav);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  font-variant-numeric: oldstyle-nums;
  letter-spacing: 0.09em;
  line-height: 1;
  color: var(--crema-dim);
  text-decoration: none;
}

.account__menu > summary {
  display: inline-block;        /* not list-item: it aligns on the baseline */
  list-style: none;             /* the default triangle, in two dialects */
  cursor: pointer;
}
.account__menu > summary::-webkit-details-marker { display: none; }

/* The mark is a caret in brass, rotating when the panel opens: the one moving
   thing in the masthead, and it moves only because it means something. */
.account__menu > summary::after {
  content: "";
  display: inline-block;
  width: 0.34em; height: 0.34em;
  margin-left: 0.5em;
  border-right: 1.5px solid var(--brass);
  border-bottom: 1.5px solid var(--brass);
  transform: rotate(45deg) translate(-0.1em, -0.1em);
  transition: transform 140ms var(--ease);
}
.account__menu[open] > summary::after {
  transform: rotate(225deg) translate(-0.06em, -0.06em);
}

.account__menu > summary:hover,
.account__menu[open] > summary { color: var(--brass-bright); }
.account__menu > summary:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 3px;
  border-radius: var(--radius);
}

.account__panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.7rem);
  z-index: 20;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.1rem;
  padding: 0.5rem;
  background: var(--raise);
  border: var(--rule) solid var(--edge-strong);
  border-radius: var(--radius);
  /* On a matte ground a shadow should read as depth, not as a glow. */
  box-shadow: 0 0.6rem 1.6rem rgb(0 0 0 / 0.45);
}

.account__groups {
  margin: 0 0 0.35rem;
  padding: 0 0.55rem 0.4rem;
  border-bottom: var(--rule) solid var(--edge);
  color: var(--crema-faint);
  font-size: var(--f-fine);
  letter-spacing: 0.06em;
}

/* Buttons inside the panel are rows, not chips. */
.account__panel .auth-button,
.account__panel a.auth-button {
  width: 100%;
  text-align: left;
  border: 0;
  padding: 0.34rem 0.55rem;
  background: transparent;
}
.account__panel a.auth-button { text-decoration: none; display: block; }

.account__panel .auth-button:hover,
.account__panel a.auth-button:hover {
  background: var(--brass-wash);
  color: var(--brass-bright);
}

/* Where auth.js says what happened. Under the masthead, out of the way, and
   absent entirely until it has something to say. */
.auth-status {
  max-width: var(--measure);
  margin: calc(var(--rhythm) * -1) 0 var(--rhythm);
  font-size: var(--f-nav);
  color: var(--crema-dim);
}
.auth-status:empty { display: none; }
.auth-status[data-kind="ok"]  { color: var(--verdigris); }
.auth-status[data-kind="bad"] { color: var(--oxblood); }

/* — article furniture — */

.article__header { margin-bottom: calc(var(--rhythm) * 2); }

.article__subtitle {
  font-size: var(--t-1);
  font-style: italic;
  color: var(--crema-dim);
  line-height: 1.35;
  margin: calc(var(--rhythm) * -0.35) 0 var(--rhythm);
  text-wrap: balance;
}

.article__meta {
  font-size: var(--f-meta);
  color: var(--crema-faint);
  font-variant-caps: all-small-caps;
  font-variant-numeric: oldstyle-nums;
  letter-spacing: 0.08em;
  display: flex;
  gap: 1.1rem;
  flex-wrap: wrap;
  align-items: baseline;
}

.article__meta .sep { color: var(--edge-strong); }

/* — SIDENOTES ————————————————————————————————————————————————————————— */

.sidenote {
  float: right;
  clear: right;
  width: var(--margin-col);
  /* Pull it out of the measure and into the margin column. */
  margin-right: calc(-1 * (var(--margin-col) + var(--gutter)));
  margin-bottom: calc(var(--rhythm) * 0.6);
  margin-left: var(--gutter);

  font-size: var(--t--1);
  line-height: 1.5;
  color: var(--crema-dim);

  /* Never justify a 16rem column — it is all river. */
  text-align: left;
  hyphens: auto;
  text-indent: 0;
  position: relative;
}

.sidenote__no {
  font-variant-numeric: lining-nums;
  color: var(--brass);
  margin-right: 0.4em;
  font-size: 0.8em;
  vertical-align: 0.35em;
}

.sidenote p { margin: 0 0 0.5em; text-align: left; text-indent: 0; }
.sidenote p:last-child { margin-bottom: 0; }

/* In-text marker. */
.noteref {
  font-variant-numeric: lining-nums;
  font-size: 0.72em;
  vertical-align: 0.42em;
  color: var(--brass);
  text-decoration: none;
  cursor: pointer;
  padding-inline: 0.08em;
}
.noteref:hover { color: var(--brass-bright); }

.sidenote-toggle { display: none; }

/* — the coffee note — */

.coffee-note {
  margin: calc(var(--rhythm) * 1.25) 0;
  padding: calc(var(--rhythm) * 0.7) calc(var(--gutter) * 0.9);
  padding-right: calc(var(--gutter) * 1.8);
  background: var(--raise);
  border-left: 2px solid var(--brass-sunk);
  border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--crema-dim);
  font-size: var(--t--1);
  line-height: 1.55;
  position: relative;
  text-indent: 0;
}

/* The ring — a cup set down on the page, once, deliberately. */
.coffee-note::before {
  content: "";
  position: absolute;
  top: calc(var(--rhythm) * 0.7);
  right: calc(var(--gutter) * 0.7);
  width: 1.15rem; height: 1.15rem;
  border: 1.5px solid var(--brass-sunk);
  border-radius: 50%;
  opacity: 0.45;
}

.coffee-note__label {
  display: block;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.11em;
  font-size: 0.85em;
  color: var(--brass);
  margin-bottom: 0.25em;
}

.coffee-note p { margin-bottom: 0.5em; text-align: left; text-indent: 0; }
.coffee-note p:last-child { margin-bottom: 0; }

/* — NARROW: fold the margin column away ——————————————————————————————— */

@media (max-width: 62rem) {
  :root { --measure: 100%; --gutter: 1.35rem; }

  .page { max-width: 40rem; padding-inline: var(--gutter); }

  .sidenote {
    float: none;
    width: auto;
    margin: var(--rhythm) 0;
    padding: 0.8rem 1rem;
    background: var(--raise);
    border-left: 2px solid var(--brass-sunk);
    border-radius: 0 var(--radius) var(--radius) 0;

    /* Collapsed until its marker is tapped. */
    display: none;
  }

  .sidenote-toggle:checked + .sidenote { display: block; }

  /* A ~40-character measure cannot be justified without rivers, whatever the
     line-breaker. Below this width the text rags right. */
  .prose p, blockquote p { text-align: left; }
  .prose--indented p { text-indent: 0; margin-bottom: var(--rhythm); }
}

@media (max-width: 34rem) {
  .masthead { flex-direction: column; gap: 0.6rem; align-items: flex-start; }
  .masthead nav { gap: 1.1rem; flex-wrap: wrap; }
  /* The masthead stacks and the corner is no longer in the corner, so the
     panel hangs from its left edge instead of running off the screen. */
  .account__panel { right: auto; left: 0; }
}

/* — PRINT ————————————————————————————————————————————————————————————— */

@media print {
  :root {
    --bed: #fff; --raise: #fff; --crema: #000;
    --crema-dim: #333; --brass: #6b4a1c; --edge: #ccc;
  }
  .masthead nav, .noteref, .equation__anchor { display: none; }
  .page { max-width: none; }
  .sidenote { display: block; float: right; clear: right; width: 38%; }
  a { text-decoration: none; color: #000; }
  .prose p { text-align: justify; }
  pre, figure, .equation, blockquote { break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }
}

/* — THE SETTING STATE ——————————————————————————————————————————————————
   Justif re-breaks every paragraph before the text is fit to show, so the
   prose is held back rather than painted once badly and again well.

   The grid goes up only when the faces are still arriving, which setting.js
   determines by asking. There is no threshold and no minimum display time:
   the fade is what keeps a short wait from reading as a flash, since a grid
   that is only up for a moment never reaches full strength in the first
   place. Proportional, rather than timed. */

.prose-wrap { position: relative; }

.prose { transition: opacity 240ms var(--ease); }

:root[data-setting] .prose { opacity: 0; }

.setting-grid {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms var(--ease);
  background: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent calc(var(--rhythm) - 1px),
    var(--edge) calc(var(--rhythm) - 1px),
    var(--edge) var(--rhythm)
  );
  /* Fade the grid out at the foot so it reads as a hint, not a cage. */
  -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
}

:root[data-setting="slow"] .setting-grid { opacity: 1; }

.setting-grid__label {
  position: absolute;
  top: calc(var(--rhythm) * -1.4);
  right: 0;
  font-size: var(--t--2);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.16em;
  color: var(--crema-faint);
}

.setting-grid__label::after {
  content: "";
  display: inline-block;
  width: 0.36em;
  height: 0.36em;
  margin-left: 0.6em;
  background: var(--brass);
  transform: rotate(45deg) translateY(-0.08em);
  animation: setting-pulse 1.4s var(--ease) infinite;
}

@keyframes setting-pulse { 50% { opacity: 0.25; } }

@media (prefers-reduced-motion: reduce) {
  .setting-grid { transition: none; }
  .setting-grid__label::after { animation: none; }
}

/* No script, no held-back prose. */

/* ============================================================================
   CONTENT — maths, code, figures, references, tables.
   ========================================================================= */

/* — MATHEMATICS ————————————————————————————————————————————————————————
   Every equation on this site, display or inline, is a picture drawn by
   Typst's own typesetter, with the MathML kept beside it as the hidden
   machine-readable layer. So there are no rules here for painting <math>:
   nothing on the page ever paints one, and the maths webfont is not loaded.

   Should MathML ever be made visible again, note that it must not be given a
   `display`. MathML Core's UA stylesheet uses the two-value forms `inline
   math` and `block math`, and the second keyword is what selects the maths
   layout algorithm. Overriding it with a plain `block` or `inline` silently
   drops that, and every <mrow>, <msup> and <mfrac> falls back to generic CSS
   box layout, which stacks the expression vertically. Chromium does exactly
   this; Firefox's engine is more forgiving, which is how the bug ships unseen.
   ————————————————————————————————————————————————————————————————————— */

/* — THE THREE LAYERS OF A DISPLAY EQUATION ————————————————————————————
   Stacked in one box: the picture, the selectable description, the structure.
   See lib/theme.typ for why each one is there. */

/* Also the scroll container: a long derivation scrolls inside its own box and
   the page never scrolls sideways. */
.equation__body {
  position: relative;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  padding-block: 0.15rem;
  scrollbar-width: thin;
}

/* One copy of every glyph outline the page draws with, written by Typst's
   encoder as a definition block. It paints nothing; it is only there for the
   <use> elements in the equations to point at. */
.typst-glyphs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* The picture.

   Typst bakes the text colour into every glyph as #000000, because an SVG has
   to stand alone; here it does not, and the maths should follow the theme like
   the prose around it. That is what these two rules do, and doing it in CSS
   rather than by rewriting the built markup matters twice over: an SVG
   presentation attribute loses to any author rule, so this is the mechanism
   the language already provides; and it is scoped to the maths, so a figure
   that is deliberately black stays black.

   Both fills and strokes. A fraction bar, a radical rule and an overline are
   strokes, and leaving those black makes them disappear on a dark page while
   the glyphs beside them invert. */
.eq-svg { color: var(--crema); }
.eq-svg use,
.imath__vis use { fill: currentColor; }
.eq-svg [stroke],
.imath__vis [stroke] { stroke: currentColor; }
/* max-width belongs here, not on the element: a cap on the inline maths would
   shrink its width without its height and skew the glyphs. A wide display
   equation is scrolled by .equation__body instead of pushing the page. */
.eq-svg svg {
  display: block;
  margin-inline: auto;
  max-width: 100%;
}

/* The description, rendered but transparent, lying over the picture. This is
   what a sighted reader selects; copying an equation yields the sentence the
   author wrote rather than a string of flattened Unicode. */
.eq-sel {
  position: absolute;
  inset: 0;
  margin: 0;
  /* Centred, so a drag through the middle of a tall equation crosses the text
     line. Pinned to the top it is unselectable for anything taller than one
     line, which is most display maths. */
  display: flex;
  align-items: center;
  color: transparent;
  cursor: text;
  overflow: hidden;
  white-space: nowrap;
  font-size: var(--t--1);
  line-height: 1.2;
  user-select: text;
  -webkit-user-select: text;
}

.eq-sel::selection { background: var(--selection); }

/* The structure. Visually hidden but still rendered, so it stays in the
   accessibility tree: a screen reader can walk the expression and a braille
   display can set it as maths. `display: none` would remove it from both.
   The serif stack keeps it from pulling in the maths webfont it never shows. */
.eq-a11y {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  font-family: serif;
}

/* — INLINE MATHS ——————————————————————————————————————————————————————
   The same three layers as a display equation: the picture, the transcript a
   reader selects, and the structure assistive technology walks.

   The SVG needs no vertical-align. Typst's frame bottom is the text baseline
   and an inline <svg> aligns its bottom edge to the baseline, so they already
   agree; anything that hangs lower spills, which is why the frame is drawn
   with overflow visible. Nothing here may give the SVG a `display` or a
   `height`: either one would replace that agreement with a guess. */
.imath {
  white-space: nowrap;
}

.imath__vis {
  color: var(--crema);
}

/* The transcript. In the flow, and therefore in the selection, but occupying
   no space and painting nothing: this is what a reader gets when they drag
   through a sentence and copy it. */
.imath__txt {
  display: inline-block;
  width: 0;
  height: 0;
  overflow: hidden;
  color: transparent;
  white-space: nowrap;
  user-select: text;
  -webkit-user-select: text;
}

/* The structure. Taken out of the flow, which drops it from the selection so
   it is not copied on top of the transcript, while leaving it rendered and so
   still in the accessibility tree — a screen reader can walk the expression
   and a braille display can set it as maths. `display: none` would remove it
   from both. Never the maths face: this layer is never painted, and naming it
   here would pull a webfont down for glyphs nobody sees. */
.imath__src {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  font-family: serif;
}


/* The numbered-equation frame. The number is emitted by the Typst template
   (see lib/theme.typ) so that it always agrees with what @refs resolve to. */
.equation {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: var(--gutter);
  margin-block: var(--rhythm);
  scroll-margin-top: 3rem;
}

.equation__body > math { margin-block: 0; }

.equation__no {
  font-variant-numeric: lining-nums;
  font-size: var(--t--1);
  color: var(--crema-faint);
  user-select: none;
  white-space: nowrap;
}

/* The sign-in page, once it knows there is nothing left to sign in to. Quiet:
   it is a fact about the session, not an announcement. */
.signed-in {
  color: var(--crema-dim);
  font-size: var(--f-nav);
}

/* — the auth controls —
 *
 * Machined rather than soft: a hairline, almost no radius, small caps, and
 * brass only on the edge that is being touched. They had no styling at all
 * until now and rendered as the browser's own grey chrome, which on this page
 * looked exactly like what it was — something nobody had looked at. */
.auth-button {
  font: inherit;
  font-size: var(--f-nav);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.08em;
  color: var(--crema-dim);
  background: transparent;
  border: var(--rule) solid var(--edge-strong);
  border-radius: var(--radius);
  padding: 0.4rem 0.9rem;
  cursor: pointer;
  transition: color 120ms var(--ease), border-color 120ms var(--ease),
              background 120ms var(--ease);
}

.auth-button:hover {
  color: var(--brass-bright);
  border-color: var(--brass);
  background: var(--brass-wash);
}

.auth-button:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}

/* The slot the control goes in. Empty in the file, and taking no room until
   auth.js puts something in it. */
.share-slot:empty { display: none; }

/* The share control sits in a line of metadata, so it is smaller than a
   standalone button and carries no border until it is pointed at. */
.share {
  font: inherit;
  font-size: var(--f-fine);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--crema-faint);
  background: transparent;
  border: var(--rule) solid transparent;
  border-radius: var(--radius);
  padding: 0.05rem 0.4rem;
  cursor: pointer;
}
.share:hover { color: var(--brass-bright); border-color: var(--brass-sunk); }
.share:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }

/* The minted link, shown rather than copied silently. Monospaced because it
   is a string to be checked and handed over, and selected on arrival. */
.share-link {
  font-family: var(--mono);
  font-size: var(--f-fine);
  width: 100%;
  max-width: var(--measure);
  color: var(--crema);
  background: var(--raise-2);
  border: var(--rule) solid var(--brass-sunk);
  border-radius: var(--radius);
  padding: 0.3rem 0.5rem;
}

/* Deep-link highlight, for both #anchors and ::target-text fragments.
 *
 * Long enough to be seen after the page has scrolled, which 900ms was not:
 * following a reference moves the viewport and the animation was over before
 * the eye arrived. `:target` also only fires when the fragment *changes*, so
 * clicking the same reference twice did nothing at all — `refs.js` restarts
 * `.is-flash` by hand on every click. */
:target, .is-flash {
  animation: settle 2s var(--ease);
  /* The outline is what actually reads. A background wash at 10% alpha is
     invisible on a dark ground from across a page, which is why following a
     reference felt like nothing had happened. */
  border-radius: var(--radius);
}
::target-text { background: var(--selection); color: var(--crema); }

@keyframes settle {
  0%   { background: var(--brass-wash); box-shadow: 0 0 0 0.35rem var(--brass-wash),
                                                    0 0 0 0.03rem var(--brass); }
  60%  { background: var(--brass-wash); box-shadow: 0 0 0 0.35rem var(--brass-wash),
                                                    0 0 0 0.03rem var(--brass); }
  100% { background: transparent;      box-shadow: 0 0 0 0.35rem transparent,
                                                    0 0 0 0.03rem transparent; }
}

/* — CODE ——————————————————————————————————————————————————————————————— */

code, kbd, samp, pre {
  font-family: var(--mono);
  font-variant-ligatures: contextual;   /* JetBrains Mono's arrows and !=  */
  font-feature-settings: "zero" 1;
  font-optical-sizing: auto;
}

/* Inline code: sized down, because mono x-heights run large next to serif. */
:not(pre) > code {
  font-size: 0.875em;
  background: var(--raise);
  border: var(--rule) solid var(--edge);
  border-radius: var(--radius);
  padding: 0.08em 0.34em;
  color: var(--crema);
  /* Never let a long identifier blow out the measure. */
  overflow-wrap: break-word;
}

pre {
  /* Mildly wider than the measure, on both sides. Code is read in lines that
     do not wrap, so a little more room is worth more to it than to prose, and
     nothing sits beside it to be pushed. */
  max-width: calc(100% + var(--code-bleed) * 2);
  margin: var(--rhythm) calc(var(--code-bleed) * -1);
  padding: calc(var(--rhythm) * 0.75) 1.15rem;
  background: var(--raise);
  border: var(--rule) solid var(--edge);
  border-radius: var(--radius);
  overflow-x: auto;
  font-size: var(--t--1);
  line-height: 1.55;
  tab-size: 2;
  /* The scrollbar belongs to the block, not to the browser's default idea of
     a dark one. Both properties: `scrollbar-color` is what Firefox and current
     Chromium read, the pseudo-elements are for older WebKit, and the tokens
     mean each theme gets its own without either being named here. */
  scrollbar-width: thin;
  scrollbar-color: var(--edge-strong) transparent;
  /* code is never justified, never hyphenated */
  text-align: left;
  hyphens: none;
}

pre::-webkit-scrollbar { height: 0.55rem; }
pre::-webkit-scrollbar-track { background: transparent; }
pre::-webkit-scrollbar-thumb {
  background: var(--edge-strong);
  border-radius: 0.3rem;
}
pre:hover::-webkit-scrollbar-thumb { background: var(--crema-faint); }

pre code {
  background: none;
  border: 0;
  padding: 0;
  font-size: inherit;
}

/* Syntax colours: drawn from the coffee palette, not a rainbow. Hue does
   semantic work only where it must; the rest is weight and value. */
/* Syntax highlighting.
 *
 * Typst emits `class="hl-d73948"` on each token — the colour its theme would
 * have painted it, as a name rather than as a decision. What that becomes is
 * decided here, so it can differ between the light and dark grounds; an
 * inline colour could not.
 *
 * The comment on each line is the scope selector the colour comes from in
 * `typst_library::text::RAW_THEME`. blog-build reads that theme on every build
 * and refuses to publish if a colour in it has no rule here, so this list
 * cannot fall behind the highlighter.
 *
 * Several of these match nothing on the site today, because no code sample
 * reaches that scope yet — one Rust attribute or one escape sequence in a
 * string is enough. They are not dead: the build requires them, and the first
 * post that needs one would otherwise ship unhighlighted. */
.hl-d73948 { color: var(--brass);        font-weight: 600; }  /* keyword, storage */
.hl-4b69c6 { color: var(--brass-bright); }                    /* function, type, support */
.hl-b60157 { color: var(--verdigris);    font-variant-numeric: lining-nums; } /* constant */
.hl-198810 { color: var(--verdigris);    }                    /* string, insertion */
.hl-74747c { color: var(--crema-faint);  font-style: italic; } /* comment */
.hl-6b6b6f { color: var(--crema-faint);  }                    /* raw markup */
.hl-16718d { color: var(--brass-bright); font-weight: 500; }  /* macro */
.hl-8b41b1 { color: var(--brass-bright); font-weight: 500; }  /* interpolation, diff range */
.hl-1d6c76 { color: var(--brass-sunk);   }                    /* escape, label, math operator */
.hl-301414 { color: var(--crema-dim);    }                    /* annotation */

/* — FIGURES ———————————————————————————————————————————————————————————— */

figure {
  margin: calc(var(--rhythm) * 1.5) 0;
  scroll-margin-top: 3rem;
}

figure svg,
figure img {
  display: block;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}

/* CeTZ figures are emitted as inline SVG with currentColor strokes by the
   build step, so they invert correctly between themes. */
figure svg { color: var(--crema); }

figcaption {
  margin-top: calc(var(--rhythm) * 0.5);
  font-size: var(--t--1);
  line-height: 1.5;
  color: var(--crema-dim);
  text-align: left;
  hyphens: auto;
}

/* Typst emits "Figure 1: caption" — style the label run distinctly. */
figcaption::first-line { }
.fig-label {
  font-variant-caps: all-small-caps;
  letter-spacing: 0.08em;
  color: var(--brass);
  font-variant-numeric: lining-nums;
}

/* A figure allowed to use the sidenote column too. */
figure.wide { max-width: none; }

/* — REFERENCES & HOVER PREVIEWS ——————————————————————————————————————— */

/* Internal cross-references read as part of the sentence, not as links. */
a.ref {
  text-decoration: none;
  color: var(--brass);
  font-variant-numeric: lining-nums;
  border-bottom: 1px dotted var(--brass-sunk);
}
a.ref:hover { color: var(--brass-bright); border-bottom-style: solid; }

/* The preview card, positioned by 40-refs.js. */
.refcard {
  /* Fixed, not absolute: an absolutely positioned card placed low on the page
     extends the document, which on a phone scrolls the reader into empty
     space below the content. */
  position: fixed;
  overflow-y: auto;
  z-index: 40;
  max-width: min(30rem, 88vw);
  padding: 0.85rem 1.1rem;
  background: var(--raise-2);
  border: var(--rule) solid var(--edge-strong);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px -12px rgba(0,0,0,0.75), 0 0 0 1px rgba(0,0,0,0.2);
  font-size: var(--t--1);
  line-height: 1.5;
  color: var(--crema);
  opacity: 0;
  transform: translateY(3px);
  transition: opacity 130ms var(--ease), transform 130ms var(--ease);
  pointer-events: none;
}
.refcard[data-open="true"] { opacity: 1; transform: none; }
.refcard__kind {
  display: block;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.1em;
  font-size: 0.82em;
  color: var(--brass);
  margin-bottom: 0.25em;
}
.refcard math { font-size: 1em; }

/* Citations. */
a.cite {
  text-decoration: none;
  color: var(--brass);
  font-variant-numeric: lining-nums;
}
a.cite::before { content: "["; color: var(--crema-faint); }
a.cite::after  { content: "]"; color: var(--crema-faint); }

.bibliography {
  font-size: var(--t--1);
  line-height: 1.5;
  color: var(--crema-dim);
}
.bibliography li { margin-bottom: 0.6em; scroll-margin-top: 3rem; }

/* — FOOTNOTES (Typst's doc-endnotes, when not promoted to sidenotes) —— */

[role="doc-endnotes"] {
  margin-top: calc(var(--rhythm) * 2.5);
  padding-top: var(--rhythm);
  border-top: var(--rule) solid var(--edge);
  font-size: var(--t--1);
  color: var(--crema-dim);
}
[role="doc-endnotes"] ol { padding-left: 0; }
[role="doc-endnotes"] li { margin-bottom: 0.5em; }
[role="doc-noteref"] a { color: var(--brass); text-decoration: none; }

/* — TABLES ————————————————————————————————————————————————————————————
   Set the way a statistics table in a journal is set: rules only where they
   carry meaning, never a full grid. A vertical rule is almost always the
   typesetter admitting the columns are too close together. */

figure:has(table) {
  overflow-x: auto;
  scrollbar-width: thin;
  margin: calc(var(--rhythm) * 1.5) 0;
}

table {
  border-collapse: collapse;
  width: 100%;
  font-size: var(--t--1);
  line-height: 1.45;
  /* Digits in a column must align, always. */
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "zero" 1;
}

/* Booktabs: a heavier rule above and below the table, a lighter one under
   the header, and nothing else. */
thead { border-top: 1.5px solid var(--edge-strong); }
tbody { border-bottom: 1.5px solid var(--edge-strong); }

th {
  text-align: left;
  font-weight: 600;
  font-variant-caps: all-small-caps;
  font-variant-numeric: normal;
  letter-spacing: 0.07em;
  color: var(--crema-dim);
  border-bottom: var(--rule) solid var(--edge-strong);
  padding: 0.55em 1.1em 0.5em 0;
  white-space: nowrap;
}

td {
  padding: 0.42em 1.1em 0.42em 0;
  border-bottom: var(--rule) solid var(--edge);
  vertical-align: baseline;
}

tbody tr:last-child td { border-bottom: 0; }

/* Last column carries no trailing padding, so the table optically aligns to
   the measure rather than sitting short of it. */
th:last-child, td:last-child { padding-right: 0; }

/* A column's alignment comes from `align:` in the Typst source and arrives as
   an inline style on the cell, so there is nothing to do here. */

/* A table inside a figure loses the figure's own top margin: the caption
   already provides the separation. */
figure > table { margin: 0; }

/* — QUOTATION ————————————————————————————————————————————————————————
   Two registers. A blockquote is evidence: someone else's words, set apart
   and attributed. A pull quote is emphasis: the page's own words, enlarged.
   Conflating them is how a design ends up quoting itself for decoration. */

blockquote {
  margin: calc(var(--rhythm) * 1.25) 0;
  padding-left: var(--gutter);
  border-left: var(--rule) solid var(--edge-strong);
  color: var(--crema-dim);
  font-style: italic;
}

blockquote p { text-align: justify; }
blockquote p:last-child { margin-bottom: 0; }

/* Attribution sits below the quotation, ranged right, in the small-caps
   register the rest of the furniture uses. */
.attribution {
  display: block;
  margin-top: 0.5em;
  text-align: right;
  font-style: normal;
  font-size: var(--t--1);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.07em;
  color: var(--crema-faint);
}
.attribution::before { content: "— "; }
.attribution cite { font-style: italic; font-variant-caps: normal; }

/* The pull quote carries no quotation marks, so it gets no hanging indent
   either - a negative text-indent with nothing to hang just pushes the first
   line out past the measure. It is indented to the same edge as a blockquote
   and set one step up, which is enough of a change of voice without turning
   into a headline. */
.pull-quote {
  margin: calc(var(--rhythm) * 1.6) 0;
  padding-left: var(--gutter);
  font-size: var(--t-1);
  line-height: 1.36;
  font-weight: 380;
  letter-spacing: -0.004em;
  color: var(--crema);
  text-align: left;
  text-wrap: balance;
  hyphens: none;
  font-style: normal;
  border: 0;
}

/* — CITATIONS & BIBLIOGRAPHY ——————————————————————————————————————————— */

/* Typst emits author-date citations as doc-biblioref links. They should read
   as part of the sentence, not interrupt it. */
a[role="doc-biblioref"] {
  color: var(--brass);
  text-decoration: none;
  border-bottom: 1px dotted var(--brass-sunk);
  font-variant-numeric: oldstyle-nums;
}
a[role="doc-biblioref"]:hover {
  color: var(--brass-bright);
  border-bottom-style: solid;
}

[role="doc-bibliography"] {
  margin-top: calc(var(--rhythm) * 2.5);
  padding-top: var(--rhythm);
  border-top: var(--rule) solid var(--edge);
  font-size: var(--t--1);
  line-height: 1.5;
  color: var(--crema-dim);
}

[role="doc-bibliography"] h2 {
  font-size: var(--t-0);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.11em;
  font-weight: 600;
  color: var(--crema-dim);
  margin-block: 0 var(--rhythm);
}

[role="doc-bibliography"] ul { padding-left: 0; margin: 0; }

/* Hanging indent: the author's name is what you scan for, so it is the only
   thing on the left edge. */
[role="doc-bibliography"] li {
  margin-bottom: 0.7em;
  padding-left: 1.6em;
  text-indent: -1.6em;
  scroll-margin-top: 3rem;
}

[role="doc-bibliography"] li em { color: var(--crema); }

/* — LISTS —————————————————————————————————————————————————————————————— */

ul, ol { padding-left: 1.4em; margin: 0 0 var(--rhythm); }
li { margin-bottom: 0.3em; }
li::marker { color: var(--brass-sunk); }
ol { font-variant-numeric: oldstyle-nums; }

/* — GATED CONTENT MARKER ——————————————————————————————————————————————— */

.gated {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4em;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  font-size: var(--f-fine);
  color: var(--oxblood);
  border: var(--rule) solid currentColor;
  border-radius: var(--radius);
  padding: 0.1em 0.5em;
}

/* Macros get their own value so `println!` reads as invocation, not function. */

/* — CONTROLS ——————————————————————————————————————————————————————————— */

/* Type is set with the rest of the nav, in 20-layout.css, so the three items
   in it cannot drift apart. What is left here is the behaviour. */
.theme-toggle {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  min-width: 3.4em;         /* stop the nav reflowing as the label changes */
  text-align: left;
}
.theme-toggle:hover { color: var(--brass-bright); }
.theme-toggle:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }

.quote-link {
  position: fixed;
  z-index: 50;
  font: inherit;
  font-size: var(--t--2);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.08em;
  background: var(--raise-2);
  color: var(--brass);
  border: var(--rule) solid var(--edge-strong);
  border-radius: var(--radius);
  padding: 0.3em 0.7em;
  cursor: pointer;
  box-shadow: 0 8px 22px -10px rgba(0,0,0,0.8);
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms var(--ease);
}
.quote-link[data-open="true"] { opacity: 1; pointer-events: auto; }
.quote-link:hover { color: var(--brass-bright); border-color: var(--brass-sunk); }

/* — INDEX ——————————————————————————————————————————————————————————————— */

.index-entry {
  padding-block: calc(var(--rhythm) * 0.9);
  border-bottom: var(--rule) solid var(--edge);
}
.index-entry:last-of-type { border-bottom: 0; }

.index-entry__title {
  display: block;
  font-size: var(--t-2);
  font-weight: 600;
  letter-spacing: -0.011em;
  line-height: 1.15;
  text-decoration: none;
  text-wrap: balance;
}
.index-entry__title:hover { color: var(--brass-bright); }

.index-entry__sub {
  margin: 0.3em 0 0.55em;
  color: var(--crema-dim);
  font-size: var(--t-0);
  line-height: 1.4;
  text-wrap: pretty;
}

.index-entry .article__meta { margin: 0; }

/* — JUSTIF ————————————————————————————————————————————————————————————— */
/* Justif reads these at layout time. Tuned rather than left at defaults:
   this face has no `wdth` axis, so width adjustment is unavailable and the
   work has to be done by spacing, tracking and protrusion. */

.prose {
  --justif-hyphenate: auto;
  --justif-hang-punctuation: auto;   /* optical margin alignment */
  --justif-tracking-limit: 0.006em;  /* barely-there, never visible as tracking */
  --justif-max-adjustment-ratio: 1.6;
}

/* The section permalink: present, but only visible when looked for. */
.heading-anchor {
  margin-left: 0.4em;
  color: var(--brass-sunk);
  text-decoration: none;
  font-weight: 400;
  opacity: 0;
  transition: opacity 120ms var(--ease);
}
:is(h2, h3, h4):hover > .heading-anchor,
.heading-anchor:focus-visible { opacity: 1; }

/* Keep the anchor out of copied text and out of the heading's own balance. */
@media print { .heading-anchor { display: none; } }

/* — SPECIMEN ————————————————————————————————————————————————————————————
   Only used by the specimen page, which exists so the type scale can be
   judged at the sizes it is actually set in. */

.specimen-scale {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  margin: var(--rhythm) 0;
  padding: calc(var(--rhythm) * 0.8) 0;
  border-block: var(--rule) solid var(--edge);
}

.specimen-scale__row {
  display: grid;
  grid-template-columns: 6.5rem 1fr;
  align-items: baseline;
  gap: var(--gutter);
}

.specimen-scale__label {
  font-family: var(--mono);
  font-size: var(--t--2);
  color: var(--crema-faint);
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: 0.02em;
}

.specimen-scale__x { line-height: 1.1; color: var(--crema); }
.specimen-scale__x.s5  { font-size: var(--t-5); font-weight: 600; letter-spacing: -0.021em; }
.specimen-scale__x.s3  { font-size: var(--t-3); font-weight: 600; letter-spacing: -0.012em; }
.specimen-scale__x.s1  { font-size: var(--t-1); font-weight: 600; }
.specimen-scale__x.s0  { font-size: var(--t-0); }
.specimen-scale__x.sm1 { font-size: var(--t--1); color: var(--crema-dim); }

/* Preview cards for the kinds that are not equations need a little room to
   breathe: a table preview is wide, a cited work is a paragraph. */
.refcard table { font-size: 0.92em; }
.refcard figcaption { margin-top: 0.4em; font-size: 0.92em; }
.refcard li { margin: 0; padding-left: 1.4em; text-indent: -1.4em; list-style: none; }
.refcard ul { padding: 0; margin: 0; }

