/* ============================================================
   press — documentation site
   One stylesheet · self-hosted fonts · light + dark. The only
   script is a small progressive-enhancement copy helper (copy.js);
   the page works fully without it, and asks nothing of third parties.
   Design system: cool graphite paper, one warm cinnabar ink.
   Authored in Claude Design; see DESIGN.md for the full system
   and the rationale behind every token.
   ============================================================ */

/* ---- Fonts (self-hosted, OFL; subset to Latin) ------------- */
@font-face{font-family:"Literata";src:url("fonts/Literata-Regular.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Literata";src:url("fonts/Literata-Italic.woff2") format("woff2");font-weight:400;font-style:italic;font-display:swap}
@font-face{font-family:"Literata";src:url("fonts/Literata-SemiBold.woff2") format("woff2");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Literata";src:url("fonts/Literata-Bold.woff2") format("woff2");font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url("fonts/HankenGrotesk-Regular.woff2") format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url("fonts/HankenGrotesk-Medium.woff2") format("woff2");font-weight:500;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url("fonts/HankenGrotesk-SemiBold.woff2") format("woff2");font-weight:600;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url("fonts/HankenGrotesk-Bold.woff2") format("woff2");font-weight:700;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url("fonts/HankenGrotesk-ExtraBold.woff2") format("woff2");font-weight:800;font-display:swap}
@font-face{font-family:"JetBrains Mono";src:url("fonts/JetBrainsMono-Regular.woff2") format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"JetBrains Mono";src:url("fonts/JetBrainsMono-Medium.woff2") format("woff2");font-weight:500;font-display:swap}
@font-face{font-family:"JetBrains Mono";src:url("fonts/JetBrainsMono-SemiBold.woff2") format("woff2");font-weight:600;font-display:swap}

/* ---- Tokens ------------------------------------------------ */
:root{
  --paper:#FAFAFB; --surface:#F2F3F6; --zebra:#F6F7F9;
  --ink:#1B1D22; --muted:#565C66;
  --accent:#D64A2A; --accent-ink:#B23A1E;
  --rule:#E5E7EC; --rule-strong:#CCD0D8; --code-ink:#33373F;

  --serif:"Literata",Georgia,"Times New Roman",serif;
  --sans:"Hanken Grotesk",system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono:"JetBrains Mono",ui-monospace,"SFMono-Regular",Menlo,monospace;

  /* rem, not ch: a ch-based measure scales with each element's own
     font-size, so a big heading or lead paragraph would get a wider pixel
     column than body text and their left edges would not align. A rem
     measure gives every element one shared column. */
  --measure:42rem; --measure-wide:54rem;
  --gutter:clamp(20px,5vw,40px);
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:20px; --s6:24px; --s8:32px; --s10:40px; --s12:48px; --s16:64px;
}
@media (prefers-color-scheme:dark){
  :root{
    --paper:#17191D; --surface:#1E2126; --zebra:#1B1E22;
    --ink:#E8EAEE; --muted:#98A0AB;
    --accent:#F0774E; --accent-ink:#F0774E;
    --rule:#2A2E34; --rule-strong:#3B4048; --code-ink:#D7DBE1;
  }
}

/* ---- Reset & base ------------------------------------------ */
*,*::before,*::after{box-sizing:border-box}
/* Fluid root: the whole document (body and rem-sized headings, and the
   rem-based reading measure) grows with the viewport. Mobile stays ~17px;
   a wide desktop reaches ~34px, about twice the mobile size, so desktop
   reads large and comfortable. */
html{-webkit-text-size-adjust:100%; font-size:clamp(16px, 1vw + 8px, 28px)}
body{
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--serif); font-size:1.0625rem; line-height:1.62;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  padding-bottom:var(--s16);
}
::selection{background:color-mix(in srgb,var(--accent) 22%,transparent)}

/* One reading column. Every piece of content lives inside <main class="prose">
   (added by build_site.py), so the whole column is a single centered
   container and nothing can detach from it. The toolbar and footer sit
   outside .prose and stay full-width. */
.prose{
  max-width:var(--measure-wide); margin-inline:auto;
  padding-inline:var(--gutter);
}
/* Prose text keeps a comfortable measure and left-aligns to the column edge;
   tables and code use the column's full width. All share one left edge, so
   headings, paragraphs, lists, and tables line up. */
.prose > :is(p, ul, ol, blockquote, dl){
  max-width:var(--measure); margin-left:0; margin-right:auto;
}
.prose > table, .prose > pre, .prose > .code-wrap{
  width:100%; max-width:100%; margin-inline:0;
}

/* ---- Headings ---------------------------------------------- */
h1,h2,h3,h4{font-family:var(--sans); color:var(--ink); margin:0; text-wrap:balance}
h1{font-weight:800; font-size:2rem; line-height:1.14; letter-spacing:-.022em; margin:2.2rem auto .5rem}
h2{font-weight:700; font-size:1.36rem; line-height:1.2; letter-spacing:-.012em;
   margin:2.4rem auto .6rem; padding-top:1rem; border-top:1px solid var(--rule)}
h3{font-weight:700; font-size:1.06rem; line-height:1.3; margin:1.8rem auto .45rem}
h4{font-weight:700; font-size:.735rem; text-transform:uppercase; letter-spacing:.08em;
   color:var(--muted); margin:1.5rem auto .4rem}
h1 + h2{border-top:0; padding-top:0; margin-top:1.4rem}

/* ---- Text -------------------------------------------------- */
p{margin:0 auto .9em; text-wrap:pretty; overflow-wrap:break-word}
strong{font-weight:700; color:var(--ink)}
em{font-style:italic}
hr{border:0; border-top:1px solid var(--rule); margin:2rem auto}

a{color:var(--accent-ink); text-decoration:underline; text-underline-offset:2px;
  text-decoration-thickness:1px; transition:text-decoration-thickness .12s ease}
a:hover,a:focus-visible{text-decoration-thickness:2px}
/* Only long prose links may break mid-token to avoid overflow; chrome links
   (wordmark, nav, footer) must never wrap mid-word. */
.prose a{overflow-wrap:anywhere}

ul,ol{margin:0 auto 1rem; padding-left:1.35rem}
li{margin-bottom:.4rem}
li::marker{color:var(--accent)}

blockquote{
  margin:1.4rem auto; padding:.8rem 1.1rem; background:var(--surface);
  border-left:3px solid var(--accent); border-radius:0 7px 7px 0; color:var(--ink);
}
blockquote > :first-child{margin-top:0} blockquote > :last-child{margin-bottom:0}

/* ---- Code -------------------------------------------------- */
code{font-family:var(--mono); font-size:.85em; color:var(--code-ink);
  background:var(--surface); border:1px solid var(--rule); border-radius:4px; padding:1px 5px}
pre{
  font-family:var(--mono); font-size:0.84rem; line-height:1.6; color:var(--code-ink);
  background:var(--surface); border:1px solid var(--rule); border-radius:9px;
  padding:16px 18px; overflow-x:auto; max-width:100%; margin:1.2rem auto; tab-size:2;
}
pre > code{background:none; border:0; padding:0; font-size:inherit; color:inherit}
/* pandoc wraps highlighted blocks in <div class="sourceCode"><pre>...; the
   div must scroll too, or a long unbroken command line widens the whole page
   and (via the sticky top bar) pushes the mobile menu button off-screen. */
div.sourceCode{overflow-x:auto; max-width:100%}

/* ---- Syntax highlighting (theme-aware; pandoc/skylighting classes) --- */
/* Restrained and calm: comments recede, strings/keywords/numbers carry one
   quiet hue each, and command and function names take the cinnabar so the
   one accent does the identity work here too. Un-tokenized text stays
   --code-ink, so console and plain blocks read cleanly. */
:root{
  --hl-comment:#6b7280; --hl-string:#2f7d5b; --hl-keyword:#1f6fb2;
  --hl-number:#8a6100;  --hl-func:var(--accent-ink); --hl-attr:#0f6f80;
}
@media (prefers-color-scheme:dark){:root{
  --hl-comment:#828c99; --hl-string:#82c7a2; --hl-keyword:#72b4ef;
  --hl-number:#d6a95c;  --hl-func:var(--accent);    --hl-attr:#5cbccf;
}}
pre code .co, pre code .do, pre code .cv{color:var(--hl-comment); font-style:italic}
pre code .st, pre code .ch, pre code .vs, pre code .ss, pre code .sc{color:var(--hl-string)}
pre code .kw, pre code .cf, pre code .im, pre code .bu{color:var(--hl-keyword)}
pre code .dv, pre code .fl, pre code .bn, pre code .cn, pre code .bo{color:var(--hl-number)}
pre code .fu, pre code .ex{color:var(--hl-func)}
pre code .at, pre code .va, pre code .dt{color:var(--hl-attr)}
pre code .op{color:var(--muted)}
pre code .er, pre code .al, pre code .wa{color:var(--accent); font-weight:600}

/* ---- Copy button (progressive enhancement; see copy.js) ---- */
/* The script wraps each <pre> in .code-wrap so the button pins to the block
   corner while the <pre> scrolls inside. With JS off there is no wrapper and
   no button, and the block looks and works exactly the same. */
.code-wrap{position:relative; margin:1.2rem auto}
.code-wrap > pre{margin:0}
.copy-btn{
  position:absolute; top:8px; right:8px; width:30px; height:30px; padding:0;
  display:inline-flex; align-items:center; justify-content:center; cursor:pointer;
  background:var(--paper); color:var(--muted);
  border:1px solid var(--rule); border-radius:7px; opacity:.55;
  transition:opacity .12s ease, color .12s ease, border-color .12s ease;
}
.copy-btn:hover,.copy-btn:focus-visible{color:var(--accent-ink); border-color:var(--rule-strong); opacity:1}
.copy-btn .i-done{display:none}
.copy-btn.copied{color:var(--hl-string); border-color:var(--hl-string); opacity:1}
.copy-btn.copied .i-copy{display:none}
.copy-btn.copied .i-done{display:inline}
/* On devices that hover, keep it out of the way until the block is hovered;
   on touch (no hover) it stays visible so it is reachable. */
@media (hover:hover){
  .copy-btn{opacity:0}
  .code-wrap:hover .copy-btn, .copy-btn:focus-visible{opacity:1}
}
@media (prefers-reduced-motion:reduce){.copy-btn{transition:none}}

/* ---- Tables ------------------------------------------------ */
table{
  border-collapse:collapse; width:100%; display:block; overflow-x:auto;
  font-family:var(--sans); font-size:0.82rem; color:var(--ink); margin:1.3rem auto;
}
thead th{
  font-weight:700; font-size:0.69rem; text-transform:uppercase; letter-spacing:.06em;
  color:var(--muted); text-align:left; white-space:nowrap;
  padding:11px 16px; border-bottom:2px solid var(--rule-strong); background:var(--paper);
}
tbody td{padding:10px 16px; border-bottom:1px solid var(--rule); vertical-align:top}
tbody tr:nth-child(even){background:var(--zebra)}
td[style*="right"],th[style*="right"]{font-variant-numeric:tabular-nums}   /* pandoc alignment */

/* ---- Reference records (body.doc-entries) ------------------ */
/* Records are h2 in the generated docs (## per record); each is carded by
   rhythm, not a box: a strong top rule, its id/criticality subtitle, and a
   compact field spec-box. */
body.doc-entries h2{
  font-size:1.2rem; margin-top:2.4rem; padding-top:1.4rem;
  border-top:1px solid var(--rule-strong);
}
/* Only drop the top rule when a record heading follows the page title
   directly; a first record that comes after intro prose keeps its
   separator, so the intro and the first record are visibly divided. */
body.doc-entries h1 + h2{border-top:0; padding-top:0; margin-top:1.2rem}
body.doc-entries h2 + p{                       /* id · criticality subtitle */
  font-family:var(--mono); font-size:0.78rem; color:var(--muted); margin-bottom:.9rem;
}
body.doc-entries h2 + p code{color:var(--accent-ink); font-weight:600}
/* A reference record's table reads at the column's width in the reading
   font. It used to be pinned at a fixed 440px while its cells were monospace
   at a rem size that scales with the fluid root, so on a wide desktop huge
   monospace text was crammed into a narrow box and prose wrapped a few words
   per line. Width is now the reading measure (rem, so it scales with the same
   root the text does), and the description reads in the body face; inline
   `code` stays monospace on its own. */
body.doc-entries table{max-width:var(--measure); margin-left:0;
  border:1px solid var(--rule); border-radius:9px; overflow:hidden; display:table}
body.doc-entries td:first-child{
  font-family:var(--sans); font-weight:600; color:var(--muted);
  background:var(--surface); width:28%; white-space:nowrap;
}
body.doc-entries td{font-size:0.95rem; line-height:1.5}

/* ---- Landing (body.home) ----------------------------------- */
/* The landing is h1 then a description paragraph (no eyebrow in the
   generated README); the first paragraph reads as the lead. */
body.home h1{font-size:clamp(3rem,7vw,3.75rem); letter-spacing:-.045em; line-height:1; margin-top:1.4rem}
body.home > p:first-of-type{font-size:1.24rem; line-height:1.5; color:var(--ink)}  /* lead */

/* ---- Navigation -------------------------------------------------------
   Two layouts from one markup: a full-height left SIDEBAR on desktop (a
   vertical, grouped, always-visible nav that fills the space beside the
   centered column), and a compact TOP BAR + CSS-only hamburger on mobile.
   Chrome text uses --ui, a gentler scale than the big prose root, so the
   sidebar stays a sane width while body copy is large. */
:root{ --ui:clamp(13px, 0.25vw + 11px, 16px); }

/* -- base = mobile: top bar with a hamburger that reveals the nav -- */
.toolbar{
  display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  padding:12px var(--gutter); background:var(--paper);
  border-bottom:1px solid var(--rule); font-family:var(--sans);
  position:sticky; top:0; z-index:20;
  /* The bar is sticky at top:0; when the hamburger reveals a nav taller
     than a short screen, a pinned bar would strand its last items below the
     fold with no way to scroll to them. Cap it to the viewport and let it
     scroll inside its own region so every item stays reachable (#195). */
  max-height:100vh; overflow-y:auto;
}
/* The wordmark is the designed lockup (badge + serif press.), swapped by
   theme: the ink badge / dark serif reads on light, the transparent
   vermilion-badge / paper serif reads on dark. */
/* flex:0 0 auto is load-bearing, not tidiness. The desktop toolbar is a
   COLUMN flex container capped at max-height:100vh (#195), so whenever the
   nav is taller than the viewport every shrinkable item is under shrink
   pressure -- and `overflow:hidden` here zeroes this item's automatic
   minimum size, so it collapsed to height 0 and clipped the lockup to a
   sliver at every desktop width. Refusing to shrink keeps the logo whole;
   min-width:0 + overflow:hidden still contain it horizontally. */
.wordmark{display:inline-flex; align-items:center; text-decoration:none;
  min-width:0; overflow:hidden; flex:0 0 auto}
.wm-lockup{height:48px; width:auto; max-width:100%; display:block}
.wm-dark{display:none}
@media (prefers-color-scheme:dark){.wm-light{display:none} .wm-dark{display:block}}
:root[data-theme="light"] .wm-light{display:block} :root[data-theme="light"] .wm-dark{display:none}
:root[data-theme="dark"] .wm-light{display:none} :root[data-theme="dark"] .wm-dark{display:block}
.nav-toggle{position:absolute; width:1px; height:1px; opacity:0; pointer-events:none}
.nav-burger{display:flex; flex-direction:column; justify-content:center; gap:4px;
  margin-left:auto; flex:0 0 auto; width:42px; height:38px; padding:9px; cursor:pointer;
  border:1px solid var(--rule-strong); border-radius:9px}
.nav-burger span{display:block; height:2px; background:var(--ink); border-radius:2px}

nav[aria-label="Site"]{
  display:none; flex-direction:column; align-items:flex-start; gap:22px;
  width:100%; margin-top:10px; padding-top:16px; border-top:1px solid var(--rule);
}
#nav-toggle:checked ~ nav[aria-label="Site"]{display:flex}   /* CSS-only, no JS */
.nav-group{display:flex; flex-direction:column; align-items:flex-start; gap:7px; width:100%}
.nav-group-label{font-size:calc(var(--ui) - 2px); font-weight:700; letter-spacing:.11em;
  text-transform:uppercase; color:var(--muted)}
nav[aria-label="Site"] a{font-size:var(--ui); font-weight:500; color:var(--muted);
  text-decoration:none; line-height:1.3}
nav[aria-label="Site"] a:hover{color:var(--accent-ink)}
nav[aria-label="Site"] a[aria-current="page"]{color:var(--ink); font-weight:700}
nav[aria-label="Site"] a[aria-current="page"]::before{
  content:""; display:inline-block; width:3px; height:1em; vertical-align:-.15em;
  background:var(--accent); border-radius:2px; margin-right:7px; margin-left:-10px}
.repo{color:var(--muted); font-weight:600}
.repo:hover{color:var(--ink)}

/* -- desktop: the sidebar -- */
@media (min-width:900px){
  body{display:flex; flex-wrap:wrap; align-items:flex-start}
  .toolbar{
    flex-direction:column; flex-wrap:nowrap; align-items:flex-start; gap:26px;
    width:clamp(200px, 19vw, 250px); flex:0 0 auto; align-self:flex-start;
    height:auto; max-height:100vh; overflow-y:auto;
    padding:24px 22px; border-bottom:none; border-right:1px solid var(--rule);
  }
  .nav-burger{display:none}
  nav[aria-label="Site"]{
    display:flex !important; gap:24px; margin:0; padding:0; border-top:none;
  }
  main.prose{flex:1; min-width:0}
  .colophon{width:100%}
}

/* ---- Footer ------------------------------------------------ */
.colophon{
  border-top:1px solid var(--rule); padding:24px var(--gutter); background:var(--paper);
  font-family:var(--sans); display:flex; align-items:center; justify-content:space-between;
  flex-wrap:wrap; gap:12px 28px; margin-top:var(--s16);
  /* The desktop sidebar is position:sticky over the full body height (z-index
     20); the full-width footer must paint above it, or the sidebar's bottom
     clips the footer's left edge when the page is scrolled to the end. */
  position:relative; z-index:21;
}
.colophon nav{display:flex; gap:22px; flex-wrap:wrap}
.colophon a{color:var(--muted); font-size:0.85rem; font-weight:500; text-decoration:none}
.colophon a:hover{color:var(--accent-ink)}
.stamp{margin:0; font-family:var(--mono); font-size:0.78rem; color:var(--muted)}
.stamp code, .stamp .sha{color:var(--accent-ink); background:none; border:0; padding:0}

/* ---- Feature components: the parts-of-a-book guide ---------
   Reusable, theme-aware blocks a Markdown page composes through pandoc
   fenced divs (::: {.callout}) and bracketed spans ([x]{.tag}). Two
   families read at a glance: generated matter takes the cinnabar rail
   (press builds it from config); authored matter takes a graphite rail
   (you write the Markdown). */

/* Classification pill: [generated]{.tag .gen} · [authored]{.tag .made} */
.tag{
  display:inline-block; font-family:var(--sans); font-weight:700;
  font-size:.62em; letter-spacing:.05em; text-transform:uppercase;
  line-height:1.5; padding:.05em .5em; border-radius:5px;
  vertical-align:.14em; white-space:nowrap; border:1px solid transparent;
}
.tag.gen{color:var(--accent-ink);
  background:color-mix(in srgb,var(--accent) 13%,transparent);
  border-color:color-mix(in srgb,var(--accent) 32%,transparent);}
.tag.made{color:var(--muted); background:var(--surface); border-color:var(--rule-strong);}
h1 .tag, h2 .tag, h3 .tag{vertical-align:.28em; margin-left:.35rem}

/* Anatomy: a book's parts in reading order, grouped front / body / back.
   Each row's left rail is colored by its family. */
.prose > .anatomy{max-width:var(--measure-wide); margin:1.8rem auto 2.4rem;}
.anatomy .an-band{margin:0 0 var(--s5)}
.anatomy .an-band-label{font-family:var(--sans); font-weight:800; font-size:.7rem;
  letter-spacing:.12em; text-transform:uppercase; color:var(--muted); margin:0 0 var(--s3);}
.anatomy ol, .anatomy ul{list-style:none; margin:0; padding:0}
.anatomy li{margin:0 0 var(--s2); padding:var(--s3) var(--s4);
  background:var(--surface); border:1px solid var(--rule);
  border-left:4px solid var(--rule-strong); border-radius:0 8px 8px 0;
  display:flex; flex-wrap:wrap; align-items:baseline; gap:.3rem .8rem;}
.anatomy li.gen{border-left-color:var(--accent)}
.anatomy .an-name{font-family:var(--sans); font-weight:700; color:var(--ink); font-size:.98rem}
.anatomy .an-where{font-family:var(--mono); font-size:.72rem; color:var(--muted); margin-left:auto}

/* Part cards: an at-a-glance grid, one card per part. */
.prose > .partgrid{max-width:var(--measure-wide); margin:1.8rem auto 2.4rem; padding:0;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(15.5rem,1fr)); gap:var(--s4);}
.partgrid .part{margin:0; background:var(--surface); border:1px solid var(--rule);
  border-top:3px solid var(--rule-strong); border-radius:9px;
  padding:var(--s5); display:flex; flex-direction:column; gap:.45rem;}
.partgrid .part.gen{border-top-color:var(--accent)}
.partgrid .part-name{font-family:var(--sans); font-weight:700; font-size:1rem; color:var(--ink)}
.partgrid .part-what{color:var(--muted); font-size:.9rem; line-height:1.5; margin:0}
.partgrid .part-where{font-family:var(--mono); font-size:.72rem; color:var(--accent-ink);
  margin-top:auto; word-break:break-word}
.partgrid .part.made .part-where{color:var(--muted)}

/* Callout: a set-off panel. Default = a rule press enforces (cinnabar);
   .tip = a helpful aside (green); .gap = a stated limitation (dashed). */
.prose > .callout{max-width:var(--measure); margin:1.4rem auto; padding:var(--s4) var(--s5);
  background:var(--surface); border:1px solid var(--rule); border-left:4px solid var(--accent);
  border-radius:0 8px 8px 0;}
.callout > :first-child{margin-top:0} .callout > :last-child{margin-bottom:0}
.callout p{max-width:none}
.callout > p:first-child > strong:first-child{color:var(--accent-ink)}
.callout.tip{border-left-color:var(--hl-string)}
.callout.tip > p:first-child > strong:first-child{color:var(--hl-string)}
.callout.gap{border-left-style:dashed; border-left-color:var(--rule-strong)}
.callout.gap > p:first-child > strong:first-child{color:var(--muted)}

/* Page lead: the one large intro paragraph on a guide page. */
.prose > .lede{max-width:var(--measure); font-size:1.2rem; line-height:1.5; color:var(--ink);}

/* ---- Narrow-screen tables ---------------------------------- */
/* Two-column tables (a term and a description) crowd on a phone; tighten
   the cells and ease the code size so text wraps less brutally, and keep
   wide tables scrolling inside their box rather than off the page. */
@media (max-width:620px){
  .prose table{font-size:.92rem}
  .prose th, .prose td{padding:8px 10px}
  body.doc-entries td{font-size:.9rem}
  body.doc-entries td:first-child{width:32%; white-space:normal}

  /* A three- or four-column table cannot be read on a phone: the far columns
     run off the edge and a reader must scroll a box sideways to learn what a
     row says. Each row becomes a CARD instead -- the same idiom as the part
     and gallery cards -- and every cell wears its column header, stamped on
     as data-label at build time (label_table_cells), because CSS cannot
     reach a cell's header on its own. Only tables that HAVE headers stack;
     the reference records, whose first cell is already the label, keep their
     two-column shape. */
  .prose table:has(td[data-label]){
    display:block; border-collapse:separate; overflow-x:visible;
  }
  .prose table:has(td[data-label]) thead{
    position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%);
  }
  .prose table:has(td[data-label]) tbody,
  .prose table:has(td[data-label]) tr,
  .prose table:has(td[data-label]) td{display:block; width:auto}
  .prose table:has(td[data-label]) tr{
    background:var(--surface); border:1px solid var(--rule);
    border-top:3px solid var(--rule-strong); border-radius:9px;
    padding:var(--s4); margin-bottom:var(--s4);
  }
  .prose table:has(td[data-label]) tbody tr:nth-child(even){background:var(--surface)}
  .prose table:has(td[data-label]) td{
    padding:0; border-bottom:0; margin-bottom:.7rem; overflow-wrap:anywhere;
  }
  .prose table:has(td[data-label]) tr td:last-child{margin-bottom:0}
  .prose table:has(td[data-label]) td::before{
    content:attr(data-label); display:block;
    font-size:.68rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em;
    color:var(--muted); margin-bottom:.15rem;
  }
  /* An empty cell would otherwise render as a lone header with nothing under
     it; pandoc writes "-" for absent values, so this only catches true gaps. */
  .prose table:has(td[data-label]) td:empty{display:none}
}

/* ---- Accessibility ----------------------------------------- */
:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:2px}
@media (prefers-reduced-motion:reduce){*{transition:none !important; scroll-behavior:auto !important}}

/* Skip link: off-screen until focused, then a visible chip at top-left so a
   keyboard/AT user jumps past the nav to the content. */
.skip-link{
  position:absolute; left:8px; top:-52px; z-index:50;
  background:var(--accent); color:var(--paper); font-family:var(--sans);
  font-weight:600; font-size:var(--ui); text-decoration:none;
  padding:8px 14px; border-radius:8px; transition:top .12s ease;
}
.skip-link:focus{top:8px}

/* The CSS-only mobile menu is keyboard-operable: the toggle checkbox is
   focusable (Tab) and switched with Space; show a focus ring on the burger
   while the hidden checkbox has focus so the control is visible (#156). */
.nav-toggle:focus-visible + .nav-burger{outline:2px solid var(--accent); outline-offset:2px}

/* ---- The example gallery ------------------------------------
   Same family as .partgrid: a flat card on a rem-based auto-fill grid, so it
   scales with the root type instead of fighting it. Each card states its
   book's own palette three ways -- the top border, the quote bar, and a row
   of swatches -- all fed by --ex-* properties the generator sets per card
   from that example's config/aesthetic.yaml. Nothing here is sized in px:
   the root font-size clamps up to 34px on a wide screen, and a fixed box
   around scaling text overflows. */
.prose > .gallery{
  max-width:var(--measure-wide); margin:1.8rem auto 2.4rem; padding:0;
  /* min(100%, 17rem) so the single track can shrink below 17rem on a phone
     narrower than that, rather than overflowing its column. */
  display:grid; grid-template-columns:repeat(auto-fill,minmax(min(100%, 17rem),1fr));
  gap:var(--s4);
}
.gallery .ex{
  margin:0; background:var(--surface); border:1px solid var(--rule);
  border-top:3px solid var(--ex-accent); border-radius:9px;
  padding:var(--s5); display:flex; flex-direction:column; gap:.4rem;
  overflow:hidden;
}
/* The palette, named: paper, ink, accent -- the three colours the cover above
   is drawn from. A small caption rides beside each chip so the legend is
   legible without relying on hover alone. */
.ex-swatches{display:flex; flex-wrap:wrap; gap:.6rem; margin:.15rem 0; padding:0; list-style:none}
.ex-swatches li{
  display:inline-flex; align-items:center; gap:.32rem;
  font-family:var(--sans); font-size:.6rem; letter-spacing:.06em;
  text-transform:uppercase; color:var(--muted);
}
.ex-swatches li::before{
  content:""; width:.8rem; height:.8rem; border-radius:3px;
  background:var(--sw); border:1px solid rgb(0 0 0 / .2);
}
.ex-kind{
  display:flex; flex-wrap:wrap; gap:.4rem; align-items:center; margin:0;
  font-family:var(--sans); font-size:.68rem; letter-spacing:.07em;
  text-transform:uppercase; color:var(--muted); max-width:none;
}
.ex-trim{border:1px solid var(--rule-strong); border-radius:999px; padding:.05em .45em}
.ex-name{
  font-family:var(--serif); font-weight:700; font-size:1.06rem; line-height:1.25;
  color:var(--ink); margin:0; letter-spacing:-.005em;
}
.ex-sub{margin:0; color:var(--muted); font-size:.84rem; font-style:italic; max-width:none}
.ex-summary{margin:.1rem 0 0; font-size:.87rem; line-height:1.5; max-width:none}
.ex-register{
  margin:.15rem 0 0; padding-left:.6rem; border-left:2px solid var(--ex-accent);
  color:var(--muted); font-size:.8rem; line-height:1.45; max-width:none;
}
.ex-chips{display:flex; flex-wrap:wrap; gap:.3rem; list-style:none; margin:.3rem 0 0; padding:0; max-width:none}
.ex-chips li{
  font-family:var(--sans); font-size:.67rem; letter-spacing:.01em;
  background:var(--zebra); border:1px solid var(--rule);
  border-radius:999px; padding:.1rem .5rem; color:var(--muted);
}
.ex-foot{
  display:flex; flex-wrap:wrap; gap:.35rem; justify-content:space-between;
  align-items:baseline; margin:auto 0 0; padding-top:.6rem;
  border-top:1px solid var(--rule); max-width:none;
  font-family:var(--sans); font-size:.7rem; color:var(--muted);
}
.ex-foot a{white-space:nowrap; font-weight:600}

/* The built-book previews: real page images rendered from each example's PDF,
   shown as sheets on the book's OWN paper colour (--ex-paper, set per card).
   The pages carry the single interior ink on that paper -- the honest print
   palette -- so the colours the card names are the colours you see. The mat
   stays the book's light paper in dark mode too: paper is paper. Three pages
   sit in a row and scale down together; on a narrow card they scroll rather
   than shrink to nothing. */
.ex-pages{
  display:flex; gap:.55rem; margin:0 0 .1rem; padding:.7rem;
  background:var(--ex-paper); border:1px solid var(--rule); border-radius:7px;
  justify-content:center; align-items:flex-start;
}
/* The three pages share the row and shrink together to fit -- flex-basis 0 so
   none can push the strip past its box and force a scrollbar. Aspect is the
   page's own; a cap keeps them from dominating a tall card. */
.ex-pages img{
  flex:1 1 0; min-width:0; width:100%; height:auto; max-height:15rem;
  object-fit:contain; object-position:top;
  border:1px solid rgb(0 0 0 / .14); border-radius:2px;
  background:#fff; box-shadow:0 1px 5px rgb(0 0 0 / .2);
}
/* A hairline of the book's paper against a dark card, so the light mat reads
   as a deliberate lightbox rather than a glare. */
@media (prefers-color-scheme:dark){ .ex-pages{box-shadow:inset 0 0 0 1px rgb(0 0 0 / .3)} }
:root[data-theme="dark"] .ex-pages{box-shadow:inset 0 0 0 1px rgb(0 0 0 / .3)}
:root[data-theme="light"] .ex-pages{box-shadow:none}
.ex-links{display:inline-flex; gap:.7rem; align-items:baseline; flex-wrap:wrap}
.ex-pdf{font-weight:600; white-space:nowrap; color:var(--accent-ink)}

/* ---- The cover-style catalogue -----------------------------
   One sample cover per style, the same book across the library. A responsive
   grid of framed covers with a caption block; all rem so it scales with the
   fluid root like the rest of the site. */
.prose > .cover-styles{
  max-width:var(--measure-wide); margin:1.8rem auto 2.4rem; padding:0;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(min(100%, 15rem),1fr));
  gap:var(--s5);
}
.cs{margin:0; display:flex; flex-direction:column; gap:.55rem}
.cs img{
  width:100%; height:auto; border-radius:4px;
  border:1px solid var(--rule); background:#fff;
  box-shadow:0 2px 10px rgb(0 0 0 / .18);
}
.cs figcaption{display:flex; flex-direction:column; gap:.15rem}
.cs-name{font-family:var(--serif); font-weight:700; font-size:1.02rem; color:var(--ink)}
.cs-era{font-family:var(--sans); font-size:.72rem; letter-spacing:.03em; color:var(--muted)}
.cs-note{font-size:.86rem; line-height:1.45; color:var(--ink); margin-top:.1rem}
.cs-id{
  align-self:flex-start; margin-top:.15rem; font-family:var(--mono);
  font-size:.72rem; color:var(--accent-ink);
  background:var(--zebra); border:1px solid var(--rule);
  border-radius:5px; padding:.05rem .4rem;
}

/* The --from showcase: a source photo, an arrow, the plate drawn from it. */
.prose > .from-demo{
  max-width:var(--measure-wide); margin:1.4rem auto 2.4rem; padding:0;
  display:flex; flex-direction:column; gap:1.6rem;
}
.from-pair{margin:0}
.from-imgs{display:flex; align-items:center; gap:.8rem}
.from-imgs img{
  flex:1 1 0; min-width:0; width:100%; height:auto; border-radius:4px;
  border:1px solid var(--rule); background:#fff; box-shadow:0 2px 10px rgb(0 0 0 / .14);
}
.from-arrow{flex:0 0 auto; color:var(--muted); font-size:1.5rem; line-height:1}
.from-pair figcaption{
  margin-top:.5rem; font-family:var(--sans); font-size:.84rem; color:var(--ink);
}
.from-medium{font-weight:700}
.from-credit{display:block; color:var(--muted); font-size:.76rem; margin-top:.1rem}
@media (max-width:520px){ .from-imgs{flex-direction:column} .from-arrow{transform:rotate(90deg)} }

/* The figure-placement showcase: each place= value's source beside a shot of
   what the press typeset from it. Breaks out to the wide measure; each card is
   the shot left, the source and a note right, stacking on a narrow screen. */
.prose > .place-demo{
  max-width:var(--measure-wide); margin:1.4rem auto 2.4rem; padding:0;
  display:flex; flex-direction:column; gap:1.9rem;
}
.place-card{
  margin:0; display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);
  gap:1rem 1.6rem; align-items:start;
  padding-bottom:1.7rem; border-bottom:1px solid var(--rule);
}
.place-card:last-child{border-bottom:0; padding-bottom:0}
.place-shot{margin:0}
.place-shot img{
  width:100%; height:auto; max-height:26rem; object-fit:contain; object-position:top center;
  border-radius:4px; border:1px solid var(--rule); background:#fff;
  box-shadow:0 2px 10px rgb(0 0 0 / .14);
}
.place-src{display:flex; flex-direction:column; gap:.7rem; min-width:0}
.place-name{margin:0; font-family:var(--sans); font-weight:700; color:var(--ink)}
.place-name code{font-size:.94em; color:var(--accent-ink)}
/* Wrap the source instead of scrolling it: attribute syntax is not
   whitespace-sensitive, and the whole point is to read every attribute. */
.place-code, .place-code > code{white-space:pre-wrap; overflow-wrap:anywhere}
.place-code{margin:0; font-size:.82rem}
.place-note{margin:0; font-family:var(--sans); font-size:.9rem; color:var(--ink)}
@media (max-width:640px){ .place-card{grid-template-columns:1fr; gap:.8rem} }

/* How the width measure changes each placement: labelled rows of shots that
   shrink full -> half -> third, so the effect reads at a glance. */
.prose > .measure-demo{
  max-width:var(--measure-wide); margin:1.4rem auto 2.4rem; padding:0;
  display:flex; flex-direction:column; gap:1.9rem;
}
.measure-group{display:flex; flex-direction:column; gap:.85rem}
.measure-head{margin:0; font-family:var(--sans); font-size:.92rem; color:var(--ink)}
.measure-row{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(165px,1fr));
  gap:1rem; align-items:start;
}
.measure-shot{margin:0; display:flex; flex-direction:column; gap:.5rem}
.measure-shot img{
  width:100%; height:auto; border-radius:4px; border:1px solid var(--rule);
  background:#fff; box-shadow:0 2px 10px rgb(0 0 0 / .12);
}
.measure-shot figcaption{
  text-align:center; font-family:var(--sans); font-size:.8rem; color:var(--muted);
}
.measure-shot figcaption code{color:var(--accent-ink)}

/* How a wrap behaves against running text: full-page scenarios. Each card is
   the shot(s) over a name and a note; the guard card pairs a page foot with the
   next page's head, an arrow between. */
.prose > .behav-demo{
  max-width:var(--measure-wide); margin:1.4rem auto 2.4rem; padding:0;
  display:flex; flex-direction:column; gap:2.1rem;
}
.behav-card{
  margin:0; display:flex; flex-direction:column; gap:.9rem;
  padding-bottom:2rem; border-bottom:1px solid var(--rule);
}
.behav-card:last-child{border-bottom:0; padding-bottom:0}
.behav-shots{display:flex; justify-content:center; align-items:flex-start; gap:1rem; flex-wrap:wrap}
.behav-shots img{
  max-width:100%; height:auto; border-radius:4px; border:1px solid var(--rule);
  background:#fff; box-shadow:0 2px 12px rgb(0 0 0 / .14);
}
.behav-pair img{max-width:47%}
.behav-arrow{align-self:center; color:var(--muted); font-size:1.7rem; line-height:1}
.behav-name{margin:0; font-family:var(--sans); font-weight:700; color:var(--ink)}
.behav-note{margin:0; font-family:var(--sans); font-size:.9rem; color:var(--ink); max-width:46rem}
@media (max-width:560px){ .behav-pair img{max-width:100%} .behav-arrow{transform:rotate(90deg)} }

/* The whole vocabulary at work on real, dense pages: a row of full-page shots,
   sized so the layout and the figures-in-text read even if the type does not. */
.prose > .realpage-demo{
  max-width:var(--measure-wide); margin:1.4rem auto 2.4rem; padding:0;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  gap:1.4rem; align-items:start;
}
.realpage{margin:0; display:flex; flex-direction:column; gap:.6rem}
.realpage img{
  width:100%; height:auto; border-radius:4px; border:1px solid var(--rule);
  background:#fff; box-shadow:0 3px 14px rgb(0 0 0 / .16);
}
.realpage figcaption{font-family:var(--sans); font-size:.82rem; color:var(--muted)}
