: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 .cmdline, which is monospace without being code. */
  --font-mono:
    "IosevkaTerm Nerd Font Mono", "Cascadia Mono NF", ui-monospace, SFMono-Regular,
    Menlo, Consolas, "Liberation Mono", monospace;
  --page: #f6f7f9;
  --paper: #ffffff;
  --text: #24292f;
  --muted: #57606a;
  --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;
  /* Admonitions */
  --tip: #1a7f37;
  --tip-soft: #dafbe1;
  --note: #0969da;
  --note-soft: #ddf4ff;
  --caution: #9a6700;
  --caution-soft: #fff8c5;
  --important: #8250df;
  --important-soft: #fbefff;
  --warning: #c05700;
  --warning-soft: #fff3e0;
  /* Code blocks */
  --code-overlay: rgba(0, 0, 0, 0.06);
  --code-border-overlay: rgba(0, 0, 0, 0.10);
  --pre-bg: #f0f4f8;
  --pre-text: #1e2430;
  --pre-border: #cdd5de;
  --console-bg: #edf5ed;
  /* {.ascart}: the largest leading that still joins box-drawing. See pre.ascart. */
  --ascart-line-height: 1.15;
  --output-label-bg: #e2e8f0;
  --output-label-text: #57606a;
  --console-border: #6b9b6b;
  /* 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;
  --h1-accent: #c47828;
  --hero-subtitle: #96263a;
  /*--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 {
  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 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;
}

section.footnotes ol {
  margin-left: 1.65rem;
  margin-right: 1.65rem;
  padding-left: 1em;
}

.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;
}

#TOC {
  margin: 0 0 2rem;
  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;
  list-style: none;
  break-inside: 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);
}

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;
}
.content 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 {
  margin-top: 2.0rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--border-strong);
  font-size: 1.45rem;
}

h3 {
  display: flex;
  align-items: center;
  gap: 0;
  margin-top: 1.9rem;
  font-size: 1.3rem;
}

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

h4 {
  margin-top: 1.5rem;
  font-size: 1rem;
}

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;
}

/* ── 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 {
  margin: 1.5rem 0 0;
  padding: 0.45rem 0.9rem;
  background: var(--thead-bg);
  border: 1px solid var(--border-strong);
  border-bottom: 1px solid var(--border);
  border-radius: 8px 8px 0 0;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.35;
  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. */
.code-label code {
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  white-space: normal;
  font-size: 1em;
  font-weight: 600;
  color: var(--h1-accent);
}

/* 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) {
  color: var(--h1-accent);
}

/* The description tells the two kinds of label apart, since the name cannot: a language
   designator and a filename look alike. A complete file is the heavier thing, so it takes
   the weight; a fragment takes italic. codelabels.lua marks the description whether or not
   it became a self-link, so both rules reach it either way. */
.code-label.file .code-label-desc {
  font-weight: 600;
}

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

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

/* 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. */
.code-label:has(+ .cmdline) {
  border-color: var(--console-border);
}

.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 + .cmdline {
  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. */
:is(.cc, .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 {
  margin: 2.5rem 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;
}

ul,
ol {
  padding-left: 1.2rem;
}

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

ul.task-list {
  padding-left: 1.8rem;
}

ul > li {
  list-style-type: square;
}

ul > li::marker {
  font-size: 1.15em;
}

ul ul {
  padding-left: 1.25rem;
}

ul > li > ul > li {
  list-style-type: disc;
}

ul > li > ul > li > ul > li {
  list-style-type: circle;
}

ul.task-list > li {
  list-style-type: 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);
}

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;
  }
}

.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: 7px;
}

.tip {
  border-left-color: var(--tip);
  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);
  background: var(--warning-soft);
}

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

.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;
}

.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);
}

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

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

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

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

table {
  display: block;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
  margin: 1.4rem 0 1.4rem 0;
  border-collapse: collapse;
  border: 1px solid var(--border);
  border-radius: 6px;
  hyphens: none;
}

caption {
  margin-bottom: 0.4rem;
  font-size: 0.875em;
  font-style: italic;
  color: var(--muted);
  text-align: left;
}

thead {
  background: var(--thead-bg);
}

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

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

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

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

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);
  word-break: break-all;
}

pre {
  position: relative;
  margin: 1rem 0 1.35rem;
  padding: 0.72rem 0.9rem;
  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, so this stops short of it: 1.15 clears the
   tightest face in the mono stack (Liberation Mono, whose glyph is 21px, so 1.167) while
   still buying most of the leading back. 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: 0.72rem 0.9rem;
  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;
}

div.sourceCode:has(pre.console) {
  background: var(--console-bg);
  border-color: var(--console-border);
  border-left-width: 4px;
}

/* ── 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. */
pre.output:not(.sourceCode)::before,
div.sourceCode:has(> pre.output)::before {
  content: "Output";
  display: block;
  margin: -0.72rem -0.9rem 0.62rem;
  padding: 0.1rem 0.9rem 0.14rem;
  border-bottom: 1px solid var(--pre-border);
  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: 1.5;
  text-transform: uppercase;
}

/* {.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;
}

/* 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);
  }

  .tra::after {
    clip-path: polygon(
      0 43%,
      calc(100% - var(--arrow-point, 0.5em)) 43%,
      calc(100% - var(--arrow-point, 0.5em)) 4%,
      100% 50%,
      calc(100% - var(--arrow-point, 0.5em)) 96%,
      calc(100% - var(--arrow-point, 0.5em)) 57%,
      0 57%);
  }

  .tla::before {
    clip-path: polygon(
      100% 43%,
      var(--arrow-point, 0.5em) 43%,
      var(--arrow-point, 0.5em) 4%,
      0 50%,
      var(--arrow-point, 0.5em) 96%,
      var(--arrow-point, 0.5em) 57%,
      100% 57%);
  }
}

/* ── 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. */

.cc {
  font-family: var(--font-sans);
  font-weight: 650;
  font-variant-ligatures: none;
  letter-spacing: var(--cc-track, 0.04em);
  margin-right: calc(-1 * var(--cc-track, 0.04em));
}

/* ── Drawn symbols: [ ]{.rar} and friends ────────────────────────────────────
   → ← ⇒ ⇐ ≡ ‖ as empty spans whose shape is drawn, not typed: the symbol *is* the element.
   Same reason as the labelled arrow — a character renders in some fonts, at some sizes, on
   some devices, and these are exactly the characters that go missing. Drawn, they cannot.

       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

   --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 {
  font-style: normal;
  font-weight: 400;
}

/* Typed, for an engine that cannot clip a shape. */
.rar::before { content: "\2192"; }
.lar::before { content: "\2190"; }
.dra::before { content: "\21D2"; }
.dla::before { content: "\21D0"; }
.eqv::before { content: "\2261"; }
.alt::before { content: "\2016"; }

@supports (clip-path: polygon(0 0, 1px 0, 0 1px)) {
  .rar,
  .lar,
  .dra,
  .dla,
  .eqv,
  .alt {
    display: inline-block;
    height: var(--sym-height, 0.66em);
    vertical-align: -0.04em;
    background: var(--sym-color, var(--muted));
  }

  .rar::before,
  .lar::before,
  .dra::before,
  .dla::before,
  .eqv::before,
  .alt::before {
    content: none;
  }

  /* Single arrows: the same shaft and head the labelled arrow uses. */
  .rar,
  .lar {
    width: var(--sym-arrow, 1.05em);
  }

  .rar {
    clip-path: polygon(
      0 43%,
      calc(100% - var(--arrow-point, 0.5em)) 43%,
      calc(100% - var(--arrow-point, 0.5em)) 4%,
      100% 50%,
      calc(100% - var(--arrow-point, 0.5em)) 96%,
      calc(100% - var(--arrow-point, 0.5em)) 57%,
      0 57%);
  }

  .lar {
    clip-path: polygon(
      100% 43%,
      var(--arrow-point, 0.5em) 43%,
      var(--arrow-point, 0.5em) 4%,
      0 50%,
      var(--arrow-point, 0.5em) 96%,
      var(--arrow-point, 0.5em) 57%,
      100% 57%);
  }

  /* Double arrows, drawn as an SVG mask rather than a clipped polygon.
     ⇒ is not one outline. Measured off the real glyph: three ink bands either side of its
     centre at 70% across, meaning two shaft strokes that continue *into* the head with a
     chevron flanking them — not a shaft that flares into a point. As a single polygon that
     needs the arms to be the outer edge offset perpendicularly, which is solvable but was
     wrong twice: first a filled head, then a chevron 1.7x too thick where it left the
     shaft, because there the stroke was bounded by a horizontal edge rather than a parallel
     one. Measured, not judged by eye: 12.16px against the shaft's 7.1px.

     Three SVG strokes of one stroke-width are the same drawing with none of the arithmetic,
     and constant width is then a property of the tool rather than something to re-derive
     whenever a proportion changes. A mask rather than a background image, so the colour
     still comes from CSS and follows the theme.

     Proportions, in the viewBox's own units: the two shafts sit 21 apart, three stroke
     widths, and run to 84 — far enough to meet the arms, since a chevron that only sits
     near the shafts reads as a separate character and the pair comes out as "=>". The
     chevron opens at 90° and stands 56 tall, which is 0.61em of the element against a body
     x-height near 0.52em: below that it looks like a small mark trailing the lines rather
     than the head of one symbol. */
  .dra,
  .dla {
    width: var(--sym-double, 1.25em);
    height: var(--sym-double-height, 0.72em);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
  }

  .dra {
    --sym-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 66'%3E%3Cg fill='none' stroke='%23fff' stroke-width='7'%3E%3Cpath d='M3 22.5H84'/%3E%3Cpath d='M3 43.5H84'/%3E%3Cpath d='M66 5L94 33L66 61'/%3E%3C/g%3E%3C/svg%3E");
    -webkit-mask-image: var(--sym-mask);
    mask-image: var(--sym-mask);
  }

  .dla {
    --sym-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 66'%3E%3Cg fill='none' stroke='%23fff' stroke-width='7'%3E%3Cpath d='M97 22.5H16'/%3E%3Cpath d='M97 43.5H16'/%3E%3Cpath d='M34 5L6 33L34 61'/%3E%3C/g%3E%3C/svg%3E");
    -webkit-mask-image: var(--sym-mask);
    mask-image: var(--sym-mask);
  }

  /* Parallel bars are gradients rather than clips: three or two separate strokes cannot be
     one polygon, and hard colour stops give exactly the same crisp edges. */
  .eqv {
    width: var(--sym-eqv, 0.85em);
    height: var(--sym-eqv-height, 0.62em);
    background: linear-gradient(
      to bottom,
      var(--sym-color, var(--muted)) 0 18%, transparent 18% 41%,
      var(--sym-color, var(--muted)) 41% 59%, transparent 59% 82%,
      var(--sym-color, var(--muted)) 82% 100%);
  }

  /* A space either side, always. The bars mean "or" between two alternatives, and without
     it every use needs the gap written by hand. Margin, not padding: padding would stretch
     the gradient that draws the bars. */
  .alt {
    width: var(--sym-alt, 0.34em);
    height: var(--sym-alt-height, 0.86em);
    margin: 0 var(--sym-alt-gap, 0.25em);
    vertical-align: -0.12em;
    background: linear-gradient(
      to right,
      var(--sym-color, var(--muted)) 0 34%, transparent 34% 66%,
      var(--sym-color, var(--muted)) 66% 100%);
  }
}

/* ── Command lines: ::: {.cmdline} ───────────────────────────────────────────
   A shell transcript, not a code block. Every top-level list item is one command
   line; its sub-items are that command's output.

   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; cmdline.lua turns them into
   the custom properties read below. */

.cmdline {
  /* ch, not em: in a monospace block the gutter is measured in characters, and
     cmdline.lua widens it to fit a longer prompt. */
  --cmdline-gutter: 3.5ch;
  margin: 1rem 0 1.35rem;
  padding: 0.72rem 0.9rem;
  border: 1px solid var(--console-border);
  border-radius: 8px;
  background: var(--console-bg);
  color: var(--pre-text);
  font-family: var(--font-mono);
  font-size: 0.92em;
  font-variant-ligatures: none;
  line-height: 1.45;
  hyphens: none;
}

.cmdline 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 .cmdline 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. */
.cmdline > 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. */
.cmdline > 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. */
.cmdline li {
  margin: 0;
  list-style: none;
}

/* Absolute markers rather than a hanging indent, so a wrapped command aligns under
   its own first character and not under the prompt. */
.cmdline > ul > li {
  position: relative;
  padding-left: var(--cmdline-gutter);
}

.cmdline > ul > li::before {
  content: var(--cmdline-prompt, "$>");
  position: absolute;
  left: 0;
  color: var(--tip);
  font-weight: 650;
  user-select: none;
}

/* The continuation prompt, standing in the gutter where the prompt itself stands. It is
   pulled left by the gutter's width and gives that width back, so the text after it resumes
   at the same column as the line above. Not selectable, for the same reason the prompt is
   not: what a reader copies should be the command. */
.cmdline-cont {
  display: inline-block;
  width: var(--cmdline-gutter);
  margin-left: calc(-1 * var(--cmdline-gutter));
  color: var(--tip);
  user-select: none;
}

.cmdline-cont::before {
  content: var(--cmdline-cont, "\00B7\00B7");
}

/* Output carries no marker and no indent of its own: it lines up with the command it
   belongs to, which is what a terminal does. The nesting only says which command it came
   from, and the colour is what tells the two apart. */
.cmdline ul ul {
  margin: 0.1rem 0 0.45rem;
}

/* That bottom margin exists to separate a command's output from the next command. The last
   one has no next command, so it is only distance to the frame — and it read as the block
   having a deeper floor when its last line happened to be output. */
.cmdline > ul > li:last-child > ul:last-child {
  margin-bottom: 0;
}

.cmdline ul ul > li {
  padding-left: 0;
  color: var(--muted);
}

.cmdline 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. Wrap, not pre, so a long
   line still breaks rather than overflowing the block. */
.cmdline code {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  font-size: 1em;
  white-space: pre-wrap;
}

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

.copy-btn {
  position: absolute;
  top: 0.4rem;
  right: 0.45rem;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  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;
}

div.sourceCode:hover .copy-btn,
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.        */

/* Token colours apply to inline highlighted code too — `# comment`{.sh} in a .cmdline 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,
code.sourceCode span.wa { color: var(--syn-co); font-style: italic; font-weight: normal; }

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;
}

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;
    --border: #30363d;
    --border-strong: #484f58;
    --accent: #58a6ff;
    --link: #7ab8d4;
    --link-local: #61c291;  /* 7.94:1, matching --link exactly */
    --accent-soft: #1c2d3f;
    --tip: #3fb950;
    --tip-soft: #0e2d16;
    --note: #58a6ff;
    --note-soft: #1c2d3f;
    --caution: #d29922;
    --caution-soft: #271e06;
    --important: #a371f7;
    --important-soft: #211535;
    --warning: #db6d28;
    --warning-soft: #271508;
    --code-overlay: rgba(255, 255, 255, 0.08);
    --code-border-overlay: rgba(255, 255, 255, 0.12);
    --pre-bg: #010409;
    --pre-text: #e6edf3;
    --pre-border: transparent;
    --console-bg: #03060e;
    /* Lighter than the light-mode strip is relative to its page: on a dark page a
       near-black strip on near-black paper reads as nothing at all. Brighter text
       with it, to buy the lift without dropping below 4.5:1. */
    --output-label-bg: #30363d;
    --output-label-text: #a2acb8;
    --console-border: #2a5a2a;
    --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: #e08940;
    --hero-subtitle: #e88296;
    --title-text: #d4b87a;
    --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;
  --border: #30363d;
  --border-strong: #484f58;
  --accent: #58a6ff;
  --link: #7ab8d4;
  --link-local: #61c291;  /* 7.94:1, matching --link exactly */
  --accent-soft: #1c2d3f;
  --tip: #3fb950;
  --tip-soft: #0e2d16;
  --tip-code: #1c3d24;
  --note: #58a6ff;
  --note-soft: #1c2d3f;
  --note-code: #1b3348;
  --caution: #d29922;
  --caution-soft: #271e06;
  --caution-code: #362b08;
  --important: #a371f7;
  --important-soft: #211535;
  --important-code: #2d1a4a;
  --warning: #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;
  --pre-text: #e6edf3;
  --pre-border: transparent;
  --console-bg: #03060e;
  /* Lighter than the light-mode strip is relative to its page: on a dark page a
     near-black strip on near-black paper reads as nothing at all. Brighter text
     with it, to buy the lift without dropping below 4.5:1. */
  --output-label-bg: #30363d;
  --output-label-text: #a2acb8;
  --console-border: #2a5a2a;
  --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: #e08940;
  --hero-subtitle: #e88296;
  --title-text: #d4b87a;
  --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);
}

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

/* 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: 0.5rem 0.6rem;
  }


  #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));
  }

  .layout:has(#TOC) {
    display: flex;
    align-items: flex-start;
    gap: 2.5rem;
  }

  .layout:has(#TOC) > #TOC {
    flex: 0 0 clamp(288px, 24vw, 384px);
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 0;
    background: none;
  }

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

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

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

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

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

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

@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;
  }
}
