:root{--primary:#7c3aed;--accent:#db2777;--bg:#fbf9ff;--surface:#f3eff9;--surface-2:color-mix(in oklab, #1f1a29 5%, #fbf9ff);--fg:#1f1a29;--fg-muted:#665f73;--fg-subtle:color-mix(in oklab, #665f73 72%, #fbf9ff);--border:#ded7e8;--border-strong:#665f73;--link:#6d28d9;--tint:color-mix(in oklab, #6d28d9 9%, transparent);--tint-strong:color-mix(in oklab, #6d28d9 16%, transparent);--bar-bg:color-mix(in oklab, #fbf9ff 82%, transparent);--shadow-1:0 1px 2px rgba(0,0,0,.04),0 1px 3px rgba(0,0,0,.06);--shadow-2:0 4px 12px rgba(0,0,0,.08);--shadow-3:0 16px 48px rgba(0,0,0,.18);--tok-keyword:color-mix(in oklab, #b02a5b 78%, var(--fg));--tok-string:color-mix(in oklab, #0a6b3d 78%, var(--fg));--tok-number:color-mix(in oklab, #0b5fa5 78%, var(--fg));--tok-builtin:color-mix(in oklab, #6b3fc0 78%, var(--fg));--tok-operator:color-mix(in oklab, #b5390d 78%, var(--fg));--tok-variable:color-mix(in oklab, #8a4b06 78%, var(--fg));--tok-comment:var(--fg-subtle);--text-2xs:.6875rem;--text-xs:.75rem;--text-sm:.8125rem;--text-ui:.875rem;--text-base:1rem;--text-lg:1.125rem;--text-xl:1.375rem;--text-2xl:1.75rem;--text-3xl:2.5rem;--text-4xl:3.5rem;--track-4xl:-.022em;--track-3xl:-.018em;--track-2xl:-.014em;--track-xl:-.01em;--track-base:0;--track-caps:.085em;--leading-tight:1.15;--leading-snug:1.45;--leading-normal:1.7;--space-1:.25rem;--space-2:.5rem;--space-3:.75rem;--space-4:1rem;--space-5:1.5rem;--space-6:2rem;--space-7:3rem;--space-8:4rem;--measure:73ch;--sidebar-w:16rem;--toc-w:14rem;--bar-h:3.25rem;--header-h:var(--bar-h);--radius:18px;--radius-sm:calc(18px / 2);--font:ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-display:ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono:ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Mono", "JetBrains Mono", Menlo, Consolas, "Liberation Mono", monospace;--border-width:1px;--surface-image:none;}@media (prefers-color-scheme: dark){:root:not([data-theme=light]){--primary:#a78bfa;--accent:#f472b6;--bg:#0f0a16;--surface:#191020;--surface-2:color-mix(in oklab, #f7f1ff 8%, #0f0a16);--fg:#f7f1ff;--fg-muted:#b4a9c3;--fg-subtle:color-mix(in oklab, #b4a9c3 72%, #0f0a16);--border:#33263f;--border-strong:#b4a9c3;--link:#d8b4fe;--tint:color-mix(in oklab, #d8b4fe 13%, transparent);--tint-strong:color-mix(in oklab, #d8b4fe 22%, transparent);--bar-bg:color-mix(in oklab, #0f0a16 82%, transparent);--shadow-1:0 1px 2px rgba(0,0,0,.4);--shadow-2:0 4px 14px rgba(0,0,0,.5);--shadow-3:0 20px 56px rgba(0,0,0,.66);--tok-keyword:color-mix(in oklab, #ff8098 78%, var(--fg));--tok-string:color-mix(in oklab, #7ee787 78%, var(--fg));--tok-number:color-mix(in oklab, #79c0ff 78%, var(--fg));--tok-builtin:color-mix(in oklab, #d2a8ff 78%, var(--fg));--tok-operator:color-mix(in oklab, #ffab70 78%, var(--fg));--tok-variable:color-mix(in oklab, #ffc857 78%, var(--fg));--tok-comment:var(--fg-subtle);}}:root[data-theme=dark]{--primary:#a78bfa;--accent:#f472b6;--bg:#0f0a16;--surface:#191020;--surface-2:color-mix(in oklab, #f7f1ff 8%, #0f0a16);--fg:#f7f1ff;--fg-muted:#b4a9c3;--fg-subtle:color-mix(in oklab, #b4a9c3 72%, #0f0a16);--border:#33263f;--border-strong:#b4a9c3;--link:#d8b4fe;--tint:color-mix(in oklab, #d8b4fe 13%, transparent);--tint-strong:color-mix(in oklab, #d8b4fe 22%, transparent);--bar-bg:color-mix(in oklab, #0f0a16 82%, transparent);--shadow-1:0 1px 2px rgba(0,0,0,.4);--shadow-2:0 4px 14px rgba(0,0,0,.5);--shadow-3:0 20px 56px rgba(0,0,0,.66);--tok-keyword:color-mix(in oklab, #ff8098 78%, var(--fg));--tok-string:color-mix(in oklab, #7ee787 78%, var(--fg));--tok-number:color-mix(in oklab, #79c0ff 78%, var(--fg));--tok-builtin:color-mix(in oklab, #d2a8ff 78%, var(--fg));--tok-operator:color-mix(in oklab, #ffab70 78%, var(--fg));--tok-variable:color-mix(in oklab, #ffc857 78%, var(--fg));--tok-comment:var(--fg-subtle);}:root{--radius:18px;--surface-image:radial-gradient(circle at 8% 0%,var(--tint-strong),transparent 34rem),radial-gradient(circle at 94% 12%,color-mix(in oklab,var(--accent) 10%,transparent),transparent 30rem);--hero-pad:var(--space-8) var(--space-7);--hero-border-width:1px;--hero-radius:calc(var(--radius) * 1.35);--hero-bg:linear-gradient(135deg,color-mix(in oklab,var(--bg) 90%,transparent),color-mix(in oklab,var(--surface) 78%,transparent));--hero-border:color-mix(in oklab,var(--link) 28%,var(--border));--hero-shadow:var(--shadow-2);--hero-glow:1;--card-bg:color-mix(in oklab,var(--bg) 88%,transparent);--card-glow:.68;--card-shadow:0 10px 35px color-mix(in oklab,var(--primary) 8%,transparent);--card-hover-shadow:0 20px 54px color-mix(in oklab,var(--primary) 15%,transparent);}header.site{backdrop-filter:saturate(1.4) blur(18px);-webkit-backdrop-filter:saturate(1.4) blur(18px);}
*,*::before,*::after{box-sizing:border-box;}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;}
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--font);
 font-size:var(--text-base);line-height:var(--leading-normal);
 background-image:var(--surface-image);background-attachment:fixed;
 -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;}
a{color:var(--link);text-decoration:none;}
a code{color:inherit;}
::selection{background:var(--tint-strong);}
:focus-visible{outline:2px solid var(--link);outline-offset:2px;
 border-radius:var(--radius-sm);}
/* There is deliberately no `:focus:not(:focus-visible)` outline reset here.
   Every current browser already withholds the ring for pointer interaction, so
   the reset only suppressed a ring nobody saw — and a blanket suppression is
   one edit away from being why a keyboard user cannot tell where they are.
   One focus treatment, applied everywhere, is also simply easier to trust. */
img{max-width:100%;height:auto;}
svg{display:block;}

/* Skip link: the first thing a keyboard lands on, invisible until it matters. */
.skip{position:absolute;left:var(--space-2);top:-4rem;z-index:80;
 background:var(--bg);color:var(--link);border:1px solid var(--border-strong);
 border-radius:var(--radius-sm);padding:var(--space-2) var(--space-3);
 font-size:var(--text-ui);transition:top .15s;}
.skip:focus{top:var(--space-2);}

/* --- header ---------------------------------------------------------------- */
header.site{position:sticky;top:0;z-index:40;border-bottom:1px solid var(--border);
 background:var(--bar-bg);backdrop-filter:saturate(1.6) blur(12px);
 -webkit-backdrop-filter:saturate(1.6) blur(12px);}
.bar{display:flex;align-items:center;gap:var(--space-3);height:var(--bar-h);
 padding:0 var(--space-5);max-width:100rem;margin:0 auto;}
.brand{display:flex;align-items:center;gap:var(--space-2);color:var(--fg);
 font-family:var(--font-display);font-size:var(--text-lg);font-weight:700;
 letter-spacing:var(--track-xl);}
.brand .mark{width:1.5rem;height:1.5rem;flex:none;color:var(--link);}
.browse{display:inline-flex;align-items:center;height:2rem;padding:0 var(--space-3);
 border:1px solid var(--border-strong);border-radius:var(--radius-sm);
 font-family:var(--font-mono);font-size:var(--text-sm);font-weight:700;
 color:var(--fg);text-decoration:none;letter-spacing:.01em;}
.browse:hover{border-color:var(--link);background:var(--surface-2);}
.browse:focus-visible{outline:2px solid var(--link);outline-offset:2px;}
.bar .spacer{flex:1;}
/* The links menu. Declared with the header rather than with the components so
   it does not arrive a frame late and shove the theme control sideways. */
.more{position:relative;flex:none;}
.more>summary{width:2rem;height:2rem;display:inline-flex;align-items:center;
 justify-content:center;cursor:pointer;list-style:none;border-radius:var(--radius-sm);
 border:1px solid transparent;color:var(--fg-muted);
 transition:background .14s,color .14s,border-color .14s;}
.more>summary::-webkit-details-marker{display:none;}
.more>summary:hover{background:var(--surface-2);color:var(--fg);}
.more>summary:focus-visible{outline:2px solid var(--link);outline-offset:2px;}
.more[open]>summary{background:var(--surface-2);color:var(--fg);
 border-color:var(--border-strong);}
.more>summary svg{width:1.125rem;height:1.125rem;}
.more-menu{position:absolute;top:calc(100% + var(--space-2));right:0;z-index:70;
 min-width:14rem;padding:var(--space-2);background:var(--bg);
 border:1px solid var(--border-strong);border-radius:var(--radius);
 box-shadow:var(--shadow-3);}
/* Every row carries its name. An icon that needs a tooltip was not carrying
   its meaning, which is what made three unlabelled glyphs read as strange. */
.more-item{display:flex;align-items:center;gap:var(--space-3);
 padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);
 color:var(--fg-muted);font-size:var(--text-ui);line-height:var(--leading-snug);
 text-decoration:none;}
.more-item:hover{background:var(--surface-2);color:var(--fg);}
.more-item:focus-visible{outline:2px solid var(--link);outline-offset:-2px;}
.more-item svg{width:1.125rem;height:1.125rem;flex:none;}

/* --- layout ---------------------------------------------------------------- */
/* The measure lives on the *column*, not on `main`. Capping `main` inside a
   `1fr` track leaves the track's leftover width as a hole between the content
   and the table of contents; sizing the track to the measure and centring the
   packed tracks puts that space in the margins where it belongs. */
.layout{display:grid;
 grid-template-columns:var(--sidebar-w) minmax(0,var(--measure)) var(--toc-w);
 justify-content:center;gap:var(--space-7);max-width:100rem;margin:0 auto;
 padding:var(--space-6) var(--space-5) var(--space-8);}
main{min-width:0;}
/* A page outside any nav section has no sidebar to render. Placing `main` in
   the second track anyway keeps the reading column at exactly the same x as on
   every other page -- which is what stops the column jumping under instant
   navigation -- where letting it flow into the empty first track would squeeze
   it to the sidebar's width and push the contents page into the middle. */
.layout.no-side>main{grid-column:2;}

/* --- prose ----------------------------------------------------------------- */
/* One rhythm rule instead of per-element margins that fight each other: every
   sibling gets the same top gap, and the headings widen it below. */
.prose>*+*{margin-top:var(--space-4);}
.prose h1,.prose h2,.prose h3{font-family:var(--font-display);
 line-height:var(--leading-tight);
 scroll-margin-top:calc(var(--header-h) + var(--space-4));}
.prose h1{font-size:var(--text-3xl);letter-spacing:var(--track-3xl);font-weight:400;}
.prose h2{font-size:var(--text-2xl);letter-spacing:var(--track-2xl);font-weight:700;}
.prose h3{font-size:var(--text-xl);letter-spacing:var(--track-xl);font-weight:700;}
/* h4 drops out of the display voice into the structural one — at the fourth
   level a label reads better than a smaller heading. */
.prose h4{font-family:var(--font-mono);font-size:var(--text-2xs);font-weight:700;
 text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-muted);
 line-height:var(--leading-snug);
 scroll-margin-top:calc(var(--header-h) + var(--space-4));}
.prose>h2{margin-top:var(--space-7);}
.prose>h3{margin-top:var(--space-6);}
.prose>h4{margin-top:var(--space-5);}
.prose>h1+*,.prose>h2+*,.prose>h3+*,.prose>h4+*{margin-top:var(--space-3);}
/* The lead: one step up, straight after the title. Costs one rule and does the
   work an author would otherwise do with a bold first sentence. */
.prose>h1+p{font-size:var(--text-lg);line-height:1.6;margin-top:var(--space-4);}
.prose>ul,.prose>ol{padding-left:var(--space-5);}
li+li{margin-top:var(--space-1);}
li>ul,li>ol{margin-top:var(--space-1);padding-left:var(--space-5);}
/* Prose links are underlined by default. Colour alone is not a distinguishing
   signal (WCAG 1.4.1) and hover-only underlines never help a reader who is
   scanning rather than pointing; the hairline weight keeps it quiet. */
.prose p a:not(.hero-action),.prose li a,.prose td a,
.prose blockquote a{text-decoration:underline;
 text-decoration-thickness:1px;text-underline-offset:.16em;
 text-decoration-color:color-mix(in oklab, var(--link) 38%, transparent);
 transition:text-decoration-color .12s;}
.prose p a:not(.hero-action):hover,.prose li a:hover,.prose td a:hover,
.prose blockquote a:hover{text-decoration-color:var(--link);}
:target{scroll-margin-top:calc(var(--header-h) + var(--space-4));}

/* --- controls -------------------------------------------------------------- */
.icon-btn{border:1px solid transparent;background:transparent;color:var(--fg-muted);
 border-radius:var(--radius-sm);cursor:pointer;font:inherit;width:2rem;height:2rem;
 display:inline-flex;align-items:center;justify-content:center;flex:none;
 transition:background .14s,color .14s;}
.icon-btn:hover{background:var(--surface-2);color:var(--fg);}
.icon-btn svg{width:1.125rem;height:1.125rem;}
#menu-toggle{display:none;}
.theme svg{display:none;}
.theme[data-mode=system] .i-auto,.theme[data-mode=light] .i-sun,
.theme[data-mode=dark] .i-moon{display:block;}
kbd{font-family:var(--font-mono);font-size:var(--text-2xs);line-height:1;
 padding:.25em .4em;border:1px solid var(--border);border-bottom-width:2px;
 border-radius:4px;background:var(--surface);color:var(--fg-muted);
 white-space:nowrap;}

/* --- search trigger -------------------------------------------------------- */
/* A button that reads as a field. It is not an input: there is nothing to type
   into until the palette opens, and a decorative input is a keyboard trap that
   promises typing it cannot do. */
.search-open{display:inline-flex;align-items:center;gap:var(--space-2);
 border:1px solid var(--border-strong);background:var(--bg);color:var(--fg-muted);
 border-radius:var(--radius-sm);padding:0 var(--space-2) 0 var(--space-3);
 height:2rem;cursor:pointer;font:inherit;font-size:var(--text-ui);width:15rem;
 transition:border-color .14s,color .14s;}
.search-open:hover{border-color:var(--link);color:var(--fg);}
.search-open svg{width:1rem;height:1rem;flex:none;}
.search-open .label{flex:1;text-align:left;}
html.no-js .search-open{display:none;}

/* --- repository link ------------------------------------------------------- */
/* The name and the counts stack: the name is the link's subject, the counts are
   a footnote to it, and side-by-side they read as three separate controls. */
/* The sidebar names the section it is showing, and the name is the way back to
   that section's own landing page -- a cookbook recipe had no visible route to
   the cookbook index, only a nav entry called "Overview" with nothing saying
   what it was the overview of. */
.side-head{display:block;margin:0 0 var(--space-4);padding-bottom:var(--space-3);
 border-bottom:1px solid var(--border);font-family:var(--font-mono);
 font-size:var(--text-2xs);font-weight:700;text-transform:uppercase;
 letter-spacing:var(--track-caps);color:var(--fg-muted);text-decoration:none;
 transition:color .14s;}
.side-head:hover{color:var(--fg);}
.side-head:focus-visible{outline:2px solid var(--link);outline-offset:2px;}

.repo{line-height:var(--leading-snug);text-decoration:none;}
.repo-name{font-family:var(--font-mono);font-size:var(--text-sm);
 white-space:nowrap;max-width:12rem;overflow:hidden;text-overflow:ellipsis;}
/* Muted, not subtle: a star count is information at 11px, and `--fg-subtle` is
   the token that is deliberately below AA because nothing reads it for content. */
.repo-stats{display:flex;gap:var(--space-2);color:var(--fg-muted);
 font-size:var(--text-2xs);}
.repo-stats .stat{display:inline-flex;align-items:center;gap:.25em;}
.repo-stats svg{width:.75rem;height:.75rem;}
/* Name over counts in one column, so the mark centres against the pair. */
.repo-text{display:flex;flex-direction:column;min-width:0;}

/* --- section switcher ------------------------------------------------------ */
/* Replaces a row of twelve tabs. It occupies one slot of the bar at every
   width, so there is no second row to fit and nothing scrolls sideways. */
.sections{position:relative;flex:none;min-width:0;}
.sections>summary{display:inline-flex;align-items:center;gap:var(--space-2);
 height:2rem;padding:0 var(--space-2) 0 var(--space-3);cursor:pointer;
 border:1px solid transparent;border-radius:var(--radius-sm);
 font-family:var(--font-mono);font-size:var(--text-sm);color:var(--fg);
 white-space:nowrap;min-width:0;list-style:none;
 transition:border-color .14s,background .14s;}
.sections>summary::-webkit-details-marker{display:none;}
.sections>summary:hover{border-color:var(--border-strong);background:var(--surface-2);}
.sections>summary:focus-visible{outline:2px solid var(--link);outline-offset:2px;}
.sections-here{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.sections .chev{width:1rem;height:1rem;flex:none;color:var(--fg-subtle);
 transition:transform .16s;}
.sections[open] .chev{transform:rotate(180deg);}
.sections[open]>summary{border-color:var(--border-strong);background:var(--surface-2);}
.sections-menu{position:absolute;top:calc(100% + var(--space-2));left:0;z-index:70;
 min-width:15rem;max-width:min(22rem, calc(100vw - var(--space-5) * 2));
 max-height:min(28rem, calc(100vh - var(--bar-h) - var(--space-6)));overflow-y:auto;
 padding:var(--space-2);background:var(--bg);border:1px solid var(--border-strong);
 border-radius:var(--radius);box-shadow:var(--shadow-3);}
.sections-menu a{display:block;padding:var(--space-2) var(--space-3);
 border-radius:var(--radius-sm);border-left:2px solid transparent;
 color:var(--fg-muted);font-size:var(--text-ui);line-height:var(--leading-snug);}
.sections-menu a:hover{background:var(--surface-2);color:var(--fg);}
.sections-menu a.active{color:var(--fg);border-left-color:var(--link);
 background:var(--surface-2);}
.sections-menu a:focus-visible{outline:2px solid var(--link);outline-offset:-2px;}

/* --- sidebar: the thread --------------------------------------------------- */
/* The signature. One continuous stroke runs from the section root down to the
   page you are on, so "you are here" carries its whole ancestry instead of
   being a highlight with no context. Off-path branches keep the same rail in
   the hairline colour, so the thread is a change of state, not of structure. */
nav.side{font-size:var(--text-ui);line-height:var(--leading-snug);align-self:start;
 position:sticky;top:calc(var(--header-h) + var(--space-5));
 max-height:calc(100vh - var(--header-h) - var(--space-7));overflow-y:auto;
 overscroll-behavior:contain;padding-right:var(--space-2);
 scrollbar-width:thin;scrollbar-color:var(--border) transparent;}
nav.side::-webkit-scrollbar{width:9px;}
nav.side::-webkit-scrollbar-thumb{background:var(--border);border-radius:5px;
 border:2px solid var(--bg);}
nav.side::-webkit-scrollbar-thumb:hover{background:var(--fg-muted);}
.lvl{display:flex;flex-direction:column;}
.lvl:not(.lvl-0){border-left:1px solid var(--border);margin-left:var(--space-2);
 padding-left:var(--space-3);transition:border-color .18s;}
.lvl.on-path{border-left-color:var(--link);box-shadow:inset 1px 0 0 var(--link);}
.nav-page{position:relative;color:var(--fg-muted);padding:var(--space-1) 0;
 transition:color .12s;}
.nav-page:hover{color:var(--fg);}
.nav-page.active{color:var(--link);font-weight:600;}
/* The terminal node: a dot sitting on the thread, centred over the 1px rail. */
.lvl:not(.lvl-0)>.nav-page.active::before{content:"";position:absolute;
 left:calc(var(--space-3) * -1 - 3px);top:calc(50% - 2.5px);width:5px;height:5px;
 border-radius:50%;background:var(--link);}
.sec>summary{list-style:none;cursor:pointer;display:flex;align-items:center;
 gap:var(--space-2);color:var(--fg);padding:var(--space-1) 0;user-select:none;}
.sec>summary::-webkit-details-marker{display:none;}
.sec>summary::before{content:"";width:.34em;height:.34em;flex:none;margin-left:.15em;
 border-right:1.5px solid var(--fg-subtle);border-bottom:1.5px solid var(--fg-subtle);
 transform:rotate(-45deg);transition:transform .18s;}
.sec[open]>summary::before{transform:rotate(45deg);}
.sec>summary:hover{color:var(--link);}
/* Depth is carried by the voice: the top level of the sidebar is a structural
   label in mono, everything under it is a page-shaped title in the body face. */
.sec-0>summary{font-family:var(--font-mono);font-size:var(--text-2xs);
 text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-muted);
 font-weight:700;margin-top:var(--space-4);}
.lvl-0>.sec-0:first-child>summary{margin-top:0;}
.sec-1>summary,.sec-2>summary{font-weight:600;}

/* --- on this page ---------------------------------------------------------- */
aside.toc{font-size:var(--text-sm);line-height:var(--leading-snug);align-self:start;
 position:sticky;top:calc(var(--header-h) + var(--space-5));
 max-height:calc(100vh - var(--header-h) - var(--space-7));overflow-y:auto;
 scrollbar-width:thin;}
.toc-head{font-family:var(--font-mono);font-size:var(--text-2xs);font-weight:700;
 text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-subtle);
 margin-bottom:var(--space-3);}
/* One continuous rail with a lit segment, not a border per row: per-row borders
   leave hairline gaps at every margin and read as a dashed line by accident. */
.toc-rail{border-left:1px solid var(--border);}
.toc-rail a{display:block;color:var(--fg-muted);padding:var(--space-1) 0
 var(--space-1) var(--space-3);margin-left:-1px;border-left:2px solid transparent;
 transition:color .12s,border-color .12s;}
.toc-rail a:hover{color:var(--fg);}
.toc-rail a.sub{padding-left:var(--space-5);}
.toc-rail a.toc-active{color:var(--link);border-left-color:var(--link);}

/* --- code ------------------------------------------------------------------ */
code{background:color-mix(in oklab, var(--fg) 6%, var(--bg));padding:.1em .3em;
 border-radius:var(--radius-sm);font-size:.875em;font-family:var(--font-mono);}
.code{position:relative;border:var(--border-width) solid var(--border);
 border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow-1);
 overflow:hidden;}
.code pre{margin:0;background:none;border:none;border-radius:0;box-shadow:none;
 padding:var(--space-4);overflow-x:auto;line-height:var(--leading-snug);
 scrollbar-width:thin;}
pre code{background:none;padding:0;border:none;font-size:var(--text-sm);}
/* The chrome strip: what this block is on the left, what you can do with it on
   the right. Present only when the fence names a language or a title. */
.code-head{display:flex;align-items:center;gap:var(--space-3);
 padding:var(--space-2) var(--space-2) var(--space-2) var(--space-4);
 border-bottom:1px solid var(--border);
 background:color-mix(in oklab, var(--fg) 3%, var(--surface));}
.code-title{font-family:var(--font-mono);font-size:var(--text-xs);color:var(--fg);
 flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.code-lang{font-family:var(--font-mono);font-size:var(--text-2xs);
 text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-subtle);}
.copy-btn{border:1px solid transparent;background:transparent;color:var(--fg-muted);
 border-radius:var(--radius-sm);padding:var(--space-1);cursor:pointer;font:inherit;
 display:inline-flex;align-items:center;transition:color .14s,background .14s;}
.copy-btn svg{width:.95rem;height:.95rem;}
.copy-btn:hover{color:var(--fg);background:var(--surface-2);}
.copy-btn.copied{color:var(--link);}
/* Without a head strip the button floats. Hover reveals it for a mouse; focus
   reveals it for a keyboard — it is a real tab stop, so hiding it on focus too
   would be a control nobody can reach. */
.code>.copy-btn{position:absolute;top:var(--space-2);right:var(--space-2);
 background:var(--bg);border-color:var(--border);opacity:0;transition:opacity .15s;}
.code:hover>.copy-btn,.code>.copy-btn:focus-visible{opacity:1;}
.code:has(>.copy-btn) pre{padding-right:var(--space-8);}
.hl{display:block;margin:0 calc(var(--space-4) * -1);
 padding:0 var(--space-4) 0 calc(var(--space-4) - 2px);
 border-left:2px solid var(--accent);background:var(--tint);}
.tok-comment{color:var(--tok-comment);font-style:italic;}
.tok-string{color:var(--tok-string);}
.tok-number{color:var(--tok-number);}
.tok-keyword{color:var(--tok-keyword);font-weight:600;}
.tok-builtin{color:var(--tok-builtin);}
.tok-variable{color:var(--tok-variable);}
.tok-operator{color:var(--tok-operator);}

/* --- headings: the anchor -------------------------------------------------- */
.anchor{opacity:0;margin-left:var(--space-2);color:var(--fg-subtle);font-weight:400;
 font-family:var(--font-mono);font-size:.7em;text-decoration:none;}
h1:hover .anchor,h2:hover .anchor,h3:hover .anchor,h4:hover .anchor,
.anchor:focus-visible{opacity:1;}
.anchor:hover{color:var(--link);}

/* --- blockquote ------------------------------------------------------------ */
/* No fill and no card. A pull quote that is boxed competes with admonitions,
   which are boxed because they mean something; this one only means "aside". */
blockquote{margin-inline:0;padding-left:var(--space-4);
 border-left:2px solid var(--border-strong);color:var(--fg-muted);}
blockquote>*+*{margin-top:var(--space-3);}

/* --- admonitions ----------------------------------------------------------- */
/* A rule, a mono label, and the faintest wash rather than a saturated card. */
.admonition{--adm:var(--accent);position:relative;padding:var(--space-3)
 var(--space-4) var(--space-4);border-left:2px solid var(--adm);
 border-radius:0 var(--radius) var(--radius) 0;}
.admonition.warning,.admonition.caution,.admonition.attention,.admonition.danger,
.admonition.error,.admonition.bug,.admonition.failure{
 background:color-mix(in oklab, var(--adm) 6%, var(--bg));}
.admonition>*+*{margin-top:var(--space-3);}
.admonition-title{display:flex;align-items:center;gap:var(--space-2);
 font-family:var(--font-mono);font-size:var(--text-2xs);font-weight:700;
 text-transform:uppercase;letter-spacing:var(--track-caps);
 color:color-mix(in oklab, var(--adm) 30%, var(--fg));line-height:var(--leading-snug);}
/* A Unicode glyph rather than an icon font or an SVG sprite: it costs no bytes,
   needs no network, and still says *which kind* of callout this is — which a
   plain coloured rule does not. The glyph per kind is set below. */
.admonition-title::before{content:var(--adm-glyph,"\24D8");color:var(--adm);
 font-size:1.25em;line-height:1;flex:none;}
details.admonition>summary{list-style:none;cursor:pointer;}
details.admonition>summary::-webkit-details-marker{display:none;}
details.admonition>summary::after{content:"+";font-family:var(--font-mono);
 color:var(--adm);margin-left:auto;font-weight:700;font-size:1.2em;}
details.admonition[open]>summary::after{content:"\2212";}
details.admonition[open]>summary{margin-bottom:var(--space-3);}
.admonition.note,.admonition.info,.admonition.important{--adm:#2563eb;}
.admonition.tip,.admonition.success,.admonition.check,.admonition.hint
{--adm:#059669;--adm-glyph:"\2713";}
.admonition.warning,.admonition.caution,.admonition.attention
{--adm:#b45309;--adm-glyph:"\26A0";}
.admonition.danger,.admonition.error,.admonition.bug,.admonition.failure
{--adm:#dc2626;--adm-glyph:"\2715";}
.admonition.question,.admonition.example,.admonition.faq{--adm:#7c3aed;--adm-glyph:"?";}
.admonition.quote,.admonition.abstract,.admonition.summary
{--adm:var(--fg-muted);--adm-glyph:"\275D";}
/* Dark mode lifts each hue: the light-mode set is tuned for white and goes
   muddy on a near-black surface. */
:root[data-theme=dark] .admonition.note,:root[data-theme=dark] .admonition.info,
:root[data-theme=dark] .admonition.important{--adm:#60a5fa;}
:root[data-theme=dark] .admonition.tip,:root[data-theme=dark] .admonition.success,
:root[data-theme=dark] .admonition.check,:root[data-theme=dark] .admonition.hint{--adm:#34d399;}
:root[data-theme=dark] .admonition.warning,:root[data-theme=dark] .admonition.caution,
:root[data-theme=dark] .admonition.attention{--adm:#fbbf24;}
:root[data-theme=dark] .admonition.danger,:root[data-theme=dark] .admonition.error,
:root[data-theme=dark] .admonition.bug,:root[data-theme=dark] .admonition.failure{--adm:#f87171;}
:root[data-theme=dark] .admonition.question,:root[data-theme=dark] .admonition.example,
:root[data-theme=dark] .admonition.faq{--adm:#a78bfa;}
@media (prefers-color-scheme:dark){:root:not([data-theme=light]) .admonition.note,
 :root:not([data-theme=light]) .admonition.info,
 :root:not([data-theme=light]) .admonition.important{--adm:#60a5fa;}
 :root:not([data-theme=light]) .admonition.tip,
 :root:not([data-theme=light]) .admonition.success,
 :root:not([data-theme=light]) .admonition.check,
 :root:not([data-theme=light]) .admonition.hint{--adm:#34d399;}
 :root:not([data-theme=light]) .admonition.warning,
 :root:not([data-theme=light]) .admonition.caution,
 :root:not([data-theme=light]) .admonition.attention{--adm:#fbbf24;}
 :root:not([data-theme=light]) .admonition.danger,
 :root:not([data-theme=light]) .admonition.error,
 :root:not([data-theme=light]) .admonition.bug,
 :root:not([data-theme=light]) .admonition.failure{--adm:#f87171;}
 :root:not([data-theme=light]) .admonition.question,
 :root:not([data-theme=light]) .admonition.example,
 :root:not([data-theme=light]) .admonition.faq{--adm:#a78bfa;}}

/* --- tabs ------------------------------------------------------------------ */
/* CSS-only, driven by a radio group. The previous version was JS-only with
   `display:none` panels, so with JavaScript off *every tab but the first was
   unreachable content* — and the labels were buttons with no tab semantics.
   A radio group is keyboard-navigable natively (arrow keys, one tab stop) and
   needs no script at all. */
.tabbed{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;}
.tabbed>input{position:absolute;opacity:0;width:0;height:0;}
.tab-labels{display:flex;gap:var(--space-1);background:var(--surface);
 border-bottom:1px solid var(--border);padding:var(--space-1) var(--space-2) 0;
 overflow-x:auto;scrollbar-width:none;}
.tab-label{font-family:var(--font-mono);font-size:var(--text-sm);
 color:var(--fg-muted);padding:var(--space-2) var(--space-3);cursor:pointer;
 border-bottom:2px solid transparent;white-space:nowrap;
 transition:color .12s,border-color .12s;}
.tab-label:hover{color:var(--fg);}
.tab-panel{display:none;padding:var(--space-4);}
.tab-panel>*+*{margin-top:var(--space-3);}

/* --- tables ---------------------------------------------------------------- */
/* The wrapper is what stops a wide table scrolling the whole page sideways on
   a phone; the table itself only knows how to be as wide as its content. */
.table-wrap{overflow-x:auto;scrollbar-width:thin;
 border:1px solid var(--border);border-radius:var(--radius);}
table{border-collapse:collapse;width:100%;font-size:var(--text-sm);
 font-variant-numeric:tabular-nums;}
th,td{padding:var(--space-2) var(--space-4);text-align:left;
 border-bottom:1px solid var(--border);}
/* Horizontal rules only. A full 1px grid on every cell is the spreadsheet look
   and it fights the hairline weight used everywhere else. */
thead th{background:var(--surface);font-family:var(--font-mono);
 font-size:var(--text-2xs);font-weight:700;text-transform:uppercase;
 letter-spacing:var(--track-caps);color:var(--fg-muted);white-space:nowrap;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:var(--surface-2);}

/* --- page footer ----------------------------------------------------------- */
.page-nav{display:flex;justify-content:space-between;gap:var(--space-4);
 margin-top:var(--space-8);padding-top:var(--space-5);
 border-top:1px solid var(--border);}
.page-nav a{flex:1;max-width:49%;padding:var(--space-3) var(--space-4);
 border:1px solid var(--border);border-radius:var(--radius);color:var(--fg);
 transition:border-color .15s,background .15s;}
.page-nav a:hover{border-color:var(--link);background:var(--surface-2);}
.page-nav .dir{display:block;font-family:var(--font-mono);font-size:var(--text-2xs);
 text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-subtle);
 margin-bottom:var(--space-1);}
.page-nav .title{font-family:var(--font-display);font-size:var(--text-lg);
 line-height:var(--leading-snug);}
.page-nav .nav-next{text-align:right;margin-left:auto;}
.page-meta{margin-top:var(--space-5);font-family:var(--font-mono);
 font-size:var(--text-xs);color:var(--fg-subtle);}
.page-meta a{color:var(--fg-muted);}
.page-meta a:hover{color:var(--link);}

/* --- search palette -------------------------------------------------------- */
/* A native <dialog>: focus containment, Escape, and inertness of the page
   behind it are all browser behaviour rather than a script to get wrong. */
/* Positioned rather than margin-centred so it lands in the same place whether
   the browser opened it modally or fell back to a plain `open` attribute. */
dialog.palette{position:fixed;inset:12vh 0 auto 0;margin-inline:auto;
 border:1px solid var(--border);border-radius:var(--radius);
 background:var(--bg);color:var(--fg);box-shadow:var(--shadow-3);padding:0;
 width:min(40rem,92vw);max-height:min(30rem,72vh);
 overflow:hidden;flex-direction:column;}
dialog.palette[open]{display:flex;}
dialog.palette::backdrop{background:color-mix(in oklab, #000 44%, transparent);
 backdrop-filter:blur(2px);}
dialog.palette[open]{animation:pop .14s ease-out;}
@keyframes pop{from{opacity:0;transform:translateY(-6px) scale(.99);}}
.palette-bar{display:flex;align-items:center;gap:var(--space-3);
 padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border);}
.palette-bar svg{width:1.1rem;height:1.1rem;color:var(--fg-subtle);flex:none;}
#docs-search{flex:1;min-width:0;border:none;background:none;color:var(--fg);
 font:inherit;font-size:var(--text-lg);outline:none;}
#docs-search::placeholder{color:var(--fg-subtle);}
#docs-search::-webkit-search-cancel-button{display:none;}
.palette-results{overflow-y:auto;padding:var(--space-2);scrollbar-width:thin;}
.palette-results .group{font-family:var(--font-mono);font-size:var(--text-2xs);
 font-weight:700;text-transform:uppercase;letter-spacing:var(--track-caps);
 color:var(--fg-subtle);padding:var(--space-3) var(--space-2) var(--space-1);
 display:flex;gap:var(--space-2);align-items:baseline;}
/* Where the page sits in the nav. Un-bolded: it qualifies the page name, and
   two equally loud labels on one line read as two separate groups. */
.palette-results .group .in{font-weight:400;text-transform:none;
 letter-spacing:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.palette-results a{display:block;padding:var(--space-2) var(--space-3);
 border-radius:var(--radius-sm);color:var(--fg);border-left:2px solid transparent;}
.palette-results a:hover,.palette-results a:focus-visible{background:var(--surface-2);
 border-left-color:var(--link);outline:none;}
.palette-results .r-title{display:block;font-weight:600;}
.palette-results .r-ctx{display:block;color:var(--fg-muted);font-size:var(--text-sm);
 overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.palette-results mark{background:var(--tint-strong);color:inherit;
 border-radius:2px;padding:0 1px;}
.palette-empty{padding:var(--space-6) var(--space-4);text-align:center;
 color:var(--fg-muted);font-size:var(--text-ui);}
.palette-hint{display:flex;gap:var(--space-3);align-items:center;
 padding:var(--space-2) var(--space-4);border-top:1px solid var(--border);
 background:var(--surface);font-size:var(--text-2xs);color:var(--fg-subtle);}

/* --- back to top ----------------------------------------------------------- */
.to-top{position:fixed;right:var(--space-5);bottom:var(--space-5);z-index:30;
 width:2.5rem;height:2.5rem;border-radius:50%;border:1px solid var(--border-strong);
 background:var(--bg);color:var(--fg-muted);cursor:pointer;box-shadow:var(--shadow-2);
 display:flex;align-items:center;justify-content:center;
 opacity:0;visibility:hidden;transform:translateY(6px);
 transition:opacity .18s,transform .18s,visibility .18s,color .14s;}
.to-top svg{width:1.05rem;height:1.05rem;}
.to-top.on{opacity:1;visibility:visible;transform:none;}
.to-top:hover{color:var(--link);border-color:var(--link);}

/* --- hero -------------------------------------------------------------------- */
/* The one place a docs page is allowed to be loud. It earns that by being rare:
   two pages in the whole site open with one, and the band is built from the same
   three voices as everything else -- mono eyebrow, display headline, body lede.
   No panel, no fill, no gradient; the space around it does the work. */
.hero{position:relative;isolation:isolate;overflow:hidden;
 padding:var(--hero-pad,var(--space-6) 0 var(--space-7));
 border:1px solid var(--hero-border,var(--border));
 border-width:var(--hero-border-width,0 0 1px);border-radius:var(--hero-radius,0);
 background:var(--hero-bg,transparent);box-shadow:var(--hero-shadow,none);
 margin-bottom:var(--space-7);}
.hero::before,.hero::after{content:"";position:absolute;z-index:-1;
 border-radius:50%;pointer-events:none;opacity:var(--hero-glow,0);filter:blur(2px);}
.hero::before{width:24rem;height:24rem;right:-10rem;top:-13rem;
 background:radial-gradient(circle,var(--tint-strong),transparent 68%);}
.hero::after{width:19rem;height:19rem;right:5rem;bottom:-15rem;
 background:radial-gradient(circle,
 color-mix(in oklab,var(--accent) 18%,transparent),transparent 70%);}
.hero-signals{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-5);}
.hero-signals span{padding:.3rem .65rem;border:1px solid var(--border);
 border-radius:999px;background:color-mix(in oklab,var(--bg) 72%,transparent);
 color:var(--fg-muted);font-family:var(--font-mono);font-size:var(--text-2xs);
 letter-spacing:.02em;}
.layout.no-side .hero.wide,.layout.no-side .story-grid.wide{width:min(80rem,calc(100vw - 3rem));
 margin-left:50%;transform:translateX(-50%);}

/* --- the dependency plate -------------------------------------------------- */
/* The home page's opener. A herbarium plate: a small-caps caption, the
   specimen, and a determination line under it. The specimen here is the list
   of packages the reader no longer installs, set dense enough that it reads as
   engraving hatch at arm's length and as their own requirements.txt up close.
   That double reading is the whole idea, and it is why the names are set small
   rather than large -- a list this long set big is a wall, set small it is a
   texture with an argument inside it. */
.plate{padding:var(--space-6) 0 var(--space-7);margin-bottom:var(--space-7);
 border-bottom:1px solid var(--border);}
.plate-caption{margin:0 0 var(--space-4);font-family:var(--font-mono);
 font-size:var(--text-2xs);font-weight:700;text-transform:uppercase;
 letter-spacing:var(--track-caps);color:var(--fg-muted);}
.plate-title{margin:0;font-family:var(--font-display);font-weight:400;
 font-size:clamp(2rem, 5vw, var(--text-4xl));line-height:1.05;
 letter-spacing:var(--track-4xl);text-wrap:balance;}
.plate-lede{margin:var(--space-5) 0 0;font-size:var(--text-lg);
 line-height:1.55;color:var(--fg-muted);max-width:54ch;}

/* The specimen. `column-width` rather than a fixed count, so the block reflows
   from five columns to one without a breakpoint for each step. */
.plate-names{margin:var(--space-6) 0 0;padding:0;list-style:none;
 column-width:8.5rem;column-gap:var(--space-5);
 font-family:var(--font-mono);font-size:var(--text-2xs);
 line-height:1.9;color:var(--fg-subtle);
 /* A band, not a wall. Five columns hold the whole list on a laptop and the
    cap never engages; two columns on a phone would run to some 1600px of
    names before the reader reached a sentence, which is the same
    small-viewport failure this redesign exists to fix. Faded rather than cut,
    because a hard edge reads as a bug and a fade reads as hatch continuing
    past the plate -- and the count below states the total either way, so
    nothing is hidden that is not also said. */
 max-height:24rem;overflow:hidden;
 -webkit-mask-image:linear-gradient(to bottom, #000 82%, transparent 100%);
 mask-image:linear-gradient(to bottom, #000 82%, transparent 100%);
 /* The rules top and bottom are the plate's edges. Hairlines, because an
    engraving is made of them and a heavier border would read as a card. */
 border-top:1px solid var(--border);border-bottom:1px solid var(--border);
 padding-block:var(--space-4);}
.plate-names li{break-inside:avoid;}
/* The strike is a rule through the name, not a decoration on it: one pixel, in
   the ink colour rather than the text colour, so the name greys out and the
   line stays crisp. `line-through` and not `<del>` styling, because these were
   never in the document -- they are things the reader will not add. */
.plate-names li{text-decoration:line-through;text-decoration-thickness:1px;
 text-decoration-color:color-mix(in oklab, var(--fg) 42%, transparent);}
.plate-count{margin:var(--space-4) 0 0;font-family:var(--font-mono);
 font-size:var(--text-xs);color:var(--fg-muted);max-width:54ch;
 line-height:var(--leading-snug);}
.plate-count strong{color:var(--fg);font-weight:700;}
.plate-actions{display:flex;flex-wrap:wrap;gap:var(--space-3);
 margin:var(--space-6) 0 0;}
.plate-action{display:inline-flex;align-items:center;gap:var(--space-2);
 font-family:var(--font-mono);font-size:var(--text-sm);color:var(--fg);
 border:1px solid var(--border-strong);border-radius:var(--radius-sm);
 padding:var(--space-2) var(--space-4);
 transition:border-color .14s,background .14s,color .14s;}
.plate-action::after{content:"\2192";color:var(--fg-subtle);transition:color .14s;}
.plate-action:hover{border-color:var(--link);color:var(--link);
 background:var(--tint);text-decoration:none;}
.plate-action:hover::after{color:var(--link);}
.plate-action.primary{border-color:var(--link);color:var(--link);}
@media (max-width:46rem){
 .plate-names{column-width:7rem;column-gap:var(--space-4);}
}
.hero-eyebrow{margin:0 0 var(--space-4);font-family:var(--font-mono);
 font-size:var(--text-2xs);font-weight:700;text-transform:uppercase;
 letter-spacing:var(--track-caps);color:var(--fg-muted);}
.hero-title{margin:0;font-family:var(--font-display);font-weight:400;
 font-size:clamp(2.25rem, 5.5vw, var(--text-4xl));
 line-height:1.05;letter-spacing:var(--track-4xl);text-wrap:balance;}
.hero-lede{margin:var(--space-5) 0 0;font-size:var(--text-lg);line-height:1.55;
 color:var(--fg-muted);max-width:52ch;}
.hero-actions{display:flex;flex-wrap:wrap;gap:var(--space-3);
 margin:var(--space-6) 0 0;}
.hero-action{display:inline-flex;align-items:center;gap:var(--space-2);
 font-family:var(--font-mono);font-size:var(--text-sm);color:var(--fg);
 border:1px solid var(--border-strong);border-radius:var(--radius-sm);
 padding:var(--space-2) var(--space-4);
 transition:border-color .14s,background .14s,color .14s;}
.hero-action::after{content:"\2192";color:var(--fg-subtle);transition:color .14s;}
.hero-action:hover{border-color:var(--link);color:var(--link);
 background:var(--tint);text-decoration:none;}
.hero-action:hover::after{color:var(--link);}
.hero-action.primary{border-color:var(--link);color:var(--link);}

/* --- story cards ---------------------------------------------------------- */
.story-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
 gap:var(--space-4);margin:var(--space-7) 0;}
.story-card{position:relative;display:flex;min-height:17rem;flex-direction:column;
 padding:var(--space-5);overflow:hidden;color:var(--fg);text-decoration:none;
 border:1px solid var(--border);border-radius:var(--radius);
 background:var(--card-bg,var(--bg));box-shadow:var(--card-shadow,var(--shadow-1));
 transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease;}
.story-card::before{content:"";position:absolute;width:12rem;height:12rem;
 right:-7rem;top:-7rem;border-radius:50%;opacity:var(--card-glow,0);
 background:radial-gradient(circle,var(--tint-strong),transparent 68%);
 transition:transform .25s ease,opacity .25s ease;}
.story-card:hover{transform:translateY(-3px);border-color:var(--link);
 box-shadow:var(--card-hover-shadow,var(--shadow-2));}
.story-card:hover::before{transform:scale(1.2);opacity:1;}
.story-card h2{margin:var(--space-6) 0 0;font-family:var(--font-display);
 font-size:var(--text-xl);line-height:1.15;letter-spacing:var(--track-xl);}
.story-card p{margin:var(--space-3) 0 var(--space-6);color:var(--fg-muted);
 line-height:1.55;font-size:var(--text-ui);}
.story-index,.story-meta{font-family:var(--font-mono);font-size:var(--text-2xs);
 text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-subtle);}
.story-meta{margin-top:auto;color:var(--fg-muted);}
.story-arrow{position:absolute;right:var(--space-5);top:var(--space-5);
 color:var(--link);font-size:var(--text-lg);transition:transform .2s ease;}
.story-card:hover .story-arrow{transform:translate(2px,-2px);}
@media (max-width:44rem){
 .story-grid{grid-template-columns:1fr;}
 .story-card{min-height:14rem;}
 .layout.no-side .hero.wide,.layout.no-side .story-grid.wide{width:100%;
  margin-left:0;transform:none;}
}

/* --- explanatory figures ---------------------------------------------------- */
/* Every figure runs one 8s timeline shared by both halves of its comparison, so
   the difference is something you watch rather than something a caption claims.
   The conventional structure borrows the charts' muted "field" treatment; only
   wreath's half gets the brand colour. */
.fig{margin-inline:0;border:var(--border-width) solid var(--border);
 border-radius:var(--radius);background:var(--bg);overflow:hidden;}
.fig-head{display:flex;align-items:center;gap:var(--space-3);
 padding:var(--space-2) var(--space-3) var(--space-2) var(--space-4);
 border-bottom:1px solid var(--border);
 background:color-mix(in oklab, var(--fg) 3%, var(--bg));}
.fig-title{flex:1;font-family:var(--font-mono);font-size:var(--text-2xs);
 font-weight:700;text-transform:uppercase;letter-spacing:var(--track-caps);
 color:var(--fg-muted);}
/* A checkbox, not a script: WCAG 2.3.2 wants a way to stop anything that moves
   for more than five seconds, and a control that only exists once a runtime has
   loaded is not one. */
.fig-pause{position:absolute;opacity:0;width:0;height:0;}
.fig-pause-label{font-family:var(--font-mono);font-size:var(--text-2xs);
 color:var(--fg-muted);border:1px solid var(--border-strong);
 border-radius:var(--radius-sm);padding:var(--space-1) var(--space-2);
 cursor:pointer;white-space:nowrap;transition:color .14s,border-color .14s;}
.fig-pause-label::before{content:"\23F8  ";}
.fig-pause:checked+.fig-pause-label{color:var(--link);border-color:var(--link);}
.fig-pause:checked+.fig-pause-label::before{content:"\25B6  ";}
.fig-pause:focus-visible+.fig-pause-label{outline:2px solid var(--link);
 outline-offset:2px;}
.fig:has(.fig-pause:checked) .fig-body *{animation-play-state:paused !important;}
.fig-body{padding:var(--space-4);overflow-x:auto;color:var(--fg);}
.fig-note{margin:0;padding:0 var(--space-4) var(--space-4);
 font-size:var(--text-xs);color:var(--fg-subtle);}

/* Figure type: the same three voices as the page. Labels take the body face,
   everything measured or literal takes mono. */
.f-label{font-family:var(--font-mono);font-size:11px;font-weight:700;
 text-transform:uppercase;letter-spacing:var(--track-caps);fill:var(--fg-muted);}
.f-tick{font-family:var(--font-mono);font-size:11px;fill:var(--fg-subtle);}
.f-side{font-family:var(--font-mono);font-size:9px;letter-spacing:.08em;
 fill:var(--fg-subtle);}
.f-node-t{font-family:var(--font-mono);font-size:9px;fill:var(--fg-muted);}
.f-boundary{stroke:var(--border-strong);stroke-width:1;stroke-dasharray:2 4;}
.f-edge{stroke:var(--border-strong);stroke-width:1;fill:none;}
.f-node{fill:var(--surface);stroke:var(--border-strong);stroke-width:1.2;}
.f-rule{stroke:var(--border-strong);stroke-width:1;}

/* --- figure: the boundary --- */
.f-path{fill:none;stroke-width:1.5;}
.f-path-other{stroke:var(--fg-subtle);stroke-dasharray:3 3;}
.f-path-wreath{stroke:var(--primary);}
.f-dot{offset-rotate:0deg;}
.f-dot-other{fill:var(--fg-muted);animation:f-travel-other 8s linear infinite;}
.f-dot-wreath{fill:var(--primary);animation:f-travel-wreath 8s linear infinite;}
/* Same pixel speed, different distance: wreath's dot arrives at 72% of the
   cycle and waits, which is the whole comparison in one number. */
@keyframes f-travel-other{from{offset-distance:0%;}to{offset-distance:100%;}}
@keyframes f-travel-wreath{0%{offset-distance:0%;}72%{offset-distance:100%;}
 100%{offset-distance:100%;}}
.f-note-in{font-family:var(--font-mono);font-size:10px;font-weight:700;
 fill:var(--primary);opacity:0;animation:f-appear 8s linear infinite;}
@keyframes f-appear{0%,55%{opacity:0;}62%,100%{opacity:1;}}

/* --- figure: routing --- */
.f-box{fill:var(--surface);stroke:var(--border-strong);stroke-width:1.2;}
.f-box-ghost{fill:none;stroke:var(--accent);stroke-dasharray:3 3;}
.f-box-t{font-family:var(--font-mono);font-size:10px;fill:var(--fg);}
.f-brace{fill:none;stroke:var(--accent);stroke-width:1.2;}
.f-tick-warn{fill:var(--accent);}
.f-fold{opacity:0;animation:f-reveal 8s ease-out infinite;
 animation-delay:calc(1s + var(--i) * .45s);}
@keyframes f-reveal{0%{opacity:0;transform:translateY(-3px);}
 4%,92%{opacity:1;transform:none;}99%,100%{opacity:0;}}
.f-route{font-family:var(--font-mono);font-size:10.5px;fill:var(--fg-muted);}
.f-route-hit{fill:var(--primary);font-weight:700;}
/* A cell is one route's bit after one segment test. Off cells stay drawn, so a
   row shows you *when* a route dropped out rather than just that it did. */
.f-cell{fill:none;stroke:var(--border-strong);stroke-width:1;opacity:.45;}
.f-cell-on{fill:var(--fg-muted);stroke:none;opacity:0;
 animation:f-cell-in 8s ease-out infinite;animation-delay:calc(var(--t) * 1s + .6s);}
.f-cell-hit{fill:var(--primary);}
@keyframes f-cell-in{0%{opacity:0;}3%,92%{opacity:.9;}99%,100%{opacity:0;}}

/* --- figure: timers --- */
.f-mark{fill:none;stroke:var(--accent);stroke-width:1.5;stroke-dasharray:3 3;
 opacity:0;animation:f-reveal 8s ease-out infinite;animation-delay:.6s;}
/* One pulse per level of the sift, so the log-n in the caption is something you
   count rather than something you are told. */
.f-sift{animation:f-sift 8s ease-out infinite;animation-delay:calc(var(--t) * 1s);}
@keyframes f-sift{0%,100%{fill:var(--surface);stroke:var(--border-strong);}
 2%{fill:var(--fg-muted);stroke:var(--fg-muted);}
 9%{fill:var(--surface);stroke:var(--border-strong);}}
.f-pip{opacity:.2;animation:f-pip 8s steps(1,end) infinite;
 animation-delay:calc(var(--t) * 1s);}
.f-pip-other{fill:var(--fg-muted);}
.f-pip-wreath{fill:var(--primary);}
@keyframes f-pip{0%{opacity:.2;}2%,94%{opacity:1;}99%,100%{opacity:.2;}}
.f-compact{fill:none;stroke:var(--fg-subtle);stroke-width:1;stroke-dasharray:3 3;
 opacity:0;animation:f-compact 8s ease-in-out infinite;}
@keyframes f-compact{0%,58%{opacity:0;}66%,80%{opacity:.85;}88%,100%{opacity:0;}}
.f-compact-t{opacity:0;animation:f-compact 8s ease-in-out infinite;}
/* The mark's own broken stroke, scaled to r=58: the wheel is a circular buffer
   and the mark is a circle of separate things held in one shape. */
.f-ring{fill:none;stroke:var(--border);stroke-width:2;stroke-linecap:round;
 stroke-dasharray:101 27 61 27 88 61;}
.f-slot-tick{stroke:var(--border-strong);stroke-width:1;}
.f-timer{fill:var(--primary);opacity:.85;}
.f-hand{stroke:var(--accent);stroke-width:2;stroke-linecap:round;
 transform-origin:466px 132px;animation:f-sweep 8s linear infinite;}
@keyframes f-sweep{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}
.f-hub{fill:var(--accent);}
.f-callout{fill:none;stroke:var(--border-strong);stroke-width:1;stroke-dasharray:2 3;}
.f-link{stroke:var(--border-strong);stroke-width:1.5;}
/* The link that closes over the departed node: drawn from nothing, at the same
   instant the node leaves. That splice is the entire cost of a cancel. */
.f-link-closed{stroke:var(--primary);stroke-dasharray:84;stroke-dashoffset:84;
 animation:f-splice 8s ease-out infinite;animation-delay:1s;}
@keyframes f-splice{0%{stroke-dashoffset:84;}3%,94%{stroke-dashoffset:0;}
 99%,100%{stroke-dashoffset:84;}}
.f-chain{fill:var(--surface);stroke:var(--border-strong);stroke-width:1.5;}
.f-chain-gone{fill:var(--primary);stroke:var(--primary);
 animation:f-unlink 8s ease-out infinite;animation-delay:1s;}
@keyframes f-unlink{0%{opacity:1;transform:none;}
 4%{opacity:0;transform:translateY(-14px);}
 94%{opacity:0;transform:translateY(-14px);}99%,100%{opacity:1;transform:none;}}

/* --- misc ------------------------------------------------------------------ */
hr{border:none;border-top:1px solid var(--border);margin-block:var(--space-7);}
figure.chart{margin-inline:0;padding:var(--space-4);
 border:var(--border-width) solid var(--border);border-radius:var(--radius);
 background:var(--bg);color:var(--fg);overflow-x:auto;}
.chart-title{font-family:var(--font-mono);font-size:var(--text-2xs);font-weight:700;
 text-transform:uppercase;letter-spacing:var(--track-caps);color:var(--fg-muted);
 margin-bottom:var(--space-4);}
.chart-label{font-family:var(--font);font-size:13px;}
.chart-value{font-family:var(--font-mono);font-size:12px;
 font-variant-numeric:tabular-nums;opacity:.85;}
.chart-error{color:var(--fg);background:color-mix(in oklab, #dc2626 10%, var(--bg));
 border:1px solid #dc2626;font-size:var(--text-sm);padding:var(--space-3);
 border-radius:var(--radius);}
li input[type=checkbox]{margin-right:var(--space-2);}
del{color:var(--fg-subtle);}

/* --- responsive ------------------------------------------------------------ */
.scrim{display:none;}
@media (max-width:82rem){
 .layout{grid-template-columns:var(--sidebar-w) minmax(0,var(--measure));
  gap:var(--space-6);}
 aside.toc{display:none;}
}
@media (max-width:62rem){
 #menu-toggle{display:inline-flex;}
 .layout,.layout.no-side{grid-template-columns:1fr;gap:var(--space-5);
  padding:var(--space-5) var(--space-4) var(--space-7);}
 .layout.no-side>main{grid-column:auto;}   /* one track again; track 2 is implicit */
 nav.side{position:fixed;top:0;left:0;bottom:0;width:19rem;max-width:84vw;
  max-height:100vh;z-index:60;background:var(--bg);border-right:1px solid var(--border);
  padding:var(--space-5) var(--space-4);transform:translateX(-100%);
  transition:transform .22s ease;box-shadow:var(--shadow-3);}
 body.nav-open nav.side{transform:none;}
 body.nav-open .scrim{display:block;position:fixed;inset:0;z-index:55;
  background:rgba(0,0,0,.5);}
 .prose h1{font-size:var(--text-2xl);}
 .prose h2{font-size:var(--text-xl);}
 .page-nav{flex-direction:column;}
 .page-nav a{max-width:none;}
}
@media (max-width:46rem){
 .search-open{width:2rem;padding:0;justify-content:center;}
 .search-open .label,.search-open kbd{display:none;}
 .bar{padding:0 var(--space-3);gap:var(--space-2);}
 /* The word beside the mark goes before the section name does. The mark
    identifies the site; the section name is the only way to reach the other
    eleven sections from a phone, because the sidebar shows the section you are
    already in, so losing it would strand a reader in one branch of the tree.
    Hidden with `display:none` on its own span rather than `font-size:0` on the
    link: the link keeps its accessible name from `aria-label`, and a zeroed
    font size is a trick some screen readers read differently from others. */
 .brand-name{display:none;}
 .brand{gap:0;}
 .sections-here{max-width:9rem;}
}
@media (max-width:23rem){
 /* A 320px phone in portrait. The section name truncates rather than the
    control disappearing, so the switcher is still operable. */
 .sections-here{max-width:5.5rem;}
}

/* --- motion ---------------------------------------------------------------- */
/* WCAG 2.3.3. Not "less motion" — none, for anyone who asked. */
@media (prefers-reduced-motion:reduce){
 html{scroll-behavior:auto;}
 *,*::before,*::after{animation-duration:.01ms !important;
  animation-iteration-count:1 !important;transition-duration:.01ms !important;}
 /* Figures carry their argument in elements that animate *in*. With motion off
    they have to be visible, not stuck on whichever keyframe came last. */
 .f-fold,.f-note-in,.f-timer,.f-cell-on,.f-pip,.f-mark{opacity:1 !important;}
 .f-link-closed{stroke-dashoffset:0 !important;}
 .f-chain-gone{opacity:0 !important;}
 .fig-pause-label{display:none;}
 ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){
  animation:none !important;}
}

/* --- view transitions ------------------------------------------------------ */
/* Instant navigation swaps <main> in place. Cross-fading only the content —
   not the chrome — is what makes it read as "the page updated" rather than
   "something reloaded", and it keeps the sidebar's scroll position visibly
   untouched. */
@view-transition{navigation:none;}
::view-transition-old(content){animation:fade-out .1s ease both;}
::view-transition-new(content){animation:fade-in .16s ease both;}
@keyframes fade-out{to{opacity:0;}}
@keyframes fade-in{from{opacity:0;transform:translateY(4px);}}
main{view-transition-name:content;}

/* --- print ----------------------------------------------------------------- */
@media print{
 header.site,nav.side,aside.toc,.page-nav,.copy-btn,.skip,.scrim,.to-top,
 dialog.palette,.page-meta{display:none !important;}
 .layout,.layout.no-side{display:block;max-width:none;padding:0;}
 main{max-width:none;}
 .code,.admonition,.tabbed,figure.chart,.fig{box-shadow:none;break-inside:avoid;}
 .fig-pause-label{display:none;}
 .tab-panel{display:block !important;}
 a[href^="http"]::after{content:" (" attr(href) ")";font-size:var(--text-xs);
  color:var(--fg-muted);}
}
.tabbed>input:nth-of-type(1):checked~.tab-panel:nth-of-type(2){display:block;}.tabbed>input:nth-of-type(1):checked~.tab-labels>.tab-label:nth-of-type(1){color:var(--link);border-bottom-color:var(--link);}.tabbed>input:nth-of-type(1):focus-visible~.tab-labels>.tab-label:nth-of-type(1){outline:2px solid var(--link);outline-offset:-2px;}.tabbed>input:nth-of-type(2):checked~.tab-panel:nth-of-type(3){display:block;}.tabbed>input:nth-of-type(2):checked~.tab-labels>.tab-label:nth-of-type(2){color:var(--link);border-bottom-color:var(--link);}.tabbed>input:nth-of-type(2):focus-visible~.tab-labels>.tab-label:nth-of-type(2){outline:2px solid var(--link);outline-offset:-2px;}.tabbed>input:nth-of-type(3):checked~.tab-panel:nth-of-type(4){display:block;}.tabbed>input:nth-of-type(3):checked~.tab-labels>.tab-label:nth-of-type(3){color:var(--link);border-bottom-color:var(--link);}.tabbed>input:nth-of-type(3):focus-visible~.tab-labels>.tab-label:nth-of-type(3){outline:2px solid var(--link);outline-offset:-2px;}.tabbed>input:nth-of-type(4):checked~.tab-panel:nth-of-type(5){display:block;}.tabbed>input:nth-of-type(4):checked~.tab-labels>.tab-label:nth-of-type(4){color:var(--link);border-bottom-color:var(--link);}.tabbed>input:nth-of-type(4):focus-visible~.tab-labels>.tab-label:nth-of-type(4){outline:2px solid var(--link);outline-offset:-2px;}.tabbed>input:nth-of-type(5):checked~.tab-panel:nth-of-type(6){display:block;}.tabbed>input:nth-of-type(5):checked~.tab-labels>.tab-label:nth-of-type(5){color:var(--link);border-bottom-color:var(--link);}.tabbed>input:nth-of-type(5):focus-visible~.tab-labels>.tab-label:nth-of-type(5){outline:2px solid var(--link);outline-offset:-2px;}.tabbed>input:nth-of-type(6):checked~.tab-panel:nth-of-type(7){display:block;}.tabbed>input:nth-of-type(6):checked~.tab-labels>.tab-label:nth-of-type(6){color:var(--link);border-bottom-color:var(--link);}.tabbed>input:nth-of-type(6):focus-visible~.tab-labels>.tab-label:nth-of-type(6){outline:2px solid var(--link);outline-offset:-2px;}.tabbed>input:nth-of-type(7):checked~.tab-panel:nth-of-type(8){display:block;}.tabbed>input:nth-of-type(7):checked~.tab-labels>.tab-label:nth-of-type(7){color:var(--link);border-bottom-color:var(--link);}.tabbed>input:nth-of-type(7):focus-visible~.tab-labels>.tab-label:nth-of-type(7){outline:2px solid var(--link);outline-offset:-2px;}.tabbed>input:nth-of-type(8):checked~.tab-panel:nth-of-type(9){display:block;}.tabbed>input:nth-of-type(8):checked~.tab-labels>.tab-label:nth-of-type(8){color:var(--link);border-bottom-color:var(--link);}.tabbed>input:nth-of-type(8):focus-visible~.tab-labels>.tab-label:nth-of-type(8){outline:2px solid var(--link);outline-offset:-2px;}