/* main.css — "split site"
 *
 * Two surfaces, deliberately different:
 *   .surface-term  homepage / about / 404 — full terminal chrome
 *   .surface-read  article pages          — no chrome at all
 * The only thing crossing between them is one mono breadcrumb line.
 *
 * Colors come from assets/css/interdot-theme.css (generated). Every var()
 * here carries a literal fallback so the site stays readable if that file
 * is ever missing — CSS fails silently and this is the only defence. The
 * fallback is always the LIGHT value copied verbatim from the generated file;
 * a fallback that has drifted from it still parses, so it voids the property
 * it claims to protect without ever failing loudly.
 */

/* ============================================================
   FONT — Maple Mono, self-hosted (SIL OFL 1.1)
   ============================================================ */
@font-face {
  font-family: "Maple Mono";
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url("/assets/fonts/MapleMono-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "Maple Mono";
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  src: url("/assets/fonts/MapleMono-Bold.woff2") format("woff2");
}
@font-face {
  font-family: "Maple Mono";
  font-weight: 400;
  font-style: italic;
  font-display: swap;
  src: url("/assets/fonts/MapleMono-Italic.woff2") format("woff2");
}

/* ============================================================
   SCALE
   ============================================================ */
:root {
  --font: "Maple Mono", "JetBrains Mono", "Monaspace Argon", ui-monospace, monospace;

  /* Body is monospace, so it needs more leading and a wider measure than a
   * proportional face would: mono runs ~15-20% wider per character, so 72ch
   * of mono reads like ~62ch of prose. */
  --fs: 1.0625rem;   /* 17px */
  --lh: 1.7;
  --measure: 72ch;
  --measure-wide: 92ch;

  --fs-xs: 0.75rem;
  --fs-sm: 0.8125rem;
  --fs-md: 0.9375rem;
  --fs-lg: 1.25rem;
  --fs-xl: 1.75rem;

  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2.25rem;
  --s7: 3.5rem;
  --s8: 5.5rem;

  /* The height the sticky bar OCCUPIES, and the only number anchor offsets are
   * allowed to be computed from — every heading in .prose does
   * `calc(var(--bar-h) + 1.5rem)`. One tmux status row
   * at rest; the narrow branch under the bar wraps it and raises this to match,
   * because a bar that grew without it would hide the very heading it had just
   * scrolled to.
   *
   * The height of ONE row is a separate quantity once the bar wraps, and it is
   * deliberately NOT declared here: the bar reads `var(--bar-row, var(--bar-h))`
   * so that at rest — and in card.css, which overrides --bar-h to 46px and
   * takes .tmux across unedited — a row simply IS the bar, exactly as before.
   * --bar-row appears only where the two stop being the same number. */
  --bar-h: 1.75rem;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--id-bg, #fbf1c7);
  color: var(--id-fg, #654735);
  font-family: var(--font);
  font-size: var(--fs);
  line-height: var(--lh);
  font-variant-ligatures: none;
  /* Slashed zero, site-wide. Maple Mono ships `zero`, and both of the author's
   * terminals already switch it on for the whole grid (kitty
   * features='+calt +zero', ghostty font-feature = calt,zero) — so a global
   * declaration is the faithful scope, not a scoped one. It is also the only
   * scope that stays coherent: 0x405000 must not carry a slash inside a <code>
   * and lose it three words later in the same sentence, or in a heading.
   * Independent of font-variant-ligatures above — that property governs
   * liga/clig/dlig/calt and never touches `zero`. */
  font-feature-settings: "zero" 1;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: var(--id-info, #436d77); }
a:hover { text-decoration-thickness: 2px; }

:focus-visible {
  outline: 2px solid var(--id-info, #436d77);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Selection is the terminal's own: mode-style and menu-selected-style are both
 * fg=selected.fg,bg=selected.bg,bold — orange on panel_inset. Never green.
 * Green has exactly one job in the whole config and it is the success caret.
 *
 * Measured: --id-select-bg on --id-bg is 1.16:1 (1.07:1 on --id-bg-deep), so
 * the fill is all but invisible and the hue change carries the signal on its
 * own — which is equally true in the author's tmux, where menu-style is
 * ALREADY bg=panel_inset. mode-style's `bold` is the other half of the signal,
 * but a highlight pseudo-element may only take non-layout-affecting
 * properties, so font-weight is silently dropped here and is applied on the
 * hover/current rows below instead. Colour is left to do it, and it can:
 * --id-select-fg is 4.67:1 on the fill and 5.41:1 on the bare ground, so a
 * selection is legible whichever of the two the fill actually lands on. */
::selection {
  background: var(--id-select-bg, #eee0b7);
  color: var(--id-select-fg, #9c4b08);
}

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: var(--s4);
  top: var(--s4);
  z-index: 100;
  background: var(--id-bg-deep, #f4e8be);
  border: 1px solid var(--id-dim, #928374);
  padding: var(--s2) var(--s3);
}

.wrap {
  width: min(var(--measure), 100% - 2rem);
  margin-inline: auto;
}
.wrap-wide {
  width: min(var(--measure-wide), 100% - 2rem);
  margin-inline: auto;
}

/* ============================================================
   TMUX STATUS BAR — top, matching the author's real config
   ============================================================ */
/* `status-style "bg=default,fg=dim"`: no fill and no divider. Both are gone —
 * the band is the page ground, exactly as the terminal's is.
 *
 * The web has to pay for that transparency, though: tmux gets a reserved
 * terminal row that nothing can scroll under, and a sticky element does not.
 * The compensation is a scrim rather than a fill — the ground colour is
 * painted for the top 58% of the row and fades out from there, so prose
 * passing beneath is masked with no hard edge anywhere. The fade continues
 * past the row as a box-shadow in the ground colour: the same job the
 * specimen's ::after does, but ::after cannot be used here because this
 * element is a horizontal scroller (overflow-x below) and would clip a
 * pseudo-element placed under it, whereas an element's own box-shadow is
 * never clipped by its own overflow.
 *
 * The fade is stated as `calc(100% - 0.75rem)` rather than the 58% it used to
 * be so that it stays a FADE AT THE BOTTOM EDGE at any bar height. At one row
 * the two are the same 16px of opaque ground; at the two rows the narrow branch
 * produces, 58% would have started fading halfway down the first row and left
 * the window list on row two standing over scrolling prose. */
.tmux {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: stretch;
  gap: 0;
  /* min-height, not height: the narrow branch lets this wrap, and a wrapped bar
   * has to be as tall as the rows it actually grew. */
  min-height: var(--bar-row, var(--bar-h));
  background: linear-gradient(to bottom, var(--id-bg, #fbf1c7) calc(100% - 0.75rem), transparent);
  box-shadow: 0 0.35rem 0.45rem var(--id-bg, #fbf1c7);
  font-size: var(--fs-sm);
  line-height: var(--bar-row, var(--bar-h));
  /* A11Y-03 — `scrollbar-width: none` is gone. Hiding the scrollbar on a
   * scroller is only defensible while nothing can overflow, and the whole point
   * of the wrap below is that on a phone nothing does. What remains here is a
   * last resort for a width nobody predicted — a fifth window, a fallback face
   * with a wider advance — and that is exactly the case where the reader needs
   * to be told there is more. `thin` because the bar is 28px and a classic
   * scrollbar would take half of it; the thumb is --id-dim, which is 3.24:1 on
   * the ground — under AA for text, over the 3:1 that a non-text control needs.
   * A scroller that fits still draws nothing, so at rest this costs no pixels. */
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--id-dim, #928374) transparent;
}

/* FID-04 — `status-left "#[fg=dim] #S #[fg=pink]❯ "`. A dim session name and a
 * pink sigil on the bare band: no brackets, no fill. The name holds at
 * --id-muted rather than the literal `fg=dim` for the same reason the window
 * names do — 3.24:1 is below AA and this is the only thing identifying the
 * session. */
.tmux__session {
  color: var(--id-muted, #6b665f);
  padding-inline-start: 0.8ch;
  white-space: nowrap;
}
.tmux__sigil {
  color: var(--id-pane-active, #a15335);
  font-weight: 700;
  padding-inline: 0.8ch 0.4ch;
}
.tmux__windows {
  display: flex;
  gap: 0;
  flex: 1;
  /* A11Y-03 — this was `min-width: 0`, and that was the bug. It let the list
   * shrink below its own content, so the four links overflowed it and painted
   * on top of the theme toggle at every width from 320px to ~460px: `4:about`
   * and `light` occupied the same pixels and neither could be read or hit.
   * A floor at max-content turns that into honest overflow — the bar scrolls,
   * with the scrollbar restored above — and the narrow branch below removes
   * even that by wrapping instead. */
  min-width: max-content;
}
/* Inactive windows should be `fg=dim` to make room for the active one, and they
 * are not: --id-dim is 3.24:1 on the ground here and these are the site's only
 * navigation links, so they hold at --id-muted (5.01:1). This is the one place
 * in the bar where fidelity would cost AA, and AA wins. */
.tmux__win {
  padding-inline: 0.9ch;
  color: var(--id-muted, #6b665f);
  text-decoration: none;
  white-space: nowrap;
}
.tmux__win:hover { color: var(--id-fg-strong, #4f3829); }

/* FID-05 — `window-status-separator "#[fg=dim]<U+2758> "`. U+2758 is not in the
 * shipped Maple Mono subset and would render as a tofu box that breaks the cell
 * advance, so the separator is drawn rather than emitted: generated content
 * never lands in a copied window list. pointer-events is off because the
 * pseudo-element sits inside the <a> and would otherwise enlarge its click
 * target.
 *
 * It is a 1px rule and not a glyph now, for two reasons. U+2758 is a THIN
 * vertical bar with air above and below it; the nearest available substitute,
 * U+2502, is box-drawing, so it fills the cell top to bottom and reads as a
 * table rule cutting through the status line rather than as a gap marker.
 * And a glyph could not be centred: with `content` sitting inside the second
 * link, the separator had 1.8ch of gap on its left (both links' padding) and
 * 1ch on its right (its own trailing space), so it visibly belonged to the name
 * that followed it instead of standing between the two.
 *
 * The negative start margin pulls the rule out of the second link's padding to
 * land exactly on the boundary between the two boxes, and the equal end margin
 * restores the space it took — 0.9ch of gap either side, symmetric by
 * construction rather than by tuning. */
.tmux__win + .tmux__win::before {
  content: "" / "";
  display: inline-block;
  width: 1px;
  height: 0.9em;
  vertical-align: -0.1em;
  background: var(--id-dim, #928374);
  margin-inline: -0.9ch 0.9ch;
  pointer-events: none;
}

/* `window-status-current-style "#{?window_zoomed_flag,fg=yellow,fg=pink},bold"`
 * — coloured bold text and nothing else. No inverse video, no fill: the fill
 * was invented, and it was painted in a blue that tools.tmux.colors does not
 * even define. Pink at 4.87:1, and the weight is free in a fixed-advance face,
 * so nothing in the bar reflows when the current window changes. */
.tmux__win[aria-current] {
  color: var(--id-pane-active, #a15335);
  font-weight: 700;
}
.tmux__right {
  display: flex;
  align-items: center;
  gap: 0.9ch;
  padding-inline: 0.8ch;
  color: var(--id-muted, #6b665f);
  white-space: nowrap;
}


/* The float tier: `popup-border-lines "padded"` under `ui.border: none` draws no
 * line at all, only padding where the border would be. So no border and no
 * radius here — this control is chrome floating on the bar, not a widget. The
 * padding is what remains of the frame; :focus-visible still rings it. */
.themebtn {
  font: inherit;
  /* The `font` shorthand resets font-feature-settings to normal, so a control
   * that inherits its font would silently lose the slashed zero set on body. */
  font-feature-settings: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
  background: none;
  border: 0;
  color: var(--id-fg, #654735);
  padding: 0 0.7ch;
  /* One status row, not 1.4. At 1.4 the button's box was 18px tall — under the
   * 24px WCAG 2.5.8 minimum for a pointer target, on the one control in the bar
   * that is not a link. A row's worth of line box gives it 28px and centres the
   * label in it, and since the row height is unchanged nothing else moves. */
  line-height: var(--bar-row, var(--bar-h));
}
.themebtn:hover {
  background: var(--id-panel, #e5d5ad);
  color: var(--id-fg-strong, #4f3829);
}

/* ---- A11Y-03: the narrow branch. The bar wraps instead of scrolling ----
 * Measured, not estimated: the bar's natural width is 462px, or 478px with the
 * copy-mode flag showing, against a 390px iPhone. `overflow-x: auto` was never
 * saving it — see .tmux__windows above — and `scrollbar-width: none` meant
 * there was no signal that anything was elsewhere.
 *
 * A narrow tmux status line wraps, so this one wraps: session + sigil + the
 * right-hand side on row one, the window list on row two. The window list may
 * wrap again inside its own row, so the layout never depends on four windows
 * fitting side by side.
 *
 * THE BREAKPOINT IS IN em ON PURPOSE, and that is the answer to the "it might
 * be three rows" caveat. Every width in this bar is rem- and ch-derived, so it
 * scales with the reader's default font size — and a media query's `em` is that
 * same default size, never this stylesheet's. A reader at 20px gets a 578px bar
 * AND a 600px breakpoint, so the branch flips at the same relative place it
 * does at 16px. A px breakpoint would have flipped too late for exactly the
 * reader the caveat is about. 30em = 480px clears the 478px copy-mode width
 * with room for a fallback face whose advance is wider than Maple Mono's 0.6em.
 */
@media (max-width: 30em) {
  :root {
    /* --bar-h is no longer one row here, so a row needs its own name. */
    --bar-row: 1.75rem;
    /* An UPPER BOUND on the rows the bar occupies — CSS cannot count flex
     * lines, so this is asserted, not measured. Over-counting costs a heading
     * 1.75rem of extra slack after an anchor jump and nothing else;
     * under-counting puts the heading behind the bar. It errs upward. */
    --bar-rows: 2;
    --bar-h: calc(var(--bar-row) * var(--bar-rows));
  }

  .tmux { flex-wrap: wrap; }

  /* flex-basis: 100% is what puts the window list on a line of its own — an
   * item that wants the whole line can never share one — and `order` then moves
   * it past the right-hand side so row one reads `embe221ed ❯ … theme`.
   *
   * ACCEPTED COST: tab order follows the DOM, so the four window links are
   * reached before the theme button that is drawn on the row above them.
   * Correcting it means reordering _includes/tmux-bar.html; for five controls
   * inside one 3.5rem band this is the smaller harm than the layouts that keep
   * DOM order, all of which spend a third row to do it.
   *
   * min-width goes back to 0 here: max-content is a floor, and a floor wider
   * than the line it sits on would push the viewport sideways again. Alone on
   * its line the list has somewhere to wrap to instead. */
  .tmux__windows {
    order: 1;
    flex-basis: 100%;
    flex-wrap: wrap;
    min-width: 0;
  }
  .tmux__right { margin-inline-start: auto; }
}

/* The window list measures ~19.1em on its own, so under that it wraps too and
 * the bar is three rows. Declared at 20em rather than 19.1em: the extra 0.9em
 * is the same slack the 30em above carries, spent in the direction that cannot
 * hide a heading. Between 19.1em and 20em the bar really is two rows and an
 * anchor lands 1.75rem lower than it had to — which is invisible, and is the
 * trade this variable exists to make. */
@media (max-width: 20em) {
  :root { --bar-rows: 3; }
}

/* ============================================================
   PANE — a tmux split.
   Mirrors the author's real config: single-line borders, title drawn ON the
   top border (pane-border-status top), and exactly one active pane whose
   border and title take the accent colour.
   Borders are CSS, never box-drawing characters — they stay crisp at any
   zoom, reflow on mobile, and never end up in a text selection.

   Three border tiers exist in the config and they are not interchangeable:
     pane-border-lines  "single"   square, dim      — this component
     menu-border-lines  "rounded"  6px, pink        — .menu, below
     popup-border-lines "padded"   no line at all   — .popup / .themebtn
   ============================================================ */
.panes {
  display: grid;
  gap: var(--s5);
}
@media (min-width: 56rem) {
  /* `|` split: the listing takes the larger share, the picker sits beside it,
     the same way you would actually lay this session out. */
  .panes--split {
    grid-template-columns: 1.55fr 1fr;
    align-items: start;
  }
}

.pane {
  position: relative;
  border: 1px solid var(--id-pane-border, #8d877d);
  /* `pane-border-lines "single"` is emitted unconditionally: ─ and │ have no
   * rounded variant, so a pane corner is square. Rounding belongs to the menu
   * tier alone, and it was borrowing this one. */
  border-radius: 0;
  padding: var(--s5) var(--s4) var(--s4);
  min-width: 0;
}

/* The title sits in a gap punched through the top border. */
.pane__title {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 1.5ch;
  translate: 0 -50%;
  padding-inline: 0.6ch;
  background: var(--id-bg, #fbf1c7);
  font-size: var(--fs-sm);
  color: var(--id-pane-border, #8d877d);
  white-space: nowrap;
  max-width: calc(100% - 3ch);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* tmux always has exactly one active pane. The first is active until the
   reader's pointer or keyboard focus moves to another one.

   `:not(.panes .panes)` is what keeps that promise: home.html nests a
   .panes--split inside .panes, and an unscoped rule matched the first child of
   BOTH containers — two pink panes at rest, in a component whose whole claim is
   that there is exactly one. Only the outermost container may nominate a
   resting active pane. The accepted trade: a pane that is first inside a split
   can never be the resting one, so on the homepage `~/writeups` stays dim until
   it is hovered or focused. Hover and focus themselves are unscoped, since
   those really are one-at-a-time. */
.panes:not(.panes .panes):not(:has(.pane:hover, .pane:focus-within)) > .pane:first-child,
.pane:hover,
.pane:focus-within {
  border-color: var(--id-pane-active, #a15335);
}
.panes:not(.panes .panes):not(:has(.pane:hover, .pane:focus-within)) > .pane:first-child > .pane__title,
.pane:hover > .pane__title,
.pane:focus-within > .pane__title {
  color: var(--id-pane-active, #a15335);
}

/* ---- the other two tiers ----
 * No markup emits these yet. They are written here so the tier stays a property
 * of the config rather than of whichever component is built first: when the
 * choose-tree / actions overlay lands it inherits `rounded + pink`, and it is
 * the ONLY thing on the site allowed to be round.
 *   menu-border-lines "rounded", menu-border-style fg=pink, menu-style
 *   bg=panel_inset — and since the menu ground IS panel_inset, which is the same
 *   colour menu-selected-style fills with, the selected row cannot be marked by
 *   a fill at all. Hue and weight do all of it, exactly as in the terminal. */
.menu {
  background: var(--id-select-bg, #eee0b7);
  border: 1px solid var(--id-pane-active, #a15335);
  border-radius: 6px;
  padding-block: var(--s2);
}
.menu__row--on {
  color: var(--id-select-fg, #9c4b08);
  font-weight: 700;
}
/* `popup-border-lines "padded"` with `ui.border: none` draws no line whatsoever:
 * the padding is the frame. A filled box, square, and nothing else. */
.popup {
  background: var(--id-bg-deep, #f4e8be);
  border: 0;
  border-radius: 0;
  padding: var(--s4);
}

/* ============================================================
   SURFACE 1 — TERMINAL (home, about, 404)
   ============================================================ */
.surface-term { padding-block: var(--s6) var(--s8); }

/* ---- two-line zsh prompt, the author's real one ---- */
.ps1 {
  margin: 0 0 var(--s6);
  font-size: var(--fs);
  font-weight: 400;
  line-height: 1.6;
}
.ps1__ctx { display: block; }
/* FID-06/07/08 — line 1 is `$(virtualenv_prompt_info)%c $(git_prompt_info)`.
 * `.ps1__user/__at/__host/__path` are gone: user@host was never on this prompt,
 * it is pane-border-format, which .pane__title already draws in the same pane.
 *
 * %c is rendered in cyan AND BOLD (`%{$cyan%}%B%c`), where tools.zsh.colors
 * aliases cyan to $aqua$ — a hue no leaf role carries, so interdotensional now
 * emits it as --id-prompt-dir (4.93:1 light / 6.28:1 dark). */
.ps1__dir { color: var(--id-prompt-dir, #467156); font-weight: 700; }
/* `git:(` and `)` are ZSH_THEME_GIT_PROMPT_PREFIX/CLEAN's `$blue$`, which
 * tools.zsh.colors aliases to $grey1$ — structure, not a link colour. */
.ps1__git { color: var(--id-dim, #928374); }
/* The branch is `$magenta$` = $pink$: the same terracotta as the tmux accent,
 * never purple. */
.ps1__branch { color: var(--id-pane-active, #a15335); }
/* ZSH_THEME_GIT_PROMPT_DIRTY's `%{$yellow%}%1{✗%}`. */
.ps1__dirty { color: var(--id-accent, #925c07); }
.ps1__line {
  display: flex;
  align-items: baseline;
  gap: 1ch;
}
.ps1__caret { color: var(--id-success, #626d2a); font-weight: 700; }
.ps1__said { color: var(--id-fg-strong, #4f3829); }

/* Exactly one blinking cursor on the whole site, and it lives here. */
.cursor {
  display: inline-block;
  width: 1ch;
  height: 1.15em;
  translate: 0 0.18em;
  background: var(--id-success, #626d2a);
  animation: blink 1.06s steps(1) infinite;
}
@keyframes blink {
  50% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cursor { animation: none; }
}

/* ---- section headings on the terminal surface ---- */
/* `whatis` output: NAME (section)  - one-line description.
 *
 * A three-column grid rather than one run of inline text, so a description that
 * wraps hangs under itself instead of running back beneath `blog (7) -`. That
 * is not what a terminal does — real whatis wraps flush at the terminal width —
 * but the terminal has no reflow and a browser pane does, and a ragged second
 * line under a fixed-width label reads as a broken column in a monospace grid.
 *
 * The label parts are aria-hidden in the markup: the <h1>'s accessible name has
 * to be the description alone, not "blog 7 - <description>". */
.whatis {
  display: grid;
  grid-template-columns: auto auto auto 1fr;
  gap: 0 1ch;
  margin: 0;
  font-size: var(--fs);
  font-weight: 400;
  line-height: var(--lh);
  color: var(--id-fg, #654735);
  letter-spacing: 0;
}
.whatis__name { color: var(--id-fg-strong, #4f3829); font-weight: 700; }
.whatis__sec,
.whatis__dash { color: var(--id-muted, #6b665f); }

/* A prompt whose output follows it, and the prompt that output returns to.
 * Both are flush: a shell prints output on the line after the command and the
 * next prompt on the line after that, with no blank line anywhere. The block's
 * 1.7 line-height is already generous enough that flush does not read cramped,
 * and the gaps that were here made three consecutive lines of one session look
 * like three separate paragraphs. */
.ps1--cmd { margin-block-end: 0; }
.ps1__line--idle { margin-block-start: 0; }

/* A prompt that is not the hero. Same two lines and the same colours — the
 * point of the change is that these panes stop having a bespoke heading and
 * start having a prompt — but it sits directly under the pane title rather
 * than opening the page, so it needs less air beneath it than the hero's.
 * (.term-h, the bespoke heading it replaces, is gone: nothing emitted it.) */
.ps1--pane {
  margin-block-end: var(--s4);
  font-size: var(--fs-md);
}

/* ---- post index as `ls -la` output ----
 * No `font-variant-numeric: tabular-nums` anywhere on this site: Maple Mono has
 * no `tnum` table, so the declaration resolved to nothing and implied a
 * safeguard that was not there. Every glyph in a fixed-advance face is already
 * tabular — the dates in these rows line up because the font is mono. */
.ls {
  list-style: none;
  margin: 0;
  padding: 0;
}
.ls__row {
  display: grid;
  grid-template-columns: auto 6.5rem 1fr;
  gap: 0 1.5ch;
  align-items: baseline;
  padding: var(--s1) var(--s2);
  border-radius: 2px;
  text-decoration: none;
  color: inherit;
}
/* The current row is `mode-style` / `menu-selected-style`:
 * fg=selected.fg,bg=selected.bg,bold. The fill is 1.16:1 against the ground and
 * cannot be seen on its own, so the weight is not decoration here — it is the
 * second channel the config asks for. Free of reflow: the face is
 * fixed-advance, so bolding a row cannot move the `auto` mode column. */
a.ls__row:hover,
a.ls__row:focus-visible {
  background: var(--id-select-bg, #eee0b7);
  color: var(--id-select-fg, #9c4b08);
  font-weight: 700;
}
a.ls__row:hover .ls__mode,
a.ls__row:hover .ls__date,
a.ls__row:hover .ls__cat,
a.ls__row:focus-visible .ls__mode,
a.ls__row:focus-visible .ls__date,
a.ls__row:focus-visible .ls__cat { color: inherit; }

.ls__mode {
  font-size: var(--fs-sm);
  color: var(--id-dim, #928374);
}
.ls__date {
  font-size: var(--fs-sm);
  color: var(--id-muted, #6b665f);
}
.ls__name { min-width: 0; }
.ls__cat {
  color: var(--id-accent2, #a65009);
  margin-right: 1ch;
}
.ls__row--draft { color: var(--id-muted, #6b665f); cursor: default; }
.ls__row--draft .ls__cat { color: var(--id-dim, #928374); }

@media (max-width: 34rem) {
  .ls__row {
    grid-template-columns: auto 1fr;
  }
  .ls__mode { display: none; }
}

/* ---- theme picker, shaped like `interdot list` ---- */
.picker { margin: 0; padding: 0; list-style: none; }
.picker__cmd {
  color: var(--id-muted, #6b665f);
  font-size: var(--fs-sm);
  margin-bottom: var(--s2);
}
.picker__row {
  display: flex;
  align-items: baseline;
  gap: 1ch;
  width: 100%;
  text-align: left;
  font: inherit;
  font-feature-settings: inherit;   /* the shorthand above resets it */
  background: none;
  border: 0;
  border-radius: 2px;
  padding: var(--s1) var(--s2);
  color: var(--id-fg, #654735);
  cursor: pointer;
}
/* menu-selected-style again — this list is a menu, so it gets the same pair. */
.picker__row:hover {
  background: var(--id-select-bg, #eee0b7);
  color: var(--id-select-fg, #9c4b08);
  font-weight: 700;
}
.picker__row[disabled] {
  color: var(--id-dim, #928374);
  cursor: not-allowed;
}
/* A row you cannot select never takes the selected style — including the weight,
 * which the hover rule above would otherwise leave switched on. */
.picker__row[disabled]:hover {
  background: none;
  color: var(--id-dim, #928374);
  font-weight: 400;
}

/* The `*` marks the author's own active theme, exactly like `interdot list`.
 * Driven purely off :root so it can never disagree with the stylesheet. */
.picker__star { width: 1ch; color: var(--id-accent, #925c07); font-weight: 700; }
.picker__row[data-theme-option="gruvbox-material-light"] .picker__star::before { content: "*"; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .picker__row[data-theme-option="gruvbox-material-light"] .picker__star::before { content: ""; }
  :root:not([data-theme]) .picker__row[data-theme-option="gruvbox-material-dark"] .picker__star::before { content: "*"; }
}
:root[data-theme="dark"] .picker__row[data-theme-option="gruvbox-material-light"] .picker__star::before { content: ""; }
:root[data-theme="dark"] .picker__row[data-theme-option="gruvbox-material-dark"] .picker__star::before { content: "*"; }

.term-note {
  color: var(--id-muted, #6b665f);
  font-size: var(--fs-sm);
  margin: var(--s3) 0 0;
}

/* ============================================================
   SURFACE 2 — READER (articles). No chrome. That is the point.
   ============================================================ */
.surface-read { padding-block: var(--s5) var(--s8); }
.post__head { max-width: var(--measure); }

/* The entire bridge back to surface 1. Resist adding more. */
.bridge {
  display: inline-block;
  margin-bottom: var(--s6);
  font-size: var(--fs-sm);
  color: var(--id-muted, #6b665f);
  text-decoration: none;
}
.bridge:hover { color: var(--id-info, #436d77); text-decoration: underline; }

.post__title {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--id-fg-strong, #4f3829);
  text-wrap: balance;
}
.post__meta {
  margin: var(--s3) 0 var(--s7);
  font-size: var(--fs-sm);
  color: var(--id-muted, #6b665f);
  display: flex;
  flex-wrap: wrap;
  gap: 0 1.5ch;
}
.post__meta a { color: var(--id-accent2, #a65009); }

/* ---- prose ----
 * A named-track grid, so code can break out wider than the reading measure
 * while extending RIGHT ONLY. Across 45 code blocks the left reading edge
 * must never move, and the track can never overflow the viewport the way a
 * fixed `width` would. */
.prose {
  display: grid;
  grid-template-columns:
    [text-start] minmax(0, var(--measure)) [text-end]
    minmax(0, 1fr) [wide-end];
}
.prose > * {
  grid-column: text-start / text-end;
  margin-block: 0 var(--s5);
  min-width: 0;
}
.prose > :last-child { margin-block-end: 0; }

.prose h2,
.prose h3,
.prose h4,
.prose h5,
.prose h6 {
  font-weight: 700;
  line-height: 1.25;
  color: var(--id-fg-strong, #4f3829);
  text-wrap: balance;
  /* .tmux is sticky, so a heading scrolled to by the table of contents has to
   * clear it. --bar-h is the bar's occupied height at THIS width, not a
   * constant — it grows with the rows the bar wraps to on a phone. See the
   * narrow branch in the status-bar section: keeping those two in step is the
   * whole reason --bar-h is a variable and not the 1.75rem it reads as. */
  scroll-margin-block-start: calc(var(--bar-h) + 1.5rem);
}
.prose h2 {
  margin-block: var(--s7) var(--s4);
  padding-block-end: var(--s2);
  font-size: var(--fs-lg);
  border-bottom: 1px solid var(--id-dim, #928374);
}
.prose h3 {
  margin-block: var(--s6) var(--s3);
  font-size: var(--fs);
}
/* h4 in these writeups is almost always a C++ symbol, not a prose heading —
 * 14 of them run consecutively under `functions`. Treated as identifiers:
 * same size, distinguished by color and space rather than by scale. */
.prose h4 {
  margin-block: var(--s5) var(--s2);
  font-size: var(--fs);
  color: var(--id-accent2, #a65009);
}
.prose h5,
.prose h6 {
  margin-block: var(--s4) var(--s2);
  font-size: var(--fs-md);
  color: var(--id-muted, #6b665f);
}

/* Markdown prefixes as marginal notation. The ` / ""` supplies empty alt text
 * so screen readers announce "introduction", not "hash hash introduction";
 * browsers without alt-text support simply read the glyphs. */
.prose h2::before,
.prose h3::before,
.prose h4::before {
  color: var(--id-dim, #928374);
  font-weight: 400;
}
.prose h2::before { content: "## " / ""; }
.prose h3::before { content: "### " / ""; }
.prose h4::before { content: "#### " / ""; }
.prose h5::before { content: "##### " / ""; }
.prose h6::before { content: "###### " / ""; }

/* jekyll-toc's inject_anchors emits GitHub octicon markup we have no font for.
 * Draw our own `#` instead, revealed on hover to the RIGHT of the heading so
 * it never collides with the markdown prefix on the left. */
.prose .anchor {
  float: right;
  margin-inline-start: 1ch;
  text-decoration: none;
  opacity: 0;
  transition: opacity 120ms ease;
}
.prose .anchor .octicon-link::before {
  content: "#";
  color: var(--id-dim, #928374);
}
.prose :is(h2, h3, h4):hover .anchor,
.prose .anchor:focus-visible { opacity: 1; }
.prose .anchor:hover .octicon-link::before { color: var(--id-info, #436d77); }
@media (prefers-reduced-motion: reduce) {
  .prose .anchor { transition: none; }
}

.prose ul,
.prose ol { padding-inline-start: 3ch; }
.prose li { margin-block: var(--s1); }

.prose blockquote {
  margin-inline: 0;
  padding: var(--s2) 0 var(--s2) 2ch;
  border-left: 2px solid var(--id-accent, #925c07);
  color: var(--id-muted, #6b665f);
}
.prose blockquote > :last-child { margin-block-end: 0; }

.prose img {
  max-width: 100%;
  height: auto;
  border: 1px solid var(--id-border, #d5c4a1);
}

.prose hr {
  border: 0;
  border-top: 1px solid var(--id-border, #d5c4a1);
  margin-block: var(--s6);
}

/* inline code
 * leaf: `inline_code_fg = accent2`, `inline_code_bg = panel`. The foreground is
 * taken; the ground is not. accent2 on --id-panel measures 3.83:1 in light,
 * which fails AA outright, and on the site's existing --id-bg-deep it is 4.54:1
 * (6.36:1 dark). Keeping bg-deep is therefore both the readable choice and the
 * quieter one: this post has 176 inline spans, up to three in a single sentence,
 * and orange on the louder panel tint turns running prose into a warning label.
 * Orange on the ground it already had is as much colour as 176 instances can
 * carry.
 *
 * `user-select: all` is `word-separators " "` ported: space is the author's ONLY
 * word boundary, so in his terminal one grab takes `Corefile::ParseNtFile()` or
 * a whole /usr/lib path. Every browser instead breaks on . / : ( and hands back
 * `Corefile`. Scoped deliberately to inline code and never to a block — a click
 * now selects the entire token, which is wrong for anyone trying to place a
 * caret inside a long fence, and a block needs partial selection to be useful.
 * overflow-wrap is the other half: a token that layout cannot break either
 * would push the reading measure sideways. */
.prose :not(pre) > code {
  font-size: 0.9em;
  color: var(--id-accent2, #a65009);
  background: var(--id-bg-deep, #f4e8be);
  border: 1px solid var(--id-border, #d5c4a1);
  border-radius: 3px;
  padding: 0.06em 0.36em;
  overflow-wrap: anywhere;
  -webkit-user-select: all;
  user-select: all;
}

/* ---- tables ----
 * Struct-offset tables want the wide track and must scroll rather than push
 * the page sideways. `display: block` is what makes overflow-x work on a
 * <table> without wrapping it in an extra element kramdown will not emit. */
.prose > table {
  grid-column: text-start / wide-end;
  display: block;
  overflow-x: auto;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.prose th,
.prose td {
  text-align: left;
  padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--id-border, #d5c4a1);
}
/* leaf: `table_header = accent`. Colour is the whole treatment. Uppercase plus
 * 0.05em plus a smaller size did the one thing a monospace design cannot afford
 * — it took the header off the character grid, so OFFSET's six glyphs landed
 * nowhere near the six of 0x0000 in the column beneath. The header now inherits
 * the table's size and sits character-for-character above its data. Accent is
 * 4.93:1 on the ground; the fill it used to carry would only have dropped that
 * to 4.56:1 while adding a second signal the design system does not define. */
.prose thead th {
  color: var(--id-accent, #925c07);
}
.prose tbody tr:last-child td { border-bottom: 0; }

/* ---- table of contents ----
 * A pane, like everything else that is chrome rather than prose. Entries carry
 * the same `##` / `###` / `####` prefixes the headings themselves use, so the
 * contents read as an outline of the markdown source rather than as a widget.
 * The prefixes are ::before with empty alt text — a screen reader gets the
 * heading name, not a run of hashes. */
.toc-pane { margin-block-end: var(--s7); }
.toc { margin: 0; }
.toc > summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--id-muted, #6b665f);
  list-style: none;
  display: flex;
  gap: 1ch;
}
.toc > summary::-webkit-details-marker { display: none; }
.toc > summary::before {
  content: "❯";
  color: var(--id-success, #626d2a);
  rotate: 90deg;
  transition: rotate 120ms ease;
}
.toc:not([open]) > summary::before { rotate: 0deg; }
.toc > summary:hover { color: var(--id-fg-strong, #4f3829); }
@media (prefers-reduced-motion: reduce) {
  .toc > summary::before { transition: none; }
}

.toc nav {
  margin-block-start: var(--s3);
  max-height: 60vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.toc ul { list-style: none; margin: 0; padding: 0; }
.toc ul ul { padding-inline-start: 1.5ch; }
.toc li { margin-block: 0; }
.toc a {
  display: block;
  padding: 0.08rem 0;
  font-size: var(--fs-sm);
  color: var(--id-fg, #654735);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.toc a::before {
  color: var(--id-dim, #928374);
  margin-inline-end: 0.6ch;
}
.toc > nav > ul > li > a::before { content: "##" / ""; }
.toc > nav > ul > li > ul > li > a::before { content: "###" / ""; }
.toc > nav > ul > li > ul > li > ul > li > a::before { content: "####" / ""; }
/* Same selected pair. The `####` prefixes and the de-emphasised h4 entries below
 * both inherit it, so a hovered entry is one colour top to bottom. */
.toc a:hover {
  color: var(--id-select-fg, #9c4b08);
  background: var(--id-select-bg, #eee0b7);
  font-weight: 700;
  border-radius: 2px;
}
.toc a:hover::before { color: inherit; }
/* h4 entries — the 14-sibling `functions` run. De-emphasised so the depth
 * stays legible instead of becoming a wall. */
.toc > nav > ul > li > ul > li > ul > li > a {
  font-size: var(--fs-xs);
  color: var(--id-muted, #6b665f);
}

/* ============================================================
   CODE BLOCKS
   Background is --id-bg-deep in both themes (#f4e8be / #1d2021). Not --id-panel:
   in dark, danger/accent2/special all fail WCAG AA on #3c3836.
   ============================================================ */
/* Code takes the wide track: decompiled C++ signatures need the room and
 * paragraphs do not. kramdown wraps a fenced block in
 * <div class="language-x highlighter-rouge"><div class="highlight"><pre>. */
.prose > .highlighter-rouge {
  grid-column: text-start / wide-end;

  /* 45 highlighted blocks on a page that is 25,641px tall. Skipping the layout
   * and paint of the off-screen ones is the only measure here that costs no
   * markup, and this wrapper is the exact unit: one rule reaches all 45 and
   * nothing else on the page.
   *
   * Deliberately NOT applied to prose, headings or inline code. That is the
   * whole mitigation for the caveat: an engine that skips a
   * content-visibility subtree for in-page find would hide only fenced code,
   * while every paragraph, all 36 headings and all 176 inline spans — the text
   * a reader actually searches for — stay live. Chrome and Edge do match
   * inside these subtrees (they are searchable, and a hit forces the block to
   * render), Firefox and Safari support the property, and any engine that does
   * not simply ignores the declaration. Accepted residual risk: Ctrl+F for a
   * string that exists ONLY inside a fence may come up empty in an engine that
   * skips them. A heading or anchor target can never be inside one, so no link
   * into the page can land in skipped content.
   *
   * The estimate is measured, not guessed: 466 code lines across 45 blocks is
   * 10.4 lines each, and at --fs-sm with 1.6 leading plus the block's padding
   * that is ~15rem. `auto` means the browser replaces the estimate with the
   * real height once a block has been rendered once, so the scrollbar settles
   * rather than jumping around as the reader scrolls a 25,000px page. */
  content-visibility: auto;
  contain-intrinsic-size: auto 15rem;
}
/* Print has no viewport to be off-screen of, and a skipped block in a PDF is a
 * blank hole. Belt and braces — engines have shipped bugs here. */
@media print {
  .prose > .highlighter-rouge { content-visibility: visible; }
}

/* syntax.css owns the LOOK of every highlighted container (div.highlighter-rouge
 * / div.highlight / pre.highlight): colour, ground, frame. Do not restyle those
 * here — the selectors tie on specificity and syntax.css loads last, so a rule
 * here would be silently half-applied. The two exceptions above are placement,
 * not appearance — where the block sits in the .prose grid, and whether it is
 * rendered at all — and syntax.css declares neither. If it ever starts to, it
 * wins, and the rule above stops working with no warning.
 *
 * This is only for the case syntax.css cannot reach: a fence with NO language
 * tag bypasses Rouge entirely and kramdown emits a bare <pre><code> with no
 * wrapper at all. */
.prose > pre {
  grid-column: text-start / wide-end;
  overflow-x: auto;
  background: var(--id-bg-deep, #f4e8be);
  border: 1px solid var(--id-border, #d5c4a1);
  border-radius: 6px;
  padding: 0.85rem 1rem;
  font-size: var(--fs-sm);
  line-height: 1.6;
  tab-size: 4;
}
.prose > pre > code {
  background: none;
  border: 0;
  padding: 0;
  font-size: inherit;
}

/* Font size for highlighted code — the one property syntax.css leaves to the
 * page, since it belongs to the type scale rather than the palette. */
.prose div.highlight {
  font-size: var(--fs-sm);
  line-height: 1.6;
}

/* ============================================================
   FOOTER
   ============================================================ */
.foot {
  margin-block-start: var(--s8);
  padding-block-start: var(--s4);
  border-top: 1px solid var(--id-border, #d5c4a1);
  font-size: var(--fs-sm);
  color: var(--id-muted, #6b665f);
  display: flex;
  flex-wrap: wrap;
  gap: 0 2ch;
}
.foot a { color: var(--id-muted, #6b665f); }
.foot a:hover { color: var(--id-info, #436d77); }

/* ============================================================
   CHOOSER — /tags/ and /categories/ index pages.
   Shaped like the author's `choose-tree` binding, whose format is
   "[#{window_name} @ path]": a name, a count, and a selection row you move
   through. Replaces what would otherwise be a bare directory listing.
   ============================================================ */
.chooser {
  list-style: none;
  margin: 0;
  padding: 0;
}
.chooser__row {
  display: grid;
  grid-template-columns: 1.5ch auto 1fr auto;
  gap: 0 1ch;
  align-items: baseline;
  padding: var(--s1) var(--s2);
  border-radius: 2px;
  text-decoration: none;
  color: var(--id-fg, #654735);
}
.chooser__row::before {
  content: "";
  color: var(--id-success, #626d2a);
  font-weight: 700;
}
/* choose-tree's current row is menu-selected-style, same as everything else the
 * reader moves a cursor through. The ❯ appearing in the gutter is the third
 * channel, since hue plus weight on a fill this quiet is still hue plus weight. */
.chooser__row:hover,
.chooser__row:focus-visible {
  background: var(--id-select-bg, #eee0b7);
  color: var(--id-select-fg, #9c4b08);
  font-weight: 700;
}
.chooser__row:hover::before,
.chooser__row:focus-visible::before {
  content: "❯" / "";
  color: inherit;
}
.chooser__key { color: var(--id-accent2, #a65009); }
.chooser__row:hover .chooser__key,
.chooser__row:focus-visible .chooser__key,
.chooser__row:hover .chooser__count,
.chooser__row:focus-visible .chooser__count { color: inherit; }
.chooser__bar {
  color: var(--id-dim, #928374);
  overflow: hidden;
  white-space: nowrap;
}
.chooser__count {
  color: var(--id-muted, #6b665f);
  font-size: var(--fs-sm);
}

/* ============================================================
   ARCHIVE (tag / category pages)
   ============================================================ */
.archive__title {
  margin: 0 0 var(--s5);
  font-size: var(--fs-lg);
  color: var(--id-fg-strong, #4f3829);
}
.archive__title code { color: var(--id-accent2, #a65009); }

/* ============================================================
   UTILITIES AND LATE COMPONENTS
   Added during integration: these classes are emitted by markup written
   alongside the rest of this file, so they land here rather than being
   threaded into sections that were already settled.
   ============================================================ */



/* CHK-01 — the post header block, in checksec's own output shape.
 *
 * The aligned `Label:` column IS the component, so it is ONE grid on the <dl>
 * rather than one grid per row. The per-row version this replaces only lined
 * up because every label track was hardcoded to 12.5ch, and a schema that lets
 * a post name its own rows cannot promise anything about label width.
 * fit-content() gives the real behaviour: the column is as wide as the longest
 * label actually present, and never wider than 18ch — past that a runaway
 * label wraps inside its own column instead of shoving every value off the
 * page.
 *
 * `overflow-wrap: anywhere` on both cells is load-bearing, and `break-word`
 * will NOT do: fit-content() floors at the item's min-content size, so one
 * unbroken 60-character token still outvotes the 18ch cap — and `anywhere` is
 * the only one of the two that shrinks min-content. Same reason the value
 * column is minmax(0, 1fr) rather than 1fr. With both, nothing in this block
 * can force the page sideways and none of it needs an overflow-x container. */
/* Not a pane — see the include for why. Output sits under the metadata line
 * with the same air a pane had, so removing the frame did not move anything. */
/* Both margins, because this is no longer a pane. A .pane carried its own
 * padding, so the block only ever needed a top margin; stripped of the frame it
 * had nothing holding the toc pane off its last row, and `Solved:` rendered
 * behind the toc's top border. */
.chk-block { margin-block: var(--s5) var(--s6); }

/* `❯ checksec <slug>`. The two colours are copied from .ps1__caret and
 * .ps1__said rather than sharing their selectors: the reader surface has no
 * prompt component on purpose — no git context, no cursor, no user@host — and
 * reusing that component to borrow two colours would drag the rest in. */
.chk__cmd {
  display: flex;
  align-items: baseline;
  gap: 1ch;
  margin: 0 0 var(--s3);
  font-size: var(--fs-sm);
}
.chk__caret { color: var(--id-success, #626d2a); font-weight: 700; }
.chk__said { color: var(--id-fg-strong, #4f3829); }

/* The <dt>/<dd> pairs are DIRECT children — no per-row wrapper — because grid
 * only shares a track between siblings. The known cost is that WebKit drops a
 * <dl>'s list semantics once it is display:grid; the accepted trade is that
 * DOM order still reads label-then-value for every row, which is the whole of
 * what this block has to say. The wrapper version it replaces had the same
 * exposure and no shared column. */
.chk {
  display: grid;
  grid-template-columns: fit-content(18ch) minmax(0, 1fr);
  gap: var(--s1) 1ch;
  align-items: baseline;
  margin: 0;
  font-size: var(--fs-sm);
}

/* pwntools colours the value and never the label — in checksec, in log output,
 * anywhere. The label is structure, so it takes the structural grey.
 * --id-muted, 5.01:1 light / 5.89:1 dark. Not --id-dim (3.24:1) and never
 * --id-faint (2.21:1); neither is legal as text on this ground. */
.chk dt {
  color: var(--id-muted, #6b665f);
  overflow-wrap: anywhere;
}
/* The colon is drawn, not written. It belongs to the column rather than to the
 * term, so it stays out of a copied selection and out of the accessible name
 * that <dt> already carries. */
.chk dt::after { content: ":" / ""; }

.chk dd {
  margin: 0;
  min-width: 0;
  color: var(--id-fg, #654735);
  overflow-wrap: anywhere;
}

/* ...and it colours the value by whether the thing is good or bad. Every
 * stated row this site emits also says the word — `offline`, `Solved` — so the
 * hue is the second channel and never the only one (WCAG 1.4.1).
 * Contrast on --id-bg, light / dark:
 *   good 4.95 / 6.72   bad 5.00 / 4.70   warn 4.93 / 6.68   note 5.01 / 5.94 */
.chk dd.chk__v--good { color: var(--id-success, #626d2a); }
.chk dd.chk__v--bad { color: var(--id-danger, #b43e3e); }
.chk dd.chk__v--warn { color: var(--id-warning, #925c07); }
.chk dd.chk__v--note { color: var(--id-info, #436d77); }

/* Links here take .post__meta's link colour, not the global --id-info one:
 * info is a state in this block, and a link wearing it would be
 * indistinguishable from a `note` value. --id-accent2, 4.91:1 / 5.72:1. */
.chk dd a { color: var(--id-accent2, #a65009); }
/* A stated value owns its hue outright, link included — a second colour inside
 * it would claim a second meaning. The underline still marks the link. */
.chk dd:is(.chk__v--good, .chk__v--bad, .chk__v--warn, .chk__v--note) a {
  color: inherit;
}

/* A list-valued row — the handout files. One item per line inside the value,
 * so the row grows downward and the label column is untouched. */
.chk__stack {
  display: flex;
  flex-direction: column;
}

/* SHIP-02 — the /archives/ year heading, in the `ls -R` shape: a directory
 * line, then its contents. */
.arch__year {
  margin-block: var(--s6) var(--s2);
  font-size: var(--fs-md);
  font-weight: 400;
  color: var(--id-muted, #6b665f);
}
.arch__year:first-of-type { margin-block-start: var(--s4); }

/* CODE-05 — `{% highlight %}` is the only way to pass mark_lines, and it emits
 * <figure class="highlight"> with no .highlighter-rouge wrapper, so the two
 * rules that place a code block have to name it too or a marked block sits at
 * the reading measure instead of the wide track. */
.prose > figure.highlight { grid-column: text-start / wide-end; margin: 0; }
.prose figure.highlight { font-size: var(--fs-sm); line-height: 1.6; }
