/* ============================================================
   scryops — Telemetry design system
   Single CSS file: tokens + base + components.
   Load once in the <head>. Dark is default; add class="light"
   on <html> to switch. All components read from the tokens —
   never hard-code a hex below this block.
   ============================================================ */

:root {
  /* ---- color · dark (default) — Telemetry, warm/de-neon (DS Jul-2026) ---- */
  /* UA-painted parts (scrollbars in overflow-x:auto figures, tables and code,
     form controls) follow color-scheme, which otherwise stays light and paints
     a pale scrollbar inside dark cards. Calm inherits dark; light overrides. */
  color-scheme:dark;
  --bg:#141210;             /* warm charcoal — softened near-black (body still ~12.6:1) */
  --surface:#1C1915;        /* card elevation above the page */
  --border:#2B2620;
  /* --frame is a container's OUTER edge (diagram card, figure, insight, inline
     code chip); --border stays the subtle hairline for dividers. At ~1.25:1
     --border let cards dissolve into the page (their fill is only ~1.07:1
     off it); ~1.7–1.9:1 reads as an edge without ruling the page. */
  --frame:#453E33;
  --heading:#ECE6D9;
  --text:#D7D0C4;
  --muted:#9B9382;
  --green:#66C892;          /* healthy / values / INFO / brand / CTA (de-neoned) */
  --cyan:#E4DDCE;           /* identifiers / links / edges — warm ink, not a hue */
  --link:#E4DDCE;
  --warn:#C9AE6B;
  --danger:#D98E80;         /* muted terracotta — reads as error, not alarm */
  --violet:#A79E8C;         /* OpenTelemetry topic accent — warm mid ink */

  /* party-gallery.html fixed per-character identity accents (flavor, not
     data — pref-mono already forces .party-card/.party-name/.party-ph-cls
     to neutral ink regardless of these, see the pref-mono block below) */
  --party-sorcerer:#E55DC8;
  --party-warlock:#8B6CEF;
  --party-artificer:#D98A2B;

  /* obs-mascot.html fixed per-class identity accents — same pattern as
     the party-gallery tokens above (flavor, not data). Monk and paladin
     are shared with party-gallery now that both have print portraits. */
  --mascot-noob:#A99E88;
  --mascot-warrior:#D98166;
  --mascot-wizard:#8FA8C4;
  --mascot-rogue:#7FB37E;
  --mascot-bard:#B0A2D8;
  --mascot-cleric:#D8B85C;
  --mascot-ranger:#6FB39A;
  --mascot-oracle:#A78FE0;
  --mascot-barbarian:#D2894F;
  --mascot-monk:#C48B9F;
  --mascot-paladin:#93A3CC;
  --mascot-queen:#C98AAB;

  /* the print portraits are two-ink screenprints on cream paper: their ink
     outlines vanish on a dark ground, so the paper STAYS paper in every
     theme (same reasoning as the always-dark code well). Never override. */
  --mascot-paper:#F6F3EC;

  /* inline code chip */
  --ic-bg:#1E1A14; --ic-text:#E4DDCE; --ic-bd:var(--frame);

  /* code well — STAYS DARK in all three themes */
  --code-bg:#100E0B; --code-text:#CEC8BC; --code-bd:#8E8574;
  /* --code-bd is the well's OUTER boundary (needs 3:1, WCAG 1.4.11) — the
     previous #201C16 measured ~1.10:1 against --bg, a silent failure; this
     value (== --mc-ramp-3) clears ~5.1:1. Internal row dividers must stay
     subtle (~1.3:1) — pointing one at --code-bd puts a 5.3:1 hairline under
     every row and the terminal reads as a ruled table. Use --code-rule for
     any internal divider instead. */
  --code-rule:#2A251E;
  /* readout constants — used ON the always-dark code/terminal wells,
     so they must NOT flip with theme (light/calm leave these alone). Anything
     drawn on the well must use these, never --green/--cyan/--warn/--danger:
     those invert with the theme and vanish against the dark surface. */
  --code-hi:#ECE6D9; --code-muted:#8E877A; --code-green:#66C892; --code-cyan:#E4DDCE;
  --code-warn:#C9AE6B; --code-danger:#D98E80; --code-violet:#A79E8C; --code-eco:#8AA97C;
  /* permacomputing — resource-awareness accent (page level; the well uses --code-eco) */
  --eco:#8AA97C; --eco-bright:#A9C79B;

  /* figures */
  --fig-bg:#17130E;
  /* no graph-paper texture behind figures (the old --fig-grid): it sat only
     in the frame's margin and ran behind captions — decoration, not a
     reading aid. --fig-bd is for rules INSIDE a figure; a figure's outer
     edge uses --frame. */
  --fig-bd:#2B2620;
  --node-fill:#16120C; --node-stroke:var(--muted); --edge:var(--cyan); --nlab:var(--heading);

  /* callouts — mapped to log levels INFO / WARN / ERROR.
     DERIVED from the semantic accents (not hard-coded), so they follow every
     theme and the monochrome mode automatically. One source of truth. */
  --info-bg:color-mix(in srgb,var(--green) 8%,transparent); --info-bd:var(--green); --info-lb:var(--green);
  --warn-bg:color-mix(in srgb,var(--warn) 10%,transparent); --warn-bd:var(--warn); --warn-lb:var(--warn);
  --dgr-bg:color-mix(in srgb,var(--danger) 10%,transparent); --dgr-bd:var(--danger); --dgr-lb:var(--danger);

  /* tables */
  --th:#211D17; --tborder:#221E18;

  /* ---- type — proportional reading face, monospace chrome ----
     Reading face is the proportional Atkinson Hyperlegible (already loaded);
     Space Mono headings + IBM Plex Mono code/labels keep the terminal voice.
     Courier Prime is retained as an opt-in typewriter face (--font-typewriter). */
  --font-display:'Space Mono', ui-monospace, monospace;
  --font-read:'Atkinson Hyperlegible', system-ui, sans-serif;
  --font-code:'IBM Plex Mono', ui-monospace, monospace;
  /* Pixel voice, split in two: 'scry' is dot-matrix Doto (scrying — discrete
     points you infer across), 'ops' is solid Pixelify Sans (built practice).
     --font-pixel aliases the ops face — it's denser, so it holds at label
     sizes (it's a local re-cut with open apertures; stock Pixelify's
     C/G/S/Z/2/3/5/6/9 shut and B loses to 8 at 10-13px — see
     scripts/selfhost-fonts.py), and it's the workhorse for every pixel label besides the
     wordmark's first half. Press Start 2P is retained only so pages that
     still name it directly keep rendering; new pixel-voice work uses
     --font-pixel-scry / --font-pixel-ops. Both fall back to the same
     monospace stack so a missing file degrades to one uniform word rather
     than splitting the name across two different fallbacks. */
  --font-pixel-scry:'Doto', ui-monospace, monospace;
  --font-pixel-ops:'Pixelify Sans', ui-monospace, monospace;
  --fw-pixel-scry:900;
  --fw-pixel-ops:700;
  --font-pixel:var(--font-pixel-ops);
  --font-typewriter:'Courier Prime', ui-monospace, monospace;
  --font-body:var(--font-read);

  --fs-display:34px; --fs-h1:29px; --fs-h2:20px; --fs-h3:16px;
  --fs-lede:17.5px;  --fs-body:16px; --fs-code:13.5px;   /* body ≥16px per GUIDELINES non-negotiable #3 */
  --fs-label:11px;   --fs-pixel:11px;
  --lh-body:1.75;    --lh-lede:1.72;   --lh-code:1.75;
  --read-track:0;   /* proportional reading face wants no extra tracking (loosens in .pref-spacing) */
  --measure:70ch;

  /* ---- spacing — 4px base ---- */
  --sp-1:4px; --sp-2:8px; --sp-3:14px; --sp-4:22px; --sp-5:32px; --sp-6:52px;

  /* ---- form ---- */
  --radius:6px; --radius-sm:4px; --radius-retro:3px; --radius-pill:999px; --space:8px; --hair:1px;

  /* ---- bridge: repo's evolved (warm-paper) token names → Telemetry ----
     The repo's components were authored against an earlier vocabulary.
     Aliasing those names onto the Telemetry tokens re-skins every existing
     component to the dark-default palette with no per-component edits.
     var() resolves per-theme, so these follow light/calm automatically. */
  --line:var(--border);
  --head:var(--heading);
  --accent:var(--green);            /* brand / wordmark / CTAs → terminal green */
  --accent-hover:var(--cyan);
  --accent-press:var(--green);
  --on-accent:var(--bg);            /* text on an accent fill = page bg */
  --ok:var(--green);
  --pending:var(--warn);
  --alert:var(--danger);
  --info:var(--cyan);
  --num-idle:var(--muted);
  --ac:var(--green);                /* party-gallery card accent */
  --amber:var(--warn);              /* 404 page */
  --amber-deep:var(--warn); --gold:var(--warn);
  --klein:var(--cyan); --klein-mid:var(--cyan); --klein-glow:var(--cyan);
  --font-mono:var(--font-code); --font-sans:var(--font-read);
}

html.light {
  color-scheme:light;
  --bg:#F6F3EC;
  --surface:#FFFDF7;
  --border:#E6DFD2;
  --frame:#C9BFAD;
  --heading:#1E1811;
  --text:#2C261E;
  --muted:#6F6556;
  --green:#157A45;
  --cyan:#1E1811;           /* identifiers / links / hover — ink, not a hue */
  --link:#1E1811;           /* darker than body text, plus the underline */
  --warn:#7A6220;
  --danger:#A8483C;
  --violet:#5E5648;

  /* darkened to clear 4.5:1 as text against the cream surface — the dark
     hues at the top of this file read fine on charcoal but fail on cream */
  --party-sorcerer:#D222AD;
  --party-warlock:#7D5AED;
  --party-artificer:#A5671E;

  /* same reasoning as the party-gallery light overrides above */
  --mascot-noob:#80745D;
  --mascot-warrior:#C25330;
  --mascot-wizard:#5578A1;
  --mascot-rogue:#4D814C;
  --mascot-bard:#7E67BF;
  --mascot-cleric:#8E7222;
  --mascot-ranger:#44806A;
  --mascot-oracle:#8260D3;
  --mascot-barbarian:#AA642C;
  --mascot-monk:#94586D;
  --mascot-paladin:#5E6F99;
  --mascot-queen:#8A4B6B;

  --ic-bg:#EDE7DB; --ic-text:#1E1811; --ic-bd:var(--frame);

  /* code well intentionally NOT overridden — stays dark */

  /* Callout LABELS are nudged toward the heading ink here (rails are left
     on the pure accent). On cream, the accents land at 4.39–4.60:1 against
     the tinted well — under or barely at the 4.5:1 text threshold, since an
     11px/600 label is normal-size text, not large. The rail is a non-text
     UI component (3:1) and already clears ~4.9–5.3:1, so only the text
     moves and the severity hues stay intact. */
  --info-lb:color-mix(in srgb,var(--green) 80%,var(--heading));
  --warn-lb:color-mix(in srgb,var(--warn) 88%,var(--heading));
  --dgr-lb:color-mix(in srgb,var(--danger) 80%,var(--heading));

  --fig-bg:#FFFDF7;
  --fig-bd:#E6DFD2;
  --node-fill:#F0EBE0;
  /* node-stroke / edge / nlab and all callout -bg/-bd tokens are derived in :root */

  --th:#EFEAE0; --tborder:#E6DFD2;
}

html.calm {
  --bg:#17140E;
  --surface:#1F1B13;
  --border:#332C20;
  --frame:#4A4232;
  --heading:#F2EAD8;
  --text:#E6DECE;
  --muted:#AA9D84;
  --green:#8FB386;
  --cyan:#EFE7D6;           /* identifiers / links / hover — bright warm ink */
  --link:#EFE7D6;
  --warn:#C9AE7B;
  --danger:#DE9384;
  --violet:#B0A48C;

  /* same as dark, except warlock nudged brighter — the original hue sits
     at 4.48:1 against calm's slightly-lighter card surface, just under
     the 4.5:1 text floor */
  --party-sorcerer:#E55DC8;
  --party-warlock:#9078F2;
  --party-artificer:#D98A2B;

  /* all clear 4.5:1 against calm's card surface unchanged from dark */
  --mascot-noob:#A99E88;
  --mascot-warrior:#D98166;
  --mascot-wizard:#8FA8C4;
  --mascot-rogue:#7FB37E;
  --mascot-bard:#B0A2D8;
  --mascot-cleric:#D8B85C;
  --mascot-ranger:#6FB39A;
  --mascot-oracle:#A78FE0;
  --mascot-barbarian:#D2894F;
  --mascot-monk:#C48B9F;
  --mascot-paladin:#93A3CC;
  --mascot-queen:#C98AAB;

  --ic-bg:#241F15; --ic-text:#EFE7D6; --ic-bd:var(--frame);

  /* code well still dark — keep syntax constant */

  --fig-bg:#1A160F;
  --fig-bd:#332C20;
  --node-fill:#221D14;
  /* node-stroke / edge / nlab and all callout tokens are derived in :root */

  --th:#1C180F; --tborder:#2A2418;
}

/* Reading prefs — independent, stackable token overrides. */
html.pref-legible{ --fs-body:18px; --fs-lede:19px; --lh-body:1.72; }  /* Atkinson is the default reading face now; this option enlarges it for low-vision & dyslexia */
html.pref-spacing{ --lh-body:2.0; --lh-lede:1.85; --read-track:.03em; }
html.pref-spacing .article > * + *,
html.pref-spacing .prose > * + *{ margin-top:calc(var(--sp-4) + 4px); }
html.pref-reduce *,html.pref-reduce *::before,html.pref-reduce *::after{transition:none!important;animation:none!important;scroll-behavior:auto!important}
@media (prefers-reduced-motion: reduce){*{transition:none!important;animation:none!important}}

/* ------------------------------------------------------------
   MONOCHROME — the charting system's core law, applied whole-system.
   Every semantic hue collapses onto the neutral value ramp, so meaning
   must survive on the redundant channels the components already carry:
   labels (INFO / WARN / ERROR), icon silhouettes, dashes, stroke weight,
   texture, position, and printed values.

   Serves colour-vision deficiency, greyscale print, OS colour filters,
   and readers who want the lowest-stimulation surface available. It is
   also the system's own honesty test: if a component becomes ambiguous
   here, it was leaning on hue and needs another channel.

   Written against --mc-ramp-*, which is already theme-scoped, so this one
   block resolves correctly in dark, light and calm. Also exposed as a
   bare `.pref-mono` class so a single subtree can be shown monochrome
   (used by the redundant-encoding specimen).
   ------------------------------------------------------------ */
html.pref-mono, .pref-mono {
  /* brightest step = the channels that must read first */
  --cyan:var(--mc-ramp-1);
  --link:var(--mc-ramp-1);
  --danger:var(--mc-ramp-1);
  /* second step = brand, values, warnings */
  --green:var(--mc-ramp-2);
  --warn:var(--mc-ramp-2);
  --eco:var(--mc-ramp-2);
  --eco-bright:var(--mc-ramp-1);
  /* third step = topical accents */
  --violet:var(--mc-ramp-3);

  /* the code well stays dark in every theme, so it takes fixed neutrals */
  --code-green:#B9B2A4;
  --code-cyan:#E4DED0;
  --code-warn:#B9B2A4;
  --code-danger:#E4DED0;
  --code-violet:#9C9587;
  --code-eco:#B9B2A4;
  --ic-text:#D8D2C4;

  /* Derived tokens compute in the scope that DECLARES them, so they don't
     re-derive from the overrides above in a subtree. Restate them here — this
     is a no-op for html.pref-mono and required for a .pref-mono subtree.
     Value is monotonic with severity (ERROR brightest → INFO calmest), so
     brightness itself becomes a working redundant channel.

     RAIL vs LABEL are decoupled on purpose: the rail is a non-text UI
     component (WCAG 1.4.11, needs 3:1) so it stays exactly on the ramp step
     that encodes severity; the label is TEXT (1.4.3, needs 4.5:1 — 11px/600
     is normal-size, not large). INFO sits on the faintest of the three
     steps, so its LABEL alone is nudged toward the theme's strongest ink
     until it clears 4.5:1, without moving the rail off the ramp or
     colliding with WARN's value. Mixing against --mc-ink keeps this correct
     in every theme: --mc-ink is bright in dark/calm and near-black in
     light, so the nudge always increases contrast. */
  --info-bg:color-mix(in srgb,var(--mc-ramp-3) 12%,transparent);
  --info-bd:var(--mc-ramp-3);
  --info-lb:color-mix(in srgb,var(--mc-ramp-3) 72%,var(--mc-ink));
  --warn-bg:color-mix(in srgb,var(--mc-ramp-2) 10%,transparent);
  --warn-bd:var(--mc-ramp-2); --warn-lb:var(--mc-ramp-2);
  --dgr-bg:color-mix(in srgb,var(--mc-ramp-1) 10%,transparent);
  --dgr-bd:var(--mc-ramp-1); --dgr-lb:var(--mc-ramp-1);
  --node-stroke:var(--mc-ramp-2); --edge:var(--mc-ramp-1);
}

/* Pixel art is full-colour SVG; desaturate rather than hide it. */
html.pref-mono .scry-cucco__sprite img, html.pref-mono .scry-cucco__sprite svg,
html.pref-mono .scry-cucco__medallion img, html.pref-mono .scry-cucco__medallion svg,
html.pref-mono .scry-cucco__fgframe img, html.pref-mono .scry-cucco__fgframe svg,
html.pref-mono .scry-cucco__solo img, html.pref-mono .scry-cucco__solo svg,
.pref-mono .scry-cucco__sprite img, .pref-mono .scry-cucco__fgframe img {
  filter:grayscale(1);
}

/* Mascot class accents are inline (set per class in JS), so they need an
   explicit override here — the party reads by name + role + stats instead. */
html.pref-mono .scry-cucco__wrap, html.pref-mono .scry-cucco__fg { border-top-color:var(--muted) !important; }
/* the print portraits' accent disc goes neutral with everything else */
html.pref-mono .scry-cucco--print, html.pref-mono .party-card { --cucco-disc:color-mix(in srgb,var(--muted) 35%,transparent); }
html.pref-mono .scry-cucco__name, html.pref-mono .scry-cucco__plate,
html.pref-mono .scry-cucco__fgnm { color:var(--heading) !important; }
html.pref-mono .scry-cucco__lvl { color:var(--heading) !important; border-color:var(--border) !important; }
html.pref-mono .scry-cucco__fgdot { background:var(--muted) !important; }
html.pref-mono .scry-cucco__fgstat b { color:var(--heading) !important; }
html.pref-mono .scry-cucco__xp > span { background:var(--muted) !important; }
html.pref-mono .scry-cucco__hp > span { color:var(--muted) !important; }

/* party-gallery.html (.party-card) uses the same --ac fixed-identity-accent
   pattern the mascot comment above references — needs the same override. */
html.pref-mono .party-card { border-top-color:var(--muted) !important; }
html.pref-mono .party-name { color:var(--heading) !important; }
html.pref-mono .party-ph-cls { color:var(--muted) !important; }

/* Lite mode (permacomputing Phase 3): drop to system fonts (zero web-font
   bytes) and let the Mermaid runtime be skipped. Auto-applies when the
   browser signals reduced-data. Fonts are the shared block below. */
html.pref-lite{
  --font-display:ui-monospace,'Courier New',monospace;
  --font-read:ui-monospace,'Courier New',monospace;
  --font-code:ui-monospace,'Courier New',monospace;
  --font-body:ui-monospace,'Courier New',monospace;
  --font-pixel:ui-monospace,monospace;
}
@media (prefers-reduced-data: reduce){
  :root{
    --font-display:ui-monospace,'Courier New',monospace;
    --font-read:ui-monospace,'Courier New',monospace;
    --font-code:ui-monospace,'Courier New',monospace;
    --font-body:ui-monospace,'Courier New',monospace;
    --font-pixel:ui-monospace,monospace;
  }
}
/* Mermaid fallback — diagrams are static inline SVG (no JS needed), so no-JS
   visitors get the real diagram. The .mermaid source stays dormant and is
   shown as a readable text block only in Lite/data-saver mode. */
.diagram .mermaid{display:none}
html.pref-lite .diagram .diag-card > svg{display:none}
html.pref-lite .diagram .mermaid{display:block;white-space:pre-wrap;font-family:var(--font-code);font-size:12px;line-height:1.5;color:var(--code-muted);background:var(--code-bg);border:1px solid var(--code-bd);border-radius:var(--radius);padding:14px 16px;overflow-x:auto}
html.pref-lite .diagram .mermaid::before{content:"diagram source";display:block;color:var(--eco);font-size:10px;letter-spacing:.08em;text-transform:uppercase;margin-bottom:8px}

/* No-JS resilience (Permacomputing Phase 4): the footprint badge needs JS to
   fill real numbers — hide its placeholders entirely when JS is off. */
html:not(.js) .scry-fp{display:none}

/* ============================================================
   base
   ============================================================ */
*{box-sizing:border-box}
html{background:var(--bg)}
body{
  margin:0;
  background:var(--bg);
  color:var(--text);
  font-family:var(--font-read);
  font-size:var(--fs-body);
  line-height:var(--lh-body);
  -webkit-font-smoothing:antialiased;
  /* No transition on the mode-switching root: both background and
     color are var-bound, and custom properties can't interpolate —
     transitioning either sticks on the old value (dark-on-dark on
     toggle-back). The mode flip is instant by design. */
}

/* ============================================================
   article shell + measure
   ============================================================ */
.article{max-width:var(--measure);margin:0 auto;padding:0 24px}
.article > * + *{margin-top:var(--sp-4)}
/* Paragraph rhythm for the actual content — .Content renders directly into
   .prose (see single.html), so this is the selector that spaces prose
   siblings; .article's own > * + * above only spans its few structural
   children (single-header / prose / article-end). */
.prose > * + *{margin-top:var(--sp-4)}

/* ============================================================
   headings — Space Mono, lowercase
   ============================================================ */
h1,.h1{font-family:var(--font-display);font-weight:400;font-size:var(--fs-h1);line-height:1.25;letter-spacing:-.01em;color:var(--heading);text-transform:lowercase;margin:0}
h2,.h2{font-family:var(--font-display);font-weight:400;font-size:var(--fs-h2);line-height:1.3;color:var(--heading);text-transform:lowercase;margin:var(--sp-6) 0 0}
h3,.h3{font-family:var(--font-display);font-weight:400;font-size:var(--fs-h3);color:var(--heading);text-transform:lowercase;margin:38px 0 0}
/* A plain element right after a heading gets a tighter step than the general
   34px evidence / 22px paragraph rhythm (--sp-4) — 30px, deliberately off the
   4px scale, per the DS reading-page template. The :not() guards are
   intentional, not decorative: .prose > .table-scroll and .prose > .highlight
   (2 classes each) already outrank this rule (1 class + 1 type), but the
   explicit :not() exclusion makes evidence structurally unmatchable here, so
   evidence keeps full spacing after a heading regardless of future edits to
   this file. */
.prose > h2 + *:not(.table-scroll):not(.highlight):not(figure),
.prose > h3 + *:not(.table-scroll):not(.highlight):not(figure){margin-top:30px}
/* .prose > * + * (line 324) has higher specificity (one class) than the bare
   h2,.h2 / h3,.h3 selectors above (one type selector for a plain <h2>/<h3>),
   so it silently wins margin-top for any heading that isn't .prose's first
   child — i.e. essentially every real heading. These two rules restore the
   intended heading rhythm specifically inside prose content, without
   touching the base h2,.h2/h3,.h3 rule that other, non-.prose headings
   (e.g. card titles) still rely on. */
.prose > h2{margin-top:var(--sp-6)}
.prose > h3{margin-top:38px}
.title{font-family:var(--font-display);font-weight:400;font-size:var(--fs-display);line-height:1.18;letter-spacing:-.01em;color:var(--heading);text-transform:lowercase;margin:0}

/* ============================================================
   prose
   ============================================================ */
p{margin:0;color:var(--text)}
.lede{font-size:var(--fs-lede);line-height:var(--lh-lede);color:var(--text)}

a{color:var(--link);text-decoration:none;border-bottom:1px solid color-mix(in srgb,currentColor 72%,transparent)}
a:hover{border-bottom-color:var(--link)}
a:visited{color:var(--link)}

/* inline code */
:not(pre) > code{
  font-family:var(--font-code);font-size:.85em;
  background:var(--ic-bg);color:var(--ic-text);
  border:1px solid var(--ic-bd);padding:1px 6px;border-radius:var(--radius-sm);
  overflow-wrap:anywhere; /* not break-word: only `anywhere` lowers min-content, so long identifiers stop widening grid/flex ancestors at phone width */
}

/* ============================================================
   code block — dark in both modes (see Chroma CSS for tokens)
   ============================================================ */
.code-tab{font-family:var(--font-code);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin:0 0 8px}
pre,.highlight pre,.chroma{
  font-family:var(--font-code);
  background:var(--code-bg);color:var(--code-text);
  border:1px solid var(--code-bd);border-radius:var(--radius);
  padding:16px 18px 22px;font-size:var(--fs-code);line-height:var(--lh-code);
  overflow-x:auto;tab-size:4;
  color-scheme:dark; /* the well is dark in every theme, so is its scrollbar */
}
pre code{background:none;border:none;padding:0;color:inherit;font-size:inherit}
/* Evidence gets more vertical air than the paragraph rhythm (34px vs
   --sp-4's 22px — deliberately off the 4px scale, per the DS readme) —
   scoped to a direct child of .prose, which is where .Content actually
   renders, so a code block inside a card isn't double-margined. Bare
   fences (no language tag) get no .highlight wrapper from Hugo, so they
   need the same margin repeated, or they read as tighter to the
   surrounding text than a labeled block right above or below them.
   Targets BOTH .prose > pre (the no-JS/pre-hydration shape, where <pre>
   is still the direct .prose child) and .prose > .code-well (the
   with-JS shape below, where code-copy.js has moved <pre> one level
   deeper and .code-well has taken its place as the direct child) —
   whichever one is actually the direct child at render time is the one
   that needs the margin; the other selector is simply inert. `.layout`
   on both bare-fence branches isn't decorative: see the specificity
   note below. */
.prose > .highlight{margin:34px 0}
.layout .prose > pre,
.layout .prose > .code-well{margin:34px 0}
/* code-copy.js wraps every fenced block's <pre> — labeled or bare — in a
   <div class="code-well">, which is what actually bleeds 19px past its
   own 720px-capped ancestor on each side (width = measure + 38px,
   centred via a negative-ish margin-inline), not <pre> itself. <pre>
   just fills that wrapper at 100% and keeps its own overflow-x:auto for
   scrolling.

   That split matters: an earlier version bled and scrolled the SAME
   element, so the copy button (anchored to it for correct edge
   alignment) scrolled away with the code on any line wider than the
   well — unreachable on exactly the lines worth copying — and, with no
   reserved gutter, sat on top of line 1. .code-well doesn't scroll, so
   the button anchored to it (position:relative here) stays put and
   reachable regardless of horizontal scroll position; padding-top:44px
   on <pre> below reserves the room instead of overlapping.

   max-width:none defeats the generic `.layout .prose > *{max-width:720px}`
   rule, which would otherwise re-clamp the bare-fence branch (.code-well
   sits as a direct .prose child there) back down and silently cancel the
   bleed — confirmed happening in an earlier version, where a bare fence
   measured margin-left:0 and maxWidth:720px against a labeled fence's
   correct -19px/none. The `.layout` prefix on that branch is what wins
   the specificity fight (0,2,1 vs the generic rule's 0,2,0); without it
   max-width:none is present in the rule but loses the cascade anyway.

   Scoped narrower than langswitch on purpose: its Chroma output lives
   several DOM levels inside .langswitch .ls-panels, in its own nested
   context with its own chrome, so this measure-relative bleed formula
   doesn't apply there — langswitch already scrolls on its own <pre>
   correctly via the generic rule above; only tab-size/margin needed to
   be shared, not this width math.

   .layout .prose > pre is also included here: it's the bare-fence
   shape for the brief window before code-copy.js runs and wraps <pre>
   in .code-well (see that script's own comment — without it the code
   "renders fine... just without the measure-flush bleed"). Without
   this line a bare fence flashes at the generic 720px cap and then
   snaps 19px wider the instant JS wraps it; matching the width here up
   front removes that reflow instead of just narrowing its window. */
.prose > .highlight .code-well,
.layout .prose > .code-well,
.layout .prose > pre{
  width:calc(min(100%,720px) + 38px);max-width:none;
  margin-inline:calc(max(0px,(100% - 720px)/2) - 19px) auto;
  position:relative;
}
.prose > .highlight .code-well > pre,
.layout .prose > .code-well > pre{
  width:100%;margin-inline:0;
  border-left:none;border-right:none;
  padding:44px 18px 22px; /* top clears the button; bottom clears the
    horizontal scrollbar, which paints inside the padding box */
}
.prose > .highlight .code-well > pre > code,
.layout .prose > .code-well > pre > code{
  display:block;padding:0;overflow:visible;
}
.code-copy-btn{
  position:absolute;top:10px;right:12px;z-index:1;
  font-family:var(--font-code);font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--code-muted);background:var(--code-bg);border:1px solid var(--code-bd);
  padding:5px 11px;cursor:pointer;border-radius:0;
  transition:color .15s,border-color .12s,background .12s;
}
.code-copy-btn:hover{color:var(--cyan);border-color:var(--cyan)}
.code-copy-btn:active{transform:translateY(1px)}
.code-copy-btn:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}
/* Touch: grow the hit area past 44px with an invisible ::after, not the button.
   A 44px button would overlap the first code line (the well's top padding is
   44px and the button sits at top:10px). */
@media (pointer:coarse){.code-copy-btn::after{content:"";position:absolute;inset:-12px -4px}}

/* ============================================================
   callouts  —  {{< callout info >}} … {{< /callout >}}
   ============================================================ */
.scry-callout{border-radius:var(--radius);padding:14px 17px;font-size:15px;line-height:1.6;color:var(--text)}
.scry-callout > :first-child{margin-top:0}
.scry-callout > :last-child{margin-bottom:0}
.scry-callout__label{font-family:var(--font-code);font-size:11px;letter-spacing:.1em;font-weight:600;display:flex;align-items:center;gap:8px;margin-bottom:6px}
.scry-callout.info{background:var(--info-bg);border-left:3px solid var(--info-bd)}
.scry-callout.info  .scry-callout__label{color:var(--info-lb)}
.scry-callout.warn{background:var(--warn-bg);border-left:3px solid var(--warn-bd)}
.scry-callout.warn  .scry-callout__label{color:var(--warn-lb)}
.scry-callout.danger{background:var(--dgr-bg);border-left:3px solid var(--dgr-bd)}
.scry-callout.danger .scry-callout__label{color:var(--dgr-lb)}

/* ============================================================
   figures — obs-* shortcodes + mermaid
   ============================================================ */
.obs-fig{
  background:var(--fig-bg);
  border:1px solid var(--frame);border-radius:var(--radius);padding:22px 20px 8px;
}
/* A figure's evidence panel: the one frame an obs-* figure gets. Shortcodes
   that bring their own panel add .obs-panel to it (previously each inlined
   this same rule), and the outer .obs-fig box then drops its frame, so the
   caption sits under the panel the way a diagram's sits under .diag-card.
   Without :has() support the figure just keeps its old double frame. */
.obs-panel{background:var(--fig-bg);border:1px solid var(--frame);border-top:3px solid var(--mc-ink);padding:1.4rem 1.4rem 1.2rem;font-family:var(--font-code)}
.obs-fig:has(> .obs-panel){background:none;border:0;border-radius:0;padding:0}
.obs-fig svg{display:block;width:100%;height:auto}
.obs-fig .node{fill:var(--node-fill);stroke:var(--node-stroke)}
.obs-fig .nlab{font-family:var(--font-code);font-size:12px;fill:var(--nlab)}
.obs-fig .edge{stroke:var(--edge);stroke-width:1.5}
.obs-fig .arr{fill:var(--edge)}
figcaption,.obs-cap{font-family:var(--font-code);font-size:12px;line-height:1.45;color:var(--muted);margin:6px 2px 0}

/* mermaid: pre-rendered static SVG, themed via CSS vars; container just frames it */
.mermaid{
  background:var(--fig-bg);
  border:1px solid var(--frame);border-radius:var(--radius);padding:18px;
}

/* ============================================================
   tables
   ============================================================ */
table{width:100%;border-collapse:collapse;font-size:14px}
/* Evidence (code, figures, tables) gets more vertical air than the paragraph
   rhythm (34px vs --sp-4's 22px — deliberately off the 4px scale, per the
   DS readme) — a table header band or code well's own fill reads as flush
   against the line above it at plain paragraph spacing. Scoped to direct
   children of .prose (where .Content actually renders) so a table already
   nested inside a figure card (e.g. obs-comparison-table.html's
   .scry-table) isn't double-margined. */
.prose > .table-scroll{margin:34px 0}
/* Markdown tables arrive wrapped by render-table.html. A table wider than the
   720px measure (or the phone viewport) scrolls inside this box instead of
   widening the page (GUIDELINES #2). tabindex=0 on the wrapper makes it
   keyboard-scrollable; the global :focus-visible ring covers it. */
.table-scroll{overflow-x:auto}
thead th{text-align:left;font-family:var(--font-code);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);font-weight:500;padding:9px 13px;background:var(--th);border-bottom:1px solid var(--tborder)}
tbody td{padding:10px 13px;color:var(--text);border-bottom:1px solid var(--tborder);line-height:1.5;vertical-align:top}

/* ============================================================
   table of contents (partial: toc.html)
   ============================================================ */
.toc{position:sticky;top:88px;align-self:start}
.toc-label{font-family:var(--font-code);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);margin-bottom:14px}
/* Reset Hugo's #TableOfContents list — no bullets, no default indent;
   the left rail on each link carries the structure. */
.toc nav,.toc ul{margin:0;padding:0;list-style:none}
.toc li{margin:0}
.toc a{display:block;text-transform:lowercase;font-family:var(--font-code);font-size:12.5px;line-height:1.45;color:var(--muted);text-decoration:none;border-bottom:none;padding:6px 0 6px 13px;border-left:2px solid var(--border)}
/* nested h3 entries: indent the TEXT, not the rail — every link keeps the same
   left edge so the rail is one clean continuous line (matches the reference). */
.toc ul ul a{padding-left:26px}
.toc a:hover{color:var(--text)}
.toc a.active{color:var(--link);border-left-color:var(--link)}
/* The fold (toc.html) is a <details>; on the sidebar it is always open and
   its summary is just the label, so strip the marker and the toggle. */
.toc-fold > summary{list-style:none;display:block;pointer-events:none}
.toc-fold > summary::-webkit-details-marker{display:none}
.toc-count{letter-spacing:.08em}

/* ============================================================
   chrome — eyebrow, pixel wordmark, mode toggle
   ============================================================ */
.scry-wordmark{font-size:13px;line-height:1;color:var(--heading)}
/* header lockup: the wordmark is set to the lens's 16px so mark and name read
   as one unit, a clear step above the 12px nav (Doto's open dots read lighter
   than solid caps, so at 13px the brand looked smaller than "ARTICLES").
   The extra right margin separates the brand from the nav it introduces. */
.site-top .scry-wordmark{display:inline-flex;align-items:center;font-size:16px;margin-right:var(--sp-3)}
.scry-mark{display:block;flex-shrink:0}
/* space after the mark only — a flex gap would also split the scry|ops spans */
.site-top .scry-wordmark .scry-mark,.foot-logo .scry-mark{margin-right:var(--sp-2)}
.scry-mark__ops{fill:var(--green)}
.scry-wordmark__scry{font-family:var(--font-pixel-scry);font-weight:var(--fw-pixel-scry)}
.scry-wordmark__ops{font-family:var(--font-pixel-ops);font-weight:var(--fw-pixel-ops);color:var(--green)}

/* skip link — off-screen until keyboard focus */
.skip-link{position:absolute;left:-9999px;top:0}
.skip-link:focus{left:8px;top:8px;z-index:10;background:var(--surface);color:var(--link);font-family:var(--font-code);font-size:13px;padding:8px 12px;border:1px solid var(--border);border-radius:var(--radius-sm)}
/* Visible keyboard focus for every interactive element (WCAG 2.4.7). --link is
   high-contrast in all three themes (cyan/teal/amber); offset lifts it off the control. */
:focus-visible{outline:2px solid var(--link);outline-offset:2px}

/* ============================================================
   buttons
   ============================================================ */
.scry-btn{font-family:var(--font-code);font-size:13px;line-height:1;display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:8px;padding:9px 16px;min-height:40px;cursor:pointer;border:1px solid var(--border);background:transparent;color:var(--text);text-decoration:none;transition:border-color .12s,background .12s,color .12s;white-space:nowrap}
.scry-btn:hover{border-color:var(--cyan);color:var(--heading)}
.scry-btn:active{transform:translateY(1px)}
.scry-btn:focus-visible{outline:2px solid var(--link);outline-offset:2px}
.scry-btn[disabled],.scry-btn[aria-disabled="true"]{opacity:.45;cursor:not-allowed;pointer-events:none}
.scry-btn.sm{font-size:12px;padding:7px 12px;min-height:34px}
.scry-btn.primary{background:var(--green);color:var(--on-accent);border-color:var(--green);font-weight:500}
.scry-btn.primary:hover{background:var(--cyan);border-color:var(--cyan);color:var(--on-accent)}
.scry-btn.ghost{border-color:transparent;color:var(--link);text-decoration:underline;text-underline-offset:3px}
.scry-btn.ghost:hover{color:var(--cyan);background:transparent}

/* ============================================================
   layout: guide template (toc rail + article)
   ============================================================ */
.layout{max-width:820px;margin:0 auto;display:grid;grid-template-columns:minmax(0,1fr);gap:54px;padding:50px 28px 130px}
/* Sidebar layout is CENTRED on the viewport, not left-anchored. A prior
   version gave the TOC its own fixed 200px track and put the only
   flexible track on the right (`200px minmax(0,758px) minmax(0,1fr)`) —
   that pushes the whole grid right by the TOC's width, since surplus
   width only ever collects in the trailing track: measured 59px of
   leftward prose offset from true centre at 1440px, growing with
   viewport width since the flexible track absorbs 100% of the surplus
   on one side only. Centring a fixed-width measure needs flexible
   tracks on BOTH sides, so the TOC now lives inside the left 1fr gutter
   (`justify-self:end`, `width:200px`) instead of owning a dedicated
   track — the gutter still holds it flush to the measure's left edge,
   it just no longer forces the grid off-centre to do it. The TOC
   partial still renders before <article> in single.html (both for this
   grid placement and, at the stacked breakpoint below, so DOM order
   matches reading order for assistive tech too).

   The article track is 758px, not 720: the article width is not a
   reading-width decision, it's the 720px measure PLUS whatever evidence
   is allowed to bleed past it. A code well is deliberately 758px
   (720 + 2x19 — see .code-well below), and with the track at exactly
   720 there was no slack for that bleed, so every code block rendered
   19px outside its own article on each side — confirmed by measuring
   .article at 282-1002 (720px) against a code well at 263-1021 (758px)
   at 1400px viewport. Widening the track to match doesn't change the
   measure itself: `.layout .prose > *{max-width:720px}` still caps
   prose content at 720 regardless of how wide .article's own box is. */
.layout.has-toc{max-width:none;grid-template-columns:minmax(0,1fr) minmax(0,758px) minmax(0,1fr)}
.layout.has-toc > .toc{grid-column:1;grid-row:1;justify-self:end;width:200px;max-width:100%}
.layout > .article{min-width:0}   /* min-width:0 lets the grid item shrink below wide content (tables/diagrams) so their own overflow-x containers scroll instead of the page. On every .layout, not just .has-toc: TOC-less pages overflowed at phone width without it */
.layout.has-toc > .article{grid-column:2;grid-row:1}
/* Reading column: prose holds the 720px measure (centered). Evidence
   (code, figures, tables) shares that same measure and aligns flush with
   the prose left/right edges, rather than breaking out wider (GUIDELINES
   #2 — "one measure for prose and evidence alike"). Anything still too
   wide for 720px (a dense comparison table, a wide diagram) scrolls
   inside its own overflow-x:auto container instead of forcing the column
   wider. */
.layout .article{margin:0;padding:0;max-width:none}
.layout .article > .single-header,
.layout .article > .article-end,
.layout .article > .article-sub,
.layout .prose > *{max-width:720px;margin-inline:auto}
.site-top{position:sticky;top:0;z-index:5;display:flex;align-items:center;gap:14px;padding:16px 28px;background:var(--bg);border-bottom:1px solid var(--border)}
.site-top .spacer{flex:1}
/* tagline beside the wordmark: only where the bar has room (was an unexplained "obs") */
.site-tagline{font-family:var(--font-code);font-size:11px;color:var(--muted);white-space:nowrap}
@media (max-width:1199px){.site-tagline{display:none}}

/* Below 1084 a sidebar can only exist by stealing from the measure. The
   threshold counts BOTH gaps and BOTH paddings for the 3-track grid above:
     28 + 200 + 54 + 720 + 54 + 28 = 1084
   (a naive count using one gap/padding pair, e.g. ~990, understates it and
   leaves a band where the measure silently shrinks below 720 while the
   sidebar still renders — verified against the scryops-design kit, whose
   own history includes finding 1030 insufficient for the same reason.) */
@media (max-width:1083px){
  .layout,.layout.has-toc{max-width:800px;grid-template-columns:minmax(0,1fr);gap:30px;padding:34px 22px 90px}
  /* grid-row set explicitly, not auto: auto lets DOM order win, and if
     the TOC partial is ever moved back after </article> in single.html
     it would drop the TOC to the page bottom instead of stacking it
     above the article as intended. justify-self reset to start: the
     desktop rule now reads end (to hug the centred measure), which
     this breakpoint must not inherit, or the stacked TOC shifts right
     instead of keeping its original left alignment. */
  .layout.has-toc > .toc{grid-column:1;grid-row:1;justify-self:start}
  .layout.has-toc > .article{grid-column:1;grid-row:2}
  .toc{position:static}
  /* Stacked: the TOC folds to one 44px row so the title leads the page.
     stretch (not start) so the row spans the column; still left-aligned. */
  .layout.has-toc > .toc{justify-self:stretch;width:auto}
  .toc-fold{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}
  .toc-fold > summary{pointer-events:auto;cursor:pointer;display:flex;align-items:center;justify-content:space-between;min-height:44px;box-sizing:border-box;margin:0;padding:12px 14px;color:var(--text)}
  .toc-fold > summary::after{content:"▾";color:var(--muted);letter-spacing:0}
  .toc-fold[open] > summary::after{content:"▴"}
  .toc-fold > summary:focus-visible{outline:2px solid var(--link);outline-offset:2px}
  .toc-fold > nav{padding:0 14px 12px}
  .title{font-size:28px}
}

/* ============================================================
   diagram components  —  obs-* HTML/SVG shortcodes
   Ported from the legacy static/css/style.css (which the
   Telemetry chrome no longer loads) and rewired onto the tokens
   above, so every figure flips dark/light. Palette mapping from
   the old system: amber/brand → --green, klein-blue → --cyan,
   success → --green, danger family → --danger, Commit Mono →
   --font-code, Press Start 2P → --font-pixel. No hard-coded hex
   below this line — components read tokens only.
   ============================================================ */

/* shared figure wrapper + flat diagram card (neutral frame, no chromatic accent) */
figure.diagram,figure.knt-fig,.obs-fig{margin:34px 0}
figure.diagram figcaption,figure.knt-fig figcaption{font-family:var(--font-code);font-size:12px;line-height:1.45;color:var(--muted);margin-top:10px;padding-left:2px}
.diag-card{background:var(--surface);border:1px solid var(--frame);border-radius:var(--radius);padding:20px 20px 16px;overflow-x:auto}
/* Scroll cue for diagrams wider than the card (the legibility floor below
   makes that common): an ink shade on whichever edge hides content. CSS only —
   the `local` covers scroll with the content and mask the `scroll`-attached
   shades once an edge is reached, so a diagram that fits shows nothing.
   --heading ink lightens the edge on dark and darkens it on light. */
.diag-card{
  --diag-shade:color-mix(in srgb,var(--heading) 22%,transparent);
  background:
    linear-gradient(to right,var(--surface) 40%,transparent) left / 48px 100% no-repeat local,
    linear-gradient(to left,var(--surface) 40%,transparent) right / 48px 100% no-repeat local,
    linear-gradient(to right,var(--diag-shade),transparent) left / 16px 100% no-repeat scroll,
    linear-gradient(to left,var(--diag-shade),transparent) right / 16px 100% no-repeat scroll,
    var(--surface);
}
/* Hand-built figures carry no inline max-width, so they take the 636px floor.
   Mermaid emits its own inline max-width pinning the SVG to its natural size;
   min-width previously beat that in the cascade and upscaled it (e.g. 277px
   -> 636px, a 2.3x stretch), rendering mermaid's 14px text far larger than
   body copy. Scoping this out lets mermaid render at scale 1.0. */
.diag-card > svg:not([style*="max-width"]){min-width:636px}
/* Mermaid has no fixed floor, so wide diagrams shrank to fit the card (8.4px
   labels at phone width and in the 678px desktop card). The shortcode sets
   --diag-min = viewBox width x 12/14, which holds Mermaid's 14px labels at the
   12px figure floor; wider diagrams pan in the card like the obs-* figures.
   Applies at every width: the desktop card fits viewBoxes up to ~791 units. */
.diag-card > svg[style*="max-width"]{min-width:var(--diag-min,0)}

/* divider between stacked comparison flows (obs-reactive-loop) */
.flow-divider{height:1px;background:var(--border);margin:19px 0}

/* comparison / two-version tables (obs-comparison-table, obs-monitoring-shifts) */
.scry-table{width:100%;border-collapse:collapse;background:var(--surface);border:1px solid var(--border);font-size:14px}
.scry-table th{background:var(--th);border:1px solid var(--tborder);padding:9px 14px;font-family:var(--font-code);font-size:12px;text-align:left;letter-spacing:.04em;color:var(--text)}
.scry-table th:first-child{color:var(--text);letter-spacing:.1em;text-transform:uppercase}
.scry-table th.v1{color:var(--text);border-bottom:2px solid var(--muted)}
.scry-table th.v2{color:var(--cyan);border-bottom:2px solid var(--cyan)}
.scry-table td{border:1px solid var(--tborder);padding:8px 14px;font-size:13px;line-height:1.5;vertical-align:middle;color:var(--text)}
.scry-table td:first-child,.scry-table tbody th[scope="row"],.scry-table tbody th.rowhead{font-family:var(--font-code);color:var(--text);font-weight:400;font-size:12px;text-transform:uppercase;letter-spacing:.08em;white-space:nowrap;background:var(--th);text-align:left;padding:8px 14px;line-height:1.5;vertical-align:middle}
.scry-table td.v1{color:var(--text)}
.scry-table td.v2{color:var(--cyan)}
.scry-table-wrap{overflow-x:auto}

/* insight callout (insight shortcode) */
.scry-insight{display:flex;gap:14px;align-items:flex-start;background:var(--surface);border:1px solid var(--frame);border-radius:var(--radius);padding:14px 18px;margin:24px 0}
.scry-insight.lightbulb{border-left:3px solid var(--green)}
.scry-insight.bookmark{border-left:3px solid var(--cyan)}
.scry-insight__icon{flex-shrink:0;margin-top:3px;width:16px;height:16px}
.scry-insight.lightbulb .scry-insight__icon{color:var(--green)}
.scry-insight.bookmark .scry-insight__icon{color:var(--cyan)}
.scry-insight__body{flex:1;min-width:0;font-family:var(--font-read);font-size:14.5px;line-height:1.66;color:var(--text)}
.scry-insight__body strong{display:block;font-weight:700;margin-bottom:4px}
.scry-insight.lightbulb .scry-insight__body strong{color:var(--green)}
.scry-insight.bookmark .scry-insight__body strong{color:var(--cyan)}
.scry-insight__body p{margin:0;color:var(--text)}

/* ============================================================
   site chrome — nav menu, homepage, listings, cards, tags, Q&A,
   subscribe, footer. Ported from the retired static/css/style.css
   onto the tokens above (via the legacy aliases) so every page
   flips dark/light. Reading pages use .layout/.article (above);
   these styles cover everything else.
   ============================================================ */

/* page container for non-reading pages */
.wrap{max-width:1100px;margin:0 auto;padding:46px 28px 96px}

/* base lists inside reading/content flow (telemetry base omitted these) */
.article ul,.article ol,.prose ul,.prose ol{margin:14px 0 14px 1.4rem}
.article li,.prose li{margin:4px 0}

/* top-bar main menu (added to site-top.html) */
.top-nav{display:flex;gap:18px;flex-wrap:wrap;align-items:center}
.top-nav a{font-family:var(--font-code);font-size:12px;text-transform:uppercase;letter-spacing:.12em;color:var(--muted);padding:7px 0;display:inline-block}
.top-nav a:hover,.top-nav a[aria-current]{color:var(--cyan)}
/* header links are navigation, not prose: no hairline under the wordmark or
   each nav item (the global `a` rule draws one on every link). Only the
   current section is underlined — brightness alone is a colour-only cue
   (lost in pref-mono), so it gets a 2px inset rule that shifts nothing. */
.site-top .scry-wordmark,.top-nav a{border-bottom:none}
.top-nav a[aria-current]{box-shadow:inset 0 -2px 0 currentColor}

/* terminal identity strip (homepage hero line) */
.site-identity{display:flex;align-items:center;gap:.6rem;padding:.85rem 0;border-bottom:1px solid var(--border);font-size:.88rem;flex-wrap:wrap;row-gap:.25rem;font-family:var(--font-code)}
.prompt{color:var(--green);flex-shrink:0;user-select:none}
.cmd{color:var(--text)}
.si-sep{color:var(--green);user-select:none;flex-shrink:0}
.si-desc{color:var(--muted)}
.cursor{display:inline-block;width:9px;height:1em;background:var(--green);vertical-align:text-bottom;animation:scry-blink 1.1s step-end infinite;margin-left:2px}
@keyframes scry-blink{0%,100%{opacity:1}50%{opacity:0}}
@media (prefers-reduced-motion:reduce){.cursor{animation:none;opacity:1}}

/* two-column hero: featured + topics */
.hero-grid{display:grid;grid-template-columns:1fr 220px;gap:0 3.5rem;padding:2.8rem 0 3rem;border-bottom:1px solid var(--border);margin-bottom:3rem;align-items:start}
.featured-article{display:block;padding-right:2rem;border-right:1px solid var(--border);text-decoration:none}
.featured-article:hover .featured-title{color:var(--cyan)}
.featured-meta{display:flex;align-items:center;gap:.6rem;margin-bottom:1.1rem;flex-wrap:wrap}
.featured-date{font-family:var(--font-code);font-size:.78rem;color:var(--muted);margin-left:auto}
.featured-title{font-family:var(--font-display);font-size:1.6rem;font-weight:400;line-height:1.2;color:var(--heading);margin-bottom:1rem;letter-spacing:-.01em;text-transform:lowercase}
.featured-excerpt{font-family:var(--font-read);font-size:.92rem;line-height:1.8;color:var(--muted);margin-bottom:1.4rem}
.featured-cta{font-family:var(--font-code);font-size:.8rem;color:var(--green);text-transform:uppercase;letter-spacing:.1em}

.hero-topics{padding-top:0}
.topic-all{display:block;flex-basis:100%;margin-top:1rem;font-family:var(--font-code);font-size:.75rem;color:var(--muted);opacity:.7}
.topic-all:hover{color:var(--cyan);opacity:1}

/* section label + rule */
.section-head{display:flex;align-items:center;gap:1rem;margin-bottom:.5rem}
/* muted like the eyebrow: section labels are structure, not a CTA, so they
   stay off the green budget */
.section-label{font-family:var(--font-code);font-size:.6875rem;color:var(--muted);text-transform:uppercase;letter-spacing:.18em;white-space:nowrap}
.section-rule{flex:1;height:1px;background:var(--border)}

/* article feed (list/term) + cards (was .article — renamed .art-card to avoid
   colliding with the reading column .article) */
.feed{margin-bottom:4rem}
.scry-artcard{display:grid;grid-template-columns:2.8rem 1fr;gap:1rem;padding:1.6rem 0;border-bottom:1px solid var(--border);cursor:pointer;transition:background .12s;text-decoration:none}
.scry-artcard:first-child{border-top:1px solid var(--border)}
.scry-artcard:hover{background:color-mix(in srgb,var(--cyan) 4%,transparent)}
.scry-artcard__num{font-family:var(--font-pixel);font-size:.63rem;color:var(--muted);padding-top:.4rem;line-height:1.8}
.scry-artcard:hover .scry-artcard__num{color:var(--cyan)}
.scry-artcard__meta{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin-bottom:.45rem}
.scry-artcard__title{font-family:var(--font-display);font-size:1.15rem;font-weight:400;line-height:1.35;margin-bottom:.4rem;color:var(--heading);text-transform:lowercase}
.scry-artcard:hover .scry-artcard__title{color:var(--cyan)}
.scry-artcard__excerpt{font-family:var(--font-read);font-size:.95rem;color:var(--text);line-height:1.7;margin-top:.5rem}

/* ── M3 section listing ──────────────────────────────────────────────── */

/* page header */
.lhead{display:flex;align-items:flex-end;gap:16px;padding:40px 0 22px;border-bottom:1px solid var(--border);margin-bottom:26px}
.lh-l{flex:1}
.scry-eyebrow{font-family:var(--font-code);font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);margin-bottom:11px}
.scry-eyebrow.brand{color:var(--green)}
.lh-title{font-family:var(--font-display);font-weight:400;font-size:32px;line-height:1.1;color:var(--heading);text-transform:lowercase;margin:0}
.lh-sub{font-family:var(--font-read);font-size:15px;line-height:1.6;color:var(--text);max-width:60ch;margin:14px 0 0}
.cnt{font-family:var(--font-code);font-size:11.5px;color:var(--muted);border:1px solid var(--border);border-radius:999px;padding:5px 12px;white-space:nowrap;flex-shrink:0}

@media(max-width:840px){
  .lh-title{font-size:27px}
  .lhead{flex-wrap:wrap;align-items:flex-start}
}

/* ── M3 section listing ──────────────────────────────────────────────── */

/* content-type tags (palette constrained to tokens: identifiers=cyan,
   how-to=green, Q&A=neutral) */
.scry-tag{font-family:var(--font-code);font-size:.6875rem;text-transform:uppercase;letter-spacing:.09em;color:var(--cyan);border:1px solid var(--cyan);padding:.08rem .4rem;background:color-mix(in srgb,var(--cyan) 8%,transparent);border-radius:3px;display:inline-block;line-height:1.5;white-space:nowrap}
.scry-tag.guide,.scry-tag.trends,.scry-tag.article{color:var(--cyan);border-color:var(--cyan)}
.scry-tag.howto{color:var(--green);border-color:var(--green);background:color-mix(in srgb,var(--green) 8%,transparent)}
.scry-tag.qa{color:var(--muted);border-color:var(--border);background:var(--surface)}
.read-time{font-family:var(--font-code);font-size:.75rem;color:var(--muted)}

/* Q&A list */
.qa-section{margin-bottom:4rem;background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--cyan);padding:1.6rem 2rem 1.8rem}
.qa-item{padding:1.4rem 0;border-bottom:1px solid var(--border)}
.qa-item:first-child{border-top:1px solid var(--border)}
.qa-q{font-family:var(--font-display);font-size:.95rem;margin-bottom:.4rem;display:flex;gap:.6rem;color:var(--heading)}
.qa-q::before{content:'Q:';color:var(--green);font-family:var(--font-code);font-size:.6875rem;padding-top:.35rem;flex-shrink:0}
.qa-a{font-family:var(--font-read);font-size:.85rem;color:var(--muted);font-style:italic;padding-left:2.1rem;line-height:1.55}
.qa-a::before{content:'A → ';color:var(--muted);font-style:normal}
.ask-cta{margin-top:1.5rem;display:inline-block;font-family:var(--font-code);font-size:.8rem;text-transform:uppercase;letter-spacing:.1em;color:var(--green);border:1px solid var(--green);padding:.5rem 1.1rem}
.ask-cta:hover{background:var(--green);color:var(--bg)}

/* tag cloud (taxonomy) */
.tags-section{margin-bottom:4rem}
.tag-cloud{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:1rem}
.scry-tagpill{font-family:var(--font-code);font-size:.75rem;color:var(--muted);border:1px solid var(--border);border-radius:var(--radius-pill);padding:0 .9rem;letter-spacing:.02em;display:inline-flex;align-items:center;min-height:44px}
/* Tag pills read in the ink ramp, not green: a page of tags was ~19 green
   marks against a budget of ~3. The # matches the home hero chips (--cyan is
   bright ink); hover brightens like a link instead of shifting hue. */
.scry-tagpill::before{content:"#";color:var(--cyan);font-weight:700;margin-right:.15rem}
.scry-tagpill:hover{color:var(--link);border-color:var(--link);background:color-mix(in srgb,var(--link) 6%,transparent)}
/* Topics page (taxonomy.html): one-page tags sit in a quieter row. The cue is
   the dashed border, not hue, so it survives pref-mono; pills keep 44px. */
.tag-also{margin-top:2.2rem}
.tag-cloud--quiet{margin-top:.4rem}
.tag-cloud--quiet .scry-tagpill{border-style:dashed}
.tag-cloud--quiet .scry-tagpill::before{color:var(--muted)}

/* single-header (qa/single) + about page */
.single-header{margin-bottom:2.5rem;padding-bottom:1.5rem;border-bottom:1px solid var(--border)}
/* series strip (partial: series-strip.html) — the TOC's rail language:
   lowercase mono, a 2px rail, the current part bright with a bright rail */
.series{margin-top:1.1rem;padding:12px 14px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}
.series-label{font-family:var(--font-code);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:8px}
.series ol{list-style:none;margin:0;padding:0}
.series li{margin:0}
.series a{display:flex;gap:10px;align-items:baseline;font-family:var(--font-code);font-size:12.5px;line-height:1.45;text-transform:lowercase;color:var(--muted);border-bottom:none;padding:6px 0 6px 12px;border-left:2px solid var(--border)}
.series a:hover{color:var(--text)}
.series a[aria-current]{color:var(--link);border-left-color:var(--link)}
.series-n{flex:none;min-width:1.5ch;color:inherit}
@media (pointer:coarse){.series a{min-height:44px;align-items:center}}
.single-header h1{font-family:var(--font-display);font-size:1.9rem;font-weight:400;line-height:1.25;margin-bottom:.8rem;color:var(--heading);text-transform:lowercase}
.about-content{font-family:var(--font-read);font-size:.95rem;line-height:1.75;color:var(--text)}
.about-content p{margin-bottom:.8rem}
.about-content strong{color:var(--heading)}
.about-content a{color:var(--link);text-decoration:underline;text-underline-offset:2px}

/* subscribe */
/* Green budget (DS: ~3 marks per view): the home first screen already spends
   green on the wordmark, the headline word and "Start reading", so the
   subscribe block uses the ink ramp: rule in --cyan, an outlined button.
   The button is a plain link to Substack (no form, no script), so it needs
   inline-flex for its padding and 44px touch height to apply. */
.scry-sub{margin-bottom:4rem;border:1px solid var(--border);border-left:3px solid var(--cyan);padding:1.6rem 2rem;background:var(--surface);border-radius:var(--radius)}
.scry-sub__label{font-family:var(--font-display);font-size:1.05rem;color:var(--heading);margin-bottom:.4rem;text-transform:lowercase}
.scry-sub__desc{font-family:var(--font-read);font-size:.9rem;color:var(--muted);margin-bottom:1rem;line-height:1.6}
.scry-sub__btn{display:inline-flex;align-items:center;font-family:var(--font-code);font-size:.82rem;text-transform:uppercase;letter-spacing:.1em;color:var(--text);background:transparent;border:1px solid var(--muted);padding:.55rem 1.2rem;cursor:pointer;white-space:nowrap;border-radius:var(--radius-sm)}
.scry-sub__btn:hover{border-color:var(--cyan);color:var(--cyan)}
/* end of an article/guide/how-to: after the body, before resources + related */
.article-sub{margin-top:3rem}

/* footer */
.site-footer{border-top:1px solid var(--border);padding:2.5rem 2rem;text-align:center}
.foot-logo{font-size:.65rem;color:var(--muted);margin-bottom:.8rem;display:flex;justify-content:center;align-items:center}
.foot-logo__scry{font-family:var(--font-pixel-scry);font-weight:var(--fw-pixel-scry)}
.foot-logo__ops{font-family:var(--font-pixel-ops);font-weight:var(--fw-pixel-ops);color:var(--green)}
.site-footer p{font-family:var(--font-code);font-size:.8rem;color:var(--muted)}
.site-footer a{color:var(--cyan)}
.site-footer a:hover{text-decoration:underline}

@media (max-width:720px){
  .hero-grid{grid-template-columns:1fr;gap:2rem 0;padding:1.8rem 0}
  .featured-article{padding-right:0;border-right:none;border-bottom:1px solid var(--border);padding-bottom:2rem}
  .featured-title{font-size:1.3rem}
  .wrap{padding:30px 20px 70px}
  .top-nav{gap:12px}
}

/* let the top bar wrap when the menu + meta + toggle don't fit one row */
.site-top{flex-wrap:wrap;row-gap:10px}

/* Nav fold (site-top.html): on wide screens the <details> is always open and
   its summary hidden, so the nav reads inline exactly as before. */
.top-menu > summary{display:none}
.top-menu__btn::-webkit-details-marker{display:none}

/* Phones: one row — wordmark · spacer · Reading · menu. The open menu drops
   as a full-width panel under the sticky bar (the bar is its containing
   block). "obs" and the type/readtime meta go: the article repeats the meta. */
@media (max-width:599px){
  .site-top{flex-wrap:nowrap;padding:6px 12px 6px 20px;gap:6px}
  .site-top .scry-wordmark{order:0}
  .site-top .spacer{order:1}
  .site-top .scry-prefs__btn{order:2}
  .top-menu{order:3}
  .top-menu > summary{display:flex;align-items:center;list-style:none;min-height:44px;min-width:44px;box-sizing:border-box;padding:0 12px;font-family:var(--font-code);font-size:12px;letter-spacing:.08em;color:var(--text);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer}
  .top-menu[open] > summary{border-color:var(--link)}
  .top-menu > summary:focus-visible{outline:2px solid var(--link);outline-offset:2px}
  .top-menu .top-nav{position:absolute;top:100%;left:0;right:0;flex-direction:column;align-items:stretch;gap:0;padding:4px 20px 12px;background:var(--bg);border-bottom:1px solid var(--border)}
  .top-menu .top-nav a{display:flex;align-items:center;min-height:44px;padding:0;border-bottom:1px solid var(--border)}
  .top-menu .top-nav a:last-child{border-bottom:none}
  /* in the phone menu, rows already carry separator rules: mark the current one with a left bar */
  .top-menu .top-nav a[aria-current]{border-bottom-color:var(--border);box-shadow:inset 2px 0 0 currentColor;padding-left:12px}
  .top-menu .top-nav a[aria-current]:last-child{border-bottom:none}
}

/* Touch: 44px targets for the header controls and buttons on coarse
   pointers (desktop mouse layouts keep their tighter rhythm). */
@media (pointer:coarse){
  .site-top .scry-wordmark{display:inline-flex;align-items:center;min-height:44px;border-bottom:none}
  .scry-prefs__btn{min-height:44px}
  .top-nav a{display:inline-flex;align-items:center;min-height:44px}
  .scry-btn,.scry-sub__btn{min-height:44px}
  .scry-prefs__seg button{min-height:44px}
}

/* quote_with_author shortcode */
.scry-quote{margin:24px 0;border-left:3px solid var(--green);padding:4px 0 4px 1.2rem}
.scry-quote blockquote{border:none;padding:0;margin:0 0 .8rem;color:var(--text);font-style:italic;font-family:var(--font-read);font-size:.95rem;line-height:1.65}
.scry-quote blockquote p{margin:0}
.scry-quote blockquote p::before{content:'\201C';color:var(--green);font-style:normal}
.scry-quote blockquote p::after{content:'\201D';color:var(--green);font-style:normal}
.scry-quote__attr{display:flex;align-items:center;gap:.7rem}
.scry-quote__avatar{width:32px;height:32px;border-radius:50%;border:1px solid var(--border);object-fit:cover;flex-shrink:0}
.scry-quote__avatar.pixel{width:56px;height:56px;border-radius:7px;object-fit:contain;background:var(--surface);padding:1px;image-rendering:pixelated;image-rendering:crisp-edges}
.scry-quote__initials{width:32px;height:32px;border-radius:50%;border:1px solid var(--border);background:var(--surface);display:flex;align-items:center;justify-content:center;font-family:var(--font-pixel);font-weight:var(--fw-pixel-ops);font-size:.625rem;color:var(--green);flex-shrink:0}
.scry-quote__name{display:block;font-family:var(--font-code);font-size:.8rem;font-weight:700;color:var(--green)}
.scry-quote__role{display:block;font-family:var(--font-code);font-size:.75rem;color:var(--muted)}

/* ── Front page (layouts/index.html) — split hero + terminal feed ────────
   Identity statement + CTA left; the latest pieces as shell output in a
   terminal window on the always-dark code well, so the feed reads the same
   in dark / light / calm. Collapses to one column < 820px. */
.front{max-width:1080px;margin:0 auto;padding:0 28px 90px;display:grid;grid-template-columns:1fr 1fr;gap:44px;align-items:center;min-height:62vh}
.front-hero{display:flex;flex-direction:column;gap:18px;padding:48px 0}
.front-hero__eyebrow{display:flex;align-items:center;gap:var(--sp-2)}
.front-hero__eyebrow .scry-mark__ops{fill:var(--muted)}   /* ink-only: keeps the hero under the green budget */
/* Lens dots on a muted line take --text, not --muted: each dot covers ~half its
   cell, and at 2x DPR the dot centre lands on a pixel corner, so --muted dots
   blend to ~2.4:1 on light. --text keeps the dotted half legible as dots. */
.front-hero__eyebrow .scry-mark,.foot-logo .scry-mark{color:var(--text)}
.landing-statement{font-family:var(--font-display);font-weight:400;font-size:clamp(32px,5vw,46px);line-height:1.12;letter-spacing:-.02em;color:var(--heading);text-transform:lowercase;margin:0;max-width:18ch}
.landing-statement b{color:var(--green);font-weight:400}
.landing-lede{font-family:var(--font-read);font-size:var(--fs-lede);line-height:var(--lh-lede);letter-spacing:var(--read-track);color:var(--text);max-width:60ch;margin:0}
.topics{display:flex;gap:8px;flex-wrap:wrap;margin:0}
.topic-chip{font-family:var(--font-code);font-size:12px;color:var(--text);border:1px solid var(--border);border-radius:999px;padding:6px 13px}
.topic-chip b{color:var(--cyan);font-weight:400}
.landing-cta{display:flex;gap:12px;flex-wrap:wrap;align-items:center;margin:6px 0 0}
.landing-cta .scry-btn{text-decoration:none}

.front-feed{padding:24px 0}
.scry-feed{background:var(--code-bg);border:1px solid var(--code-bd);border-radius:var(--radius);overflow:hidden}
.scry-feed__hdr{font-family:var(--font-code);font-size:11.5px;color:var(--code-muted);padding:12px 15px;border-bottom:1px solid var(--code-bd)}
/* prompt + cursor in bright ink, not green: the terminal is identity, not a CTA */
.scry-feed__hdr .fp{color:var(--code-cyan)}
.scry-feed__hdr .fa{color:var(--code-cyan)}
.scry-feed__cursor{display:inline-block;width:7px;height:13px;background:var(--code-hi);vertical-align:-2px;margin-left:3px;animation:scry-blink 1.1s step-end infinite}
@media (prefers-reduced-motion:reduce){.scry-feed__cursor{animation:none}}
.scry-feed__row{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:11px;align-items:baseline;padding:10px 15px;border-bottom:1px solid var(--code-bd);text-decoration:none}
.scry-feed__row:last-child{border-bottom:none}
.scry-feed__row .scry-feed__glyph{font-family:var(--font-code);font-size:13px;width:15px;text-align:center;line-height:1}
.scry-feed__row .scry-feed__title{font-family:var(--font-code);font-size:13px;color:var(--code-text)}
.scry-feed__row:hover .scry-feed__title{color:var(--code-cyan)}
.scry-feed__row .scry-feed__meta{font-family:var(--font-code);font-size:10.5px;color:var(--code-muted);white-space:nowrap}
.scry-tp-trace{color:var(--cyan)}.scry-tp-metric{color:var(--green)}.scry-tp-prof{color:var(--warn)}.scry-tp-log{color:var(--muted)}.scry-tp-otel{color:var(--violet)}
/* The feed sits on the always-dark code well, so its glyphs take the fixed
   --code-* readouts; the theme accents above flip with light/calm and
   vanished on the well (light: guide glyph 1.19:1). */
.scry-feed .scry-tp-trace{color:var(--code-cyan)}.scry-feed .scry-tp-metric{color:var(--code-green)}.scry-feed .scry-tp-prof{color:var(--code-warn)}.scry-feed .scry-tp-log{color:var(--code-muted)}.scry-feed .scry-tp-otel{color:var(--code-violet)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

@media (max-width:820px){
  .front{grid-template-columns:1fr;gap:26px;min-height:0}
  .front-hero{padding:36px 0 8px}
  .front-feed{padding:0 0 8px}
}

/* ── Reading-preferences panel (chrome) ─────────────────────────────── */
.scry-prefs__btn{font-family:var(--font-code);font-size:12px;color:var(--text);background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:7px 13px;cursor:pointer;transition:border-color .15s}
.scry-prefs__btn:hover{border-color:var(--cyan)}
.scry-prefs__btn b{color:var(--cyan);font-weight:400}
.scry-prefs__panel{position:fixed;top:64px;right:24px;z-index:60;width:290px;background:var(--surface);border:1px solid var(--border);border-radius:13px;padding:18px;box-shadow:0 20px 50px -20px rgba(0,0,0,.7)}
.scry-prefs__panel[hidden]{display:none}
.scry-prefs__panel h2{font-family:var(--font-code);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);margin:0 0 13px}
.scry-prefs__group{margin-bottom:16px}
.scry-prefs__group > .scry-prefs__glabel{font-family:var(--font-code);font-size:11px;color:var(--heading);margin-bottom:9px}
.scry-prefs__seg{display:flex;border:1px solid var(--border);border-radius:8px;overflow:hidden}
.scry-prefs__seg button{flex:1;font-family:var(--font-code);font-size:11.5px;color:var(--muted);background:transparent;border:none;padding:8px 4px;cursor:pointer}
.scry-prefs__seg button[aria-pressed="true"]{background:var(--bg);color:var(--heading)}
/* the segment clips overflow (rounded ends), so draw the focus ring inside */
.scry-prefs__seg button:focus-visible{outline-offset:-2px}
.scry-prefs__row{display:flex;align-items:center;gap:11px;padding:9px 0;cursor:pointer;border:none;background:transparent;width:100%;text-align:left;border-bottom:1px solid var(--border)}
.scry-prefs__row:last-child{border-bottom:none}
.scry-prefs__row .scry-prefs__l{flex:1}
/* name over description: both were inline spans, so they ran together on one line */
.scry-prefs__row .scry-prefs__l .scry-prefs__name{display:block;font-family:var(--font-code);font-size:12px;color:var(--text)}
.scry-prefs__row .scry-prefs__l .scry-prefs__desc{display:block;font-family:var(--font-code);font-size:11px;line-height:1.45;color:var(--muted);margin-top:2px}
.scry-prefs__sw{width:38px;height:21px;border-radius:999px;background:var(--border);border:1px solid var(--muted);flex:none;position:relative;transition:background .15s}
.scry-prefs__sw::after{content:"";position:absolute;top:1px;left:1px;width:17px;height:17px;border-radius:50%;background:var(--muted);transition:transform .15s,background .15s}
.scry-prefs__row[aria-checked="true"] .scry-prefs__sw{background:var(--green)}
.scry-prefs__row[aria-checked="true"] .scry-prefs__sw::after{transform:translateX(17px);background:var(--on-accent)}

/* ── Permacomputing · Phase 2 — footprint badge + colophon ──────────────
   Terminal readouts that live on the always-dark code well, so the
   living-green eco signal reads identically in warm-paper AND soft-dark.
   footprint.js fills the real numbers via [data-footprint] + [data-fp-*]. */
.scry-fp{font-family:var(--font-code);font-size:12px;line-height:1.85;background:var(--code-bg);border:1px solid var(--code-bd);border-radius:var(--radius);padding:12px 14px;color:var(--code-text)}
.scry-fp .scry-fp__pr{color:var(--code-eco)}
.scry-fp .scry-fp__ar{color:var(--code-cyan)}
.scry-fp .scry-fp__hi{color:var(--code-hi)}
.scry-fp .scry-fp__eco{color:var(--code-eco)}
.scry-fp .scry-fp__status{color:var(--code-eco)}
.scry-fp.over .scry-fp__status{color:var(--code-danger)}
.scry-fp__bar{height:7px;border-radius:4px;background:rgba(127,176,105,.14);overflow:hidden;margin:8px 0 4px}
.scry-fp__fill{height:100%;width:0;background:linear-gradient(90deg,var(--code-eco),var(--code-eco));transition:width .4s ease}
.scry-fp.over .scry-fp__fill{background:linear-gradient(90deg,var(--code-danger),var(--code-warn))}
.scry-fp__note{font-family:var(--font-read);font-size:10.5px;color:var(--code-muted)}
/* Scoped under .scry-fp (0,2,0) so it out-ranks .site-footer a (0,1,1): the
   badge sits on the always-dark well, and the footer's theme --cyan went to
   dark ink there in light theme (1.1:1). Keeps the base a{} hairline. */
.scry-fp .scry-fp__method{color:var(--code-cyan);text-decoration:none;border-bottom:1px solid color-mix(in srgb,currentColor 72%,transparent)}
.scry-fp .scry-fp__method:hover{border-bottom-color:currentColor}
@media (prefers-reduced-motion:reduce){.scry-fp__fill{transition:none}}

/* In the site footer the badge stays compact and centered. */
footer .scry-fp{max-width:440px;margin:0 auto;text-align:left}
.scry-fp__noscript{font-family:var(--font-code);font-size:11.5px;line-height:1.6;color:var(--muted);max-width:440px;margin:10px auto 0;text-align:left}
.scry-fp__noscript a{color:var(--cyan)}

.scry-colophon{font-family:var(--font-code);font-size:12.5px;line-height:1.7;background:var(--code-bg);border:1px solid var(--code-bd);border-radius:var(--radius);padding:16px 18px;color:var(--code-text);max-width:var(--measure)}
.scry-colophon .scry-colophon__cmd{color:var(--code-muted);margin-bottom:8px}
.scry-colophon .scry-colophon__cmd b{color:var(--code-eco);font-weight:400}
.scry-colophon .scry-colophon__row{display:grid;grid-template-columns:130px 1fr;gap:14px;padding:7px 0;border-bottom:1px solid var(--code-rule)}
.scry-colophon .scry-colophon__row:last-child{border-bottom:none}
.scry-colophon .scry-colophon__k{color:var(--code-muted)}
.scry-colophon .scry-colophon__v{color:var(--code-hi)}
.scry-colophon .scry-colophon__v .eco{color:var(--code-eco)}
.scry-colophon .scry-colophon__v a{color:var(--code-cyan);text-decoration:none}
.scry-colophon .scry-colophon__v a:hover{text-decoration:underline}

/* ── Mascot ("the Cucco") — pixel-art RPG chicken party, Phase 3c ────────
   Structural chrome reads Telemetry tokens so it recolors across
   dark/light/calm. --ac (set once per rendered instance) carries the
   fixed per-class identity accent through every variant — same pattern
   .party-card already uses. HP hearts read --danger, not the accent. */
.scry-cucco{margin:2.5rem 0 2rem;font-family:var(--font-code)}
.scry-cucco__wrap{display:flex;gap:16px;align-items:stretch;background:var(--surface);border:1px solid var(--border);border-top:2px solid var(--ac);border-radius:var(--radius-retro);padding:16px;overflow:hidden}
@media(max-width:560px){.scry-cucco__wrap{flex-direction:column;text-align:center}}
.scry-cucco__sprite{width:104px;flex-shrink:0;align-self:center}
.scry-cucco__sprite img{width:100%;height:auto;display:block;image-rendering:pixelated}
.scry-cucco__bubble{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
.scry-cucco__name{font-family:var(--font-pixel);font-size:.68rem;line-height:1.5;color:var(--ac)}
.scry-cucco__role{font-size:.75rem;color:var(--muted);margin-bottom:6px}
.scry-cucco__quip{font-family:var(--font-read);font-size:.9rem;line-height:1.55;color:var(--heading);margin:0}
.scry-cucco__quip::before{content:'\201C'}
.scry-cucco__quip::after{content:'\201D'}
.scry-cucco__stats{display:flex;align-items:center;gap:10px;margin-top:10px;flex-wrap:wrap}
.scry-cucco__lvl{font-family:var(--font-pixel);font-size:.625rem;border:1px solid var(--ac);color:var(--ac);padding:4px 6px;line-height:1}
.scry-cucco__hp{letter-spacing:1px;font-size:.8rem}
.scry-cucco__hp .scry-cucco__heart--filled{color:var(--danger)}
.scry-cucco__hp .scry-cucco__heart--empty{color:var(--muted)}
.scry-cucco__xplabel{font-family:var(--font-pixel);font-size:.625rem;color:var(--muted)}
.scry-cucco__xp{position:relative;flex:1;min-width:60px;max-width:130px;height:8px;background:var(--bg);border:1px solid var(--border);overflow:hidden}
.scry-cucco__xp>span{position:absolute;left:0;top:0;bottom:0;display:block;background:var(--ac)}
.scry-cucco__cap{font-size:12px;line-height:1.45;color:var(--muted);font-style:italic;margin-top:10px}
.scry-cucco__badge{display:inline-flex;flex-direction:column;align-items:center;gap:8px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-retro);padding:14px 16px;text-align:center}
.scry-cucco__medallion{width:88px;height:88px;display:flex;align-items:center;justify-content:center;background:var(--bg);border:1px solid var(--border);border-radius:50%;overflow:hidden}
.scry-cucco__medallion img{width:78%;height:auto;image-rendering:pixelated}
.scry-cucco__plate{font-family:var(--font-pixel);font-size:.625rem;color:var(--ac)}
.scry-cucco__sub{font-size:.68rem;color:var(--muted)}
.scry-cucco__solo{width:120px}
.scry-cucco__solo img{width:100%;height:auto;image-rendering:pixelated}
.scry-cucco__fg{display:flex;gap:15px;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:15px}
@media(max-width:560px){.scry-cucco__fg{flex-direction:column;text-align:center}}
.scry-cucco__fgframe{width:88px;flex:none;align-self:center;background:var(--bg);border:1px solid var(--border);border-radius:7px;padding:7px}
.scry-cucco__fgframe img{width:100%;height:auto;image-rendering:pixelated;display:block}
.scry-cucco__fgbody{min-width:0}
.scry-cucco__fgnm{display:flex;align-items:center;gap:9px;font-family:var(--font-pixel);font-size:.625rem;line-height:1.4;color:var(--heading)}
.scry-cucco__fgdot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--ac)}
.scry-cucco__fgrole{font-family:var(--font-code);font-size:.72rem;color:var(--muted);margin:6px 0 9px}
.scry-cucco__fgquip{font-family:var(--font-read);font-size:.88rem;color:var(--text);line-height:1.55;margin:0 0 11px}
.scry-cucco__fgstat{font-family:var(--font-code);font-size:.73rem;color:var(--muted);letter-spacing:.02em}
.scry-cucco__fgstat b{font-weight:600;color:var(--ac)}
.scry-cucco__fgsep{opacity:.5;margin:0 7px}

/* Print portraits (scry-cucco--print): the two-ink screenprint art. It is
   vector line art, so it renders smooth, not pixelated, and it always sits
   on --mascot-paper with one flat disc in the class accent behind it — the
   disc is drawn here rather than baked into each SVG, so it follows the
   theme's --ac and pref-mono. Pixel sprites (scry-cucco--pixel) keep the
   rules above unchanged. */
.scry-cucco--print img{image-rendering:auto}
.scry-cucco--print .scry-cucco__sprite,
.scry-cucco--print .scry-cucco__solo,
.scry-cucco--print .scry-cucco__medallion,
.scry-cucco--print .scry-cucco__fgframe{
  background:radial-gradient(circle at 50% 47%,var(--cucco-disc,color-mix(in srgb,var(--ac) 42%,transparent)) 0 37%,transparent 37.5%),var(--mascot-paper);
  border:1px solid var(--frame);border-radius:var(--radius-retro);aspect-ratio:1/1;padding:0;box-sizing:border-box}
.scry-cucco--print .scry-cucco__sprite{width:120px}
.scry-cucco--print .scry-cucco__medallion{border-radius:50%}
.scry-cucco--print .scry-cucco__medallion img{width:100%}
.scry-cucco--print .scry-cucco__fgframe{width:96px}

/* ── Accessible monochrome charting tokens, Phase 5 ───────────────────
   Ported verbatim from the DS bundle (.claude/skills/scryops-design/
   tokens/charts.css), itself ported from the "Accessible monochrome
   chart templates" handoff. Hue is NEVER the sole carrier of meaning —
   every category, rank and relationship is encoded through value
   (lightness), texture, stroke treatment, shape or position too.
   Light theme uses the handoff's values verbatim; dark/calm value-invert
   the ramp (brightest = highest rank) so ordering survives on a dark
   ground. */
:root {
  --mc-surface:var(--surface);
  --mc-inverse:#ECE6D9;
  --mc-on-inverse:#141210;

  --mc-ink:var(--heading);
  --mc-ink-secondary:var(--text);
  --mc-ink-muted:var(--muted);
  --mc-border:var(--border);
  --mc-axis:var(--heading);
  --mc-conditional:#8E8574;

  --mc-ramp-1:#ECE6D9;   /* highest */
  --mc-ramp-2:#BDB3A0;
  --mc-ramp-3:#8E8574;
  --mc-ramp-4:#635C4F;
  --mc-ramp-5:#413B32;   /* lowest */
  --mc-tint:#2A251D;

  /* stroke widths — theme-independent, identical everywhere */
  --mc-stroke-axis:1.5px;
  --mc-stroke-gridline:1px;
  --mc-stroke-shape-outline:1px;   /* outline on EVERY filled/patterned mark */
  --mc-stroke-node:1.6px;
  --mc-stroke-node-primary:3.5px;  /* entry point */
  --mc-stroke-edge:1.8px;
  --mc-stroke-edge-conditional:1.6px;

  /* type — theme-independent */
  --mc-font-sans:var(--font-read);
  --mc-font-mono:var(--font-code);
  --mc-text-h2:26px;
  --mc-text-chart-title:15px;      /* weight 500 */
  --mc-text-node:15px;
  --mc-text-body:14.5px;
  --mc-text-label:14px;            /* axis category labels — HARD FLOOR on screen */
  --mc-text-caption:13.5px;
  --mc-text-value:13px;            /* mono, data values */
  --mc-text-tick:12px;             /* mono, axis ticks */
  --mc-text-eyebrow:12px;          /* mono, uppercase, tracking .14em */
  --mc-text-node-caption:12px;

  /* geometry — theme-independent */
  --mc-radius-card:4px;
  --mc-radius-chip:3px;
  --mc-chart-max:100%;             /* charts fill their column (viewBox is 640 units) */
  --mc-shadow:none;                /* the system uses no shadows */
  --mc-gradient:none;              /* and no gradients, ever */
  --mc-motion:none;                /* charts do not animate */

  /* texture — theme-independent */
  --mc-pattern-period:8px;         /* minimum tile — below this it shimmers */
  --mc-pattern-hatch-width:3.4px;
  --mc-pattern-dot-radius:2.4px;
  --mc-pattern-grid-width:1.6px;
}

html.light {
  --mc-surface:#FFFDF8;
  --mc-inverse:#1F1E1B;
  --mc-on-inverse:#F4F2EC;

  --mc-ink:#1F1E1B;
  --mc-ink-secondary:#3C3A35;
  --mc-ink-muted:#6E6A62;
  --mc-border:#DBD7CD;
  --mc-axis:#1F1E1B;
  --mc-conditional:#5C5850;

  --mc-ramp-1:#1F1E1B;   /* highest */
  --mc-ramp-2:#4F4C45;
  --mc-ramp-3:#7C786F;
  --mc-ramp-4:#ABA79D;
  --mc-ramp-5:#D5D1C7;   /* lowest */
  --mc-tint:#EAE7DF;
}

html.calm {
  --mc-inverse:#F2EAD8;
  --mc-on-inverse:#17140E;
  --mc-conditional:#9C8F74;

  --mc-ramp-1:#F2EAD8;
  --mc-ramp-2:#C9BC9E;
  --mc-ramp-3:#9C8F74;
  --mc-ramp-4:#6E6450;
  --mc-ramp-5:#453E30;
  --mc-tint:#2A2418;
}

/* ── Print: prepare for the worst — a copy that reads on paper, no power required ── */
@media print {
  :root { color-scheme: light; }
  html, body { background: #fff; }
  body { color: #111; font-size: 11pt; line-height: 1.5; }

  /* Drop interactive + navigational chrome. */
  .site-top, .toc, .site-footer, .scry-fp,
  .ask-cta, .featured-cta { display: none !important; }

  /* Single-column, full-bleed article. */
  .layout { display: block; }
  .article { max-width: none; width: auto; margin: 0; padding: 0; }

  /* Ink-economical type. */
  h1, h2, h3, .h1, .h2, .h3, .title, .heading { color: #000; }
  a { color: #000; text-decoration: underline; }

  /* Preserve external link targets on paper. */
  .article a[href^="http"]::after {
    content: " (" attr(href) ")"; font-size: .85em; color: #333; word-break: break-all;
  }

  /* Don't split figures, tables, or code across pages; keep headings with bodies. */
  figure, table, pre, .scry-callout, .scry-insight__body, .obs-fig, .scry-table { page-break-inside: avoid; break-inside: avoid; }
  h1, h2, h3 { page-break-after: avoid; }

  /* Legible boxes without color fills. */
  pre, code, .scry-callout, table, th, td {
    border-color: #999 !important; background: transparent !important; color: #111 !important;
  }
}
/* ============================================================
   scryops — parameterised figure shortcodes
   Append to themes/scryops/assets/css/telemetry.css.

   The shortcodes emit MARKUP ONLY and read their styling from here, so a
   page with six figures ships one copy of these rules instead of six.

   Every rule below encodes rank on a non-colour channel. Nothing in this
   block references --cyan / --warn / --danger / --green: those collapse
   under html.pref-mono. Ramp steps are referenced directly.
   ============================================================ */

/* shared glyph — the Callout silhouette set, used by every figure */
.obs-g{width:12px;height:12px;flex:none;display:block}

/* ---- ranked rows: rail value + rail width + glyph ---- */
.obs-rank{display:flex;flex-direction:column;gap:12px;font-family:var(--font-code)}
.obs-rank__row{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:start;
  background:var(--surface);border:1px solid var(--mc-border);border-radius:var(--mc-radius-chip);padding:13px 16px}
/* rank-ladder escalation (0.5px increments) has no canonical charts.css
   equivalent — it's a 4-step severity scale, not a single named role like
   --mc-stroke-node. Left literal on purpose rather than force one of the
   four onto --mc-stroke-node-primary just because 3.5px happens to match. */
.obs-rank__row[data-rank="0"]{border-left:1.5px solid var(--mc-ramp-3)}
.obs-rank__row[data-rank="1"]{border-left:2.5px solid var(--mc-ramp-2)}
.obs-rank__row[data-rank="2"]{border-left:3.5px solid var(--mc-ramp-1)}
.obs-rank__row[data-rank="3"]{border-left:4px solid var(--mc-ramp-1)}
/* 5th rung: obs-maturity-ladder.html only (a 5-level ladder that reuses
   the diamond glyph for both rank 3 and rank 4 per the resolved open
   decision — same brightest ramp step as rank 3, one heavier weight so
   the two rungs still read apart without a new shape). */
.obs-rank__row[data-rank="4"]{border-left:4.5px solid var(--mc-ramp-1)}
.obs-rank__g{margin-top:3px}
.obs-rank__head{font-size:var(--mc-text-value);line-height:1.5;color:var(--mc-ink);font-weight:600}
.obs-rank__act{font-family:var(--font-read);font-size:var(--mc-text-label);line-height:1.55;color:var(--mc-ink-secondary);margin:4px 0 0}

/* ---- decision stem ---- */
.obs-dt{font-family:var(--font-code);container-type:inline-size}
.obs-dt__hd{font-family:var(--font-code);font-weight:600;font-size:var(--mc-text-value);line-height:1.5;color:var(--mc-ink);margin-bottom:9px}
.obs-dt__sub{font-size:var(--mc-text-tick);color:var(--mc-ink-secondary);margin-bottom:20px}
.obs-dt__body{display:grid;grid-template-columns:minmax(0,178px) 56px minmax(0,1fr);align-items:center}
.obs-dt__body.noStem{grid-template-columns:minmax(0,1fr)}
.obs-dt__stem{display:flex;flex-direction:column}
.obs-dt__node{background:var(--surface);border:var(--mc-stroke-node) solid var(--mc-ink);border-radius:var(--mc-radius-chip);padding:11px 15px;text-align:center}
.obs-dt__node.entry{border-width:var(--mc-stroke-node-primary)}
.obs-dt__node.tint{background:var(--mc-tint,var(--surface))}
.obs-dt__nm{font-size:var(--mc-text-node);line-height:1.35;color:var(--mc-ink)}
.obs-dt__cap{display:block;font-size:var(--mc-text-node-caption);color:var(--mc-ink-secondary);line-height:1.4;margin-top:4px}
.obs-dt__link{display:flex;flex-direction:column;align-items:center;gap:2px;padding:7px 0;color:var(--mc-ink);font-size:var(--mc-text-node-caption)}
.obs-dt__link span{color:var(--mc-ink-secondary)}
.obs-dt__fan{display:flex;align-items:center;justify-content:center;color:var(--mc-ink)}
@container (max-width:520px){
  .obs-dt__body{grid-template-columns:1fr}
  .obs-dt__fan{display:none}
}

/* ---- gauge: extent + segmentation + state-on-the-spent-portion ---- */
.obs-gauge{font-family:var(--font-code);display:block}
.obs-gauge__top{display:flex;align-items:baseline;gap:10px;margin-bottom:7px}
.obs-gauge__label{font-size:var(--mc-text-eyebrow);letter-spacing:.1em;text-transform:uppercase;color:var(--mc-ink-secondary)}
.obs-gauge__val{margin-left:auto;font-size:var(--mc-text-value);color:var(--mc-ink);font-weight:600;display:inline-flex;align-items:center;gap:7px}
.obs-gauge__track{display:flex;gap:2px;padding:3px;border:var(--mc-stroke-shape-outline) solid var(--mc-border);background:var(--surface);position:relative}
/* warn/critical border escalation is its own 2-step severity scale, same
   reasoning as the rank-ladder above — 1.8px coincides with --mc-stroke-edge
   but 2.5px (critical) has no canonical match, so both stay literal rather
   than tokenize one step and not the other. */
.obs-gauge__track[data-state="warn"]{border-width:1.8px}
.obs-gauge__track[data-state="critical"]{border-width:2.5px}
.obs-gauge__cell{flex:1;height:22px;min-width:3px;background:var(--mc-ramp-1)}
/* filled cells stay SOLID — they are the value. State escalates on the spent
   portion, so every channel increases monotonically with severity. */
.obs-gauge__cell.off{background:transparent;border:var(--mc-stroke-shape-outline) solid var(--mc-border)}
.obs-gauge__track[data-state="warn"] .obs-gauge__cell.off{
  background-image:repeating-linear-gradient(45deg,var(--mc-ramp-4) 0 3px,transparent 3px 9px)}
.obs-gauge__track[data-state="critical"] .obs-gauge__cell.off{
  background-image:repeating-linear-gradient(45deg,var(--mc-ramp-3) 0 4px,transparent 4px 9px)}
.obs-gauge__tick{position:absolute;top:-4px;bottom:-4px;width:2px;background:var(--mc-ink)}
.obs-gauge__note{font-size:var(--mc-text-node-caption);color:var(--mc-ink-secondary);margin-top:7px;line-height:1.5}
.obs-gauge__pips{display:inline-flex;gap:4px;align-items:center}
.obs-gauge__pip{width:13px;height:13px;flex:none;background:var(--mc-ramp-1)}
.obs-gauge__pip.off{background:transparent;border:var(--mc-stroke-shape-outline) solid var(--mc-border)}
.obs-gauge__pipnum{font-size:var(--mc-text-node-caption);color:var(--mc-ink-secondary);margin-left:6px}

/* ---- ranked bars (template 01): length + ramp value + printed figure ---- */
.obs-bars{font-family:var(--font-code);display:flex;flex-direction:column;gap:10px}
.obs-bars__row{display:grid;grid-template-columns:minmax(0,150px) minmax(0,1fr) auto;gap:12px;align-items:center}
.obs-bars__lab{font-family:var(--font-read);font-size:var(--mc-text-label);color:var(--mc-ink);text-align:right}
.obs-bars__track{height:30px;position:relative}
.obs-bars__fill{height:100%;border:var(--mc-stroke-shape-outline) solid var(--mc-ink)}
.obs-bars__val{font-size:var(--mc-text-value);color:var(--mc-ink-secondary);white-space:nowrap}
/* <small> would shrink 13px values to 10.8px; hold the 12px figure floor */
.obs-bars__val small{font-size:12px}
.obs-bars__row.on .obs-bars__fill{border-width:var(--mc-stroke-node-primary)}
.obs-bars__row.on .obs-bars__lab,.obs-bars__row.on .obs-bars__val{color:var(--mc-ink);font-weight:600}

/* ---- series (template 03b): stroke weight + dash + marker, per series ----
   Sizes are set here rather than as SVG attributes so they follow the type
   floor. The chart's 640-unit viewBox renders at the card's content width, so
   a declared size is multiplied by renderedWidth/640 — at a typical 636px card
   that is 0.994, which pushed a declared 12px to 11.93px rendered, just under
   the 11px floor's nearest step. Sizes carry a small margin for that: ticks
   and captions 12.5px, x labels 13.5px, end labels 14.5px. Never lower them
   without re-measuring RENDERED size (computedFontSize x scale). */
.obs-ser{width:100%;height:auto;display:block;font-family:var(--font-code)}
.obs-ser__hd{font-family:var(--font-read);font-size:var(--mc-text-chart-title);font-weight:500;color:var(--mc-ink);margin-bottom:4px}
.obs-ser__sub{font-family:var(--font-code);font-size:var(--mc-text-tick);color:var(--mc-ink-secondary);margin-bottom:14px}
/* tick/thr/ev are SVG text in this chart's 640-unit viewBox (see the
   render-scaling comment above) — 12.5px was deliberately measured to
   survive that scaling; canonical --mc-text-tick is 12px declared, which
   would reopen the exact bug this session already fixed. Left literal
   until the canonical token itself accounts for viewBox scaling. */
.obs-ser__tick{font-size:12.5px;fill:var(--mc-ink-secondary)}
.obs-ser__xlab{font-family:var(--font-read);font-size:var(--mc-text-caption);fill:var(--mc-ink)}
.obs-ser__end{font-family:var(--font-read);font-size:var(--mc-text-body);font-weight:500;fill:var(--mc-ink)}
.obs-ser__thr{font-size:12.5px;fill:var(--mc-ink-secondary)}
.obs-ser__ev{font-size:12.5px;fill:var(--mc-ink)}

/* ---- same-incident-two-views: terminal well beneath the line chart ----
   Same theme-independent code tokens and sizing as the .bg-term / .calc
   wells (obs-pii-bingo.html, obs-hash-danger.html) — always dark, never
   overridden per theme. */
.obs-siv__term{margin-top:14px;background:var(--code-bg);border:1px solid var(--code-bd);border-radius:var(--radius);padding:.85rem 1rem;font-family:var(--font-code);font-size:.78rem;line-height:1.85;color:var(--code-text)}
.obs-siv__term .p{color:var(--code-text)}
.obs-siv__note{color:var(--code-text)}
.obs-siv__answer{color:var(--code-hi);font-weight:600;margin-top:.3rem}

/* ---- column (template 01b): vertical sibling of obs-ranked-bars, log scale ----
   Same viewBox-scaling note as obs-ser above: tick/y-label text stays at
   the measured-safe 12.5px rather than canonical --mc-text-tick (12px);
   value/label text (13/14px canonical) already clears the floor with
   margin at this scale, so those reference the tokens directly. */
.obs-col{width:100%;height:auto;display:block;font-family:var(--font-code)}
.obs-col__hd{font-family:var(--font-read);font-size:var(--mc-text-chart-title);font-weight:500;color:var(--mc-ink);margin-bottom:4px}
.obs-col__sub{font-family:var(--font-code);font-size:var(--mc-text-tick);color:var(--mc-ink-secondary);margin-bottom:14px}
.obs-col__tick{font-size:12.5px;fill:var(--mc-ink-secondary)}
.obs-col__val{font-family:var(--font-code);font-size:var(--mc-text-value);fill:var(--mc-ink-secondary)}
.obs-col__val.on{fill:var(--mc-ink)}
.obs-col__lab{font-family:var(--font-code);font-size:var(--mc-text-label);fill:var(--mc-ink-secondary)}
.obs-col__lab.on{fill:var(--mc-ink)}
.obs-col__cap{font-size:12.5px;fill:var(--mc-ink-secondary)}

/* ---- waterfall (template 08): span timeline, self-time vs waiting ----
   Same viewBox-scaling note as obs-ser/obs-col above: tick/name/value text
   (all SVG fill:, not HTML color:) stays at the measured-safe 12.5px
   rather than canonical --mc-text-tick (12px). hd/sub are plain HTML divs
   with no scaling risk, so those reference the tokens directly. */
.obs-wf{width:100%;height:auto;display:block;font-family:var(--font-code)}
.obs-wf__hd{font-family:var(--font-read);font-size:var(--mc-text-chart-title);font-weight:500;color:var(--mc-ink);margin-bottom:4px}
.obs-wf__sub{font-size:var(--mc-text-tick);color:var(--mc-ink-secondary);margin-bottom:14px}
.obs-wf__tick{font-size:12.5px;fill:var(--mc-ink-secondary)}
.obs-wf__nm{font-size:12.5px;fill:var(--mc-ink-secondary)}
.obs-wf__nm.on{fill:var(--mc-ink);font-weight:600}
/* the end-of-retention ✕ straddles the bar's end edge: a card-ground halo
   keeps it legible over the ramp fill as well as the card (2.7:1 in calm before) */
.obs-wf__end{stroke:var(--surface);stroke-width:3px;stroke-linejoin:round;paint-order:stroke}
.obs-wf__val{font-size:12.5px;fill:var(--mc-ink-secondary)}
.obs-wf__val.on{fill:var(--mc-ink);font-weight:600}
.obs-wf__key{display:flex;gap:18px;flex-wrap:wrap;margin-top:13px;padding-top:11px;
  border-top:1px solid var(--mc-border);font-size:var(--mc-text-node-caption);color:var(--mc-ink-secondary)}
.obs-wf__key span{display:inline-flex;align-items:center;gap:6px}
.obs-wf__sw{width:22px;height:11px;flex:none;border:1px solid var(--mc-ink)}
.obs-wf__sw.auto{background:var(--mc-ramp-3)}
.obs-wf__sw.manual{background:var(--mc-ramp-2);border-width:2px}
.obs-wf__sw.err{background:var(--mc-ramp-1);border-style:dashed}
.obs-wf__sw.hatch{background:repeating-linear-gradient(45deg,var(--mc-ink) 0 3px,var(--surface) 3px 9px)}
.obs-wf__sw.heavy{background:var(--mc-ramp-1);border-width:3px}

/* ---- state machine (template 07): glyph rank + entry border + return lane ---- */
.obs-sm{font-family:var(--font-code);container-type:inline-size}
.obs-sm__hd{font-family:var(--font-code);font-weight:600;font-size:var(--mc-text-value);line-height:1.5;color:var(--mc-ink);margin-bottom:9px}
.obs-sm__sub{font-size:var(--mc-text-tick);color:var(--mc-ink-secondary);margin-bottom:20px}
.obs-sm__row{display:flex;align-items:stretch;flex-wrap:nowrap}
.obs-sm__st{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:7px;
  background:var(--surface);border:var(--mc-stroke-node) solid var(--mc-ink);border-radius:var(--mc-radius-chip);padding:14px 12px;text-align:center}
.obs-sm__st.entry{border-width:var(--mc-stroke-node-primary)}
.obs-sm__st.tint{background:var(--mc-tint,var(--surface))}
.obs-sm__nm{font-size:var(--mc-text-node);line-height:1.3;color:var(--mc-ink)}
.obs-sm__cap{font-size:var(--mc-text-node-caption);color:var(--mc-ink-secondary);line-height:1.4}
.obs-sm__edge{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  padding:0 11px;min-width:74px;color:var(--mc-ink)}
.obs-sm__arrow{font-size:16px;line-height:1}
.obs-sm__trig{font-size:var(--mc-text-node-caption);color:var(--mc-ink-secondary);text-align:center;line-height:1.35}
/* returns: a dashed lane, so the forward path stays readable instead of
   carrying crossing arrows back across the row */
.obs-sm__returns{margin-top:16px;display:flex;flex-direction:column;gap:9px}
.obs-sm__ret{display:grid;grid-template-columns:auto minmax(0,1fr);gap:11px;align-items:center;
  border-top:1px dashed var(--mc-border);padding-top:9px}
.obs-sm__retg{font-size:var(--mc-text-label);line-height:1;color:var(--mc-ink)}
.obs-sm__rett{font-size:var(--mc-text-tick);color:var(--mc-ink-secondary);line-height:1.5}
.obs-sm__rett b{color:var(--mc-ink);font-weight:600}
@container (max-width:520px){
  .obs-sm__row{flex-direction:column}
  .obs-sm__edge{padding:8px 0;min-width:0}
}

/* ---- flow (template 05): one-direction flow, DOM boxes not SVG ----
   Ported from the handoff's FlowDiagram.jsx. One meaning per channel:
   border weight = rank (entry), fill = kind (paper/tint/grid/terminal),
   dash = conditionality. Never wraps a row — below ~520px it re-flows to
   the vertical arrangement (arrows swap to down) instead of scrolling. */
.obs-flow-wrap{container-type:inline-size}
.obs-flow__hd{font-family:var(--font-code);font-weight:600;font-size:var(--mc-text-value);line-height:1.5;color:var(--mc-ink);margin-bottom:9px}
.obs-flow__sub{font-size:var(--mc-text-tick);color:var(--mc-ink-secondary);margin-bottom:20px}
/* row label for stacked-comparison usage (obs-reactive-loop): a plain
   mono tag, deliberately the SAME neutral tone for every row — the old
   .flow-version.v2{color:var(--cyan)} split had no meaning to carry, it
   just tinted the "new model" row for no reason other than novelty */
.obs-flow__rowlabel{font-family:var(--font-code);font-size:12px;letter-spacing:.08em;color:var(--mc-ink-secondary);margin-bottom:9px}
.obs-flow{display:flex;align-items:stretch;flex-wrap:nowrap;overflow-x:auto;font-family:var(--mc-font-sans)}
.obs-flow.stack{flex-direction:column;align-items:stretch;overflow-x:visible}
.obs-flow__node{flex:1 1 0;min-width:86px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
  background:var(--mc-surface);border:var(--mc-stroke-node) solid var(--mc-ink);border-radius:var(--mc-radius-chip);padding:15px 12px;text-align:center}
.obs-flow__node.entry{border-width:var(--mc-stroke-node-primary)}
.obs-flow__node.tint{background:var(--mc-tint)}
.obs-flow__node.grid{
  background-image:linear-gradient(var(--mc-ink) 1.6px,transparent 1.6px),linear-gradient(90deg,var(--mc-ink) 1.6px,transparent 1.6px);
  background-size:9px 9px;background-color:var(--mc-surface)}
.obs-flow__node.terminal{background:var(--mc-inverse);border-color:var(--mc-inverse)}
.obs-flow__node.conditional{border-style:dashed}
.obs-flow__nm{font-size:var(--mc-text-node);font-weight:500;line-height:1.3;color:var(--mc-ink)}
.obs-flow__cap{font-family:var(--mc-font-mono);font-size:var(--mc-text-node-caption);color:var(--mc-ink-secondary);line-height:1.3}
.obs-flow__text{display:flex;flex-direction:column;align-items:center;gap:6px}
/* text over ANY pattern fill sits on one contiguous surface-coloured band,
   so no pattern lines run between the label and its caption */
.obs-flow__node.grid .obs-flow__text{background:var(--mc-surface);border-radius:2px;padding:4px 8px}
.obs-flow__node.terminal .obs-flow__nm{color:var(--mc-on-inverse)}
.obs-flow__node.terminal .obs-flow__cap{color:var(--mc-on-inverse);opacity:.72}
.obs-flow__edge{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;padding:0 10px;min-width:32px;color:var(--mc-ink)}
.obs-flow__edge.cond{color:var(--mc-conditional)}
.obs-flow__arrow{font-size:16px;line-height:1}
.obs-flow__arrow .a-col{display:none}
.obs-flow__elabel{font-family:var(--mc-font-mono);font-size:var(--mc-text-node-caption);color:var(--mc-ink-secondary);text-align:center;max-width:66px;line-height:1.3}
.obs-flow.stack .obs-flow__edge{padding:8px 0}
.obs-flow.stack .obs-flow__arrow .a-row{display:none}
.obs-flow.stack .obs-flow__arrow .a-col{display:inline}
.obs-flow.stack .obs-flow__elabel{max-width:none}
/* a row needs ~520px for 4 nodes plus edge labels; below that, flow DOWN
   rather than scroll — vertical is a first-class direction here */
@container (max-width:520px){
  .obs-flow:not(.stack){flex-direction:column;align-items:stretch;overflow-x:visible}
  .obs-flow:not(.stack) .obs-flow__edge{padding:8px 0}
  .obs-flow:not(.stack) .obs-flow__arrow .a-row{display:none}
  .obs-flow:not(.stack) .obs-flow__arrow .a-col{display:inline}
  .obs-flow:not(.stack) .obs-flow__elabel{max-width:none}
}

/* Mermaid state diagrams. Painted here rather than relying on baked
   attributes: the tokenizer strips mermaid's stylesheet, and state-diagram
   theme variables were absent from the sentinel palette, so these figures
   rendered with browser defaults (black on black). Every value is a
   neutral figure token — no --cyan / --warn / --danger, which collapse
   under html.pref-mono. */
.statediagram .node rect,
.statediagram .node circle,
.statediagram .node polygon{fill:var(--node-fill);stroke:var(--node-stroke);stroke-width:1.6px}
.statediagram .node.state-start circle{fill:var(--mc-ramp-1);stroke:none}
.statediagram .node .label-container{fill:var(--node-fill);stroke:var(--node-stroke)}
.statediagram .nodeLabel,
.statediagram .nodeLabel tspan{fill:var(--nlab);font-family:var(--font-code);font-size:13px}
.statediagram .transition{stroke:var(--edge);stroke-width:1.8px;fill:none}
.statediagram .edgeLabel rect{fill:var(--fig-bg);stroke:none}
.statediagram .edgeLabel,
.statediagram .edgeLabel tspan{fill:var(--mc-ink-secondary);font-family:var(--font-code);font-size:12px}
.statediagram marker path,
.statediagram .marker{fill:var(--edge);stroke:var(--edge)}

/* ---- migrated from shortcode inline <style> blocks ---- */

/* obs-about-sections.html */
.about-sections {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  margin: 1.4rem 0 0.4rem;
}
.about-sec-card {
  display: flex;
  flex-direction: column;
  background: var(--fig-bg);
  border: 1px solid var(--border);
  padding: 1.2rem 1.2rem 1rem;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s, background 0.15s;
}
.about-sec-article { border-top: 3px solid var(--cyan); }
.about-sec-guide   { border-top: 3px solid var(--cyan); }
.about-sec-howto   { border-top: 3px solid var(--warn); }
.about-sec-qa      { border-top: 3px solid var(--violet); }
.about-sec-card:hover { background: var(--warn-bg); }
.about-sec-article:hover { border-color: var(--cyan); }
.about-sec-guide:hover   { border-color: var(--cyan); }
.about-sec-howto:hover   { border-color: var(--warn); }
.about-sec-qa:hover      { border-color: var(--violet); }
.about-sec-tag {
  font-family: var(--font-code);
  font-size:0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 0.7rem;
  display: block;
}
.about-sec-article .about-sec-tag { color: var(--cyan); }
.about-sec-guide   .about-sec-tag { color: var(--cyan); }
.about-sec-howto   .about-sec-tag { color: var(--warn); }
.about-sec-qa      .about-sec-tag { color: var(--violet); }
.about-sec-title {
  font-family: var(--font-code);
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--heading);
  margin-bottom: 0.5rem;
}
.about-sec-desc {
  font-size: 0.82rem;
  line-height: 1.65;
  color:var(--mc-ink-secondary);
  flex: 1;
  margin-bottom: 1rem;
}
.about-sec-cta {
  font-size: 0.75rem;
  color: var(--warn);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-top: auto;
}
.about-sec-card:hover .about-sec-cta { color: var(--warn); }
@media (max-width: 700px) {
  .about-sections { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 420px) {
  .about-sections { grid-template-columns: 1fr; }
}

/* obs-alert-fatigue-stats.html */
.af-wrap{background:var(--fig-bg);border:1px solid var(--border);border-top:3px solid var(--mc-ink);padding:1.4rem 1.4rem 1.2rem;font-family:var(--font-code);}
.af-hdr{font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.1em;text-align:center;margin-bottom:1.3rem;}
.af-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:1.6rem;}
@media(max-width:640px){.af-grid{grid-template-columns:repeat(2,1fr);}}
.af-card{background:var(--bg);border:1px solid var(--border);border-top:3px solid;display:flex;flex-direction:column;align-items:center;text-align:center;gap:.35rem;padding:1rem .8rem .85rem;}
.af-glyph{width:14px;height:14px;margin-bottom:.15rem;}
.af-num{font-size:2.3rem;font-weight:700;line-height:1;letter-spacing:-.02em;}
.af-desc{font-size:.78rem;color:var(--mc-ink-secondary);line-height:1.5;flex:1;}
.af-bars-hdr{font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.08em;margin-bottom:.5rem;}
.af-div{height:1px;background:var(--border);margin-bottom:1rem;}
.af-row{display:grid;grid-template-columns:190px 1fr;align-items:center;gap:.8rem;margin-bottom:.7rem;}
@media(max-width:640px){.af-row{grid-template-columns:110px 1fr;}}
.af-lbl{font-size:.8rem;color:var(--mc-ink-secondary);text-align:right;line-height:1.35;}
@media(max-width:640px){.af-lbl{font-size:.75rem;}}
.af-track{background:var(--bg);border:1px solid var(--border);height:30px;position:relative;overflow:hidden;}
.af-bar{height:100%;display:flex;align-items:center;justify-content:flex-end;}
.af-bar span{font-size:.8rem;font-weight:700;padding-right:.5rem;}
.af-axis{display:flex;justify-content:space-between;margin-left:calc(190px + .8rem);font-size:.75rem;color:var(--mc-ink-secondary);padding-top:.2rem;}
@media(max-width:640px){.af-axis{margin-left:calc(110px + .8rem);font-size:.75rem;}}
.af-src{margin-top:1.1rem;font-family:var(--font-code);font-size:.75rem;line-height:1.7;color:var(--mc-ink-secondary);letter-spacing:.03em;}
figure.af-fig{margin:2.5rem 0 2rem;}
figure.af-fig figcaption{font-size:12px;color:var(--mc-ink-muted);font-style:italic;margin-top:.6rem;padding-left:.2rem;}

/* obs-alert-routing.html */
.rt-wrap{background:var(--fig-bg);border:1px solid var(--border);border-top:3px solid var(--mc-ink);padding:1.4rem 1.4rem 1.2rem;font-family:var(--font-code);}
.rt-hdr{font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.1em;text-align:center;margin-bottom:1.3rem;}
.rt-cols{display:grid;grid-template-columns:104px 1fr 1fr 132px;gap:.8rem;padding:0 .2rem .4rem;font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.06em;}
.rt-cols span:last-child{text-align:right;}
@media(max-width:640px){.rt-cols{display:none;}}
.rt-lane{background:var(--surface);border:1px solid var(--border);border-left-style:solid;display:grid;grid-template-columns:104px 1fr 1fr 132px;gap:.8rem;align-items:center;padding:.95rem .8rem;margin-bottom:.7rem;}
@media(max-width:640px){.rt-lane{grid-template-columns:1fr;gap:.55rem;}}
.rt-badge{display:flex;flex-direction:column;gap:.25rem;}
.rt-p{display:flex;align-items:center;gap:.45rem;font-size:1.5rem;font-weight:700;line-height:1;color:var(--mc-ink);}
.rt-g{width:14px;height:14px;flex:none;display:block;}
.rt-burn{font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.04em;}
.rt-cell{font-size:.82rem;color:var(--mc-ink-secondary);line-height:1.45;}
.rt-cell b{color:var(--mc-ink);font-weight:700;}
.rt-cell small{display:block;color:var(--mc-ink-secondary);font-size:.75rem;margin-top:.15rem;}
.rt-route{font-size:.82rem;color:var(--mc-ink-secondary);line-height:1.45;}
.rt-route small{display:block;color:var(--mc-ink-secondary);font-size:.75rem;margin-top:.15rem;}
.rt-route::before{content:"\2192";color:var(--mc-ink-secondary);margin-right:.4rem;font-weight:700;}
@media(max-width:640px){.rt-route::before{content:"\2192 ";}}
.rt-tag{justify-self:end;font-family:var(--font-code);font-size:.75rem;letter-spacing:.04em;padding:.45rem .55rem;border-style:solid;text-align:center;line-height:1.4;color:var(--mc-ink);}
@media(max-width:640px){.rt-tag{justify-self:start;}}
.rt-foot{margin-top:1rem;padding-top:.9rem;border-top:1px solid var(--border);font-size:.8rem;color:var(--mc-ink-secondary);line-height:1.5;}
.rt-foot b{color:var(--mc-ink);}
figure.rt-fig{margin:2.5rem 0 2rem;}
figure.rt-fig figcaption{font-size:12px;color:var(--mc-ink-muted);font-style:italic;margin-top:.6rem;padding-left:.2rem;}

/* obs-knowledge-tiers.html */
.knt-wrap{background:var(--fig-bg);border:1px solid var(--border);border-top:3px solid var(--mc-ink);padding:1.4rem 1.4rem 1.2rem;font-family:var(--font-code);}
.knt-hdr{font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.1em;text-align:center;margin-bottom:1.3rem;}
.knt-lane{background:var(--surface);border:1px solid var(--border);border-left-style:solid;display:grid;grid-template-columns:26px 180px 1fr 148px;gap:.8rem;align-items:center;padding:.95rem .8rem;margin-bottom:.7rem;}
@media(max-width:640px){.knt-lane{grid-template-columns:22px 1fr;gap:.55rem;}}
.knt-glyph{width:14px;height:14px;}
.knt-lp{font-family:var(--font-code);font-size:.75rem;font-weight:700;line-height:1.5;}
.knt-ls{font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.06em;margin-top:.3rem;}
.knt-desc{font-size:.82rem;color:var(--mc-ink-secondary);line-height:1.5;}
.knt-desc b{color:var(--mc-ink);}
.knt-desc small{display:block;color:var(--mc-ink-secondary);font-size:.75rem;margin-top:.2rem;}
.knt-tag{font-family:var(--font-code);font-size:.75rem;letter-spacing:.04em;padding:.45rem .55rem;border:1px solid;text-align:center;line-height:1.6;justify-self:end;}
/* Phone: the lane collapses to glyph + one text column; desc must join column 2
   or auto-placement drops it into the 22px glyph column under the tag. */
@media(max-width:640px){.knt-desc,.knt-tag{grid-column:2;}.knt-tag{justify-self:start;}}
.knt-foot{margin-top:1rem;padding-top:.9rem;border-top:1px solid var(--border);font-size:.8rem;color:var(--mc-ink-secondary);line-height:1.5;}
.knt-foot b{color:var(--mc-ramp-1);}

/* obs-stats-chart.html */
.obs-chart-wrap{background:var(--fig-bg);border:1px solid var(--border);border-top:3px solid var(--warn);padding:1.4rem 1.4rem 1.2rem;font-family:var(--font-code);}
.obs-chart-hdr{font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.1em;text-align:center;margin-bottom:1.2rem;}
.obs-stat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:1.6rem;}
@media(max-width:640px){.obs-stat-grid{grid-template-columns:repeat(2,1fr);}}
.obs-stat-card{background:var(--bg);border:1px solid var(--border);border-top:3px solid;display:flex;flex-direction:column;align-items:center;text-align:center;gap:.3rem;padding:1rem .8rem .8rem;}
.obs-stat-card svg.obs-stat-glyph{width:13px;height:13px;display:block;}
.obs-stat-num{font-size:2.4rem;font-weight:700;line-height:1;letter-spacing:-.02em;}
.obs-stat-desc{font-size:.78rem;color:var(--mc-ink-secondary);line-height:1.5;flex:1;}
.obs-stat-src{font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.04em;margin-top:.4rem;}
.obs-mttd-hdr{font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.1em;margin-bottom:.5rem;}
.obs-mttd-div{height:1px;background:var(--border);margin-bottom:1rem;}
.obs-mttd-row{display:grid;grid-template-columns:160px 1fr auto;align-items:center;gap:.8rem;margin-bottom:.7rem;}
@media(max-width:640px){.obs-mttd-row{grid-template-columns:90px 1fr auto;}}
.obs-mttd-lbl{font-size:.8rem;color:var(--mc-ink-secondary);text-align:right;word-break:break-word;line-height:1.4;}
@media(max-width:640px){.obs-mttd-lbl{font-size:.75rem;}}
.obs-mttd-track{background:var(--bg);border:1px solid var(--border);height:28px;position:relative;overflow:hidden;}
.obs-mttd-bar{height:100%;width:0;transition:width .9s cubic-bezier(.25,.46,.45,.94);position:relative;display:flex;align-items:center;}
@media(prefers-reduced-motion:reduce){.obs-mttd-bar{transition:none;}}
.obs-mttd-bar-fill{position:absolute;inset:0;opacity:.22;}
.obs-mttd-bar-border{position:absolute;inset:0;border:1.5px solid;}
.obs-mttd-val{font-size:.8rem;font-weight:700;white-space:nowrap;padding-left:.4rem;}
.obs-mttd-badge{font-size:.8rem;font-weight:700;white-space:nowrap;text-align:right;min-width:80px;}
.obs-mttd-badge small{display:block;font-size:.75rem;font-weight:400;color:var(--mc-ink-secondary);}
.obs-mttd-lbl svg.obs-mttd-glyph{width:12px;height:12px;flex:none;display:inline-block;vertical-align:-1.5px;margin-right:.4rem;}
.obs-mttd-axis{display:flex;justify-content:space-between;padding:.3rem 0 0;margin-left:calc(160px + .8rem);font-size:.75rem;color:var(--mc-ink-secondary);}
/* Phone: ten ticks cannot fit the ~140px axis at the 12px floor, and the axis
   spans the badge column so it never lined up with the narrow track. Each bar
   carries its own direct value label, so the axis is dropped. */
@media(max-width:640px){.obs-mttd-axis{display:none;}}
.obs-src{margin-top:1.2rem;font-family:var(--font-code);font-size:.75rem;color:var(--mc-ink-secondary);letter-spacing:.04em;}
figure.obs-fig{margin:2.5rem 0 2rem;}
figure.obs-fig figcaption{font-size:12px;color:var(--muted);font-style:italic;margin-top:.6rem;padding-left:.2rem;}

/* party-gallery.html */
.party-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1.1rem;margin:1.5rem 0;}
.party-card{margin:0;background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--ac);border-radius:3px;overflow:hidden;display:flex;flex-direction:column;}
.party-art{background:var(--bg);aspect-ratio:1/1;display:flex;align-items:center;justify-content:center;}
.party-art img{width:100%;height:100%;object-fit:contain;image-rendering:pixelated;display:block;}
/* print portraits: same paper ground + accent disc as .scry-cucco--print */
.party-art--print{background:radial-gradient(circle at 50% 47%,var(--cucco-disc,color-mix(in srgb,var(--ac) 42%,transparent)) 0 37%,transparent 37.5%),var(--mascot-paper);}
.party-art--print img{image-rendering:auto;}
.party-ph{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;width:88%;height:88%;border:2px dashed var(--border);border-radius:3px;color:var(--mc-ink-secondary);}
.party-ph-cls{font-family:'Press Start 2P',monospace;font-size:.7rem;color:var(--ac);opacity:.8;}
.party-ph-sub{font-family:var(--font-code);font-size:.7rem;letter-spacing:.06em;}
.party-meta{padding:.9rem 1rem 1.1rem;font-family:var(--font-code);}
.party-name{display:block;font-family:'Press Start 2P',monospace;font-size:.66rem;line-height:1.5;margin-bottom:.35rem;}
.party-cls{display:block;font-size:.72rem;color:var(--mc-ink-secondary);margin-bottom:.55rem;}
.party-quip{margin:0;font-size:.86rem;line-height:1.55;color:var(--text);}

