/*
 * zbase.css — zbifrost-client structural baseline
 *
 * Always injected by bifrost_core.js on startup. Provides only what the
 * rendering runtime NEEDS to function: reset, grid, flex, tabs, nav-pills,
 * spacing helpers, and spinners. No colors. No typography scale. No component
 * aesthetics — those are the app's responsibility.
 *
 * Sections:
 *   0. Base color tokens (SSOT — --color-*)
 *   1. Reset
 *   2. Grid  (zRow / zCol / zCol-auto — minimal, used by zDash)
 *   3. Gutters (zG-0 only)
 *   4. Flex utilities
 *   5. Display helpers (.zD-flex, .zD-block, etc.)
 *   6. Spacing helpers (.zm-*, .zp-*, .zme-*, .zms-*)
 *   7. Tabs (.zTab-content / .zTab-pane / .zActive)
 *   8. Nav (.zNav / .zNav-pills / .zFlex-column)
 *   9. Spinner (.zSpinner-border / .zSpinner-border-sm)
 *  10. Misc runtime helpers (.zVisually-hidden, .zContainer-fluid)
 */

/* ─── 0. Base color tokens — SSOT ──────────────────────────────────────────── */
/* The canonical zCLI palette, mirrored from zSys/formatting/colors.py. Every
 * var(--color-*) reference below (zText-*, zBtn-*, fields, blockquote, links)
 * resolves here so Bifrost output matches the terminal. Apps re-skin by
 * redefining any of these in a later stylesheet (zCanvas / zBrush). */
:root {
  --color-primary:   #A2D46E; /* Intention — the heart of zCLI */
  --color-secondary: #9370DB; /* Validation — structure & elegance */
  --color-info:      #5CA9FF; /* Perception — clarity & understanding */
  --color-success:   #52B788; /* Manifestation — organic & vibrant */
  --color-warning:   #FFB347; /* Caution — warm & approachable */
  --color-error:     #E63946; /* Decisive — clear & strong */

  /* Light hues — pastel fills derived once from the base palette. Reuse anywhere
   * a soft tint of a brand colour is needed (signals, badges, highlights) rather
   * than re-mixing per component. SSOT: change the base above, these follow. */
  --color-primary-light:   color-mix(in srgb, var(--color-primary)   14%, #fff);
  --color-secondary-light: color-mix(in srgb, var(--color-secondary) 14%, #fff);
  --color-info-light:      color-mix(in srgb, var(--color-info)      14%, #fff);
  --color-success-light:   color-mix(in srgb, var(--color-success)   14%, #fff);
  --color-warning-light:   color-mix(in srgb, var(--color-warning)   14%, #fff);
  --color-error-light:     color-mix(in srgb, var(--color-error)     14%, #fff);

  /* Default document/body text — page-level ink for bare zText, <p>, headings
   * (zH0–zH6), and ul/ol/dl items that carry NO zOS color property. Soft
   * near-black (never pure #000) so it reads deliberately on light doc pages.
   * Dark CARDS (zTable/zTerminal) set their own --ink; a dark theme re-skins by
   * redefining --text once. SSOT for "the default text colour". */
  --text:          #1d2230;

  /* Opaque page background — SSOT for "what colour is the page behind the
   * chrome". A FLOATING bar (sticky global navbar, dropdowns) mixes its own
   * frosted surface from THIS, never from currentColor — currentColor is the
   * page ink, so a transparent bar would let dark cards / dark sections bleed
   * straight through and the links vanish. Dark theme / apps re-skin once. */
  --page-bg:       #ffffff;

  /* Neutral foundation — surfaces, ink & lines on the dark zOS canvas.
   * SSOT for every NON-accent value so no component carries raw rgba/hex.
   * Apps re-skin the whole chrome by redefining these once. */
  --ink:           #f9fafb;                   /* primary text on dark   */
  --ink-muted:     rgba(255, 255, 255, 0.65); /* secondary / captions   */
  --ink-faint:     rgba(255, 255, 255, 0.45); /* placeholders / hints   */
  --surface-1:     rgba(255, 255, 255, 0.04); /* raised panel           */
  --surface-2:     rgba(255, 255, 255, 0.08); /* input / hover fill     */
  --border:        rgba(255, 255, 255, 0.12); /* hairline divider       */
  --border-strong: rgba(255, 255, 255, 0.18); /* field / control outline*/
  --shadow:        rgba(0, 0, 0, 0.20);       /* ambient drop shadow    */
  --surface-base:  #1e1e2e;                   /* opaque dark CARD base — event cards
                                                 (zTable, zTerminal…) read identically
                                                 on a light OR dark page, not page-tied */
  --surface-field: #26263a;                   /* opaque FIELD surface — inputs read as a
                                                 dark island on light pages AND stay
                                                 distinct (a touch lighter) inside dark
                                                 cards. Same balance as --surface-base. */
  --surface-field-focus: #2d2d46;             /* field surface on focus (slightly lifted) */

  /* Canonical spacing scale — SSOT for every margin/padding rhythm across
   * events. Mirrors the zCLI step scale. Events consume these vars directly;
   * nothing hand-applies a utility class. Apps re-rhythm by redefining once. */
  --space-0: 0;
  --space-1: 0.25rem;   /*  4px */
  --space-2: 0.5rem;    /*  8px */
  --space-3: 0.75rem;   /* 12px */
  --space-4: 1rem;      /* 16px */
  --space-5: 1.5rem;    /* 24px */

  /* Heading type scale — SSOT for the zH0–zH6 sizes (zCLI parity). */
  --h0-size: clamp(2.5rem, 5vw, 4rem);
  --h1-size: 2.5rem;
  --h2-size: 2rem;
  --h3-size: 1.75rem;
  --h4-size: 1.5rem;
  --h5-size: 1.25rem;
  --h6-size: 1rem;
}

/* ─── 1. Reset ─────────────────────────────────────────────────────────────── */

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

html {
  font-family: sans-serif;
  line-height: 1.15;
}

body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  overflow-x: hidden;
  font-size: 1rem;
  line-height: 1.5;
  /* Default text colour (SSOT) — bare zText/<p>/headings/ul-ol-dl inherit this
     instead of the UA pure-black. zText-* and dark cards override explicitly. */
  color: var(--text);
}

img, svg, video { display: block; max-width: 100%; }

/* Default link color — zCLI/info palette, overridable via --color-link.
 * Navbar links and buttons opt out below (they manage their own color). */
a { text-decoration: none; color: var(--color-link, #5CA9FF); }

/* Deliberate hover affordance for a content link (a bare zURL <a>): underline
 * on hover, color held. Bifrost owns the interactive state too, not just the
 * resting one — so a link never feels static. Navbar links + buttons opt out
 * (they own their own hover). */
a:hover { text-decoration: underline; }

/* Class-based (.zNavbar, not the <zNavBar> element) so BOTH the global chrome bar
   and the inline block bar opt their links out of the generic content-link color +
   hover-underline identically — SSOT with the navbar rules below. */
.zNavbar a,
.zBtn { color: inherit; }

.zNavbar a:hover,
.zBtn:hover { text-decoration: none; }

/* ─── Custom elements — must be block for layout ───────────────────────────── */

zvaf,
zNavBar,
zBifrostBadge { display: block; }

/* ─── zOS heading scale (zH0–zH6) ──────────────────────────────────────────── */
/* Bifrost owns the heading baseline so a bare <h1>…<h6> renders deliberately,
 * identical to zCLI, with no app CSS. Apps re-skin via _zClass — they never
 * SUPPLY the scale. h0 is the zOS system/hero heading (not in HTML5). */

h0, h1, h2, h3, h4, h5, h6 {
  margin-top: 0;
  margin-bottom: var(--space-2);
  font-weight: 600;
  line-height: 1.2;
}

h0 {
  display: block;
  font-size: var(--h0-size);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

h1 { font-size: var(--h1-size); font-weight: 700; line-height: 1.15; letter-spacing: -0.02em; }
h2 { font-size: var(--h2-size); font-weight: 700; line-height: 1.2;  letter-spacing: -0.01em; }
h3 { font-size: var(--h3-size); }
h4 { font-size: var(--h4-size); }
h5 { font-size: var(--h5-size); }
h6 { font-size: var(--h6-size); }

/* ─── Paragraph (zText default) ────────────────────────────────────────────── */
/* Bifrost owns the <p> baseline so a bare zText carries deliberate vertical
 * rhythm with no app CSS. Apps re-skin via _zClass — they never SUPPLY it. */

p {
  margin-top: 0;
  margin-bottom: var(--space-4);
  line-height: 1.5;
}

p:last-child { margin-bottom: 0; }

/* ─── Inline emphasis (zMD / zText markdown) ───────────────────────────────── */
/* Bifrost owns the inline tags the markdown parser emits (**bold**, *italic*,
 * __underline__, ~~strike~~, ==highlight==) so rich text renders deliberately
 * and themed — never on raw browser UA defaults. Apps re-skin via _zClass. */

strong, b { font-weight: 700; }
em, i     { font-style: italic; }
u         { text-decoration: underline; }
del, s    { text-decoration: line-through; opacity: 0.7; }

mark {
  background: color-mix(in srgb, var(--color-warning, #FFB347) 30%, transparent);
  color: inherit;
  padding: 0.05em 0.25em;
  border-radius: 4px;
}

/* ─── zTheme semantic text colors — zCLI parity ───────────────────────────── */
/* The client maps a header/text `color:` token → `.zText-<token>`. These are
 * the zOS defaults so `color: PRIMARY` paints in Bifrost without any _zClass,
 * mirroring zCLI. Apps override by redefining the --color-* variable (the hex
 * is only a fallback) or with a higher-specificity rule. */

.zText-primary   { color: var(--color-primary,   #A2D46E); }
.zText-secondary { color: var(--color-secondary, #9370DB); }
.zText-success   { color: var(--color-success,   #52B788); }
.zText-info      { color: var(--color-info,      #5CA9FF); }
.zText-warning   { color: var(--color-warning,   #FFB347); }
.zText-danger    { color: var(--color-error,     #E63946); }
.zText-error     { color: var(--color-error,     #E63946); }

/* ─── Inline code chip — zCLI/info palette, overridable via --color-code ───── */
/* Any inline <code> (not inside a <pre> block) gets a subtle monospace badge.
 * Apps override colour with --color-code; block code is left to zCode/Prism. */

code:not(pre code) {
  font-family: 'SF Mono', 'Monaco', 'Courier New', monospace;
  font-size: 0.88em;
  font-weight: 600;
  /* Self-contained chip: opaque light-blue fill + dark-blue ink, anchored to the
     info hue. Fixed contrast (not page-dependent translucency) so it reads on
     light pages, dark pages, AND the dark zTable card. Override via --color-code. */
  color: var(--color-code, color-mix(in srgb, var(--color-info) 52%, #000));
  background: color-mix(in srgb, var(--color-info) 22%, #fff);
  border: 1px solid color-mix(in srgb, var(--color-info) 40%, #fff);
  border-radius: 5px;
  padding: 0.1em 0.4em;
  letter-spacing: -0.01em;
}

/* ─── Blockquote — zMD `> quote` base, overridable via --color-primary ─────── */
/* Subtle theme-tinted background (color-mix) so it sits right on light AND dark
 * surfaces — no hardcoded grey. Apps restyle by redefining --color-primary or
 * with a higher-specificity rule. */

blockquote {
  margin: var(--space-3) 0;
  padding: var(--space-2) var(--space-4);
  border-left: 4px solid var(--color-primary, #A2D46E);
  background: color-mix(in srgb, var(--color-primary, #A2D46E) 8%, transparent);
  border-radius: 0 6px 6px 0;
  color: inherit;
}

blockquote > p { margin: 0; }

/* ─── Preformatted block (zText semantic: pre) ─────────────────────────────── */
/* Plain <pre> baseline — bifrost-owned so a raw preformatted block renders
 * deliberately. Language-tagged pre (pre-zolo, pre-js…) routes through Prism
 * and is left alone. Apps re-skin via _zClass. */

pre {
  margin: var(--space-3) 0;
  padding: var(--space-3) var(--space-4);
  font-family: 'SF Mono', 'Monaco', 'Courier New', monospace;
  font-size: 0.88em;
  line-height: 1.5;
  color: inherit;
  background: color-mix(in srgb, var(--color-code, #5CA9FF) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-code, #5CA9FF) 22%, transparent);
  border-radius: 8px;
  overflow-x: auto;
  white-space: pre;
}

/* ─── Lists (zUL / zOL / zDL) ──────────────────────────────────────────────── */
/* Bifrost owns the list baseline so a bare zUL/zOL/zDL — and every markdown list
 * — renders deliberately and themed, never on raw browser UA defaults. Marker
 * variants are CANONICAL classes (.zList-circle/square/letter/roman) the renderer
 * appends — never inline list-style on the element. Here we own the rhythm,
 * indent, brand-tinted markers, the variants, and the dl term/definition look.
 * Apps re-skin via _zClass — they never SUPPLY the baseline. */

ul, ol {
  margin: 0 0 1rem;
  padding-left: 1.4rem;
  line-height: 1.6;
}
ul:last-child, ol:last-child { margin-bottom: 0; }

li { margin-bottom: 0.4rem; }
li:last-child { margin-bottom: 0; }

/* Markers track the item's own ink via the theme-adaptive frost play (same mix
 * used by the floating nav / breadcrumb) — a muted currentColor that reads on a
 * light OR dark page, where a fixed --text would vanish on the opposite theme. */
li::marker { color: color-mix(in srgb, currentColor 70%, transparent); }

/* Nested lists tuck under their parent item with a single tight gap. */
li > ul, li > ol { margin: 0.4rem 0 0; }

/* Marker variants — canonical, renderer-appended (zList event `style`). The
 * renderer picks <ul> vs <ol>; these only set the marker glyph/sequence. */
.zList-circle { list-style-type: circle; }
.zList-square { list-style-type: square; }
.zList-number { list-style-type: decimal; }
.zList-letter { list-style-type: lower-alpha; }
.zList-roman  { list-style-type: lower-roman; }
.zList-none   { list-style-type: none; padding-left: 0; }

/* Definition list (zDL) — term bold, definition indented. Both inherit the base
   --text colour (no hardcoded --ink, which is the dark-card near-white and would
   vanish on a light doc page). A dark theme re-skins via --text. */
dl { margin: 0 0 1rem; }
dl:last-child { margin-bottom: 0; }
dt { font-weight: 700; }
dd {
  margin: 0 0 0.6rem 1.2rem;
  line-height: 1.6;
}
dd:last-child { margin-bottom: 0; }

/* Inline list (zList-inline) — horizontal, marker-less row of items. */
.zList-inline {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  margin: 0 0 1rem;
  padding-left: 0;
  list-style: none;
}
.zList-inline-item { margin: 0; }

/* ─── Media (zImage / zVideo) ──────────────────────────────────────────────── */
/* Bifrost owns the media baseline. <img>/<video> stay responsive (display:block
 * + max-width:100% in the reset above); <video> keeps its aspect via height:auto.
 * A captioned media renders inside a <figure> — own it so the UA's default
 * `margin: 1em 40px` side-indent never offsets the media. figcaption is themed
 * HERE (SSOT) — the renderer emits a bare <figcaption>, no utility classes. */

video { height: auto; }

figure { margin: 0 0 1rem; }
figure:last-child { margin-bottom: 0; }
figure > img,
figure > video { margin-bottom: 0; }
/* Caption: centred, slightly smaller, dimmed via opacity so it follows the base
 * --text colour on light pages AND a re-skinned --text on dark (no hardcoded ink). */
figcaption {
  margin-top: var(--space-2);
  text-align: center;
  font-size: 0.9em;
  line-height: 1.5;
  opacity: 0.7;
}

/* ─── Form controls — canonical field base (Bootstrap-like) ────────────────── */
/* The client emits a real field as `.zForm-control` (default) or `.zInput`
 * (input-groups / explicit). Style BOTH so a field on ANY page — not just
 * dashboards — gets a proper look instead of the raw browser white box.
 * `.zInput` is element-qualified so a wrapper div carrying `_zClass: zInput`
 * is NOT mistaken for the field. Override via --zinput-* or an app rule. */

.zForm-control,
input.zInput, textarea.zInput, select.zInput {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 0.55rem 0.85rem;
  font-family: inherit;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--zinput-color, var(--ink));
  background: var(--zinput-bg, var(--surface-field));
  border: 1.5px solid var(--zinput-border, var(--border-strong));
  border-radius: var(--zinput-radius, 10px);
  transition: border-color 0.18s, background 0.18s, box-shadow 0.18s;
}

.zForm-control::placeholder,
.zInput::placeholder { color: var(--zinput-placeholder, var(--ink-faint)); }

.zForm-control:focus,
input.zInput:focus, textarea.zInput:focus, select.zInput:focus {
  outline: none;
  border-color: var(--zinput-focus, var(--color-primary));
  background: var(--zinput-bg-focus, var(--surface-field-focus));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--zinput-focus, var(--color-primary)) 20%, transparent);
}

.zForm-control:disabled, .zForm-control[readonly],
input.zInput:disabled, input.zInput[readonly] { opacity: 0.55; cursor: not-allowed; }

textarea.zForm-control, textarea.zInput { min-height: 6rem; resize: vertical; }
select.zForm-control,   select.zInput   { cursor: pointer; }

/* size + type variants */
.zForm-control.zInput-sm, .zInput-sm    { padding: 0.32rem 0.6rem; font-size: 0.82rem; }

/* field label rendered above an input */
.zLabel {
  display: block;
  margin-bottom: 0.2rem;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--zlabel-color, inherit);
}

/* The input handler emits a <br> after each label; suppress it so the gap is
   controlled purely by .zLabel margin-bottom and the field hugs its label. */
.zLabel + br { display: none; }

/* Required-field marker — red asterisk suffix on the label (SSOT). */
.zRequired {
  color: var(--zrequired-color, var(--color-error));
  margin-left: 0.15rem;
  font-weight: 600;
}

/* File selector — native file inputs (emitted as .zForm-control) styled to match
   the theme: muted "no file" text + a secondary-token pill for the browse button. */
input[type="file"].zForm-control,
input[type="file"].zInput {
  display: block;
  width: 100%;
  padding: 0.5rem 0.65rem;
  font-size: 0.9375rem;
  color: var(--zinput-placeholder, var(--ink-muted));
  line-height: 1.6;
  cursor: pointer;
}
input[type="file"].zForm-control::file-selector-button,
input[type="file"].zInput::file-selector-button {
  margin-right: 0.85rem;
  padding: 0.45rem 1rem;
  border: 0;
  border-radius: 8px;
  background: var(--zinput-accent, var(--color-secondary));
  color: #fff;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.05s ease;
}
input[type="file"].zForm-control::file-selector-button:hover,
input[type="file"].zInput::file-selector-button:hover {
  filter: brightness(1.12);
}
input[type="file"].zForm-control::file-selector-button:active,
input[type="file"].zInput::file-selector-button:active {
  transform: translateY(1px);
}

/* Color picker — native color input as a sweet circular swatch that FILLS the
   disc (no frame, no outline). Depth via ambient shadow + an inner gloss.
   Fully token-driven — restyle size/shape/glow via --zswatch-* (SSOT). */
input[type="color"].zForm-control,
input[type="color"].zInput {
  -webkit-appearance: none;
  appearance: none;
  width: var(--zswatch-size, 2.6rem);
  height: var(--zswatch-size, 2.6rem);
  padding: 0;
  border: none;
  background: none;
  border-radius: var(--zswatch-radius, 50%);
  cursor: pointer;
  box-shadow: var(--zswatch-shadow, 0 2px 6px var(--shadow));
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
input[type="color"].zForm-control:hover,
input[type="color"].zInput:hover {
  transform: translateY(-1px);
  box-shadow: var(--zswatch-shadow-hover, 0 4px 10px var(--shadow));
}
input[type="color"].zForm-control:focus,
input[type="color"].zInput:focus {
  outline: none;
  box-shadow: var(--zswatch-shadow, 0 2px 6px var(--shadow)),
              0 0 0 3px color-mix(in srgb, var(--zinput-focus, var(--color-primary)) 25%, transparent);
}
input[type="color"].zForm-control::-webkit-color-swatch-wrapper,
input[type="color"].zInput::-webkit-color-swatch-wrapper { padding: 0; }
input[type="color"].zForm-control::-webkit-color-swatch,
input[type="color"].zInput::-webkit-color-swatch {
  border: none;
  border-radius: var(--zswatch-radius, 50%);
  box-shadow: var(--zswatch-gloss,
              inset 0 2px 3px rgba(255, 255, 255, 0.35),
              inset 0 -2px 4px rgba(0, 0, 0, 0.12));
}
input[type="color"].zForm-control::-moz-color-swatch,
input[type="color"].zInput::-moz-color-swatch {
  border: none;
  border-radius: var(--zswatch-radius, 50%);
  box-shadow: var(--zswatch-gloss,
              inset 0 2px 3px rgba(255, 255, 255, 0.35),
              inset 0 -2px 4px rgba(0, 0, 0, 0.12));
}

/* Date / time — fields already inherit .zForm-control styling; this only makes
   the native calendar/clock picker indicator legible on dark themes (SSOT). */
input[type="date"].zForm-control::-webkit-calendar-picker-indicator,
input[type="time"].zForm-control::-webkit-calendar-picker-indicator,
input[type="datetime-local"].zForm-control::-webkit-calendar-picker-indicator,
input[type="month"].zForm-control::-webkit-calendar-picker-indicator,
input[type="week"].zForm-control::-webkit-calendar-picker-indicator,
input[type="date"].zInput::-webkit-calendar-picker-indicator,
input[type="time"].zInput::-webkit-calendar-picker-indicator,
input[type="datetime-local"].zInput::-webkit-calendar-picker-indicator,
input[type="month"].zInput::-webkit-calendar-picker-indicator,
input[type="week"].zInput::-webkit-calendar-picker-indicator {
  filter: var(--zinput-picker-filter, invert(0.85));
  opacity: 0.75;
  cursor: pointer;
}
input[type="date"].zForm-control::-webkit-calendar-picker-indicator:hover,
input[type="time"].zForm-control::-webkit-calendar-picker-indicator:hover,
input[type="datetime-local"].zForm-control::-webkit-calendar-picker-indicator:hover,
input[type="month"].zForm-control::-webkit-calendar-picker-indicator:hover,
input[type="week"].zForm-control::-webkit-calendar-picker-indicator:hover,
input[type="date"].zInput::-webkit-calendar-picker-indicator:hover,
input[type="time"].zInput::-webkit-calendar-picker-indicator:hover,
input[type="datetime-local"].zInput::-webkit-calendar-picker-indicator:hover,
input[type="month"].zInput::-webkit-calendar-picker-indicator:hover,
input[type="week"].zInput::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
}

/* input-group affix chips (prefix / suffix) */
.zInputGroup > .zInputGroup-text {
  display: flex;
  align-items: center;
  padding: 0.55rem 0.85rem;
  font-size: 0.95rem;
  color: var(--zinput-color, var(--ink));
  background: var(--zinput-affix-bg, color-mix(in srgb, var(--color-secondary) 22%, var(--surface-field)));
  border: 1.5px solid var(--zinput-border, var(--border-strong));
  white-space: nowrap;
}
.zInputGroup > .zInputGroup-text:first-child { border-radius: 10px 0 0 10px; border-right: 0; }
.zInputGroup > .zInputGroup-text:last-child  { border-radius: 0 10px 10px 0; border-left: 0; }
.zInputGroup > .zForm-control:not(:first-child),
.zInputGroup > .zInput:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.zInputGroup > .zForm-control:not(:last-child),
.zInputGroup > .zInput:not(:last-child)  { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* ─── Checkbox / radio — canonical control base (matches .zForm-control) ───── */
/* The client emits `zCheckbox` / `zSelect` (radio) as a `.zForm-check` row:
 *   <div class="zForm-check"><input class="zForm-check-input"><label class="zForm-check-label">
 * Style the box by element type (so an app's `_zClass` override on the input
 * doesn't strip the look) and theme it via --color-primary, like buttons/fields. */

.zForm-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.zForm-check input[type="checkbox"],
.zForm-check input[type="radio"],
.zForm-check-input {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0;
  display: inline-grid;
  place-content: center;
  background: var(--zinput-bg, var(--surface-field));
  border: 1.5px solid var(--zinput-border, var(--border-strong));
  cursor: pointer;
  transition: border-color 0.18s, background 0.18s, box-shadow 0.18s;
}
.zForm-check input[type="checkbox"],
.zForm-check-input { border-radius: var(--zcheck-radius, 5px); }
.zForm-check input[type="radio"] { border-radius: 50%; position: relative; }

/* checked fill — theme primary */
.zForm-check input[type="checkbox"]:checked,
.zForm-check input[type="radio"]:checked,
.zForm-check-input:checked {
  background: var(--zinput-focus, var(--color-primary));
  border-color: var(--zinput-focus, var(--color-primary));
}
/* checkmark glyph */
.zForm-check input[type="checkbox"]:checked::after,
.zForm-check-input:checked::after {
  content: "";
  width: 0.32rem;
  height: 0.6rem;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) translate(-1px, -1px);
}
/* radio dot — absolutely centered (grid place-content is sub-pixel off on a circle) */
.zForm-check input[type="radio"]:checked::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: #fff;
}

.zForm-check input[type="checkbox"]:focus-visible,
.zForm-check input[type="radio"]:focus-visible,
.zForm-check-input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--zinput-focus, var(--color-primary)) 30%, transparent);
}

.zForm-check-label {
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1.4;
  color: inherit;
  user-select: none;
}

/* disabled — dim box AND label, block interaction (SSOT) */
.zForm-check input:disabled,
.zForm-check-input:disabled { opacity: 0.55; cursor: not-allowed; }
.zForm-check-disabled { opacity: 0.6; }
.zForm-check-disabled .zForm-check-label { cursor: not-allowed; }

/* native radios/checkboxes NOT given the custom .zForm-check treatment
 * still pick up the theme color (belt-and-braces — groups now emit .zForm-check). */
input[type="radio"], input[type="checkbox"] { accent-color: var(--color-primary); }

/* radio / checkbox group (zSelect type:radio|checkbox) — legend + row rhythm */
.zForm-check-group .zLabel { margin-bottom: 0.45rem; }

/* ─── Select / dropdown — canonical control (matches .zForm-control) ───────── */
/* The client emits `zSelect` as a native <select class="zSelect">. Give it the
 * same field look as text inputs plus a themed caret. Element-qualified so a
 * wrapper carrying `_zClass: zSelect` is NOT mistaken for the control. */

select.zSelect,
select.zForm-control,
select.zInput {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 0.55rem 2.25rem 0.55rem 0.85rem;
  font-family: inherit;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--zinput-color, var(--ink));
  background-color: var(--zinput-bg, var(--surface-field));
  border: 1.5px solid var(--zinput-border, var(--border-strong));
  border-radius: var(--zinput-radius, 10px);
  cursor: pointer;
  transition: border-color 0.18s, background 0.18s, box-shadow 0.18s;
  /* themed chevron caret (no external asset) */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 1rem;
}

select.zSelect:focus,
select.zForm-control:focus,
select.zInput:focus {
  outline: none;
  border-color: var(--zinput-focus, var(--color-primary));
  background-color: var(--zinput-bg-focus, var(--surface-field-focus));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--zinput-focus, var(--color-primary)) 20%, transparent);
}

select.zSelect:disabled,
select.zForm-control:disabled,
select.zInput:disabled { opacity: 0.55; cursor: not-allowed; }

/* native option popup — keep legible regardless of page theme */
select.zSelect option,
select.zForm-control option,
select.zInput option { color: #1f2937; background: #fff; }
select.zSelect option:disabled,
select.zForm-control option:disabled { color: var(--ink-muted); }

/* multi-select / sized list box — themed box, no caret, themed inline options */
select.zSelect[multiple],
select.zSelect[size],
select.zForm-control[multiple] {
  padding: 0.4rem;
  height: auto;
  min-height: 0;
  background-image: none;
}
select.zSelect[multiple] option,
select.zSelect[size] option,
select.zForm-control[multiple] option {
  color: var(--zinput-color, var(--ink));
  background: transparent;
  padding: 0.35rem 0.55rem;
  border-radius: 6px;
  margin-bottom: 1px;
}
/* selected rows — gradient hack forces the highlight color in Chromium */
select.zSelect[multiple] option:checked,
select.zSelect[size] option:checked,
select.zForm-control[multiple] option:checked {
  color: #fff;
  background: linear-gradient(var(--color-primary), var(--color-primary));
}
select.zSelect[multiple] option:disabled,
select.zForm-control[multiple] option:disabled { color: var(--zinput-placeholder, var(--ink-faint)); }

/* size variants */
select.zSelect.zSelect-lg { padding-top: 0.7rem;  padding-bottom: 0.7rem;  font-size: 1.05rem; }
select.zSelect.zSelect-sm { padding-top: 0.35rem; padding-bottom: 0.35rem; font-size: 0.82rem; }

zBifrostBadge {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  z-index: 9999;
  pointer-events: none;
}

/* ─── zBifrostBadge states ─────────────────────────────────────────────────── */

.zBadge-pending { color: var(--color-warning); }
.zBadge-success { color: var(--color-success); }
.zBadge-error   { color: var(--color-error); }
.zBadge-dot     { border-radius: 50%; background: currentColor; }
/* Inline SVG sprite icon (e.g. the connection/dev badge dot). Named zSvgIcon to
 * stay distinct from the zIcon EVENT, which renders a Bootstrap <i class="bi">. */
.zSvgIcon       { display: inline-block; width: 0.75em; height: 0.75em; fill: currentColor; }

/* ─── zVaF content host ─────────────────────────────────────────────────────── */
/* The re-rendered subtree under the <zVaF> root. Chrome (navbar/badge/errors) are
 * its siblings inside the root, so clearing this node never destroys them. */
.zVaF-content { display: block; }

/* ─── Error toasts — bifrost-owned fixed-corner container (was inline styles) ── */
.bifrost-error-container {
  position: fixed;
  z-index: 10000;
  max-width: 400px;
  pointer-events: none;
}
.bifrost-error-container--top-right    { top: 1rem; right: 1rem; }
.bifrost-error-container--top-left     { top: 1rem; left: 1rem; }
.bifrost-error-container--bottom-right { bottom: 1rem; right: 1rem; }
.bifrost-error-container--bottom-left  { bottom: 1rem; left: 1rem; }

/* ─── zNavBar — Bifrost-owned default chrome (token-driven, theme-adaptive) ──── */
/* The global bar is STICKY and FLOATS over scrolling content, so it owns a
 * SELF-CONTAINED frosted surface mixed from the opaque --page-bg (NOT
 * currentColor): the surface + ink stay stable no matter what light/dark
 * content scrolls underneath. Light/dark balance comes from the theme flipping
 * --page-bg/--text once — not from content bleed. (The in-block variant below
 * is the SAME look but keeps currentColor on purpose — it sits inside a known
 * host surface and must adapt to it; see the note there.) Brand at full ink,
 * links muted → full on hover, active = secondary-tinted pill. Apps re-skin via
 * --znav-* vars or a higher-specificity rule — no need to re-declare the bar. */

zNavBar {
  position: sticky;
  top: 0;
  z-index: 1000;
  display: block;

  /* Full-bleed self-defense — the navbar is page CHROME and must span the full
     viewport even when its <zVaF> root is clamped to a centered, max-width
     content column (apps routinely cap <zVaF> for a readable measure — e.g. a
     zCanvas `zVaF { max-width }`). Breaking out HERE, canonically, makes
     "navbar = full width" a framework guarantee instead of a per-app override
     fight. The math self-cancels when <zVaF> is already full width
     (50% == 50vw → margin 0), so it's inert on uncapped pages. (100vw counts the
     scrollbar gutter: exact on overlay scrollbars, ~15px over on classic ones —
     fine for chrome; revisit only if it ever yields a sliver of h-scroll.) */
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}

/* ─── zNavbar — SSOT base: BOTH the global chrome bar AND inline block bars ────
   build_nav_html emits class="zNavbar zNavbar-<theme> <variant>" for BOTH bars,
   so ALL shared look (surface, spacing, links) lives on .zNavbar exactly ONCE.
   The two variants below add ONLY their framing delta — they must NEVER
   re-declare color/spacing/link rules:
     • <zNavBar> .zNavbar  → global chrome: bottom-border, no radius (full-bleed +
                             sticky live on the <zNavBar> element above)
     • .zNavbar-inblock    → in-content bar: inherits the whole base look; only
                             neutralises host <li> spacing
   Re-skin by overriding a --znav-* token, never by re-writing these rules. */

.zNavbar {
  /* tokens — the single source of truth. Both bars resolve from --page-bg/--text
     (NOT currentColor) so an inline bar is pixel-identical to the chrome bar.
     Override one of these (not the rules below) to re-skin every navbar at once. */
  --znav-bg: color-mix(in srgb, var(--page-bg) 78%, transparent);
  --znav-border: color-mix(in srgb, var(--text) 10%, transparent);
  --znav-link-color: color-mix(in srgb, var(--text) 66%, transparent);
  --znav-link-hover-color: var(--text);
  --znav-link-hover-bg: color-mix(in srgb, var(--text) 8%, transparent);
  --znav-link-active-bg: color-mix(in srgb, var(--color-secondary) 24%, transparent);
  --znav-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  display: flex;
  align-items: baseline;   /* brand + links share ONE text baseline — keeps all-caps
                              items (FAQ) and the larger brand from floating off-line */
  flex-wrap: wrap;
  padding: 0.75rem 1.5rem;
  color: var(--text);
  background: var(--znav-bg);
  border: 1px solid var(--znav-border);
  border-radius: 10px;
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
  font-family: var(--znav-font);
}

.zNavbar .zNavbar-brand {
  font-size: 1.15rem;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin-right: 2rem;
  color: var(--znav-brand-color, inherit);
}

.zNavbar .zNavbar-toggler {
  display: none;
  margin-left: auto;
  padding: 0.4rem 0.6rem;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  color: var(--znav-brand-color, inherit);
  background: var(--znav-link-hover-bg);
  border: 1px solid var(--znav-border);
  border-radius: 6px;
}

.zNavbar .zNavbar-collapse {
  display: flex;
  align-items: baseline;
  flex: 1;
}

.zNavbar .zNavbar-nav {
  display: flex;
  align-items: baseline;
  gap: 0.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.zNavbar .zNav-item { position: relative; }

.zNavbar .zNav-link {
  display: block;
  padding: 0.45rem 0.85rem;
  font-size: 0.9rem;
  font-weight: 500;
  line-height: 1.1;
  border-radius: 8px;
  color: var(--znav-link-color);
  transition: color 0.2s, background 0.2s;
}

/* hover = neutral --text wash; active = secondary-tinted pill (the pick) */
.zNavbar .zNav-link:hover {
  color: var(--znav-link-hover-color);
  background: var(--znav-link-hover-bg);
}
.zNavbar .zNav-link.active {
  color: var(--znav-link-hover-color);
  background: var(--znav-link-active-bg);
}

/* ─── Variant: global chrome (inside the sticky, full-bleed <zNavBar>) ─────────
   Drop the card framing → the chrome bar is a full-width strip with one hairline
   underneath. Width/sticky come from the <zNavBar> element rule above. */
zNavBar .zNavbar {
  border: 0;
  border-bottom: 1px solid var(--znav-border);
  border-radius: 0;
}

/* ─── zDropdown (navbar menus) — JS toggles .zShow ─────────────────────────── */

.zDropdown-menu {
  display: none;
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  z-index: 100;
  min-width: 11rem;
  padding: 0.35rem;
  background: var(--zdropdown-bg, color-mix(in srgb, var(--page-bg) 92%, transparent));
  border: 1px solid var(--znav-border, color-mix(in srgb, var(--text) 14%, transparent));
  border-radius: 10px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.22);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
}
.zDropdown-menu.zShow { display: block; }

.zDropdown-item {
  display: block;
  padding: 0.5rem 0.85rem;
  font-size: 0.875rem;
  border-radius: 7px;
  color: var(--znav-link-color, color-mix(in srgb, var(--text) 78%, transparent));
  transition: color 0.15s, background 0.15s;
}
.zDropdown-item:hover {
  color: var(--znav-link-hover-color, var(--text));
  background: var(--znav-link-active-bg, color-mix(in srgb, var(--color-secondary) 18%, transparent));
}

/* ─── Variant: in-content block bar (rendered inline, OUTSIDE <zNavBar>) ───────
   A block-level navbar (navbar_inline event — e.g. an in-page table-of-contents)
   lives in normal page content. It already carries .zNavbar, so it inherits the
   ENTIRE base look above (rounded card, same --page-bg surface + link ink as the
   chrome bar — that's the SSOT). Its ONLY delta is content-defence: neutralise
   host prose <li> spacing so embedding inside a .zCallout (whose
   `li { margin: 0 0 .5rem }` + `li:last-child { margin-bottom: 0 }`) can't drop
   the last item off-baseline. Dropdowns reuse the shared .zDropdown-menu rules. */
.zNavbar-inblock .zNav-item { margin: 0; line-height: 1; }

/* ─── Mobile hamburger ─────────────────────────────────────────────────────── */

@media (max-width: 768px) {
  /* Scoped `zNavBar .zNavbar …` (specificity 0,2,1) so it outranks the SSOT base
     `.zNavbar …` (0,2,0) — the hamburger only ever applies to the global chrome
     bar; the inline bar stays one expanded row. */
  zNavBar .zNavbar .zNavbar-toggler { display: block; }
  zNavBar .zNavbar .zNavbar-collapse {
    flex-basis: 100%;
    flex-direction: column;
    align-items: flex-start;
  }
  zNavBar .zNavbar .zNavbar-collapse:not(.zShow) { display: none; }
  zNavBar .zNavbar .zNavbar-collapse.zShow { display: flex; padding: 0.5rem 0 0.75rem; }
  zNavBar .zNavbar .zNavbar-nav { flex-direction: column; width: 100%; gap: 0; }
  zNavBar .zNavbar .zNav-link { width: 100%; }
  .zDropdown-menu {
    position: static;
    box-shadow: none;
    border: none;
    border-radius: 0;
    padding-left: 0.75rem;
    background: var(--znav-link-hover-bg, color-mix(in srgb, currentColor 6%, transparent));
  }
}

/* ─── zCrumbs — breadcrumb trails (session / manual / structure) ───────────────
   ONE style for ALL zCrumbs modes — there is deliberately NO per-type rule. The
   visual variation (clickable ancestor vs quiet/disabled ancestor vs current page)
   is driven entirely by the HTML the renderer emits — <a>, <a aria-disabled>,
   <span>, or the .zActive leaf — never by which crumb mode produced it.

   Look: a rounded "rail" track holds the trail; ancestors are quiet text that warm
   on hover; the current page is the rail's own end SEGMENT — filled with the accent
   and flush to the rail (full height, hugging the rounded end), a background-fill
   feel rather than a pill floating on top. No separators. Theme-adaptive: rail
   surface, ink and hover all derive from currentColor, so this single rule set reads
   on light doc pages AND a dark surface with no light/dark branch. Every crumb kind
   shares ONE box geometry, so the rhythm is perfectly even — only fill + weight
   change for the active. Re-skin geometry/color via --zcrumb-*. */
.zBreadcrumb {
  --zcrumb-radius: 999px;                 /* rail + crumb rounding */
  --zcrumb-pad-y: 0.26rem;                /* crumb box vertical pad */
  --zcrumb-pad-x: 0.6rem;                 /* crumb box horizontal pad */
  --zcrumb-rail-pad-y: 0.22rem;           /* rail inner vertical pad */
  --zcrumb-rail-pad-x: 0.26rem;           /* rail inner horizontal pad */
  display: inline-flex;
  align-items: stretch;                   /* let the active segment fill rail height */
  flex-wrap: wrap;
  margin: 0;
  padding: var(--zcrumb-rail-pad-y) var(--zcrumb-rail-pad-x);
  list-style: none;
  font-size: 0.8rem;
  border-radius: var(--zcrumb-radius);
  background: var(--zcrumb-rail-bg, color-mix(in srgb, currentColor 5%, transparent));
  border: 1px solid var(--zcrumb-rail-border, color-mix(in srgb, currentColor 12%, transparent));
}
/* margin:0 is load-bearing — the global `li { margin-bottom }` otherwise inflates
   the flex line's cross-size, stretching the current-page segment taller than the rail */
.zBreadcrumb-item { display: inline-flex; align-items: center; margin: 0; }
/* uniform crumb box — a, span AND the active leaf share ONE geometry so the rhythm
   reads perfectly even; only fill + weight change for the active page */
.zBreadcrumb-item > a,
.zBreadcrumb-item > span,
.zBreadcrumb-item.zActive {
  display: inline-flex;
  align-items: center;
  padding: var(--zcrumb-pad-y) var(--zcrumb-pad-x);
  line-height: 1.2;
  text-decoration: none;
  border-radius: var(--zcrumb-radius);
  transition: background 0.15s ease, color 0.15s ease;
  color: var(--zcrumb-ink, color-mix(in srgb, currentColor 54%, transparent));
}
/* clickable ancestor (manual zMenu / session hop) */
.zBreadcrumb-item > a:hover {
  background: var(--zcrumb-hover-bg, color-mix(in srgb, currentColor 10%, transparent));
  color: currentColor;
}
/* quiet, non-interactive ancestor (structure / manual zMenu:false / session span) */
.zBreadcrumb-item > a[aria-disabled="true"],
.zBreadcrumb-item > span {
  color: var(--zcrumb-muted, color-mix(in srgb, currentColor 42%, transparent));
  pointer-events: none;
  cursor: default;
}
/* current page (always last): the rail's filled end segment. Bleeds the rail's own
   padding (top / bottom / right, +1px border) so it reaches full rail height and
   hugs the rounded end; flat leading edge so it reads as part of the track, not a
   pill on top. */
.zBreadcrumb-item.zActive {
  font-weight: 650;
  align-self: stretch;
  background: var(--zcrumb-active-bg, var(--color-info, #5CA9FF));
  color: var(--zcrumb-active-ink, #fff);
  margin: calc(-1 * var(--zcrumb-rail-pad-y) - 1px)
          calc(-1 * var(--zcrumb-rail-pad-x) - 1px)
          calc(-1 * var(--zcrumb-rail-pad-y) - 1px) 0;
  padding: calc(var(--zcrumb-pad-y) + var(--zcrumb-rail-pad-y) + 1px) 0.8rem;
  border-radius: 0 var(--zcrumb-radius) var(--zcrumb-radius) 0;
}
/* lone crumb (single-block session / fresh page): no ancestors — fill the WHOLE
   rail, both ends rounded. */
.zBreadcrumb-item:only-child.zActive {
  margin-left: calc(-1 * var(--zcrumb-rail-pad-x) - 1px);
  border-radius: var(--zcrumb-radius);
}

/* ─── Session echo ──────────────────────────────────────────────────────────
   `show: session` is the raw, UNFILTERED mirror of zSession's crumb trail — the
   voodoo X-ray, NOT the curated page-chain. It is ONE continuous ribbon (no
   container box) that flows and WRAPS PER ITEM like text and ends exactly after
   the current scope — nothing trails past it. Every crumb carries the rail fill
   so the ribbon is seamless; the fill TINT flips per scope (.zT1) to mark the
   boundary. Crumbs flex-grow so each wrapped row justifies to a common right
   edge; a single invisible .zCrumb-fill soaks the LAST row's slack so the final
   line stays natural (left, no stretch). The current scope is the accent fill
   (canonical active); the first crumb rounds the left end, the last the right.
   Theme-agnostic: every colour is a currentColor mix (works light + dark). */
.zBreadcrumb-echo {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: auto;
  margin: 0;
  padding: 0;                              /* no rail box — crumbs ARE the ribbon */
  border: 0;
  border-radius: 0;
  background: none;
  row-gap: 0.28rem;
  font-size: 0.8rem;
  line-height: 1.2;
}
.zBreadcrumb-echo .zCrumb {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 1 1 auto;                          /* grow to justify each wrapped row */
  padding: 0.34rem 0.5rem;
  white-space: nowrap;
  text-decoration: none;
  color: color-mix(in srgb, currentColor 54%, transparent);
  background: color-mix(in srgb, currentColor 5%, transparent);
}
/* alternating scope tint — the boundary marker, washed over the base fill */
.zBreadcrumb-echo .zCrumb.zT1 {
  background: color-mix(in srgb, currentColor 12%, transparent);
}
/* slack-eater: only ever lands on the last row, absorbs its free space */
.zBreadcrumb-echo .zCrumb-fill { flex: 1000 1 0; background: none; }
/* rounded ribbon ends */
.zBreadcrumb-echo .zCrumb:first-child { border-radius: 999px 0 0 999px; padding-left: 0.8rem; }
.zBreadcrumb-echo .zCrumb-end         { border-radius: 0 999px 999px 0; padding-right: 0.8rem; }
/* scope lead — the block / $arrival sentinel, zLink back into the scope */
.zBreadcrumb-echo .zCrumb-lead {
  font-weight: 680;
  color: color-mix(in srgb, currentColor 80%, transparent);
}
.zBreadcrumb-echo .zCrumb-lead:hover { color: currentColor; }
/* SECTION crumb — zOmega anchor: jump to that section within the scope */
.zBreadcrumb-echo .zCrumb-om { cursor: pointer; }
.zBreadcrumb-echo .zCrumb-om:hover { color: var(--color-info, #5CA9FF); }
/* EVENT crumb — terminal action, plain + quiet, NOT clickable */
.zBreadcrumb-echo .zCrumb-ev {
  color: color-mix(in srgb, currentColor 34%, transparent);
}
/* CURRENT scope — accent fill (canonical active). NOTE: selector carries BOTH
   .zCrumb and .zCrumb-on so it ties the scope-tint rule (.zCrumb.zT1) on
   specificity and, coming later, WINS — otherwise an odd-indexed current scope
   keeps its tint and the blue accent never paints. */
.zBreadcrumb-echo .zCrumb.zCrumb-on {
  background: var(--zcrumb-active-bg, var(--color-info, #5CA9FF));
  color: var(--zcrumb-active-ink, #fff);
}
.zBreadcrumb-echo .zCrumb.zCrumb-on.zCrumb-lead,
.zBreadcrumb-echo .zCrumb.zCrumb-on.zCrumb-ev { color: var(--zcrumb-active-ink, #fff); }

/* ─── 2. Grid ──────────────────────────────────────────────────────────────── */

.zRow {
  display: flex;
  flex-wrap: wrap;
  margin-right: calc(0.75rem * -1);
  margin-left:  calc(0.75rem * -1);
}

[class*="zCol"] {
  position: relative;
  width: 100%;
  padding-right: 0.75rem;
  padding-left:  0.75rem;
  flex-shrink: 0;
}

.zCol      { flex: 1 0 0%; }
.zCol-auto { flex: 0 0 auto; width: auto; }

/* zbase ships ONLY the minimal grid the renderer emits (zRow / zCol / zCol-auto —
 * used by zDash). The full 12-column matrix and responsive breakpoint variants
 * (zCol-1..12, zCol-{sm,md,lg,xl}-*) were evicted: zOS is not a CSS framework.
 * An app that wants a column grid brings its own stylesheet. Strip, don't compile —
 * it's CSS, trivially re-added if a real zOS event ever needs it. */

/* ─── 3. Gutters ───────────────────────────────────────────────────────────── */

/* Only zG-0 is kept — zDash builds its sidebar/content row with `.zRow.zG-0`
 * (zero gutters). The zG-1..5 gutter scale was a framework convenience nothing
 * emits; evicted with the 12-col grid above. */
.zRow.zG-0 { margin-right: 0; margin-left: 0; }
.zRow.zG-0 > [class*="zCol"] { padding-right: 0; padding-left: 0; }

/* ─── 4. Flex utilities ────────────────────────────────────────────────────── */

.zFlex-row             { flex-direction: row !important; }
.zFlex-column          { flex-direction: column !important; }
.zFlex-row-reverse     { flex-direction: row-reverse !important; }
.zFlex-column-reverse  { flex-direction: column-reverse !important; }

.zFlex-wrap            { flex-wrap: wrap !important; }
.zFlex-nowrap          { flex-wrap: nowrap !important; }

.zFlex-start           { justify-content: flex-start !important; }
.zFlex-end             { justify-content: flex-end !important; }
.zFlex-center          { justify-content: center !important; }
.zFlex-between         { justify-content: space-between !important; }
.zFlex-around          { justify-content: space-around !important; }
.zFlex-evenly          { justify-content: space-evenly !important; }

.zFlex-items-start     { align-items: flex-start !important; }
.zFlex-items-end       { align-items: flex-end !important; }
.zFlex-items-center    { align-items: center !important; }
.zFlex-items-stretch   { align-items: stretch !important; }
.zFlex-items-baseline  { align-items: baseline !important; }

/* grow/shrink/fill helpers evicted — not in the _GUI container mapper
 * (container_utils.js) and never emitted. Re-add only if a real zOS event needs
 * flex grow/shrink. */
.zGap-0 { gap: 0 !important; }
.zGap-1 { gap: 0.25rem !important; }
.zGap-2 { gap: 0.5rem !important; }
.zGap-3 { gap: 0.75rem !important; }
.zGap-4 { gap: 1rem !important; }
.zGap-5 { gap: 1.5rem !important; }

/* ─── 5. Display helpers ───────────────────────────────────────────────────── */

.zD-none   { display: none !important; }
.zD-block  { display: block !important; }
.zD-flex   { display: flex !important; }
.zD-inline { display: inline !important; }
.zD-inline-block { display: inline-block !important; }
.zD-inline-flex  { display: inline-flex !important; }

/* ─── 6. Spacing helpers ───────────────────────────────────────────────────── */

.zm-0 { margin: 0 !important; }
.zm-1 { margin: 0.25rem !important; }
.zm-2 { margin: 0.5rem !important; }
.zm-3 { margin: 0.75rem !important; }
.zm-4 { margin: 1rem !important; }
.zm-5 { margin: 1.5rem !important; }

.zmt-0 { margin-top: 0 !important; }
.zmt-1 { margin-top: 0.25rem !important; }
.zmt-2 { margin-top: 0.5rem !important; }
.zmt-3 { margin-top: 0.75rem !important; }
.zmt-4 { margin-top: 1rem !important; }
.zmt-5 { margin-top: 1.5rem !important; }

.zmb-0 { margin-bottom: 0 !important; }
.zmb-1 { margin-bottom: 0.25rem !important; }
.zmb-2 { margin-bottom: 0.5rem !important; }
.zmb-3 { margin-bottom: 0.75rem !important; }
.zmb-4 { margin-bottom: 1rem !important; }
.zmb-5 { margin-bottom: 1.5rem !important; }

.zms-0 { margin-left: 0 !important; }
.zms-1 { margin-left: 0.25rem !important; }
.zms-2 { margin-left: 0.5rem !important; }
.zms-3 { margin-left: 0.75rem !important; }
.zms-4 { margin-left: 1rem !important; }
.zms-5 { margin-left: 1.5rem !important; }

.zme-0 { margin-right: 0 !important; }
.zme-1 { margin-right: 0.25rem !important; }
.zme-2 { margin-right: 0.5rem !important; }
.zme-3 { margin-right: 0.75rem !important; }
.zme-4 { margin-right: 1rem !important; }
.zme-5 { margin-right: 1.5rem !important; }

/* axis margins — emitted by spacing_utils (getMarginClass side 'x'/'y'). zmx-auto
 * (centering) lives in the misc helpers section. */
.zmx-0 { margin-left: 0 !important;       margin-right: 0 !important; }
.zmx-1 { margin-left: 0.25rem !important; margin-right: 0.25rem !important; }
.zmx-2 { margin-left: 0.5rem !important;  margin-right: 0.5rem !important; }
.zmx-3 { margin-left: 0.75rem !important; margin-right: 0.75rem !important; }
.zmx-4 { margin-left: 1rem !important;    margin-right: 1rem !important; }
.zmx-5 { margin-left: 1.5rem !important;  margin-right: 1.5rem !important; }

.zmy-0 { margin-top: 0 !important;       margin-bottom: 0 !important; }
.zmy-1 { margin-top: 0.25rem !important; margin-bottom: 0.25rem !important; }
.zmy-2 { margin-top: 0.5rem !important;  margin-bottom: 0.5rem !important; }
.zmy-3 { margin-top: 0.75rem !important; margin-bottom: 0.75rem !important; }
.zmy-4 { margin-top: 1rem !important;    margin-bottom: 1rem !important; }
.zmy-5 { margin-top: 1.5rem !important;  margin-bottom: 1.5rem !important; }

.zp-0 { padding: 0 !important; }
.zp-1 { padding: 0.25rem !important; }
.zp-2 { padding: 0.5rem !important; }
.zp-3 { padding: 0.75rem !important; }
.zp-4 { padding: 1rem !important; }
.zp-5 { padding: 1.5rem !important; }

.zpt-0 { padding-top: 0 !important; }
.zpt-1 { padding-top: 0.25rem !important; }
.zpt-2 { padding-top: 0.5rem !important; }
.zpt-3 { padding-top: 0.75rem !important; }
.zpt-4 { padding-top: 1rem !important; }
.zpt-5 { padding-top: 1.5rem !important; }

.zpb-0 { padding-bottom: 0 !important; }
.zpb-1 { padding-bottom: 0.25rem !important; }
.zpb-2 { padding-bottom: 0.5rem !important; }
.zpb-3 { padding-bottom: 0.75rem !important; }
.zpb-4 { padding-bottom: 1rem !important; }
.zpb-5 { padding-bottom: 1.5rem !important; }

.zps-0 { padding-left: 0 !important; }
.zps-1 { padding-left: 0.25rem !important; }
.zps-2 { padding-left: 0.5rem !important; }
.zps-3 { padding-left: 0.75rem !important; }
.zps-4 { padding-left: 1rem !important; }
.zps-5 { padding-left: 1.5rem !important; }

.zpe-0 { padding-right: 0 !important; }
.zpe-1 { padding-right: 0.25rem !important; }
.zpe-2 { padding-right: 0.5rem !important; }
.zpe-3 { padding-right: 0.75rem !important; }
.zpe-4 { padding-right: 1rem !important; }
.zpe-5 { padding-right: 1.5rem !important; }

/* axis padding — emitted by spacing_utils (getPaddingClass side 'x'/'y'). */
.zpx-0 { padding-left: 0 !important;       padding-right: 0 !important; }
.zpx-1 { padding-left: 0.25rem !important; padding-right: 0.25rem !important; }
.zpx-2 { padding-left: 0.5rem !important;  padding-right: 0.5rem !important; }
.zpx-3 { padding-left: 0.75rem !important; padding-right: 0.75rem !important; }
.zpx-4 { padding-left: 1rem !important;    padding-right: 1rem !important; }
.zpx-5 { padding-left: 1.5rem !important;  padding-right: 1.5rem !important; }

.zpy-0 { padding-top: 0 !important;       padding-bottom: 0 !important; }
.zpy-1 { padding-top: 0.25rem !important; padding-bottom: 0.25rem !important; }
.zpy-2 { padding-top: 0.5rem !important;  padding-bottom: 0.5rem !important; }
.zpy-3 { padding-top: 0.75rem !important; padding-bottom: 0.75rem !important; }
.zpy-4 { padding-top: 1rem !important;    padding-bottom: 1rem !important; }
.zpy-5 { padding-top: 1.5rem !important;  padding-bottom: 1.5rem !important; }

/* ─── 7. Tabs ──────────────────────────────────────────────────────────────── */

.zTab-content > .zTab-pane { display: none; }
.zTab-content > .zActive   { display: block; }

/* Fade animation (opt-in via .zFade) */
.zTab-pane.zFade {
  opacity: 0;
  transition: opacity 0.15s linear;
}
.zTab-pane.zFade.zShow { opacity: 1; }

/* ─── 8. Nav / Nav-pills ───────────────────────────────────────────────────── */

.zNav {
  display: flex;
  flex-wrap: wrap;
  padding-left: 0;
  margin-bottom: 0;
  list-style: none;
}

.zNav-link {
  display: block;
  padding: 0.5rem 1rem;
  text-decoration: none;
  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out;
}

.zNav-link.zDisabled {
  opacity: 0.5;
  pointer-events: none;
  cursor: default;
}

/* Pills */
.zNav-pills .zNav-link {
  border-radius: 0.375rem;
}

/* Active state — color set by app CSS, not here */
.zNav-pills .zNav-link.zActive {
  font-weight: 500;
}

/* ─── 9. Spinner ───────────────────────────────────────────────────────────── */

.zSpinner-border {
  display: inline-block;
  width: 2rem;
  height: 2rem;
  border: 0.25em solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: zSpinner-border-spin 0.75s linear infinite;
  vertical-align: -0.125em;
}

.zSpinner-border-sm {
  width: 1rem;
  height: 1rem;
  border-width: 0.2em;
}

.zSpinner-grow {
  display: inline-block;
  width: 2rem;
  height: 2rem;
  background-color: currentColor;
  border-radius: 50%;
  opacity: 0;
  animation: zSpinner-grow-pulse 0.75s linear infinite;
  vertical-align: -0.125em;
}

.zSpinner-grow-sm { width: 1rem; height: 1rem; }

@keyframes zSpinner-border-spin { to { transform: rotate(360deg); } }
@keyframes zSpinner-grow-pulse  {
  0%   { transform: scale(0); opacity: 1; }
  50%  { opacity: 1; transform: scale(1); }
  100% { transform: scale(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .zSpinner-border, .zSpinner-grow { animation-duration: 1.5s; }
}

/* ─── 9b. zIcon baseline fix ───────────────────────────────────────────────────
   Bootstrap font icons (<i class="bi bi-*">) sit slightly low on the text baseline.
   This is an ALIGNMENT correction only — invisible, universally wanted. Size and
   colour are deliberately left to inherit (1em / currentColor) so a zIcon scales and
   tints with whatever text it sits in. No font-size, no margins — never fight the user. */
.bi {
  vertical-align: -0.125em;
  line-height: 1;
}

/* ─── 10. Misc runtime helpers ─────────────────────────────────────────────── */

.zContainer-fluid {
  width: 100%;
  padding-right: 0.75rem;
  padding-left: 0.75rem;
  margin-right: auto;
  margin-left: auto;
}

.zVisually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0,0,0,0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

.zText-muted    { opacity: 0.6; }
.zText-dark     { color: var(--ink); }
.zBorder        { border: 1px solid var(--border); }
.zShadow-sm     { box-shadow: 0 1px 3px var(--shadow); }
.zBg-white      { background: var(--surface-1); }
.zText-center   { text-align: center !important; }
.zText-start    { text-align: left !important; }
.zText-end      { text-align: right !important; }

.zW-100  { width: 100% !important; }
.zH-100  { height: 100% !important; }
.zmx-auto { margin-left: auto !important; margin-right: auto !important; }

.zRounded        { border-radius: 0.375rem !important; }
.zRounded-circle { border-radius: 50% !important; }

/* Base button structural (no colors — app/theme sets those) */
.zBtn {
  display: inline-block;
  box-sizing: border-box;
  cursor: pointer;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  user-select: none;
  padding: 0.7rem 1.5rem;
  border: 1px solid transparent;
  border-radius: 980px;
  font-size: 0.95rem;
  line-height: 1.4;
  letter-spacing: -0.01em;
  text-decoration: none;
}
.zBtn:focus { outline: 2px solid color-mix(in srgb, var(--color-primary) 55%, transparent); outline-offset: 2px; }

/* ─── Button color variants — canonical fills (theme tokens) ───────────────── */
/* The renderer maps a zBtn's `color:` (default primary) to .zBtn-{color}; these
 * give that class an actual fill everywhere — not just inside .zDash-container.
 * Keyed to --color-* (mirrors .zText-*); apps restyle by redefining the token.
 * Dashboard keeps its own look via the higher-specificity .zDash-container rule. */
.zBtn-primary, .zBtn-secondary, .zBtn-success,
.zBtn-danger, .zBtn-warning, .zBtn-info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  padding: 0.7rem 1.5rem;
  border: none;
  border-radius: 980px;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.01em;
  cursor: pointer;
  box-shadow: 0 4px 14px var(--shadow);
  transition: filter 0.15s ease, transform 0.08s ease, box-shadow 0.15s ease;
}
.zBtn-primary:hover, .zBtn-secondary:hover, .zBtn-success:hover,
.zBtn-danger:hover, .zBtn-warning:hover, .zBtn-info:hover {
  filter: brightness(1.06);
  transform: translateY(-2px);
  box-shadow: 0 8px 22px var(--shadow);
}
.zBtn-primary:active, .zBtn-secondary:active, .zBtn-success:active,
.zBtn-danger:active, .zBtn-warning:active, .zBtn-info:active {
  transform: translateY(0);
  box-shadow: 0 2px 6px var(--shadow);
}
.zBtn-primary:disabled, .zBtn-secondary:disabled, .zBtn-success:disabled,
.zBtn-danger:disabled, .zBtn-warning:disabled, .zBtn-info:disabled {
  opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; filter: none;
}

/* Generic disabled — covers outline/plain buttons (e.g. paging First/Prev at edge)
 * so a disabled control always reads as muted and never out-ranks an active one. */
.zBtn:disabled, .zBtn[disabled] {
  opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none;
}

/* SSOT fills — keyed to --color-* (same tokens/hexes as .zText-*), so a button
 * matches a heading of the same color name. Dark text on the light hues
 * (primary/success/warning/info), white on the deep ones (secondary/danger). */
.zBtn-primary   { background: var(--color-primary,   #A2D46E); color: #14210a; }
.zBtn-secondary { background: var(--color-secondary, #9370DB); color: #ffffff; }
.zBtn-success   { background: var(--color-success,   #52B788); color: #06281c; }
.zBtn-danger    { background: var(--color-error,     #E63946); color: #ffffff; }
.zBtn-warning   { background: var(--color-warning,   #FFB347); color: #3a2400; }
.zBtn-info      { background: var(--color-info,       #5CA9FF); color: #07243d; }

/* Size + outline + group variants — emitted by the renderer (e.g. the zTable
 * paging control bar). Outline buttons keep the base box model and only swap the
 * skin (transparent fill, token-tinted text/border). */
.zBtn-sm { padding: 0.4rem 0.95rem;  font-size: 0.82rem; border-radius: 980px; }
.zBtn-lg { padding: 0.95rem 2.2rem;  font-size: 1.05rem; border-radius: 980px; }

.zBtn-outline-primary,
.zBtn-outline-secondary,
.zBtn-outline-success,
.zBtn-outline-danger,
.zBtn-outline-warning,
.zBtn-outline-info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  font-family: inherit;
  font-weight: 600;
  line-height: 1.4;
  background: transparent;
  border-color: currentColor;
  transition: background 0.15s ease, transform 0.08s ease;
}
.zBtn-outline-primary   { color: var(--color-primary,   #A2D46E); }
.zBtn-outline-secondary { color: var(--color-secondary, #9370DB); }
.zBtn-outline-success   { color: var(--color-success,   #52B788); }
.zBtn-outline-danger    { color: var(--color-error,     #E63946); }
.zBtn-outline-warning   { color: var(--color-warning,   #FFB347); }
.zBtn-outline-info      { color: var(--color-info,       #5CA9FF); }
.zBtn-outline-primary:hover,
.zBtn-outline-secondary:hover,
.zBtn-outline-success:hover,
.zBtn-outline-danger:hover,
.zBtn-outline-warning:hover,
.zBtn-outline-info:hover {
  background: color-mix(in srgb, currentColor 14%, transparent);
}

.zBtn-group { display: inline-flex; }
.zBtn-group > .zBtn { border-radius: 0; }
.zBtn-group > .zBtn:first-child { border-top-left-radius: 7px; border-bottom-left-radius: 7px; }
.zBtn-group > .zBtn:last-child  { border-top-right-radius: 7px; border-bottom-right-radius: 7px; }
.zBtn-group > .zBtn:not(:first-child) { margin-left: -1px; }

/* Structural display defaults */
.zSignal     { display: block; }
/* zSignals — semantic status feedback. Inline colored text, 1:1 with the
   zCLI signal lines (success/error/warning/info). Flush-card variant TBD. */
/* Each type binds the base hue (--signal) and its light fill (--signal-light)
   from the palette; the card (.zAlert) consumes both. No per-component mixing. */
.zSignal-success   { --signal: var(--color-success);   --signal-light: var(--color-success-light);   color: var(--color-success); }
.zSignal-error     { --signal: var(--color-error);     --signal-light: var(--color-error-light);     color: var(--color-error); }
.zSignal-warning   { --signal: var(--color-warning);   --signal-light: var(--color-warning-light);   color: var(--color-warning); }
.zSignal-info      { --signal: var(--color-info);      --signal-light: var(--color-info-light);      color: var(--color-info); }
/* Non-status brand emphasis signals (zPrimary / zSecondary). */
.zSignal-primary   { --signal: var(--color-primary);   --signal-light: var(--color-primary-light);   color: var(--color-primary); }
.zSignal-secondary { --signal: var(--color-secondary); --signal-light: var(--color-secondary-light); color: var(--color-secondary); }
/* flush modifier (GUI-only, flush:true) — turns the inline signal into a
   flushed block. Geometry only; border + tint derive from currentColor (the
   .zSignal-* type color), so the colour stays single-sourced on the primitive. */
.zAlert {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-2) 0;
  padding: var(--space-3) var(--space-4);
  border-radius: 8px;
  /* Light flush: pastel fill from the --light token, colored border + text from
     the base hue. All straight from the palette — no per-card mixing. */
  border: 1px solid var(--signal);
  background: var(--signal-light);
  color: var(--signal);
}
/* Signal card internals — message takes the row, × dismisses. Colour inherits
   from .zSignal-* (currentColor), so the close glyph matches the signal type. */
/* min-width:0 lets the flex child shrink below content size so long, unbroken
   values (JSON, urls, tokens) wrap instead of overflowing the card. */
.zSignal-text  { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.zSignal-close {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: currentColor;
  cursor: pointer;
  font-size: 1.15rem;
  line-height: 1;
  padding: 0;
  opacity: 0.65;
}
.zSignal-close:hover { opacity: 1; }
.zSignal.zSignal-out { animation: zSignal-fade 0.2s ease-out forwards; }
@keyframes zSignal-fade { from { opacity: 1; } to { opacity: 0; } }
/* Toast presentation (flush:true) — the flushed signal renders OUT of flow as a
   timed card in a fixed corner stack, not inline. Box geometry stays from
   .zAlert; .zToast adds an opaque card surface, lift, and a slide entrance so it
   reads over any page content. Colour still single-sourced from .zSignal-*.
   Corner is overridable per-app via _zClass on the container — we are not a
   utility framework, just a sane default. */
.zToast-container {
  position: fixed;
  top: var(--space-4);
  right: var(--space-4);
  z-index: 10000;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 360px;
  pointer-events: none;
}
/* Toast = the same .zAlert card, just floated. No own background — the look is
   single-sourced from .zAlert/.zSignal-* so flush and in-flow signals are
   identical; .zToast only adds the lift + slide entrance. */
.zToast {
  box-shadow: 0 8px 22px var(--shadow);
  pointer-events: auto;
  animation: zToast-in 0.25s ease-out;
}
.zToast.zToast-out { animation: zToast-out 0.25s ease-out forwards; }
@keyframes zToast-in {
  from { transform: translateX(120%); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
@keyframes zToast-out {
  from { transform: translateX(0);    opacity: 1; }
  to   { transform: translateX(120%); opacity: 0; }
}
/* ─── zFunc result flush — roomy, code-like card (flush:true + result) ───────── */
/* A zFunc return surfaces as a toast: structured data reads as a code block,
   a scalar/message reads as prose. Geometry overrides the bare .zToast card;
   colour is still single-sourced from .zSignal-* (--signal / --signal-light). */
.zAlert-result {
  display: block;
  padding: 0;
  margin: var(--space-2) 0;
  border: 1px solid var(--signal);
  background: var(--signal-light);
  border-radius: 11px;
  overflow: hidden;
}
/* header strip — status glyph + level label, then dismiss, in the signal hue */
.zResult-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  color: var(--signal);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-bottom: 1px solid color-mix(in srgb, var(--signal) 35%, transparent);
}
.zResult-head .zResult-glyph { font-size: 1rem; line-height: 1; }
.zResult-head .zSignal-close { margin-left: auto; }
/* prose body — a scalar / human message stays readable sans, a touch bigger */
.zResult-msg {
  padding: var(--space-3) var(--space-4);
  color: var(--signal);
  font-size: 1.05rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
}
/* code body — structured data (dict / list) reads as a code block */
.zResult-code {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-base);
  color: var(--ink);
  font-family: ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace;
  font-size: 0.95rem;
  line-height: 1.6;
  white-space: pre;
  overflow-x: auto;
}
/* JSON token tints — keys/strings/numbers read as code, not a mono wall */
.zResult-code .tok-key { color: var(--color-info); }
.zResult-code .tok-str { color: var(--color-primary); }
.zResult-code .tok-num { color: var(--color-warning); }
.zResult-code .tok-pun { color: var(--ink-muted); }
.zCard       { position: relative; }
.zInputGroup { display: flex; align-items: stretch; }
.zInputGroup > .zInputGroup-text { display: flex; align-items: center; }
/* zDialog actions row — Submit + Reset side by side with rhythm. */
.zDialog-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }

/* Badge (structural — no color) */
.zBadge {
  display: inline-block;
  padding: 0.25em 0.5em;
  font-size: 0.75em;
  font-weight: 700;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  vertical-align: baseline;
  border-radius: 0.375rem;
}

/* ─── 10. zDash layout formats ─────────────────────────────────────────────── */

/* sidebar: fixed left-nav + scrollable content area.
   Framed as a self-contained card — border + radius + clipped corners give the
   dashboard defined edges and a bottom (border colour lives in §11). The row
   STRETCHES so the sidebar rail runs full height to the bottom edge; min-height
   is a content floor, not a viewport lock, so the frame hugs its content. */
.zDash-format-sidebar.zDash-container {
  display: flex;
  flex-direction: column;
  min-height: 400px;
  border-radius: 12px;
  overflow: hidden;
  /* zContainer-fluid adds inline padding; zero it here so the sidebar rail and
     panel sit flush to the frame border (no inset white strip on the edges). */
  padding: 0;
}
.zDash-format-sidebar > .zRow {
  display: flex !important;
  flex-wrap: nowrap !important;
  align-items: stretch !important;
  flex: 1;
  margin: 0;
}
.zDash-format-sidebar > .zRow > .zCol-auto { flex-shrink: 0; }
.zDash-format-sidebar > .zRow > .zCol { flex: 1; min-width: 0; width: 0; }

/* Tablet ≤900px: sidebar collapses to horizontal top bar */
@media (max-width: 900px) {
  .zDash-format-sidebar > .zRow {
    flex-direction: column !important;
    flex-wrap: wrap !important;
  }
  .zDash-format-sidebar > .zRow > .zCol-auto,
  .zDash-format-sidebar > .zRow > .zCol {
    width: 100% !important;
    flex: none !important;
    min-width: unset !important;
  }
  .zDash-format-sidebar > .zRow > .zCol-auto {
    border-right: none !important;
    border-bottom: 1px solid color-mix(in srgb, currentColor 12%, transparent);
  }
  .zDash-format-sidebar .zDash-sidebar {
    flex-direction: row !important;
    flex-wrap: wrap;
    width: 100%;
    padding: 0.5rem 0.75rem;
    gap: 0.25rem;
  }
  .zDash-format-sidebar .zDash-sidebar .zNav-link.zActive {
    box-shadow: none !important;
    border-bottom: 2px solid var(--color-secondary);
  }
  .zDash-panel { padding: 1.25rem 1rem; }
}

/* Mobile sidebar toggle button — hidden by default (desktop/tablet) */
.zDash-sidebar-toggle {
  display: none;
}

/* Mobile ≤600px: sidebar becomes a toggle-driven drawer */
@media (max-width: 600px) {
  /* Container must be relative so the sidebar overlay positions correctly */
  .zDash-format-sidebar { position: relative; }

  /* Sidebar column hidden by default on mobile */
  .zDash-format-sidebar > .zRow > .zCol-auto {
    display: none !important;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 50;
    /* Frosted overlay — a currentColor tint + blur obscures the content behind
       without needing the host's opaque bg (which we don't have as a token). */
    background: color-mix(in srgb, currentColor 8%, transparent);
    border-bottom: 1px solid color-mix(in srgb, currentColor 14%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(1.6);
    backdrop-filter: blur(14px) saturate(1.6);
  }

  /* Open state: sidebar slides in as a full-width dropdown */
  .zDash-format-sidebar.zDash-sidebar-open > .zRow > .zCol-auto {
    display: block !important;
  }
  .zDash-format-sidebar.zDash-sidebar-open .zDash-sidebar {
    flex-direction: column !important;
    width: 100% !important;
    padding: 0.5rem 0.75rem;
    gap: 0.1rem;
  }
  /* Push content down when sidebar is open */
  .zDash-format-sidebar.zDash-sidebar-open > .zRow > .zCol {
    margin-top: var(--zdash-sidebar-h, 0px);
  }

  /* Show the toggle button on mobile */
  .zDash-sidebar-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0.75rem 0.75rem 0;
    padding: 0.45rem 0.85rem;
    background: color-mix(in srgb, var(--color-secondary) 15%, transparent);
    border: 1px solid color-mix(in srgb, var(--color-secondary) 30%, transparent);
    border-radius: 8px;
    color: currentColor;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    font-family: inherit;
  }
  .zDash-sidebar-toggle:hover {
    background: color-mix(in srgb, var(--color-secondary) 25%, transparent);
    color: currentColor;
  }

  .zDash-panel { padding: 1rem 0.75rem; }
}

/* tabs format (future) */
.zDash-format-tabs.zDash-container { display: block; }
.zDash-format-tabs .zDash-sidebar {
  display: flex;
  flex-direction: row;
  border-bottom: 1px solid color-mix(in srgb, var(--color-secondary) 20%, transparent);
}

/* accordion format (future) */
.zDash-format-accordion > .zRow { flex-direction: column; }

/* ─── 11. zDash default visual theme ───────────────────────────────────────── */
/*
 * Canonical zDash chrome — styles ONLY the sidebar rail + the container frame.
 * It deliberately targets NOTHING inside the panel: each panel is its own zUI
 * with its own zBrush, and the dashboard never reaches into it.
 *
 * Built entirely from currentColor (the host app's own ink) + transparent mixes —
 * NOT from --text/--page-bg (a host like zCloud sets its theme via real
 * color/background, not those tokens). So the one class self-balances on a light
 * OR dark app with zero token plumbing — no data-theme, no media query. The only
 * non-currentColor value is the brand accent (--color-secondary) on the active
 * item, which reads on both and never affects the light/dark balance.
 */

/* Outer frame — the dashboard's defining outline, a faint wash of the host ink
   over the host's own background. Pairs with radius+overflow in §10. */
.zDash-format-sidebar.zDash-container {
  border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
}

/* Sidebar column chrome — surface + divider mixed from currentColor, so the SAME
   rule reads as a faint raised rail on a light app AND a dark one. */
.zDash-format-sidebar > .zRow > .zCol-auto {
  background: color-mix(in srgb, currentColor 5%, transparent);
  border-right: 1px solid color-mix(in srgb, currentColor 12%, transparent);
  padding: 0;
}

/* Sidebar nav */
.zDash-sidebar {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 1rem 0.75rem;
  width: 200px;
}
.zDash-sidebar .zNav-link {
  display: flex;
  align-items: center;
  padding: 0.55rem 0.85rem;
  border-radius: 8px;
  color: color-mix(in srgb, currentColor 60%, transparent);
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
  white-space: nowrap;
  text-decoration: none;
}
.zDash-sidebar .zNav-link:hover {
  color: currentColor;
  background: color-mix(in srgb, currentColor 7%, transparent);
}
.zDash-sidebar .zNav-link.zActive {
  color: currentColor;
  background: color-mix(in srgb, var(--color-secondary) 20%, transparent);
  box-shadow: inset 3px 0 0 var(--color-secondary);
}

/* Content slot — ONLY the frame inset (padding) + tab-pane show/hide. The
   dashboard styles none of the content itself: each panel is its own zUI with
   its own zBrush, rendered untouched. A panel fill / card look is CUSTOM per app
   (see the Dashboards leaf's customization section). */
.zDash-panel { padding: 1.75rem 2rem; }
.zDash-panel > .zTab-pane         { display: none; }
.zDash-panel > .zTab-pane.zActive { display: block; }

/* Spinner — the dashboard's own panel-loading indicator (not panel content). */
.zSpinner-border { color: var(--color-primary); }

/* ============================================================================
   Tables (zTable) — canonical dark-theme data tables
   ----------------------------------------------------------------------------
   Bifrost-owned SSOT for the zTable look (moved here from zCloud so every app
   inherits the same table styling). The zTable event opts in via _zClass: zTable.
   ========================================================================== */
.zTable-container { margin: 1.5rem 0; }

/* Title sits on the PAGE (outside the card) → inherit so it reads on light AND
   dark doc pages (white --ink would vanish on a light page). */
.zTable-container h4 {
  color: inherit;
  font-size: 1.1rem;
  font-weight: 700;
  margin: 0 0 0.75rem;
}

.zTable-responsive {
  overflow-x: auto;
  border-radius: 12px;
  border: 1px solid var(--border);
  /* Self-contained dark CARD (matches the zTerminal card). Page-independent, so
     the table reads identically on a light OR dark page — white ink works on it
     everywhere. Border + shadow seat it on a dark page; it reads as an embedded
     panel on a light one. */
  background: var(--surface-base);
  box-shadow: 0 8px 28px var(--shadow);
}

.zTable {
  width: 100%;
  border-collapse: collapse;
  background: transparent;
  font-size: 0.95rem;
  text-align: left;
  /* Default body colour lives here (not on td) so a per-cell utility
     (zText-success/-danger…) wins on equal specificity — a bare .zText-* (0,1,0)
     would otherwise lose to `.zTable tbody td` (0,1,2). */
  color: var(--ink-muted);
}

.zTable thead th {
  background: color-mix(in srgb, var(--color-secondary) 22%, transparent);
  color: var(--ink);
  font-weight: 700;
  text-align: left;
  padding: 0.85rem 1rem;
  letter-spacing: 0.02em;
  border-bottom: 1px solid var(--border-strong);
  white-space: nowrap;
}

.zTable tbody td {
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.zTable tbody tr:last-child td { border-bottom: none; }
.zTable tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--ink) 3%, transparent); }
.zTable tbody tr:hover td { background: color-mix(in srgb, var(--color-secondary) 14%, transparent); }
.zTable td strong { color: var(--ink); }

/* Caption — subtitle line under the title (what the table shows / source / date).
   The renderer emits .zTable-caption; styling lives here (no utility/inline). */
.zTable-caption {
  margin: 0 0 0.75rem;
  font-size: 0.875rem;
  color: inherit;       /* on the page, outside the card — adapt to light/dark */
  opacity: 0.7;
}

/* "… N more rows" truncation footer. */
.zTable-more {
  margin: 0.5rem 0 0 0.75rem;
  font-size: 0.875rem;
  font-style: italic;
  color: inherit;       /* on the page, outside the card — adapt to light/dark */
  opacity: 0.6;
}

/* Pagination nav — canonical part. The renderer emits the structure
   (.zTable-nav > .zTable-nav-info + .zTable-nav-controls > [.zBtn-group] +
   .zTable-nav-jump); zbase owns ALL chrome here via vars. Buttons/inputs inside
   delegate to the zBtn / zInput events and cascade from their own SSOT. */
.zTable-nav {
  margin-top: 0.75rem;
  padding: 0.75rem;
  /* Matching dark CARD (sibling to the table) so its white ink reads on a light
     OR dark page — consistent with .zTable-responsive. */
  background: var(--surface-base);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.zTable-nav-info {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 0.75rem;
  font-size: 0.875rem;
  color: var(--ink-muted);
}
.zTable-nav-info strong { color: var(--ink); font-weight: 700; }

.zTable-nav-controls {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.zTable-nav-jump {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--ink-muted);
}
.zTable-nav-jump .zInput { width: 64px; text-align: center; }

/* ════════════════════════════════════════════════════════════════════════════
   Progress bar (zProgress) — canonical animated progress component

   Bifrost-owned SSOT for the progress look (mirrors the zCLI bar). The renderer
   builds: .zProgress-container > [label row] + .zProgress (track) > .zProgress-bar
   (the fill). Fill WIDTH is set inline (0–100%); fill COLOR comes from a
   zBg-{color} class the renderer derives from the event's `color` property.
   ════════════════════════════════════════════════════════════════════════════ */

.zProgress-container { width: 100%; margin: 0.75rem 0; }

/* Label / percentage row above the track, pushed to the edges (self-contained
   so it never depends on zTheme flex utilities being present). */
.zProgress-container > .zD-flex {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.4rem;
}
.zProgress-container .zFw-bold     { font-weight: 600; color: var(--zprogress-label, var(--ink)); }
.zProgress-container .zText-muted  { color: var(--zprogress-info, var(--ink-muted)); }

/* Track — the rail behind the fill. Inset shadow gives the pill real depth. */
.zProgress {
  width: 100%;
  height: 1rem;                 /* default; renderer may override inline via height prop */
  background: var(--zprogress-track, color-mix(in srgb, var(--ink) 7%, transparent));
  border-radius: 999px;
  overflow: hidden;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

/* Fill — the moving bar. It carries a solid tint (zBg-{color}) PLUS two layered
   overlays: a fixed top gloss for a glossy 3-D pill, and a diagonal sheen that
   is ALWAYS running — so the bar reads as alive even when the value is static.
   Width animates with a soft ease when it does change. */
.zProgress-bar {
  height: 100%;
  width: 0;
  background-color: var(--color-primary, #A2D46E);  /* default; zBg-{color} overrides */
  background-image:
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) 28%, transparent), var(--surface-1) 48%, rgba(0, 0, 0, 0.10)),
    linear-gradient(-45deg,
      color-mix(in srgb, var(--ink) 16%, transparent) 25%, transparent 25%,
      transparent 50%, color-mix(in srgb, var(--ink) 16%, transparent) 50%,
      color-mix(in srgb, var(--ink) 16%, transparent) 75%, transparent 75%, transparent);
  background-size: 100% 100%, 1.15rem 1.15rem;
  background-repeat: no-repeat, repeat;
  border-radius: inherit;
  transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  animation: zProgress-flow 0.9s linear infinite;
}

/* The diagonal sheen scrolls; the gloss layer stays put (position pinned). */
@keyframes zProgress-flow {
  from { background-position: 0 0, 1.15rem 0; }
  to   { background-position: 0 0, 0 0; }
}

/* Respect reduced-motion: keep the look, drop the perpetual scroll. */
@media (prefers-reduced-motion: reduce) {
  .zProgress-bar { animation: none; }
}

/* `color` property → fill color. Bar-scoped (.zProgress-bar.zBg-*) so it always
   wins over the generic zTheme utility and pulls from the same --color-* SSOT. */
.zProgress-bar.zBg-primary   { background-color: var(--color-primary,   #A2D46E); }
.zProgress-bar.zBg-secondary { background-color: var(--color-secondary, #9370DB); }
.zProgress-bar.zBg-success   { background-color: var(--color-success,   #52B788); }
.zProgress-bar.zBg-info      { background-color: var(--color-info,      #5CA9FF); }
.zProgress-bar.zBg-warning   { background-color: var(--color-warning,   #FFB347); }
.zProgress-bar.zBg-danger,
.zProgress-bar.zBg-error     { background-color: var(--color-error,     #E63946); }

/* Indeterminate / "working" state — bolder stripes (renderer adds these when a
   bar has no total). Reuses the same flow animation. */
.zProgress-bar-striped {
  background-image:
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) 28%, transparent), var(--surface-1) 48%, rgba(0, 0, 0, 0.10)),
    linear-gradient(-45deg,
      color-mix(in srgb, var(--ink) 28%, transparent) 25%, transparent 25%,
      transparent 50%, color-mix(in srgb, var(--ink) 28%, transparent) 50%,
      color-mix(in srgb, var(--ink) 28%, transparent) 75%, transparent 75%, transparent);
}
.zProgress-bar-animated { animation: zProgress-flow 0.9s linear infinite; }

/* Indeterminate "executing" marquee — opaque work of UNKNOWN length (a zFunc /
   zBtn plugin call). A partial fill chunk marches across the track: the web twin
   of the zCLI lit-window marquee that parks on the EXECUTE stage. It reads as
   "zOS is working" and NEVER sits at 100% — so a full bar only ever means done.
   Completion is signalled by the bar being replaced with the result. */
.zProgress--indeterminate { overflow: hidden; }
.zProgress--indeterminate > .zProgress-bar {
  width: 42% !important;
  transition: none;
  animation: zProgress-flow 0.9s linear infinite,
             zProgress-march 1.25s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
@keyframes zProgress-march {
  from { transform: translateX(-115%); }
  to   { transform: translateX(258%); }
}
@media (prefers-reduced-motion: reduce) {
  .zProgress--indeterminate > .zProgress-bar {
    animation: none; transform: none; width: 60% !important;
  }
}

/* ─── zMenu — interactive menu events ──────────────────────────────────────────
   Canonical Bifrost render of a zMenu: a vertical list of full-width selectable
   rows, each with a numbered chip (keyboard shortcut) + label. Overrides the
   generic zBtn-pill fallback so menus read as navigation, not loose buttons.

   Live markup (zdisplay_orchestrator.js _renderZMenuBlock):
     nav.zMenu-nav > ul.zNavbar-nav > li.zNav-item
        > button.zNav-link.zBtn[data-key] > span.zBadge + label
   Legacy markup (menu_renderer.js): .zMenu-container > .zMenu-option > button.
   Both paths are styled below.

   The block wrapper (.zCallout) supplies the card surface; this owns the inset
   "island" group, the rows, and the number chip. Rows carry a faint resting
   fill + a reserved left accent rail (filled on hover/active), the selected row
   is a tinted fill (never a solid slab — text stays legible), the chip is a
   secondary-tinted rounded square, and the auto Back row (data-key="zBack") is
   muted. Everything is currentColor / --color-* driven, so light & dark pages
   stay balanced. Accents pull from the zCLI SSOT palette (--color-primary lime,
   --color-secondary purple). */

/* Wrapper — group the options into a faint inset "island" so the menu reads as
   one cohesive control, not loose rows. currentColor-driven → identical balance
   on a light OR dark page (the .zCallout block wrapper still supplies the card). */
.zMenu-nav,
.zMenu-container {
  background: color-mix(in srgb, currentColor 3%, transparent);
  border: 1px solid color-mix(in srgb, currentColor 8%, transparent);
  border-radius: 0.875rem;
  padding: 0.4rem;
}

/* Options list — kill inherited bullets / list padding */
.zMenu-nav .zNavbar-nav,
.zMenu-options {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.zMenu-nav .zNav-item,
.zMenu-option {
  margin: 0;
  padding: 0;
  list-style: none;
}
.zMenu-nav .zNav-item::marker,
.zMenu-option::marker { content: none; }

/* Row — overrides the fallback zBtn pill into a full-width nav row. A faint
   resting fill + reserved left accent rail makes each row read as a real,
   tappable surface before any interaction. */
.zMenu-nav .zNav-link,
.zMenu-container .zMenu-option button,
.zMenu-container .zMenu-option .zBtn {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  text-align: left;
  padding: 0.65rem 0.8rem;
  font-size: 1rem;
  font-weight: 500;
  font-family: inherit;
  color: inherit;
  background: color-mix(in srgb, currentColor 4%, transparent);
  border: 1px solid transparent;
  border-left: 3px solid transparent;   /* reserved accent rail (no reflow on state change) */
  border-radius: 0.5rem;
  cursor: pointer;
  position: relative;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.12s ease;
}

.zMenu-nav .zNav-link:hover,
.zMenu-nav .zNav-link:focus-visible,
.zMenu-container .zMenu-option button:hover,
.zMenu-container .zMenu-option button:focus-visible {
  background: color-mix(in srgb, var(--color-primary) 14%, transparent);
  border-color: color-mix(in srgb, var(--color-primary) 35%, transparent);
  border-left-color: var(--color-primary, #A2D46E);
  transform: translateX(2px);
  outline: none;
}

/* Selected — tinted fill + solid accent rail. Text stays inherited (legible on
   light AND dark), no solid slab. */
.zMenu-nav .zNav-link.active,
.zMenu-container .zMenu-option button.active {
  background: color-mix(in srgb, var(--color-primary) 20%, transparent);
  border-color: color-mix(in srgb, var(--color-primary) 45%, transparent);
  border-left-color: var(--color-primary, #A2D46E);
  font-weight: 600;
}

.zMenu-nav .zNav-link:disabled,
.zMenu-container .zMenu-option button:disabled {
  opacity: 0.55;
  cursor: default;
  transform: none;
}

/* Leading chip — the keyboard index. Rounded-square + bordered reads sharper
   than a circle and balances on both themes (secondary-tinted). */
.zMenu-nav .zNav-link .zBadge,
.zMenu-container .zMenu-option .zBadge,
.zMenu-container .zMenu-index {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  font-size: 0.8125rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-secondary, #9370DB);
  background: color-mix(in srgb, var(--color-secondary) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-secondary) 30%, transparent);
  border-radius: 0.5rem;
}

.zMenu-nav .zNav-link:hover .zBadge,
.zMenu-nav .zNav-link:focus-visible .zBadge,
.zMenu-container .zMenu-option button:hover .zBadge,
.zMenu-container .zMenu-option button:focus-visible .zBadge {
  color: #fff;
  background: var(--color-secondary, #9370DB);
  border-color: var(--color-secondary, #9370DB);
}

.zMenu-nav .zNav-link.active .zBadge,
.zMenu-container .zMenu-option button.active .zBadge {
  color: #1a1a1a;
  background: var(--color-primary, #A2D46E);
  border-color: var(--color-primary, #A2D46E);
}

/* Auto-injected Back row (data-key="zBack") — muted + set apart from the
   numbered options; its chip stays neutral (currentColor), not brand-accented. */
.zMenu-nav .zNav-link[data-key="zBack"] { opacity: 0.78; font-weight: 500; }
.zMenu-nav .zNav-link[data-key="zBack"] .zBadge {
  color: inherit;
  background: color-mix(in srgb, currentColor 10%, transparent);
  border-color: color-mix(in srgb, currentColor 16%, transparent);
  font-weight: 600;
}
.zMenu-nav .zNav-link[data-key="zBack"]:hover .zBadge,
.zMenu-nav .zNav-link[data-key="zBack"]:focus-visible .zBadge {
  color: inherit;
  background: color-mix(in srgb, currentColor 18%, transparent);
  border-color: color-mix(in srgb, currentColor 24%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .zMenu-nav .zNav-link,
  .zMenu-container .zMenu-option button { transition: none; }
  .zMenu-nav .zNav-link:hover,
  .zMenu-container .zMenu-option button:hover { transform: none; }
}

/* ─── 12. zSwiper / zCarousel ──────────────────────────────────────────────── */
/*
 * Canonical carousel chrome for zSwiper (see swiper_renderer.js). Self-contained:
 * the component owns every part it draws (wrapper, label, slide, controls, dots)
 * so it never depends on utility classes that may not exist.
 *
 * Built from currentColor + transparent mixes (same approach as §11 zDash), with
 * the brand accent (--color-secondary) only on the active dot — so ONE rule set
 * self-balances on a light OR dark app, no data-theme / media query plumbing.
 */

.zCarousel-wrapper { margin: 1rem 0; }
.zCarousel-label { font-size: 1.05rem; font-weight: 600; margin-bottom: 0.6rem; }

/* Frame — a faint wash of the host ink; clips the slides that sit inside it. */
.zCarousel {
  position: relative;
  overflow: hidden;
  border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, currentColor 3%, transparent);
}
.zCarousel-inner { position: relative; }

/* THE rule that makes it a carousel and not a stack: one slide shows at a time. */
.zCarousel-item { display: none; }
.zCarousel-item.zActive { display: block; }

.zCarousel-slide {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 200px;
  padding: 2rem 3rem 2.75rem;
}

/* Fade variant — keep slides stacked and cross-fade opacity instead of swapping. */
.zCarousel-fade .zCarousel-inner { min-height: 200px; }
.zCarousel-fade .zCarousel-item {
  display: block;
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
}
.zCarousel-fade .zCarousel-item.zActive {
  position: relative;
  opacity: 1;
  pointer-events: auto;
}

/* Prev / next controls — round buttons floated over the frame edges. */
.zCarousel-control-prev,
.zCarousel-control-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 2.4rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  color: currentColor;
  background: color-mix(in srgb, currentColor 8%, transparent);
  transition: background 0.15s;
}
.zCarousel-control-prev { left: 0.6rem; }
.zCarousel-control-next { right: 0.6rem; }
.zCarousel-control-prev:hover,
.zCarousel-control-next:hover { background: color-mix(in srgb, currentColor 18%, transparent); }

/* Chevrons drawn from borders — no icon-font dependency. */
.zCarousel-control-prev-icon,
.zCarousel-control-next-icon {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
}
.zCarousel-control-prev-icon { transform: rotate(-135deg); margin-left: 4px; }
.zCarousel-control-next-icon { transform: rotate(45deg); margin-right: 4px; }

/* Position dots. */
.zCarousel-indicators {
  position: absolute;
  bottom: 0.6rem;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
  z-index: 2;
}
.zCarousel-indicators button {
  width: 0.5rem;
  height: 0.5rem;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  background: color-mix(in srgb, currentColor 25%, transparent);
  transition: background 0.15s, transform 0.15s;
}
.zCarousel-indicators button:hover { background: color-mix(in srgb, currentColor 45%, transparent); }
.zCarousel-indicators button.zActive {
  background: var(--color-secondary, currentColor);
  transform: scale(1.3);
}

@media (prefers-reduced-motion: reduce) {
  .zCarousel-fade .zCarousel-item { transition: none; }
  .zCarousel-indicators button { transition: none; }
}

/* ─── 13. zTerminal ────────────────────────────────────────────────────────── */
/*
 * Self-contained chrome for the zTerminal widget (see terminal_renderer.js).
 * zTerminal is a DELIBERATELY DARK card (like zTable) — it reads as a console on
 * a light OR dark page, so it carries its own dark palette instead of currentColor.
 * A dark theme can reskin by overriding the --zterm-* vars on .zTerminal-container.
 *
 * Why this block is the SSOT: the renderer used to lean on utility/slot classes
 * (zCard, zCard-body, zCard-footer, zM-0, zP-3, zMb-3) that were NEVER part of
 * zbase — so the code <pre> fell through to the generic `pre` baseline (§ pre
 * above) and inherited its margin + border + radius, prising the header, code,
 * and output apart. Everything the widget draws now lives here, flush by default.
 */
.zTerminal-container {
  --zterm-surface: #1e1e2e;
  --zterm-output:  #0d0d14;
  --zterm-border:  #333;
  --zterm-ink:     #e0e0e0;
  margin: 0 0 var(--space-4);
  border: 1px solid var(--zterm-border);
  border-radius: 8px;
  overflow: hidden;                       /* clip children to the rounded frame */
  background: var(--zterm-surface);
  color: var(--zterm-ink);
}

/* Header — title + language on the left, mode badge + Copy/Run on the right. */
.zTerminal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--zterm-surface);
  border-bottom: 1px solid var(--zterm-border);
}
.zTerminal-header-left,
.zTerminal-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.zTerminal-title {
  color: var(--zterm-ink);
  font-weight: 500;
  font-size: 0.9rem;
}
.zTerminal-lang {
  background: rgba(59, 130, 246, 0.2);
  color: #60a5fa;
  border: 1px solid rgba(59, 130, 246, 0.3);
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 0.75rem;
  font-weight: 500;
}

/* Body — the code area owns NO padding; the <pre> inside owns it. */
.zTerminal-body { background: var(--zterm-surface); }

/* THE reset: neutralise BOTH the generic `pre` baseline AND prism-tomorrow within
 * the widget so the three sections sit flush. Prism's highlightElement() promotes
 * the `language-*` class onto the <pre>, so prism-tomorrow's `pre[class*="language-"]`
 * (0,1,1) matches and — loaded after zbase at equal specificity — would win (its
 * .5em margin + 1em padding + #2d2d2d background = a floating lighter panel with a
 * gap). We list the attribute variant too so `.zTerminal-body pre[class*="language-"]`
 * (0,2,1) out-specifies it. Padding is HORIZONTAL-ONLY: no vertical gap between the
 * code and the header divider above / the output below (the body butts flush). */
.zTerminal-body pre,
.zTerminal-body pre[class*="language-"] {
  margin: 0;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: 0;
  background: transparent;
  overflow: auto;
}

/* Output — the run-result footer; hidden until a Run streams into it. */
.zTerminal-output {
  padding: var(--space-3);
  background: var(--zterm-output);
  color: var(--zterm-ink);
  border-top: 1px solid var(--zterm-border);
  font-family: 'SF Mono', 'Monaco', 'Courier New', monospace;
  white-space: pre-wrap;
  overflow: auto;
}

/* ─── 11. zModal — floating detour overlay ─────────────────────────────────
 * The Bifrost skin of the zModal CALL verb: backdrop + centered card the
 * ModalRenderer mounts on <body> when a render_modal frame arrives. Only the
 * STRUCTURE lives here (position, layering, dismiss affordance) — the card
 * inherits the page palette via the tokens above; apps re-skin via zBrush. */
.zModal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000; /* Z_INDEX.MODAL — above content, below toasts (1100) */
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(29, 34, 48, 0.55); /* --text at 55% — dims, never blacks out */
  backdrop-filter: blur(2px);
  padding: var(--space-4, 1rem);
}

.zModal-card {
  position: relative;
  background: var(--page-bg, #fff);
  color: var(--text, #1d2230);
  border-radius: 12px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.35);
  width: min(560px, 100%);
  max-height: min(80vh, 100%);
  overflow: auto;
  padding: var(--space-4, 1rem) var(--space-4, 1.25rem);
  animation: zModal-pop 160ms ease-out;
}

@keyframes zModal-pop {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

.zModal-close {
  position: absolute;
  top: 10px;
  right: 12px;
  z-index: 1; /* above positioned content siblings (e.g. .zCard) painted later in DOM order */
  border: 0;
  background: transparent;
  color: inherit;
  opacity: 0.55;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
}
.zModal-close:hover { opacity: 1; }
