/* KPress design tokens -- the source of truth for the KPress design system.
 *
 * STRUCTURE (one comment-headed section per group; the public subset is pinned by
 * contract.py and the design-system playground exercises the tunable surface):
 *
 *   Foundations (.kpress, .kpress-page-main, .kpress-tooltip) -- theme-INDEPENDENT:
 *     1. Typography: families / weights / sizes / line-heights / section-label caps
 *     2. Spacing & layout    3. Shape (radii)    4. Motion    5. Chrome (card)
 *     6. Print    7. Color (theme-independent roles)    8. Compatibility aliases
 *   Display/Page switches  -- font mode (custom/system), reading font (serif/sans)
 *   Color -- palette x theme -- the single source of truth for every theme color
 *   Webfonts (@font-face)
 *
 * NAMING: foundation tokens are --kpress-<group>-<role>[-<variant>]
 * (--kpress-font-*, --kpress-font-size-*, --kpress-radius-*, --kpress-transition-*);
 * color roles are --kpress-doc-<role>; host override hooks are --kpress-host-*;
 * bare --color-* / --font-* are textpress-compat ALIASES (section 8), not new tokens.
 *
 * Design RULES are kept here as comments next to each group, close to the code
 * (kpress-design.md is the map, not a duplicate ledger). The public subset is pinned by
 * contract.py::PUBLIC_CSS_VARIABLES. Core doc tokens read var(--kpress-host-*, <default>)
 * so an embedding host app can theme them; static pages and static-site builds override
 * via the same variables. */
:root,
.kpress,
.kpress-page-main,
.kpress-tooltip {
  /* Adapted from Kash base_styles.css.jinja and base_webpage.html.jinja.
     This block holds the theme-independent tokens (type, spacing, radii, motion,
     chrome, print, the theme-independent color roles) and the --color-* / --font-*
     compatibility alias map (section 8). The actual theme COLOR VALUES are set per
     theme x palette in the "Color -- palette x theme" section below; an embedding
     host re-themes by overriding those --kpress-doc-* tokens directly. */

  /* ============================ 1. TYPOGRAPHY ============================ */
  /* SIZING POLICY: every font size in KPress derives from ONE base knob,
     --kpress-font-size-base, via calc(base * ratio) -- ratios are design, units
     are the host's choice. No font-size rule may reference `rem` directly (the
     lint in devtools/public_hygiene.py enforces this): `rem` would couple an
     embedded document's proportions to a root font size the embedder may not
     control, while the base knob keeps standalone pages preference-respecting
     (the 1rem default resolves against the reader's browser setting) and lets a
     host with any unit policy scale the whole document as one proportional
     unit. Embedders set --kpress-host-font-size-base ONCE (e.g. `17px` on
     :root); never override individual sizes. Deliberately root-relative and NOT
     derived from the base: layout lengths (--kpress-measure, container-query
     band conditions, grid tracks, tooltip width caps, page margins) -- see
     kpress-design.md "Sizing policy". Intentionally context-relative em/% sizes
     (the summary chevron, checkbox, sup/sub) also stay as they are. */
  --kpress-font-size-base: var(--kpress-host-font-size-base, 1rem);

  /* Families. body = UI/chrome sans; prose = serif reading face; sans = the sans
     stack; mono = code; footnote/table default to sans; punctuation is the
     shipped quote face. Each reads a --kpress-host-* hook so a host can swap it.
     The text families lead with a face KPress ships, so a document draws the
     same glyphs on every machine and a printed page embeds them; the system
     stacks behind each one are the fallback for a face that failed to load, not
     the design. The two places the platform is asked for a font on purpose are
     font_mode="system" and mono_font="system". */
  --kpress-font-body: var(
    --kpress-host-font-body,
    "Source Sans 3 Variable",
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif
  );
  /* "KPress Quotes" leads: it answers six code points and passes everything
     else down to PT Serif. See the punctuation token below for why. */
  --kpress-font-prose: var(
    --kpress-host-font-prose,
    var(--kpress-font-punctuation),
    "PT Serif",
    Georgia,
    "Times New Roman",
    serif
  );
  --kpress-font-sans: var(
    --kpress-host-font-sans,
    "Source Sans 3 Variable",
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif
  );
  /* The platform's own mono stack, named once. Three rules need it -- the mono
     token below (as the fallback behind the shipped face), the reader's
     system-font switch, and mono_font="system" -- and three hand-copied stacks
     drift apart. Internal, not a host hook: a host swaps the mono face through
     --kpress-host-font-mono, which leads every one of those three rules. */
  --kpress-mono-stack-platform: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* "Planetaire Mono Text" leads: B612 Mono's letterforms with Hack's
     punctuation and symbols, vendored as latin subsets by
     devtools/subset_mono.py. Which of its styles a document actually declares
     is RenderOptions.mono_weights; whether it declares any is
     RenderOptions.mono_font, whose "system" value swaps this token for the
     platform stack alone and drops the faces from the manifest. */
  --kpress-font-mono: var(
    --kpress-host-font-mono,
    "Planetaire Mono Text",
    var(--kpress-mono-stack-platform)
  );
  --kpress-font-footnote: var(--kpress-host-font-footnote, var(--kpress-font-sans));
  --kpress-font-table: var(--kpress-host-font-table, var(--kpress-font-sans));
  /* The punctuation slot: the family that answers the quote and apostrophe code
     points, and nothing else. It leads --kpress-font-prose because PT Serif
     draws those six glyphs badly -- at 18px its opening pair hangs 2px above
     its closing pair and its curly doubles are 16% wider than Georgia's, which
     is why KPress borrowed Georgia's marks through a local() face for years.
     Borrowing made a document's punctuation depend on the reader's machine and
     could not be embedded in a PDF, so the marks are shipped instead: six
     glyphs of Source Serif 4, generated by devtools/subset_quotes.py. See
     kpress-design.md "Quotation Marks".

     A host swaps the marks for another family, or drops them by naming its own
     reading face, in one line:

       --kpress-host-font-punctuation: "PT Serif"; */
  --kpress-font-punctuation: var(--kpress-host-font-punctuation, "KPress Quotes");
  --kpress-font-features-sans: normal;

  /* Regular is the build-time input to instance_sans + katex_text_metrics.
     Regenerate both tools after changing it so math outlines and metrics agree. */
  --kpress-font-weight-sans-regular: 410;
  --kpress-font-weight-sans-light: var(--kpress-font-weight-sans-regular);
  --kpress-font-weight-sans-medium: 550;
  --kpress-font-weight-sans-bold: 650;
  --_kpress-font-weight-prose: 400;

  /* Sizes. One ramp for prose + UI; the mono sizes are a touch smaller so code
     aligns visually with surrounding text. `normal` is the base itself --
     derived, not a second knob: hosts set the base, not normal. */
  --kpress-font-size-large: calc(var(--kpress-font-size-base) * 1.2);
  --kpress-font-size-normal: var(--kpress-font-size-base);
  --kpress-font-size-small: calc(var(--kpress-font-size-base) * 0.95);
  --kpress-font-size-smaller: calc(var(--kpress-font-size-base) * 0.9);
  --kpress-font-size-tiny: calc(var(--kpress-font-size-base) * 0.85);
  /* The mono rung: 0.82 of the prose size, a judgement about how code should read
     beside prose rather than a number the ink forces. What the ink gives is the
     scale to make that judgement on. Planetaire Mono Text draws an x-height of
     1120/2000 = 0.560 em against PT Serif's 500/1000 = 0.500, so parity of
     x-height -- code and prose with the same visual height of lowercase -- would
     want 0.500/0.560 = 0.893. At 0.82 code's x-height is 0.82 x 0.560 = 0.459 em,
     91.8% of the prose x-height beside it: deliberately under parity, so a code
     span reads as an inset in the line rather than competing with it. 0.87 sat at
     97.4%, near enough to parity that code read too large beside the prose; 0.82 is
     the owner's decision of 2026-09-08.

     Width follows from the same number: Planetaire advances 1204/2000 = 0.602 em
     per column, so 45 / (0.82 x 0.602) = 91.16, and 91 columns fit the
     --kpress-measure reading column -- six more than 0.87 allowed.
     `tests/test_mono_face.py` re-derives both numbers from the shipped faces, so
     the ratio's consequences cannot drift from the ink even though the ratio itself
     is a choice.

     A host retunes the three rungs together through --kpress-host-font-size-mono,
     the one sizing hook beside --kpress-host-font-size-base: small and tiny derive
     from the mono rung rather than from the base, so a host that sets it keeps the
     internal proportions instead of forking three tokens.

     The two ramps pair by index -- mono/normal, mono-small/smaller,
     mono-tiny/tiny -- so the small and tiny multipliers are the prose ramp's own
     steps, 0.9 and 0.85. That is what makes the ratio hold: 0.82 x 0.9 / 0.9 is
     0.82 again, so all three rungs sit at the same 91.8% of the prose x-height
     beside them instead of only the first one. They were 0.915 and 0.855, the
     pre-Planetaire absolutes rescaled (0.75/0.82, 0.70/0.82) and tuned for the
     system monos this face replaced, which left small and tiny above the rung they
     sit under instead of level with it. */
  --kpress-font-size-mono: var(
    --kpress-host-font-size-mono,
    calc(var(--kpress-font-size-base) * 0.82)
  );
  --kpress-font-size-mono-small: calc(var(--kpress-font-size-mono) * 0.9);
  --kpress-font-size-mono-tiny: calc(var(--kpress-font-size-mono) * 0.85);

  /* KaTeX sizing. Context-relative (em) on purpose, like the sup/sub sizes the
     policy above exempts and for the same reason: these are ratios against
     whatever face surrounds the math, not another rung of the base-derived ramp.
     KaTeX ships `.katex { font-size: 1.21em }`, a compensation tuned so its own
     KaTeX_Main sits right beside a Times-like body face. PT Serif -- the prose
     face here -- carries a notably larger x-height, so 1.21 reads about a fifth
     too big against it, and further out of scale inside the small Source Sans
     contexts (figure captions, section labels, tab labels), which start below
     the prose size to begin with. Display math keeps a little more of the
     upstream lift because it stands alone rather than sitting in a line of
     text. See components.css "KaTeX sizing" for where each one applies. */
  --kpress-katex-size-prose: 1.05em;
  --kpress-katex-size-sans: 1em;
  --kpress-katex-size-display: 1.1em;

  /* Prose section-heading sizes (H2-H4; H1 and H5/H6 keep their literals).
     These narrow/medium defaults step up on wide panes only (the >=64rem
     container band in components.css); phones and embedded panes keep the
     tighter scale. Body text does not scale with pane width — only the
     section headings do. */
  --kpress-font-size-h2: calc(var(--kpress-font-size-base) * 1.32);
  --kpress-font-size-h3: calc(var(--kpress-font-size-base) * 1.15);
  --kpress-font-size-h4: calc(var(--kpress-font-size-base) * 1.12);

  /* Line heights. */
  --kpress-line-height-normal: 1.5;
  --kpress-line-height-tight: 1.2;

  /* Section-label caps. Section labels use real uppercase, not small-caps. Our
     subset Source Sans may not ship true `smcp` glyphs, so `font-variant:
     small-caps` synthesized thin, mis-sized caps that the 1.42 multiplier then
     propped up. Uppercase renders the font's real capital glyphs at exactly the
     size we ask for, so no compensation is needed and the multiplier is 1 (kept as
     a tunable dial, not removed, so the `calc(base * multiplier)` consumers don't
     all have to change). */
  --kpress-caps-transform: uppercase;
  --kpress-caps-variant: normal;
  --kpress-caps-spacing: 0.021em;
  --kpress-caps-heading-size-multiplier: 1;
  --kpress-caps-heading-line-height: calc(1.2 / var(--kpress-caps-heading-size-multiplier));
  /* One canonical uppercase section-label size (TOC title, metadata summary, tab
     labels, diagnostics) so they all match instead of drifting. */
  --kpress-caps-label-size: calc(
    var(--kpress-font-size-smaller) *
    var(--kpress-caps-heading-size-multiplier)
  );

  /* ========================= 2. SPACING & LAYOUT ========================= */
  /* Prose reading measure: the width of the TEXT itself, padding excluded.
     Every element that bounds the reading column derives from this token and
     the inset below -- see "the width chain" in components.css. It is the one
     knob a host turns to change how wide documents read.

     Derived from the type base rather than the root, `calc(base * 45)`, so the
     column tracks the text it holds: a host that pins
     --kpress-host-font-size-base gets the same characters per line at any size,
     where a root-relative length would hold the pixels and change the line.
     Written as calc(base * N) rather than `45em` deliberately -- an `em` in a
     custom property resolves at each CONSUMING element, and this token is read
     on .kpress-doc, .kpress-doc-layout and .kpress-long-text, which do not
     share a font-size. calc(base * N) is the same idiom the type ramp uses and
     resolves to one length everywhere.

     45 is a character-count decision, converted. Measured over 507 characters
     of representative English prose at 100px (the average glyph advance, not
     the `ch` unit -- `ch` is the advance of "0" and overstates a proportional
     face by ~23%):

       PT Serif (prose default)  0.4335 em/char -> 45em reads ~104 chars
       Source Sans 3 (sans mode) 0.4039 em/char -> 45em reads ~111 chars

     Eight common proportional text faces (PT Serif, Source Sans, Georgia,
     Times, system-ui, Helvetica, Palatino, Charter) span 0.392-0.434 em/char,
     so ~45em lands in the 104-115 character range for any of them. KPress does
     not carry per-face constants: a host may swap the face through
     --kpress-host-font-prose, which would invalidate them. A host that wants a
     true character count measures its own pinned face and sets this token. */
  --kpress-measure: calc(var(--kpress-font-size-base) * 45);
  /* Inline padding inside the reading column, between the text and the content
     card's edge. Band-dependent (the three bands reset it in components.css),
     and paired with the measure everywhere the column is bounded: a cap that
     forgot to add it is what made the measure mean "text" in one band and
     "text + padding" in another. Declared here on the foundation scope and
     reset per band on .kpress-doc -- the same element as .kpress, so the reset
     wins on cascade order (components.css loads after this file) -- and it
     inherits down to the layout and the column, which read the one value. */
  --kpress-column-inset: 1.5rem;
  /* Inline padding on the article, outside the column: the page gutter. Paired
     with the measure in .kpress-doc's cap for the same reason as the inset. */
  --kpress-doc-gutter: 1rem;
  --kpress-page-margin-inline: 2rem;
  --kpress-page-margin-block-start: 2.5rem;
  /* Settings gear position (standalone page shell only). The gear is `position:
     fixed`, pinned to the top-right of the document frame; these two tokens are
     its block/inline insets. Host-overridable through the `--kpress-host-*`
     hooks so a site can move it without touching kpress -- e.g. align it to the
     content column's right edge instead of flush to the window, by setting on :root
       --kpress-host-settings-inset-inline: max(3rem, calc(50vw - 24rem));
     (24rem = half the 48rem --kpress-measure; use the literal on :root, where
     --kpress-measure is not in scope. See kpress-design.md -> Settings menu.) */
  --kpress-settings-inset-block: var(--kpress-host-settings-inset-block, 0.75rem);
  --kpress-settings-inset-inline: var(--kpress-host-settings-inset-inline, 0.75rem);
  /* Document-actions cluster position (doc-actions widget; off by default).
     Defaults to the slot just inline-left of the settings gear (28px button
     + 12px gap = 2.5rem); host-overridable through the --kpress-host-* hooks,
     same pattern as the settings insets above. */
  --kpress-doc-actions-inset-block: var(
    --kpress-host-doc-actions-inset-block,
    var(--kpress-settings-inset-block)
  );
  --kpress-doc-actions-inset-inline: var(
    --kpress-host-doc-actions-inset-inline,
    calc(var(--kpress-settings-inset-inline) + 2.5rem)
  );
  /* Doc-actions badge: the format letters are the caps-label idiom one notch
     down — slightly smaller than the TOC "Contents" label — so the badge
     reads as an icon, not a heading. The frame corner is square by default
     to match the document's square corners. Both are host-tunable. */
  --kpress-doc-actions-badge-size: calc(var(--kpress-caps-label-size) * 0.85);
  --kpress-doc-actions-badge-radius: var(--kpress-radius-none);
  /* Inline gutter the document reserves for the floating TOC toggle in the
     narrow band. Deliberately a little tighter than the toggle's full
     footprint (0.75rem inset + 32px button): the toggle carries a translucent
     document-background fill, so a slight overlap with the text gutter stays
     legible while phones keep more reading width. A host whose pane inset
     already contributes to the gutter shrinks the reservation by redeclaring
     this on an element scope. See components.css -> .kpress-doc. */
  --kpress-toc-toggle-clearance: 2rem;
  /* Task-list checkbox size: slightly larger than body text so the check glyph
     is clearly legible at a glance and not confused with a bullet. */
  --kpress-checkbox-size: 1.15em;
  /* Square list bullet -- one size for prose lists, claim markers, and concept
     items, so every bulleted list matches. This is the ::before's font size, and
     the square is 0.226em of it: the marker is DRAWN rather than set, so the
     token now controls the mark exactly instead of choosing a size at which some
     fallback face's U+25AA would be drawn (see document.css). NOTE for diverging
     hosts: the marker OFFSETS track the base, not this token, so a bullet size
     far from the default shifts the square against the first line. */
  --kpress-bullet-size: calc(var(--kpress-font-size-base) * 0.9);

  /* ============================== 3. SHAPE ============================== */
  /* Corner radii -- one scale used everywhere. Rounded-vs-square is a deliberate
     per-surface choice (use --kpress-radius-none for square). */
  --kpress-radius-none: 0;
  --kpress-radius-sm: 3px;
  --kpress-radius-md: 6px;
  --kpress-radius-lg: 0.5rem;
  --kpress-radius-pill: 999px;

  /* ============================== 4. MOTION ============================= */
  /* Shared motion. One easing; a fast transition for hovers and size/shape
     changes, a slower fade for overlays. Suppressed under prefers-reduced-motion
     (see the reduce block in components.css). */
  --kpress-ease: ease-in-out;
  --kpress-transition-fast: 0.15s var(--kpress-ease);
  --kpress-transition-med: 0.2s var(--kpress-ease);
  --kpress-transition-slow: 0.4s var(--kpress-ease);
  --kpress-transition-fade: 0.3s var(--kpress-ease);

  /* ========================= 5. CHROME (card) ========================== */
  /* Content card chrome: the reading column rendered as a bordered sheet
     floating over the page (see components.css "Content card" and
     kpress-design.md). Border + shadow are textpress's values verbatim; the
     dark themes deepen the shadow (overridden in the dark palette block). */
  --kpress-card-border: 1px solid var(--kpress-doc-border);
  --kpress-card-shadow:
    0 10px 15px -3px oklch(0.00% 0 0 / 0.1), 0 -2px 6px -1px oklch(0.00% 0 0 / 0.07);

  /* ============================== 6. PRINT ============================= */
  --kpress-print-page-margin: 0.7in;
  --kpress-print-font-size: 11pt;
  --kpress-print-footer: "Formatted by KPress";

  /* =============== 7. COLOR -- theme-independent roles ================= */
  /* Most color roles (--kpress-doc-bg/text/muted/border/link/code-bg + the
     surface hover/selected steps, success, danger) are set per theme x palette in
     the "Color -- palette x theme" section below, on :root (standalone) and
     .kpress (embedded), so they cascade to the whole shell (gear, tooltips, TOC)
     with no host-var / literal-fallback indirection that could silently theme-swap
     on a typo. The two roles below do NOT vary by theme, so they live here:
       accent     -- a fixed teal used for small accents
       surface-bg -- the subtle fill shared by code blocks, table headers, and the
                     metadata block (an alias of the code bg) so they read as one
                     family; the hover/selected interaction steps that DO vary are
                     set per palette below. */
  --kpress-doc-accent: oklch(51.09% 0.0861 186.4);
  --kpress-doc-surface-bg: var(--kpress-doc-code-bg);

  /* ===================== 8. COMPATIBILITY ALIASES ===================== */
  /* textpress/kash vocabulary mapped onto the --kpress-* tokens above, so styles
     ported from kash keep working. These are ALIASES, not new tokens -- add new
     design values as --kpress-* and alias here only if a ported consumer needs it. */
  --font-sans: var(--kpress-font-sans);
  --font-serif: var(--kpress-font-prose);
  --font-mono: var(--kpress-font-mono);
  --font-features-sans: var(--kpress-font-features-sans);
  --font-weight-sans-medium: var(--kpress-font-weight-sans-medium);
  --font-weight-sans-bold: var(--kpress-font-weight-sans-bold);
  --font-size-large: var(--kpress-font-size-large);
  --font-size-normal: var(--kpress-font-size-normal);
  --font-size-small: var(--kpress-font-size-small);
  --font-size-smaller: var(--kpress-font-size-smaller);
  --font-size-tiny: var(--kpress-font-size-tiny);
  --font-size-mono: var(--kpress-font-size-mono);
  --font-size-mono-small: var(--kpress-font-size-mono-small);
  --font-size-mono-tiny: var(--kpress-font-size-mono-tiny);
  --line-height-normal: var(--kpress-line-height-normal);
  --line-height-tight: var(--kpress-line-height-tight);
  --caps-transform: var(--kpress-caps-transform);
  --caps-caps-variant: var(--kpress-caps-variant);
  --caps-spacing: var(--kpress-caps-spacing);
  --caps-heading-size-multiplier: var(--kpress-caps-heading-size-multiplier);
  --caps-heading-line-height: var(--kpress-caps-heading-line-height);
  --color-bg: var(--kpress-doc-bg);
  --color-bg-solid: var(--kpress-doc-bg);
  --color-bg-alt: var(--kpress-doc-surface-bg);
  --color-bg-alt-solid: var(--kpress-doc-surface-bg);
  --color-bg-meta-solid: var(--kpress-doc-surface-bg);
  --color-bg-selected: var(--kpress-doc-surface-selected);
  --color-text: var(--kpress-doc-text);
  --color-primary: var(--kpress-doc-link);
  --color-primary-light: color-mix(in srgb, var(--kpress-doc-link) 72%, white);
  --color-secondary: var(--kpress-doc-muted);
  --color-hint: var(--kpress-doc-muted);
  --color-hint-strong: var(--kpress-doc-muted);
  /* Faint, neutral-reading hairline for inline code, code blocks, tooltips and
     the key-claims/concepts boxes -- modeled on textpress's `hint_gentle`
     (~rgba(155,172,175,0.2)). Derived from the muted gray (not the lighter
     blue-tinted doc-border) at low opacity so it reads gray, not blue. */
  --color-hint-gentle: color-mix(in srgb, var(--kpress-doc-muted) 18%, transparent);
  --color-border-hint: var(--kpress-doc-border);
  --color-border-hairline: var(--kpress-doc-border-hairline);
  --color-hover-bg: var(--kpress-doc-surface-hover);
  --color-hover: var(--kpress-doc-border);
  --color-selection: color-mix(in srgb, var(--kpress-doc-link) 20%, transparent);
  --color-scrollbar: color-mix(in srgb, var(--kpress-doc-muted) 45%, transparent);
  --color-scrollbar-hover: color-mix(in srgb, var(--kpress-doc-muted) 70%, transparent);
  --color-success: var(--kpress-doc-success);
  --color-danger: var(--kpress-doc-danger);
}

/* ===================== DISPLAY/PAGE SWITCHES ========================= */

/* Global font switch. Default ("custom", set by render.py on the .kpress
   wrapper) uses the vendored PT Serif / Source Sans reader faces below.
   "system" opts out of the vendored faces and uses the platform system
   font stack, so no reader fonts are downloaded. Switchable via
   RenderOptions.font_mode at render time, and per reader via the settings
   widget's font-set chooser, which stamps data-kpress-font-set on <html>
   (persisted as `kpress.fontSet`; the pre-paint bootstrap re-applies it). */
.kpress[data-kpress-fonts="system"],
[data-kpress-font-set="system"] .kpress,
[data-kpress-font-set="system"] .kpress-page-main,
[data-kpress-font-set="system"] .kpress-tooltip {
  --kpress-font-body: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --kpress-font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --kpress-font-prose: Georgia, "Times New Roman", Times, serif;
  --kpress-font-punctuation: Georgia, "Times New Roman", Times, serif;
  /* The host hook still leads. This block opts a reader out of the fonts KPress
     downloads; it is not a statement about the face a host installed itself, and
     dropping the var() here would silently retire a documented public seam on a
     control the reader owns. Same shape as the mono_font block below. */
  --kpress-font-mono: var(--kpress-host-font-mono, var(--kpress-mono-stack-platform));
}

/* Mono face switch. Default ("planetaire", the absent attribute) sets code in
   the vendored Planetaire Mono Text subsets. "system" hands code back to the
   platform stack, and the render drops every Planetaire face from the asset
   manifest, so a page under this setting neither fetches nor inlines one. Set by
   RenderOptions.mono_font / `format.mono_font`, stamped as data-kpress-mono-font
   on <html> by the standalone page shell; an embedding host stamps it on its own
   root and passes the same value to the render.

   This is deliberately separate from the font mode above. font_mode="system" is a
   display switch a reader can flip and leaves the asset set alone; mono_font is a
   publishing decision that changes what ships. Either one alone puts code in the
   platform mono. */
.kpress[data-kpress-mono-font="system"],
[data-kpress-mono-font="system"] .kpress,
[data-kpress-mono-font="system"] .kpress-page-main,
[data-kpress-mono-font="system"] .kpress-tooltip {
  --kpress-font-mono: var(--kpress-host-font-mono, var(--kpress-mono-stack-platform));
}

/* Reading-font chooser (settings widget): "sans" flips the prose body from the
   serif reading face to the sans stack, leaving headings/UI/code untouched.
   Stamped as data-kpress-prose-font on <html> (persisted as `kpress.proseFont`;
   pre-paint applies it before first paint). Hosts may point the sans prose
   stack anywhere via --kpress-host-font-prose-sans. */
[data-kpress-prose-font="sans"] .kpress,
[data-kpress-prose-font="sans"] .kpress-page-main,
[data-kpress-prose-font="sans"] .kpress-tooltip,
.kpress[data-kpress-prose-font="sans"] {
  --_kpress-font-weight-prose: var(--kpress-font-weight-sans-regular);
  --kpress-font-prose: var(--kpress-host-font-prose-sans, var(--kpress-font-sans));
}

/* ============================================================================
   COLOR -- PALETTE x THEME -- the single source of truth for every theme color.

   Each theme x palette block sets the document color tokens (--kpress-doc-*)
   DIRECTLY to literal values -- no host-var / fallback indirection, so a typo
   renders obviously broken instead of silently swapping themes.

   SELECTOR GRAMMAR (the one theme vocabulary): CSS reads exactly ONE input,
   `data-kpress-resolved-theme="light|dark"` -- the resolver's OUTPUT. The mode
   attribute (data-kpress-theme: system|light|dark) is resolver state and never
   keys CSS (enforced by devtools/public_hygiene.py). Every keyed state uses the
   same three selector forms, in one rule:

     :root:where([state])            -- the standalone/host shell: tokens on the
                                        root so body-level chrome (video popover,
                                        future overlays) inherits them; :where()
                                        keeps it at :root's own specificity.
     :where([state]) :is(scopes)     -- ANY ancestor theme scope (:root or a host
                                        wrapper), flattened to (0,1,0) so it wins
                                        over the unkeyed defaults by source order
                                        alone.
     :is(scopes)[state]              -- the ELEMENT scope, (0,2,0): an element's
                                        own attribute beats any ancestor state,
                                        so a stamped element is a coherent theme
                                        island in either direction.

   Light and dark are keyed SYMMETRICALLY (light re-asserts its values; the
   unkeyed defaults remain as the no-attribute fallback, identical to light).
   `color-scheme` is declared in an adjacent rule keyed identically (component
   scopes only -- KPress never restyles a host root's UA behavior), so palette
   and color-scheme cannot resolve to different themes. Palette forms are
   flattened with :where() except the explicit palette x theme element forms,
   so the theme axis outranks a lone palette attribute across scopes. Combined
   states (palette + theme) bind fully when both attributes sit on the SAME
   scope element; splitting axes across scopes intentionally resolves theme
   first. Order in this file is load-bearing: defaults -> light -> warm ->
   dark -> warm-dark. `neutral` is the default (bare, no [data-kpress-palette]);
   each other palette overrides ONLY the tokens that differ. An embedding host
   overrides any --kpress-doc-* directly, or stamps the attributes and lets
   these blocks resolve. Add a palette = add its two rules.

   ROLE ORDER within each block (a color family taxonomy -- the same grouping the
   playground palette editor uses): foreground (text, muted) -> hairline (border)
   -> interactive (link) -> surfaces (bg, code-bg, surface-hover, surface-selected)
   -> status (success, danger).

   Design: `warm` is modeled on textpress/kash and is GENTLE. The warmth is ONLY
   the cream code/surface background (hue ~83). Text, muted, border and link stay
   COOL (teal, hue ~208) at neutral's exact lightness -- so text is plain near-black
   (never tan) and borders are a quiet hairline (never warm). Warm reads as
   "neutral with a teal accent over warm paper," not a tan wash.
   ============================================================================ */

/* neutral -- light (default; also the pre-JS / no-attribute state). :root drives
   the standalone shell; .kpress drives the embedded fragment. */
:root,
.kpress {
  --kpress-doc-text: oklch(21.01% 0.0318 264.7);
  --kpress-doc-muted: oklch(50.06% 0.025 259.2);
  --kpress-doc-border: oklch(89.90% 0.0151 260.7);
  /* A crisp near-ink rule for deliberate frames (boxed-text): kash's
     border-hairline (hsl 188 2% 34%) was a *dark* thin line, not a faint one. */
  --kpress-doc-border-hairline: oklch(42.00% 0.006 260.7);
  --kpress-doc-link: oklch(45.76% 0.1445 254.7);
  --kpress-doc-bg: oklch(100.00% 0 0);
  --kpress-doc-code-bg: oklch(97.85% 0.0045 258.3);
  --kpress-doc-surface-hover: color-mix(in srgb, var(--kpress-doc-link) 8%, transparent);
  --kpress-doc-surface-selected: color-mix(in srgb, var(--kpress-doc-link) 10%, transparent);
  --kpress-doc-success: oklch(52.73% 0.1371 150.1);
  --kpress-doc-danger: oklch(50.03% 0.1821 29.5);
}

/* Default color-scheme, component scopes only (never a host's root). */
.kpress,
.kpress-page-main,
.kpress-tooltip {
  color-scheme: light;
}

/* neutral -- light, keyed: the symmetric twin of the dark block below. Values
   MUST mirror the unkeyed defaults above (plus the light card shadow from the
   Chrome section), so a stamped light element inside a dark scope renders as a
   coherent light island instead of a specificity accident. */
:root:where([data-kpress-resolved-theme="light"]),
:where([data-kpress-resolved-theme="light"]) :is(.kpress, .kpress-page-main, .kpress-tooltip),
:is(.kpress, .kpress-page-main, .kpress-tooltip)[data-kpress-resolved-theme="light"] {
  --kpress-doc-text: oklch(21.01% 0.0318 264.7);
  --kpress-doc-muted: oklch(50.06% 0.025 259.2);
  --kpress-doc-border: oklch(89.90% 0.0151 260.7);
  --kpress-doc-border-hairline: oklch(42.00% 0.006 260.7);
  --kpress-doc-link: oklch(45.76% 0.1445 254.7);
  --kpress-doc-bg: oklch(100.00% 0 0);
  --kpress-doc-code-bg: oklch(97.85% 0.0045 258.3);
  --kpress-doc-surface-hover: color-mix(in srgb, var(--kpress-doc-link) 8%, transparent);
  --kpress-doc-surface-selected: color-mix(in srgb, var(--kpress-doc-link) 10%, transparent);
  --kpress-doc-success: oklch(52.73% 0.1371 150.1);
  --kpress-doc-danger: oklch(50.03% 0.1821 29.5);
  --kpress-card-shadow:
    0 10px 15px -3px oklch(0.00% 0 0 / 0.1), 0 -2px 6px -1px oklch(0.00% 0 0 / 0.07);
}

:where([data-kpress-resolved-theme="light"]) :is(.kpress, .kpress-page-main, .kpress-tooltip),
:is(.kpress, .kpress-page-main, .kpress-tooltip)[data-kpress-resolved-theme="light"] {
  color-scheme: light;
}

/* warm -- light values: teal accent + cream surface (textpress/kash). Overrides
   only the cool-identity tokens -- to teal (hue ~208) at neutral's exact
   lightness, so text stays near-black and the border stays a quiet hairline --
   plus the code surface to cream (hue ~83). bg, surfaces, success, danger
   inherit neutral. The lone-element form is :where()-flattened so the dark
   block below outranks a palette attribute split across scopes; the explicit
   warm+light element form binds the combined state at full strength. */
:root:where([data-kpress-palette="warm"]),
:where([data-kpress-palette="warm"]) :is(.kpress, .kpress-page-main, .kpress-tooltip),
:is(.kpress, .kpress-page-main, .kpress-tooltip):where([data-kpress-palette="warm"]),
:is(
  .kpress,
  .kpress-page-main,
  .kpress-tooltip
)[data-kpress-palette="warm"][data-kpress-resolved-theme="light"] {
  --kpress-doc-text: oklch(21.01% 0.022 208);
  --kpress-doc-muted: oklch(50.06% 0.022 208);
  --kpress-doc-border: oklch(89.90% 0.012 208);
  --kpress-doc-border-hairline: oklch(42.00% 0.006 208);
  --kpress-doc-link: oklch(56.60% 0.061 208);
  /* A mild cream -- kept very light (matches textpress's near-white code surface)
     so monospace spans and tooltips read as a gentle tan, not a heavy one. */
  --kpress-doc-code-bg: oklch(98.40% 0.005 83);
}

/* neutral -- dark. */
:root:where([data-kpress-resolved-theme="dark"]),
:where([data-kpress-resolved-theme="dark"]) :is(.kpress, .kpress-page-main, .kpress-tooltip),
:is(.kpress, .kpress-page-main, .kpress-tooltip)[data-kpress-resolved-theme="dark"] {
  --kpress-doc-text: oklch(94.40% 0.0097 252.8);
  --kpress-doc-muted: oklch(76.58% 0.0205 252.9);
  --kpress-doc-border: oklch(33.73% 0.0207 254.1);
  --kpress-doc-border-hairline: oklch(80.00% 0.008 254.1);
  --kpress-doc-link: oklch(76.68% 0.1208 255.4);
  --kpress-doc-bg: oklch(18.98% 0.0094 255.6);
  --kpress-doc-code-bg: oklch(24.16% 0.0153 256.8);
  --kpress-doc-surface-hover: color-mix(in srgb, var(--kpress-doc-muted) 85%, var(--kpress-doc-bg));
  --kpress-doc-surface-selected: color-mix(in srgb, var(--kpress-doc-link) 10%, transparent);
  --kpress-doc-success: oklch(72.27% 0.192 149.6);
  --kpress-doc-danger: oklch(59.30% 0.128 29);
  /* Deeper card shadow so the sheet still reads against the dark page. */
  --kpress-card-shadow:
    0 10px 15px -3px oklch(0.00% 0 0 / 0.3), 0 -2px 6px -1px oklch(0.00% 0 0 / 0.2);
}

:where([data-kpress-resolved-theme="dark"]) :is(.kpress, .kpress-page-main, .kpress-tooltip),
:is(.kpress, .kpress-page-main, .kpress-tooltip)[data-kpress-resolved-theme="dark"] {
  color-scheme: dark;
}

/* warm -- dark: same teal accent (lighter) over neutral's dark surfaces. Must
   override the SAME token set as the warm light block so warm light values
   never leak into dark. */
:root:where([data-kpress-resolved-theme="dark"][data-kpress-palette="warm"]),
:where([data-kpress-resolved-theme="dark"][data-kpress-palette="warm"])
  :is(.kpress, .kpress-page-main, .kpress-tooltip),
:is(
  .kpress,
  .kpress-page-main,
  .kpress-tooltip
)[data-kpress-resolved-theme="dark"][data-kpress-palette="warm"] {
  --kpress-doc-text: oklch(94.40% 0.01 208);
  --kpress-doc-muted: oklch(76.58% 0.02 208);
  --kpress-doc-border: oklch(33.73% 0.016 208);
  --kpress-doc-border-hairline: oklch(80.00% 0.007 208);
  --kpress-doc-link: oklch(74.90% 0.07 208);
  --kpress-doc-code-bg: oklch(24.16% 0.012 83);
}

/* ============================== WEBFONTS ============================== */









/* KPress Quotes: six glyphs of Source Serif 4 -- the straight quote and
   apostrophe, the curly singles, the curly doubles -- and nothing else. About
   700 bytes, generated by devtools/subset_quotes.py, and shipped rather than
   borrowed from the reader's Georgia so a document sets the same marks on every
   machine and a printed page embeds them. Provenance: static/fonts/README.md. */


@font-face {
  font-family: "Source Sans 3 Variable";
  font-style: normal;
  font-display: block;
  font-weight: 200 900;
  src: url("../fonts/source-sans-3-latin-wght-normal.7a19a7027e125257.woff2") format("woff2-variations");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
    U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
    U+FFFD;
}


