/* tokens.css */
/*
  Yarpiz design tokens. Every other stylesheet reads these custom properties
  and never uses raw colors. Design and contrast checks: DESIGN.md "Tokens".

  Bundles: site = tokens, base, components, content, site
           admin = tokens, base, components, content, admin
*/

/* ------------------------------------------------------------ fonts
   Figtree (variable, Latin subset) is copied by the build from
   @fontsource-variable/figtree to /assets/fonts/. The stack falls back to
   the system UI font while it loads (font-display: swap). */
@font-face {
  font-family: "Figtree";
  src: url("/assets/fonts/figtree-latin-wght-normal.woff2") format("woff2-variations"),
    url("/assets/fonts/figtree-latin-wght-normal.woff2") format("woff2");
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Figtree";
  src: url("/assets/fonts/figtree-latin-wght-italic.woff2") format("woff2-variations"),
    url("/assets/fonts/figtree-latin-wght-italic.woff2") format("woff2");
  font-weight: 300 900;
  font-style: italic;
  font-display: swap;
}

/* ------------------------------------------------------------ light theme */
:root {
  color-scheme: light;

  /* Families */
  --font-sans: "Figtree", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "Cascadia Code", Consolas, "JetBrains Mono", monospace;

  /* Neutrals */
  --ink: #0f172a;          /* headings, strong text */
  --ink-2: #334155;        /* body text */
  --ink-3: #475569;        /* captions, meta, muted text */
  --line: #e2e8f0;         /* hairlines */
  --line-2: #eef2f6;       /* very subtle dividers */
  --control-border: #64748b; /* form control boundary, 3:1 or more */
  --surface: #ffffff;      /* page */
  --surface-2: #f8fafc;    /* inset panels, code */
  --surface-3: #f1f5f9;    /* hover washes, neutral callout */

  /* Brand accent: Yarpiz blue, softened on 2026-10-25 (owner feedback, TODO
     10.3). Candidates and measurements are in
     review/2026-10-25-2227-owner-feedback/tokens/. --accent is for fills, rules
     and marks. Text, links, focus rings and button fills use the
     darker --accent-ink. */
  --accent: #3b6fd1;
  --accent-ink: #2c5cbf;
  --accent-ink-hover: #234b9c;
  --accent-soft: #eef3fc;
  --accent-line: #c5d4f1;
  --on-accent: #ffffff;

  /* Second color: orange, for a few marks and fills only (DESIGN.md "Second
     color"). Deeper and redder than the amber warning state. */
  --accent-2: #ea580c;
  --accent-2-ink: #c2410c;
  --accent-2-soft: #fff1e7;
  --accent-2-line: #fdba74;

  /* Important callouts: violet, so they stay distinct from the brand blue. */
  --important-ink: #6d28d9;
  --important-soft: #f5f3ff;
  --important-line: #ddd6fe;

  /* Status inks (text safe), tints and lines */
  --info-ink: #1d6ad5;
  --info-soft: #eef3fc;
  --info-line: #c5d4f1;
  --success-ink: #15803d;
  --success-soft: #f0fdf4;
  --success-line: #bbf7d0;
  --warning-ink: #b45309;
  --warning-soft: #fffbeb;
  --warning-line: #fde68a;
  --danger-ink: #b91c1c;
  --danger-soft: #fef2f2;
  --danger-line: #fecaca;
  --on-danger: #ffffff;

  /* Links keep the accent everywhere, also inside tinted callouts. */
  --link: var(--accent-ink);
  --link-hover: var(--accent-ink-hover);

  /* Code */
  --code-bg: var(--surface-2);
  --code-inline-bg: var(--surface-3);

  /* Type scale (rem at 16px root) */
  --text-xs: 0.8125rem;    /* 13px: eyebrows, badges, table heads */
  --text-sm: 0.9375rem;    /* 15px: captions, controls, code */
  --text-base: 1.0625rem;  /* 17px: body */
  --text-lg: 1.1875rem;    /* 19px: h4, lead */
  --text-xl: 1.375rem;     /* 22px: h3 */
  --text-2xl: 1.75rem;     /* 28px: h2 */
  --text-3xl: 2.5rem;      /* 40px: page title */

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  --leading-tight: 1.15;
  --leading-snug: 1.3;
  --leading-normal: 1.5;
  --leading-relaxed: 1.65;

  /* Spacing */
  --space-2xs: 4px;
  --space-xs: 8px;
  --space-sm: 14px;
  --space-md: 22px;
  --space-lg: 34px;
  --space-xl: 50px;
  --space-2xl: 72px;
  --space-3xl: 100px;

  /* Radii */
  --radius-sm: 8px;
  --radius: 10px;
  --radius-md: 12px;
  --radius-lg: 20px;
  --radius-pill: 999px;

  /* Shadows (used sparingly: menus, dialogs, toasts, sticky banners) */
  --shadow-sm: 0 1px 2px rgb(15 23 42 / 0.06), 0 1px 1px rgb(15 23 42 / 0.04);
  --shadow: 0 4px 14px rgb(15 23 42 / 0.06), 0 1px 3px rgb(15 23 42 / 0.05);
  --shadow-lg: 0 18px 48px rgb(15 23 42 / 0.12), 0 4px 12px rgb(15 23 42 / 0.06);

  /* Motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --duration-fast: 120ms;
  --duration: 200ms;

  /* Focus */
  --focus-width: 2px;
  --focus-offset: 2px;
  --focus-ring: var(--focus-width) solid var(--accent-ink);

  /* Layout */
  --measure: 40rem;          /* reading column, about 75 characters */
  --rail-width: 16rem;       /* post sidebar: outline and main actions */
  --rail-gap: clamp(2rem, 4vw, 4rem);
  --page-wide: 72rem;        /* home, archive, header and footer frame */
  --gutter: var(--space-md);
  --scroll-offset: calc(var(--admin-bar-h, 0px) + var(--post-bar-h, 0px) + var(--space-md)); /* space above a heading reached by an anchor */

  /* Layers */
  --z-sticky: 20;
  --z-header: 30;
  --z-menu: 40;
  --z-toast: 60;
  --z-dialog: 80;
  --z-skip: 100;
}

/* Phones: one step smaller, narrower gutter. */
@media (max-width: 650px) {
  :root {
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.25rem;
    --text-2xl: 1.5rem;
    --text-3xl: 2rem;
    --gutter: 1rem;
  }
}

/* ------------------------------------------------------------ dark theme
   The same block twice: once for the system preference (unless the visitor
   chose light), once for an explicit choice. Keep them identical. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ink: #f8fafc;
    --ink-2: #cbd5e1;
    --ink-3: #a3b1c6;
    --line: #243044;
    --line-2: #1c2637;
    --control-border: #64748b;
    --surface: #0b1220;
    --surface-2: #111a2e;
    --surface-3: #162033;
    --accent: #7aa6ea;
    --accent-ink: #8db3ee;
    --accent-ink-hover: #b4ccf5;
    --accent-soft: #152339;
    --accent-line: #2a4576;
    --on-accent: #0b1220;
    --accent-2: #fb923c;
    --accent-2-ink: #fdac6a;
    --accent-2-soft: #2c1a0f;
    --accent-2-line: #7c2d12;
    --important-ink: #c4b5fd;
    --important-soft: #1e1636;
    --important-line: #4c1d95;
    --info-ink: #8db3ee;
    --info-soft: #152339;
    --info-line: #2a4576;
    --success-ink: #4ade80;
    --success-soft: #0f2a1c;
    --success-line: #166534;
    --warning-ink: #fbbf24;
    --warning-soft: #2a2410;
    --warning-line: #78350f;
    --danger-ink: #f87171;
    --danger-soft: #2b1620;
    --danger-line: #7f1d1d;
    --on-danger: #0b1220;
    --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);
    --shadow: 0 4px 14px rgb(0 0 0 / 0.35), 0 1px 3px rgb(0 0 0 / 0.3);
    --shadow-lg: 0 18px 48px rgb(0 0 0 / 0.5), 0 4px 12px rgb(0 0 0 / 0.3);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --ink: #f8fafc;
  --ink-2: #cbd5e1;
  --ink-3: #a3b1c6;
  --line: #243044;
  --line-2: #1c2637;
  --control-border: #64748b;
  --surface: #0b1220;
  --surface-2: #111a2e;
  --surface-3: #162033;
  --accent: #7aa6ea;
  --accent-ink: #8db3ee;
  --accent-ink-hover: #b4ccf5;
  --accent-soft: #152339;
  --accent-line: #2a4576;
  --on-accent: #0b1220;
  --accent-2: #fb923c;
  --accent-2-ink: #fdac6a;
  --accent-2-soft: #2c1a0f;
  --accent-2-line: #7c2d12;
  --important-ink: #c4b5fd;
  --important-soft: #1e1636;
  --important-line: #4c1d95;
  --info-ink: #8db3ee;
  --info-soft: #152339;
  --info-line: #2a4576;
  --success-ink: #4ade80;
  --success-soft: #0f2a1c;
  --success-line: #166534;
  --warning-ink: #fbbf24;
  --warning-soft: #2a2410;
  --warning-line: #78350f;
  --danger-ink: #f87171;
  --danger-soft: #2b1620;
  --danger-line: #7f1d1d;
  --on-danger: #0b1220;
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow: 0 4px 14px rgb(0 0 0 / 0.35), 0 1px 3px rgb(0 0 0 / 0.3);
  --shadow-lg: 0 18px 48px rgb(0 0 0 / 0.5), 0 4px 12px rgb(0 0 0 / 0.3);
}

/* Reduced motion: every transition and animation that reads these tokens
   becomes instant. base.css also shortens any remaining animation. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast: 0ms;
    --duration: 0ms;
  }
}

/* base.css */
/*
  Base: reset, document typography, links, focus ring, skip link and
  utility classes. Element defaults only. Components and page layout live in
  components.css, content.css, site.css and admin.css.
*/

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Anchor targets keep a little space above them. */
  scroll-padding-block-start: var(--scroll-offset);
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  min-block-size: 100vh;
  margin: 0;
  color: var(--ink-2);
  background: var(--surface);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

[hidden] {
  display: none !important;
}

img,
svg,
video,
canvas,
iframe {
  max-inline-size: 100%;
}

img,
video {
  block-size: auto;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

/* ------------------------------------------------------------ typography */
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  color: var(--ink);
  line-height: var(--leading-snug);
  text-wrap: balance;
}

h1 {
  font-size: var(--text-3xl);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: -0.015em;
}

h2 {
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  letter-spacing: -0.01em;
}

h3 {
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
}

h4 {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
}

p {
  margin: 0;
}

strong,
b {
  color: var(--ink);
  font-weight: var(--weight-semibold);
}

code,
kbd,
samp,
pre {
  font-family: var(--font-mono);
}

small {
  font-size: var(--text-sm);
}

hr {
  block-size: 0;
  margin: var(--space-lg) 0;
  border: 0;
  border-block-start: 1px solid var(--line);
}

/* ------------------------------------------------------------ links
   One rule for the whole site and the admin (owner, 2026-10-29, decisions log):
   links inside running text are underlined, because color alone does not mark a
   link in a paragraph (WCAG 1.4.1). Every other link (navigation, logo, cards,
   chips, tabs, menus, table names, breadcrumbs) has no underline, and hover only
   changes its color. Components never add a hover underline. */
a {
  color: var(--link);
  text-decoration: none;
}

a:hover {
  color: var(--link-hover);
}

/* Running text: rendered content, comments, alerts and hints, and a bare link
   inside a paragraph. :where keeps this weak, so a component rule wins. */
:where(.prose, .comment-body, .alert, .field-hint, .field-error, .toast-body) a,
:where(p, dd, blockquote, figcaption) > a:not([class]) {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

:where(.prose, .comment-body, .alert, .field-hint, .field-error, .toast-body) a:hover,
:where(p, dd, blockquote, figcaption) > a:not([class]):hover {
  text-decoration-thickness: 2px;
}

/* ------------------------------------------------------------ focus
   One ring for every interactive element, keyboard focus only. */
:focus:not(:focus-visible) {
  outline: none;
}

:where(a, button, input, select, textarea, summary, dialog, [tabindex]):focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* The main landmark receives focus from the skip link but shows no ring. */
main:focus {
  outline: none;
}

/* ------------------------------------------------------------ skip link */
.skip-link {
  position: fixed;
  z-index: var(--z-skip);
  inset-block-start: var(--space-xs);
  inset-inline-start: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
  color: var(--surface);
  background: var(--ink);
  border-radius: var(--radius-sm);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  transform: translateY(-200%);
}

.skip-link:focus {
  color: var(--surface);
  transform: none;
}

/* ------------------------------------------------------------ utilities */
.visually-hidden {
  position: absolute !important;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

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

/* components.css */
/*
  Shared components (src/ui/components) and small shared patterns used by
  both the public site and the admin app. Reference: DESIGN.md "Components".
*/

/* ------------------------------------------------------------ icon */
.icon {
  display: inline-block;
  flex: none;
  max-inline-size: none;
  vertical-align: middle;
}

/* ------------------------------------------------------------ eyebrow
   A small normal-case label above a heading (no all caps: owner, 2026-10-25). */
.eyebrow {
  margin: 0 0 var(--space-xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.eyebrow a {
  color: inherit;
  text-decoration: none;
}

/* One label style above every title (owner, 2026-10-29): gray even when it links, so it does not
   compete with the title. Hover darkens it, with no underline. */
.eyebrow a:hover {
  color: var(--ink);
}
/* ------------------------------------------------------------ buttons
   One primary (solid accent) action per view. Secondary is neutral, ghost
   is for toolbars and headers, danger is for destructive actions. */
.btn {
  --btn-fg: var(--on-accent);
  --btn-bg: var(--accent-ink);
  --btn-border: var(--accent-ink);
  --btn-bg-hover: var(--accent-ink-hover);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-block-size: 2.5rem;
  margin: 0;
  padding: 7px 18px;
  color: var(--btn-fg);
  background: var(--btn-bg);
  border: 1px solid var(--btn-border);
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--duration-fast) ease, border-color var(--duration-fast) ease, color var(--duration-fast) ease;
}

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

.btn:active {
  transform: translateY(1px);
}

.btn-secondary {
  --btn-fg: var(--ink);
  --btn-bg: var(--surface);
  --btn-border: var(--control-border);
  --btn-bg-hover: var(--surface-3);
}

.btn-secondary:hover {
  border-color: var(--control-border);
}

/* Remove buttons (admin edit lists and the private Liked items page) (owner, 2026-10-29): quiet at rest, red on hover and focus,
   and every one asks first (confirmRemove in Confirm.tsx). Delete buttons keep the danger variant. */
.btn-remove:is(:hover, :focus-visible) {
  color: var(--danger-ink);
  background: var(--danger-soft);
}

/* A quieter call to action: a brand tint, no border (owner, 2026-10-29: "Get the code"
   must not look like the purpose of the whole site). */
.btn-soft {
  --btn-fg: var(--accent-ink);
  --btn-bg: var(--accent-soft);
  --btn-border: transparent;
  --btn-bg-hover: var(--accent-line);
}

.btn-soft:hover {
  color: var(--accent-ink-hover);
}

.btn-ghost {
  --btn-fg: var(--ink-2);
  --btn-bg: transparent;
  --btn-border: transparent;
  --btn-bg-hover: var(--surface-3);
}

.btn-ghost:hover {
  color: var(--ink);
  border-color: transparent;
}

.btn-danger {
  --btn-fg: var(--on-danger);
  --btn-bg: var(--danger-ink);
  --btn-border: var(--danger-ink);
  --btn-bg-hover: color-mix(in srgb, var(--danger-ink) 85%, var(--ink));
}

.btn-sm {
  gap: 6px;
  min-block-size: 2rem;
  padding: 4px 12px;
  font-size: var(--text-sm);
}

.btn-icon {
  inline-size: 2.5rem;
  padding: 0;
}

.btn-sm.btn-icon {
  inline-size: 2rem;
}

.btn-block {
  display: flex;
  inline-size: 100%;
}

.btn:disabled,
.btn[aria-disabled="true"] {
  cursor: not-allowed;
  transform: none;
}

/* Busy buttons keep focus, so they keep full contrast: the spinner shows the state. */
.btn:disabled,
.btn[aria-disabled="true"]:not([aria-busy="true"]) {
  opacity: 0.5;
}

.btn:disabled:hover,
.btn[aria-disabled="true"]:hover {
  background: var(--btn-bg);
  border-color: var(--btn-border);
}

/* Pending: full contrast for the label and the focus ring, cursor shows progress. */
.btn[aria-busy="true"] {
  cursor: progress;
}

/* ------------------------------------------------------------ spinner */
.spinner {
  display: inline-block;
  flex: none;
  inline-size: 1em;
  block-size: 1em;
  border: 2px solid currentColor;
  border-inline-end-color: transparent;
  border-radius: 50%;
  vertical-align: -0.125em;
  animation: spin 0.8s linear infinite;
}

.spinner-wrap {
  display: inline-flex;
  align-items: center;
  color: var(--ink-3);
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* A static ring with a gap still reads as "busy". */
  .spinner {
    animation: none;
  }
}

/* ------------------------------------------------------------ badge */
.badge {
  --badge-fg: var(--ink-2);
  --badge-bg: var(--surface-3);
  --badge-line: var(--line);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 1px 8px;
  color: var(--badge-fg);
  background: var(--badge-bg);
  border: 1px solid transparent; /* owner rule 2026-10-26: no accent borders on rounded components */
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: 1.5;
  white-space: nowrap;
}

.badge-accent {
  --badge-fg: var(--accent-ink);
  --badge-bg: var(--accent-soft);
  --badge-line: var(--accent-line);
}

.badge-success {
  --badge-fg: var(--success-ink);
  --badge-bg: var(--success-soft);
  --badge-line: var(--success-line);
}

.badge-warning {
  --badge-fg: var(--warning-ink);
  --badge-bg: var(--warning-soft);
  --badge-line: var(--warning-line);
}

.badge-danger {
  --badge-fg: var(--danger-ink);
  --badge-bg: var(--danger-soft);
  --badge-line: var(--danger-line);
}

/* ------------------------------------------------------------ chip
   A link pill: categories and tags. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-block-size: 2rem;
  padding: 3px 12px;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  text-decoration: none;
  transition: border-color var(--duration-fast) ease, background-color var(--duration-fast) ease;
}

.chip:hover {
  color: var(--ink);
  background: var(--accent-soft);
  text-decoration: none;
}

.chip-count {
  color: var(--ink-3);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ alert */
.alert {
  --alert-fg: var(--info-ink);
  --alert-bg: var(--info-soft);
  --alert-line: var(--info-line);
  display: flex;
  gap: var(--space-sm);
  margin-block: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  color: var(--ink-2);
  background: var(--alert-bg);
  border: 1px solid transparent; /* owner rule 2026-10-26: no accent borders on rounded components */
  border-radius: var(--radius);
}

.alert-icon {
  margin-block-start: 3px;
  color: var(--alert-fg);
}

.alert-body {
  flex: 1;
  min-inline-size: 0;
}

.alert-body > * + * {
  margin-block-start: var(--space-xs);
}

.alert-title {
  color: var(--ink);
  font-weight: var(--weight-semibold);
}

.alert-success {
  --alert-fg: var(--success-ink);
  --alert-bg: var(--success-soft);
  --alert-line: var(--success-line);
}

.alert-warning {
  --alert-fg: var(--warning-ink);
  --alert-bg: var(--warning-soft);
  --alert-line: var(--warning-line);
}

.alert-danger {
  --alert-fg: var(--danger-ink);
  --alert-bg: var(--danger-soft);
  --alert-line: var(--danger-line);
}

/* ------------------------------------------------------------ form fields
   One alignment system: label, control, then hint and error below the
   control. .form-row places fields on a grid (see admin-app.css), and
   because hints never sit between label and control, every control in a
   row starts at the same height. */
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-inline-size: 0;
}

.field-label {
  color: var(--ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.field-required {
  margin-inline-start: var(--space-2xs);
  padding: 0 6px;
  color: var(--ink-3);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  vertical-align: 1px;
}

.field-hint {
  color: var(--ink-3);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.field-error {
  color: var(--danger-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: var(--leading-normal);
}

.input,
.textarea,
.select {
  display: block;
  inline-size: 100%;
  min-block-size: 2.5rem;
  padding: 7px 12px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  transition: border-color var(--duration-fast) ease, box-shadow var(--duration-fast) ease;
}

.input::placeholder,
.textarea::placeholder {
  color: var(--ink-3);
  opacity: 1;
}

.input:hover,
.textarea:hover,
.select:hover {
  border-color: var(--ink-3);
}

/* Text controls show the ring on any focus, since typing needs it. */
.input:focus,
.textarea:focus,
.select:focus {
  outline: var(--focus-ring);
  outline-offset: 0;
  border-color: var(--accent-ink);
}

.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select[aria-invalid="true"] {
  border-color: var(--danger-ink);
}

.input:disabled,
.textarea:disabled,
.select:disabled {
  color: var(--ink-3);
  background: var(--surface-2);
  cursor: not-allowed;
}

.textarea {
  min-block-size: 6rem;
  line-height: var(--leading-normal);
  resize: vertical;
}

.select {
  padding-inline-end: 2.25rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: calc(100% - 18px) 55%, calc(100% - 13px) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  appearance: none;
}

/* The open list follows the design where the browser supports a customizable select
   (Chromium). Elsewhere the browser draws its own list. Keyboard and screen reader
   behavior stay native either way. */
@supports (appearance: base-select) {
  .select,
  .select::picker(select) {
    appearance: base-select;
  }

  .select::picker-icon {
    display: none; /* the chevron below stays the one indicator */
  }

  .select::picker(select) {
    margin-block: 4px;
    padding: 4px;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    font-size: var(--text-base);
  }

  .select option {
    gap: var(--space-xs);
    min-block-size: 2.25rem;
    padding: 6px 10px;
    border-radius: var(--radius-sm);
    cursor: pointer;
  }

  .select option::checkmark {
    color: var(--accent-ink);
  }

  .select option:hover,
  .select option:focus-visible {
    background: var(--surface-2);
    outline: none;
  }

  .select option:checked {
    color: var(--accent-ink);
    background: var(--accent-soft);
    font-weight: var(--weight-semibold);
  }

  .select optgroup {
    padding-block-start: 4px;
    color: var(--ink-3);
    font-size: var(--text-sm);
  }
}

.checkbox {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--space-xs);
  color: var(--ink-2);
  line-height: var(--leading-normal);
  cursor: pointer;
}

.checkbox-input {
  flex: none;
  inline-size: 1.125rem;
  block-size: 1.125rem;
  margin: 0.2em 0 0;
  accent-color: var(--accent-ink);
}

/* Vertical rhythm for forms and button groups. */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}

/* A labelled divider: "or" between sign in options. */
.divider {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-block: var(--space-md);
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.divider::before,
.divider::after {
  flex: 1;
  block-size: 1px;
  background: var(--line);
  content: "";
}

/* Term and value pairs: account profile, admin details. */
.details-list {
  display: grid;
  gap: var(--space-xs);
  margin: 0;
}

.details-list > div {
  display: grid;
  grid-template-columns: minmax(6rem, 9rem) minmax(0, 1fr);
  gap: var(--space-sm);
  padding-block: var(--space-xs);
  border-block-end: 1px solid var(--line-2);
}

.details-list dt {
  color: var(--ink-3);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.details-list dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------ disclosure
   details.disclosure > summary + .disclosure-body. Shared by the Disclosure
   component, the "::: details" content block and the post Contents fold.
   The chevron is the Icon component. Content blocks have no icon, so a CSS
   chevron is drawn for them. */
.disclosure,
.toc-fold {
  margin-block: var(--space-md);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

.disclosure > summary,
.toc-fold > summary {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 12px var(--space-md);
  color: var(--ink);
  border-radius: calc(var(--radius-sm) - 1px);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  list-style: none;
  cursor: pointer;
  transition: background-color var(--duration-fast) ease;
}

.disclosure > summary::-webkit-details-marker,
.toc-fold > summary::-webkit-details-marker {
  display: none;
}

.disclosure > summary:hover,
.toc-fold > summary:hover {
  background: var(--surface-2);
}

/* CSS chevron when the summary has no icon (content blocks). */
.disclosure > summary:not(:has(.disclosure-chevron))::after {
  flex: none;
  inline-size: 0.5em;
  block-size: 0.5em;
  margin-inline-start: auto;
  border-block-end: 2px solid var(--ink-3);
  border-inline-end: 2px solid var(--ink-3);
  content: "";
  transform: translateY(-25%) rotate(45deg);
  transition: transform var(--duration) var(--ease);
}

.disclosure[open] > summary:not(:has(.disclosure-chevron))::after {
  transform: translateY(25%) rotate(-135deg);
}

.disclosure-label {
  flex: 1;
  min-inline-size: 0;
}

.disclosure-chevron {
  color: var(--ink-3);
  transition: transform var(--duration) var(--ease);
}

details[open] > summary > .disclosure-chevron {
  transform: rotate(180deg);
}

.disclosure[open] > summary,
.toc-fold[open] > summary {
  border-block-end: 1px solid var(--line-2);
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}

.disclosure-body {
  padding: var(--space-sm) var(--space-md) var(--space-md);
}

.disclosure-body > :first-child {
  margin-block-start: 0;
}

.disclosure-body > :last-child {
  margin-block-end: 0;
}

/* ------------------------------------------------------------ dialog */
.dialog {
  inline-size: min(32rem, calc(100vw - 2 * var(--gutter)));
  max-block-size: calc(100vh - 2 * var(--gutter));
  padding: 0;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}

.dialog::backdrop {
  background: rgb(15 23 42 / 0.45);
}

.dialog[open] {
  display: flex;
  flex-direction: column;
  animation: dialog-in var(--duration) var(--ease);
}

@keyframes dialog-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

.dialog-head {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-sm) var(--space-sm) var(--space-md);
  border-block-end: 1px solid var(--line);
}

.dialog-title {
  flex: 1;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
}

.dialog-body {
  flex: 1;
  padding: var(--space-md);
  overflow: auto;
}

.dialog-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface-2);
  border-block-start: 1px solid var(--line);
}

/* Touch devices: comfortable targets for small buttons (WCAG 2.5.8 asks for 24 px, we aim for 44 px). */
@media (pointer: coarse) {
  .btn-sm {
    min-block-size: 2.75rem;
  }
  .btn-sm.btn-icon {
    min-inline-size: 2.75rem;
  }
}

/* ------------------------------------------------------------ back to top
   src/client/back-to-top.ts, on the public site and in the admin (owner,
   2026-10-29). Bottom right, under toasts, which sit centered. */
.back-to-top {
  position: fixed;
  z-index: var(--z-sticky);
  inset-block-end: var(--space-md);
  inset-inline-end: var(--space-md);
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  padding: 0;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 50%;
  box-shadow: var(--shadow);
  cursor: pointer;
  animation: toast-in var(--duration) var(--ease);
}

.back-to-top:hover {
  color: var(--accent-ink);
  border-color: var(--accent-ink);
}

.back-to-top[hidden] {
  display: none;
}

@media print {
  .back-to-top {
    display: none;
  }
}

/* ------------------------------------------------------------ toasts
   src/ui/components/Toasts.tsx, used by the admin and the public islands (TODO 12.2).
   .toast-region is an aria-live="polite" container, centered at the bottom.
   Redesign 2026-10-29 (owner): a compact neutral card with a hairline and a
   shadow. Only the icon carries the tone, so no tinted or colored frame. */
.toast-region {
  position: fixed;
  z-index: var(--z-toast);
  inset-block-end: var(--space-md);
  inset-inline: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  padding-inline: var(--space-md);
  pointer-events: none;
}

.toast {
  --toast-accent: var(--info-ink);
  display: flex;
  align-items: center;
  gap: 10px;
  max-inline-size: 28rem;
  min-block-size: 2.75rem;
  padding: 6px 6px 6px 14px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  pointer-events: auto;
  animation: toast-in var(--duration) var(--ease);
}

.toast > .icon {
  flex: none;
  color: var(--toast-accent);
}

.toast-body {
  flex: 1;
  min-inline-size: 0;
  padding-block: 4px;
}

/* One optional link per toast, for example "View liked items". */
.toast-link {
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.toast .btn-icon {
  flex: none;
  inline-size: 1.75rem;
  min-block-size: 1.75rem;
  block-size: 1.75rem;
  padding: 0;
  color: var(--ink-3);
}

.toast .btn-icon:hover {
  color: var(--ink);
}

.toast-success {
  --toast-accent: var(--success-ink);
}

.toast-warning {
  --toast-accent: var(--warning-ink);
}

.toast-danger {
  --toast-accent: var(--danger-ink);
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

/* Windows high contrast: keep decorations that rely on a background visible. */
@media (forced-colors: active) {
  .tab-dot {
    forced-color-adjust: none;
    background: Mark;
  }
  .badge,
  .toast,
  .stat,
  .card {
    border: 1px solid CanvasText;
  }
}

/* Avatar (TODO 12.29): a profile picture, or initials when there is none.
   Shared by the account page, comments, the header menu and the admin. */
.avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  inline-size: 3rem;
  block-size: 3rem;
  color: var(--ink-2);
  background: var(--surface-3);
  border-radius: 50%;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  object-fit: cover;
  vertical-align: middle;
}

.avatar-sm {
  inline-size: 1.75rem;
  block-size: 1.75rem;
  font-size: var(--text-xs);
}

.avatar-lg {
  inline-size: 5rem;
  block-size: 5rem;
  font-size: var(--text-xl);
}

/* Admin users list and user detail. */
.user-row-avatar {
  margin-inline-end: var(--space-2xs);
}

.user-picture {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}

/* content.css */
/*
  Post and page body: everything inside .prose, including every class that
  src/content/render.ts emits. The admin preview pane uses the same rules,
  so a preview matches the public page.

  Rules: one reading column, flat blocks (hairline and tint, no shadows),
  and wide code, math and tables scroll inside their own box, never the page.
  Reference: DESIGN.md "Content rendering".
*/

/* ------------------------------------------------------------ flow */
.prose {
  color: var(--ink-2);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  overflow-wrap: break-word;
}

/* Contain floats from left and right aligned figures. */
.prose::after {
  display: block;
  clear: both;
  content: "";
}

.prose > :first-child {
  margin-block-start: 0;
}

.prose :where(p, ul, ol, dl, blockquote, pre, table) {
  margin-block: 0 var(--space-sm);
}

/* No scroll-margin here: the html scroll-padding already keeps anchors clear of
   the bars, and the two added up, so headings landed twice as low (2026-10-29). */
.prose :where(h2, h3, h4) {
  clear: both;
}

.prose h2 {
  margin-block: var(--space-xl) var(--space-sm);
  font-size: var(--text-2xl);
}

.prose h3 {
  margin-block: var(--space-lg) var(--space-xs);
  font-size: var(--text-xl);
}

.prose h4 {
  margin-block: var(--space-md) var(--space-xs);
  font-size: var(--text-lg);
}

.prose :where(ul, ol) {
  padding-inline-start: 1.4em;
}

.prose li {
  margin-block: 0.25em;
}

.prose li > :where(p, ul, ol) {
  margin-block: 0.25em;
}

.prose li::marker {
  color: var(--ink-3);
}

.prose :where(em, i, cite) {
  font-style: italic;
}

.prose a {
  overflow-wrap: anywhere;
}

.prose blockquote {
  margin-inline: 0;
  padding: var(--space-sm) var(--space-md);
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.prose blockquote > :last-child {
  margin-block-end: 0;
}

.prose hr {
  margin-block: var(--space-xl);
}

/* Plain Markdown images: block, never wider than the column. */
.prose img {
  block-size: auto;
}

.prose p > img:only-child {
  display: block;
  margin-inline: auto;
  border-radius: var(--radius-sm);
}

/* ------------------------------------------------------------ inline code */
.prose :not(pre) > code {
  padding: 0.1em 0.35em;
  color: var(--ink);
  background: var(--code-inline-bg);
  border: 1px solid var(--line);
  border-radius: 5px;
  font-size: 0.88em;
  overflow-wrap: anywhere;
}

.prose a code {
  color: inherit;
}

/* ------------------------------------------------------------ code block
   figure.code-block > figcaption.code-head (.code-title, .code-lang, and
   .code-actions added by the client script) + pre.shiki > code. Shiki
   writes --shiki-light and --shiki-dark on each token span. */
.code-block {
  margin: var(--space-md) 0;
  overflow: hidden;
  background: var(--code-bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.code-head {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-block-size: 2.5rem;
  padding: 4px 6px 4px var(--space-md);
  background: var(--surface);
  border-block-end: 1px solid var(--line);
  font-family: var(--font-sans);
  line-height: var(--leading-snug);
}

.code-title {
  flex: 1;
  min-inline-size: 0;
  color: var(--ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

.code-lang {
  flex: none;
  color: var(--ink-3);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.04em;
}

/* Copy and Download buttons, injected by the client script. */
.code-actions {
  display: flex;
  flex: none;
  gap: 2px;
}

/* button.code-action: a square, icon-only ghost button (Lucide icon, label
   in aria-label and title). The script swaps the icon to a check (.is-done)
   or an alert (.is-error) for two seconds after Copy. */
.code-action {
  display: inline-grid;
  place-items: center;
  inline-size: 2rem;
  block-size: 2rem;
  padding: 0;
  color: var(--ink-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: calc(var(--radius-sm) - 2px);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--duration-fast) ease, color var(--duration-fast) ease;
}

.code-action:hover {
  color: var(--ink);
  background: var(--surface-3);
  border-color: var(--line);
}

.code-action:active {
  transform: translateY(1px);
}

.code-action:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.code-action .icon {
  inline-size: 16px;
  block-size: 16px;
}

.code-action.is-done,
.code-actions .is-done {
  color: var(--success-ink);
}

.code-action.is-error {
  color: var(--danger-ink);
}

/* The language label and the buttons sit together at the end. */
.code-lang + .code-actions {
  margin-inline-start: var(--space-2xs);
  padding-inline-start: var(--space-2xs);
  border-inline-start: 1px solid var(--line);
}

.code-block pre,
.prose pre {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  overflow-x: auto;
  color: var(--ink);
  background: var(--code-bg);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  tab-size: 4;
  white-space: pre;
}

.code-block pre code {
  font-family: inherit;
  font-size: inherit;
  font-variant-ligatures: none;
}

.code-block pre:focus-visible {
  outline-offset: -2px;
}

.shiki span {
  color: var(--shiki-light);
  font-style: var(--shiki-light-font-style);
  font-weight: var(--shiki-light-font-weight);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .shiki span {
    color: var(--shiki-dark);
    font-style: var(--shiki-dark-font-style);
    font-weight: var(--shiki-dark-font-weight);
  }
}

:root[data-theme="dark"] .shiki span {
  color: var(--shiki-dark);
  font-style: var(--shiki-dark-font-style);
  font-weight: var(--shiki-dark-font-weight);
}

/* A code block that is the whole disclosure body fills the disclosure. */
.disclosure-body > .code-block:only-child {
  margin: calc(-1 * var(--space-sm)) calc(-1 * var(--space-md)) calc(-1 * var(--space-md));
  border: 0;
  border-radius: 0 0 calc(var(--radius-sm) - 1px) calc(var(--radius-sm) - 1px);
}

@media (max-width: 650px) {
  .code-head {
    padding-inline-start: var(--space-sm);
  }

  .code-block pre {
    padding-inline: var(--space-sm);
  }
}

/* ------------------------------------------------------------ math (KaTeX)
   Display math scrolls sideways inside .equation. The block padding keeps
   the scrollbar clear of descenders. */
.equation {
  margin: var(--space-md) 0;
  padding-block: var(--space-2xs) var(--space-xs);
  overflow-x: auto;
  overflow-y: hidden;
  color: var(--ink);
}

.equation .katex-display {
  margin: 0;
}

.equation .katex-display > .katex {
  padding-inline: 2px;
}

.prose .katex {
  font-size: 1.08em;
}

/* Long inline formulas may break, never widen the page. */
.prose p .katex {
  white-space: normal;
}

.equation:focus-visible {
  outline: var(--focus-ring);
}

.math-error {
  padding: 0.1em 0.35em;
  color: var(--danger-ink);
  background: var(--danger-soft);
  border-color: var(--line); /* owner rule: the tint carries the meaning, not the border */
  border-radius: 5px;
}

/* ------------------------------------------------------------ diagrams (Mermaid)
   figure.diagram holds the source in pre.diagram-source. The client script
   draws the SVG, adds .is-drawn and moves the source into details.diagram-details. */
.diagram {
  margin: var(--space-md) 0;
  padding: var(--space-sm) var(--space-md);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-align: center;
}

.diagram > svg {
  display: block;
  max-inline-size: 100%;
  block-size: auto;
  margin-inline: auto;
}

.diagram-source {
  margin: 0;
  text-align: start;
}

.diagram.is-drawn .diagram-source {
  margin-block-start: var(--space-xs);
}

.diagram-details {
  margin-block-start: var(--space-xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
  text-align: start;
}

.diagram-details > summary {
  cursor: pointer;
}

.diagram-error {
  margin: 0 0 var(--space-xs);
  color: var(--danger-ink);
  font-size: var(--text-sm);
}

/* ------------------------------------------------------------ tables */
.table-wrap {
  margin: var(--space-md) 0;
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.table-wrap:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

.table-wrap table {
  inline-size: 100%;
  margin: 0;
  border-collapse: collapse;
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  font-variant-numeric: tabular-nums;
}

.table-wrap :where(th, td) {
  padding: var(--space-xs) var(--space-sm);
  text-align: start;
  vertical-align: top;
  border-block-end: 1px solid var(--line);
}

.table-wrap th {
  color: var(--ink);
  background: var(--surface-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.table-wrap tr:last-child > td {
  border-block-end: 0;
}

.table-wrap :where(th, td)[style*="right"] {
  text-align: end;
}

/* ------------------------------------------------------------ figures
   .figure.align-{center,left,right,wide,full}. Left and right float only on
   wide screens. On phones every figure is a centered block. */
.figure {
  margin: var(--space-md) 0;
}

.figure img {
  display: block;
  margin-inline: auto;
  border-radius: var(--radius-sm);
}

.figure figcaption,
.video figcaption,
.embed figcaption,
.simulator figcaption,
.demo figcaption,
.island-note {
  margin-block-start: var(--space-xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  text-align: center;
}

.figure figcaption > :last-child {
  margin-block-end: 0;
}

/* Wide fills the column even when the image is narrower. */
.figure.align-wide img,
.figure.align-full img {
  inline-size: 100%;
}

@media (min-width: 760px) {
  .figure.align-left,
  .figure.align-right {
    max-inline-size: 45%;
    margin-block: var(--space-2xs) var(--space-sm);
  }

  .figure.align-left {
    float: inline-start;
    margin-inline-end: var(--space-md);
  }

  .figure.align-right {
    float: inline-end;
    margin-inline-start: var(--space-md);
  }

  .figure.align-left figcaption,
  .figure.align-right figcaption {
    text-align: start;
  }
}

/* Posts without a contents rail have room to grow past the column.
   site.css adds the .has-toc variant, which keeps wide and full in the
   column so they never cover the rail. */
@media (min-width: 64rem) {
  .post:not(.has-toc) .prose .figure.align-wide {
    margin-inline: -6rem;
  }

  .post:not(.has-toc) .prose .figure.align-full {
    inline-size: min(calc(100vw - 2 * var(--gutter) - 20px), var(--page-wide));
    margin-inline-start: 50%;
    translate: -50% 0;
  }
}

/* ------------------------------------------------------------ callouts
   Four kinds: one hairline boundary and a light tint, title semibold at
   body size. Links keep the accent ink (--link is set on :root). */
.callout {
  --callout-soft: var(--surface-3);
  --callout-line: var(--line);
  margin: var(--space-md) 0;
  padding: var(--space-sm) var(--space-md);
  color: var(--ink-2);
  background: var(--callout-soft);
  border: 0; /* owner rule 2026-10-26: no accent borders on rounded components */
  border-radius: var(--radius);
}

.callout-tip {
  --callout-soft: var(--accent-soft);
  --callout-line: var(--accent-line);
}

.callout-important {
  --callout-soft: var(--important-soft);
  --callout-line: var(--important-line);
}

.callout-warning {
  --callout-soft: var(--warning-soft);
  --callout-line: var(--warning-line);
}

.callout > .callout-title {
  margin: 0 0 4px;
  color: var(--ink);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.callout > :last-child {
  margin-block-end: 0;
}

.callout .code-block,
.callout .table-wrap {
  background: var(--surface);
}

/* ------------------------------------------------------------ disclosure
   details.disclosure: see components.css. Inside prose only spacing. */
.prose .disclosure + .disclosure {
  margin-block-start: calc(-1 * var(--space-md) + var(--space-xs));
}

/* ------------------------------------------------------------ video
   A YouTube facade: a link with the thumbnail and a play button. The client
   script swaps it for the nocookie iframe on click. Without the script the
   link opens YouTube. */
.video {
  margin: var(--space-md) 0;
}

.video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #0b1220;
  border-radius: var(--radius);
}

.video-frame iframe,
.video > video {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border: 0;
}

.video > video {
  block-size: auto;
  aspect-ratio: 16 / 9;
  background: #0b1220;
  border-radius: var(--radius);
}

.video-facade {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #ffffff;
  background: linear-gradient(135deg, #0b1220, #13233a);
  text-decoration: none;
}

.video-facade:hover {
  color: #ffffff;
}

.video-facade:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
}

.video-facade img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.video-play {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 10px 18px 10px 14px;
  color: #ffffff;
  background: rgb(11 18 32 / 0.82);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  transition: background-color var(--duration-fast) ease;
}

.video-facade:hover .video-play,
.video-facade:focus-visible .video-play {
  background: #1d4ed8;
}

/* Play triangle drawn with borders (no image request, no icon markup). */
.video-play-icon {
  flex: none;
}

/* A playlist without a named first video: a neutral branded cover, no remote image. */
.video-placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: start end;
  padding: var(--space-md);
  color: rgb(255 255 255 / 0.28);
  background: radial-gradient(circle at 20% 15%, rgb(37 99 235 / 0.45), transparent 60%), linear-gradient(135deg, #0b1220, #13233a);
}

/* Title, facts and the YouTube link sit below the frame, so they stay after the click. */
.video .video-info {
  text-align: start;
}

.video-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-xs);
}

.video-title {
  color: var(--ink);
  font-weight: var(--weight-semibold);
  margin-inline-end: auto;
}

.video-badge,
.video-duration {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.video-badge {
  color: var(--accent-2-ink);
  background: var(--accent-2-soft);
  box-shadow: inset 0 0 0 1px var(--accent-2-line);
}

.video-duration {
  color: var(--ink-2);
  background: var(--surface-2);
  font-variant-numeric: tabular-nums;
}

.video-watch {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: var(--weight-semibold);
}

.video-caption {
  margin-block-start: var(--space-2xs);
}

.video-note {
  position: absolute;
  inset-block-end: var(--space-xs);
  inset-inline-end: var(--space-xs);
  padding: 2px 8px;
  color: #e2e8f0;
  background: rgb(11 18 32 / 0.82);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
}

/* ------------------------------------------------------------ embed and simulator */
.embed,
.simulator {
  margin: var(--space-md) 0;
}

.embed iframe,
.simulator-frame {
  display: block;
  inline-size: 100%;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.simulator-label {
  display: inline-block;
  margin-inline-end: var(--space-2xs);
  padding: 0 8px;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

/* ------------------------------------------------------------ download card */
.download-card {
  margin: var(--space-md) 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.download-link {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  color: var(--ink);
  border-radius: calc(var(--radius) - 1px);
  text-decoration: none;
  transition: background-color var(--duration-fast) ease;
}

.download-link:hover {
  color: var(--ink);
  background: var(--accent-soft);
}
/* Lucide download (or external link) icon in a tinted square. */
.download-icon {
  display: inline-grid;
  place-items: center;
  flex: none;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
}

.download-text {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
}

.download-label {
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.download-file {
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

.download-note {
  padding: 0 var(--space-md) var(--space-sm) calc(var(--space-md) + 2.25rem + var(--space-sm));
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.download-note > :last-child {
  margin-block-end: 0;
}

/* ------------------------------------------------------------ islands and demos */
.island {
  margin: var(--space-md) 0;
}

.island-note {
  margin-block: calc(-1 * var(--space-sm)) var(--space-md);
}

.demo {
  margin: 0;
  padding: var(--space-sm) var(--space-md) var(--space-md);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.demo svg {
  display: block;
  inline-size: 100%;
  block-size: auto;
  overflow: visible;
}

.demo .axis {
  stroke: var(--ink-3);
  stroke-width: 1;
}

.demo .target {
  stroke: var(--ink-3);
  stroke-width: 1;
  stroke-dasharray: 4 4;
}

.demo .tick {
  fill: var(--ink-3);
  font-family: var(--font-sans);
  font-size: 12px;
}

.demo .curve {
  fill: none;
  stroke: var(--accent-ink);
  stroke-width: 2.5;
  stroke-linejoin: round;
}

.demo-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-sm) var(--space-md);
  margin-block-start: var(--space-sm);
}

.demo-controls label {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  color: var(--ink-2);
  font-size: var(--text-sm);
}

.demo-controls output {
  color: var(--ink);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.demo input[type="range"] {
  inline-size: 100%;
  min-block-size: 1.75rem;
  margin: 0;
  accent-color: var(--accent-ink);
}

/* Validation placeholders. Publishing refuses these, so they appear only in
   previews. */
.island-missing,
.unknown-block {
  margin: var(--space-md) 0;
  padding: var(--space-sm) var(--space-md);
  color: var(--danger-ink);
  background: var(--danger-soft);
  border-radius: var(--radius);
}

.island-missing:empty::before {
  content: "Unknown demo";
}

/* ------------------------------------------------------------ legacy HTML
   Sanitized markup imported from WordPress. Keep it inside the column. */
.legacy {
  margin-block: var(--space-md);
}

.legacy :where(img, video, iframe) {
  max-inline-size: 100%;
  block-size: auto;
}

.legacy iframe {
  display: block;
  inline-size: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: var(--radius);
}

.legacy :where(.alignleft, .alignright, .aligncenter) {
  display: block;
  margin-inline: auto;
}

.legacy table {
  display: block;
  max-inline-size: 100%;
  overflow-x: auto;
  border-collapse: collapse;
}

.legacy :where(th, td) {
  padding: var(--space-2xs) var(--space-xs);
  border: 1px solid var(--line);
}

.legacy::after {
  display: block;
  clear: both;
  content: "";
}

/* ------------------------------------------------------------ how to cite
   section.cite at the end of every post (PostView.tsx): two code blocks
   (plain text and BibTeX) that get the usual Copy button. The plain
   citation wraps, so a phone reader sees it whole. */
.cite {
  margin-block: var(--space-xl) 0;
}

.cite-title {
  margin-block-end: var(--space-2xs);
  font-size: var(--text-lg);
}

.cite-intro {
  margin-block-end: var(--space-sm);
  color: var(--ink-2);
}

.cite .code-block {
  margin-block: var(--space-sm) 0;
}

.cite .code-block[data-lang="text"] pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* site.css */
/*
  Public site layout: header, footer, home, cards, archives, search, the post
  page with its contents rail, comments, pagination, and the sign in and
  account pages (body.layout-app). Reference: DESIGN.md "Layout".
*/

/* ------------------------------------------------------------ frame */
.container {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
  margin-inline: auto;
}

.container.narrow {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--measure));
}

/* The footer stays at the bottom of short pages. */
body {
  display: flex;
  flex-direction: column;
}

.site-main {
  display: block;
  flex: 1;
  padding-block: var(--space-lg) var(--space-2xl);
}

/* ------------------------------------------------------------ header */
.site-header {
  background: var(--surface);
  border-block-end: 1px solid var(--line);
}

.site-header.site-header-sticky {
  position: sticky;
  inset-block-start: var(--admin-bar-h, 0px);
}

.site-header-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-md);
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
  min-block-size: 3.75rem;
  margin-inline: auto;
  padding-block: var(--space-xs);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--ink);
  font-size: 1.3rem;
  font-weight: var(--weight-bold);
  letter-spacing: -0.01em;
  text-decoration: none;
}

.wordmark:hover {
  color: var(--ink);
  text-decoration: none;
}

.wordmark-mark {
  flex: none;
}

.wordmark-mark path {
  fill: var(--accent);
}

/* The main menu and its panels: menu.css (TODO 12.44). */

.header-tools {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-inline-start: auto;
}

.header-search {
  position: relative;
  display: flex;
  align-items: center;
}

.header-search .input {
  inline-size: 14rem;
  min-block-size: 2.5rem;
  padding-inline-end: 2.5rem;
  font-size: var(--text-sm);
}

.header-search .btn {
  position: absolute;
  inset-inline-end: 2px;
}

.header-search-link {
  display: none;
}

/* The toggle shows the theme it switches to. */
.theme-icon-light {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-icon-light {
    display: inline-block;
  }

  :root:not([data-theme="light"]) .theme-icon-dark {
    display: none;
  }
}

:root[data-theme="dark"] .theme-icon-light {
  display: inline-block;
}

:root[data-theme="dark"] .theme-icon-dark {
  display: none;
}

.account-slot {
  display: flex;
  align-items: center;
}

.account-slot > a {
  display: inline-flex;
  align-items: center;
  min-block-size: 2.5rem;
  padding: 0 var(--space-sm);
  color: var(--ink);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  text-decoration: none;
}

.account-slot > a:hover {
  background: var(--surface-3);
}

/* Account menu for signed-in visitors (injected by the client script):
   .account-slot > details.account-menu > summary + .account-menu-list */
.account-menu {
  position: relative;
}

.account-menu > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  max-inline-size: 14rem;
  min-block-size: 2.5rem;
  padding: 0 var(--space-sm);
  overflow: hidden;
  color: var(--ink);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  text-overflow: ellipsis;
  list-style: none;
  cursor: pointer;
}

.account-menu > summary::-webkit-details-marker {
  display: none;
}

/* A small CSS chevron after the name. */
.account-menu > summary::after {
  flex: none;
  inline-size: 0.4em;
  block-size: 0.4em;
  margin-inline-start: 2px;
  border-block-end: 2px solid var(--ink-3);
  border-inline-end: 2px solid var(--ink-3);
  content: "";
  transform: translateY(-20%) rotate(45deg);
}

.account-menu > summary:hover,
.account-menu[open] > summary {
  background: var(--surface-3);
}

.account-menu-list {
  position: absolute;
  z-index: var(--z-menu);
  inset-block-start: calc(100% + 6px);
  inset-inline-end: 0;
  display: flex;
  flex-direction: column;
  min-inline-size: 12rem;
  margin: 0;
  padding: var(--space-2xs);
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
}

.account-menu-list form {
  margin: 0;
}

.account-menu-list :is(a, button) {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  inline-size: 100%;
  min-block-size: 2.25rem;
  padding: 0 var(--space-sm);
  color: var(--ink-2);
  background: transparent;
  border: 0;
  border-radius: calc(var(--radius-sm) - 2px);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-align: start;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.account-menu-list :is(a, button):hover {
  color: var(--ink);
  background: var(--surface-3);
}

.account-menu-list :is(a, button) .icon {
  flex: none;
  color: var(--ink-3);
}

.account-menu-list :is(a, button):hover .icon {
  color: var(--ink-2);
}

.account-menu-list :is(a, button):focus-visible {
  outline-offset: -2px;
}

/* Separates the sign out form from the links above it. */
.account-menu-list form,
.account-menu-list li:has(> form) {
  margin-block-start: var(--space-2xs);
  padding-block-start: var(--space-2xs);
  border-block-start: 1px solid var(--line-2);
}

.account-menu-list li:has(> form) form {
  margin: 0;
  padding: 0;
  border: 0;
}

/* Below 860px the search field becomes a link to the search page. */

@media (max-width: 860px) {
  .header-search {
    display: none;
  }

  .header-search-link {
    display: inline-flex;
  }
}

/* ------------------------------------------------------------ footer */
.site-footer {
  color: var(--ink-3);
  background: var(--surface-2);
  border-block-start: 1px solid var(--line);
  font-size: var(--text-sm);
}

.site-footer-inner {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
  margin-inline: auto;
  padding-block: var(--space-lg);
}

.footer-heading {
  margin-block-end: var(--space-xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.footer-main {
  display: grid;
  gap: var(--space-lg);
  margin-block-end: var(--space-lg);
}

.footer-brand .wordmark {
  display: inline-flex;
  padding-block: 0;
  color: var(--ink);
}

.footer-brand .footer-text {
  max-inline-size: 22rem;
  margin-block-start: var(--space-xs);
  line-height: var(--leading-normal);
}

.footer-categories ul {
  margin: 0;
  padding: 0;
  list-style: none;
  columns: 3 9.5rem;
  column-gap: var(--space-lg);
}

@media (min-width: 900px) {
  .footer-main {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    column-gap: var(--space-xl);
  }
}

.footer-categories li {
  padding-block: 3px;
  break-inside: avoid;
}

.site-footer a {
  color: var(--ink-2);
  text-decoration: none;
}

/* The logo never changes on hover (owner, 2026-10-29). */
.site-footer a:not(.wordmark):hover {
  color: var(--link-hover);
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs) var(--space-md);
  padding-block-start: var(--space-md);
  border-block-start: 1px solid var(--line);
}

.footer-links {
  display: flex;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ------------------------------------------------------------ page heads */
.page-head {
  margin-block-end: var(--space-lg);
}

.page-head > * + * {
  margin-block-start: var(--space-xs);
}

/* Category heads (TODO 12.26): the category icon (.topic-icon, as on the
   home cards) beside the title, then the rendered description in the
   reading measure. The post grid after it stays the main content. */
.term-title {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.term-description {
  max-inline-size: var(--measure);
  color: var(--ink-2);
}

/* The same icon before each name on /categories. */
.section-item-topic {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.section-item-topic .topic-icon {
  inline-size: 2rem;
  block-size: 2rem;
}

.page-lead {
  max-inline-size: var(--measure);
  text-wrap: pretty;
  color: var(--ink-2);
  font-size: var(--text-lg);
  line-height: var(--leading-normal);
}

.page-meta {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.section-title {
  margin-block-end: var(--space-md);
  font-size: var(--text-xl);
}

.empty-state {
  padding: var(--space-lg) var(--space-md);
  color: var(--ink-3);
  background: var(--surface-2);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  text-align: center;
}

.empty-state > * + * {
  margin-block-start: var(--space-xs);
}

/* ------------------------------------------------------------ home */
.intro {
  margin-block-end: var(--space-xl);
  padding-block: var(--space-md) var(--space-lg);
  border-block-end: 1px solid var(--line);
}

.intro-title {
  max-inline-size: 22ch;
  font-size: clamp(2rem, 4.5vw, var(--text-3xl));
}

.intro-lead {
  max-inline-size: var(--measure);
  text-wrap: pretty;
  margin-block-start: var(--space-sm);
  color: var(--ink-2);
  font-size: var(--text-lg);
  line-height: var(--leading-normal);
}

/* Hero (TODO 12.1, full bleed since 2026-10-29, TODO 12.56): an abstract
   risograph print in the featured image style spans the page under the header, edge to edge, and the headline,
   lead and buttons sit on it inside the page frame. A scrim in the page color
   fades in behind the text, so text contrast is the same as on the page in
   both themes. The picture keeps a fixed box (no layout shift) and its focal
   point (the connected blue and orange fields, right of center) stays in view when cropped. */
.hero {
  position: relative;
  isolation: isolate;
  display: grid;
  align-items: center;
  /* About 70% of the viewport, capped so the topic cards start near the fold at 1280 by 800. */
  min-block-size: clamp(26rem, 68svh, 34rem);
  margin-block-start: calc(-1 * var(--space-lg));
  overflow: hidden;
}

.hero-image {
  position: absolute;
  inset: 0;
  z-index: -2;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: 62% 50%;
}

.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(90deg, var(--surface) 0%, color-mix(in srgb, var(--surface) 92%, transparent) 36%, color-mix(in srgb, var(--surface) 55%, transparent) 56%, transparent 76%);
}

/* Never wider than the strong part of the scrim, also from 900 to 1100 px. */
.hero-text {
  max-inline-size: min(34rem, 52%);
  padding-block: var(--space-2xl);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-block-start: var(--space-md);
}

/* Phones and tablets: a 4:3 crop around the focal point (the basin, the
   network and the bell curves) on top, and the text below it on the page itself. Text laid
   over a narrow crop either hid the focal point under the scrim or lost
   contrast, so here the text never sits on the picture. */
@media (max-width: 899px) {
  .hero {
    display: block;
    min-block-size: 0;
  }

  .hero-image {
    position: static;
    block-size: auto;
    aspect-ratio: 4 / 3;
    object-position: 68% 55%;
  }

  .hero::before {
    display: none;
  }

  .hero-text {
    max-inline-size: none;
    padding-block: var(--space-lg) 0;
  }
}

/* Dark theme: the cream print is dimmed so it sits behind the dark scrim
   instead of showing as a bright box. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hero-image {
    filter: brightness(0.5) saturate(0.85);
  }
}

:root[data-theme="dark"] .hero-image {
  filter: brightness(0.5) saturate(0.85);
}

.chips ul,
.post-tags ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Topic cards: one per category, with an icon, under the intro text. */
.intro .topics {
  margin-block-start: var(--space-lg);
  scroll-margin-block-start: var(--space-lg);
}

.topic-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.topic-grid > li {
  display: flex;
}

.topic-card {
  display: flex;
  flex: 1;
  gap: var(--space-sm);
  padding: var(--space-md);
}

.topic-icon {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border-radius: var(--radius);
}

.topic-body {
  min-inline-size: 0;
}

/* Phones: two columns, icon above the text, so the list stays short. */
@media (max-width: 650px) {
  .topic-grid {
    grid-template-columns: 1fr 1fr;
  }

  .topic-card {
    flex-direction: column;
    padding: var(--space-sm);
  }
}

.topic-body > * + * {
  margin-block-start: var(--space-2xs);
}

.topic-title {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.topic-count {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

/* The "All posts" link under the latest posts. */
.section-more {
  margin-block-start: var(--space-lg);
}

/* ------------------------------------------------------------ cards */
.post-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.post-grid > li {
  display: flex;
}

.card {
  position: relative;
  display: flex;
  flex: 1;
  flex-direction: column;
  min-inline-size: 0;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  transition: border-color var(--duration-fast) ease, box-shadow var(--duration-fast) ease;
}

.card:hover {
  border-color: var(--line);
  box-shadow: var(--shadow);
}

/* One target: the title link covers the card. The ring goes on the card. */
.card-link {
  color: var(--ink);
  text-decoration: none;
}

.card-link::after {
  position: absolute;
  inset: 0;
  content: "";
}

/* Hover changes only the color, anywhere on the card (no underline, owner 2026-10-29). */
.card:hover .card-link {
  color: var(--accent-ink-hover);
}

.card-link:focus-visible {
  outline: none;
}

.card:has(.card-link:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.card-media {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--surface-3);
  border-block-end: 1px solid var(--line);
}

.card-media img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.card-media-empty {
  display: grid;
  place-items: center;
  block-size: 100%;
  color: var(--accent-ink);
  background: var(--accent-soft);
}

.card-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-md) var(--space-md);
}

.card-category {
  color: var(--accent-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.card-title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.card-excerpt {
  display: -webkit-box;
  overflow: hidden;
  color: var(--ink-2);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

.card-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-2xs) var(--space-sm);
  margin-block-start: auto;
  padding-block-start: var(--space-2xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
}

/* A quiet read affordance: secondary text, never the primary fill. */
.card-read {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-inline-start: auto;
  color: var(--ink-2);
  font-weight: var(--weight-semibold);
}

/* The second color (DESIGN.md "Second color"): a small mark, not a link. */
.card:hover .card-read,
.card:focus-within .card-read {
  color: var(--accent-2-ink);
}

.post-card-compact .card-body {
  padding: var(--space-sm) var(--space-md);
}

.post-card-compact .card-title {
  font-size: var(--text-base);
}

/* ------------------------------------------------------------ search */
.search-form {
  display: flex;
  gap: var(--space-xs);
  max-inline-size: var(--measure);
}

.search-form .input {
  flex: 1;
  min-inline-size: 0;
}

.page-head > .search-form {
  margin-block-start: var(--space-sm);
}

.search-count {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

/* Error pages use the status itself as the visual, keeping search close by. */
.error-hero {
  display: grid;
  gap: var(--space-lg);
  align-items: center;
  padding-block: var(--space-lg) var(--space-xl);
  border-block-end: 1px solid var(--line);
  margin-block-end: var(--space-xl);
}

.error-code {
  color: var(--accent-ink);
  font-size: clamp(5rem, 13vw, 11rem);
  font-weight: var(--weight-bold);
  line-height: 1;
  letter-spacing: -0.06em;
}

.error-code span {
  display: block;
  margin-block-start: var(--space-xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  letter-spacing: normal;
}

.error-text > * + * {
  margin-block-start: var(--space-sm);
}

.error-text .search-form {
  margin-block-start: var(--space-md);
}

.error-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

@media (min-width: 900px) {
  .error-hero {
    grid-template-columns: minmax(12rem, 1fr) minmax(0, 2fr);
    gap: var(--space-xl);
  }
}

.message > * + * {
  margin-block-start: var(--space-sm);
}

/* ------------------------------------------------------------ pagination */
.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  margin-block-start: var(--space-xl);
}

.pagination-pages {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.pagination a,
.pagination-step,
.pagination-gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-inline-size: 2.5rem;
  min-block-size: 2.5rem;
  padding: 0 var(--space-xs);
  color: var(--ink-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}

.pagination a {
  border: 1px solid var(--line);
}

.pagination a:hover {
  color: var(--ink);
  background: var(--surface-3);
}

.pagination a[aria-current="page"] {
  color: var(--on-accent);
  background: var(--accent-ink);
  border-color: var(--accent-ink);
}

.pagination-step.is-disabled {
  color: var(--ink-3); /* 7.6:1, so no extra fading */
}

.pagination-gap {
  min-inline-size: 1.5rem;
  color: var(--ink-3);
}

@media (max-width: 480px) {
  .pagination-step span {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ------------------------------------------------------------ post layout
   One 40rem column. From 980px a 15rem contents rail sits at the inline
   end and sticks while the text scrolls. Below 980px the rail is hidden and
   a Contents disclosure sits above the text. */
.post-layout {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--measure));
  margin-inline: auto;
}

.post-main {
  min-inline-size: 0;
}

.post-rail {
  display: none;
}

@media (min-width: 980px) {
  .post.has-toc .post-layout {
    display: grid;
    grid-template-columns: minmax(0, var(--measure)) var(--rail-width);
    column-gap: var(--rail-gap);
    align-items: start;
    inline-size: min(calc(100% - 2 * var(--gutter)), calc(var(--measure) + var(--rail-gap) + var(--rail-width)));
  }

  .post-rail {
    position: sticky;
    inset-block-start: calc(var(--admin-bar-h, 0px) + var(--post-bar-h, 0px) + var(--space-md));
    display: block;
    max-block-size: calc(100vh - var(--admin-bar-h, 0px) - var(--post-bar-h, 0px) - 2 * var(--space-md));
    margin: -4px;
    padding: 4px;
    overflow: auto;
    overscroll-behavior: contain;
  }

  .toc-fold {
    display: none;
  }
}

.post-header {
  margin-block-end: var(--space-lg);
}

.post-title {
  font-size: clamp(2rem, 4.5vw, var(--text-3xl));
}

.post-lead {
  margin-block-start: var(--space-sm);
  color: var(--ink-2);
  font-size: var(--text-lg);
  line-height: var(--leading-normal);
}

.post-meta {
  margin-block-start: var(--space-sm);
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.post-featured {
  margin: 0 0 var(--space-lg);
}

.post-featured img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  border-radius: var(--radius);
}

.post-featured-box {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--surface-3);
  border-radius: var(--radius);
}

.post-featured-box img {
  block-size: 100%;
  object-fit: cover;
}

/* Contents (rail and fold share the list styles). */
.toc-title {
  margin-block-end: var(--space-xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.toc-list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-inline-start: 1px solid var(--line);
}

.toc-list a {
  display: block;
  margin-inline-start: -1px;
  padding: 5px var(--space-xs) 5px var(--space-sm);
  color: var(--ink-3);
  border-inline-start: 2px solid transparent;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  text-decoration: none;
}

.toc-level-3 a {
  padding-inline-start: calc(var(--space-sm) + var(--space-sm));
}

.toc-list a:hover {
  color: var(--ink);
}

.toc-list a[aria-current] {
  color: var(--ink);
  background: var(--accent-soft); /* a light tint; square corners because of the accent rule */
  border-inline-start-color: var(--accent);
  font-weight: var(--weight-semibold);
}

.toc-fold nav {
  padding: var(--space-xs) var(--space-md) var(--space-sm);
}

.toc-fold {
  margin-block: 0 var(--space-lg);
}

/* Downloads box */
.downloads {
  margin-block: var(--space-xl) 0;
  padding: var(--space-md);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

.downloads-title {
  margin-block-end: var(--space-sm);
  font-size: var(--text-lg);
}

.downloads-list {
  display: grid;
  gap: var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.downloads-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  text-decoration: none;
}

.downloads-item:hover {
  color: var(--ink);
  background: var(--accent-soft);
  border-color: var(--line);
}
.downloads-icon {
  color: var(--accent-ink);
}

.downloads-text {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
}

.downloads-label {
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.downloads-meta {
  color: var(--ink-3);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

/* Publish and update dates: after the author box, before the tags. */
.post-dates {
  margin-block-start: var(--space-md);
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.post-categories {
  margin-block-end: var(--space-xs);
}

/* The post's topics use the shared .eyebrow style (components.css), like every page head. */
/* The dot between categories: wider spacing on both sides (owner request 2026-10-29). */
.post-categories > span[aria-hidden="true"] {
  margin-inline: 0.5em;
  color: var(--ink-3);
}

.series-box-label {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.post-tags {
  margin-block-start: var(--space-md);
}

/* Related posts */
.related {
  margin-block-start: var(--space-xl);
  padding-block-start: var(--space-lg);
  border-block-start: 1px solid var(--line);
}

.related-title {
  margin-block-end: var(--space-md);
  font-size: var(--text-xl);
}

.related-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.related-list > li {
  display: flex;
}

/* ------------------------------------------------------------ preview banner
   Admin preview only. It sticks to the top, and the problem list folds. */
.preview-banner {
  position: sticky;
  z-index: var(--z-sticky);
  inset-block-start: 0;
  margin-block: calc(-1 * var(--space-lg)) var(--space-lg);
  background: var(--warning-soft);
  border-block-end: 1px solid var(--warning-line);
  box-shadow: var(--shadow-sm);
}

.preview-banner.has-errors {
  background: var(--danger-soft);
  border-block-end-color: var(--danger-line);
}

.preview-banner-inner {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
  margin-inline: auto;
  padding-block: var(--space-xs);
}

.preview-banner-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-sm);
}

.preview-summary {
  flex: 1;
  color: var(--ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.preview-details > summary {
  margin-block-start: var(--space-2xs);
  color: var(--ink-2);
  font-size: var(--text-sm);
  cursor: pointer;
}

.preview-issues {
  max-block-size: 40vh;
  margin: var(--space-xs) 0 0;
  padding: 0;
  overflow: auto;
  list-style: none;
  font-size: var(--text-sm);
}

.preview-issue {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-sm);
  padding-block: 4px;
  border-block-start: 1px solid var(--line);
}

.preview-issue-level {
  font-weight: var(--weight-semibold);
}

.preview-issue-error .preview-issue-level {
  color: var(--danger-ink);
}

.preview-issue-warning .preview-issue-level {
  color: var(--warning-ink);
}

.preview-issue-line {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.preview-issue-message {
  flex: 1 1 20rem;
  color: var(--ink);
}

/* ------------------------------------------------------------ comments
   The comments island renders inside #comments (islands/comments.tsx).
   Each root and its replies share an avatar rail with neutral connectors.
   Replies stay at one modest indentation, with smaller avatars. No colored
   borders on rounded parts: tints and --line carry the meaning. */
.comments {
  margin-block-start: var(--space-xl);
  padding-block-start: var(--space-lg);
  border-block-start: 1px solid var(--line);
}

.comments > h2 {
  margin-block-end: var(--space-xs);
  font-size: var(--text-xl);
}

.comments-loading,
.comments-empty,
.comments-count {
  margin: 0;
  color: var(--ink-3);
}

.comments-loading {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.comments-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
  margin-block-end: var(--space-md);
}

/* No comments yet: an invitation, not an empty box (owner, 2026-10-29). Tint, no colored border. */
.comments-invite {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  margin-block-end: var(--space-md);
  padding: var(--space-md);
  background: var(--accent-soft);
  border-radius: var(--radius-md);
}

.comments-invite-icon {
  display: grid;
  flex: none;
  place-items: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  color: var(--accent-ink);
  background: var(--surface);
  border-radius: 50%;
}

.comments-invite-title {
  margin: 0;
  color: var(--ink);
  font-weight: var(--weight-semibold);
}

.comments-invite-text {
  margin: 2px 0 0;
  color: var(--ink-2);
}

/* Room between the form's Post button and whatever follows it. */
.comments-app .comment-form {
  margin-block-end: var(--space-lg);
}

.comment-list,
.comment-replies {
  margin: 0;
  padding: 0;
  list-style: none;
}

.comment {
  padding-block: var(--space-md);
  border-block-start: 1px solid var(--line);
}

.comment-item {
  position: relative;
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr);
  gap: var(--space-xs);
}

.comment-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  color: var(--ink-2);
  background: var(--surface-3);
  border-radius: 50%;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.comment-main {
  min-inline-size: 0;
}

.comment-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-xs);
  min-block-size: 1.5rem;
  margin-block-end: var(--space-2xs);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

.comment-identity,
.comment-timestamp {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  min-inline-size: 0;
  max-inline-size: 100%;
}

.comment-timestamp,
.comment-timestamp .comment-edited {
  font-size: var(--text-xs);
}

.comment-author {
  color: var(--ink);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

.comment-date {
  color: var(--ink-3);
}


.comment-replying {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  margin: 0 0 var(--space-2xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.comment-body {
  overflow-wrap: anywhere;
}

.comment-body > * + * {
  margin-block-start: var(--space-xs);
}

.comment-body :where(h1, h2, h3, h4, h5, h6) {
  margin-block: var(--space-sm) var(--space-xs);
  font-size: var(--text-base);
}

.comment-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  margin-block-start: var(--space-2xs);
  /* Align the first action's label with the comment text above it. */
  margin-inline-start: calc(-1 * var(--space-xs));
}

.comment-actions .btn {
  padding-inline: var(--space-xs);
  color: var(--ink-3);
  font-weight: var(--weight-medium);
}

.comment-actions .btn:hover {
  color: var(--ink);
}

/* The rail begins beneath the root avatar and branches to each reply.
   The last branch ends at its avatar instead of leaving a dangling rule. */
.comment-list > .comment:has(> .comment-thread) > .comment-item::before {
  content: '';
  position: absolute;
  inset-inline-start: 1.25rem;
  inset-block-start: calc(2.5rem + var(--space-2xs));
  inset-block-end: calc(-1 * var(--space-sm));
  border-inline-start: 1px solid var(--line);
  pointer-events: none;
}

.comment-thread {
  margin-block-start: var(--space-sm);
  margin-inline-start: 1.25rem;
  padding-inline-start: var(--space-sm);
}

.comment-thread > .comment {
  position: relative;
  padding-block: 0 var(--space-sm);
  border-block-start: 0;
}

.comment-thread > .comment::before,
.comment-thread > .comment::after {
  content: '';
  position: absolute;
  inset-inline-start: calc(-1 * var(--space-sm));
  pointer-events: none;
}

.comment-thread > .comment::before {
  inset-block: 0;
  border-inline-start: 1px solid var(--line);
}

.comment-thread > .comment::after {
  inset-block-start: 1rem;
  inline-size: var(--space-sm);
  border-block-start: 1px solid var(--line);
}

.comment-thread > .comment:last-child {
  padding-block-end: 0;
}

.comment-thread > .comment:last-child::before {
  inset-block-end: calc(100% - 1rem);
}

.comment-replies .comment-item {
  grid-template-columns: 2rem minmax(0, 1fr);
}

.comment-replies .comment-avatar {
  inline-size: 2rem;
  block-size: 2rem;
  font-size: var(--text-xs);
}

img.comment-avatar[src^="/assets/icons/favicon.svg"] {
  padding: 4px;
  border-radius: var(--radius-sm);
  background: transparent;
  object-fit: contain;
}

@media (max-width: 599px) {
  .comment-actions .btn {
    min-block-size: 2.75rem;
  }
}

/* The reader's own comment while it waits for approval: a soft tint, no
   border, so it stays in its thread position but is not mixed with the rest. */
.comment-pending {
  padding-inline: var(--space-sm);
  background: var(--warning-soft);
  border-radius: var(--radius-sm);
}

.comment-pending-note {
  margin: var(--space-2xs) 0 0;
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.comment-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-block-start: var(--space-md);
}

.comment-form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-xs);
}

.comment-form-count {
  margin-inline-end: auto;
  color: var(--ink-3);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.comments-signin {
  margin: var(--space-md) 0 0;
  padding-block-start: var(--space-md);
  border-block-start: 1px solid var(--line);
}

/* ------------------------------------------------------------ app pages
   Sign in, confirmation, account and messages: a narrow centered panel. */
.layout-app .site-main {
  padding-block: var(--space-xl) var(--space-2xl);
}

.app-panel {
  inline-size: min(calc(100% - 2 * var(--gutter)), 28rem);
  margin-inline: auto;
  padding: var(--space-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

.app-panel-wide {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--measure));
}

.app-panel > h1 {
  margin-block-end: var(--space-sm);
  font-size: var(--text-2xl);
}

.app-panel-lead {
  margin-block-end: var(--space-md);
  color: var(--ink-2);
}

.app-panel-foot {
  margin-block-start: var(--space-md);
}

.app-panel-body > * + * {
  margin-block-start: var(--space-sm);
}

/* Sign-in code (TODO 12.39): large spaced digits, easy to check against the email. */
.input-code {
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  letter-spacing: 0.3em;
  text-align: center;
}

@media (max-width: 480px) {
  .app-panel {
    padding: var(--space-md);
  }
}

.account-section + .account-section {
  margin-block-start: var(--space-lg);
}

/* Paragraphs have no margin, so the delete form keeps the field gap from the warning above it. */
.account-delete-form {
  margin-block-start: var(--space-sm);
}

.account-section > h2 {
  margin-block-end: var(--space-sm);
  font-size: var(--text-lg);
}

.provider-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.provider-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-sm);
  padding-block: var(--space-xs);
  border-block-end: 1px solid var(--line-2);
}

.provider-name {
  min-inline-size: 7rem;
  color: var(--ink);
  font-weight: var(--weight-semibold);
}

.provider-detail {
  flex: 1;
  min-inline-size: 0;
  color: var(--ink-3);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------ print
   Posts print as clean black on white: no chrome, links spelled out,
   blocks kept whole, code wrapped. */
@media print {
  :root:root:root {
    color-scheme: light;
    --ink: #000000;
    --ink-2: #1f2937;
    --ink-3: #374151;
    --line: #d1d5db;
    --line-2: #e5e7eb;
    --surface: #ffffff;
    --surface-2: #ffffff;
    --surface-3: #f3f4f6;
    --accent-ink: #1d4ed8;
    --accent-soft: #ffffff;
    --accent-line: #d1d5db;
    --info-soft: #ffffff;
    --warning-soft: #ffffff;
    --danger-soft: #ffffff;
    --code-bg: #ffffff;
    --code-inline-bg: #f3f4f6;
    --text-base: 11pt;
  }

  .shiki span {
    color: var(--shiki-light) !important;
  }

  .skip-link,
  .site-header,
  .site-footer,
  .post-rail,
  .toc-fold,
  .comments,
  .related,
  .preview-banner,
  .pagination,
  .code-actions,
  .demo-controls,
  .video-note {
    display: none !important;
  }

  .site-main {
    padding: 0;
  }

  .post-layout {
    display: block !important;
    inline-size: auto !important;
  }

  .prose a[href^="http"]::after,
  .downloads-item[href^="http"]::after {
    content: " (" attr(href) ")";
    color: var(--ink-3);
    font-size: 0.85em;
    overflow-wrap: anywhere;
  }

  .code-block pre {
    overflow: visible;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }

  .equation,
  .table-wrap {
    overflow: visible;
  }

  .code-block,
  .callout,
  .figure,
  .equation,
  .table-wrap,
  .download-card,
  .demo {
    break-inside: avoid;
  }

  .prose :where(h2, h3, h4) {
    break-after: avoid;
  }

  .video-frame {
    aspect-ratio: auto;
    background: none;
  }

  .video-facade {
    position: static;
    display: block;
    color: var(--ink);
    background: none;
  }

  .video-facade img,
  .video-play-icon {
    display: none;
  }

  .video-play {
    padding: 0;
    color: var(--ink);
    background: none;
  }

  .video-facade::after {
    content: " " attr(href);
    color: var(--ink-3);
  }

  .embed iframe,
  .simulator-frame {
    display: none;
  }
}

/* Site sections ----------------------------------------------------------------
   Category, tag and series indexes, the series box on posts, the contact
   form, and footer additions (TODO 6.2 to 6.6). Owner: site agent. */
.footer-links {
  flex-wrap: wrap;
  row-gap: var(--space-xs);
}

.footer-text {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.section-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.section-item {
  position: relative;
  padding: var(--space-md);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  transition: border-color var(--duration-fast) ease, box-shadow var(--duration-fast) ease;
}

.section-item:hover {
  border-color: var(--line);
  box-shadow: var(--shadow);
}

/* The title link covers the whole card. */
.section-item-title a {
  color: var(--ink);
  text-decoration: none;
}

.section-item-title a::after {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  content: "";
}

.section-item-title a:hover {
  color: var(--accent-ink);
}

.section-item-title a:focus-visible {
  outline: none;
}

.section-item-title a:focus-visible::after {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
}

.section-item > * + * {
  margin-block-start: var(--space-2xs);
}

.section-item-title {
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
}

.section-item-meta {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.series-box {
  margin-block: var(--space-xl) 0;
  padding: var(--space-md);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

.series-box-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2xs) var(--space-sm);
}

.series-box-name {
  font-weight: var(--weight-semibold);
}

.series-box-position {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.series-box-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-sm);
  margin: var(--space-sm) 0 0;
  padding: 0;
  list-style: none;
}

.series-box-links a {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  block-size: 100%;
  padding: var(--space-xs) var(--space-sm);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  text-decoration: none;
}

.series-box-links a:hover {
  background: var(--accent-soft);
  border-color: var(--line);
}

.series-box-next a {
  text-align: end;
}

.series-box-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.series-box-next .series-box-label {
  align-self: flex-end;
}

.series-box-title {
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.series-box-all {
  margin-block-start: var(--space-sm);
  font-size: var(--text-sm);
}

/* Other collections of the post as compact links (TODO 12.41). A separator only when the full box is above. */
.collection-also {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2xs) var(--space-xs);
  font-size: var(--text-sm);
}

.series-box-all + .collection-also {
  margin-block-start: var(--space-sm);
  padding-block-start: var(--space-sm);
  border-block-start: 1px solid var(--line);
}

.collection-also a {
  text-decoration: none;
}

.contact-intro {
  margin-block-end: var(--space-lg);
}

.contact-form {
  max-inline-size: var(--measure);
}

/* Signed-out visitors: the sign-in buttons take the form's place, at a button-friendly width. */
.contact-signin {
  max-inline-size: 28rem;
}

.contact-signin h2 {
  margin: 0;
  font-size: var(--text-lg);
}

.contact-sender {
  margin: 0;
  color: var(--ink-2);
}

/* Honeypot: off screen for people, still in the form for bots. */
.contact-trap {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Reader features
   Owner: reader agent. Post actions bar (island "post-actions"), comment
   edit, delete and report states, and the account sub pages. */
.post-actions {
  margin-block-start: var(--space-sm);
  min-block-size: 2.5rem;
}

.post-actions-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
}

.post-action {
  gap: var(--space-2xs);
}

/* One pattern for the reader actions on posts, topics, collections and comments (owner,
   2026-10-29): the icon carries the state, the label keeps the text color, the count is muted.
   Like is a red heart, outlined, then filled when liked. */
[data-action="like"] .icon,
[data-action="like"] .icon.is-filled {
  color: var(--danger-ink);
}

.post-action-count {
  margin-inline-start: 0.3em;
  color: var(--ink-3);
  font-weight: var(--weight-normal);
  font-variant-numeric: tabular-nums;
}

/* A hairline between the reader actions and the sharing actions. */
.post-actions-sep {
  flex: none;
  inline-size: 1px;
  block-size: 1.25rem;
  margin-inline: var(--space-2xs);
  background: var(--line);
}

/* A category or collection toolbar ends in its post count, plain muted text (TODO 12.57). */
.post-actions-meta {
  margin-inline-start: var(--space-xs);
  color: var(--ink-3);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Copy link and Share say "Copied" for two seconds. Both labels share one grid
   cell, so the button keeps its width. Opacity, not visibility, keeps the
   first label as the accessible name. */
.post-action-swap {
  display: inline-grid;
  justify-items: start;
}

.post-action-swap > span {
  grid-area: 1 / 1;
}

.post-action-swap > span + span,
.post-action-swap[data-on] > span:first-child {
  opacity: 0;
}

.post-action-swap[data-on] > span + span {
  opacity: 1;
}

.post-action:has(.post-action-swap[data-on]) {
  color: var(--success-ink);
}

.comments-notice:empty {
  display: none;
}

.comments-notice {
  margin-block-end: var(--space-sm);
  color: var(--success-ink);
  font-size: var(--text-sm);
}

.comment-edited,
.comment-reported {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.comment-reported {
  align-self: center;
  padding-inline: var(--space-xs);
}

.comment-deleted .comment-body,
.comment-deleted .comment-author {
  color: var(--ink-3);
  font-style: italic;
}

/* Account area: vertical tabs in a left sidebar from 760px (owner request 2026-10-29, more
   sections will come), a scrolling tab strip above the content on phones. The title and the
   content share the right column. */
.account-page {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
}

@media (min-width: 760px) {
  .account-page {
    display: grid;
    grid-template-columns: 13rem minmax(0, 1fr);
    column-gap: var(--space-lg);
    align-content: start;
  }

  .account-page > * {
    grid-column: 2;
  }

  .account-page > .account-nav {
    grid-column: 1;
    grid-row: 1 / span 50;
    align-self: start;
    position: sticky;
    inset-block-start: calc(var(--admin-bar-h, 0px) + var(--space-md));
    margin: 0;
    padding: 0 var(--space-md) 0 0;
    border-block-end: 0;
    border-inline-end: 1px solid var(--line);
  }

  .account-page > .account-nav ul {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 2px;
  }

  .account-page > .account-nav a {
    display: flex;
  }
}

.account-nav {
  margin-block: 0 var(--space-md);
  padding-block-end: var(--space-xs);
  border-block-end: 1px solid var(--line);
  overflow-x: auto;
}

.account-nav ul {
  display: flex;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.account-nav a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-block-size: 2.5rem;
  padding: var(--space-2xs) var(--space-sm);
  color: var(--ink-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  text-decoration: none;
  white-space: nowrap;
}

.account-nav a svg {
  flex: none;
  color: var(--ink-3);
}

.account-nav a:hover {
  color: var(--ink);
  background: var(--surface-2);
}

.account-nav a[aria-current="page"] {
  color: var(--accent-ink);
  background: var(--accent-soft);
  font-weight: var(--weight-semibold);
}

.account-nav a[aria-current="page"] svg {
  color: var(--accent-ink);
}

.account-form {
  margin-block-start: var(--space-md);
}

.account-form + .account-section {
  margin-block-start: var(--space-lg);
}

.account-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.account-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs) var(--space-sm);
  padding-block: var(--space-sm);
  border-block-end: 1px solid var(--line-2);
}

.account-item-block {
  display: block;
}

.account-item-block > * + * {
  margin-block-start: var(--space-2xs);
}

.account-item-main {
  flex: 1;
  min-inline-size: 0;
}

.account-item-title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  color: var(--ink);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

.account-item-meta {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.account-comment-body {
  overflow-wrap: anywhere;
  white-space: pre-line;
}

.account-comment-body.is-deleted {
  color: var(--ink-3);
  font-style: italic;
}

.account-empty {
  color: var(--ink-3);
}

.account-actions {
  margin-block-start: var(--space-md);
}
/* End of reader features */

/* Card counts (TODO 5.6) */
.card-counts { white-space: nowrap; }

/* Phones: skip the empty image box on cards without a featured image. */
@media (max-width: 650px) {
  .card-media:has(.card-media-empty) { display: none; }
}

/* Author profile ---------------------------------------------------------------
   The profile page at /author/{slug} and the author box after each post body
   (TODO 7.7). Owner: author agent. Tokens only. */
.author-box {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  margin-block: var(--space-xl) 0;
  padding: var(--space-md);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

.author-box-photo {
  flex: none;
  inline-size: 4rem;
  block-size: 4rem;
  object-fit: cover;
  border-radius: 50%;
  background: var(--surface-3);
}

.author-box-text {
  min-inline-size: 0;
}

.author-box-text > * + * {
  margin-block-start: var(--space-2xs);
}

.author-box-label {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.author-box-name {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
}

.author-box-name a {
  color: var(--ink);
  text-decoration: none;
}

.author-box-name a:hover {
  color: var(--accent-ink-hover);
}

.author-box-headline {
  color: var(--ink-2);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.author-box-more {
  font-size: var(--text-sm);
}

/* Standalone "more" links are navigation, not running text: no underline. */
.series-box-all a,
.author-box-more a {
  text-decoration: none;
}

.author-profile {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-lg) var(--space-xl);
  margin-block-end: var(--space-2xl);
}

.author-profile-text {
  flex: 1 1 24rem;
  min-inline-size: 0;
  max-inline-size: var(--measure);
}

.author-profile-text > * + * {
  margin-block-start: var(--space-sm);
}

.author-profile h1 {
  margin: 0;
}

.author-headline {
  color: var(--ink-2);
  font-size: var(--text-lg);
  line-height: var(--leading-normal);
  text-wrap: pretty;
}

.author-photo {
  flex: none;
  inline-size: 11rem;
  block-size: 11rem;
  object-fit: cover;
  border-radius: 50%;
  background: var(--surface-3);
  box-shadow: 0 0 0 4px var(--surface), 0 0 0 5px var(--line);
}

.author-bio {
  margin-block-start: var(--space-md);
  font-size: var(--text-base);
}

.author-bio p {
  margin-block: 0 var(--space-sm);
  line-height: var(--leading-relaxed);
}

.author-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin: var(--space-md) 0 0;
  padding: 0;
  list-style: none;
}

.author-links .chip {
  gap: var(--space-2xs);
}

.author-posts-count {
  color: var(--ink-3);
  font-weight: var(--weight-normal);
}

@media (max-width: 650px) {
  .author-profile {
    gap: var(--space-md);
  }

  .author-photo {
    inline-size: 7rem;
    block-size: 7rem;
  }
}

@media print {
  .author-box {
    break-inside: avoid;
    background: none;
  }
}
/* End of author profile */

/* UI/UX audit fixes (2026-10-08, review/2026-10-08-1330-ui-ux-audit) */

/* F04: the lead is a short summary (at most about 200 characters). The clamp only stops unusually long ones. */
.post-lead {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 5;
  line-clamp: 5;
}

/* F02: the main actions of a post, right under its meta line. */
.post-primary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-block-start: var(--space-sm);
}

@media (max-width: 650px) {
  /* F11: one row of icon buttons with counts. Labels stay for screen readers. */
  .post-action-label {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .post-actions-bar {
    flex-wrap: nowrap;
  }

  /* F21: comfortable touch targets. */
  .post-actions .post-action,
  .post-primary .btn,
  .chip,
  .account-nav a {
    min-block-size: 2.75rem;
  }

  .site-footer a {
    display: inline-block;
    padding-block: var(--space-xs);
  }
}

/* F10: series pages list their parts in order. */
.page-actions {
  margin-block-start: var(--space-sm);
}

/* Rows span the frame (TODO 12.21): part number, a 16:9 thumbnail, then the text.
   The title link is stretched over the row, so the thumbnail opens the part too. */
.series-list {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  border-block-start: 1px solid var(--line);
}

.series-item {
  position: relative;
  display: grid;
  grid-template-columns: 2.75rem 14rem minmax(0, 1fr);
  gap: var(--space-sm) var(--space-md);
  align-items: start;
  padding-block: var(--space-sm);
  border-block-end: 1px solid var(--line);
}

.series-thumb {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
}

.series-thumb img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.series-body {
  max-inline-size: var(--measure);
}

/* Phones: the thumbnail sits above the text, and an empty frame is dropped. */
@media (max-width: 649px) {
  .series-item {
    grid-template-columns: 2.75rem minmax(0, 1fr);
  }
  .series-thumb {
    grid-column: 2;
  }
  .series-body {
    grid-column: 2;
  }
  .series-thumb.is-empty {
    display: none;
  }
}

.series-part {
  display: grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border: 0;
  border-radius: var(--radius-pill);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.series-title {
  margin: 0;
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
}

.series-title a {
  color: var(--ink);
  text-decoration: none;
}

.series-title a::after {
  content: '';
  position: absolute;
  inset: 0;
}

.series-title a:focus-visible {
  outline: none;
}

.series-item:has(.series-title a:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.series-title a:hover {
  color: var(--link);
}

.series-excerpt {
  margin-block: var(--space-2xs) 0;
  color: var(--ink-2);
  font-size: var(--text-sm);
}

.series-meta {
  margin-block: var(--space-2xs) 0;
  color: var(--ink-3);
  font-size: var(--text-xs);
}

.series-code {
  font-family: var(--font-mono);
}

/* ------------------------------------------------------------ layout review (2026-10-08)
   Posts and pages use the header frame (--page-wide) with a permanent
   sidebar: the outline of the page and the main actions. Wider lines get a
   slightly larger body size and line height to stay comfortable. */
.post-layout {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
}

@media (min-width: 980px) {
  .post.has-toc .post-layout {
    grid-template-columns: minmax(0, 1fr) var(--rail-width);
    column-gap: var(--space-xl);
    inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
  }

  .post .prose {
    font-size: 1.125rem;
    line-height: 1.75;
  }

  /* The sidebar has no rules of its own (owner, 2026-10-25): the column gap
     separates it, and the contents marker is the only line. The header copy
     of the main buttons hides here, since the sidebar shows the same ones. */
  .post-cta .post-primary {
    display: none;
  }

  /* The rail is first in the DOM (tab order, 2026-10-28) but stays on the right. */
  .post.has-toc .post-main {
    grid-column: 1;
    grid-row: 1;
  }

  .post.has-toc .post-rail {
    grid-column: 2;
    grid-row: 1;
  }
}

/* The header ends in one action row: primary buttons, then reader actions. */
.post-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm) var(--space-md);
  margin-block-start: var(--space-md);
}

.post-cta .post-primary,
.post-cta .post-actions {
  margin: 0;
}

/* Sidebar actions: the same buttons, full width, under the outline. */
.rail-actions {
  display: grid;
  gap: var(--space-xs);
  margin-block-start: var(--space-lg);
}

.rail-actions .btn {
  justify-content: center;
}

/* After the text: downloads lead (brand tint), series and author follow
   closely, with less visual weight. */
.downloads {
  background: var(--accent-soft);
  border-color: var(--line);
}

.downloads + .series-box,
.downloads + .author-box,
.series-box + .author-box {
  margin-block-start: var(--space-md);
}

.author-box {
  background: transparent;
}

/* Narrow pages (contact, messages, 404) align with the header frame: same
   left edge as the logo, with a readable width for their content. */
.container.narrow {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
}

.container.narrow > * {
  max-inline-size: var(--measure);
}

/* Contact page: the form in the main column, guidance in a sidebar. */
.contact-layout {
  display: grid;
  gap: var(--space-lg);
}

.contact-main {
  min-inline-size: 0;
  max-inline-size: var(--measure);
}

.contact-aside {
  align-self: start;
  padding: var(--space-md);
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

.contact-aside h2 {
  margin-block: 0 var(--space-xs);
  font-size: var(--text-lg);
}

.contact-links {
  margin: var(--space-sm) 0 0;
  padding-inline-start: 1.1em;
}

@media (min-width: 980px) {
  .contact-layout {
    grid-template-columns: minmax(0, 1fr) var(--rail-width);
    column-gap: var(--space-xl);
  }
}

/* Search page: category filter chips. */
.search-filter {
  margin-block-start: var(--space-sm);
}

.chips-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.chip[aria-current="true"] {
  color: var(--accent-ink);
  background: var(--accent-soft);
  border-color: var(--line);
}

/* Cards lift slightly on hover, unless the visitor prefers less motion. */
@media (prefers-reduced-motion: no-preference) {
  .card,
  .section-item {
    transition: border-color var(--duration-fast) ease, box-shadow var(--duration-fast) ease, transform var(--duration-fast) ease;
  }

  .card:hover,
  .section-item:hover {
    transform: translateY(-2px);
  }
}

/* Home: space between the "Most discussed" and "Latest posts" sections. */
.post-section + .post-section {
  margin-block-start: var(--space-xl);
}

/* Development sign-in page (/dev, TODO 10.1). */
.dev-roles {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-xs);
}

.dev-roles form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

.dev-role-email,
.dev-role-goes {
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.dev-outbox {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-md);
}

.dev-outbox-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: var(--text-sm);
}

/* Members-only posts (TODO 12.28) ----------------------------------------------
   A quiet badge on cards and the post header, and the panel that replaces the
   body for visitors. Neutral surfaces, so the sign-in button is the one accent. */
.members-badge svg {
  flex: none;
}

.post-categories .members-badge {
  margin-inline-end: var(--space-sm);
  vertical-align: middle;
}

.members-panel {
  display: grid;
  justify-items: center;
  gap: var(--space-sm);
  margin-block: var(--space-xl) 0;
  padding: var(--space-xl) var(--space-md);
  text-align: center;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

.members-panel-icon {
  color: var(--ink-3);
}

.members-panel-title {
  font-size: var(--text-lg);
}

.members-panel p {
  max-inline-size: 36rem;
  color: var(--ink-2);
}

/* End of members-only posts */

/* Social (TODO 12.29): comment likes, profile pictures and the picker.
   Owner: social agent. */
img.comment-avatar {
  object-fit: cover;
}


.comment-likes {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-block-size: 2rem;
  padding-inline: 12px;
  color: var(--ink-3);
  font-size: var(--text-sm);
}

.account-menu > summary {
  padding-inline-start: var(--space-2xs);
}

.account-menu-avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  inline-size: 1.75rem;
  block-size: 1.75rem;
  color: var(--ink-2);
  background: var(--surface-3);
  border-radius: 50%;
  font-size: var(--text-xs);
  object-fit: cover;
}

.account-menu-name {
  overflow: hidden;
  text-overflow: ellipsis;
}

.avatar-current {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-block-end: var(--space-md);
}

.avatar-current p {
  margin: 0;
  color: var(--ink-2);
}

.avatar-choices {
  margin: 0;
  padding: 0;
  border: 0;
}

.avatar-choices legend {
  margin-block-end: var(--space-xs);
  padding: 0;
  font-weight: var(--weight-semibold);
}

.avatar-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(3.5rem, 1fr));
  gap: var(--space-xs);
}

/* Each choice is a tile. The chosen one gets a tinted tile, not a colored
   border (owner rule, 2026-10-26). The focus ring stays. */
.avatar-choice {
  position: relative;
  display: flex;
  justify-content: center;
  padding: 4px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.avatar-choice:hover {
  background: var(--surface-3);
}

.avatar-choice:has(input:checked) {
  background: var(--accent-soft);
}

.avatar-choice:has(input:checked) .avatar {
  transform: scale(0.88);
}

.avatar-choice:has(input:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* ------------------------------------------------------------ sticky post bar
   src/client/post-bar.ts moves the reader actions here when the header scrolls
   away (owner, 2026-10-29). It sits under the admin bar. Anchors and the rail
   keep --post-bar-h clear on post pages. */
html:has([data-post-bar]) {
  --post-bar-h: 3.25rem;
}

.post-bar {
  position: fixed;
  z-index: var(--z-header);
  inset: var(--admin-bar-h, 0px) 0 auto;
  background: var(--surface);
  border-block-end: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
}

.post-bar[hidden] {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .post-bar {
    animation: post-bar-in var(--duration-fast, 150ms) ease-out;
  }
}

@keyframes post-bar-in {
  from {
    opacity: 0;
    transform: translateY(-0.5rem);
  }
}

.post-bar-inner {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
  min-block-size: var(--post-bar-h, 3.25rem);
  margin-inline: auto;
}

.post-bar-title {
  flex: 1;
  min-inline-size: 0;
  overflow: hidden;
  color: var(--ink);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.post-bar-title:hover {
  color: var(--accent-ink-hover);
}

.post-bar-brand {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  min-inline-size: 2.75rem;
  min-block-size: 2.75rem;
  margin-inline-start: -8px;
}

.post-bar-slot {
  flex: none;
}

.post-bar .post-actions {
  margin: 0;
  min-block-size: 0;
}

@media (max-width: 640px) {
  .post-bar-inner {
    gap: var(--space-xs);
  }

  .post-bar-title {
    font-size: var(--text-sm);
  }
}

/* Keep the brand and a shortened entity title beside the icon actions. */
@media (max-width: 480px) {
  .post-bar .post-actions-bar {
    gap: 0;
  }

  /* Only the actions fit here. */
  .post-bar .post-actions-meta {
    display: none;
  }
}

@media print {
  .post-bar {
    display: none;
  }
}

/* menu.css */
/*
  The header's main menu and its mega menu panels (TODO 12.44). Markup in
  src/site/components/Document.tsx, behavior in src/client/mega-menu.ts,
  reference in DESIGN.md "Menus".

  Wide screens (900 px and up): the top entries sit in the header (their own
  row until 1280 px, then between the wordmark and the tools). A panel is a
  full width layer right under the header. The header is not sticky, so the
  panel follows the page and the admin bar needs no offset here.

  Phones: the script hides the list behind the Menu button. Open, it is a
  plain list under the header, and each panel is an accordion inside it.
  Without the script the list stays open and every entry is a link.
*/

.site-header {
  position: relative;
  z-index: var(--z-menu);
}

.site-nav {
  order: 3;
  flex-basis: 100%;
  min-inline-size: 0;
}

.menu-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  margin: 0 calc(-1 * var(--space-sm));
  padding: 0;
  list-style: none;
}

.menu-top {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-block-size: 2.5rem;
  padding: 0 var(--space-sm);
  color: var(--ink-2);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
}

.menu-top:hover,
.menu-top[aria-expanded="true"] {
  color: var(--ink);
  background: var(--surface-3);
}

.menu-top[aria-current="page"],
.menu-top.is-current {
  color: var(--accent-ink);
  background: var(--accent-soft);
}

.menu-chevron {
  color: var(--ink-3);
}

@media (prefers-reduced-motion: no-preference) {
  .menu-chevron {
    transition: transform var(--duration-fast) ease;
  }
}

.menu-top[aria-expanded="true"] .menu-chevron {
  transform: rotate(180deg);
}

/* The panel. */
.menu-panel {
  position: absolute;
  inset-block-start: 100%;
  inset-inline: 0;
  background: var(--surface);
  border-block: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
}

.menu-panel-inner {
  inline-size: min(calc(100% - 2 * var(--gutter)), var(--page-wide));
  margin-inline: auto;
  padding-block: var(--space-md);
}

.menu-columns {
  display: flex;
  gap: var(--space-lg);
}

.menu-column {
  flex: 1 1 0;
  min-inline-size: 0;
}

/* Categories and collections get two columns of their own. */
.menu-column.is-categories,
.menu-column.is-collections {
  flex-grow: 2;
}

.menu-heading {
  margin: 0 0 var(--space-xs);
  padding-inline: var(--space-xs);
  color: var(--ink-3);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.menu-links,
.menu-more {
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.menu-column.is-categories .menu-links,
.menu-column.is-collections .menu-links {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2px var(--space-sm);
}

.menu-link {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  padding: var(--space-xs);
  color: var(--ink-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  text-decoration: none;
}

.menu-link:hover {
  color: var(--ink);
  background: var(--surface-3);
}

.menu-link[aria-current="page"] {
  color: var(--accent-ink);
  background: var(--accent-soft);
}

.menu-link-icon {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  inline-size: 2rem;
  block-size: 2rem;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
}

.menu-link-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.menu-link-label {
  color: var(--ink);
  font-weight: var(--weight-semibold);
}

/* Post titles in a column read as a list, not as tiles. */
.menu-column.is-posts .menu-link {
  padding-block: 6px;
}

.menu-column.is-posts .menu-link-label {
  font-weight: var(--weight-medium);
}

/* "All topics" and other single items of a panel: one row under the columns. */
.menu-more {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-sm);
  margin-block-start: var(--space-sm);
  padding-block-start: var(--space-sm);
  border-block-start: 1px solid var(--line-2);
}

.menu-more .menu-link-label {
  color: var(--accent-ink);
}

/* The Menu button only exists for the phone drawer. */
.menu-toggle {
  display: none;
}

@media (min-width: 1280px) {
  .site-nav {
    order: 0;
    flex-basis: auto;
  }

  .menu-bar {
    margin: 0;
  }

  .site-header-inner {
    flex-wrap: nowrap;
    min-block-size: 4rem;
  }
}

@media (max-width: 899.98px) {
  /* An expanded sticky home menu must keep its last link reachable. */
  .site-header-sticky:has(.site-nav[data-open]) {
    max-block-size: calc(100dvh - var(--admin-bar-h, 0px));
    overflow-y: auto;
  }

  .menu-toggle {
    display: inline-flex;
  }

  .menu-toggle[hidden] {
    display: none;
  }

  /* The script marks the nav ready, then the list waits behind the Menu button. */
  .site-nav[data-ready]:not([data-open]) {
    display: none;
  }

  .site-nav[data-ready] {
    padding-block: var(--space-2xs) var(--space-xs);
  }

  .site-nav[data-ready] .menu-bar {
    flex-direction: column;
    flex-wrap: nowrap;
    margin: 0;
  }

  .site-nav[data-ready] .menu-top {
    justify-content: space-between;
    inline-size: 100%;
    min-block-size: 2.75rem;
    font-size: var(--text-base, 1rem);
  }

  .menu-panel {
    position: static;
    background: none;
    border: 0;
    box-shadow: none;
  }

  .menu-panel-inner {
    inline-size: auto;
    padding: var(--space-2xs) 0 var(--space-xs) var(--space-sm);
  }

  .menu-columns {
    flex-direction: column;
    gap: var(--space-sm);
  }

  .menu-column.is-categories .menu-links,
  .menu-column.is-collections .menu-links {
    grid-template-columns: minmax(0, 1fr);
  }

  .menu-link-icon {
    inline-size: 1.75rem;
    block-size: 1.75rem;
  }
}

@media print {
  .menu-panel {
    display: none !important;
  }
}

/* search-suggest.css */
/* Search as you type (TODO 10.8): the listbox under the header and search page
   boxes, added by src/client/search-suggest.ts. Tokens only, so it follows the
   theme. The list spans the whole form, which is the input width on phones. */
.has-search-suggest {
  position: relative;
}

.search-suggest {
  position: absolute;
  inset-block-start: calc(100% + 4px);
  inset-inline: 0;
  z-index: var(--z-menu);
  margin: 0;
  padding: var(--space-2xs);
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  max-block-size: min(70vh, 32rem);
  overflow-y: auto;
  text-align: start;
}

/* The header box is narrow, so the list grows to the right of it. */
.header-search .search-suggest {
  inset-inline: auto 0;
  inline-size: min(26rem, calc(100vw - 2 * var(--space-md)));
}

/* A row: a 16:9 thumbnail of the featured image, then the title and the category. */
.search-suggest-option {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs);
  border-radius: var(--radius-sm);
  color: var(--ink);
  cursor: pointer;
}

.search-suggest-option:hover {
  background: var(--surface-3);
}

.search-suggest-option[aria-selected='true'] {
  background: var(--accent-soft);
  /* The tint alone is about 1.1:1, so the keyboard choice also gets an inset ring. */
  outline: 2px solid var(--accent-ink);
  outline-offset: -2px;
}

.search-suggest-thumb {
  flex: none;
  inline-size: 4.5rem;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
}

.search-suggest-thumb img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.search-suggest-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.search-suggest-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  font-weight: var(--weight-semibold);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

.search-suggest-meta {
  color: var(--ink-3);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
}

/* "See all results": a search icon and the accent ink, under a hairline. */
.search-suggest-all {
  gap: var(--space-xs);
  margin-block-start: var(--space-2xs);
  padding-inline: var(--space-sm);
  border-block-start: 1px solid var(--line);
  border-start-start-radius: 0;
  border-start-end-radius: 0;
  color: var(--accent-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.search-suggest-all .icon {
  flex: none;
}

/* admin-bar.css */
/* ------------------------------------------------------------ admin bar
   Black toolbar on public pages for editors and admins (src/client/admin-bar.ts).
   Black in both themes so it never reads as part of the page. Sticky elements
   add --admin-bar-h, which the script sets to the bar's real height. */
:root {
  --admin-bar-h: 0px;
}

/* theme.ts sets the class before the first paint for editors, so the strip is reserved and
   painted at once. The script then corrects the height if the bar wraps. */
.has-admin-bar {
  --admin-bar-h: 2.5rem;
}

.has-admin-bar body {
  padding-block-start: var(--admin-bar-h);
}

.has-admin-bar body::before {
  content: "";
  position: fixed;
  z-index: calc(var(--z-menu) + 1);
  inset: 0 0 auto;
  block-size: var(--admin-bar-h);
  background: #16181d;
}

.admin-bar {
  position: fixed;
  z-index: calc(var(--z-menu) + 2);
  inset: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0 var(--space-xs);
  min-block-size: 2.5rem;
  padding-inline: var(--space-xs);
  color: #e8eaee;
  background: #16181d;
  font-size: var(--text-sm);
  line-height: 1;
}

.admin-bar-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
}

.admin-bar-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-block-size: 2.5rem;
  padding-inline: 10px;
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  list-style: none;
}

.admin-bar-item::-webkit-details-marker {
  display: none;
}

.admin-bar-item:hover,
.admin-bar-menu[open] > .admin-bar-item {
  color: #fff;
  background: #2a2e37;
}

.admin-bar-item:focus-visible {
  outline: 2px solid #8db3ee;
  outline-offset: -2px;
}

.admin-bar-item svg {
  flex: none;
  color: #a9b0bd;
}

.admin-bar-count {
  min-inline-size: 1.25rem;
  padding: 3px 6px;
  color: #16181d;
  background: #8db3ee;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-align: center;
}

.admin-bar-menu {
  position: relative;
}

.admin-bar-menu-list {
  position: absolute;
  inset-block-start: 100%;
  inset-inline-start: 0;
  display: grid;
  min-inline-size: 11rem;
  padding-block: 4px;
  background: #16181d;
  box-shadow: var(--shadow-lg);
}

.admin-bar-menu-list .admin-bar-item {
  min-block-size: 2.25rem;
}

@media (max-width: 640px) {
  .admin-bar-label.is-wide {
    display: none;
  }
  .admin-bar-item {
    min-inline-size: 2.75rem;
    justify-content: center;
  }
}

@media print {
  .admin-bar,
  .has-admin-bar body::before {
    display: none;
  }
  .has-admin-bar body {
    padding-block-start: 0;
  }
}
