/* @pagina/shell-static — default theme ----------------------------------------------------
 *
 * Five cascade layers, declared here in cascade order (later wins):
 *
 *   pagina.reset    box-sizing and body basics      (tokens.css)
 *   pagina.tokens   the `--pg-*` contract           (tokens.css)
 *   pagina.reading  the content column              (reading.css)
 *   pagina.chrome   header, nav, TOC, pager, theme toggle, layout grid   (below)
 *   pagina.editor   the editing tool                (@pagina/editor's theme.css)
 *
 * `pagina.editor` is named here even though nothing in this file uses it: naming all five in
 * every pagina sheet is what makes the load order irrelevant. Whichever sheet a host links
 * first fixes the same order, so the editor's rules win over the reading layer whether the
 * host loads `pagina.css` before `editor.css` or the other way round.
 *
 * Every rule below lives in a layer, and *unlayered CSS beats layered CSS regardless of
 * specificity*. A host therefore restyles pagina with ordinary selectors and no `!important`;
 * a host that wants only the tokens links `pagina.tokens.css` instead of this file.
 *
 * The declaration line must stay the first statement in the file: it is what fixes the order,
 * and a layer first used later would otherwise sort itself after the ones named here.
 *
 * The two `@import`s are **build inputs, not runtime requests**: `dist/pagina.css` (and the
 * `_pagina/pagina.css` a site build emits) has them inlined, so a host links one file with one
 * hash. An imported sheet has no URL of its own for a host's cache-busting to stamp, which made
 * a tokens-only edit invisible to the importing sheet's hash. See `docs/theming.md`.
 */
@layer pagina.reset, pagina.tokens, pagina.reading, pagina.chrome, pagina.editor;
/*
 * @pagina/shell-static — the token contract, plus the minimal reset the tokens assume.
 *
 * This file is the *published surface* of pagina's theming: a host maps its own design system
 * onto these ~20 custom properties and everything pagina draws follows. It is shipped two ways
 * from this one source, so the two can never drift:
 *
 *   - inlined into `pagina.css` (the full sheet) at build time, and
 *   - copied verbatim to `dist/pagina.tokens.css` and to a built site's
 *     `_pagina/pagina.tokens.css` for `theme: "tokens"` hosts, which want pagina's structure
 *     and tokens but style the content column themselves.
 *
 * Everything is inside a cascade layer. Unlayered CSS beats layered CSS whatever its
 * specificity, so a host's plain `.pg-content h2 { … }` wins over pagina without `!important`.
 * All five layers are declared here, in the order the full sheet uses them, so the order is
 * fixed even when only this file is loaded — and identical to the order every other pagina
 * sheet declares, so which one a host links first cannot change the cascade.
 *
 * See `docs/theming.md` for the token table and the escape hatches.
 */

@layer pagina.tokens {
  :root {
    /* --8<-- [start:core] */
    /* Surfaces */
    --pg-bg: #ffffff;
    --pg-bg-raised: #f6f7f9;
    --pg-bg-sunken: #eceef2;
    /* Ink */
    --pg-fg: #1a1d23;
    --pg-muted: #6b7280;
    /* Primary action / links */
    --pg-accent: #3b5bdb;
    --pg-accent-fg: #ffffff;
    /* Rules and borders */
    --pg-line: #e3e6eb;
    --pg-line-strong: #c8cdd6;
    /* Corner rhythm */
    --pg-radius: 6px;
    --pg-radius-lg: 10px;
    /* Type */
    --pg-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    --pg-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    --pg-font-display: var(--pg-font);
    /* Content column width */
    --pg-measure: 72ch;
    /* --8<-- [end:core] */
    /*
     * Admonitions, three tokens per kind.
     *
     * `--pg-<kind>` is the hue: the accent edge and the glyph. `--pg-<kind>-surface` is the
     * tinted ground the block sits on. `--pg-<kind>-fg` is the title text, which is a separate
     * token rather than the hue because a hue chosen to read as a 3px edge is usually too light
     * to carry text at 4.5:1 on its own tint — `tip`'s green is 3.1:1 on white. A host retints a
     * kind by defining the three, and never by overriding a rule.
     */
    --pg-note: #3b5bdb;
    --pg-note-surface: #eef1fd;
    --pg-note-fg: #2b3f9e;
    --pg-tip: #0f9d58;
    --pg-tip-surface: #e9f7f0;
    --pg-tip-fg: #0a6b3d;
    --pg-info: #0b7285;
    --pg-info-surface: #e6f4f7;
    --pg-info-fg: #0a5a6b;
    --pg-warning: #b7791f;
    --pg-warning-surface: #fdf3e3;
    --pg-warning-fg: #8a5a12;
    --pg-danger: #d64545;
    --pg-danger-surface: #fdecec;
    --pg-danger-fg: #a12727;
    --pg-example: #7048e8;
    --pg-example-surface: #f1ecfd;
    --pg-example-fg: #5227b8;
    --pg-quote: #6b7280;
    --pg-quote-surface: #f2f3f5;
    --pg-quote-fg: #4b5159;
    /* Code surfaces */
    --pg-code-bg: #f6f7f9;
    --pg-shiki-bg: #ffffff;

    /*
     * Kineglyph figures.
     *
     * A rendered figure writes every paint as `var(--kg-color-<role>, <the colour it was drawn
     * with>)`, so it inherits its palette from the page instead of carrying one. Pointing each
     * role at the `--pg-*` that already means the same thing is what makes a host's theme reach
     * its diagrams: map pagina's tokens once and the figures come with them, in light and dark,
     * with no re-render and no per-host prerender.
     *
     * These are only the defaults. A host that wants finer control over its diagrams than over
     * its prose sets `--kg-color-*` directly — see `docs/theming.md`. Left undefined entirely,
     * a figure still paints exactly what it was drawn with.
     *
     * Only colour is mapped. Type and geometry are decided when a figure is rendered, because
     * SVG cannot reflow text, so `--kg-font-family` and `--kg-radius-*` are not offered here.
     *
     * Tone pairings match the Figure Builder's swatches (`FigureBuilder.tsx`), so the colour an
     * author picks in the editor is the colour the published figure shows.
     *
     * One role is deliberately *not* mapped to the rule colour it superficially resembles.
     * `--kg-color-connector` paints the arrows in a flow diagram, and an arrow is the sentence's
     * verb, not a rule between paragraphs: pointed at `--pg-line-strong` it carried 1.49:1 on the
     * figure canvas and vanished. `--pg-muted` is the token that already means "ink that is
     * secondary but still ink" (4.51:1 here), which is what a connector is. A host that wants its
     * diagrams' arrows in its brand colour sets `--kg-color-connector: var(--pg-accent)`.
     */
    --kg-color-canvas: var(--pg-bg-raised);
    --kg-color-surface: var(--pg-bg);
    --kg-color-surface-raised: var(--pg-bg);
    --kg-color-surface-muted: var(--pg-bg-sunken);
    --kg-color-border: var(--pg-line-strong);
    --kg-color-text: var(--pg-fg);
    --kg-color-text-muted: var(--pg-muted);
    --kg-color-accent: var(--pg-accent);
    --kg-color-accent-contrast: var(--pg-accent-fg);
    --kg-color-connector: var(--pg-muted);
    --kg-color-info: var(--pg-note);
    --kg-color-success: var(--pg-tip);
    --kg-color-warning: var(--pg-warning);
    --kg-color-danger: var(--pg-danger);
    --kg-color-chart1: var(--pg-accent);
    --kg-color-chart2: var(--pg-note);
    --kg-color-chart3: var(--pg-warning);
    --kg-color-chart4: var(--pg-tip);
    --kg-color-chart5: var(--pg-danger);
    --kg-color-chart6: var(--pg-example);
    --kg-color-chart-positive: var(--pg-tip);
    --kg-color-chart-negative: var(--pg-danger);
    --kg-color-chart-neutral: var(--pg-quote);

    /*
     * The cover band.
     *
     * A cover spans the whole page, so it has a shape of its own — and pagina copies the image
     * without decoding it, so the ratio box is also the only thing holding the layout still while
     * the image loads. `--pg-cover-position` decides which part of a *cropped* cover survives; it
     * does nothing under the default `cover_fit: contain`, which crops nothing.
     */
    --pg-cover-ratio: 3 / 1;
    --pg-cover-max: 22rem;
    --pg-cover-position: center;

    /* How far a figure may scale down before it scrolls instead. See `reading.css`. */
    --pg-figure-min-scale: 0.7;
    /*
     * How wide a figure may get. A diagram is wider than prose by nature, so a host with room in
     * its gutters can let figures use it: `min(960px, calc(100vw - 4rem))`, say. The default is
     * the reading measure, so a host that says nothing sees exactly the layout it had. Only the
     * figure moves — its caption stays in the column and the prose does not shift.
     */
    --pg-figure-max: 100%;
  }

  /* Dark redefines colours only: geometry, type and measure are scheme-independent.
   *
   * `@media screen` is the whole of pagina's print palette, and it is a wrapper rather than a second
   * set of values on purpose. Paper is white whatever the screen was, so a reader who chose dark
   * and pressed ⌘P was, before this line, asking for near-white ink: Chrome prints `color` but
   * drops backgrounds, so the page came out blank-looking with faint grey text. Excluding the dark
   * block from the print medium leaves `:root` — the light palette, already correct — in effect,
   * with nothing duplicated to drift. The shell's inline `--kg-color-*` block does the same, so a
   * figure's palette follows the page's. See `docs/theming.md`. */
  @media screen {
    [data-theme="dark"] {
      --pg-bg: #14161a;
      --pg-bg-raised: #1c1f26;
      --pg-bg-sunken: #0f1115;
      --pg-fg: #e7e9ee;
      --pg-muted: #9aa1ac;
      --pg-accent: #7c9bff;
      --pg-accent-fg: #0d1117;
      --pg-line: #2b2f38;
      --pg-line-strong: #3d434f;
      --pg-note: #7c9bff;
      --pg-note-surface: #1b2130;
      --pg-note-fg: #a8bcff;
      --pg-tip: #3ddc84;
      --pg-tip-surface: #16241d;
      --pg-tip-fg: #6ee7a8;
      --pg-info: #4dc4d9;
      --pg-info-surface: #14242a;
      --pg-info-fg: #7fd8e8;
      --pg-warning: #e0b34c;
      --pg-warning-surface: #2a2317;
      --pg-warning-fg: #edc76e;
      --pg-danger: #ff7b7b;
      --pg-danger-surface: #2c1b1c;
      --pg-danger-fg: #ff9d9d;
      --pg-example: #b197fc;
      --pg-example-surface: #221c33;
      --pg-example-fg: #c9b6ff;
      --pg-quote: #9aa1ac;
      --pg-quote-surface: #1e2027;
      --pg-quote-fg: #b6bcc6;
      --pg-code-bg: #1c1f26;
      --pg-shiki-bg: #1c1f26;
    }
  }
}

@layer pagina.reset {
  * { box-sizing: border-box; }
  html { color-scheme: light; }
  /* Screens only, for the reason the palette above is: see the note there. */
  @media screen {
    [data-theme="dark"] { color-scheme: dark; }
  }
  body {
    margin: 0;
    background: var(--pg-bg);
    color: var(--pg-fg);
    font-family: var(--pg-font);
    line-height: 1.55;
  }
}

/* @pagina/shell-static — the reading layer -------------------------------------------------
 *
 * The content column and nothing else: typography, code, admonitions, tabs, tables, figures.
 * Everything here is scoped to `.pg-content` or to a `pg-`/`kg-` class the renderer emits, so
 * the sheet is safe to load on a host page that has its own layout.
 *
 * It is a file of its own because *two* surfaces need it and only one of them used to get it:
 * the published page (via `pagina.css`) and the editor (`@pagina/editor`'s `theme.css`, for both
 * the preview pane and the ProseMirror document, which are the same `.pg-content` markup). Under
 * a host with a CSS reset the difference is not subtle — an `h1` with no reading layer is body
 * size, and the preview stops resembling the page it is previewing.
 *
 * The layer-order declaration must stay the first statement: it fixes the order no matter which
 * pagina sheet a host happens to load first. See `docs/theming.md`.
 */

@layer pagina.reading {
  a { color: var(--pg-accent); }
  code, pre, kbd { font-family: var(--pg-font-mono); font-size: 0.875em; }

  /* Content typography ----------------------------------------------------------------------
   *
   * Headings and lists state *everything* they need, including the values that happen to match a
   * browser default (`font-weight: 700`, the heading margins, the list markers and indent).
   * Leaning on the UA sheet for those was the bug: under a host reset — Tailwind's preflight sets
   * `font-size: inherit; font-weight: inherit; margin: 0` on every heading and strips list
   * markers — the UA rule is gone and pagina's rules were only ever half the declaration. The
   * published page looked fine because pagina's own shell has no reset; the editor's preview,
   * dropped into a host that has one, rendered an `h1` at body size in book weight.
   *
   * Every value below is the one that was previously arriving from the UA sheet, so nothing about
   * a page without a reset changes. */
  /*
   * `container-type` makes the reading column *measurable* from inside it: `100cqw` is the real
   * width of the prose, whatever decided it — the measure, a host's own cap, or the viewport.
   * A figure that has broken out of the column needs that number to put its caption back in line
   * with the paragraphs, and `--pg-measure` cannot supply it: it is `72ch`, and `ch` is
   * font-relative, so the same token means one thing on a paragraph, another inside a smaller
   * `figcaption`, and another again on a host element that caps the column in a different size.
   */
  .pg-content { max-width: var(--pg-measure); container-type: inline-size; }
  .pg-content h1, .pg-content h2, .pg-content h3, .pg-content h4 {
    font-family: var(--pg-font-display); font-weight: 700; line-height: 1.25; scroll-margin-top: 4rem;
  }
  .pg-content h1 { font-size: 1.9rem; margin: 0.67em 0; }
  .pg-content h2 { font-size: 1.4rem; margin: 2rem 0 0.83em; }
  .pg-content h3 { font-size: 1.15rem; margin: 1.5rem 0 1em; }
  .pg-content h4 { margin: 1.33em 0; }
  .pg-content p, .pg-content ul, .pg-content ol { margin: 0.9rem 0; }
  .pg-content ul, .pg-content ol { padding-inline-start: 40px; }
  .pg-content ul { list-style: disc; }
  .pg-content ul ul { list-style: circle; }
  .pg-content ul ul ul { list-style: square; }
  .pg-content ol { list-style: decimal; }
  .pg-content img { max-width: 100%; }
  .pg-content blockquote {
    margin: 1.25rem 0; padding: 0 0 0 1rem;
    border-left: 3px solid var(--pg-line-strong); color: var(--pg-muted);
  }
  /* Wide content scrolls, it does not shrink -------------------------------------------------
   *
   * A block whose contents cannot reflow — a long code line, a diagram measured once at publish
   * time — is given its own scroll box rather than being squeezed into the column. `pre` has
   * worked this way from the start; figures join the same rule rather than growing a second
   * mechanism, because "wide thing, scrolls sideways" should read as one idea. */
  .pg-content pre { overflow: auto; overscroll-behavior-x: contain; }
  /*
   * A figure's scroll box is stated unscoped, on the same selector as the floor that makes it
   * necessary (`.kg-frame > svg`'s `min-width`, below). The two used to be written apart — the
   * floor everywhere, the scrolling only inside `.pg-content` — which meant a figure rendered
   * anywhere else got a drawing wider than its box and no way to reach the rest of it, and the
   * page scrolled sideways instead. A container that enforces a minimum has to be able to scroll
   * past it; keeping the pair together is what stops them drifting apart again.
   */
  .kg-frame { overflow: auto; overscroll-behavior-x: contain; }

  .pg-content pre {
    position: relative; background: var(--pg-code-bg);
    border: 1px solid var(--pg-line); border-radius: var(--pg-radius-lg); padding: 0.9rem 1rem;
  }
  .pg-content :not(pre) > code {
    background: var(--pg-code-bg); border-radius: var(--pg-radius); padding: 0.1rem 0.35rem;
  }

  /* Tables ---------------------------------------------------------------------------------- */
  /*
   * A table too wide for the phone scrolls inside itself, not by taking the page with it.
   *
   * A markdown table is sized by its content and has no way to say "wrap": the token table on the
   * theming page is 387px of unbreakable identifiers, which at a 390px viewport pushed the
   * document to 592px and left the whole article — prose, headings, figures — sliding sideways
   * under the reader. Every other wide thing on the page already owns its overflow (`.kg-frame`
   * scrolls, `pre` scrolls); the table was the one that did not.
   *
   * `display: block` is what makes `overflow-x` apply at all — it does not apply to a table box —
   * and `width: max-content` keeps the block from collapsing to the column and squeezing the
   * columns it is meant to be scrolling. A table that already fits is untouched: at a 1280px
   * viewport this one is 726px wide before and after, and scrolls nothing.
   */
  .pg-content table {
    border-collapse: collapse; margin: 1.25rem 0;
    display: block; width: max-content; max-width: 100%; overflow-x: auto;
    overscroll-behavior-x: contain;
  }
  .pg-content th, .pg-content td {
    border: 1px solid var(--pg-line); padding: 0.4rem 0.65rem; text-align: left;
  }
  .pg-content thead th { background: var(--pg-bg-sunken); border-bottom-color: var(--pg-line-strong); }

  /* Tabs -------------------------------------------------------------------------------------- */
  .pg-tabs { margin: 1rem 0; }
  .pg-tabs__list { display: flex; gap: 0; list-style: none; margin: 0; padding: 0; border-bottom: 1px solid var(--pg-line); }
  .pg-tabs [role="tab"] {
    background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer;
    padding: 0.5rem 0.9rem; color: var(--pg-muted); font: inherit; font-size: 0.9rem;
  }
  .pg-tabs [role="tab"][aria-selected="true"] { color: var(--pg-accent); border-bottom-color: var(--pg-accent); font-weight: 600; }
  .pg-tabs [role="tabpanel"][hidden] { display: none; }
  .pg-tabs [role="tabpanel"] { padding-top: 0.9rem; }

  /* Admonitions --------------------------------------------------------------------------------
   *
   * One block, three tokens, seven kinds. Each kind sets `--pg-adm-*` once and every rule below
   * reads those — so a host adds a kind, or retints one, without touching a selector, and the
   * editor's node view (`theme.css`) can point the same three at the same values and get the
   * same block.
   *
   * The kind is carried by a glyph and a label, not only by a hue: a 3px stripe in a colour is
   * indistinguishable at a skim and invisible to anyone who cannot separate the hues, which is
   * how `danger` and `note` came to look like the same block. */
  .pg-admonition {
    --pg-adm-hue: var(--pg-note);
    --pg-adm-surface: var(--pg-note-surface);
    --pg-adm-fg: var(--pg-note-fg);

    background: var(--pg-adm-surface);
    border-inline-start: 3px solid var(--pg-adm-hue);
    border-radius: 0 var(--pg-radius-lg) var(--pg-radius-lg) 0;
    padding: 0.9rem 1.1rem;
    margin: 1.5rem 0;
    color: var(--pg-fg);
  }
  .pg-admonition--tip { --pg-adm-hue: var(--pg-tip); --pg-adm-surface: var(--pg-tip-surface); --pg-adm-fg: var(--pg-tip-fg); }
  .pg-admonition--info { --pg-adm-hue: var(--pg-info); --pg-adm-surface: var(--pg-info-surface); --pg-adm-fg: var(--pg-info-fg); }
  .pg-admonition--warning { --pg-adm-hue: var(--pg-warning); --pg-adm-surface: var(--pg-warning-surface); --pg-adm-fg: var(--pg-warning-fg); }
  .pg-admonition--danger { --pg-adm-hue: var(--pg-danger); --pg-adm-surface: var(--pg-danger-surface); --pg-adm-fg: var(--pg-danger-fg); }
  .pg-admonition--example { --pg-adm-hue: var(--pg-example); --pg-adm-surface: var(--pg-example-surface); --pg-adm-fg: var(--pg-example-fg); }
  .pg-admonition--quote { --pg-adm-hue: var(--pg-quote); --pg-adm-surface: var(--pg-quote-surface); --pg-adm-fg: var(--pg-quote-fg); }

  .pg-admonition__title {
    display: flex; align-items: center; gap: 0.5rem;
    margin: 0 0 0.6rem; padding: 0;
    color: var(--pg-adm-fg); font-weight: 600; line-height: 1.3;
  }
  .pg-admonition--untitled .pg-admonition__title { margin-block-end: 0.35rem; }
  .pg-admonition__icon { flex: none; inline-size: 1.15em; block-size: 1.15em; color: var(--pg-adm-hue); }
  .pg-admonition__label { min-inline-size: 0; }

  /* The body's first and last blocks must not push the padding out to double. */
  .pg-admonition > :last-child { margin-block-end: 0; }
  .pg-admonition > .pg-admonition__title + * { margin-block-start: 0; }

  /* Collapsibles. The default disclosure triangle is replaced rather than hidden: a raw `▶` next
     to an icon and a label reads as a rendering accident, which is what `??? tip` looked like. */
  .pg-admonition--collapsible > summary { cursor: pointer; list-style: none; }
  .pg-admonition--collapsible > summary::-webkit-details-marker { display: none; }
  .pg-admonition--collapsible > summary::marker { content: ""; }
  .pg-admonition__chevron {
    flex: none; inline-size: 1.1em; block-size: 1.1em; margin-inline-start: auto;
    color: var(--pg-adm-hue); transition: transform 0.15s ease;
  }
  .pg-admonition--collapsible[open] > summary .pg-admonition__chevron { transform: rotate(90deg); }
  .pg-admonition--collapsible:not([open]) > summary { margin-block-end: 0; }
  @media (prefers-reduced-motion: reduce) {
    .pg-admonition__chevron { transition: none; }
  }

  /* Code-copy button ---------------------------------------------------------------------------- */
  .pg-copy {
    position: absolute; top: 0.4rem; right: 0.4rem; font-size: 0.75rem;
    background: var(--pg-bg); color: var(--pg-muted); border: 1px solid var(--pg-line);
    border-radius: var(--pg-radius); padding: 0.15rem 0.5rem; cursor: pointer;
  }
  .pg-copy:hover { color: var(--pg-fg); }

  /* Kineglyph figures ----------------------------------------------------------------------------
   *
   * A figure arrives as *inline* SVG, not an `<img>`. That is the difference that makes the rest of
   * this block possible: an image is a separate document, so no colour, no font and no accessible
   * name ever crossed into it. Inlined, the diagram is part of the page — it takes the host's
   * `--kg-color-*` (mapped from `--pg-*` in `tokens.css`), and its own `role="img"`/`<title>`/
   * `<desc>` reach a screen reader directly.
   *
   * `--kg-w`/`--kg-h` are the figure's natural size, written onto the `<figure>` at publish time.
   * The defaults here only keep the rules valid for a figure that has not been rendered yet. */
  figure.kg {
    --kg-w: 960; --kg-h: 540;
    /*
     * The narrowest the drawing may be painted: the point below which its type stops being
     * readable and the figure scrolls instead of shrinking.
     *
     * Stated once, because two things read it — the pre-rendered frame and the live stage — and
     * written out twice they could disagree. A disagreement here is a figure that changes size
     * the moment JavaScript arrives.
     */
    --kg-floor-w: calc(var(--kg-w) * var(--pg-figure-min-scale, 0.7) * 1px);
    display: flex; flex-direction: column;
    /*
     * More air above and below the figure than between the figure and its caption. That
     * difference is the whole of it: it makes the drawing and its caption read as one object
     * placed in the prose, rather than as two stacked blocks that happen to be adjacent.
     */
    gap: 0.5rem; margin-block: 2rem;
    /*
     * A diagram is wider than prose by nature.
     *
     * The reading measure is chosen for sentences — around 72 characters, because that is how far
     * an eye tracks comfortably. A figure is not read that way; it is looked at, and squeezing one
     * into the measure scales its type down with it. A 960-wide diagram in a 697px column renders
     * at 0.73, which turns 12px labels into 8.7px: above `--pg-figure-min-scale`, so nothing
     * complains, and still too small to read.
     *
     * `--pg-figure-max` is how wide a figure may get. It defaults to `100%` — the measure, exactly
     * today's layout — so a host that says nothing sees no change. A host that has room says so:
     *
     *     :root { --pg-figure-max: min(960px, calc(100vw - 4rem)); }
     *
     * The figure is centred on the column by an inline margin that goes negative as it grows,
     * which is what keeps the prose where it is: only the figure moves. The `100%` here is the
     * column's width, because that is this element's containing block.
     */
    width: var(--pg-figure-max, 100%);
    margin-inline: calc((100% - var(--pg-figure-max, 100%)) / 2);
  }
  /*
   * The frame is the container the variants are chosen against.
   *
   * `inline-size`, not a media query, because what decides which drawing fits is the width
   * available *to this figure* — and the same figure appears in a full-width article, in a narrow
   * reading column, on a docs page beside a table of contents, and in the editor's split preview
   * pane. Those are four different widths at one viewport width, so a media query gets three of
   * them wrong. The container is the frame rather than the figure so the answer is the width the
   * drawing actually gets, after `--pg-figure-max` has had its say.
   */
  .kg-frame { max-width: 100%; container-type: inline-size; container-name: kg-frame; }
  .kg-frame > svg {
    display: block; width: 100%; height: auto;
    /*
     * Re-stated here so it is a fraction of *this drawing's* width.
     *
     * `--kg-floor-w` is also computed on the `<figure>`, and a custom property inherits already
     * resolved — so a variant that sets its own `--kg-w` would still have been floored against the
     * widest drawing's, and the 320px drawing would have been held at 672px and scaled to 2.1.
     * Recomputing it where `--kg-w` is read makes the floor mean the same thing for every variant.
     * A figure with a single drawing has no `--kg-w` of its own here, inherits the figure's, and
     * lands on exactly the number it had before.
     */
    --kg-floor-w: calc(var(--kg-w) * var(--pg-figure-min-scale, 0.7) * 1px);
    /*
     * A diagram is laid out once, at a fixed width, and scaled to fit after that — so shrinking it
     * to a phone shrinks the type with it, and 16px lands at 6px. Below the point where the type
     * stops being readable the figure keeps its size and the frame scrolls instead.
     *
     * The floor is a *scale* of the figure's own width rather than a fixed width, so it means the
     * same thing to every figure: a 400-wide one never scrolls, a 960-wide one starts scrolling
     * below 672px.
     *
     * `max(100%, …)` and deliberately not `min(…)`: the drawing is as wide as the box it is in,
     * and never narrower than the floor. A `min()` would collapse to the frame's width and the
     * floor could never be crossed.
     *
     * With variants this is a backstop rather than the everyday case: a drawing measured for a
     * container this narrow is normally chosen before the floor is anywhere near. It still matters
     * below the narrowest variant — a 280px container has no drawing of its own — and `--kg-w` is
     * read off the drawing there, so the floor is a fraction of *its* width and not the widest's.
     */
    min-width: max(100%, var(--kg-floor-w));
  }
  /*
   * One figure, drawn at several widths, of which the page shows exactly one.
   *
   * Geometry has to be measured at publish time — SVG cannot wrap text, so a drawing cannot reflow
   * in the reader's browser the way a paragraph does. That is why a single drawing is wrong on a
   * phone: it can only be scaled, and scaling a 960px diagram into a 326px column takes 12px
   * labels to 4px. Drawing the figure more than once moves the choice to the only place it can be
   * made honestly, and leaves the page choosing between finished pictures.
   *
   * The rule is "the widest drawing that is not wider than the frame". The generated queries run
   * narrowest first and each one hides every variant and then shows its own, so at any frame width
   * the last query that matches has the final say — and that is the widest drawing that fits. A
   * drawing is therefore only ever scaled *up*, never down: a label can be larger than the size it
   * was measured at, never smaller, which is what makes the author's type size a floor rather than
   * a starting point. The narrowest variant's query is `min-width: 0`, so a container narrower than
   * anything that was drawn still gets the smallest drawing rather than nothing.
   *
   * Every selector involved is deliberately the same weight — one class, one element, one
   * attribute — so order decides and nothing has to out-specify anything. That is why `:not()`
   * and `:last-of-type` are absent from the generated half: either would quietly outrank the
   * plain selectors and pin one variant on.
   *
   * A browser without container queries drops all of it, which would leave every variant showing
   * at once. The `@supports not` rule below is the answer to exactly that: it hides all but the
   * first, and since the variants are inlined widest first, such a browser gets the widest drawing
   * alone — precisely what it was given before variants existed.
   */
  @supports not (container-type: inline-size) {
    .kg-frame > svg[data-kg-variant]:not(:first-of-type) { display: none; }
  }
  /*
   * The per-width half of that rule is *generated*, not written here, and inlined with the page
   * that needs it — see `variantStyle` in `@pagina/core`'s `figures.ts`.
   *
   * The widths are the article's (`kineglyph.widths`), so a stylesheet that named them would be a
   * stylesheet that silently disagreed with any article that chose its own: no rule would match,
   * every frame would fall through to the base rule above, and every figure on a wide screen would
   * quietly show its phone drawing. Emitting the thresholds beside the drawings they select keeps
   * the two impossible to get out of step, and costs a few hundred bytes on a page with figures.
   */
  /*
   * The live stage holds back exactly the space the pre-rendered frame was occupying, so swapping
   * one for the other does not move the page under the reader.
   *
   * This used to carry an `8rem` floor, for chrome the runtime added that the frame had none of.
   * A figure in prose has no chrome now (see `client/figure-chrome.ts`), so the honest reservation
   * is the drawing's own height and nothing more — the floor would only have made a short, wide
   * figure grow on hydration in the other direction.
   *
   * Two halves, because the drawing has two states. `aspect-ratio` reserves the height the
   * drawing has when it fits — off the stage's *own* width, so it follows a figure that has
   * broken out of the column without needing to be told how wide that made it. `min-height`
   * reserves the other state: below the legibility floor the drawing keeps its size and the stage
   * scrolls, so its height stops following the stage's width and settles at the floor's.
   *
   * Both are scoped to `:empty`, which is the only state a reservation can be about: a stage with
   * a figure in it should be as tall as that figure and nothing else. Left unscoped, the
   * `aspect-ratio` caps the height of a *mounted* figure and silently clips the readout and
   * transport off an opted-in one — which is exactly what happened the first time this was tried.
   *
   * `mountAll` appends the stage last, after any caption the author wrote, so the caption is
   * ordered back to the bottom rather than the markup being fought over.
   */
  figure.kg [data-kg-stage]:empty {
    aspect-ratio: var(--kg-w) / var(--kg-h);
    min-height: calc(var(--kg-floor-w) * var(--kg-h) / var(--kg-w));
  }
  /*
   * The live drawing keeps the frame's floor as well as its geometry.
   *
   * A quiet figure is mounted at its natural width (see `client/figure-chrome.ts`), so the live
   * SVG is the same picture as the pre-rendered one and this is the same `min-width` the frame
   * carries — the two scale and scroll identically, and swapping one for the other is invisible.
   *
   * Scoped to a chromeless figure because an opted-in one is deliberately responsive: it re-lays
   * out to the column, so a floor written for a 960-wide layout would make it scroll for nothing.
   */
  figure.kg:not([data-kg-variants])
    .kg-figure:not(:has(.kg-figure__controls, .kg-figure__readout, .kg-figure__machine))
    svg {
    min-width: max(100%, var(--kg-floor-w));
  }
  figure.kg > figcaption {
    order: 1;
    /*
     * A caption, not a short paragraph: subordinate in size and colour, tighter than body text,
     * and held to a narrower measure so it reads as an aside to the picture even when the figure
     * itself is wider than the prose. `pretty` keeps a one-word last line off the end of it.
     */
    max-width: 60ch;
    /*
     * The picture may leave the column; the caption is prose and stays in it.
     *
     * This is the figure's overhang on one side: `100%` is the figure's own width, `100cqw` is the
     * reading column's (see `container-type` on `.pg-content`), so the caption starts exactly
     * where the paragraph above it does. Zero, and therefore invisible, for a figure that did not
     * break out.
     *
     * The column is measured rather than derived from `--pg-measure`, because the token is `72ch`
     * and `ch` is font-relative: read on this caption it is a different number than on a
     * paragraph, and on a host that caps the column in a different size it is a different number
     * again. The one thing that is never wrong is the width the column actually has.
     */
    margin-inline-start: max(0px, calc((100% - 100cqw) / 2));
    color: var(--pg-muted);
    font-size: 0.8125rem; line-height: 1.55;
    text-wrap: pretty;
  }
  /* Still supported: a figure authored as a plain image, and the pre-inlining `<picture>`. */
  picture.kg-static img { width: 100%; height: auto; display: block; }

  /* Shiki dual-theme hookup: shiki emits inline `color`/`background-color` plus
     `--shiki-dark`/`--shiki-dark-bg` custom properties (via `defaultColor: false`); swap to the
     dark variables under our own dark theme. The `!important` is not a preference: shiki writes
     its colours as an inline `style`, which no ordinary rule can outrank. `--pg-shiki-bg` gives
     a host the same handle on the light surface (the dark rule below is more specific, so it
     still wins under `[data-theme="dark"]`). */
  .shiki { background-color: var(--pg-shiki-bg) !important; }
  /* `@media screen` for the reason the dark palette in `tokens.css` carries it: on paper the page is
     the light theme, and shiki's dark ink would be the one thing that did not follow it. */
  @media screen {
    [data-theme="dark"] .shiki,
    [data-theme="dark"] .shiki span {
      color: var(--shiki-dark) !important;
      background-color: var(--shiki-dark-bg) !important;
    }
  }

  /* Print: the content column on paper ------------------------------------------------------------
   *
   * There is no `pagina pdf`, and this is why there does not need to be one. A docs page is already
   * a single column of prose with figures in it; what stood between it and a good PDF was not a
   * renderer but four specific defects, and they are what this block fixes:
   *
   *   1. **Things split across the page break.** A code block, a table, an admonition or a figure
   *      cut in half by a page boundary is the difference between a printout and a deliberate
   *      document. `break-inside: avoid` on each, and `break-after: avoid` on headings so a section
   *      never opens at the foot of a page with its first line overleaf.
   *   2. **Anything that scrolls, doesn't.** `overflow: auto` is a screen affordance; on paper it
   *      is a crop. Long code lines wrap here instead, and a figure that would have scrolled its
   *      frame is allowed to shrink to the page (`min-width` is what stopped it — see `.kg-frame`).
   *   3. **Links become dead text.** A URL a reader cannot click and cannot see is worth printing
   *      once, after the words. Only absolute ones: `#anchor` and `/guide/` resolve against a page
   *      the paper does not carry, so printing them is noise, and neither is printed.
   *   4. **Controls printed as furniture.** The copy button is script; on paper it is a grey word
   *      sitting in the corner of every listing.
   *
   * The chrome half of this — header, sidebar, TOC rail, pager, the search trigger — is in
   * `pagina.css` and `search.css`, where those elements are styled. A host at `theme: "tokens"`
   * gets neither, which is right: it prints its own page.
   *
   * What is deliberately *not* here: tabs are not expanded and collapsed admonitions are not
   * opened. What the reader chose on screen is what they asked to print, and a page that quietly
   * prints three copies of a tabbed example is a page that lied about what it was showing. */
  @media print {
    /* The measure is a reading aid for a screen the reader controls the width of. A print page is
       already 16-18cm of text; holding it to 34rem would leave a third of the sheet blank. */
    .pg-content { max-width: none; }
    .pg-content p { orphans: 3; widows: 3; }
    .pg-content h1, .pg-content h2, .pg-content h3, .pg-content h4 {
      break-after: avoid; break-inside: avoid;
    }
    .pg-content pre,
    .pg-content table,
    .pg-content blockquote,
    .pg-content figure,
    .pg-admonition,
    figure.kg,
    .kg-frame { break-inside: avoid; }
    /* A table longer than a page has to break somewhere; break it between rows, and repeat the
       header on the sheet that follows. `break-inside: avoid` above is a preference, not a law:
       a browser overrides it rather than leave a page empty. */
    .pg-content tr, .pg-content img { break-inside: avoid; }
    .pg-content thead { display: table-header-group; }

    /* Scrollers become wrappers. `overflow: visible` alone would clip; the wrap is what keeps a
       120-column line on the sheet. `border` because the sunken background will not print. */
    .pg-content pre {
      overflow: visible;
      white-space: pre-wrap;
      word-break: break-word;
      border: 1px solid var(--pg-line-strong);
    }
    .pg-copy { display: none; }

    /* Figures. `min-width` is the rule that makes a wide drawing scroll its frame instead of
       shrinking (see `.kg-frame > svg`), and a frame that scrolls on paper is a drawing with its
       right-hand side cut off — so on paper the drawing shrinks. `max-height` keeps a tall one
       from claiming a sheet of its own. */
    .kg-frame { overflow: visible; }
    .kg-frame > svg { min-width: 0; max-width: 100%; height: auto; max-height: 20cm; }
    figure.kg { width: 100%; margin-inline: 0; }

    /* A URL, once, after the words that linked to it — and only when it is one a reader could
       type back in. Never inside a code listing, where the text is the point. */
    .pg-content a[href^="http"]::after {
      content: " <" attr(href) ">";
      font-size: 0.85em;
      word-break: break-all;
      font-family: var(--pg-font-mono);
    }
    .pg-content pre a[href^="http"]::after,
    .pg-content code a[href^="http"]::after { content: none; }
  }
}

/**
 * The search trigger and the search dialog.
 *
 * Kept in its own file for the reason the other two are: `build-css.mjs` inlines it into
 * `pagina.css`, and a host that wants only the reading column can still read this one to see what
 * search would have cost it.
 *
 * There is not one literal colour here. Every surface, rule and piece of ink is a `--pg-*` token,
 * so a host that has retinted pagina has retinted the dialog too, without knowing it exists. The
 * one exception is the backdrop's black, which is a *scrim* rather than a colour — it darkens
 * whatever is behind it, in either theme, and a token for it would be a token every host has to
 * set to get the default.
 */
@layer pagina.chrome {
  /* Trigger ------------------------------------------------------------------------------------
     Server-rendered `disabled`, and enabled by the client. With scripting off it stays visibly
     inert and says why on hover, which is the honest thing to show for a feature that needs JS. */
  .pg-search-trigger {
    display: inline-flex; align-items: center; gap: 0.5rem;
    margin-left: auto; margin-right: 0.75rem;
    padding: 0.3rem 0.5rem 0.3rem 0.65rem;
    border: 1px solid var(--pg-line); border-radius: var(--pg-radius);
    background: var(--pg-bg-raised); color: var(--pg-muted);
    font: inherit; font-size: 0.8rem; cursor: pointer;
  }
  .pg-search-trigger:hover:not(:disabled) { border-color: var(--pg-line-strong); color: var(--pg-fg); }
  .pg-search-trigger:disabled { cursor: not-allowed; opacity: 0.55; }
  .pg-search-trigger kbd {
    font-family: var(--pg-font-mono); font-size: 0.7rem; line-height: 1;
    padding: 0.15rem 0.3rem; border: 1px solid var(--pg-line); border-radius: 3px;
    background: var(--pg-bg); color: var(--pg-muted);
    white-space: nowrap;
  }
  /* The two keys sit closer to each other than to the label: they are alternatives to one another,
     not two more items in a row. `Ctrl K` is the widest thing the combo key can say and the client
     may shorten it to `⌘K` after paint, so the width is reserved up front — a header control that
     resizes a beat after the page settles reads as a layout bug. */
  .pg-search-trigger__key + .pg-search-trigger__key { margin-left: -0.25rem; }
  .pg-search-trigger__key--combo { min-width: 2.6em; text-align: center; }

  /* Narrowing, in the order the hints stop earning their room.

     Under 760px the `/` goes: two keys and a label is more than the header row has, and the combo
     is the one a reader could not have guessed — which is the entire point of printing it.

     Under 560px both keys go and the *label* stays. A viewport that narrow is a touch device with
     no keyboard to press either shortcut on, so the hints are decoration; the word is what the
     control is. (It used to be the other way round, which left a phone with an empty box: with the
     keys hidden too, nothing was rendered inside the button at all.) */
  @media (max-width: 760px) {
    .pg-search-trigger__key:not(.pg-search-trigger__key--combo) { display: none; }
  }
  @media (max-width: 560px) {
    .pg-search-trigger { padding: 0.3rem 0.6rem; }
    .pg-search-trigger kbd { display: none; }
  }

  /* A control that opens a dialog, and a dialog, on a sheet of paper. Both are hidden rather than
     left to the chance that neither happened to be showing when ⌘P was pressed — a reader can
     print with the search dialog open, and `position: fixed` would put it over the first page. */
  @media print {
    .pg-search-trigger, .pg-search { display: none !important; }
  }

  /* Dialog -------------------------------------------------------------------------------------- */
  .pg-search[hidden] { display: none; }
  .pg-search {
    position: fixed; inset: 0; z-index: 100;
    display: flex; justify-content: center;
    padding: 1rem;
    background: rgb(0 0 0 / 45%);
  }
  /* The page behind an overlay that owns the viewport must not scroll under it. */
  html.pg-search-open, html.pg-search-open body { overflow: hidden; }

  .pg-search__dialog {
    display: flex; flex-direction: column;
    width: min(38rem, 100%); max-height: min(34rem, 100%);
    margin-top: 10vh;
    background: var(--pg-bg); color: var(--pg-fg);
    border: 1px solid var(--pg-line-strong); border-radius: var(--pg-radius-lg);
    box-shadow: 0 1.5rem 3rem rgb(0 0 0 / 25%);
    overflow: hidden;
  }
  /* A phone gets the whole screen: a 38rem card centred in a 390px viewport is a card with
     margins it cannot afford, and a keyboard covering half of what is left. */
  @media (max-width: 560px) {
    .pg-search { padding: 0; }
    .pg-search__dialog { margin-top: 0; width: 100%; max-height: 100%; height: 100%; border: 0; border-radius: 0; }
  }

  /* The dialog is labelled by this; it is not drawn, because the input beside it says the same. */
  .pg-search__label {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }

  .pg-search__bar {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.65rem 0.75rem; border-bottom: 1px solid var(--pg-line);
  }
  .pg-search__input {
    flex: 1; min-width: 0;
    border: 0; background: none; color: var(--pg-fg);
    font: inherit; font-size: 1rem; padding: 0.2rem 0;
  }
  .pg-search__input:focus { outline: none; }
  .pg-search__input::placeholder { color: var(--pg-muted); }
  .pg-search__close {
    border: 1px solid var(--pg-line); border-radius: var(--pg-radius);
    background: var(--pg-bg-raised); color: var(--pg-muted);
    font: inherit; font-size: 0.7rem; font-family: var(--pg-font-mono);
    padding: 0.2rem 0.4rem; cursor: pointer;
  }
  .pg-search__close:hover { color: var(--pg-fg); }

  .pg-search__results { list-style: none; margin: 0; padding: 0.4rem; overflow-y: auto; flex: 1; }
  .pg-search__hit + .pg-search__hit { margin-top: 0.15rem; }
  .pg-search__link {
    display: block; padding: 0.5rem 0.65rem;
    border-radius: var(--pg-radius); text-decoration: none; color: inherit;
  }
  .pg-search__hit[aria-selected="true"] .pg-search__link { background: var(--pg-bg-sunken); }
  .pg-search__hit[aria-selected="true"] { outline: 2px solid var(--pg-accent); outline-offset: -2px; border-radius: var(--pg-radius); }
  .pg-search__kicker {
    display: block; font-size: 0.7rem; color: var(--pg-muted);
    text-transform: uppercase; letter-spacing: 0.04em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .pg-search__title { display: block; font-weight: 600; font-family: var(--pg-font-display); }
  .pg-search__snippet {
    font-size: 0.85rem; color: var(--pg-muted); margin-top: 0.15rem;
    /* Two lines of context is enough to choose by, and keeps ten results on one screen. */
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
    overflow: hidden;
  }
  .pg-search__tag {
    font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em;
    border: 1px solid var(--pg-line); border-radius: 3px; padding: 0 0.25rem;
    color: var(--pg-accent);
  }
  .pg-search mark { background: none; color: var(--pg-accent); font-weight: 600; }

  .pg-search__state { margin: 0; padding: 1.25rem 1.4rem; color: var(--pg-muted); font-size: 0.9rem; }
  .pg-search__state[hidden] { display: none; }
  .pg-search__state--error { color: var(--pg-danger-fg); }
  .pg-search__retry {
    border: 1px solid var(--pg-line); border-radius: var(--pg-radius);
    background: var(--pg-bg-raised); color: var(--pg-fg);
    font: inherit; font-size: 0.8rem; padding: 0.2rem 0.5rem; cursor: pointer;
  }

  .pg-search__foot {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
    padding: 0.45rem 0.75rem; border-top: 1px solid var(--pg-line);
    background: var(--pg-bg-raised); color: var(--pg-muted); font-size: 0.7rem;
  }
  .pg-search__keys { display: flex; gap: 0.75rem; }
  .pg-search__keys kbd {
    font-family: var(--pg-font-mono);
    padding: 0.05rem 0.25rem; border: 1px solid var(--pg-line); border-radius: 3px;
    background: var(--pg-bg); margin-right: 0.2rem;
  }
  @media (max-width: 440px) {
    /* Three key hints and a count do not fit beside each other at 390px. The count is the one a
       reader is actually reading, so the hints go. */
    .pg-search__keys { display: none; }
  }
}

@layer pagina.chrome {
  /* Header ----------------------------------------------------------------------------------- */
  /* The brand on one side, everything that acts on the other.
   *
   * `space-between` across four loose children was the bug: with a title, an "Edit this page" link,
   * a search trigger and the toggle in one flex row, the free space is divided into three gaps that
   * shrink to nothing before anything wraps — so on a narrow header the link sat hard against the
   * site title with no space at all between them. Grouping the actions leaves exactly one elastic
   * gap, and every child has a real `gap` under it, so nothing can touch. */
  .pg-header {
    display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem 1rem;
    padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--pg-line);
    position: sticky; top: 0; background: var(--pg-bg); z-index: 10;
  }
  .pg-header__title { font-weight: 600; text-decoration: none; color: var(--pg-fg); font-family: var(--pg-font-display); margin-inline-end: auto; }
  .pg-header__actions { display: flex; align-items: center; gap: 0.75rem; }
  .pg-header__edit { color: var(--pg-muted); text-decoration: none; font-size: 0.85rem; white-space: nowrap; }
  .pg-header__edit:hover { color: var(--pg-accent); }

  .pg-nav-trigger {
    display: none; align-items: center; gap: 0.4rem; min-height: 2rem;
    border: 1px solid var(--pg-line); border-radius: var(--pg-radius);
    padding: 0.3rem 0.65rem; color: var(--pg-fg); background: var(--pg-bg-raised);
    font: inherit; font-size: 0.82rem; cursor: pointer;
  }
  .pg-nav-trigger:hover, .pg-nav-trigger:focus-visible { border-color: var(--pg-accent); color: var(--pg-accent); }
  .pg-nav-trigger:disabled { display: none; }

  /* Theme toggle ------------------------------------------------------------------------------ */
  .pg-theme-toggle {
    width: 34px; height: 18px; border-radius: 999px; border: 1px solid var(--pg-line);
    background: var(--pg-bg-raised); padding: 2px; cursor: pointer; display: inline-flex;
  }
  .pg-theme-toggle__thumb {
    width: 12px; height: 12px; border-radius: 50%; background: var(--pg-accent);
    transition: transform 150ms ease; transform: translateX(0);
  }
  [data-theme="dark"] .pg-theme-toggle__thumb { transform: translateX(16px); }

  /* Shell grid: nav | content | toc, collapsing to one column under 960px -------------------- */
  .pg-shell {
    display: grid; grid-template-columns: 240px minmax(0, 1fr) 220px;
    gap: 0; max-width: 1400px; margin: 0 auto;
  }
  /* A blog's `nav` is a list of standalone pages, and most blogs have none, so the shell omits the
     rail rather than rendering an empty one. The grid closes over it and the whole thing narrows:
     a two-column layout still centred on a 1400px track would leave the prose sitting off to one
     side of a wide window, which is worse than the empty column it replaced. */
  .pg-shell--no-nav { grid-template-columns: minmax(0, 1fr) 220px; max-width: 1120px; }
  @media (max-width: 960px) {
    .pg-shell { grid-template-columns: 1fr; }
    .pg-shell--no-nav { grid-template-columns: 1fr; }
    .pg-shell > .pg-nav, .pg-toc { display: none; }
    .pg-nav-trigger { display: inline-flex; }
  }

  .pg-nav { padding: 1.5rem 1rem; border-right: 1px solid var(--pg-line); }
  .pg-nav__list { list-style: none; margin: 0; padding: 0; }
  .pg-nav__list--0 { margin-bottom: 0.5rem; }
  .pg-nav__list--1 { padding-left: 0.75rem; }
  .pg-nav__section { margin-top: 0.75rem; }
  .pg-nav__label { display: block; font-weight: 600; font-size: 0.8rem; color: var(--pg-muted); text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 0.25rem; }
  .pg-nav__link { display: block; padding: 0.2rem 0; color: var(--pg-fg); text-decoration: none; }
  .pg-nav__link:hover { color: var(--pg-accent); }
  .pg-nav__link[aria-current="page"] { color: var(--pg-accent); font-weight: 600; }

  /* Mobile page picker ---------------------------------------------------------------------- */
  .pg-nav-modal[hidden] { display: none; }
  .pg-nav-modal {
    position: fixed; inset: 0; z-index: 40; display: flex; align-items: stretch;
    background: color-mix(in srgb, var(--pg-fg) 34%, transparent);
  }
  .pg-nav-modal__panel {
    width: min(88vw, 22rem); height: 100%; overflow: auto;
    background: var(--pg-bg); border-right: 1px solid var(--pg-line-strong);
    box-shadow: 0.75rem 0 2rem color-mix(in srgb, var(--pg-fg) 16%, transparent);
  }
  .pg-nav-modal__header {
    position: sticky; top: 0; z-index: 1; display: flex; align-items: center;
    justify-content: space-between; gap: 1rem; padding: 0.85rem 1rem;
    background: var(--pg-bg); border-bottom: 1px solid var(--pg-line);
  }
  .pg-nav-modal__header h2 { margin: 0; color: var(--pg-fg); font: 600 1rem/1.3 var(--pg-font-display); }
  .pg-nav-modal__close {
    border: 1px solid var(--pg-line); border-radius: var(--pg-radius); padding: 0.3rem 0.55rem;
    color: var(--pg-muted); background: var(--pg-bg-raised); font: inherit; cursor: pointer;
  }
  .pg-nav-modal__close:hover, .pg-nav-modal__close:focus-visible { color: var(--pg-accent); border-color: var(--pg-accent); }
  .pg-nav--modal { display: block; border: 0; padding: 1rem; }
  html[data-pg-nav-open] { overflow: hidden; }
  @media (min-width: 961px) { .pg-nav-modal { display: none; } }

  .pg-main { min-width: 0; padding: 1.5rem 2rem; }

  .pg-crumbs { font-size: 0.85rem; color: var(--pg-muted); margin-bottom: 1rem; }
  .pg-crumbs a { color: var(--pg-muted); text-decoration: none; }
  .pg-crumbs a:hover { color: var(--pg-accent); }
  .pg-crumbs__sep { margin: 0 0.35rem; }

  /* The cover ---------------------------------------------------------------------------------
   *
   * A band across the whole page, above the sidebar and the content column — the shell grid starts
   * *below* it. That is the difference between a cover and an illustration that happens to be
   * first: boxed inside the measure it was neither, and it is what the shell emits it outside
   * `.pg-shell` for. Three tokens make it a host's:
   *
   *   --pg-cover-ratio     the band's shape, and the reflow guard while the image loads
   *   --pg-cover-max       how tall it may get on a wide window, so a hero cannot eat the fold
   *   --pg-cover-position  which part of a cropped photograph survives (`object-position`)
   *
   * **`contain` is the default fit.** pagina copies the cover without decoding it, so it cannot
   * tell a photograph from a wordmark — and cropping a wordmark cuts the first letters off the
   * name, which is the failure this replaced. Letterboxing a photograph is merely less pretty.
   * `cover_fit: cover` is the author saying "this one is a photograph, fill the band".
   *
   * Under `contain` the image is padded off the edges, because a wordmark run edge to edge reads as
   * a mistake, and given a ground of its own so the letterboxing is deliberate rather than a gap.
   */
  .pg-cover { margin: 0; }
  .pg-cover__img {
    display: block; width: 100%;
    aspect-ratio: var(--pg-cover-ratio, 3 / 1);
    max-height: var(--pg-cover-max, 22rem);
    object-fit: cover; object-position: var(--pg-cover-position, center);
    border-bottom: 1px solid var(--pg-line);
    background: var(--pg-bg-raised);
  }
  .pg-cover--contain .pg-cover__img {
    object-fit: contain;
    padding: clamp(0.75rem, 3vw, 2rem);
  }

  /* Article header ---------------------------------------------------------------------------------
   * The title and the line of provenance, above the content and below the breadcrumbs. It renders
   * on the article's landing page only, unless `cover_on` says otherwise — a cover belongs to the
   * article, not to each of its pages. See `docs/theming.md`.
   *
   * It stays inside the reading column while the cover above it is full-bleed, which is the point:
   * the picture is the width of the page, the words under it are the width of the prose they
   * introduce, and the two lines up with the paragraphs rather than with the window.
   *
   * The title element is the page's own `h1`, *moved* here by the template rather than reprinted,
   * so it is outside `.pg-content` and the reading layer's heading rules no longer reach it. That
   * is why the whole declaration is restated below: under a host reset (Tailwind's preflight sets
   * `font-size: inherit; font-weight: inherit; margin: 0` on every heading) anything left unsaid
   * arrives flattened, which is exactly the class of bug `host-theming.spec.ts` exists for.
   *
   * A host that brings its own hero drops `.pg-cover` and `.pg-article-header` and loses nothing
   * else.
   */
  .pg-article-header { margin: 0 0 2rem; max-width: var(--pg-measure); }
  .pg-article-header h1 {
    font-family: var(--pg-font-display); font-size: 2.1rem; font-weight: 700;
    line-height: 1.2; letter-spacing: -0.01em; color: var(--pg-fg); margin: 0;
  }
  /* The meta row: date · author · reading time, each independently optional. The separator is a
   * real element rather than a `::before`, so a row missing its first item cannot open with one. */
  .pg-article-meta {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem;
    margin: 0.6rem 0 0; font-size: 0.85rem; color: var(--pg-muted);
  }
  .pg-article-meta__sep { color: var(--pg-line-strong); }

  /* The blog index -----------------------------------------------------------------------------
   *
   * The archive `form: blog` appends to `index.md`, and the surface where a blog either reads like
   * a blog or reads like a docs page with dates on it. Three decisions do most of that work:
   *
   *   - **The date goes above the title**, not under it. On a blog the date is how a reader decides
   *     whether they have read this one; putting it below makes them read the headline first to
   *     find out they have. `order` moves it without changing the document, so the heading is still
   *     the entry's first element for anything reading structure rather than layout.
   *   - **A rule between entries, not a card around each.** A column of bordered boxes is a
   *     dashboard; a hairline is a contents page, which is what this is.
   *   - **It keeps the measure.** The entries are as wide as the prose on a post, so moving between
   *     the index and a post does not move the text.
   *
   * These rules live in the chrome layer with the rest of pagina's own furniture, so they win over
   * the reading layer's `ol`/`h2`/`a` styling — the list is generated navigation that happens to sit
   * inside the content column, not something an author wrote.
   */
  .pg-posts { list-style: none; margin: 2.5rem 0 0; padding: 0; }
  .pg-post {
    display: grid; gap: 0.4rem; max-width: var(--pg-measure);
    padding: 1.75rem 0; border-top: 1px solid var(--pg-line);
  }
  .pg-post:first-child { border-top: 0; padding-top: 0; }
  .pg-post__cover { display: block; order: -2; margin-bottom: 0.5rem; }
  .pg-post__cover img {
    display: block; width: 100%;
    aspect-ratio: var(--pg-post-cover-ratio, 2 / 1);
    max-height: var(--pg-post-cover-max, 18rem);
    object-fit: cover; border-radius: var(--pg-radius);
    background: var(--pg-bg-raised); border: 1px solid var(--pg-line);
  }
  .pg-post__meta {
    order: -1; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem;
    margin: 0; font-size: 0.8rem; color: var(--pg-muted);
  }
  .pg-post__sep { color: var(--pg-line-strong); }
  .pg-post__title {
    margin: 0; font-family: var(--pg-font-display); font-size: 1.5rem; font-weight: 700;
    line-height: 1.25; letter-spacing: -0.01em;
  }
  .pg-post__title a { color: var(--pg-fg); text-decoration: none; }
  .pg-post__title a:hover, .pg-post__title a:focus-visible { color: var(--pg-accent); }
  .pg-post__summary { margin: 0; color: var(--pg-muted); line-height: 1.6; }
  .pg-post__tags { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.2rem 0 0; }
  .pg-post__tag {
    font-size: 0.72rem; color: var(--pg-muted);
    border: 1px solid var(--pg-line); border-radius: 999px; padding: 0.1rem 0.55rem;
  }
  .pg-posts__empty { color: var(--pg-muted); }
  /* The one visible way to follow the blog, under the archive on the front page. */
  .pg-subscribe { margin: 2rem 0 0; max-width: var(--pg-measure); font-size: 0.85rem; }
  .pg-subscribe a { color: var(--pg-muted); text-decoration: none; border-bottom: 1px solid var(--pg-line-strong); }
  .pg-subscribe a:hover, .pg-subscribe a:focus-visible { color: var(--pg-accent); border-color: var(--pg-accent); }

  /* Pager ------------------------------------------------------------------------------------------ */
  .pg-pager { display: flex; justify-content: space-between; gap: 1rem; margin-top: 2.5rem; }
  .pg-pager__link {
    flex: 1; display: block; border: 1px solid var(--pg-line); border-radius: var(--pg-radius);
    padding: 0.6rem 0.9rem; text-decoration: none; color: var(--pg-fg);
  }
  .pg-pager__link span { display: block; font-size: 0.75rem; color: var(--pg-muted); }
  .pg-pager__link--next { text-align: right; }

  /* Table of contents (sticky) ---------------------------------------------------------------------- */
  .pg-toc { padding: 1.5rem 1rem; position: sticky; top: 3.5rem; align-self: start; max-height: calc(100vh - 4rem); overflow: auto; }
  .pg-toc__label { font-weight: 600; font-size: 0.8rem; color: var(--pg-muted); text-transform: uppercase; letter-spacing: 0.03em; margin: 0 0 0.5rem; }
  .pg-toc ul { list-style: none; margin: 0; padding: 0; }
  .pg-toc__item a { display: block; padding: 0.15rem 0; font-size: 0.85rem; color: var(--pg-muted); text-decoration: none; }
  .pg-toc__item a:hover { color: var(--pg-accent); }
  .pg-toc__item--3 { padding-left: 0.75rem; }

  /* Print: the chrome, on paper --------------------------------------------------------------------
   *
   * Everything in this layer is a way of *moving around* a site — the brand row, the sidebar, the
   * TOC rail, the breadcrumbs, the pager, the theme toggle. None of it can be used on paper, and
   * every one of them costs a column or a strip of the sheet, so the printed page is the article
   * and nothing else. The content column's own print rules are in `reading.css`, and the search
   * trigger's in `search.css`, each next to the rules they override.
   *
   * `@page` is inside the layer with everything else in this file, so a host that wants different
   * margins writes an unlayered `@page` and wins, the way it does for every other rule here. */
  @media print {
    @page { margin: 18mm 16mm; }

    .pg-header, .pg-nav, .pg-nav-modal, .pg-toc, .pg-pager, .pg-crumbs { display: none; }

    /* The three-column grid becomes the one column that is left. `position: sticky` is measured
       against a viewport, and a sheet of paper is not one — left in place it pins the element to
       the first page and leaves a hole on the rest. */
    .pg-shell { display: block; max-width: none; margin: 0; }
    .pg-main { padding: 0; }

    /* The cover is the author's picture, not chrome, so it prints — but a hero sized for a screen
       will otherwise take most of the first sheet before a word of the article. */
    .pg-cover__img { max-height: 7cm; }
    .pg-article-header { break-inside: avoid; break-after: avoid; }
  }
}
