/* PadoxMono -- Iosevka Fixed, subset and renamed, bundled with padox.

   WHY A BUNDLED FACE AT ALL. Every measurement in docs/decisions.md that touches the mono --
   the 155.25mm measure, the 1.45 code leading, the x-height match against Fira -- was taken
   against Iosevka. A reader without it got whatever `monospace' means on their machine, and
   the sheet's careful numbers then described a font that was not on the page.

   WHY IT IS NOT FIRST IN THE STACK. A reader who has installed Iosevka has the whole of it:
   every block, their own build, their own feature choices. Ours is a subset. So the stack in
   --font-mono names theirs first and falls through to this one, and the rename is what makes
   that expressible -- a stack cannot order two families that share a name. `src: local(...)'
   is NOT the way to do it: Chrome narrowed local() matching to exact full-name matching for
   fingerprinting reasons, and the platforms disagree besides.

   FOUR RULES, NOT ONE. A single @font-face with four sources lets the browser synthesise
   bold and oblique from the regular face and stack that on top of the real ones. The
   font-weight and font-style descriptors here are what say which file is which.

   NO unicode-range. The subset drops Cyrillic, Vietnamese, CJK and the private-use area, and
   the point is that CSS falls back PER GLYPH: a codepoint this face has not got is drawn by
   the next family in the stack. A unicode-range would be a second, hand-maintained copy of
   the subsetter's coverage list, and the two would drift.

   The url() paths are relative to the sheet and are rewritten as it is emitted -- a site puts
   its assets under res/, a lone page keeps them beside it, and --self-contained replaces them
   with data: URIs. The form written here is the lone-page one, so the checked-in sheet is a
   valid stylesheet as it stands. See HtmlProfile.Fonts.

   font-display: swap -- the text is drawn in the fallback and reflows when the face arrives,
   which for a code block is the right trade. Irrelevant under --self-contained, where there
   is no fetch. */
@font-face {
  font-family: "PadoxMono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/PadoxMono-Regular.woff2?v=cfdeb02b") format("woff2");
}
@font-face {
  font-family: "PadoxMono";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/PadoxMono-Italic.woff2?v=36c41d7a") format("woff2");
}
@font-face {
  font-family: "PadoxMono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/PadoxMono-Bold.woff2?v=25770695") format("woff2");
}
@font-face {
  font-family: "PadoxMono";
  font-style: italic;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/PadoxMono-BoldItalic.woff2?v=f292615c") format("woff2");
}

/* PadoxIcons -- padox's own drawn notation, bundled with padox.

   TEN GLYPHS, AND THEY ARE THE DOCUMENT'S CONTENT rather than a preference. PadoxMono is a
   preference: a reader with their own Iosevka is better served by it, and a page that gets
   neither still reads. Nothing on anybody's machine draws a five-armed asterisk or a
   three-arm bracket, so this face ships with EVERY shape padox emits -- linked beside the
   sheet, inlined under --self-contained, packed into an EPUB -- and --embed-mono does not
   govern it. HtmlProfile.IconFaces is the other half of that sentence.

   ONE RULE, NOT FOUR. The notation is never bold and never italic: it means the same thing
   inside a heading as in a paragraph, which the rules below restate as font-weight: 400.

   NAMED ONLY BY THE NOTATION'S OWN RULES, which is what makes the codepoints safe. The
   glyphs sit at U+2192, U+2190, U+21D2, U+21D0, U+2261, U+2016, U+005B, U+005D, U+002A and
   U+002B -- real characters, re-drawn -- so a reader who never received the face falls back
   per glyph to their own copy of each. Nothing else on the page is affected by what U+005B
   looks like here, because nothing else asks for this family.

   font-display: swap, as above: the character is drawn in the fallback and replaced when
   the face arrives. Irrelevant under --self-contained, where there is no fetch. */
@font-face {
  font-family: "PadoxIcons";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/PadoxIcons-Regular.woff2?v=d474b2c7") format("woff2");
}

:root {
  color-scheme: light;
  /* Surface */
  /* Named so a rule inside a <pre> can reach back to the body face. */
  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* Likewise for div.cli, which is monospace without being code. */
  /* And for [text]{.serif}, which has to name a face rather than assume the body is not one. */
  --font-serif: Charter, "Bitstream Charter", "Source Serif Pro", Cambria, Georgia, serif;
  /* The reader's own Iosevka first, ours second, the existing fallbacks unchanged. See the
     @font-face block at the top of this sheet for why PadoxMono is not first. */
  --font-mono:
    "IosevkaTerm Nerd Font Mono", "Iosevka Fixed", Iosevka,
    "PadoxMono",
    "Cascadia Mono NF",
    ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  /* Iosevka's stylistic set 14, which puts * on the operators' own line rather than hanging
     it from the cap height -- so `a * b` reads level with `a = b`. A font without the
     feature ignores it, and every fallback in the stack above is such a font, so this costs
     nothing where Iosevka is absent. Set to `normal` to turn it off.

     Ligatures are a separate matter and are off everywhere the mono face is used, via
     font-variant-ligatures: none on each of those rules. Iosevka carries calt and dlig in
     its default language system, so -> => != <= would otherwise be drawn as arrows and
     relational signs. Code is quoted to be read and retyped character for character, and a
     glyph that stands for two characters works against that.

     Adding a tag here does NOT bring them back -- font-feature-settings only overrides
     font-variant for the tags it names, and "ss17" is not one of them. Verified in
     Chromium: with ligatures normal the arrows appear, with none they do not, and with
     "ss17" and none together they do not.

     ss17, NOT THE ss14 THIS READ UNTIL 2026-08-25. The ConTeXt template moved to ss17 and
     this did not follow, so the two formats applied DIFFERENT stylistic sets to the same
     face -- the one thing the shared `--mono-features` token exists to prevent.

     IT NOW REACHES A READER'S OWN IOSEVKA AND NOTHING ELSE, and it stays for that reason.
     PadoxMono is built from Iosevka source with the letterforms BAKED IN and `noCvSs'
     stripping every cv/ss tag, so the shipped face carries no ss17 to ask for and gets the
     look regardless. The stack puts a reader's own "Iosevka Fixed" or Iosevka FIRST, though,
     and that build does have ss17 -- so this is what makes their page match ours. The
     ConTeXt side dropped the tag because there a stylistic alternate's glyph NAME becomes
     the PDF's ToUnicode and Iosevka names some of them after look-alikes; a browser takes
     its text from the DOM, so no such fault exists here and there is nothing to answer. */
  --mono-features: "ss17";
  /* A code block's inner padding, named because the OUTPUT strip has to cancel it exactly.
     The strip runs the full width of the block and sits flush against its top border, which
     it reaches by pulling itself out through the padding with a negative margin -- so the
     two numbers are one number, and writing them twice is what let them drift apart. They
     had: the strip carried div.cli's 0.72rem/0.9rem while the block it sits in has these,
     and it overhung the frame by 5.4px on each side, painting over the side borders at its
     own height. */
  /* A LINKED SVG IS ITS OWN DOCUMENT, so the page's dark palette cannot reach inside it: a
     drawing made in light mode arrives with its own white ground and dark ink, and sits on a
     dark page as a bright slab. The drawing is left alone -- an author draws in light mode and
     should not have to think about themes -- and the PAGE inverts it instead.

     Measured, and this is why it is a filter rather than the obvious alternative: an SVG CAN
     carry its own `@media (prefers-color-scheme: dark)` block, that DOES work through <img>,
     and it survives --self-contained as a data: URI. But it follows the OS preference ONLY --
     toggled dark on a light system, the drawing stayed light -- and padox ships a toggle. A
     filter is page CSS, so it answers both.

     invert(0.92), not 1: full inversion puts white on pure black, which is harder than anything
     else this sheet paints. hue-rotate(180deg) undoes the hue flip inversion applies, so a blue
     box stays blue rather than turning orange. */
  --svg-adapt: none;
  --pre-pad-y: 0.5rem;
  --pre-pad-x: 0.6rem;
  /* THE MONO SIZE, ONCE. A mono face runs larger than the prose around it at the same nominal
     size, so every mono path takes a reduction -- inline code, a transcript, a code block,
     .cc/.co and .mono/.tt. It was written out six times at 0.92, which is six places to keep in
     step and six to find when the ratio wants moving; it is one number now. 0.95 rather than
     0.92: at 0.92 the code in a sentence read a shade small against this sheet's body face. */
  --mono-size: 0.95em;
  /* Mathematics, for MathML. Serif throughout: mathematical notation is set in a seriffed
     face by every convention this document could follow, and the body face is sans, so this
     is a deliberate departure rather than an inherited default.

     Every entry is a font with an OpenType MATH table, which is what a browser needs to size
     a radical to its contents, stretch a brace to its rows, and place limits over a sum. An
     ordinary serif has no such table, so `serif` at the end is a legibility floor and not a
     real substitute -- the layout there is the browser's own approximation.

     Cambria Math first: it ships with Windows and with Office, it is the font the OpenType
     MATH table was designed alongside, and it is therefore the one most readers on Windows
     already have. Latin Modern Math next for anyone with a TeX distribution, then the two
     open fonts a Linux box is most likely to carry. */
  --font-math:
    "Cambria Math", "Latin Modern Math", "STIX Two Math", "XITS Math",
    "TeX Gyre Termes Math", "DejaVu Math TeX Gyre", serif;
  --page: #f6f7f9;
  --paper: #ffffff;
  --text: #24292f;
  --muted: #57606a;
  --mark: #57606a;  /* line reference labels ({.lref}) */
  --border: #d8dee4;
  --border-strong: #afb8c1;
  /* Accent */
  --accent: #0969da;
  --link: #0969da;
  /* Links that stay inside the collection. Same relative luminance as --link, so it
     carries the same 5.19:1 against --paper: a different hue, not a weaker colour. */
  --link-local: #147d43;
  --accent-soft: #ddf4ff;
  /* A named term's colour, its own hook. Dark brown: it has to read as a term rather than as
     a link, so it stays clear of the blue, and against the code tint it is quieter than the
     text colour would be. */
  --co-color: #6b4423;
  /* Admonitions. THESE ARE THE PDF'S NUMBERS, hex for hex, and the tints were tuned there
     first -- ink shows a colour cast that a screen forgives, so paper is the harder case and
     what survives it survives both. Measured as the spread between a colour's highest and
     lowest channel, which at these lightnesses is the whole of the colour: admon 5, tip 8,
     note 12, important 12, warning 16, caution 16. Before that, note was 34, warning 31 and
     caution 58, and important carried a magenta cast rather than the rule's violet.

     Every colour that lands on a tint clears 4.5:1 against it -- the label, the body text,
     --link and --link-local -- and this FIXED the one that never did: warning's label was
     4.16:1 and is 4.62:1. Re-measure all four if a tint moves.

     --admon is the base: the colour a callout wears when it does not say what
     kind it is. Deliberately the muted grey rather than a sixth hue -- the five kinds mean
     something by their colour, and a neutral box is the one that means nothing in
     particular. */
  --admon: #57606a;
  --admon-soft: #f2f4f7;
  --tip: #1a7f37;
  --tip-rule: #6b9b6b;
  --tip-soft: #edf5ed;
  --note: #0969da;
  --note-soft: #f0f4fc;
  --caution: #926100;
  --caution-rule: #9a6700;
  --caution-soft: #f4f2e4;
  --important: #8250df;
  --important-soft: #f4f1fd;
  --warning: #b25100;
  --warning-rule: #c05700;
  --warning-soft: #f7f2e7;
  /* Code blocks */
  --code-overlay: rgba(0, 0, 0, 0.06);
  --code-border-overlay: rgba(0, 0, 0, 0.10);
  --pre-bg: #f0f4f8;
  /* AN OUTPUT BLOCK DARKENS ITS GROUND rather than painting a colour over it: its claim is
     "a little darker than whatever this sits on", which an opaque fill cannot make. Inside
     an admonition the flat --pre-bg was a blue-grey slab on a coloured tint. Over white this
     composites to #f0f2f5, which is what the PDF's own overlay lands on. */
  --output-bg: rgba(68, 92, 130, 0.08);
  --pre-text: #1e2430;
  --pre-border: #cdd5de;
  /* THE TRANSCRIPT'S FILL, and on a light page there is none. The green did not sit well
     beside the rest of the palette, and a box that is only its border reads as a frame
     around the commands rather than as a slab. Dark keeps a fill -- there the tint is what
     separates the box from the page at all.
     Named for the construct and not for the colour. It was called after `{.console}`, a class
     most documents never write, so a rule under `div.cli` reading that name looked like it had
     been inherited from something else -- which is exactly how it was read. The border token
     keeps its old name for now; the borders are being left alone. */
  --cli-bg: transparent;
  /* {.ascart}: the leading for box-drawing diagrams. See pre.ascart. */
  --ascart-line-height: 1.25;
  /* A code block's header bar: the OUTPUT strip and a .file/.snip label alike, because they
     are the same thing -- a band across the top of a block saying what the block is. One
     token, so the two cannot drift apart.

     Its own token rather than --thead-bg, which it matched until it needed to be darker: a
     table head is a band on --paper and this is a band on --pre-bg, so tuning one to its
     background moved the other away from its own.

     A BAND, NOT A RULE. This is the only thing separating the code from its output, so it
     has to be dark enough to read as a divider by itself -- the borders that used to run
     above and below it are gone, and a bar this close to --pre-bg would leave nothing. */
  --label-bg: #dfe6ef;
  --output-label-bg: var(--label-bg);
  /* A label over a TRANSCRIPT takes the console's own colour, not this one. A transcript is
     green — tint and border both — and a greyish-blue bar on top of it reads as two unrelated
     boxes stacked rather than as one object with a title. Derived the same way --label-bg is:
     the console tint darkened by the ratio --pre-bg takes to --label-bg, which lands it at the
     same visible separation from the block it heads (1.126:1 against the code pair's 1.138:1)
     and leaves every colour that sits on it at least as legible (accent 2.77 against 2.75,
     text 11.70 against 11.65). */
  --cli-label-bg: #dfe7e4;
  --output-label-text: #57606a;
  /* One line, fixed, so the copy button can be centred on it -- a button positioned against
     a height that depends on a font's line box is a button that moves when the font does. */
  --output-strip-h: 1.55rem;
  --cli-border: #729486;
  /* AN IN-PROSE SPAN NEEDS ITS OWN EDGE, and only in dark does it differ. The box's
     border is the band's background there, which is right for a block -- band, border
     and box read as one object -- and invisible for a span, which sits on the page
     rather than on the box: measured, that value is 1.07:1 against --paper against the
     2.02:1 this keeps. Same value as the box's in light, stated rather than inherited
     so the dark divergence is visible in one place. */
  --cli-span-border: #729486;
  /* Syntax token colours (light mode) */
  --syn-al:   #c01820;
  --syn-co:   #677080;
  --syn-at:   #2a7a5a;
  --syn-num:  #b86800;
  --syn-type: #1a5c8c;
  --syn-ctrl: #7030a0;
  --syn-cn:   #7e3030;
  --syn-fu:   #1d4f9a;
  --syn-kw:   #3545a8;
  --syn-op:   #505860;
  --syn-pp:   #a05000;
  --syn-str:  #1a6b3c;
  --syn-va:   #1e2430;
  /* Tables */
  --table-stripe: #f6f8fa;
  --thead-bg: #eef2f7;
  /* Semantic tokens for elements with hardcoded colours */
  --heading: #111827;
  --heading-xl: #111827;
  /* #90581d, the PDF's padoxAccent, and the two formats now AGREE. This was #c47828, kept
     lighter on the argument that ink shows a cast a screen forgives -- but that value is
     3.46:1 on --paper, which is a large-text ratio, and the token was colouring 0.82rem bold
     text: the title block's date and the landing page's. Its own sheet already recorded 3.46
     as "washed out" the day the hue was tried for the hero subtitle. What is left on this
     token is three 3px rules and those two dates, so darkening it costs two borders a shade
     and takes both dates to 5.83:1. Light only -- dark stays #cc8e5c, which passes on a dark
     ground and is pinned by the title-hierarchy work. */
  --h1-accent: #90581d;
  --hero-subtitle: #96263a;
  --license-badge-filter: none;
  --license-badge-opacity: 0.62;
  /*--title-text: #2e3f8f;*/
  --title-text: #097dd5;
  --del-line: rgba(190, 50, 50, 0.50);
  --kbd-bg: #eef0f3;
  --kbd-shadow: rgba(0, 0, 0, 0.20);
  --selection-bg: rgba(255, 214, 0, 0.42);
  --details-marker: #c73232;
  --author-text: #374151;
  --toc-bg: #f8fafc;
  --toc-hover: #c8deff;
  --quote-text: #1f2937;
  /* Shadows */
  --paper-shadow: rgb(31 35 40 / 9%);
  --btn-shadow: rgb(31 35 40 / 12%);
  /* Fixed-position buttons */
  --btn-border: rgb(9 105 218 / 25%);
  --btn-bg: rgb(255 255 255 / 68%);
  --btn-border-hover: rgb(9 105 218 / 38%);
  --btn-bg-hover: rgb(221 244 255 / 86%);
  --btn-bg-mobile: rgb(255 255 255 / 88%);
}

* {
  box-sizing: border-box;
}

html {
  background: var(--page);
  font-size: 18px;
}

body {
  /* Ask every font for a distinguished zero, and take it wherever it is offered. A request,
     not a guarantee: a face either has an alternate zero or it does not, and a browser will
     not draw the slash itself the way it synthesises bold or oblique.

     Measured on this machine: Iosevka's zero is slashed and DejaVu Sans Mono's is dotted
     without being asked, so the property changes nothing there; Noto Sans and Cambria have a
     plain zero and no alternate, so it changes nothing there either. It costs one declaration
     and helps whoever's body font does carry one. Inherited, so it reaches everything. */
  font-variant-numeric: slashed-zero;
  /* Ligatures in PROSE stay at the browser's default, which is on: fi, ffi and fl are what a
     text face is drawn to do, and a word is read as a word. There is deliberately no
     declaration -- `normal` would only restate the default -- and a blanket rule switching
     them off is the thing this comment exists to prevent. Verified in Chromium against the
     resolved body face: default and font-variant-ligatures: none draw the f-pairs
     differently, so the feature is genuinely applied and not merely permitted.

     Code is the opposite case and says so at each mono rule. The one prose exception is .cc,
     which names something the reader may have to type. */
  max-width: 860px;
  margin: 0 auto;
  padding: 48px 32px 72px;
  overflow-x: clip;
  background: var(--paper);
  color: var(--text);
  font-family: var(--font-sans);
  hyphens: auto;
  line-height: 1.62;
  box-shadow: 0 18px 50px var(--paper-shadow);
}

body > *:first-child {
  margin-top: 0;
}

::selection {
  background: var(--selection-bg);
  color: inherit;
}

#title-block-header {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: baseline;
  margin: -0.4rem 0 2.2rem;
  padding-bottom: 0.75rem;
  border-bottom: 3px solid var(--h1-accent);
}

#title-block-header .title {
  grid-column: 1 / -1;
  margin: 0 0 0.65rem;
  padding: 0;
  border: 0;
  font-size: 1.9rem;
  color: var(--title-text);
  text-align: left;
}

/* Title left, subtitle right: spreads the information across the block instead of
   stacking two centred/left lines, which read as one lump. */
#title-block-header .subtitle {
  grid-column: 1;
  margin: -0.35rem 0 0.8rem;
  color: var(--heading);
  font-size: 1.3rem;
  font-weight: 500;
  text-align: right;
  text-wrap: balance;
}

/* Generated, not typed: the dash marks the subtitle as a continuation of the title,
   and stays out of the document's own text and copy/paste. */
#title-block-header .subtitle::before {
  content: "\2014\2002"; /* em-dash, en-space */
}

#title-block-header p.date {
  position: absolute;
  bottom: 0;
  right: 0;
  transform: translateY(50%);
  margin: 0;
  padding: 0 0 0 0.5rem;
  background: var(--paper);
  color: var(--h1-accent);
  font-weight: 700;
  font-size: 0.82rem;
  line-height: 1.5;
}

.abstract {
  --ab-lead: 1.2rem;
  --ab-gap:  0.45rem;
  /* Inset from the measure by 2em, not 3rem: the block is a passage of the document, not
     an aside, and it was standing further off than it needed to. */
  margin: 0 2em 2rem;
  padding-bottom: 0.9rem;
  /* --border is a hairline meant for dividing table rows; against paper it is 1.35:1 and
     all but invisible at this length. --border-strong doubles that, and 2px gives the rule
     enough body to read as a deliberate edge. */
  border-bottom: 2px solid var(--border-strong);
  font-size: 1.05rem;
}

.abstract-title {
  display: flex;
  align-items: center;
  gap: var(--ab-gap);
  margin-bottom: 0.55em;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}

.abstract-title::before,
.abstract-title::after {
  content: '';
  height: 2px;
  background: var(--border-strong);
}

.abstract-title::before {
  width: var(--ab-lead);
  flex-shrink: 0;
}

.abstract-title::after {
  flex: 1;
}

/* The landing page's date, flush right on the ABSTRACT rule.
   order: 1 is what puts it there. ::after is the last flex item whatever the source order,
   so without it the date would land between the label and the rule, and the rule would then
   run past it to the margin. Ordered after ::after, the flexing rule stretches between the
   label and the date instead. --h1-accent, matching the date on an ordinary page; the
   surrounding size, weight and tracking are inherited from .abstract-title. */
.abstract-date {
  order: 1;
  flex-shrink: 0;
  color: var(--h1-accent);
}

/* The text starts where the ABSTRACT label does, past the rule that leads into it. The
   same amount on the right, so the passage sits square inside its own block rather than
   hanging off the left. */
.abstract p {
  margin: 0;
  padding-left: calc(var(--ab-lead) + var(--ab-gap));
  padding-right: calc(var(--ab-lead) + var(--ab-gap));
  font-size: 1em;
  font-style: italic;
  line-height: 1.65;
}

.abstract p + p {
  margin-top: 0.6em;
}

section.footnotes {
  margin-top: 2rem;
}

/* THE MARKER COLUMN LINES UP WITH THE LABEL, and 1.65rem is not a round number chosen by eye:
   it is .footnotes-title's rule (::before, 1.2rem) plus its flex gap (0.45rem), which is where
   the F of FOOTNOTES starts. So the ol's own box edge sits under the F.

   NO padding-left. It carried 1em, and that is a leftover from before list markers were drawn
   as an absolutely-positioned ::before in a fixed column -- back when the marker lived in the
   browser's gutter and the padding was what made room for it. `ul, ol' now set padding-left: 0
   and the li reserves --marker-col + --marker-gap itself, so the 1em was pure surplus: measured
   in Firefox at a 1200px viewport, the label's F sat at 231.7px and the marker column began at
   249.7px, 18px to its right, which put the `1.' under the T of FOOTNOTES. Removing it puts the
   COLUMN back on the F. */
section.footnotes ol {
  margin-left: 1.65rem;
  margin-right: 1.65rem;
}

/* THE NUMBER RANGES LEFT HERE, AND ONLY HERE. Every other list marker in this sheet sits flush
   RIGHT in its column, which is what keeps `9.' and `10.' on one edge; a footnote number has
   something else to line up with, and it wins. The column's own left edge is derived from the
   FOOTNOTES label above it, and ranging right left a one-digit number floating inside that
   column: measured in Chromium at 1200px, the label's F and the ol's box edge both at 231.69px,
   the marker column 28.8px wide, and `1.' starting about 14px into it -- under the second O of
   FOOTNOTES, while `10.' and `11.' started on the F. The rule the alignment exists to serve is
   the one the reader can see, so the numbers all start at the F and the two-digit ones run on
   into the gap instead.

   The note TEXT does not move: the li reserves --marker-col + --marker-gap as padding whatever
   the marker does inside it, so every note still begins on one edge. */
section.footnotes ol > li::before {
  text-align: left;
}

.footnotes-title {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 0.55em;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Same construction as the abstract's rule, and the same reason for the stronger token:
   over the width of the measure --border is not there. */
.footnotes-title::before,
.footnotes-title::after {
  content: '';
  height: 2px;
  background: var(--border-strong);
}

.footnotes-title::before {
  width: 1.2rem;
  flex-shrink: 0;
}

.footnotes-title::after {
  flex: 1;
}


/* The TOC is a collapsible disclosure in the body flow, after the abstract. The <details>
   carries the block's own margin, the <summary> reads like the other front-matter labels
   (the chevron comes from the generic summary rule), and the <nav id="TOC"> keeps the box
   and the id Summaries reads. The nav's own heading is the sidebar's label at wide widths;
   inside the disclosure the summary already says the same thing, so it stays hidden here. */
.toc {
  margin: 0 0 2rem;
}

/* A CONTROL, NOT A CAPTION. This was 0.72rem in --muted with the shared 0.4em chevron, which
   at that size is 5.2px of hairline -- measured -- so the row read as a small grey label that
   happened to sit above the contents, and nothing about it said it could be opened or shut.

   Three things carry that instead: the text at a legible size, the body's own colour rather
   than the muted grey a caption takes, and a chevron sized against the row rather than against
   0.72rem. The uppercase tracking stays, since it is what keeps the row a label for the block
   below rather than a heading of its own. */
.toc > summary {
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
  /* The hit area, and the reason it is stated: a pointer: cursor on a one-line row is a small
     target, and the padding is what a finger gets. It also gives the hover tint something to
     paint, which is the second affordance. */
  padding: 0.3rem 0.4rem;
  border-radius: 5px;
  /* Centred, with the chevron travelling with the word rather than sitting out at the margin:
     the ::before is inline-block, so text-align carries the pair together. The row spans the
     measure, which is also what makes the hover tint read as one control rather than a tab. */
  text-align: center;
}

/* Scoped to .toc, never the shared summary::before: site.css's .page-summary overrides that
   rule's content, size, borders and transform, and anything added there has to be answered
   here -- see the note above summary::before. A wider, thicker chevron is safe as long as it
   stays inside this selector. */
.toc > summary::before {
  width: 0.46em;
  height: 0.46em;
  border-right-width: 2.5px;
  border-bottom-width: 2.5px;
}

/* A MIRRORED TWIN ON THE RIGHT, so the row is bracketed rather than merely tagged. It is the
   same shape as the mark on the left -- two borders on an empty box -- at another rotation:
   -45deg points the corner right, 135deg points it left, and 45deg points it down. So closed
   the pair face inward, and open they both fall to the same downward corner and the row reads
   as one control rather than two marks that happen to agree.

   Declared in full rather than inheriting from summary::before, because that rule is shared
   with site.css's .page-summary and must not gain a ::after it would have to answer. */
.toc > summary::after {
  content: "";
  display: inline-block;
  width: 0.46em;
  height: 0.46em;
  margin-left: 0.5em;
  border-right: 2.5px solid var(--details-marker);
  border-bottom: 2.5px solid var(--details-marker);
  transform: rotate(135deg) translate(-0.07em, 0.07em);
  transition: transform 0.2s ease;
}

.toc[open] > summary::after {
  transform: rotate(45deg) translateY(-0.05em);
}

@media (prefers-reduced-motion: reduce) {
  .toc > summary::after {
    transition: none;
  }
}

.toc > summary:hover {
  background: var(--accent-soft, rgba(128, 128, 128, 0.12));
}

.toc[open] > summary {
  margin-bottom: 0.4rem;
}

.toc > #TOC > h2 {
  display: none;
}

#TOC {
  margin: 0;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--toc-bg);
}

#TOC ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

#TOC li {
  margin: 0;
  padding-left: 0;
  list-style: none;
}

#TOC li::before {
  content: none;
}

/* What may and may not be split when the list runs in two columns.
   
   `break-inside: avoid` used to sit on every li, which reads as "keep an entry together"
   and means something much larger: a top-level li CONTAINS its whole subtree, so a chapter
   and all its sections were one unbreakable block and the columns could only break between
   chapters. With a dozen of those, the second column ended wherever the last whole chapter
   happened to fit.

   The unbreakable unit is one entry's own link, not its descendants. That lets a break fall
   between any two lines, which is as granular as this list gets. */
#TOC a {
  break-inside: avoid;
}

/* A parent left at the foot of a column with its children in the next one reads as a
   heading for nothing. Cheaper than avoiding the split: keep the label with what follows. */
#TOC > ul > li > a,
#TOC > ul > li > ul > li > a {
  break-after: avoid;
}

#TOC a {
  display: block;
  padding: 0.22rem 0.55rem;
  border-radius: 5px;
  color: var(--text);
  line-height: 1.24;
  text-decoration: none;
}

#TOC > ul > li > a {
  font-weight: 600;
}

#TOC > ul > li > ul > li > a {
  padding-left: 1.25rem;
  font-size: 0.95rem;
  font-weight: 400;
  opacity: 0.82;
}

#TOC > ul > li > ul > li > ul > li > a {
  padding-left: 2.15rem;
  font-size: 0.92rem;
  opacity: 0.67;
}

#TOC a:hover {
  background: var(--toc-hover);
  color: var(--accent);
  opacity: 1;
  text-decoration: none;
}

@media (min-width: 721px) {
  #TOC > ul {
    column-count: 2;
    column-gap: 1.4rem;
  }
}

h1,
h2,
h3,
h4,
h5,
h6 {
  hyphens: none;
  line-height: 1.25;
  color: var(--heading);
  text-wrap: balance;
}

h1,
h2,
h3 {
  color: var(--heading-xl);
}

/* ── Heading numbers, under --number-sections ────────────────────────────────
   Three levels: 1, 1.1, 1.1.1, and nothing deeper. A fourth component stops being a locator
   and starts being a coordinate, and the contents list is three deep as well, so a 1.1.1.1
   would be a number that appears on the page and nowhere a reader could look it up.

   DONE IN CSS BECAUSE IT CANNOT BE DONE ANYWHERE ELSE. Pandoc applies --number-sections in
   the WRITER, not the AST, so no Lua filter can see the number to drop it, and there is no
   depth option. The span is still emitted below h3 and still carries the number; this hides
   it. The PDF caps the same three levels properly, in ConTeXt's own head setups.

   A DESCENDANT COMBINATOR, NEVER `h4 > .header-section-number`. page.js wraps each heading's
   content in a whole-title self-link, so by the time the page is on screen the span is
   `h4 > a > span` and the child form matches nothing -- measured in Chromium, where
   `querySelector('h4 > .header-section-number')` returns null on the rendered page while the
   source markup has exactly that shape. Anything selecting inside a heading has to survive
   that wrapping.

   Pandoc writes the separating space OUTSIDE the span, so hiding the span leaves one leading
   space in the heading. That collapses: it is leading whitespace in a line box, which is what
   `white-space: normal` drops. Measured -- the h4's first visible glyph sits exactly on the
   heading's own left edge, no indent. */
h4 .header-section-number,
h5 .header-section-number,
h6 .header-section-number {
  display: none;
}

/* A number is a locator; the title carries the voice. Set back a little so a run of headings
   reads down its titles and not down its numbers. `ch` rather than `em` so the gap does not
   grow with the heading's size: it is the space between two things on one line, not a share of
   the type. */
.header-section-number {
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
  margin-right: 0.6ch;
}

/* A TENTH OFF THE HEADING'S SIZE, AND ONLY IN THE HEADING. The number is the smaller half of
   the pair, and at the title's full size it competes with it -- the muted colour alone was
   carrying that distinction. `em`, so it is a tenth of whatever the heading is set at and one
   rule serves all three levels; the PDF says the same thing as `\padoxheadnumsize`, taken off
   the body size there because ConTeXt re-applies the head's size step on top.

   SCOPED TO THE HEADINGS, because pandoc copies the whole heading -- number span included --
   into the contents list, and the numbers there are right as they are. h1-h3 only: the
   stylesheet hides the span below h3 outright. */
:is(h1, h2, h3) .header-section-number {
  font-size: 0.9em;
}

h1 {
  margin: 2.5rem 0 1.5rem 0;
  padding-top: 0.65rem;
  border-top: 3px solid var(--h1-accent);
  font-size: 1.68rem;
  letter-spacing: 0;
}
.body > h1:first-child {
  margin-top: 0.5rem;
}
#title-block-header + h1 {
  border-top: none;
  padding-top: 0;
  margin-top: 1.6rem;
}

body:not(:has(#title-block-header)) h1:first-of-type {
  margin-top: 0;
  padding-bottom: 0.85rem;
  border-bottom: 3px solid var(--h1-accent);
  margin-bottom: 2.8rem;
  text-align: left; /* mimics the title block, which is left-aligned */
  color: var(--title-text);
}

h2 {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-top: 2.0rem;
  font-size: 1.45rem;
}

h2::after {
  content: '';
  flex: 1;
  height: 3px;
  background: var(--border-strong);
  transform: translateY(1px);
}

h3 {
  margin-top: 1.9rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--border-strong);
  font-size: 1.3rem;
}

/* A paragraph heading: the h4's line-height is the body's 1.62 and the following paragraph's
   top margin is zero, so the h4 stays bound to the paragraph it labels rather than standing a
   full paragraph gap above it.

   THE SPACE ABOVE IS A PARAGRAPH'S, AND IT WAS 1.5rem. A label is a new thing on the page and
   starts where a paragraph would; at 1.5rem it started nearly twice as far in, so a run of
   short labelled passages read as a run of sections. 0.8rem is the p rule's own margin, and it
   is what an h4 inside an admonition was already given for exactly this reason -- that
   override said the base value was wrong and patched the one place it showed, and it is gone
   now that the base says it. Measured, 27px above against the 14.4px two paragraphs stand
   apart.

   THE SIZE IS THE CAPTION'S, AND THE CAPTION IS WHAT SETTLES IT. It went to 1rem for one
   release, on the argument that this page is set in the sans throughout so the label is
   already the heading colour, already at the heading's leading, and already free to take the
   author's bold -- three things carrying it before any size step. What that left out is what
   sits next to it: `#### *Name*' above a table renders as sans italic, and so does the
   caption under it, so the two are the same words in the same face a line apart and a 5%
   step between them reads as one of them being wrong. Measured, 18px against 18.9px. The two
   move together in all three formats: \padoxheadparasize and \padoxcaptionsize are one
   number in the PDF for the x-height reason, and one number here because the reader can see
   both at once. What remains between them is the --heading colour, which is the label's own.

   THE BOTTOM MARGIN IS NOT ZERO, AND IT USED TO BE. At zero the space under the heading was
   exactly the body's line-to-line leading -- measured 29.16px, the same pitch as two ordinary
   prose lines -- while the heading is set in a heavier face, which puts more ink on the line
   than the roman under it and reads tighter still at the same distance. 0.25rem lands it at
   33.66px, clear of the 29.16 line pitch and well short of the 43.56 a paragraph break gives,
   so the label gains air without detaching from its block. The PDF takes the same quarter em
   through \padoxheadparaafter; both were measured, and both moved together. */
/* REGULAR WEIGHT, A STEP ABOVE THE BODY SIZE, AND THE BOLD IS THE AUTHOR'S. The PDF settled
   this: a paragraph heading is a label on the block under it, and the face and the size carry
   that — a weight on top of both read as a section heading again, which is what turning an h4
   into a labelled paragraph was meant to stop. What decided it is what the weight LEAVES the
   author: at the UA's bold, `#### **Name**' had nowhere heavier to go and an author could not
   ask for a heavier label. At 400, `<strong>` is a real step.

   1.05rem is the PDF's `\padoxheadparasize' and the caption's own number. It carries more here
   than it does there: the PDF also changes face, serif to sans, and this page is set in the
   sans throughout, so the size and the --heading colour are the whole of the distinction. */
h4 {
  margin-top: 0.8rem;
  margin-bottom: 0.25rem;
  font-size: 1.05rem;
  font-weight: 400;
  line-height: 1.62;
}

/* A LABEL ON A TRANSCRIPT STANDS OFF A BOX, and a box's edge reads further away than the same
   white over a line of type. `#### `ps` -- description' above a ::: {.cli} is the other way of
   titling a transcript -- the bar codelabels.lua draws names the block from inside its own
   frame, this names it from outside, in the prose's voice -- so the two have to be the same
   distance from what they name.

   BOTH SIDES HAVE TO MOVE. Adjacent margins collapse to the larger, so zeroing the heading's
   own 0.25rem changes nothing while div.cli's 1rem stands: measured 18px of box gap either
   way. The two are stated equal here so the collapse cannot pick a value neither of them
   means.

   0.5rem, AND IT WAS ZERO -- which is where the PDF's own geometry led and it is not right on
   the screen. What the eye compares is the label's ink against the thing under it, and the two
   formats put different amounts of that gap inside the box: in the PDF the heading's ink to
   the box edge is one line pitch, while a browser's line box already carries half the leading
   below the ink, so a zero MARGIN leaves only that half-leading -- about 5.6px, against the
   15.7px an ordinary paragraph shows under the same heading. The box edge is a hard edge and
   reads tighter still. 0.5rem lands it at 14.6px: a little under the paragraph's distance,
   which is what a label on a box should be, and visibly clear of the frame.

   :has() to look forward, since CSS has no "followed by" combinator; .cli on the heading
   itself is the escape, for the same reason context-headings.lua accepts it -- a heading
   whose transcript is not the very next block. Unnested, which is the form that parses. */
h4:has(+ div.cli),
h4.cli {
  margin-bottom: 0.5rem;
}

h4:has(+ div.cli) + div.cli {
  margin-top: 0.5rem;
}

h4 + p {
  margin-top: 0;
}

h5,
h6 {
  margin: 1.15rem 0 0.35rem;
  font-size: 1rem;
  line-height: 1.35;
}

h5 {
  font-weight: 650;
}

h6 {
  color: var(--muted);
  font-weight: 500;
}

/* A heading opening an admonition: set as the bold paragraph authors were writing by hand,
   because the box has already set the passage apart and an h6 styled as an h6 inside one
   reads as a second label under the first.

   It stays a Header in the AST, so it keeps its identifier and its self-link — which is the
   whole reason for not simply writing **bold**, since a paragraph cannot be linked to.
   Everything the heading rules set has to be undone here: the muted colour, the lighter
   weight, the wide top margin, and the balanced wrapping that would leave a short name on
   two lines. admonitions.lua adds the class; it is not written by hand. */
h6.admon-heading {
  /* Inline, so it runs on from the NOTE/WARNING label exactly as the paragraph authors were
     writing by hand does — the label and the name it introduces belong on one line.
     The gap between them is the label's own right margin, so this takes none. */
  display: inline;
  margin: 0;
  color: inherit;
  /* THE H4'S OWN SIZE AND WEIGHT: an admonition heading and a paragraph heading are one
     construct, which is what the PDF was fixed to say and what this sheet already said at 700.
     They move together or they stop being one thing. `em' not `rem', since this one is inline
     in a box that may have set its own size. */
  font-size: 1.05em;
  font-weight: 400;
  line-height: inherit;
}

/* ── Titled line blocks: :::{.lines type="Syntax"} ───────────────────────────
   A fenced div holding a level-5 heading and one or more Markdown line blocks. The heading
   names the group and says what kind of thing it is; the rule down the left ties the lines
   to their title, so they read as belonging to it rather than merely following it.

   Written for syntax summaries and not restricted to them: nothing here sets a face, so the
   lines are whatever their own spans make them -- .cc, .stx, the drawn brackets, or prose.

   GROUPS ARE SEPARATE LINE BLOCKS, not blank lines inside one. A blank line in a line block
   is a real line and takes a full line box, which is taller than the space wanted between
   two alternatives and cannot be reached from CSS -- there is no selector for "an empty
   line". Splitting the groups turns that gap into the space between two elements, which
   can be set. That is the whole reason for the div: it is what holds the groups together
   once they are no longer one block.

   The rule stays unbroken across the gap because the gap is PADDING, not margin. The blocks
   touch, each contributing half the space inside its own border, so the border runs
   continuously down the side of every group -- with margin between them it would break at
   each gap. The same padding gives the first block its space below the heading and the last
   one its space at the end. */
.lines h5,
h5.lines {
  /* Almost touching what follows: the rule should read as continuing down from the title,
     and the h5's usual 0.35rem opens a gap the rule then starts below. */
  margin-bottom: 0.15rem;
  font-style: italic;
  font-weight: 700;
}

/* The kind, in the colour a .file or .snip label gives its filename -- both name what the
   block below them is, so they are the same kind of label and take the same colour. That
   colour is --co-color, not the accent, and the two moved together: see the note on
   `.code-label code'. Here the ground is the PAGE rather than a tint, and --h1-accent failed
   on it too -- #c47828 is 3.46:1 on --paper and 3.23:1 on --page, where this is 1em text at
   weight 600 and so has no large-text allowance. --co-color is 8.48 and 7.91. Dark passed
   either way (6.25/6.84 against 7.37/8.07), which is the same light-only failure the label
   bar had, and for the same reason: the accent was only ever measured as an accent.

   lines.lua puts it there, as a span rather than as generated content, because the attribute
   it comes from cannot stay on the heading: pandoc's HTML writer discards a div whose heading
   carries a key-value attribute, and the div is what holds the groups together. See the note
   at the top of that filter.

   The separator IS generated, so it can be set apart from both the kind and the title, and so
   the heading's own text stays the words someone wrote. */
.lines-kind {
  color: var(--co-color);
  font-style: normal;
  font-weight: 600;
}

.lines-kind::after {
  content: " — ";
  color: var(--muted);
  font-weight: 400;
}

.lines .line-block,
h5.lines + .line-block {
  margin: 0;
  /* Top and bottom are the gap between groups, halved: two touching blocks make one space
     between them, and the outermost edges give the heading and the end of the div theirs.

     Left is the distance from the rule to the text: the old 0.75rem plus about one
     fixed-width space, which measured 9.7px against a 13.5px padding here. Stated as one
     number rather than as a sum, because it is a measure and not a calculation -- the space
     it was taken from belongs to the mono face and this padding does not. */
  padding: 0.4rem 0 0.4rem 1.3rem;
  border-left: 2px solid var(--border-strong);
  /* Never wrap: these lines are aligned column by column -- a .ws indent, a bracket opening
     on one line and closing on another -- and a break inserted by the window rather than by
     the author destroys that while looking exactly like a line the author wrote. Scroll
     rather than hide: a scrollbar says there is more to the right, and overflow: hidden
     takes the rest of the line away with nothing to say it did.

     No closing brace anywhere in this comment, on purpose -- not in a .ws written out in
     full, and not in a regex quoted to explain the point. verify.ps1 reads a rule body as
     "everything up to the first closing brace", so one inside a comment ends the rule early
     and the declarations after it are silently unasserted. */
  white-space: nowrap;
  overflow-x: auto;
  /* auto on one axis computes the other to auto as well, and a box a fraction of a pixel
     taller than its content then raises a vertical scrollbar that scrolls by a pixel. */
  overflow-y: hidden;
}

/* The container carries the spacing to the prose around it, so the blocks inside can keep the
   margin: 0 that holds the rule together. Without it -- a bare .lines heading and one line
   block -- the block is the last thing there and needs the margin itself.

   section AND div: pandoc's HTML writer emits <section> for a fenced div whose first block is
   a heading, hoisting the heading's id onto it, and <div> otherwise. Both are the same
   construct here and neither spelling is asked for. */
section.lines,
div.lines {
  /* Even, not the 1.15/1.35 a code block or a table takes. Those open with the block itself,
     so the eye measures from the prose to a hard edge; this one opens with a TITLE, and the
     rule that is its edge starts a line further down. At 1.15 above the title sat closer to
     the paragraph before it than the group's foot did to the paragraph after — measured
     20.68px against 24.30px — and the block read as belonging to what came before it.

     The heading's own margin-top collapses through this one, so the larger of the two wins
     and this is what has to move. */
  margin: 1.35rem 0;
}

/* The standalone form only -- a .lines heading with no container around it, where the block
   is the last thing there and nothing else will space it from the prose below.
   :not(.lines) > excludes the container's own first block, which is also a .line-block
   directly after a .lines heading and would otherwise take a bottom margin that breaks the
   rule running down the side of the group beneath it. */
:not(.lines) > h5.lines + .line-block {
  margin-bottom: 1.35rem;
}

/* .codenotes holds notes under a code block. It is its own construct, not a .lines variant:
   the one thing it shares is the left rule, which is stated here on the div itself. The left
   padding is the indent from that rule, carried by the div so every kind of content in it --
   a definition-list table, a (1) legend, prose -- starts the same distance in.

   0.8rem IS THE PDF'S OWN INDENT. There the rule measures at x 92.48 and the text starts at
   102.04, 9.56pt at a 11.96pt body -- 0.80em. This was 1.3rem, 1.30em of the same body, so the
   notes sat two-thirds again further off their rule on screen than on paper, and the block read
   as indented rather than as ruled. */
section.codenotes,
div.codenotes {
  margin: 1.35rem 0;
  padding: 0 0 0 0.8rem;
  border-left: 2px solid var(--border-strong);
}

/* A definition list inside .codenotes is rewritten (codenotes.lua) into this two-column table.
   The term is a line number or range, set in mono and kept on one line; its column sizes itself
   to the widest term, with a two-digit floor so a lone "1" does not cramp the note against it.
   Top-aligned so the term stays on the first line of a wrapped note. The term cell drops its own
   left padding -- the indent now lives on the div. */
.codenotes table td {
  vertical-align: top;
}
.codenotes table td:first-child {
  min-width: 2ch;
  white-space: nowrap;
  padding-left: 0;
  /* FLUSH RIGHT, like every other marker here: an ordered list's number and a footnote's sit
     right in a fixed column, and a line number is the same kind of thing. The column is as wide
     as the widest term, so left-aligned a lone "4" sat 49px from the words it introduced while
     "13-15" sat 13px from them -- the same pair at two different distances down one block. */
  text-align: right;
}
/* ONE padding between the columns, not two. A term and its note are a pair, and the cells'
   shared 0.7rem each put 25.2px of air between "13-15" and the words it introduces -- more
   than the space between two sentences, and the pair stopped reading as one. The note cell
   gives up its left padding, so the gap is the term cell's own 0.7rem (12.6px measured) and
   the note still starts clear of the longest term. */
.codenotes table td:first-child + td {
  padding-left: 0;
}

/* A note's LAST block ends the note: the gap to the next row is the cell's own padding, and
   not that plus a paragraph's margin as well. A note closing with a list left 26px inside the
   cell below it where one closing with a sentence left 11px -- the same construct at two
   spacings down one block, which is what the admonitions' own :last-child rule exists to stop.

   Two levels deep, because a (1) legend is an <ol> inside a .paren-list <div>: the div carries
   no margin of its own, so zeroing only the cell's own last child left the list's 18px behind
   it. */
.codenotes table td > :last-child,
.codenotes table td > :last-child > :last-child {
  margin-bottom: 0;
}

/* ── Code-block label: a heading carrying .file or .snip, or under gfm the paragraph
   shape **`name`** -- description, immediately before a code block. codelabels.lua adds
   the class; it is not written by hand.

   The heading shape stays a heading — same level, same id, same self-link — so a label is
   bookmarkable like anything else. That means undoing the heading styling here rather than
   inheriting it: the element carrying this class is an h5 or h6 in one shape and a div in
   the other, and the two must be indistinguishable. */
.code-label {
  position: relative;
  margin: 1.5rem 0 0;
  /* OPTICALLY CENTRED, NOT EQUALLY PADDED. Equal padding centres the LINE BOX, and a line box
     is not the ink: the space between the cap line and the top of the box is larger than the
     space under the baseline, because the descender room below is mostly unused. Measured on
     a rendered bar, ink rows 14..31 of a 41px band -- 14px above, 9px below, and the text read
     as sitting low in its own bar. The pair below moves it up 2.7px and gives that back
     underneath, which lands the ink at 11.3/11.7. The total is unchanged, so nothing around
     the bar moves. */
  padding: 0.26rem 0.9rem 0.44rem;
  /* A LABEL IS A CAPTION, NOT A LINE OF PROSE, so it does not need the body's leading. The bar
     inherited 1.35 from the page and stood 41px tall for one line of text; at 1.2 the line box
     is 21.6px instead of 24.3 and the bar comes down to about 34 without the text moving any
     closer to the edges -- the padding pair above is re-derived for the tighter line box and
     keeps the ink optically centred. */
  line-height: 1.2;
  background: var(--label-bg);
  border: 1px solid var(--border-strong);
  /* No rule under the label. The band IS the divider between the name and the code, and a
     line under a band that is already a line reads as two dividers where there is one
     boundary. The block below sets border-top: none for the same reason. */
  border-bottom: 0;
  border-radius: 8px 8px 0 0;
  /* The bar is ONE object, so the face is set on the bar and not only on its two halves: the
     em dash between the name and the description is ordinary text and would otherwise take
     whatever the body face is. Stated rather than inherited from the heading, because the
     gfm shape lands on a div and because a --style sheet may change the body face. */
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 400;
  /* NO line-height HERE. This block sets the FACE on the bar; the leading is set above, at 1.2,
     with the padding pair derived from it. A second `line-height: 1.35' rode in with the face
     and, being later in the same rule, silently won -- so the bar stood at 37.88px where the
     reasoning above says about 34, and had done since the face was stated. A duplicate
     declaration in one block is invisible to every check here: the property is present, the
     value is legal, and the earlier one simply never applies. */
  color: var(--text);
  text-wrap: initial;
}

/* The name, whatever wraps it: <strong> in the gfm shape, nothing in the heading shape.
   The weight is set here rather than inherited from that <strong> so the two shapes are
   indistinguishable, which is the whole requirement.

   SANS, and it says so. A label bar is chrome — it names the block under it — and chrome is
   set in the sans throughout, the way a table heading and an admonition label are. The name
   is written as a code span, so without this it inherits the mono from `code` and the bar
   reads as a first line of the code rather than as a title over it. Stated rather than left
   to the body face, because the body face is a --style sheet's to change: this sheet's is
   already a sans, and the day it is not, an inherited rule is the one that breaks. */
.code-label code {
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  white-space: normal;
  font-family: var(--font-sans);
  font-size: 1em;
  font-weight: 600;
  /* --co-color, NOT the accent, and that is a contrast fault rather than a preference.
     A label name is small text sitting on a TINT, and --h1-accent was never measured against
     one: #c47828 is 2.75:1 on the code bar (#dfe6ef) and 2.75:1 on the transcript bar
     (#dfe7e4), against 3.46:1 on white, which it only ever cleared as a large-text accent.
     --co-color is 6.75 and 6.74 on those two. In DARK the accent passed all along -- 5.85 on
     both bars -- which is why the bar read as fine there and wrong in light, and why this is
     set for both modes rather than patched in one: --co-color is 6.90 dark, and a name that
     is a named term in one mode may not be an accent-coloured word in the other.

     The PDF settled this first and the stylesheet did not follow. padoxAccent is #90581d --
     darker than this sheet's #c47828 precisely so it clears 4.5:1 on both label bands -- so
     the PDF has had a passing label name all along. #6b4423 is padoxCC's own hex, which
     .cc/.co already share with this sheet, so the name now agrees with the PDF on being a
     named term rather than disagreeing with it on being an accent. */
  color: var(--co-color);
}

/* The description carries the kind, since the names cannot: a language designator and a
   filename look alike. A complete file is the heavier thing and takes the weight; a fragment
   takes italic at the ordinary weight. Sans in both, so the bar is one object rather than a
   sans name with a body-face title after it. codelabels.lua marks the description whether or
   not it became a self-link, so both rules reach it either way. */
.code-label .code-label-desc {
  font-family: var(--font-sans);
}

.code-label.file .code-label-desc {
  font-weight: 600;
}

.code-label.snip .code-label-desc {
  font-style: italic;
  font-weight: 400;
}

/* Emphasis inside an italic run reverts to upright, or it says nothing at all. */
.code-label.snip .code-label-desc em {
  font-style: normal;
}

/* A linked filename looks exactly like an unlinked one — the bar should not change
   appearance because the name happens to point somewhere. Setting the colour on the link
   itself, not only on the code inside it, is what makes the hover underline match the
   filename: an underline is painted in the element's own colour, and without this the
   link keeps whatever --link or --link-local gave it.

   The name only. It is the first element in the bar, which is what distinguishes it from
   the description's self-link and from any link written inside the description — those
   are ordinary links and keep ordinary link colours. :not(.heading-link) covers the
   degenerate label that is all description and no name. */
.code-label > a:first-child:not(.heading-link) {
  /* --co-color, and it MUST be whatever `.code-label code' is: the rule above this one is
     that a linked name looks exactly like an unlinked one, and these two are the linked and
     unlinked halves of one thing. Moving the code span and leaving this behind would have
     made a linked filename the only accent-coloured name in the bar -- and kept it at the
     2.75:1 the code span was moved off. */
  color: var(--co-color);
}


/* Hover is where the name admits to being a link. The description is excluded along with
   the '#': both are self-links, and a heading's self-link never underlines. */
.code-label a:not(.heading-anchor, .heading-link):hover {
  text-decoration: underline;
}

/* ── Links that stay inside the collection ───────────────────────────────────
   rewritelinks.lua marks them, so this is a fact about the target rather than a guess
   from the href. Deliberately class-only (0,1,0): .code-label a is more specific and
   keeps the accent, and nothing here can outrank a component that styles its own links. */
.local-link {
  color: var(--link-local);
}

/* The transcript joins its label the same way a code block does, and the bar takes the
   console border rather than imposing the code one: the rule is the transcript's own, and
   half a left edge in each colour reads as a mistake. */
/* The border was already the console's; the tint has to follow it, or the bar is a
   greyish-blue box sitting on a green one. Both come from the block below, which is what
   makes the pair read as one object. */
.code-label:has(+ div.cli) {
  border-color: var(--cli-border);
  background: var(--cli-label-bg);
}

.code-label + div.sourceCode,
.code-label + pre {
  margin-top: 0;
  border-top: none;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  border-color: var(--border-strong);
}

/* Same join, but without taking the border colour with it: the console rule is the
   transcript's own, and the bar above adopts it rather than the other way round. */
.code-label + div.cli {
  margin-top: 0;
  border-top: none;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.heading-link,
.heading-link:hover,
.heading-anchor,
.heading-anchor:hover {
  text-decoration: none;
}

.heading-link {
  color: inherit;
}

.heading-anchor {
  opacity: 0;
  margin-left: 0.35em;
  font-size: 0.75em;
  vertical-align: middle;
  transition: opacity 0.15s;
  user-select: none;
}

.heading-anchor::after {
  content: '#';
  color: var(--muted);
  font-weight: 400;
  font-style: normal;
}

:is(h1, h2, h3, h4, h5, h6):hover .heading-anchor,
.heading-anchor:focus {
  opacity: 1;
}

#TOC .heading-anchor {
  display: none;
}

p {
  margin: 0.8rem 0;
}

a {
  color: var(--link);
  text-decoration: none;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.16em;
}

del,
s {
  position: relative;
  text-decoration: none;
}

del::after,
s::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: calc(46% + 1.5px);
  height: 0.1em;
  background: var(--del-line);
  pointer-events: none;
}

a:hover {
  text-decoration: underline;
  text-decoration-thickness: 0.14em;
}

/* [Ctrl+D]{.kbd} shares the <kbd> element's key cap rather than restating it: the class
   exists because a bracketed span is easier to write inline than raw HTML, not because it
   is a different thing.

   The gap is a *margin*, never padding: padding would widen the key cap itself, and what
   has to grow is the space outside its border. Key caps are written adjacent on purpose —
   [Ctrl+Z]{.kbd}[CR]{.kbd} is one gesture, not two words — so nothing in the source
   separates them and without this they touch exactly. After [x]{.cc} it was worse than
   touching: that class carries margin-right: -1 * --cc-track to cancel the trailing
   letter-space, which pulled the cap 0.72px *over* the preceding glyph.

   Two caps therefore end up with twice the gap of a cap beside a glyph, which is right
   rather than a rounding error: two bordered boxes need more air between them than a
   parenthesis and a border do.

   --kbd-gap is em, and em here is the *cap's* font-size (0.82em), not the body's. So
   0.12em is 1.77px against 18px text, not 2.16px — a hair space per side, a thin space
   between two caps, which is what was asked for. Read the value as 0.82 of what it says
   before changing it. */
kbd,
.kbd {
  display: inline-block;
  min-width: 1.8em;
  margin: 0 var(--kbd-gap, 0.12em);
  padding: 0.1em 0.42em 0.15em;
  font-family: inherit;
  font-size: 0.82em;
  line-height: 1.3;
  color: var(--text);
  text-align: center;
  background: var(--kbd-bg);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  box-shadow: 0 2px 0 var(--kbd-shadow);
  white-space: nowrap;
  vertical-align: 0.08em;
}

/* .cc and .chr both end with a negative margin cancelling their own trailing letter-space,
   which a following key cap would otherwise be pulled into — measured at -0.72px, i.e.
   overlapping. Give the cap back exactly what that margin took, so the gap after [x]{.cc}
   matches the gap after any other glyph.

   The sibling combinator cannot tell `[x]{.cc}[CR]{.kbd}` from `[x]{.cc} and [CR]{.kbd}`:
   intervening text does not break element adjacency in CSS. So this also fires where a
   real space already exists, adding --cc-track there — 0.72px, below the threshold of
   noticing, which is what makes the imprecision affordable. */
/* .chr ALONE now: .cc gave up its tracking with its face, so it has no negative margin left
   for a following cap to be pulled into. */
.chr + :is(kbd, .kbd) {
  margin-left: calc(var(--kbd-gap, 0.12em) + var(--cc-track, 0.04em));
}

/* [SP]{.chr} names a character, usually by its ASCII abbreviation. Smaller, because these
   are two- and three-letter capitals set among lowercase prose and at full size they shout;
   tracked a little, because capitals at a reduced size close up. */
/* A minimum width, so [I]{.chr} and [M]{.chr} read as the same kind of thing rather than
   as two sizes of one. Measured in this font at --chr-size, in the span's own em: l 0.29,
   I 0.37, 0 0.60, A 0.67, M 0.94, W 0.96. The floor is 0.85 — deliberately *under* M, so
   the wide letters keep their own width and nothing is padded out to an M or a W; only
   the narrow singles rise, I from 0.37 to 0.85, which brings it within 10% of M.

   Two- and three-letter names (SP 1.21, ESC 1.83) are already past the floor, so the
   abbreviations this class was written for are untouched.

   Note the unit: em here is the span's font-size (--chr-size, 0.85em), so the floor is
   0.72em of body text, not 0.85. inline-block is what makes min-width apply at all. */
.chr {
  display: inline-block;
  min-width: var(--chr-min, 0.85em);
  text-align: center;
  font-size: var(--chr-size, 0.85em);
  letter-spacing: var(--chr-track, 0.03em);
  margin-right: calc(-1 * var(--chr-track, 0.03em));
}

.page-jump {
  position: fixed;
  right: max(1rem, calc((100vw - 860px) / 2 - 3.4rem));
  bottom: 1rem;
  z-index: 20;
  display: grid;
  place-items: center;
  width: 2.35rem;
  height: 2.35rem;
  border: 1px solid var(--btn-border);
  border-radius: 999px;
  background: var(--btn-bg);
  color: var(--accent);
  line-height: 1;
  text-decoration: none;
  box-shadow: 0 8px 22px var(--btn-shadow);
  backdrop-filter: blur(6px);
}

.page-jump svg {
  display: block;
  width: 1.25rem;
  height: 1.25rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 3.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.page-jump:hover {
  border-color: var(--btn-border-hover);
  background: var(--btn-bg-hover);
  text-decoration: none;
}

.theme-toggle {
  position: fixed;
  right: max(1rem, calc((100vw - 860px) / 2 - 3.4rem));
  bottom: 3.85rem;
  z-index: 20;
  display: grid;
  place-items: center;
  width: 2.35rem;
  height: 2.35rem;
  padding: 0;
  border: 1px solid var(--btn-border);
  border-radius: 999px;
  background: var(--btn-bg);
  color: var(--accent);
  cursor: pointer;
  box-shadow: 0 8px 22px var(--btn-shadow);
  backdrop-filter: blur(6px);
  appearance: none;
}

.theme-toggle svg {
  display: block;
  width: 1.1rem;
  height: 1.1rem;
}

.theme-toggle:hover {
  border-color: var(--btn-border-hover);
  background: var(--btn-bg-hover);
}

hr {
  /* A paragraph's own margin (p is 0.8rem), not the 2.5rem this once was: a divider sits in
     the page like a paragraph break, not like two empty lines. Equal above and below. */
  margin: 0.8rem 0;
  border: 0;
  border-top: 2px solid var(--border-strong);
}

section.footnotes > hr {
  display: none;
}

/* The UA default for strong is "bolder", computed *relative to the parent*, so it compounds:
   700 in a 400 paragraph, 900 inside a th (650 below), and 900 again for a strong nested in a
   strong. The nesting is not hypothetical — `**[**‹*expr*›**]**` parses as
   <strong>[<strong>…</strong>]</strong>, because the inner ** is preceded by punctuation and
   so can only open, never close. An absolute weight removes the dependence on context in all
   three cases; 600 reads as emphasis rather than shouting, and matches the weights used for
   headings elsewhere in this sheet.

   A consequence worth knowing: inside a th, which is already 650, ** now reads the same as
   the surrounding cell text rather than heavier. */
strong,
b {
  font-weight: 600;
}

/* ── Lists ──────────────────────────────────────────────────────────────
   A marker sits flush right in a fixed column, then a gap, then the item's text. Two
   numbers, not one gutter: the column has to hold the widest marker (`10.` / `99.` for an
   ordered list, the drawn square for a bullet) and the gap is the air between the marker and
   the item's own first character, which a wrapped line must not hang under. The PDF's
   enumerate is the same shape -- a 1.4em marker column plus a 1.0em gap -- tuned here for the
   sans at 18px. */

ul,
ol {
  list-style: none;
  padding-left: 0;
  --marker-gap: 0.6em;
}

/* The marker column: a number needs room for two digits, a bullet a third of an em. */
ul { --marker-col: 0.6em; }
ol { --marker-col: 1.6em; }

ul > li,
ol > li {
  position: relative;
  padding-left: calc(var(--marker-col) + var(--marker-gap));
}

ul > li::before,
ol > li::before {
  position: absolute;
  left: 0;
  width: var(--marker-col);
  text-align: right;
}

/* Ordered: decimal, then a) and i) two and three deep. */
ol > li::before {
  content: counter(list-item) ".";
}

ol ol > li::before {
  content: counter(list-item, lower-alpha) ")";
}

ol ol ol > li::before {
  content: counter(list-item, lower-roman) ")";
}

/* Unordered: square, disc, circle, one per level, drawn a shade larger. */
ul > li::before {
  content: "\25AA"; /* BLACK SMALL SQUARE */
  font-size: 1.15em;
}

ul > li > ul > li::before {
  content: "\2022"; /* BULLET */
}

ul > li > ul > li > ul > li::before {
  content: "\25E6"; /* WHITE BULLET */
}

li + li {
  margin-top: 0.25rem;
}

/* Task lists carry a checkbox, not a bullet. */
ul.task-list {
  padding-left: 1.8rem;
}

ul.task-list > li {
  padding-left: 0;
}

ul.task-list > li::before {
  content: none;
}

dl {
  margin: 1rem 0 1.35rem;
}

dt {
  font-weight: 600;
  color: var(--heading);
}

dt + dt {
  margin-top: 0.75rem;
}

dd {
  margin: 0.15rem 0 0.6rem;
  margin-inline-start: 1.4rem;
  color: var(--text);
}

/* A definition list written with blank lines is "loose", and pandoc wraps each definition
   in a paragraph — whose top margin then sits between the term and its definition, pushing
   them 14.4px apart and reading as though they were unrelated. The dd's own 0.15rem is the
   spacing that was meant. The last paragraph gives its bottom margin back for the same
   reason: the gap below belongs to the dd, which is what separates one entry from the next.

   Only the first and last, so a definition of several paragraphs still has them spaced. */
dd > p:first-child {
  margin-top: 0;
}

dd > p:last-child {
  margin-bottom: 0;
}

blockquote {
  margin: 0.8rem 0;
  padding: 0 1rem;
  border-left: 3px solid var(--border-strong);
}

blockquote > :first-child {
  margin-top: 0;
}

blockquote > :last-child {
  margin-bottom: 0;
}

summary {
  list-style: none;
  cursor: pointer;
}

summary::-webkit-details-marker {
  display: none;
}

/* The disclosure mark is drawn, not typed. U+25B6 carries an emoji presentation and
   renders as a colour emoji wherever the text font lacks it -- and an emoji ignores
   `color`, so --details-marker would be silently discarded. The same reasoning made
   .tra and .dra clip-path rather than characters.

   Two borders on an empty box, rotated: right-pointing closed, down-pointing open. The
   shape matches .page-summary in site.css, which arrived at it first.

   It must stay a *border* drawing, and this rule must introduce no property that
   .page-summary does not already reset. A clip-path triangle was tried here and broke
   the page list: .page-summary > summary::before overrides content, size, borders and
   transform, but had no reason to reset `background` or `clip-path`, so both leaked
   through and painted a red triangle behind the blue chevron. Anything added here has
   to be answered there. */
summary::before {
  content: "";
  display: inline-block;
  width: 0.4em;
  height: 0.4em;
  margin-right: 0.5em;
  border-right: 2px solid var(--details-marker);
  border-bottom: 2px solid var(--details-marker);
  transform: rotate(-45deg) translateY(-0.1em);
  transition: transform 0.2s ease;
}

details[open] > summary::before {
  transform: rotate(45deg) translateY(-0.05em);
}

@media (prefers-reduced-motion: reduce) {
  summary::before {
    transition: none;
  }
}

/* ── Admonitions: .admon is the shape, a kind is the colour ──────────────────
   Everything that makes a callout a callout lives here — the box, the padding, the rule down
   the side — and each kind below overrides only what distinguishes it. Write
   :::{.admon .note} to say so explicitly, or :::{.note} and admonitions.lua adds the base
   class for you; the two produce identical markup.

   The five kinds stay in this selector list even though the filter now guarantees .admon
   beside them. Two reasons: HTML rendered before this change carries only class="note", and
   it should keep its box when a newer stylesheet is served next to it; and a document run
   through pandoc without padox's filters still gets a recognisable admonition rather than a
   bare paragraph.

   ORDER MATTERS BELOW. .admon's own colours and each kind's colours are single-class
   selectors of equal specificity, so the later rule wins. The kinds must come after .admon,
   or :::{.admon .note} would be grey. */
/* SQUARE, AND IT WAS 7px. The PDF settled this: a box whose corners are all rounded and one
   of which is then cut reads as having a fourth kind of corner, and the cut is meant to be the
   only thing the eye has to account for. Square everywhere is what makes that work. There is
   no chamfer here -- it would be a `clip-path`, which `epub.css` could not have at all, being
   CSS 2.1 -- so this half is the shape the two formats can agree on and the cut is the PDF's
   own. The tinted CODE block keeps its radius in both, being a different construct. */
.admon,
.tip,
.note,
.important,
.warning,
.caution {
  margin: 1.6rem 0;
  padding: 0.55rem 1rem 0.65rem 1rem;
  border: 1px solid var(--border);
  border-left-width: 5px;
  border-radius: 0;
}

/* A callout with no kind: grey, and the filter gives it no label. For an aside that is worth
   setting apart but is not a warning about anything. */
.admon {
  border-left-color: var(--admon);
  background: var(--admon-soft);
}

/* The one admonition whose rule is not its label's colour. --tip-soft is the console green
   a transcript is drawn on, and a rule dark enough to label the box in small caps is too dark
   to sit beside it -- so the bar takes --tip-rule, the console border, and --tip stays what
   the word TIP is set in. The arithmetic leaves no third option: against #edf5ed a colour
   needs luminance <= 0.160 to clear 4.5:1, and #1a7f37 is already 0.157. */
.tip {
  border-left-color: var(--tip-rule);
  background: var(--tip-soft);
}

.note {
  border-left-color: var(--note);
  background: var(--note-soft);
}

.important {
  border-left-color: var(--important);
  background: var(--important-soft);
}

.warning {
  border-left-color: var(--warning-rule);
  background: var(--warning-soft);
}

.caution {
  border-left-color: var(--caution-rule);
  background: var(--caution-soft);
}

.admon > .title,
.tip > .title,
.note > .title,
.important > .title,
.warning > .title,
.caution > .title {
  display: inline;
  margin: 0 0.5em 0 0;
  font-size: 0.82rem;
  font-weight: 750;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.admon > .title {
  color: var(--admon);
}

.tip > .title {
  color: var(--tip);
}

.note > .title {
  color: var(--note);
}

.important > .title {
  color: var(--important);
}

.warning > .title {
  color: var(--warning);
}

.caution > .title {
  color: var(--caution);
}

.admon > .title p,
.tip > .title p,
.note > .title p,
.important > .title p,
.warning > .title p,
.caution > .title p {
  display: inline;
  margin: 0;
}

.admon > .title + p,
.tip > .title + p,
.note > .title + p,
.important > .title + p,
.warning > .title + p,
.caution > .title + p {
  display: inline;
  margin: 0;
}

.admon > :last-child,
.tip > :last-child,
.note > :last-child,
.important > :last-child,
.warning > :last-child,
.caution > :last-child {
  margin-bottom: 0;
}

/* The same at the top, which only a box with no label ever reaches: where there is a title
   the first child is the title itself, and the paragraph beside it already has its margins
   zeroed by the rule above. A bare :::{.admon} opens straight into a paragraph, and that
   paragraph's 0.4rem carried the text down inside its own box -- measured 18.1px above
   against 12.7px below, where a labelled box sits at 15.9 and 16.8. The box has padding of
   its own to do this job; a margin inside it is a second opinion about the same gap. */
.admon > :first-child,
.tip > :first-child,
.note > :first-child,
.important > :first-child,
.warning > :first-child,
.caution > :first-child {
  margin-top: 0;
}

.admon p,
.tip p,
.note p,
.important p,
.warning p,
.caution p {
  margin-top: 0.4rem;
}

/* THE FRAME IS DRAWN BY THE CELLS, not by the table, and the caption is why. `display: block`
   is what lets a wide table scroll instead of pushing the measure open -- but it also means a
   caption, whose own display is table-caption, is wrapped in an anonymous table rather than
   becoming the table wrapper's caption. It therefore sits INSIDE this element's border box,
   and a border here boxed the caption in with the grid.

   So the border moves to the cells and the four outer corners take the radius. That needs
   border-collapse: separate, because a collapsed table ignores border-radius outright;
   border-spacing: 0 keeps the grid tight, and the doubled lines where two cells meet are
   removed one side at a time below. */
table {
  display: block;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
  /* The top margin is a PARAGRAPH's, so a table is spaced from the prose above it exactly
     as another paragraph would be. It was 1.4rem, and because overflow-x makes this its
     own formatting context the two margins do not collapse to the larger -- they are
     adjacent siblings, so they do collapse, but 1.4rem simply won. Measured: 37.89px of
     white above a captioned table against 19.55px between two paragraphs. What remains
     above the margin is the first row's own padding, which is inside the table. */
  margin: 0.8rem 0 1.4rem 0;
  border-collapse: separate;
  border-spacing: 0;
  hyphens: none;
}

/* The caption sits INSIDE the table's element box -- see the note on `table` above -- so it
   must not be boxed in with the grid, and it is not: the frame is drawn by the cells now, and
   nothing here draws one. It keeps the cells' padding so it still reads as a band, but a band
   with no border and no tint.

   1.05em, and in the body's own colour. A caption is the table's title, and it is set in the
   italic -- which at the same nominal size reads SMALLER than the prose beside it, and that is
   not an x-height deficit: Source Serif's italic `x' is TALLER than the regular's (500 against
   475 units at 1000upm). It is lighter and narrower. Measured with the real face over one
   string, at the same size, the italic lays down 0.885 of the regular's ink and runs 0.945 of
   its width; the two reach parity at about 1.06. 1.05 sits just under it, which takes the
   shrunken look off without making the caption louder than the thing it announces.
   A step DOWN was tried first and read as a footnote to the table.

   NO COLOUR, which is the second half of the same fault: a muted grey compounds with the slope
   and makes the caption an aside. Neither the PDF nor the EPUB ever had one -- this sheet was
   the odd one out, and the three now agree. Slope alone carries the difference.

   0.7rem left, so the caption's first letter sits over the first column's rather than a
   padding to its left. Stated as the same numbers the cells use, not measured off them. */
caption {
  /* The cells' own padding, so the caption reads as a band of the same kind -- and +1px on the
     left for the first cell's border, which the caption does not have. Measured in the
     browser: without it the caption's first letter sat exactly 1px left of the first
     heading's. The TOP is 0.3rem, not 0.42 -- it matches `th`'s own padding-top, so a
     captioned table and an uncaptioned one start the same distance below the prose:
     measured 24.94px and 25.94px. */
  padding: 0.3rem 0.7rem 0.42rem calc(0.7rem + 1px);
  font-size: 1.05em;
  font-style: italic;
  /* CENTRED, like the figure caption it is the twin of. It was flush left, so the first letter
     sat over the first column's; a figure's has always been centred under the image, and the two
     being one construct in everything but placement is worth more than that. The padding stays
     as it was -- it is what sets the caption off from the cells, and it is symmetrical. */
  text-align: center;
}

/* A FACE CLASS ON A TABLE IS ABOUT THE TABLE, NOT ITS CAPTION, and the caption has to say so
   here. `: A caption {.serif}' puts the class on the <table>, and <caption> is a CHILD of it, so
   the family is inherited: measured in Chromium, a `.serif` table's caption came out in Charter
   and a `.mono` one in the mono at 0.95 of its size, while the PDF -- where \padoxcaptionface
   states \ss\it -- left both in the sans. One document, two answers.

   The class wins on specificity over a bare `caption', so the take-back has to name it: a class
   plus an element is (0,1,2) against `.mono''s (0,1,0). The size goes back with the family --
   `.mono' carries --mono-size, and 1.05em of a reduced size is not the step the caption is for.
   `--font-sans' rather than `inherit': the caption is apparatus and states its face, exactly as
   \padoxcaptionface does, so a `mainfont:'-style change cannot take it somewhere else. */
table.sans > caption,
table.serif > caption,
table.mono > caption,
table.tt > caption {
  font-family: var(--font-sans);
  font-size: 1.05rem;
}

/* An image and its caption are centred, and the caption takes the table caption's slope and
   size -- the same "this is a caption" signal, and the same optical correction for the italic.
   ConTeXt centres the float caption under the image, so the HTML matches it. No colour, for
   the reason the table caption has none. */
/* THE SIDE MARGIN IS ZEROED, and that is the whole of this rule's reason for existing.
   The UA default for <figure> is `margin: 1em 40px` -- 40px on EACH side, inherited from
   HTML4's <blockquote>-ish indent -- and nothing here had ever taken it back. An image
   written `width=100%` then measured 100% of a box 80px narrower than the column: 716 of
   796 on a desktop, and 290 of 370 at a 390px viewport, where 80px is 22% of the measure
   and the picture reads as arbitrarily inset. Measured in Chromium, and the markup was
   never at fault -- pandoc emits style="width:100.0%" and the img obeys it exactly.

   epub.css has said `margin: 1em 0` all along, so this sheet was the one disagreeing.

   0.8rem/1.4rem is the TABLE's spacing, not the paragraph's: a figure is a block with a
   caption under it, and it wants the same air beneath it that a captioned table does, or
   the caption crowds the prose that follows.

   This comment sits ABOVE the rule for the reason the transcript marker's does: the checks
   anchor on the opening brace followed by the declaration, and a closing brace anywhere
   between the two -- in an attribute quoted in prose, say -- ends the run they match. */
figure {
  margin: 0.8rem 0 1.4rem 0;
  text-align: center;
}
figcaption {
  font-size: 1.05em;
  font-style: italic;
}

/* On the CELLS, not on the row group -- and the stripe below for the same reason. A row's
   background is a square rectangle, so behind a corner cell that has rounded its border it
   would show as a square of tint outside the curve. On the cell it is clipped by the cell's
   own radius. */
thead > tr > th,
thead > tr > td {
  background: var(--thead-bg);
}

th,
td {
  padding: 0.42rem 0.7rem;
  border: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}

/* One line where two cells meet, not two: the second cell gives up the side they share.
   Vertically the same, except that the first body row keeps its top border when there is no
   header above it to supply one -- a headerless table is a shape pandoc emits. */
tr > :is(th, td) + :is(th, td) {
  border-left: none;
}

tbody > tr + tr > :is(th, td),
thead + tbody > tr:first-child > :is(th, td) {
  border-top: none;
}

/* The four outer corners. Whichever row group is first carries the top pair, so a table with
   no header still has a rounded head. */
thead > tr:first-child > :is(th, td):first-child,
table:not(:has(thead)) tbody > tr:first-child > :is(th, td):first-child {
  border-top-left-radius: 6px;
}

thead > tr:first-child > :is(th, td):last-child,
table:not(:has(thead)) tbody > tr:first-child > :is(th, td):last-child {
  border-top-right-radius: 6px;
}

tbody > tr:last-child > :is(th, td):first-child {
  border-bottom-left-radius: 6px;
}

tbody > tr:last-child > :is(th, td):last-child {
  border-bottom-right-radius: 6px;
}

/* A code block inside a table cell. Pandoc's own manual puts them there by the hundred, and
   a block styled for a page is far too big for a cell: 18px above and 24.3px below, plus
   13px of its own padding, inside a cell 6.7px tall in the next column.

   The cell already provides the separation a block margin exists to give, so the margins go
   entirely and the padding comes down to what keeps the text off its own border. A cell
   holding one code block then measures close to a cell holding one line of text, which is
   what makes a table of them readable.

   Paragraphs beside them lose their outer margins for the same reason -- a cell's first
   line should start at the top of the cell. */
td > pre,
th > pre,
td > div.sourceCode,
th > div.sourceCode {
  margin: 0;
}

/* Both shapes, and the same padding on each. A fence with no language is a bare <pre>; one
   with a language becomes div.sourceCode > div.code-scroll > pre, where the box -- padding,
   border, radius -- lives on the DIV and the pre is stripped to nothing. Setting `td pre`
   alone therefore reduced the plain block and left the highlighted one at its page size,
   which is what put two differently padded blocks side by side in one table row.

   The inner pre keeps its zero: div.sourceCode pre.sourceCode is (0,2,2) and outranks this
   anyway, so the padding has to be asked of whichever element actually carries the box. */
td pre,
th pre,
td div.sourceCode,
th div.sourceCode {
  padding: 0.3rem 0.45rem;
}

td > p:first-child,
th > p:first-child {
  margin-top: 0;
}

td > p:last-child,
th > p:last-child {
  margin-bottom: 0;
}

/* Consecutive blocks in one cell still need separating from each other. */
td > pre + pre,
td > div.sourceCode + div.sourceCode,
td > p + pre,
td > pre + p {
  margin-top: 0.4rem;
}

th {
  padding-top: 0.3rem;
  padding-bottom: 0.3rem;
  font-weight: 650;
}

tbody > tr:nth-child(even) > th,
tbody > tr:nth-child(even) > td {
  background: var(--table-stripe);
}

/* {.coltab}: a table framed and shaded on request, not by default. Heading-less tables get the
   class from the filter, because the frame and the even-row stripe below both assume a header
   row is there to anchor them. The frame is drawn by the cells, exactly as an ordinary table's
   is, so the outer border is one side at a time below; the corners keep their radius. */

/* A coltab table is a list-like block: its outer spacing is the document's own paragraph
   spacing -- the `p` rule's 0.8rem, not the headed table's 0.8rem/1.4rem -- so a column-
   simulating table reads as a list of paragraphs rather than as a table with a deeper gap
   below. The rows already sit at that distance: the shared 0.42rem cell padding puts
   consecutive rows ~0.8rem apart, the same as two paragraphs. */
table.coltab {
  margin: 0.8rem 0;
}

/* The default even-row stripe does not reach a coltab table: its shading is explicit. */
table.coltab > tbody > tr > th,
table.coltab > tbody > tr > td {
  background: transparent;
}

/* shade=alt: every other row, starting with the first — the phase a header would otherwise
   fix. shade=all: every row. */
table.coltab[data-coltab-shade="alt"] > tbody > tr:nth-child(odd) > th,
table.coltab[data-coltab-shade="alt"] > tbody > tr:nth-child(odd) > td,
table.coltab[data-coltab-shade="all"] > tbody > tr > th,
table.coltab[data-coltab-shade="all"] > tbody > tr > td {
  background: var(--table-stripe);
}

/* border=: none, or the sides named. Default none, so the cells give up their own 1px line. */
table.coltab th,
table.coltab td {
  border: none;
}
table.coltab[data-coltab-border~="t"] > thead > tr:first-child > :is(th, td),
table.coltab[data-coltab-border~="t"] > tbody > tr:first-child > :is(th, td) {
  border-top: 1px solid var(--border);
}
table.coltab[data-coltab-border~="b"] > tbody > tr:last-child > :is(th, td) {
  border-bottom: 1px solid var(--border);
}
table.coltab[data-coltab-border~="l"] > thead > tr > :is(th, td):first-child,
table.coltab[data-coltab-border~="l"] > tbody > tr > :is(th, td):first-child {
  border-left: 1px solid var(--border);
}
table.coltab[data-coltab-border~="r"] > thead > tr > :is(th, td):last-child,
table.coltab[data-coltab-border~="r"] > tbody > tr > :is(th, td):last-child {
  border-right: 1px solid var(--border);
}

code,
samp {
  font-family: var(--font-mono);
  font-feature-settings: var(--mono-features, normal);
  font-variant-ligatures: none;
  font-size: var(--mono-size);
  hyphens: none;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* `[`x`]{.mono}` is a span around a code element, two ancestors each asking for 0.92, and
   0.85 is not a size any of this sheet's other code paths sit at. Cancel the multiplier here
   rather than in `code`, which every other path inherits. */
.mono code,
.tt code {
  font-size: 1em;
}

/* THE REDUCTION APPLIES ONCE, AT THE OUTERMOST MONO CONTEXT -- and the context is any element
   that has already taken it, not just a block. pre and div.cli were listed and span.cli was
   not, so `[[mkdir]{.cc} -fo x]{.cli}' -- a command name inside an INLINE transcript, which is
   how a command in a sentence is written -- came out at 0.92 x 0.92: measured 15.2352px against
   the 16.56px of every other mono thing on the page. A mono class inside another mono class had
   the same fault.

   The container list is therefore everything that carries the reduction, and the target list is
   the four face-forcing classes. epub.css states the same pairs, written out: :is() is above
   its floor. */
:is(pre, div.cli, span.cli, code, .mono, .tt, .cc, .co) :is(.mono, .tt, .cc, .co),
div.cli pre {
  font-size: 1em;
}

/* Runs of spaces INSIDE a code span survive, and they have to. A code span is quoted
   verbatim: the source wrote `a     b`, pandoc preserved it, and HTML's default collapses it
   to `a b` — which is only visible when the spacing was the point, so it reads as the
   document being wrong rather than the sheet. Measured on the guide's own `` `     `{.ws} ``,
   which quotes the fixed-width construct: five spaces in the DOM, one on the page. ConTeXt's
   \type is verbatim, so the PDF was right and this was the format that disagreed.

   pre-wrap and not pre: a long span still wraps rather than pushing the measure open, which
   is what word-break: break-all below is for. Scoped to INLINE code on purpose — inside a
   <pre> the block owns the whitespace, and pre-wrap on the inner <code> would make code
   blocks wrap where they are meant to scroll. `.ws` says the same thing for the edges and
   stays, being a fact about the content rather than a rule about the element. */
p code,
li code,
td code,
th code {
  padding: 0.12rem 0.34rem;
  border: 1px solid var(--code-border-overlay);
  border-radius: 4px;
  background: var(--code-overlay);
  white-space: pre-wrap;
  word-break: break-all;
}

/* ── An inline command line: `git commit`{.cli} or [git commit]{.cli} ────────
   For naming a short command in the middle of a sentence, where a whole transcript would be
   a paragraph break for one line of text.

   The same box as an inline code span, in the transcript's colours: the console border and
   background rather than the code overlay, so a reader recognises it as the same kind of
   thing as the block form without it shouting. Nothing about the prompt applies -- there is
   no gutter and no marker, because there is no list to hang one on.

   BOTH SPELLINGS. `text`{.cli} is a code span and keeps code's own face and escaping, which
   is what a command usually wants; [text]{.cli} is a bracketed span, for the times the
   content needs markup inside it. They are styled together so the two look the same.

   The class is an attribute, so this is Pandoc Markdown only. Under --gfm there is no
   syntax to write it with.

   word-break: break-all, matching an inline code span: a long command breaks mid token
   rather than pushing the measure open, and the box makes it obvious a token continued. */
code.cli,
span.cli {
  /* The margin keeps the BORDER clear of the prose, as the padding keeps the command clear of
     the border; without it the box's edge and a following comma occupy the same place. The key
     caps have carried the same pair since they were built and 0.12em is their number, so the
     two constructs sit in a sentence identically. */
  margin: 0 var(--cli-gap, 0.12em);
  padding: 0.12rem 0.34rem;
  border: 1px solid var(--cli-span-border);
  border-radius: 4px;
  /* Transparent in EVERY mode, unlike the div. An inline box sits in a line of prose, where a
     fill of its own is a second background inside the paragraph's; the border alone says where
     the command starts and stops, which is what the PDF does too. */
  background: transparent;
  font-family: var(--font-mono);
  font-feature-settings: var(--mono-features, normal);
  font-variant-ligatures: none;
  font-size: var(--mono-size);
  hyphens: none;
  word-break: break-all;
}

/* A code span already carries the code box from `p code` and friends, which would sit under
   this one: same padding, same radius, the wrong colours. Reset what this rule does not set,
   so one box is drawn rather than two overlapping ones. */
p code.cli,
li code.cli,
td code.cli,
th code.cli {
  border-color: var(--cli-span-border);
  background: transparent;
}

pre {
  position: relative;
  /* padox passes --preserve-tabs, so a tab in a code block reaches the page as a tab rather
     than as spaces. Four, because that is pandoc's own --tab-stop default: the character in
     the output changes, the width on screen does not. The browser's own default is 8, which
     would have silently doubled every tab-indented block. */
  tab-size: 4;
  margin: 1rem 0 1.35rem;
  padding: var(--pre-pad-y) var(--pre-pad-x);
  max-width: 100%;
  border: 1px solid var(--pre-border);
  border-radius: 8px;
  background: var(--pre-bg);
  color: var(--pre-text);
  hyphens: none;
  /* 1.32 was a compromise: tight enough that box-drawing characters almost joined up,
     which cost every other code block its air. {.ascart} below now owns that problem,
     so this can be set for reading. */
  line-height: 1.45;
}

/* Box-drawing and block-element characters join only while consecutive lines' ink still
   touches, and their ink is taller than the em: measured at 18px, U+2502 runs from 17 above
   the baseline to 5 below, so it is 22px in an 18px box. Lines may therefore sit up to 22px
   apart -- line-height 1.222 -- before a vertical rule breaks.

   That ceiling is a property of the font, and 1.25 now sits a hair ABOVE Iosevka's (1.222):
   the looser leading reads better, at the cost of a hair of gap in a vertical rule, which the
   reader accepts over the over-tight 1.15 that cleared every fallback. Liberation Mono, the
   last face in the stack, inks 21px against a 1.167 ceiling, so a document read in that face
   sees a wider gap; line-height 1 was over-tight, which is why a selection dragged across two
   lines showed the highlights overlapping.
   Override --ascart-line-height for a font that measures differently:
   ink height / font-size is the maximum. One selector covers both code shapes, since it is
   the pre that carries the text either way. */
pre.ascart {
  line-height: var(--ascart-line-height);
}

pre code {
  display: inline-block;
  /* An inline-block sits on the baseline, so its line box reserves descender space beneath
     it. At line-height 1.45 the leading absorbs that; at {.ascart}'s 1 there is none left,
     and the box ends up a pixel or two taller than the text. Top alignment removes it. */
  vertical-align: top;
  min-width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  white-space: pre;
}

div.sourceCode {
  position: relative;
  max-width: 100%;
  margin: 1rem 0 1.35rem;
  padding: var(--pre-pad-y) var(--pre-pad-x);
  /* Pandoc sets overflow to auto on div.sourceCode in its own highlighting block, which
     is right for its markup and wrong for this one: here the scrolling belongs to the inner
     .code-scroll, and this element is the padded box around it. Left as auto it is a second
     scroll container wrapping the first, and a box whose content is a fraction of a pixel
     taller than its own height raises a vertical scrollbar -- which a single-line block in a
     table cell did, overflowing by 2px once the cell padding was reduced.

     The same trap .code-scroll documents, arriving from the other direction: there
     overflow-x: auto computed overflow-y to auto, here pandoc asks for both outright.

     No closing brace appears anywhere in this comment, on purpose. It sits inside a rule,
     and a checker that reads a rule body as "everything up to the next closing brace" stops
     at one written here -- which is how the assertion for this very declaration came to
     fail twice against CSS that was correct. */
  overflow: visible;
  border: 1px solid var(--pre-border);
  border-radius: 8px;
  background: var(--pre-bg);
  color: var(--pre-text);
}

.code-scroll {
  overflow-x: auto;
  /* Explicit, and not redundant: overflow-x: auto on its own computes overflow-y to auto
     as well, and then a fraction of a pixel is enough to raise a vertical scrollbar that
     scrolls by one or two pixels. A code block is always exactly as tall as its content,
     so there is nothing here for hidden to clip. */
  overflow-y: hidden;
}

div.sourceCode pre.sourceCode {
  margin: 0;
  padding: 0;
  overflow: visible;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: inherit;
}

div.sourceCode code.sourceCode {
  display: inline-block;
  vertical-align: top;
  min-width: 100%;
  background: transparent;
  border: 0;
}

/* A {.numberLines} block: pandoc emits the number as an EMPTY <a aria-label="N"> AND ships its
   own gutter machinery in an inline style sheet -- the pre takes a 3em left margin, every line
   is shifted left 4em, and the number is a 4em-wide relatively-positioned box. That whole dance
   is undone (same selectors, this sheet loads after pandoc's inline one) and replaced with a
   simple right-aligned inline-block gutter on the number itself, so multi-digit lines keep their
   right edge under each other. The number is rendered from the attribute -- pandoc's counter is
   left unused -- in the code's own face and the comment colour. */
pre.numberSource {
  margin-left: 0;
  padding-left: 0;
}
pre.numberSource code > span {
  position: static;
  left: auto;
}
pre.numberSource code > span > a:first-child::before {
  content: attr(aria-label);
  display: inline-block;
  width: auto;
  min-width: 2ch;
  margin-right: 1ch;
  padding: 0;
  text-align: right;
  position: static;
  left: auto;
  vertical-align: baseline;
  border: none;
  text-decoration: none;
  -webkit-user-select: none;
  user-select: none;
}
pre.numberSource code > span > a:first-child {
  font-family: var(--font-mono);
  font-variant-ligatures: none;
  color: var(--syn-co);
  text-decoration: none;
}

/* A `{.console}` CODE BLOCK IS THE SAME FAMILY and follows the same token, so the two do not
   disagree where a document uses both. */
div.sourceCode:has(pre.console) {
  background: var(--cli-bg);
  border-color: var(--cli-border);
  border-left-width: 4px;
}

/* ── Mathematics: MathML from $x$ and $$x$$ ──────────────────────────────────
   pandoc --mathml turns TeX math into MathML, which browsers lay out themselves. Nothing is
   downloaded and no script runs, so the only thing this stylesheet decides is the face and
   the spacing.

   Chromium and Firefox both have their own default math font, and both are reasonable, but
   they are not the same font -- so a document read on two machines is set two ways unless
   the page says which it wants. */
math {
  font-family: var(--font-math);
  /* Math sits with the prose it is quoted in. A serif face at the body's own size reads
     slightly larger than a sans one, and the notation looks overbearing beside its sentence
     at 1em, so it comes down a little. */
  font-size: 1.05em;
  /* An inline formula is taller than a line of text -- a fraction or a superscript is enough
     to make it so -- and normal line-height would push the surrounding lines apart around
     it. The paragraph keeps its rhythm and the formula is allowed to overlap slightly, which
     is what every typeset page does with inline notation. */
  line-height: normal;
}

/* Display math. pandoc puts it in a paragraph of its own, so the block-level styling goes on
   the math element rather than on the p, which is left alone.

   It scrolls rather than wrapping, for the same reason a transcript does: there is no place
   to break `x = (-b ± √(b²-4ac)) / 2a` that leaves it readable, and a formula running off
   the side of a phone silently is worse than one the reader can push.

   Centred with auto margins on a fit-content box, NOT with text-align. A <math> box lays its
   children out by MathML's own rules, and text-align has no purchase on them: it computes to
   `center`, inherits down, and moves nothing. Shrinking the box to its contents and letting
   the margins take up the slack is what actually centres a formula.

   max-width keeps fit-content from exceeding the measure, so a formula too wide to fit
   becomes a full-width box with its contents overflowing -- which is what gives the scroll
   something to scroll. */
math[display="block"] {
  display: block;
  width: fit-content;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  margin: 1.15rem auto;
  font-size: 1.15em;
}

/* The paragraph wrapping display math contributes a second set of margins on top of the
   formula's own, which is where the extra air around a lone equation comes from. */
p:has(> math[display="block"]) {
  margin: 0;
}

/* ── Parenthesised list markers: (1) and 1) ──────────────────────────────────
   Pandoc reads `(1)` as an ordered list and remembers the punctuation, but <ol> has no way
   to express it, so all three spellings emit the same markup and render as `1.`. A document
   that said `(1)` stopped saying it on the page — which matters when the marker is
   referenced from somewhere else, `#(1)` in a transcript pointing at the note below it,
   because the reference then names something the reader cannot see.

   parenlists.lua puts the class on a wrapping Div, an OrderedList having no Attr of its own.

   counter(list-item), not @counter-style. @counter-style is the tidier spelling and was
   tried first, but it is the newer feature — Safari only got it in 17 — and where it is
   missing the list silently falls back to decimal, which is the fault it was added to fix.
   counter(list-item) is the implicit counter every ordered list already maintains, so it
   still honours `start` without the ol's attribute having to be read, and it has been
   everywhere for years.

   It also puts the spacing under this sheet's control, which @counter-style does not:
   `suffix: ") "` buys exactly one space and nothing else. --paren-margin is the marker's
   own indent, --paren-gutter the column the text starts in, and the gap between them is
   what is left over. */
.paren-list > ol,
.paren-list-one > ol {
  /* 0.2em, where the marker's own left edge used to be stated at 0.55em. It is now the
     COLUMN's left edge, and the marker ranges right inside it, so the two are not the same
     number: 0.2em plus the column less `(1)''s own 1.173em puts that marker back within half a
     pixel of where it stood, which is the point -- the gap after it grew, the indent did not. */
  --paren-margin: 0.2em;
  /* THE MARKER GETS A COLUMN, AND THE GAP IS WHAT IS LEFT AFTER IT -- which is the other way
     round from how this was written. --paren-gutter was stated at 2.1em and the gap was the
     residual: `(1)' measures 1.173em and starts at 0.55em, so it ended 0.05em short of the
     text and the two ran together, while every other marker in this sheet stands a stated
     0.6em (--marker-gap) off its item. Measured 6.8px against the ordered list's 10.8px in the
     same document.

     Stating the column also makes `(10)' behave: at width: auto the marker simply grew to the
     right and ate the gap it had left. 1.5em holds two digits, and the marker ranges right in
     it like every other marker here, so `(9)' and `(10)' close on one edge. */
  --paren-col: 1.5em;
  --paren-gutter: calc(var(--paren-margin) + var(--paren-col) + var(--marker-gap));
  list-style: none;
  padding-left: 0;
}

.paren-list > ol > li,
.paren-list-one > ol > li {
  position: relative;
  padding-left: var(--paren-gutter);
}

/* Absolute, so a wrapped line hangs under the item's own first character rather than under
   the marker — the same reason the transcript's prompt is placed this way. */
.paren-list > ol > li::before,
.paren-list-one > ol > li::before {
  position: absolute;
  left: var(--paren-margin);
  width: var(--paren-col);
  text-align: right;
}

.paren-list > ol > li::before { content: "(" counter(list-item) ")"; }
.paren-list-one > ol > li::before { content: counter(list-item) ")"; }

/* A (1) list directly under a code block or a transcript is that block's legend: the markers
   are what the block's own text points at, `#(1)` in a comment naming the note below. It
   belongs to the block, so it sits closer to it than to whatever follows — the gap is the
   only thing that says which of the two it goes with.

   BOTH SIDES HAVE TO MOVE. Adjacent siblings collapse their margins, so the gap is the
   larger of the two and not the sum: measured at 24.3px here, which is the block's 1.35rem
   bottom margin, with the list's own 1em (18px) losing. Setting only the list's margin-top
   would have changed nothing at all, which is the trap this comment exists to record.

   :has() to reach back to the block, since CSS has no "followed by" combinator. Unnested, so
   it is the form that parses -- :has(+ x:has(> y)) is a syntax error, which is why the
   output blocks needed a filter instead. Nothing here needs that: the list is a sibling and
   its class is on the element itself. */
:is(pre, div.sourceCode, div.cli):has(+ :is(.paren-list, .paren-list-one)) {
  margin-bottom: var(--legend-gap, 0.55rem);
}

:is(pre, div.sourceCode, div.cli) + :is(.paren-list, .paren-list-one) > ol {
  margin-top: var(--legend-gap, 0.55rem);
}

/* A .codenotes DIRECTLY UNDER ONE OF THOSE BLOCKS IS THE SAME THING WEARING A RULE, and takes
   the same gap. It is the construct written for exactly this -- notes on the lines of the block
   above -- so it had the least business standing a full 1.35rem off it: measured 24.3px above
   against 24.3px below, which said nothing about which of its two neighbours it belonged to.

   Both sides again, for the same reason: the collapse takes the larger, so the block's own
   bottom margin has to come down with the notes' top margin. The PDF makes the same move from
   the same number -- codenotes.lua flags the adjacency and \padoxcodenotestight closes it to
   6.55pt, which is 0.55em of an 11.955pt body. */
:is(pre, div.sourceCode, div.cli):has(+ :is(section.codenotes, div.codenotes)) {
  margin-bottom: var(--legend-gap, 0.55rem);
}

:is(pre, div.sourceCode, div.cli) + :is(section.codenotes, div.codenotes) {
  margin-top: var(--legend-gap, 0.55rem);
}

/* ── Output block: ```{.output} ──────────────────────────────────────────────
   A block holding what a program printed rather than what it was, so it says so.

   Generated content on purpose, unlike a code label: this is a fixed strip derived
   from the class, not anything the author wrote, and being unselectable is a feature
   here — copying the block should yield the output and not the word OUTPUT.

   Set like the ABSTRACT rule rather than like a code label: it names a kind of block,
   it does not title one, so it is smaller and quieter. Hence --font-sans, which is the
   only way to reach the body face from inside a <pre>.

   The negative margin cancels the padding of the box it sits in, so the strip runs the
   full width and sits flush against the top border. That is why the sourceCode variant
   is a separate selector — there the padding belongs to the wrapper, not to the pre.

   :has(pre.output), NOT :has(> pre.output) — here and everywhere below. page.js moves a
   block's children into a div.code-scroll to give the scrollbar something to belong to, so
   the pre stops being a child of div.sourceCode the moment the script runs. The child
   combinator therefore matches in the file and not on the page: a ```{.output .json} block
   was styled correctly in the HTML and lost its strip on load, which no amount of reading
   the markup would show. */
pre.output:not(.sourceCode),
div.sourceCode:has(pre.output) {
  background: var(--output-bg);
}

pre.output:not(.sourceCode)::before,
div.sourceCode:has(pre.output)::before {
  content: "Output";
  display: block;
  /* The block's own padding, negated, so the strip reaches the padding edge and stops
     there — inside the border on all three sides rather than across it. */
  margin: calc(-1 * var(--pre-pad-y)) calc(-1 * var(--pre-pad-x)) var(--pre-pad-y);
  /* Height stated rather than accumulated, so the copy button has something fixed to be
     centred on. The text is centred inside it by its own line box: one line, so line-height
     is the whole measure, less the border the height includes. */
  box-sizing: border-box;
  height: var(--output-strip-h);
  padding: 0 var(--pre-pad-x);
  /* No rule under the strip, and none above it either -- see .joined-above. The band is the
     divider between the code and what it printed; a line as well says the same thing twice,
     and at the join it said it three times: the block's bottom border, then the strip, then
     the strip's own underline. */
  border-radius: 7px 7px 0 0;
  background: var(--output-label-bg);
  color: var(--output-label-text);
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  line-height: calc(var(--output-strip-h) - 1px);
  text-transform: uppercase;
}

/* ── Joining an output block to what produced it ─────────────────────────────
   An output block directly under a code block or a transcript belongs to it, and reads that
   way only if the two are drawn as one unit: the gap closed, the corners at the meeting
   square, and one border between them instead of two.

   outputjoin.lua supplies both classes, because CSS cannot ask the question -- see the note
   at the top of that file. The strip stays: it is what says which half is output.

   The block above keeps its own colours. Only the shape changes.

   Its bottom border goes with the gap. The OUTPUT strip lands directly beneath, and the
   strip is the divider: a rule as well puts a line immediately above a band that is doing
   the same job, which reads as a boundary drawn twice. */
pre.joined-above:not(.sourceCode),
div.sourceCode:has(pre.joined-above),
div.cli.joined-above {
  margin-bottom: 0;
  border-bottom: none;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

/* The output block below, squared to meet it. No border-top here either, so what separates
   the two halves is the strip alone and the side borders run straight through. */
pre.output.joined-below:not(.sourceCode),
div.sourceCode:has(pre.joined-below) {
  margin-top: 0;
  border-top: none;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

/* Both halves take the stronger border, which is what makes the unit read as one thing.

   --pre-border is #cdd5de against a #f0f4f8 block on #f6f7f9 paper: enough to bound a single
   block, and not enough to hold a taller one together across a strip that interrupts it. The
   code label does exactly this when it joins a block, for the same reason.

   In dark --pre-border is transparent, so a lone code block is bounded by its background
   alone. The join is where that stops working -- two backgrounds and a strip between them,
   with nothing saying they are one box -- so the frame appears there and only there. */
pre.joined-above:not(.sourceCode),
div.sourceCode:has(pre.joined-above),
pre.output.joined-below:not(.sourceCode),
div.sourceCode:has(pre.joined-below) {
  border-color: var(--border-strong);
}

/* The OUTPUT strip is flush against the top of that box and carries the rounding with it, so
   it has to be squared too or the corners stay visible inside a square frame. */
pre.output.joined-below:not(.sourceCode)::before,
div.sourceCode:has(pre.joined-below)::before {
  border-radius: 0;
}

/* Under a transcript the frame is the console one, and the side borders would change colour
   at the join -- half a left edge in each, the same fault the code label documents. The
   output adopts the console border rather than the transcript adopting the code one: the
   rule belongs to the transcript, which is the larger of the two. Later than the rule above,
   so it wins for this case and the code-block case keeps --border-strong.

   Backgrounds still differ, which is wanted: one frame, two kinds of content. */
div.cli.joined-above + pre.output.joined-below,
div.cli.joined-above + div.sourceCode:has(pre.joined-below) {
  border-color: var(--cli-border);
}

/* {.ws}: this content's whitespace is significant. The preprocessor has already turned the
   spaces at a code span's edges into non-breaking ones, since the reader would have trimmed
   them; this covers the runs inside, which survive the reader but would otherwise collapse
   on the way to the screen. Wrap rather than pre, so a long line still breaks. */
.ws {
  white-space: pre-wrap;
}

/* ── Syntax placeholders: [expr]{.stx} ───────────────────────────────────────
   Stands for "some expression goes here". The guillemets are generated, which keeps the
   source readable and keeps them out of anything copied from the page — what a reader
   copies is the name, not the notation around it.

   Face, slope and weight are all stated rather than inherited. The construct turns up
   inside headings, inside bold runs, inside table cells and beside code, and it has to look
   the same in every one of them: body font, italic, never bold. */
.stx {
  font-family: var(--font-sans);
  font-style: italic;
  font-weight: 400;
  /* Two placeholders in a row is an ordinary thing to write — a rule with two of them — and
     with nothing here a closing guillemet against an opening one reads as a single
     four-stroke mark rather than as two names. Small enough not to loosen a placeholder from
     the word it follows. */
  margin: 0 var(--stx-gap, 0.08em);
}

/* Inside a command line, a placeholder is still something typed, so it keeps the mono face
   and changes only its slope -- ‹filename› set in the same family as the command around it,
   rather than a sans word wedged into a monospace run.

   This is the one exception to .stx fixing its own face, and it is deliberate: the reason
   that face is fixed is that .stx turns up inside headings, bold runs and table cells, where
   the surrounding face carries no meaning. In a .cli it carries all of it. Both spellings
   are covered -- the inline span and the transcript div -- so a placeholder looks the same
   whichever one it is written in, and ConTeXt agrees, where \padoxstylestx inherits the
   family from \padoxstylecli and only changes the slope. */
.cli .stx {
  font-family: inherit;
}

code.stx {
  font-family: var(--font-mono);
  font-feature-settings: var(--mono-features, normal);
  font-variant-ligatures: none;
}

/* Upright, as they were when the guillemets were typed outside the emphasis by hand. */
.stx::before {
  content: "\2039";
  font-style: normal;
}

.stx::after {
  content: "\203A";
  font-style: normal;
}

/* ── Labelled arrows: [v]{.tra}, [x]{.tla} ───────────────────────────────────
   "2 + 3 * 5 ──t─▶ int": the label rides in the arrow's shaft. Written as a span so the
   label can be a word or several, and the arrow generated either side of it.

   The arrow is *drawn*, not typed. A glyph arrow needs a font whose box drawing and
   arrowhead share a baseline and an advance; the mono stack has one, a phone without those
   fonts installed does not, and there the head arrived detached from the shaft and sitting
   off the line. A clipped box depends on no font at all, scales with the text because it is
   sized in em, and puts the shaft and head in one shape so they cannot come apart.

   The tail is shorter than the head side on purpose: the head carries the weight, and
   matching them makes the label look pushed to the right. All three are overridable. */

.tra,
.tla {
  font-family: var(--font-sans);
  font-weight: 400;
}

/* Typed, for an engine that cannot clip a shape: better a glyph arrow than the bare
   rectangle an unclipped box would leave. */
.tra::before { content: "\2500\2500"; }
.tra::after  { content: "\2500\25B6"; }
.tla::before { content: "\25C0\2500"; }
.tla::after  { content: "\2500\2500"; }

.tra::before,
.tra::after,
.tla::before,
.tla::after {
  font-family: var(--font-mono);
  font-size: var(--arrow-size, 0.9em);
  font-style: normal;
  font-weight: 400;
  color: var(--muted);
}

/* A thin space either side of the label, so the shaft approaches it rather than butting
   into it. On the pseudo-elements rather than as padding on the span: padding would sit
   outside them, at the far ends of the arrow, which is the one place it is not wanted. */
.tra::before,
.tla::before {
  margin-right: var(--arrow-gap, 0.15em);
}

.tra::after,
.tla::after {
  margin-left: var(--arrow-gap, 0.15em);
}

@supports (clip-path: polygon(0 0, 1px 0, 0 1px)) {
  .tra::before,
  .tra::after,
  .tla::before,
  .tla::after {
    content: "";
    display: inline-block;
    height: var(--arrow-height, 0.66em);
    background: var(--muted);
    /* Sits the shaft on the middle of lowercase, where a dash sits. */
    vertical-align: -0.04em;
  }

  /* Plain shaft. The clip leaves a band 14% of the height: a hairline at any text size. */
  .tra::before,
  .tla::after {
    width: var(--arrow-tail, 0.65em);
    clip-path: polygon(0 43%, 100% 43%, 100% 57%, 0 57%);
  }

  /* The head fills the box vertically, which is what matches the weight of the glyph
     triangle it replaces; a smaller one reads as an afterthought beside the text.

     Its width is a length, not a percentage of the box: that way --arrow-head lengthens
     the shaft on this side and leaves the point the size it is. A percentage would scale
     the whole arrowhead every time the shaft grew. */
  .tra::after,
  .tla::before {
    width: var(--arrow-head, 1.05em);
  }

  /* THE HEAD IS TWO DIAGONALS, NOT A FILLED TRIANGLE, and the shaft runs all the way to the
     point to meet them. A solid head is what a font's arrow draws at small sizes and it reads
     as a wedge with a line behind it; two strokes of the shaft's own weight read as one arrow.

     A STROKED MASK, not a clip path. A shaft plus two barbs is not one outline -- the same
     reason .dra and .dla are masks -- and as a stroke the weight is a property of the tool
     rather than something to re-derive whenever a proportion moves. The mask reveals the
     element's own background, so the colour still comes from CSS and follows the theme.

     The viewBox is 105x66, the element's own 1.05em x 0.66em at 100 units to the em, so
     `contain' scales it by exactly 0.01em per unit and nothing is letterboxed. The stroke is
     9.24 = 14% of the height, which is the band the clip path drew, so the arrow keeps the
     weight it had; only the head opens. The point sits half a stroke in from the right edge so
     the round join's ink lands exactly on it.

     THE BARB OPENS AT 45 DEGREES, the double arrow's chevron's angle, and is stated as a
     half-height -- 25.08 units, 0.38 of the box -- with its horizontal run following from the
     angle. It was --arrow-point's 0.5em, a run with the angle left as a residual, and that came
     out at 30.3 degrees: two arrows in one sentence pointed at different sharpnesses. One
     number now, and the head is shorter for it. */
  .tra::after,
  .tla::before {
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
  }

  .tra::after {
    --arrow-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 105 66'%3E%3Cg fill='none' stroke='%23fff' stroke-width='9.24' stroke-linejoin='round'%3E%3Cpath d='M0 33H100.38'/%3E%3Cpath d='M75.3 7.92L100.38 33L75.3 58.08'/%3E%3C/g%3E%3C/svg%3E");
    -webkit-mask-image: var(--arrow-mask);
    mask-image: var(--arrow-mask);
  }

  .tla::before {
    --arrow-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 105 66'%3E%3Cg fill='none' stroke='%23fff' stroke-width='9.24' stroke-linejoin='round'%3E%3Cpath d='M105 33H4.62'/%3E%3Cpath d='M29.7 7.92L4.62 33L29.7 58.08'/%3E%3C/g%3E%3C/svg%3E");
    -webkit-mask-image: var(--arrow-mask);
    mask-image: var(--arrow-mask);
  }
}

/* ── Named code and commands: [text]{.cc} ────────────────────────────────────
   A command, a file name, an identifier — named in running prose rather than quoted as
   code. A code span would do it, but a monospace face at the body size rarely shares the
   body's x-height, so the term sits visibly small or large in the line, and the grey box
   the modern convention puts behind it is a second interruption.

   Bold sans instead, stated rather than inherited so a serif document gets the same thing.

   No colour: it inherits, so a named term is the text colour in prose and the link colour
   inside a link. A teal was tried and dropped — at 4.99:1 against paper it sat where the
   link blue does at 5.19:1, and a term that is not a link should not look like one. Which
   leaves bold sans doing the distinguishing on its own, and in a document whose body font
   is itself sans that is what <strong> already looks like. Worth knowing rather than
   worth fixing here.

   The tracking is the point of the construct. Underscores fill their whole advance, so
   __init__ renders as one long rule at normal spacing and a reader cannot count them.
   0.04em separates them cleanly — measured, and a little under a hair space, which is
   what U+200A is worth. letter-spacing also adds a gap after the *last* character, which
   would push a following comma away, so the same amount comes back off the right margin.

   Never hyphenated. The body sets `hyphens: auto`, and a named term is a command or an
   identifier — often one the reader has to type — so an invented hyphen is a character on
   the page that is not in the name. reconfigureinterpreterbackend came out as
   reconfigureinterpreter- / backend, which reads as a hyphen someone must decide about.

   Wrapping itself is left alone. A term may contain spaces — [print( X )]{.cc} — and those
   are ordinary break opportunities that should stay ordinary; nothing here is nowrap. That
   was tried and reverted: it also welds the spaces, so a multi-word term can only overflow
   the column, and the surrounding prose gains nothing from it either way, since white-space
   on an inline element governs only its own content.

   One thing this does not reach: a term that already contains a hyphen may still break at
   it, because that is ordinary line-breaking rather than hyphenation and no CSS property
   separates the two. `word-break: keep-all` was measured and does not help — at 240px the
   hyphenated term still split. Closing it would mean rewriting the hyphens as U+2011, which
   is the wrong trade for this class: the term is meant to be copied and typed, and a
   non-breaking hyphen pastes as a character that is not a hyphen. */

/* SANS in the browser, and it must stay sans. A mono .cc depends on the reader having the
   mono the sheet asks for; --font-mono is a stack, and on a machine without Iosevka a named
   term drops to whatever monospace is installed and stops matching the code around it. The
   PDF is the opposite case -- there the mono face is known, embedded and chosen -- which is
   why .cc IS mono in ConTeXt and .co exists to say so here. */


/* [text]{.co} -- the same thing as .cc, set in code's own face. For a document that knows
   its readers have the mono, or one where a term is quoted close enough to real code that
   the two should match. No tracking: a monospaced underscore already occupies its own
   advance, so a dunder is two visible marks without help. Its own colour, --co-color.

   In ConTeXt the two are the same macro deliberately: there the mono is embedded, so .cc is
   already set in it and there is nothing for .co to change. */
/* [text]{.cc} AND [text]{.co} ARE ONE THING, which is what ConTeXt has always said --
   \let\padoxstyleco\padoxstylecc. .cc was a bold SANS here, tracked, colourless, because the
   mono could not be relied on in a browser; PadoxMono now ships with the document, so the
   reason is gone and the two spellings can mean what they mean everywhere else. The tracking
   goes with the face: a monospaced underscore fills its own advance, so a dunder shows without
   help, and with no letter-spacing there is no trailing gap to take back. */
.cc,
.co {
  font-family: var(--font-mono);
  /* code's own size, for the reason code has one: a mono face runs larger than the prose
     around it at the same nominal size. Without this .cc read LARGER than the sentence it
     names a term in -- the correction inverted. */
  font-size: var(--mono-size);
  font-weight: 700;
  color: var(--co-color);
  font-variant-ligatures: none;
  letter-spacing: var(--co-track, 0);
  margin-right: calc(-1 * var(--co-track, 0));
  hyphens: none;
  font-variant-numeric: slashed-zero;
}

/* [HTML]{.sc} -- an acronym set at 90% so a run of capitals in a line of lowercase prose
   stops shouting. Only the size: no small-caps synthesis, no tracking, no colour, because
   the content is already the letters someone wrote and the one thing wrong with them is how
   loud they are. em, so it follows whatever it sits inside. */
.sc {
  font-size: var(--sc-size, 0.9em);
}

/* [text]{.sans}, [text]{.serif} and [text]{.mono}/[text]{.tt} say what something is SET IN,
   not what it is, so they compose with whatever else the element carries: `[PATH]{.sc .sans}`
   is an acronym that is also sans. Available on a div too, for a whole passage.

   Both FORCE the face rather than assuming a default. The body here is a sans and ConTeXt's is
   a serif, so in each format one of the two looks like it is doing nothing — until a document
   sets its own body face and the implicit one is the one that breaks. Stated in both formats
   for that reason.

   NOT called .ss and .rm, which were the first names. Skylighting's token classes are two
   letters too, and `ss` is SpecialString: an unscoped `.ss` would have reached every one of
   them inside every code block. See the reset below for what that collision already costs. */
.sans {
  font-family: var(--font-sans);
}

/* [text]{.lref} -- a line reference label: the bare number 1 pointing at a legend below. The
   parentheses and the arrow are generated, so the mark is (1) by construction, the same marker
   the legend's own list draws, and copying a command line yields the number and not the mark. */
.lref {
  float: right;
  margin-left: 0.75em;
  font-family: var(--font-sans);
  color: var(--mark);
}
/* THE MARK IS A DRAWN TRIANGLE, NOT A BORROWED ARROW. It was `\2190', a real left arrow, and
   that is the character the whole `.rar' family stopped using: a glyph is only as good as the
   reader's font, and this one sits beside a number in the sans at a size where a missing or
   badly-fitted arrow is obvious. Drawn, it is the same shape in every font and every mode.

   A SINGLE CONIC GRADIENT, and that is why it can share a box with the paren. Four things want
   a box here -- triangle, `(', number, `)' -- and an inline element has three: its own content
   and two pseudo-elements. `clip-path', which is what the .rar family uses, clips the WHOLE box
   and would take the paren with it. A background paints only where it is told, so the triangle
   lives in this box's padding and the `(' in its content.

   `from 60deg at 0 50%' is a wedge hung on the box's left edge, opening 60 degrees about the
   horizontal -- so the box's own right edge cuts it into an isoceles triangle 0.40em wide and
   0.40*2*tan30 = 0.462em tall, which is what `background-size' states. One image rather than two
   half-triangles, because two would have to be stacked by percentage positions computed against
   `container - image' and would drift with the line height. `currentColor', so it follows
   `--mark' and the dark page without naming either. */
.lref::before {
  content: "(";
  padding-left: 0.62em;
  background-image: conic-gradient(from 60deg at 0 50%, currentColor 0 60deg, transparent 0);
  background-size: 0.40em 0.46em;
  /* NUDGED DOWN OFF THE LINE BOX'S OWN CENTRE. `center' centres the image in the ::before's
     box, and that box is the LINE box -- its middle is the font's ascent/descent midpoint,
     which sits above the middle of the characters actually drawn beside it. Measured at 8x on
     a code line: the triangle's ink centred on row 107 against the digit's 98.5 and the
     parens' 109.5 -- arithmetically between the two and visibly high, because a solid mark
     reads from its mass and the parens' descending tails do not carry any. 0.06em puts it on
     the pair. The offset is a length rather than a percentage, so it does not move with the
     line height. */
  background-position: left calc(50% + 0.06em);
  background-repeat: no-repeat;
}
.lref::after {
  content: ")";
}

.serif {
  font-family: var(--font-serif);
}

/* TeX logo names. Never breaks: a logo is one word. The filter supplies <sub> and <sup>
   for the lowered E and raised A. */
.ltx {
  white-space: nowrap;
}

.ltx sub,
.ltx sup {
  font-size: 0.90em;
  text-transform: uppercase;
}

/* THE KERNS ARE PART OF THE LOGO, not decoration. TeX's E is tucked under the T and LaTeX's A
   under the L: without them the letters sit at their ordinary sidebearings and the name reads
   as three characters that happen to be at three heights. The PDF gets this for free --
   cont-log.mkxl kerns them -- and this sheet was setting them flat. */
.ltx sub {
  margin-left: -0.08em;
}

/* SMALLER THAN THE E, AND FURTHER LEFT. The A is raised into the L's own space, so at the E's
   0.90em it collided with the T that follows and stood too tall beside the cap it sits on;
   0.78em is the step that lets it read as a raised small cap rather than as a full letter
   somebody lifted. The right margin closes it up to the T. */
.ltx sup {
  vertical-align: super;
  position: relative;
  top: 0.18em;
  font-size: 0.78em;
  margin-left: -0.07em;
  margin-right: -0.05em;
}

/* The reversed E of XeTeX, and only the first of its two. `inline-block' because a transform
   does not apply to a non-replaced inline box -- without it the rule parses, matches, and does
   nothing. logos.lua marks the part; the sheet does the flip, so the AST still holds a plain
   `e' and the name copies out of the page as XeTeX. */
.ltx .rev {
  display: inline-block;
  transform: scaleX(-1);
  /* AND IT MOVES BACK RIGHT, because the tuck above is for an upright E. `.ltx sub' pulls the
     E left by 0.08em so it sits under the T that follows it -- and this one has no T after it,
     it has an X before it, and mirroring puts the letter's open side against that X. At the
     tucked position the two collided. 0.10em is the tuck given back with a little over, which
     lands it just right of where an unkerned sub would sit. The right margin is its negative,
     so the shift moves the E alone and the T after it does not follow. */
  margin-left: 0.10em;
  margin-right: -0.10em;
}

/* THE SIZE COMES WITH THE FACE, for the same reason the ligature setting below does: a class
   whose whole job is to force the code face has to take the code face's size too, or the two
   spellings of one idea come out at two sizes in the same line -- measured 18px against code's
   16.56px, the mono then reading LARGER than the prose around it, which is what code's own
   reduction exists to correct. It is code's value and must track it.

   font-size sits directly under font-family, and this comment sits above the rule rather than
   inside it, because the checks anchor on the two declarations being adjacent: the ligature
   note below names a class in braces, and a brace between the selector and the declaration
   ends the run a check matches with. */
.mono,
.tt {
  font-family: var(--font-mono);
  font-size: var(--mono-size);
  font-feature-settings: var(--mono-features, normal);
  /* Ligatures off, as on every other rule that names this face. Iosevka carries calt and
     dlig in its default language system, so `->` is drawn as an arrow — and a class whose
     whole job is to force the code face must show what code shows. ConTeXt's own mono is
     loaded with liga=no, so without this the two formats disagree on [->]{.mono}. */
  font-variant-ligatures: none;
}

/* ── Drawn symbols: [ ]{.rar} and friends ────────────────────────────────────
   → ← ⇒ ⇐ ≡ ‖ as empty spans whose shape is DRAWN BY PADOX rather than taken from whatever
   the reader has installed: the symbol *is* the element.

       A [ ]{.dra} B      implies          A [ ]{.rar} B      maps to
       A [ ]{.dla} B      implied by       A [ ]{.lar} B      mapped from
       A [ ]{.eqv} B      identical to     A [ ]{.alt} B      alternatively

   THEY ARE GLYPHS IN PadoxIcons NOW, not masks and gradients. Same geometry to the unit —
   padoxicons/draw-notation.py in the ctxmin repository derives every one of them from the
   numbers written below, and the PDF's MetaPost figures state the same ones — but one
   drawing instead of three descriptions of one, and the third of those three was epub.css,
   which is CSS 2.1 and could draw nothing at all. The notation reaches an EPUB drawn for the
   first time because of this.

   THE FALLBACK IS THE CHARACTER EACH GLYPH REPLACES, and that is what the codepoints are
   for: an icon here is drawn AT U+2192 rather than at a private-use slot, so a reader who
   never received the face falls back PER GLYPH to their own → and gets the page this sheet
   used to produce. At a private codepoint the same reader gets a tofu box.

   --muted, the same as the labelled arrow. currentColor was the first choice, on the
   argument that these are operators in the sentence rather than punctuation around a label
   -- but a solid shape carries far more ink than a letterform of the same colour, so at the
   text's own colour they out-shouted the words either side and stood out even against the
   arrow. --sym-color sets it, should a document want them at full strength.

   All six names are lower case, deliberately. The double arrows were .Rar/.Lar first, which
   works — padox emits a doctype and class matching is case-sensitive in standards mode —
   but only there: in quirks mode class matching is case-*in*sensitive and .Rar/.rar collapse
   into one. A name that depends on that distinction is a trap for anyone who pastes a
   snippet into a bare HTML file. */

.rar,
.lar,
.dra,
.dla,
.eqv,
.alt {
  /* The face FIRST and the page's own after it. A family that matches no face is skipped
     per glyph, so this one line is both "draw it properly" and "draw it at all". */
  font-family: "PadoxIcons", var(--font-sans);
  font-style: normal;
  font-weight: 400;
  color: var(--sym-color, var(--muted));

  /* INLINE-BLOCK WITH A STATED WIDTH, because the span an author writes is `[ ]{.rar}' and
     that space is inside the element: at a natural width it would be drawn after the glyph
     and add a word space to the notation. The width is the glyph's own advance — 1.05em of
     arrow is 1050 units in padoxicons/icons.toml — so the box fits the ink exactly and the
     space overflows into nothing. Turning one of these tokens resizes the BOX; the ink
     follows font-size, so a document scaling the notation moves both.

     line-height: 0 keeps the icon out of the line box. The glyph carries its own vertical
     position — the drop each of these used to state as a vertical-align is baked into the
     artwork — so there is nothing here to align. */
  display: inline-block;
  line-height: 0;
  vertical-align: baseline;
}

.rar::before { content: "\2192"; }
.lar::before { content: "\2190"; }
.dra::before { content: "\21D2"; }
.dla::before { content: "\21D0"; }
.eqv::before { content: "\2261"; }
.alt::before { content: "\2016"; }

/* The widths, which are the advances padoxicons/icons.toml states and the geometry
   docs/decisions.md records. THE STROKE IS ONE NUMBER FOR THE WHOLE NOTATION —
   --sb-stroke, 0.075em, the weight of the bracket stems, the .eqv bars and the .alt pair —
   and it lives in the artwork now rather than in a mask here. */
.rar,
.lar {
  width: var(--sym-arrow, 1.05em);
}

.dra,
.dla {
  width: var(--sym-double, 1.25em);
}

.eqv {
  width: var(--sym-eqv, 0.85em);
}

/* A space either side, always. The bars mean "or" between two alternatives, and without it
   every use needs the gap written by hand. The width is the BRACKET's own arithmetic —
   two stems of --sb-stroke with --sb-alt-gap between them — because that is nearly always
   what it stands between, and stating it this way rather than as a similar-looking number
   is what keeps the notation from drifting apart. */
.alt {
  width: calc(2 * var(--sb-stroke, 0.075em) + var(--sb-alt-gap, 0.15em));
  margin: 0 var(--sym-alt-gap, 0.25em);
}

/* A .ws span holding nothing but spacing -- `     `{.ws}, how a document asks for a run of
   fixed-width gap. It stays a code span, which is the only thing that makes the width
   monospace, but it must not wear the tinted box inline code has in prose: five spaces in
   one is a grey chip sitting in the middle of a sentence. The preprocessor adds the class,
   since CSS cannot ask what a span contains. */
code.ws-pad {
  padding: 0;
  border: 0;
  background: transparent;
}

/* ── Syntax notation: [x]{.opt}, {.lsb} {.rsb} {.zom} {.oom} ─────────────────
   The pieces a grammar is written with, drawn rather than taken from the reader's fonts,
   for the same reason the arrows are: a font's own [ ] are the brackets of prose and read
   as part of the text, where these have to read as notation — the metalanguage rather than
   the language.

       [ [extern]{.cc} [ ]{.alt} [void]{.cc} ]{.opt} [ident]{.stx}

   .opt draws both brackets around its content; .lsb and .rsb are the same shapes as empty
   spans, for the times a bracket has to stand on its own. .zom is zero-or-more, .oom is
   one-or-more.

   A BRACKET IS A GLYPH PAIR NOW, and .opt is the two of them on its ::before and ::after.
   It was three borders on an empty box, which is how a mathematical bracket is built — a
   stem with a short arm at each end — and the glyph is that same drawing: one stroke, arms
   reaching above the cap height, stem descending past the baseline, which is what keeps a
   drawn bracket from being read as the typographic character. The three borders could not
   wrap content and could not reach epub.css; a pair can do both.

   Drawn in --muted, like the arrows and for the same reason: a bracket is punctuation around
   the grammar rather than part of it, and at the text's own weight the drawn stems -- which
   run taller than the font's -- pulled the eye away from the names between them.
   --sb-color sets it, should a document want them at full strength.

   Never bold, never italic: the notation means the same thing wherever it appears, and a
   grammar quoted inside a heading or an emphasised run would otherwise restate it. */
.lsb,
.rsb,
.opt::before,
.opt::after,
.zom,
.oom {
  font-family: "PadoxIcons", var(--font-sans);
  font-style: normal;
  font-weight: 400;
  color: var(--sb-color, var(--muted));
  display: inline-block;
  line-height: 0;
  vertical-align: baseline;
}

.lsb::before { content: "\005B"; }
.rsb::before { content: "\005D"; }
.opt::before { content: "\005B"; }
.opt::after  { content: "\005D"; }
.zom::before { content: "\002A"; }
.oom::before { content: "\002B"; }

.lsb,
.rsb,
.opt::before,
.opt::after {
  width: var(--sb-arm, 0.19em);
}

/* Margin on BOTH sides of every bracket. --sb-gap is the inner one, holding the bracket off
   what it encloses; --sb-outer is the one facing the sentence, without which an opening
   bracket sits against whatever ended the term before it. Two names rather than one because
   the two gaps are read differently: the inner belongs to the notation, the outer to the
   line. */
.lsb,
.opt::before {
  margin-left: var(--sb-outer, 0.16em);
  margin-right: var(--sb-gap, 0.16em);
}

.rsb,
.opt::after {
  margin-left: var(--sb-gap, 0.16em);
  margin-right: var(--sb-outer, 0.16em);
}

.lsb,
.rsb,
.opt,
.zom,
.oom {
  font-style: normal;
  font-weight: 400;
}

/* Zero-or-more and one-or-more.

   FIVE ARMS, NOT SIX, and the artwork is where that lives now. Six arms are three lines
   crossing at a point, which is what three strokes fall into; five is what a typeset
   asterisk has, and the difference is that five cannot be read as a crossing — one arm
   points up and the other four sit at 72° from it, so the mark has an orientation and reads
   as a glyph rather than as a snowflake.

   Both carry a HEAVIER stroke than the rest of the notation, on purpose: they are drawn at
   half its size, where 0.075em reads as a scratch. Round caps, being the only strokes here
   that end in mid-air.

   Centred ON the baseline rather than raised, and the glyph carries that: a superscript
   mark is the usual mathematical convention, but these are read against bracket arms that
   reach past the cap height and the two collided. --sb-mark is the width of the box; the
   ink follows font-size. */
.zom,
.oom {
  --sb-mark: 0.58em;
  width: var(--sb-mark);
  /* It follows the thing it quantifies with nothing between them in the source, so the
     space has to come from here or the mark touches the glyph before it. */
  margin-left: var(--sb-mark-gap, 0.07em);
}

/* ── [terminal]{.pai}: any PadoxIcons glyph, in running text ─────────────────
   The same construct `{.fai}` is, pointed at padox's OWN face -- and unlike that one it draws
   here as well as in the PDF, because the difference between them is which face is where.
   padox does not ship FontAwesome to a browser, so a page naming it would ask a reader for a
   face they may not have; PadoxIcons is in every artifact padox writes, so there is nothing
   to withhold.

   ONE DECLARATION IS THE WHOLE RULE. paicon.lua replaces the span's text -- which is the glyph
   NAME -- with the character, so there is no content to generate and nothing to hide; and
   every glyph carries its own advance and its own vertical position, drawn where its construct
   sits, so there is no width and no vertical-align to put back. That is the same division the
   notation's rules above make, where the box and the margins stayed in CSS and the ink went
   into the face.

   NO line-height AND NO inline-block, deliberately, which is where this differs from the
   notation. Those are empty spans standing in for a drawn box of a stated size; this is an
   icon in a sentence, and it should sit in the line and be measured by it like a capital.

   Colour and size are per-use and arrive inline from the filter; the family is the only thing
   a stylesheet can know. currentColor by default, so an icon inside a coloured run follows it. */
.pai {
  font-family: "PadoxIcons", var(--font-sans);
  font-style: normal;
  font-weight: 400;
}

/* ── Command lines: ::: {div.cli} ───────────────────────────────────────────
   A shell transcript, not a code block. Every top-level list item is one command
   line; a sub-item continues the command above it. Output is a .output code block
   or span inside the div.

   The prompt and the output arrow are list markers, generated by this sheet, which
   is the entire point of the construct: a prompt typed into the document is copied
   with the command and then will not paste into a shell. These cannot be selected.

   prompt= and output= on the div override the markers; cli.lua turns them into
   the custom properties read below. */

div.cli {
  /* ch, not em: in a monospace block the gutter is measured in characters, and
     cli.lua widens it to fit a longer prompt. */
  /* One character for the prompt and 1.5ch after it -- the same sum cli.lua uses when a
     longer prompt= widens it. It was 3.5ch for the two-character prompt. */
  --cli-gutter: 2.5ch;
  margin: 1rem 0 1.35rem;
  /* The same box as a code block, from the same two numbers. A transcript and a code block
     sit next to each other constantly, and until this used the variables it was 0.72/0.9
     against their 0.5/0.6 -- close enough to look like a mistake rather than a distinction. */
  padding: var(--pre-pad-y) var(--pre-pad-x);
  border: 1px solid var(--cli-border);
  border-radius: 8px;
  background: var(--cli-bg);
  color: var(--pre-text);
  font-family: var(--font-mono);
  font-feature-settings: var(--mono-features, normal);
  font-size: var(--mono-size);
  font-variant-ligatures: none;
  line-height: 1.45;
  hyphens: none;
  /* Never wrap; scroll, as a code block does. A transcript is columnar — output lines up
     under its command and a continuation is marked — and a line broken by the window
     rather than by the author destroys that alignment while looking exactly like a
     continuation the author wrote. The reader gets a scrollbar, which is honest about
     there being more to the right; a wrapped line is not. */
  overflow-x: auto;
}

div.cli li,
div.cli > p,
div.cli .output,
div.cli div.sourceCode:has(pre.output) {
  white-space: nowrap;
  /* max-content, or the block stays the width of the frame and its text merely spills:
     an overflowing INLINE does not lengthen the scroll container, so the scrollbar never
     appears and the tail is unreachable. Sizing each line to its own content is what gives
     div.cli something wider than itself to scroll. */
  width: max-content;
  /* Which would then collapse a short line to its text width -- harmless until something
     inside wants the full measure. min-width keeps the box at least as wide as the frame. */
  min-width: 100%;
}

div.cli ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A paragraph inside a transcript is a label for the commands under it -- `#= sh` above the
   commands for that shell -- never prose, because prose does not belong in a transcript. So
   it binds downward: the list it introduces starts flush against it, and what is left above
   separates one group from the next.

   Scoped to div.cli for exactly that reason. The same shape in ordinary text is a real
   paragraph and keeps its spacing, and no rule can tell the two apart outside this box. */
div.cli > p {
  margin: 0.55rem 0 0.15rem;
}

/* At the top of the block the space is the block's own padding; a margin only adds to it. */
div.cli > p:first-child {
  margin-top: 0;
}

/* The base sheet puts a square marker on ul > li and matches the li directly, so
   inheriting list-style: none from the ul does not reach it. */
div.cli li {
  margin: 0;
  list-style: none;
}

/* An in-flow marker, not an absolute one: an absolutely-positioned ::before is taken out of
   the line, and its glyph then sits on its OWN baseline -- which drifts from the command's
   wherever the two weights' metrics differ, a bold prompt against a regular command coming out
   low. An inline-block keeps the marker on the line, so it shares the command's baseline, and
   the negative margin pulls it into the gutter the padding-left reserved. A wrapped command
   still aligns under its own first character, because a transcript line does not wrap. */
div.cli > ul > li {
  padding-left: var(--cli-gutter);
}

div.cli > ul > li::before {
  content: var(--cli-prompt, ">");
  position: static;
  display: inline-block;
  width: var(--cli-gutter);
  margin-left: calc(-1 * var(--cli-gutter));
  text-align: left;
  font-size: inherit;
  color: var(--tip);
  font-weight: 650;
  user-select: none;
}

/* A SUB-ITEM IS A CONTINUATION of the command above it, not that command's output. It
   carries the continuation prompt in the gutter, exactly as a hard line break within one
   item does, so the two spellings of "this command goes on" look the same:

       * cc -Wall -Wextra
         * -o program main.c

   Output is no longer nesting. It is a .output code block or span inside the div, styled
   below — which is what lets a command's output be quoted verbatim, with its columns, and
   keeps the list structure meaning one thing only. */
div.cli ul ul {
  /* Pulled back by one gutter. The nested list sits inside its parent item's padding, so
     without this the continuation marker lands a gutter to the right of the prompt it is
     meant to line up under, and the continued text a gutter right of the command's. The
     item's own padding-left then puts both back where the top level has them. */
  margin: 0 0 0 calc(-1 * var(--cli-gutter));
}

div.cli ul ul > li {
  padding-left: var(--cli-gutter);
}

/* U+2026 HORIZONTAL ELLIPSIS, one glyph where two mid-dots were two. The PDF uses a two dot
   leader where the mono has one, which is the narrower mark and the better one at a command's
   own width; here the reader's font is unknown and an ellipsis is in all of them.
   The comment sits ABOVE the rule, not inside it: verify.ps1 anchors on `{` followed by
   `content:`, so a comment between the two reads as the generated content having been removed. */
div.cli ul ul > li::before {
  content: var(--cli-cont, "\2026");
  position: static;
  display: inline-block;
  width: var(--cli-gutter);
  margin-left: calc(-1 * var(--cli-gutter));
  text-align: left;
  font-size: inherit;
  color: var(--tip);
  user-select: none;
}

/* ── Output inside a transcript ──────────────────────────────────────────────
   `​``{.output} or [text]{.output} within a .cli div: what the command printed.

   Deliberately NOT the body-level .output block. There the OUTPUT strip names a kind of
   block standing on its own in the prose; here the surrounding transcript has already said
   what this is, and a label on every command's output would be noise. So the strip is
   suppressed and what remains is the alignment and the muted colour — which is what a
   terminal shows: output under its command, in the same column, distinguishable by tone.

   The SHADING goes too, and this says so on purpose rather than by inheritance: a fill on
   the output would sit a slab inside the transcript's own tint, so the background is cleared
   here. The PDF clears it by the same argument, in padoxclisetup.

   No marker and no indent of its own, for the same reason it had none when it was a nested
   list: it lines up with the command it belongs to. The top margin keeps a little air between
   the command and what it printed; the bottom one is larger, since the NEXT command is a new
   thing where the output belongs to the one above. */
div.cli .output,
div.cli div.sourceCode:has(pre.output) {
  display: block;
  margin: 0.35rem 0 0.45rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--muted);
  font-size: 1em;
  /* The block scrolls with the transcript, not on its own: two scrollbars inside one
     frame would let the output slide out of line with the command it belongs to. */
  overflow: visible;
}

/* The strip's own selectors carry a :not() and a :has(), so cancelling it needs at least
   their specificity — `div.cli .output::before` is (0,2,1) against (0,2,2), and lost: the
   label stayed, absolutely positioned, sitting over the command line above it. Matched
   shape for shape with div.cli in front, so each of these outweighs what it cancels. */
div.cli pre.output:not(.sourceCode)::before,
div.cli div.sourceCode:has(pre.output)::before {
  content: none;
}

/* Output sits where the command's text sits, not where its prompt does — which is what a
   terminal shows and what reads as belonging to the command above.

   Whether that needs stating depends on something the author did not decide: pandoc puts a
   fenced block INSIDE the list item when the item is indented, and beside the list when it
   is not. Inside, it inherits the item's padding and is already in the right column;
   beside, it starts at the frame. Measured at 83px against 49px for the same document
   written the two ways. So the outdented case is pushed in, and only that case — hence the
   child combinator, which is what keeps the two from compounding to two gutters.

   div.cli > p is the label form as well, so the padding goes on the span rather than on
   the paragraph: a label names the commands under it and belongs at the frame. */
div.cli > pre.output,
div.cli > div.sourceCode:has(pre.output),
div.cli > p > .output {
  padding-left: var(--cli-gutter);
}

/* A span is the one-line form — [Output of the above]{.output} — and has to sit like the
   block form, which it did not.

   The paragraph is what carries the space, so the paragraph is what has to give it up. This
   was written as `div.cli p > .output { margin-top: 0 }`, setting a vertical margin on an
   inline element, where it does nothing at all: the rule read as if it solved the problem and
   never had any effect. The gap stayed at the paragraph's own 0.55rem, against the 0.1rem the
   block form uses, and the two spellings of the same thing rendered differently.

   Zeroed rather than matched, because the span inside is display: block and already carries
   div.cli .output's own 0.1rem/0.45rem. Emptying the paragraph lets those through, so the
   span form and the block form are now spaced by one rule instead of two.

   :has(> .output) and not `div.cli > p`, which is the LABEL form as well: a label names the
   commands under it and keeps its spacing. */
div.cli > p:has(> .output) {
  margin: 0;
}

/* That bottom margin separates one command's output from the next command. The last has no
   next command, so it is only distance to the frame, and read as the block having a deeper
   floor when it happened to end in output. */
div.cli > ul > li:last-child .output:last-child,
div.cli > .output:last-child,
/* The span form, whose .output sits inside a paragraph, so the paragraph is what is last. */
div.cli > p:last-child > .output {
  margin-bottom: 0;
}

div.cli li > p {
  margin: 0;
}

/* Inline code is already in the block's own face; the box it wears in prose would
   only fight the transcript. Highlighting still applies, which is how a trailing
   `# comment`{.sh} comes out as a comment.

   pre-wrap is what makes a code span the way to keep column alignment. Markdown
   collapses runs of spaces in ordinary text before any filter or stylesheet can see
   them, so `ls -l` output written plainly loses its columns; written as a code span
   the spaces survive into the HTML, and this renders them. `pre`, not `pre-wrap`: the
   block scrolls rather than wrapping, so nothing here may break a line either. */
div.cli code {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  font-size: 1em;
  white-space: pre;
}

/* ── Copy button ──────────────────────────────────────────────────────── */

.copy-btn {
  --copy-size: 1.75rem;
  position: absolute;
  top: 0.4rem;
  right: 0.45rem;
  z-index: 1;
  display: grid;
  place-items: center;
  width: var(--copy-size);
  height: var(--copy-size);
  padding: 0;
  border: 1px solid transparent;
  border-radius: 5px;
  background: transparent;
  color: var(--pre-text);
  opacity: 0;
  cursor: pointer;
  transition: opacity 0.15s, color 0.15s, background 0.15s, border-color 0.15s;
}

.copy-btn svg {
  display: block;
  width: 0.85rem;
  height: 0.85rem;
  pointer-events: none;
}

/* On the OUTPUT strip, level with the word. The strip is a pseudo-element and cannot hold a
   button, so the button stays in the block and is centred against the strip's stated height.
   Smaller here: 1.75rem is taller than the strip, and a button that overhangs its own header
   bar is the fault this is fixing, not a smaller version of it. */
pre.output > .copy-btn,
div.sourceCode:has(pre.output) > .copy-btn {
  --copy-size: 1.3rem;
  top: calc((var(--output-strip-h) - var(--copy-size)) / 2);
}

/* On a .file/.snip label, level with the name. page.js puts the button inside the label, so
   50% is the label's own height however it wrapped. (The label declares position: relative
   with the rest of its box, above.) */
.code-label > .copy-btn {
  top: 50%;
  transform: translateY(-50%);
}

div.sourceCode:hover .copy-btn,
pre:hover .copy-btn {
  opacity: 0.45;
}

/* The label and the block it labels are one unit, so hovering either reveals the button --
   which now lives in the label and would otherwise stay hidden while the code is hovered. */
.code-label:hover > .copy-btn,
.code-label:has(+ div.sourceCode:hover) > .copy-btn,
.code-label:has(+ pre:hover) > .copy-btn {
  opacity: 0.45;
}

.copy-btn:hover {
  opacity: 1 !important;
  background: var(--code-overlay);
  border-color: var(--pre-border);
}

.copy-btn.copied {
  opacity: 1 !important;
  color: var(--tip);
}

/* ── Syntax highlighting ──────────────────────────────────────────────── */
/* Higher specificity than Pandoc's embedded `code span.X` rules.        */

/* Skylighting's token classes are two letters, and so are some of padox's own, so a few of
   them are the SAME NAME for two unrelated things. Exactly two collide today — `co` is
   padox's mono term and skylighting's Comment, `sc` is padox's acronym and skylighting's
   SpecialChar — and an unscoped rule for either reaches every such token in every code block.

   Measured before this reset: `\n` inside a Haskell string came out at 14.904px against the
   16.56px of the code around it, because `.sc` is 0.9em. It reads as a rendering fault and
   there is nothing in the code to explain it.

   So the padox properties are taken back here, where the token rules already are. The
   token's own colour is set below and is not touched. This is also why the font classes above
   are `.sans` and `.serif` and not `.ss` and `.rm`: `ss` is SpecialString, and a third
   collision was avoidable by choosing a name nobody else uses. */
code.sourceCode span.sc {
  font-size: 1em;
}

/* Code's own size wins on a code element: `PATH`{.sc} is an acronym written as code, and both
   classes are reductions, so the smaller won and an acronym in code sat below the code beside
   it -- measured 16.2px against 16.56px. The acronym reduction is for capitals shouting in
   PROSE; in a mono face already reduced there is nothing left for it to do. */
code.sc {
  font-size: var(--mono-size);
}

code.sourceCode span.co {
  font-family: inherit;
  letter-spacing: normal;
  margin-right: 0;
  font-variant-numeric: normal;
}

/* Token colours apply to inline highlighted code too — `# comment`{.sh} in a div.cli is
   the case in point. Scoped to code.sourceCode rather than to a div.sourceCode ancestor,
   which an inline code span does not have: without this it kept pandoc's own zenburn
   <style>, and a shell comment came out green instead of this sheet's grey. */
code.sourceCode span.al,
code.sourceCode span.er { color: var(--syn-al); font-weight: 700; }

code.sourceCode span.an,
code.sourceCode span.co,
code.sourceCode span.cv,
code.sourceCode span.do,
code.sourceCode span.in,
/* ITALIC, and the cost is known and accepted. Skylighting's markdown syntax classes a
   link's square brackets as a comment, so [text]{.cc} quoted in a markdown block comes
   out with slanted brackets -- which is why these were upright for a while. A comment is
   prose inside code and reads as prose; the artefact is skylighting's and shows only
   where a document quotes markdown at itself. --mono has always set them italic, so this
   is also the two modes agreeing. */
code.sourceCode span.wa { color: var(--syn-co); font-weight: normal; font-style: italic; }

code.sourceCode span.at              { color: var(--syn-at); }
code.sourceCode span.bu,
code.sourceCode span.dt              { color: var(--syn-type); }
code.sourceCode span.cf,
code.sourceCode span.im              { color: var(--syn-ctrl); font-weight: 600; }
code.sourceCode span.cn              { color: var(--syn-cn); }
code.sourceCode span.fu              { color: var(--syn-fu); }
code.sourceCode span.kw,
code.sourceCode span.ot              { color: var(--syn-kw); font-weight: 600; }
code.sourceCode span.op              { color: var(--syn-op); }
code.sourceCode span.pp              { color: var(--syn-pp); }
code.sourceCode span.bn,
code.sourceCode span.dv,
code.sourceCode span.fl              { color: var(--syn-num); }
code.sourceCode span.ch,
code.sourceCode span.sc,
code.sourceCode span.ss,
code.sourceCode span.st,
code.sourceCode span.vs              { color: var(--syn-str); }
code.sourceCode span.va              { color: var(--syn-va); }

img,
svg {
  max-width: 100%;
  height: auto;
}

/* Automatic for every SVG, because padox's SVGs are overwhelmingly diagrams and a class that
   has to be remembered will not be. Both spellings of the source are named: the linked file,
   and the data: URI --self-contained turns it into.

   `{.asis}` opts out, for a screenshot or anything where a particular colour carries the
   meaning. The name is deliberately general -- "take this as the author made it" -- rather than
   naming this one filter, so the same escape can cover anything padox would otherwise adjust on
   the author's behalf. Nothing else uses it yet. It FOLLOWS this rule rather than preceding it -- the two selectors have the same
   specificity, so the later one wins, and putting it first would silently do nothing. */
img[src$=".svg"],
img[src^="data:image/svg+xml"] {
  filter: var(--svg-adapt);
}

img.asis {
  filter: none;
}

p:has(> img:only-child) {
  margin-top: 1.2rem;
  text-align: center;
}

.center {
  text-align: center;
}

/* ── Dark mode ────────────────────────────────────────────────────────── */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --page: #0d1117;
    --paper: #161b22;
    --text: #adbac7;
    --muted: #8d96a0;
    --mark: #8d96a0;
    --border: #30363d;
    --border-strong: #484f58;
    --accent: #58a6ff;
    --link: #7ab8d4;
    --link-local: #61c291;  /* 7.94:1, matching --link exactly */
    --accent-soft: #1c2d3f;
    /* .cc/.co ARE NOT THE ACCENT, and on a dark page they had drifted into being it: #d9a066
       at S60% sat beside a --h1-accent of S72% in the same hue, so a named term read as an
       accent-coloured word rather than a term. Against body text at S19% the saturation is
       what shouted -- the LUMINANCE was already lower than the prose (8.26:1 against 9.58:1),
       so this is a chroma problem and not a contrast one. S30% keeps it identifiably warm
       without competing with the accent or the prose. 8.07:1 on #0d1117. */
    --co-color: #bfa588;
  --svg-adapt: invert(0.92) hue-rotate(180deg);
  --admon: #8d96a0;
    --admon-soft: #1c2128;
    --tip: #3fb950;
    --tip-rule: #2a5a2a;
    --tip-soft: #03060e;
    --note: #58a6ff;
    --note-soft: #1c2d3f;
    --caution: #d29922;
    --caution-rule: #d29922;
    --caution-soft: #271e06;
    --important: #a371f7;
    --important-soft: #211535;
    --warning: #db6d28;
    --warning-rule: #db6d28;
    --warning-soft: #271508;
    --code-overlay: rgba(255, 255, 255, 0.08);
    --code-border-overlay: rgba(255, 255, 255, 0.12);
    --pre-bg: #010409;
    /* Dark inverts the arithmetic, not the intent: the overlay is black, because a blue-grey
       at 8% would LIGHTEN a near-black ground. 0.35 is a judgement -- it lands the block on
       #090c10 against the page's #0d1117, close to the #010409 it replaces and still an
       overlay, so an admonition darkens with it instead of being covered. */
    --output-bg: rgba(0, 0, 0, 0.35);
    --pre-text: #e6edf3;
    --pre-border: transparent;
    --cli-bg: #03060e;
    /* The header bar goes the other way here. Light mode darkens it against a light block;
       --pre-bg is #010409 in dark, so there is no darker to go and it lifts instead. Both
       bars still move together -- one token, with --output-label-bg following it from :root
       rather than being restated, which is how they stay the same colour. */
    --label-bg: #1c2128;
    /* Same idea for a transcript's label, lifting from the dark --cli-bg rather than from
       --pre-bg: 1.274:1 against the code pair's 1.269:1, with the accent at 5.93 and the
       body text at 8.05 on it. */
    --cli-label-bg: #18231e;
    --output-label-text: #a2acb8;
    /* THE BLOCK'S EDGE IS THE SPAN'S EDGE AGAIN, and it was #18231e -- the band's own background,
       1.25:1 against the box, deliberately near-nothing on the reasoning that a green edge was the
       loudest thing on a dark page. Measured beside a labelled code block framed all round in
       --border-strong, the transcript had no visible edge at all: the band floated and the box under
       it was an unframed slab, so two constructs that sit one above the other in one document were
       drawn to two different standards.
       The old reasoning was right about SATURATION and wrong about LIGHTNESS. #315345 is the value
       the in-prose span already carries, so nothing new is invented: it measures 2.21:1 against the
       page where the code block's #484f58 measures 2.28, and 2.37 against the box against that
       block's 2.48 -- the same weight of edge, in the transcript's own hue rather than a neutral
       grey. The two tokens are equal in light mode; this makes them equal again in dark. */
    --cli-border: #315345;
    --cli-span-border: #315345;
    --table-stripe: #1c2128;
    --thead-bg: #1c2128;
    --heading: #a8bac9;
    /* Syntax token colours (dark mode) */
    --syn-al:   #e07878;
    --syn-co:   #6a7888;
    --syn-at:   #80c8b0;
    --syn-num:  #e0b87a;
    --syn-type: #7ec8c8;
    --syn-ctrl: #c09ae0;
    --syn-cn:   #e08888;
    --syn-fu:   #87bfde;
    --syn-kw:   #82aaef;
    --syn-op:   #8898a8;
    --syn-pp:   #e0a87a;
    --syn-str:  #98c892;
    --syn-va:   #c8d0d8;
    --heading-xl: #92a8b7;
    --h1-accent: #cc8e5c;
    --hero-subtitle: #e88296;
    --license-badge-filter: invert(1);
    --license-badge-opacity: 0.54;
    /* A TITLE IS PROMINENT BY SIZE, NOT BY BEING THE BRIGHTEST THING ON THE PAGE. #d4b87a was
       S51% at 8.94:1 against this ground -- above the body text's 8.75 and well above the
       section headings' 7.01, so once the accent and .cc were calmed it was left as the one
       glaring object. S42% and two points of lightness put it at 7.62:1: brighter than a
       heading, dimmer than the prose, which is the order the eye expects. */
    --title-text: #c4a96e;
    --del-line: rgba(220, 90, 90, 0.48);
    --kbd-bg: #2d333b;
    --kbd-shadow: rgba(0, 0, 0, 0.70);
    --selection-bg: rgba(100, 180, 255, 0.35);
    --details-marker: #e05c5c;
    --author-text: #adbac7;
    --toc-bg: #1c2128;
    --toc-hover: #1c2d3f;
    --quote-text: #cdd9e5;
    --paper-shadow: rgb(0 0 0 / 40%);
    --btn-shadow: rgb(0 0 0 / 30%);
    --btn-border: rgb(88 166 255 / 25%);
    --btn-bg: rgb(22 27 34 / 72%);
    --btn-border-hover: rgb(88 166 255 / 40%);
    --btn-bg-hover: rgb(28 45 63 / 86%);
    --btn-bg-mobile: rgb(22 27 34 / 88%);
    /* Added to keep this block identical to the toggle palette below. */
    --tip-code: #1c3d24;
    --note-code: #1b3348;
    --caution-code: #362b08;
    --important-code: #2d1a4a;
    --warning-code: #36230f;
    --code-bg: #1f2328;
    --tbody-code-bg: #2d333b;
  }
}

[data-theme="dark"] {
  color-scheme: dark;
  --page: #0d1117;
  --paper: #161b22;
  --text: #adbac7;
  --muted: #8d96a0;
  --mark: #8d96a0;
  --border: #30363d;
  --border-strong: #484f58;
  --accent: #58a6ff;
  --link: #7ab8d4;
  --link-local: #61c291;  /* 7.94:1, matching --link exactly */
  --accent-soft: #1c2d3f;
  --co-color: #bfa588;
    --svg-adapt: invert(0.92) hue-rotate(180deg);
  --admon: #8d96a0;
  --admon-soft: #1c2128;
  --tip: #3fb950;
  --tip-rule: #2a5a2a;
  --tip-soft: #03060e;
  --tip-code: #1c3d24;
  --note: #58a6ff;
  --note-soft: #1c2d3f;
  --note-code: #1b3348;
  --caution: #d29922;
  --caution-rule: #d29922;
  --caution-soft: #271e06;
  --caution-code: #362b08;
  --important: #a371f7;
  --important-soft: #211535;
  --important-code: #2d1a4a;
  --warning: #db6d28;
  --warning-rule: #db6d28;
  --warning-soft: #271508;
  --warning-code: #36230f;
  --code-bg: #1f2328;
  --code-overlay: rgba(255, 255, 255, 0.08);
  --code-border-overlay: rgba(255, 255, 255, 0.12);
  --pre-bg: #010409;
  /* Dark inverts the arithmetic, not the intent: the overlay is black, because a blue-grey
     at 8% would LIGHTEN a near-black ground. 0.35 is a judgement -- it lands the block on
     #090c10 against the page's #0d1117, close to the #010409 it replaces and still an
     overlay, so an admonition darkens with it instead of being covered. */
  --output-bg: rgba(0, 0, 0, 0.35);
  --pre-text: #e6edf3;
  --pre-border: transparent;
  --cli-bg: #03060e;
  /* Lifted rather than darkened, for the reason given in the media block. --output-label-bg
     is not restated: it follows --label-bg from :root. */
  --label-bg: #1c2128;
  /* Same idea for a transcript's label, lifting from the dark --cli-bg rather than from
     --pre-bg: 1.274:1 against the code pair's 1.269:1, with the accent at 5.93 and the
     body text at 8.05 on it. */
  --cli-label-bg: #18231e;
  --output-label-text: #a2acb8;
  /* THE BLOCK'S EDGE IS THE SPAN'S EDGE AGAIN, and it was #18231e -- the band's own background,
     1.25:1 against the box, deliberately near-nothing on the reasoning that a green edge was the
     loudest thing on a dark page. Measured beside a labelled code block framed all round in
     --border-strong, the transcript had no visible edge at all: the band floated and the box under
     it was an unframed slab, so two constructs that sit one above the other in one document were
     drawn to two different standards.
     The old reasoning was right about SATURATION and wrong about LIGHTNESS. #315345 is the value
     the in-prose span already carries, so nothing new is invented: it measures 2.21:1 against the
     page where the code block's #484f58 measures 2.28, and 2.37 against the box against that
     block's 2.48 -- the same weight of edge, in the transcript's own hue rather than a neutral
     grey. The two tokens are equal in light mode; this makes them equal again in dark. */
  --cli-border: #315345;
  --cli-span-border: #315345;
  --table-stripe: #1c2128;
  --thead-bg: #1c2128;
  --tbody-code-bg: #2d333b;
  /* Syntax token colours (dark mode) */
  --syn-al:   #e07878;
  --syn-co:   #6a7888;
  --syn-at:   #80c8b0;
  --syn-num:  #e0b87a;
  --syn-type: #7ec8c8;
  --syn-ctrl: #c09ae0;
  --syn-cn:   #e08888;
  --syn-fu:   #87bfde;
  --syn-kw:   #82aaef;
  --syn-op:   #8898a8;
  --syn-pp:   #e0a87a;
  --syn-str:  #98c892;
  --syn-va:   #c8d0d8;
  --heading: #a8bac9;
  --heading-xl: #92a8b7;
  --h1-accent: #cc8e5c;
  --hero-subtitle: #e88296;
  --license-badge-filter: invert(1);
  --license-badge-opacity: 0.54;
  --title-text: #c4a96e;
  --author-text: #adbac7;
  --toc-bg: #1c2128;
  --toc-hover: #1c2d3f;
  --quote-text: #cdd9e5;
  --paper-shadow: rgb(0 0 0 / 40%);
  --btn-shadow: rgb(0 0 0 / 30%);
  --btn-border: rgb(88 166 255 / 25%);
  --btn-bg: rgb(22 27 34 / 72%);
  --btn-border-hover: rgb(88 166 255 / 40%);
  --btn-bg-hover: rgb(28 45 63 / 86%);
  --btn-bg-mobile: rgb(22 27 34 / 88%);
  --selection-bg: rgba(100, 180, 255, 0.35);
  /* Present only in the media block before this; without them a reader on a light system who toggles dark kept the light values. */
  --del-line: rgba(220, 90, 90, 0.48);
  --kbd-bg: #2d333b;
  --kbd-shadow: rgba(0, 0, 0, 0.70);
  --details-marker: #e05c5c;
}

/* ── Previous / next page navigation ──────────────────────────────────── */
/* Rendered by the template only when prevpage or nextpage metadata is set, so a
   standalone document is unaffected. Either link may appear alone; margin-left:auto
   keeps a lone "Next" hard right. */

.page-nav {
  display: flex;
  gap: 1rem;
  margin-top: 2.5rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--border);
}

/* No box around each link: the rule above already separates this from the body, so
   borders and their padding only added weight. A row rather than a column, because
   the chevron sits beside the title and carries the direction on its own -- there is
   no PREVIOUS/NEXT label. align-items keeps the chevron centred against a title that
   has wrapped to more than one line. */
.page-nav a {
  display: flex;
  align-items: center;
  gap: 0.45em;
  flex: 0 1 auto;
  max-width: 46%;
  text-decoration: none;
  color: var(--link);
}

.page-nav-next {
  margin-left: auto;
  text-align: right;
}

/* Underline only the title, matching how links behave elsewhere. */
.page-nav a:hover .page-nav-title {
  text-decoration: underline;
}

/* Inline SVG rather than a glyph, whose size and baseline vary wildly between fonts.
   These are rotations of the page-jump chevron, so keep them a shade under its
   1.25rem: the page-jump is a floating button and should stay the larger of the two.
   In rem, not em, so the comparison holds however the anchor's font size changes. */
.page-nav-chevron {
  width: 1.2rem;
  height: 1.2rem;
  flex: none;
}

/* Titles are capped at under half the width, so they wrap often. Balanced, like
   headings and the subtitle, rather than leaving one word stranded on the last line.
   This is a flex child of the anchor, so it is blockified and text-wrap applies.
   hyphens: none overrides the body's `auto`, which reads badly on a short link. */
.page-nav-title {
  font-size: 0.92rem;
  hyphens: none;
  overflow-wrap: break-word;
  text-wrap: balance;
}

/* ── Footer ───────────────────────────────────────────────────────────── */

/* Copyright left, author(s) right. margin-left:auto on the byline does the pushing,
   so either half renders correctly on its own. flex-wrap lets them stack on a narrow
   screen rather than crushing together. */
footer {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1.5rem;
  margin-top: 2.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
  font-size: 0.95rem;
  color: var(--muted);
}

/* Emitted unconditionally by the templates, so a document with no rights, licence or author
   still gets the element. Hiding it here is what lets one footer replace a branch per
   combination — see the note in the templates for why it has to be a single line. */
footer:empty {
  display: none;
}

footer .rights {
  margin: 0;
  font-style: italic;
}

/* The licence sits tight under the copyright, or in its place when there is none: same
   column, a hair's gap, so the two read as one statement rather than two footer items. The
   byline still sits opposite them, because the footer aligns on the first baseline and that
   is the copyright's. */
footer .footer-legal {
  display: flex;
  flex-direction: column;
  gap: 0.12rem;
}

/* Full footer size, not reduced: the licence is a second statement of the same standing as
   the copyright, and shrinking it made it look like a footnote to the line above. */
footer .license {
  margin: 0;
}

/* Real text in the template rather than a CSS ::before, so it is selectable and travels with
   a copy of the footer — the opposite call to the OUTPUT strip, and for the opposite reason:
   that one exists to be left out of a copy, this one is part of the sentence. The label is
   outside the value so it stays plain while the licence itself is usually a link. */
/* Small caps rather than a size change, so the label reads as a label while keeping the
   copyright's size and weight. A touch of tracking with it: small capitals sit closer than
   lowercase at the same size and close up without it — the same reason .abstract-title and
   .chr carry letter-spacing. */
footer .license-label {
  color: var(--muted);
  font-variant-caps: small-caps;
  letter-spacing: 0.03em;
}

/* A licence given as a badge sizes to the line rather than to its own pixels — the CC glyph
   set is 64px tall and would tower over the footer. The negative vertical-align sits it on the
   text baseline rather than hanging it below. Height rather than width, so a one glyph badge
   and a three glyph one share a size.

   The tinting is what stops a badge shouting. CSS cannot reach inside an <img>, and an SVG's
   own prefers-color-scheme would ignore the theme *toggle* entirely, so the element is
   filtered instead — which the toggle does reach.

   On paper the artwork's flat black is darker than the label beside it; on a dark page the
   white disc is brighter. invert(1) fixes the *shape* of the dark case — a dark plate with a
   light glyph rather than a bright plate — and the opacity then lands the ink on --muted, so
   the badge carries the same weight as the word LICENSE: next to it.

   The two opacities are not the same number because they are solving different sums. Against
   white paper the ink lands at 255(1-a); against the dark page it lands at bg + a(255-bg).
   Matching --muted (#57606a light, #8d96a0 dark) gives 0.62 and 0.54. A single shared value
   was 0.72, which came out #474747 on paper and #bebfc1 on a dark page: too black and too
   white respectively, exactly as reported.

   Driven by a custom property rather than a duplicated selector, so the value sits with the
   rest of the palette and the two dark blocks are held in step by the check that compares
   them. */
footer .license img {
  height: 1.45em;
  width: auto;
  vertical-align: -0.4em;
  opacity: var(--license-badge-opacity, 0.72);
  filter: var(--license-badge-filter, none);
}

/* Authors are comma-separated by the template's $sep$, so this only has to style
   them; no separator is generated in CSS. */
footer .byline {
  margin: 0 0 0 auto;
  text-align: right;
}

footer .author {
  color: var(--author-text);
  font-weight: 600;
}

/* ── Responsive ───────────────────────────────────────────────────────── */

@media (max-width: 720px) {
  body {
    padding: 28px 10px 48px;
    box-shadow: none;
  }

  section.footnotes ol {
    margin-right: 0;
  }

  .abstract {
    --ab-lead: 0.8rem;
    margin-left: 1rem;
    margin-right: 1rem;
    font-size: 1rem;
  }

  pre,
  div.sourceCode,
  .code-label {
    padding: var(--pre-pad-y) var(--pre-pad-x);
  }


  #title-block-header {
    column-gap: 1rem;
    padding-bottom: 0.45rem;
  }

  table {
    border-radius: 0;
  }

  .page-jump {
    right: 0.75rem;
    bottom: 0.75rem;
    background: var(--btn-bg-mobile);
  }

  .theme-toggle {
    right: 0.75rem;
    bottom: 3.6rem;
    background: var(--btn-bg-mobile);
  }

  dd {
    margin-inline-start: 0.85rem;
  }
}

@media (max-width: 559px) {
  html {
    font-size: 14px;
  }

  .copy-btn {
    opacity: 0.4;
  }
}
/* ── Sidebar TOC (wide displays) ─────────────────────────────────────── */

@media (min-width: 1200px) {
  /* :not(.toc-side) keeps these inert for a site build, whose TOC is the right-hand column
     of its own grid. Without it, an ID in :has() outspecifies anything padox-site.css can
     say about body width or the theme toggle. */
  body:has(#TOC:not(.toc-side)) {
    max-width: calc(860px + clamp(288px, 24vw, 384px) + 2.5rem);
  }

  body:has(#TOC:not(.toc-side)) .page-jump,
  body:has(#TOC:not(.toc-side)) .theme-toggle {
    right: max(1rem, calc((100vw - (860px + clamp(288px, 24vw, 384px) + 2.5rem)) / 2 - 3.4rem));
  }

  /* One DOM order — abstract, TOC, body — serves both widths. Below 1200px it is the
     natural block flow (abstract, then the collapsed TOC, then the body); here the content
     column becomes a two-column grid that pulls the TOC out to the left beside the other
     two, with the abstract and the body stacked in the right column. The body is its own
     grid item — a single row for the whole body — so the sidebar's height is absorbed by
     the body's tall row rather than inflating the abstract's short one. */
  .content:has(#TOC) {
    display: grid;
    grid-template-columns: clamp(288px, 24vw, 384px) minmax(0, 1fr);
    column-gap: 2.5rem;
    align-items: start;
  }

  .content:has(#TOC) > .abstract,
  .content:has(#TOC) > .body {
    grid-column: 2;
  }

  .content:has(#TOC) > .body {
    min-width: 0;
    overflow-x: clip;
  }

  .content:has(#TOC) > .toc {
    grid-column: 1;
    grid-row: 1 / span 2;
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
    margin: 0;
  }

  /* The disclosure is emitted open in the markup, so its contents show here without any
     author CSS. The summary is hidden — the nav's own h2 below supplies the sidebar's
     label — and the box reads as the always-open column it is. */
  .content:has(#TOC) > .toc > summary {
    display: none;
  }

  .content:has(#TOC) > .toc > #TOC {
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 0;
    background: none;
  }

  .content:has(#TOC) > .toc > #TOC > h2 {
    display: block;
    margin: 0 0 0.4rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
  }

  .content:has(#TOC) > .toc > #TOC > ul {
    column-count: 1;
  }

  .content:has(#TOC) > .toc > #TOC a {
    padding-left: 0.3rem;
  }

  .content:has(#TOC) > .toc > #TOC > ul > li > ul > li > a {
    padding-left: 1rem;
  }

  .content:has(#TOC) > .toc > #TOC > ul > li > ul > li > ul > li > a {
    padding-left: 1.7rem;
  }
}

@media print {
  html,
  body {
    background: #ffffff;
  }

  body {
    max-width: none;
    padding: 0;
    box-shadow: none;
  }

  a {
    color: inherit;
  }

  pre {
    border: 1px solid var(--border-strong);
    background: #f6f8fa;
    color: #111827;
  }

  .page-jump,
  .theme-toggle {
    display: none;
  }
}
