/* ============================================================================
 * card-builder helpers — component-scoped CSS for card-builder.jsx section 1.
 * ----------------------------------------------------------------------------
 * Phase 19 Plan 04a (2026-05-25). These classes host the deliberately custom
 * style values that live inside HelpHint / HelpHintInline / SummaryBar /
 * BindingRow / FailureModeGroup / BindingDriftTableRenderer — values that
 * pre-Phase-19 sat as inline `style={{...}}` blocks containing FORBIDDEN_KEYS
 * (background, color, fontSize, padding, etc.) and were therefore audit-
 * flagged. Moving them here removes the audit hits without altering visuals.
 *
 * Why a separate file (not theme.css, not utilities.css):
 *   - utilities.css contract forbids hex literals and component-scoped names.
 *   - theme.css is per-theme; duplicating these declarations across 4 themes
 *     would bloat the migration and create drift hazard. The values here are
 *     intentionally theme-agnostic until a future visual-review pass
 *     promotes specific ones into per-theme overrides (the original Plan 07
 *     HelpHint comment frames `#0f1418 → var(--mt-bg-deep)` as exactly that
 *     follow-up — out of scope for the mechanical migration).
 *   - Mirrors the existing pattern at core/dashboard/components/Button.css /
 *     Field.css / Modal.css / Panel.css.
 *
 * Loaded BETWEEN utilities.css and theme.css (see index.html). Bump the
 * `?v=` cache-buster on edits.
 * ============================================================================
 */

/* === HelpHint badge ====================================================== *
 * The ? / ! glyph itself — small circle of currentColor that opens the
 * popover on hover/click. Default state is a dim ring (opacity 0.45); the
 * warning variant goes full-opacity in --mt-warn so it reads as a guard.
 * --------------------------------------------------------------------- */

.cb-help-hint-badge {
  border: 1px solid currentColor;
  color: inherit;
  opacity: 0.45;
}

.cb-help-hint-badge.is-warn {
  border: 1px solid var(--mt-warn);
  color: var(--mt-warn);
  opacity: 1;
}

/* === HelpHint popover ===================================================== *
 * The ? / ! badge that opens a small popover next to inspector fields. The
 * badge itself is a small ring of currentColor; the popover holds a darker
 * surface and a subtle 1px frame. Discretion Note 1 from the HelpHint
 * UI-SPEC (preserved verbatim from card-builder.jsx pre-migration): the
 * hardcoded #0f1418 popover background is INHERITED unchanged in this
 * plan — promotion to var(--mt-bg-deep) is a flagged follow-up that
 * changes 58 popovers' look at once and deserves its own visual review.
 * --------------------------------------------------------------------- */

.cb-help-hint-popover {
  min-width: 220px;
  max-width: 320px;
  padding: 8px 10px;
  background: var(--mt-bg-deep, #0f1418);
  border: 1px solid var(--mt-border, #2a3540);
  border-radius: 4px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  color: var(--mt-fg, #cbd5e1);
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1.55;
  white-space: normal;
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--mt-weight-regular);
}

.cb-help-hint-popover.is-warn {
  border-left: 2px solid var(--mt-warn);
}

/* === Inner-advanced toggle ("⏎ N advanced (M customized)") ============= *
 * The pill that sits at the top of each open accordion section. 5px 8px
 * asymmetric padding has no clean token decomposition (5px is not on the
 * mt-space-* ladder), so it lives here.
 * --------------------------------------------------------------------- */

.cb-advanced-toggle {
  padding: 5px 8px;
  border: var(--cb-toggle-border);
  background: var(--cb-toggle-bg);
  color: var(--cb-toggle-color);
}

/* === Inspector reveal controls (999.20-A) ============================ *
 * One row at the top of the inspector body: the Essentials/All/Changed
 * segmented switch on the left, the "Find a setting" search box filling
 * the rest. Together they replace every per-section "⏎ N advanced" fold.
 * All colour rides the existing --mt-* tokens (amber = var(--mt-accent))
 * so all four themes restyle for free — no hex literals. */
/* .cb-inspector-reveal rule removed (INSP-02 / INSP-04 — Phase 118 Plan 04):
   The wrapper div that held FindSettingBox inside the Data drawer was removed
   when FindSettingBox moved to the top of InspectorPane. The rule had no
   remaining effect once its only live usage was gone. */

/* --- "Find a setting" search box --- */
.cb-find-setting {
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  gap: 6px;
  padding: 0 8px;
  background: var(--mt-bg-deep);
  border: 1px solid var(--mt-border);
  border-radius: 6px;
  transition: border-color 140ms;
}

.cb-find-setting:focus-within {
  border-color: color-mix(in srgb, var(--mt-accent) 55%, var(--mt-border));
}

.cb-find-setting-icon {
  flex: 0 0 auto;
  font-size: 13px;
  color: var(--mt-muted);
  line-height: 1;
}

.cb-find-setting-input {
  flex: 1 1 auto;
  min-width: 0;
  appearance: none;
  border: none;
  outline: none;
  background: transparent;
  color: var(--mt-fg);
  font-family: var(--m-font-sans);
  font-size: 12px;
  padding: 6px 0;
}

.cb-find-setting-input::placeholder {
  color: var(--mt-muted);
}

.cb-find-setting-clear {
  flex: 0 0 auto;
  appearance: none;
  border: none;
  background: transparent;
  color: var(--mt-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
  transition: color 140ms;
}

.cb-find-setting-clear:hover {
  color: var(--mt-fg);
}

/* Empty-state line shown by FlatSearchResults when no settings match the
   search query (INSP-02 / D-01). Uses muted text so it reads as a hint,
   not an error. Padding matches the find-setting box height so the
   transition from box to no-results doesn't jump. */
.cb-find-no-results {
  padding: 10px 8px;
  color: var(--mt-muted);
  font-family: var(--m-font-sans);
  font-size: 12px;
}

/* === Layer-jump rows (INSP-03) =========================================== *
 * Rendered by FlatSearchResults when a search query matches settings in
 * Edge / Backdrop / Watermark layer drawers. Clicking a row clears the
 * search and opens the drawer that owns the setting.
 * Uses the same muted/mono aesthetic as the rest of the inspector panel.
 * --------------------------------------------------------------------- */

.cb-find-layer-jumps {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--mt-border);
}

.cb-find-layer-jump-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 5px 8px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  text-align: left;
  transition: background 0.1s, border-color 0.1s;
}

.cb-find-layer-jump-btn:hover {
  background: var(--mt-bg-elev);
  border-color: var(--mt-border);
}

.cb-find-layer-jump-label {
  font-family: var(--m-font-sans);
  font-size: 12px;
  color: var(--mt-fg);
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cb-find-layer-jump-hint {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  flex-shrink: 0;
  white-space: nowrap;
}

/* === HelpHintInline disclosure body (walkthrough variant) =============== *
 * Multi-paragraph worked-example body that sits below the Field's input,
 * read in place. Connected to the field above via a 1px left accent.
 * --------------------------------------------------------------------- */

.cb-help-hint-inline-body {
  margin-top: 6px;
  padding: 8px 10px;
  border-left: 1px solid var(--mt-border);
  background: var(--mt-bg-deep);
  color: var(--mt-fg);
  font-family: var(--m-font-sans);
  font-size: 12px;
  font-weight: var(--mt-weight-regular);
  line-height: 1.5;
}

/* === Header renderer placeholder ("brand (loading…)" etc.) ============== *
 * Tiny dim label shown while a deferred header widget global resolves.
 * --------------------------------------------------------------------- */

.cb-header-placeholder {
  opacity: 0.5;
  font-size: 11px;
}

/* === Audit / binding-drift table renderer ============================== *
 * Renders the catalog binding-drift audit response. Mostly hardcoded
 * pre-token rgba() fallbacks paired with --mt-* primary values — preserved
 * as-is to keep the visual identical to the pre-migration baseline.
 * --------------------------------------------------------------------- */

.cb-binding-drift-summary {
  font-family: var(--m-font-mono, monospace);
  font-size: calc(12px * var(--m-renderer-text-scale, 1));
  padding: 6px 10px;
  background: var(--mt-surface, rgba(255, 255, 255, 0.04));
  border-bottom: 1px solid var(--mt-border, rgba(255, 255, 255, 0.10));
  display: flex;
  flex-wrap: wrap;
  gap: 0 12px;
  user-select: text;
}

/* Per-mode count cell inside the summary bar; colour + weight injected
 * by JSX via --cb-cell-* CSS vars (mode-specific). */
.cb-summary-bar-cell {
  color: var(--cb-cell-color);
  font-weight: var(--cb-cell-weight);
}

.cb-binding-row {
  padding: 4px 12px 4px 24px;
  border-bottom: 1px solid var(--mt-border-lo, rgba(255, 255, 255, 0.05));
  font-size: 11px;
  font-family: var(--m-font-mono, monospace);
  line-height: 1.7;
}

.cb-binding-row__title {
  font-weight: var(--mt-weight-semibold);
  color: var(--mt-fg);
  display: flex;
  align-items: center;
  gap: 6px;
}

.cb-binding-row__crosshost {
  font-size: 9px;
  padding: 1px 4px;
  background: var(--mt-warn, #f59e0b);
  color: var(--mt-bg, #0f172a);
  border-radius: 3px;
  font-weight: var(--mt-weight-bold);
}

.cb-binding-row__meta {
  color: var(--mt-fg-muted, rgba(255, 255, 255, 0.55));
}

.cb-failure-mode-group {
  border-bottom: 1px solid var(--mt-border, rgba(255, 255, 255, 0.10));
}

.cb-failure-mode-group__header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  cursor: pointer;
  user-select: none;
  background: var(--mt-surface-lo, rgba(255, 255, 255, 0.02));
}

.cb-failure-mode-group__chevron {
  font-size: 10px;
  color: var(--mt-fg-muted, rgba(255, 255, 255, 0.45));
}

.cb-failure-mode-group__label {
  font-weight: var(--mt-weight-semibold);
  font-family: var(--m-font-mono, monospace);
  font-size: 12px;
  color: var(--cb-mode-color, var(--mt-fg));
}

.cb-failure-mode-group__desc {
  font-size: 11px;
  color: var(--mt-fg-muted, rgba(255, 255, 255, 0.55));
}

.cb-failure-mode-group__count {
  margin-left: auto;
  font-size: 11px;
  font-family: var(--m-font-mono, monospace);
  font-weight: var(--mt-weight-regular);
  color: var(--mt-fg-muted, rgba(255, 255, 255, 0.35));
}

.cb-failure-mode-group__count.has-rows {
  font-weight: var(--mt-weight-bold);
  color: var(--cb-mode-color, var(--mt-fg));
}

/* Top-level shell for BindingDriftTableRenderer. */
.cb-binding-drift-shell {
  font-size: calc(12px * var(--m-renderer-text-scale, 1));
  font-family: var(--m-font-mono, monospace);
  overflow: auto;
}

.cb-binding-drift-empty {
  padding: 16px;
  color: var(--mt-fg-muted, rgba(255, 255, 255, 0.4));
  font-size: calc(12px * var(--m-renderer-text-scale, 1));
}

.cb-binding-drift-error {
  padding: 16px;
  color: var(--mt-err, #ef4444);
  font-size: calc(12px * var(--m-renderer-text-scale, 1));
}

.cb-binding-drift-warning {
  padding: 4px 10px;
  background: var(--mt-warn-bg, rgba(245, 158, 11, 0.12));
  color: var(--mt-warn, #f59e0b);
  font-size: calc(11px * var(--m-renderer-text-scale, 1));
  border-bottom: 1px solid var(--mt-border, rgba(255, 255, 255, 0.10));
}

.cb-binding-drift-footer {
  padding: 4px 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.cb-binding-drift-footer__timestamp {
  font-size: calc(10px * var(--m-renderer-text-scale, 1));
  color: var(--mt-fg-muted, rgba(255, 255, 255, 0.4));
}

.cb-binding-drift-footer__refresh {
  font-size: calc(10px * var(--m-renderer-text-scale, 1));
  padding: 2px 8px;
  cursor: pointer;
  background: var(--mt-surface, rgba(255, 255, 255, 0.07));
  color: var(--mt-fg);
  border: 1px solid var(--mt-border, rgba(255, 255, 255, 0.15));
  border-radius: 3px;
}

/* === Action-result modal body ("Invoking adapter.action…" etc.) ========= *
 * Sits inside `<div class="manifold-modal-body">` for the
 * ModalActionResultRenderer; carries the pre-migration explicit font
 * stack + token-aligned size/color so the modal body text is theme-aware
 * but visually identical to the inline baseline.
 * --------------------------------------------------------------------- */

.cb-modal-result-body {
  font-family: var(--m-font-sans);
  font-size: var(--mt-text-md-size, 13px);
  line-height: 1.5;
  white-space: pre-wrap;
  color: var(--m-text, var(--mt-fg));
}

.cb-modal-result-body.is-loading {
  color: var(--m-text-muted, var(--mt-muted));
  white-space: normal;
}

.cb-modal-result-body__code {
  font-family: var(--m-font-mono);
}

.cb-modal-result-headline {
  font-weight: var(--mt-weight-semibold);
  color: var(--m-error, var(--mt-warn));
  margin-bottom: 6px;
}

.cb-modal-result-hint {
  color: var(--m-text-muted, var(--mt-muted));
  font-size: var(--mt-text-sm-size, 12px);
}

.cb-modal-result-error-bg {
  background: color-mix(in srgb, var(--m-error, var(--mt-warn)) 18%, transparent);
}

/* === CardRenderer outer frame ========================================== *
 * The root <div> of every rendered card. All four aesthetic keys are
 * runtime-driven (background = cardBg, border = cardBorder, radius =
 * cardRadius, padding = headerMode ? 0 : cardStyle.padding ?? 6), so the
 * class reads them through CSS custom properties the JSX writes into the
 * inline-style block. Pattern parallels .cb-advanced-toggle (19-04a) and
 * .cb-summary-bar-cell — keeps the className purely structural while
 * letting state inject the dynamic value.
 *
 * Defaults: bg=transparent / border=none / radius=0 / padding=0 so a
 * card that omits any of the props degrades safely (caller almost always
 * sets bg + border + radius; padding has its own headerMode branch).
 * --------------------------------------------------------------------- */

.cb-card-frame {
  background: var(--cb-card-bg, transparent);
  border: var(--cb-card-border, none);
  border-radius: var(--cb-card-radius, 0);
  padding: var(--cb-card-padding, 0);
  /* Self-contained stacking context. The four-layer model gives inner layers
   * z-index up to 2 (content) and 1 (watermark); without isolation the frame
   * is position:relative;z-index:auto, which does NOT create a stacking context,
   * so those z-indexes leak into the .cb-card-grab context and paint OVER the
   * edit-mode chrome that sits as the frame's siblings (the z-badge, lock glyph,
   * kebab — all z-index:auto). isolation:isolate traps the layer z-indexes inside
   * the frame so the chrome paints above the card content again. Harmless for old
   * cards (their layers are transparent) and a latent-bug guard against any widget
   * z-index escaping the card box. */
  isolation: isolate;
}

/* D-02/D-03 grow-mode fill (Phase 110, 110-06 gap fix) ==================== *
 * Grow mode sets the OUTER card box to min-height:h (so it can expand past the
 * saved height when content is taller — CANVAS-01 reads the real rendered
 * height for make-column / resize). But min-height alone is an INDEFINITE
 * height, and .cb-card-frame uses height:100% (CardRenderer fillHeight). Per
 * CSS2.1 §10.5, height:100% against an indefinite parent resolves to auto, so
 * the frame collapses to its CONTENT height and floats at the top of a tall
 * box — leaving a big empty gap and dropping the edit-mode selection ring +
 * z-badge far below the visible content (Karl, 110 smoke). Same trap the
 * .cb-adv-card-body rule (~7269) documents for the resize editor.
 *
 * Fix: make the grow box a flex column and let the frame flex-grow to FILL the
 * minimum height, while still letting a card whose content exceeds h push the
 * box taller (CANVAS-01 grow preserved). Two parts matter and both were learned
 * the hard way via live render-verify:
 *   1. height:auto !important — the frame carries inline height:100% from
 *      CardRenderer's fillHeight. Under this flex column that 100% resolves
 *      CIRCULARLY against the container's min-height (so a min-height:h box pins
 *      the frame to h and CLIPS taller content — grow broken). Forcing height
 *      back to auto makes the frame's flex-basis its real content height, so it
 *      grows past h when content overflows. !important is required only because
 *      the height:100% is an inline style (higher specificity than any selector).
 *   2. NO min-height:0 — the flex item's default min-height:auto (= min-content)
 *      is what FORCES the box to grow when content is taller than h. Setting
 *      min-height:0 (an earlier attempt) let the frame shrink below its content
 *      and clip instead of growing. Leave it at the auto default.
 * flex-grow:1 still fills the box when content is SHORTER than h (no empty gap,
 * the ring + z-badge hug the content). Scoped to .cb-card-grow only — clip/shrink
 * keep height:h (a definite height) so their height:100% chain fills as before. */
.cb-card-grow {
  display: flex;
  flex-direction: column;
}
.cb-card-grow > .cb-card-frame {
  flex: 1 0 auto;
  height: auto !important;
}

/* === CardRenderer four-layer model (Phase 105) ======================== *
 * Four absolute divs always present inside .cb-card-frame. Each fills
 * the frame via position:absolute; inset:0 so they exit grid flow and
 * never participate in the outer frame's display:grid context.
 *
 * Values flow through --cb-card-layer-* tokens set inline by CardRenderer.
 * Off/neutral layers render as background:transparent / opacity:0 —
 * never absent from the DOM. State toggling is CSS-property-driven, not
 * conditional mount/unmount, so there are no React reconciliation costs
 * or animation flashes when a layer is turned on/off.
 *
 * Old cards (no card.style.layers) see all tokens at their transparent
 * defaults listed here. The legacy --cb-card-bg token still drives their
 * visual via the .cb-card-frame background rule above — this layer block
 * adds nothing to those cards. Backward-compatibility is preserved (D-01).
 * -------------------------------------------------------------------- */

.cb-card-layer--frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: transparent;            /* Phase 138 §SKIN-05: flood-safe default — unsupporting browsers
                                       * (Safari < 15.4, Firefox < 90) see an invisible frame div rather
                                       * than a full-card color flood. The frame color and mask are
                                       * re-applied inside @supports below, where the ring punch-out
                                       * can confine the color to the edge band. */
  pointer-events: none;
  z-index: 3;                     /* Phase 113 §999.105: band paints LAST — above content (z:2) so
                                   * the rounded edge covers the squared corners of the layers underneath.
                                   * Old value was 0 (frame behind surface), which let the surface
                                   * background bleed past the rounded edge at each corner. */

  box-sizing: border-box;
  border-radius: var(--cb-card-radius, 0px);
  padding: var(--cb-card-frame-thickness, 1px);

  /* Phase 113 §999.105 + §999.102: paint ONLY the edge band, with BOTH corners rounded.
   *
   * This layer is a full-bleed sheet carrying the frame/edge "fill" (a flat colour or the
   * user's edge gradient) as its background. We must show only the ring at the edge and
   * keep the middle see-through, or on a transparent-backdrop card the fill floods the whole
   * card and hides the content.
   *
   * DO NOT go back to a single inset() clip-path — using clip-path with inset() KEEPS the
   * middle and trims off the outer band, which is the OPPOSITE of what we want. That mistake
   * shipped once: the edge gradient ended up painting the inside of the card and the band
   * vanished. (The phrase is kept un-colon'd here on purpose so the CORNER-03 source-grep
   * guard, which forbids that declaration, is not tripped by this very comment.)
   *
   * Technique (the well-worn "gradient ring" trick): pad the element by the band thickness,
   * then lay two mask layers and punch the inner one out —
   *   layer 1 (clipped to the content box): a solid fill over only the padded-in middle. This
   *           is the hole; because it is clipped to the content box, its corners are rounded to
   *           the inner radius (outer radius minus the padding) automatically.
   *   layer 2 (border box, the default): a solid fill over the whole rounded element.
   *   mask-composite: exclude / -webkit-mask-composite: xor — keep only where EXACTLY ONE
   *           layer paints, i.e. the ring between the two boxes. Outer corner rounds to
   *           border-radius; inner corner rounds to border-radius minus thickness. No square
   *           inner corner (closes the Phase-107.3 note), no flood (keeps §999.102).
   *
   * "black" is used for its alpha only (masks read alpha, not colour); the keyword form
   * avoids raw hex literals in this block.
   *
   * Watch-outs carried forward from §999.102:
   * 1. Transparent-backdrop cards: the middle stays punched out, so the interior is see-through.
   * 2. The gradient fills the full element box and only the ring is shown, so it still reads
   *    directionally across the band (the flipGradient / bezel look is preserved).
   * 3. Masks are per-element, not inherited — no interaction with the watermark or scrim layers.
   * 4. No JSX changes: --cb-card-frame-thickness and --cb-card-radius are set inline by
   *    buildLayerModel. Do NOT add inline aesthetic styles — the eslint deploy gate treats
   *    them as errors and silently keeps the OLD bundle live. */
}

/* Phase 138 §SKIN-05: ring punch-out + frame color, only on browsers with mask-composite.
 * On supporting browsers (Chrome 116+, Safari 15.4+, Firefox 90+) the inside rule wins by
 * source order (same specificity as the base rule above), re-applying the frame color and the
 * mask exclusion so only the edge band shows. On unsupporting browsers this block is skipped
 * entirely — the frame div stays transparent (the base background: transparent above). */
@supports (mask-composite: exclude) or (-webkit-mask-composite: xor) {
  .cb-card-layer--frame {
    background: var(--cb-card-frame-bg, transparent);
    -webkit-mask:
      linear-gradient(black, black) content-box,
      linear-gradient(black, black);
    -webkit-mask-composite: xor;          /* WebKit ring punch-out */
    mask:
      linear-gradient(black, black) content-box,
      linear-gradient(black, black);
    mask-composite: exclude;              /* standard ring punch-out */
  }
}

.cb-card-layer--surface {
  position: absolute;
  inset: var(--cb-card-frame-thickness, 1px);
  width: calc(100% - 2 * var(--cb-card-frame-thickness, 1px));
  height: calc(100% - 2 * var(--cb-card-frame-thickness, 1px));
  background: var(--cb-card-surface-bg, transparent);
  /* Round the surface to the INNER radius so the frame layer reads as an even
   * border ring. The surface is inset by the frame thickness; with square
   * corners the rounded frame (overflow-clipped) fills the whole corner arc
   * with frame colour, so corners looked like a chunky wedge that grew with the
   * radius slider. Inner radius = card radius − frame thickness, clamped >= 0.
   * The inset:0 layers (frame/watermark/content) need no radius — the frame's
   * overflow:hidden already clips them to the outer radius. */
  border-radius: max(0px, calc(var(--cb-card-radius, 0px) - var(--cb-card-frame-thickness, 1px)));
  pointer-events: none;
  z-index: 0;
}

.cb-card-layer--wm {
  position: absolute;
  /* CLAYER-01: inset by the frame thickness so the watermark never paints
   * over the border ring. Mirrors .cb-card-layer--surface exactly.
   * Inner radius keeps the watermark clipped to the rounded inner box. */
  inset: var(--cb-card-frame-thickness, 1px);
  width: calc(100% - 2 * var(--cb-card-frame-thickness, 1px));
  height: calc(100% - 2 * var(--cb-card-frame-thickness, 1px));
  border-radius: max(0px, calc(var(--cb-card-radius, 0px) - var(--cb-card-frame-thickness, 1px)));
  pointer-events: none;
  overflow: hidden;
  z-index: 1;
}

.cb-card-layer-wm-img {
  width: 100%;
  height: 100%;
  object-fit: var(--cb-card-wm-fit, contain);
  object-position: var(--cb-card-wm-position, center center);
  opacity: var(--cb-card-wm-opacity, 0);
  /* ICON-03: object-position sets the anchor, then transform: scale() grows
   * the image from its geometric center (transform-origin below), not from the
   * positioned corner. Parent overflow:hidden clips an oversized watermark at
   * the card boundary. */
  transform: scale(var(--cb-card-wm-scale, 1));
  transform-origin: center center;
  filter: blur(var(--cb-card-wm-blur, 0px));
  pointer-events: none;
  display: block;
}

.cb-card-layer--content {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  /* Content layer establishes its own grid context for widgets.
   * display:grid is set inline by CardRenderer (same as the old frame). */
}

.cb-card-content-panel {
  position: absolute;
  /* CLAYER-01(scrim): same full-bleed defect as the watermark — inset by the
   * frame thickness so an opaque scrim never bleeds over the border ring.
   * Mirrors .cb-card-layer--surface exactly. */
  inset: var(--cb-card-frame-thickness, 1px);
  width: calc(100% - 2 * var(--cb-card-frame-thickness, 1px));
  height: calc(100% - 2 * var(--cb-card-frame-thickness, 1px));
  border-radius: max(0px, calc(var(--cb-card-radius, 0px) - var(--cb-card-frame-thickness, 1px)));
  background: var(--cb-card-content-panel-bg, transparent);
  pointer-events: none;
  z-index: 0;
}

/* === Layer accordion (Inspector — Phase 105 LAYER-07) ================== *
 * Four-drawer one-at-a-time accordion in InspectorPane. Opening one drawer
 * collapses the others. Edge (layer-frame) is open by default (D-06).
 *
 * Open state uses max-height:none so a tall drawer (e.g. a multi-series Data
 * drawer) is never clipped — a former max-height:2000px cap cut off the lower
 * settings (INSP-01 #2, Phase 118). Trade-off: the open/close height can't
 * animate to/from `none`, so drawers snap rather than slide. An animated,
 * non-clipping version would need a grid-template-rows:0fr/1fr reveal with a
 * paddingless grid item.
 *
 * All color and size values route through --mt-* / --cb-* / --m-* tokens.
 * No hex literals, no inline aesthetic styles in JSX (eslint gate).
 * --------------------------------------------------------------------- */

.cb-layer-accordion {
  display: flex;
  flex-direction: column;
  gap: var(--mt-space-xs, 4px);
}

.cb-layer-drawer {
  display: flex;
  flex-direction: column;
}

.cb-layer-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 5px 8px;
  font-size: var(--mt-text-sm-size, 12px);
  font-weight: var(--mt-weight-semibold);
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 2px;
}

.cb-layer-drawer-header.is-open {
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  color: var(--mt-fg);
  border-color: var(--mt-border-strong, var(--mt-border));
}

.cb-layer-drawer-label {
  flex: 1;
  text-align: left;
}

.cb-layer-drawer-badge {
  font-size: var(--mt-text-xs-size, 10px);
  font-family: var(--m-font-mono);
  padding: 1px 6px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 33%, transparent);
  margin-right: var(--mt-space-xs, 4px);
}

.cb-layer-drawer-caret {
  font-size: 9px;
  color: var(--mt-muted);
  transition: transform var(--m-t-fast, 120ms ease);
  display: inline-block;
  flex-shrink: 0;
}

.cb-layer-drawer-caret.is-open {
  transform: rotate(180deg);
}

.cb-layer-drawer-body {
  overflow: hidden;
  max-height: 0;
  transition: max-height var(--m-t-norm, 220ms cubic-bezier(0.2, 0, 0, 1));
}

.cb-layer-drawer-body.is-open {
  /* INSP-01 #2 root-cause fix: was max-height:2000px, which (with overflow:hidden
     above) CLIPPED any open drawer taller than 2000px — a multi-series chart's
     Data drawer (source picker + many series rows) easily exceeds that, so its
     lower settings were rendered but unreachable: the inspector scrolled to its
     max and the rest was simply cut off (Karl's "freezes at the same point" /
     "didn't make it to the bottom"). `none` removes the cap so the drawer takes
     its full content height and .cb-inspector-body scrolls to all of it.
     Trade-off: the open/close height transition can't animate to/from `none`, so
     drawers snap open instead of sliding. Reachability > the slide; an animated
     version would need the grid-template-rows:0fr/1fr reveal with a paddingless
     grid item. */
  max-height: none;
}

/* Drawer body inner — padding container inside the expanded drawer body */
.cb-layer-drawer-body-inner {
  padding: var(--mt-space-sm, 8px) var(--mt-space-sm, 8px) var(--mt-space-md, 14px);
}

/* Watermark empty-state copy */
.cb-layer-wm-empty {
  color: var(--mt-dim);
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  padding: var(--mt-space-xs, 4px) 0 var(--mt-space-sm, 8px);
}

/* Watermark URL text input */
.cb-layer-wm-url-input {
  width: 100%;
  box-sizing: border-box;
  padding: 5px 7px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  margin-bottom: var(--mt-space-xs, 4px);
}

/* Watermark fit / position selects */
.cb-layer-wm-select {
  width: 100%;
  box-sizing: border-box;
  padding: 5px 7px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  margin-bottom: var(--mt-space-xs, 4px);
}

/* Watermark remove button top margin */
.cb-layer-wm-remove-btn {
  margin-top: var(--mt-space-sm, 8px);
}

/* Watermark inline remove confirm */
.cb-layer-wm-confirm {
  padding: var(--mt-space-xs, 4px) 0;
}

.cb-layer-wm-confirm-text {
  display: block;
  font-size: var(--mt-text-xs-size, 11px);
  color: var(--mt-error, var(--mt-warn));
  margin-bottom: var(--mt-space-xs, 4px);
}

.cb-layer-wm-confirm-btns {
  display: flex;
  gap: var(--mt-space-xs, 4px);
}

/* Data settings divider — separates layer controls from the 999.20 data editor */
.cb-layer-data-divider {
  display: flex;
  align-items: center;
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-bold);
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: var(--mt-space-sm, 8px) 0 var(--mt-space-xs, 4px);
  border-top: 1px solid var(--mt-border);
  margin-top: var(--mt-space-sm, 8px);
}

/* === Watermark drawer — Grid section (WMARK-02 / Phase 145) ============ *
 * Layout classes for the Grid subsection appended to buildWmBody.
 * Aesthetic rules (colors, borders, type) use --mt-* tokens.
 * The cb-layer-data-divider and cb-layer-wm-confirm* classes (above) are
 * reused for the Grid section divider and the Remove-grid confirm row.
 * --------------------------------------------------------------------- */

/* Line color row: swatch/picker input + optional "Use theme" text-button */
.cb-wm-grid-color-row {
  display: flex;
  align-items: center;
  gap: var(--mt-space-xs, 4px);
  margin-bottom: var(--mt-space-xs, 4px);
}

/* Native color input — constrained to a small swatch-sized hit target */
.cb-wm-grid-color-input {
  width: 36px;
  height: 24px;
  padding: 2px;
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  background: var(--mt-bg);
  cursor: pointer;
}

/* "Use theme" text-button — appears only when a per-card line-color override is set.
   Clicking it clears grid.lineColor and reverts to the active theme's --mt-border. */
.cb-wm-use-theme-btn {
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 2px;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* === Cell-gradient backdrop ============================================ *
 * 1×1 grid item painted behind widgets at a specific cell to host a
 * gradient background. The background string is state-driven (gradient
 * CSS computed per cell) — flows through --cb-cell-gradient-bg.
 * --------------------------------------------------------------------- */

.cb-card-cell-gradient {
  background: var(--cb-cell-gradient-bg, transparent);
  border-radius: var(--m-radius-sm, 6px);
}

/* === Repeat-mode catalog message ======================================= *
 * The "loading service catalog…" / "sign in to see services" / "service
 * catalog fetch failed" banner painted into the card grid when
 * card.repeat.kind === 'services' and the catalog isn't ready or errored.
 * Per-state color overrides ride --cb-repeat-msg-color so the same shell
 * handles muted (loading), dim (auth gate), warn (fetch fail).
 * --------------------------------------------------------------------- */

.cb-repeat-msg {
  padding: var(--mt-space-md, 12px);
  color: var(--cb-repeat-msg-color, var(--mt-muted));
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
}

/* === Zebra-stripe backdrop ============================================= *
 * Painted across a row band of the card grid to provide alternating row
 * fill in repeat-mode tables. The fill colour is state-driven (theme
 * token resolved per card) — flows through --cb-zebra-bg.
 * --------------------------------------------------------------------- */

.cb-card-zebra {
  background: var(--cb-zebra-bg, transparent);
}

/* === Scaled-content wrapper border ===================================== *
 * When card.style.contentScale or contentBorder is set on a widget, the
 * renderer wraps the inner content in a transform-scaled <div>. The
 * border colour is state-driven (resolved from token name) so flows
 * through --cb-scale-border-color; corner radius is the standard 6px.
 * --------------------------------------------------------------------- */

.cb-scale-wrapper {
  /* Default to border:none so the wrapper doesn't claim 1px of layout
   * when no border is requested. When --cb-scale-border-color is set
   * by JSX, the modifier rule below paints the visible border. */
  border: none;
  border-radius: var(--m-radius-sm, 6px);
}

.cb-scale-wrapper.has-border {
  border: 1px solid var(--cb-scale-border-color, currentColor);
}

/* === Card title (CardWithHeader, CardCell header bands) ================ *
 * The card-name header typography — Space Grotesk 14px / weight 700 /
 * tight tracking / fg colour. Appears in four places: the standalone
 * CardWithHeader chrome, plus three branches of CardCell (rest_discover
 * status tile, missing-instance tile, normal-card header band). One
 * class shared by all four so a future typography change (font swap,
 * size bump) lands in one place.
 *
 * The .with-flex modifier adds `flex:1` + ellipsis behavior used by
 * CardCell's header band where the title shares the row with edit
 * affordances; CardWithHeader's standalone use omits .with-flex.
 * --------------------------------------------------------------------- */

.cb-card-title {
  font-family: var(--m-font-sans);
  /* Default 14px == the `md` text token. The card-frame inspector's
     "Title size" control sets --cb-card-title-size (resolved from a named
     text-size token) on the title element to override; unset falls back to
     14px so existing cards render pixel-identical. */
  font-size: var(--cb-card-title-size, 14px);
  font-weight: var(--mt-weight-bold);
  /* #164: the `title` part of a card. --mt-part-title is set on :root only
     when the saved mapping names a colour for card titles; unset — which is
     every dashboard nobody has run the theme wizard on — the fallback fires
     and this paints the same var(--mt-fg) it always did.

     #181: THE PART NOW HAS TWO SITES AND THIS IS NOT THE ONE ON A CANVAS.
     This rule is the card LIST — CardWithHeader, which draws a card's name in
     the picker, and whose count on a running dashboard is zero. The headings
     people point at and call card titles are drawn by the `Label` component in
     components/card-builder-renderers.jsx, which joined this part in #181.
     A title in the list is still a title, so this stays.

     The two sites disagree on what they paint when nobody has chosen — `fg`
     here, `muted` on a canvas — and lib/card-parts.js's header carries the
     whole of why it records `muted` and what that costs this rule. Read it
     there; #176 is where the two are reconciled, and this line moves with the
     registry row or not at all. */
  color: var(--mt-part-title, var(--mt-fg));
  letter-spacing: -0.01em;
}

.cb-card-title.with-flex {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* === CardWithHeader top-padding band =================================== *
 * The 2px-vertical / 4px-horizontal padding around the standalone
 * CardWithHeader title (no flex-row chrome, just a name above the card
 * body). Tight enough that mt-pad-xs (4px all-axis) would visually
 * over-pad it — keep the asymmetric 2px/4px shape.
 * --------------------------------------------------------------------- */

.cb-card-with-header-title {
  padding: 2px 4px;
}

/* === CardCell header row =============================================== *
 * The flex band hosting the card title plus optional drag-handle / edit
 * / remove affordances. Sits at the top of CardCell when showHeader is
 * true. Same shape in all three CardCell branches.
 * --------------------------------------------------------------------- */

.cb-card-header-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 2px 4px;
}

/* === Drag handle ⠿ ==================================================== *
 * Six-dot grab handle on the left edge of CardCell's header row when
 * dnd is wired. Visible only in edit mode.
 * --------------------------------------------------------------------- */

.cb-drag-handle {
  cursor: grab;
  user-select: none;
  font-size: 13px;
  line-height: 1;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  padding: 2px 4px;
  flex-shrink: 0;
}

/* === Mini chrome button (✎ edit / × remove) =========================== *
 * Small mono-typeset button used at the right edge of CardCell's header
 * row. Identical shape to the "Edit → inspect" / "Edit → reassign"
 * affordance below; the difference is a block-display + auto-margin
 * wrapper applied via the .is-block modifier.
 * --------------------------------------------------------------------- */

.cb-mini-button {
  padding: 3px 8px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-muted);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
}

.cb-mini-button.is-block {
  display: block;
  margin: 4px auto 0;
}

/* === Missing-instance caption ========================================== *
 * The italic small-print "instance 'X' no longer exists" line shown in
 * CardCell's missing-instance branch under the placeholder tile.
 * --------------------------------------------------------------------- */

.cb-missing-instance-caption {
  font-family: var(--m-font-mono);
  font-size: 10px;
  font-style: italic;
  color: var(--mt-muted);
  padding: 4px 8px;
  text-align: center;
}

/* === CardCell drop-indicator wrapper =================================== *
 * CardCell's outer wrapper carries a `box-shadow: inset 3px 0 0 accent`
 * during drag-over to mark the drop target. State-driven (only painted
 * when dnd.isDragOver), so flows through --cb-card-drop-shadow.
 * --------------------------------------------------------------------- */

.cb-card-cell {
  box-shadow: var(--cb-card-drop-shadow, none);
}

/* === Warn-tinted error frame =========================================== *
 * Mono-font, warn-coloured callout used for two siblings: (a) the
 * card-validation error list shown when validateCard() returns errs, and
 * (b) the "Missing card" placeholder rendered by _renderCardCell when a
 * tab references a card id that no longer exists. Same warn ring + warn
 * text + small mono body — distinct messages, identical chrome.
 *
 * The 14px and 12px paddings from the pre-migration sites both round to
 * --mt-space-md (12px). The 2px shift in the validation-errors site is
 * inside the 19-SCREENSHOT-MANIFEST tolerance rubric.
 * --------------------------------------------------------------------- */

.cb-warn-frame {
  padding: var(--mt-space-md);
  border: 1px solid var(--mt-warn);
  border-radius: var(--m-radius-md, 8px);
  color: var(--mt-warn);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  line-height: var(--mt-text-xs-lh, 1.4);
}

.cb-warn-frame__heading {
  font-weight: var(--mt-weight-bold, 700);
  margin-bottom: var(--mt-space-xs, 4px);
}

/* === Action widget — anonymous / insufficient-role placeholder ========= *
 * Replaces the action button with a small lock-icon caption when the
 * viewer's role falls below the action's declared minRole.
 * Also worn by SignInPlaceholder (Phase 19-04b) — same dashed-frame
 * lock-icon shape signals "sign in / wrong role" identically in both
 * cases. SignInPlaceholder additionally wears .cb-sign-in-placeholder
 * as a no-op marker so anyone grepping for the component finds it.
 * --------------------------------------------------------------------- */

.cb-sign-in-placeholder {
  /* No styling — marker class; behaviour comes from .cb-action-locked
   * plus the JS hover handlers in card-builder.jsx::SignInPlaceholder. */
}

.cb-action-locked {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: var(--m-text-muted, var(--mt-dim));
  background: var(--m-bg, var(--mt-bg));
  border: 1px dashed var(--m-border, var(--mt-border));
  border-radius: 8px;
  padding: 8px;
}

.cb-action-locked__inner {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-direction: column;
  text-align: center;
}

.cb-action-locked__label {
  font-family: var(--m-font-mono);
  font-size: 11px;
  font-weight: var(--mt-weight-semibold);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  line-height: 1.3;
}

/* === Action widget — inline-result "clear" affordance ================== *
 * Tiny × top-right of an inline action result that re-runs the gateway
 * action on click.
 * --------------------------------------------------------------------- */

.cb-action-clear-result {
  position: absolute;
  top: 6px;
  right: 6px;
  font-family: var(--m-font-mono);
  font-size: 10px;
  padding: 2px 8px;
  background: transparent;
  color: var(--m-text-muted, var(--mt-muted));
  border: 1px solid var(--m-border, var(--mt-border));
  border-radius: 3px;
  cursor: pointer;
}

/* === Action widget — primary button override =========================== *
 * The button picks up `.manifold-button` shape from theme.css; this rule
 * applies the per-action typography that was previously inline.
 * --------------------------------------------------------------------- */

.cb-action-button {
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-semibold);
  padding: 6px 14px;
  font-size: var(--cb-action-button-font-size, 13px);
}

/* 227-07: the size above never reached the screen, and the reason is the cascade
 * rather than the code. The rendered button carries BOTH classes —
 * `manifold-button primary cb-action-button` (card-builder.jsx:2862-2864, :2907) —
 * and theme.css:818 declares `.manifold-button { font-size: 12px }`. Two single-class
 * selectors have IDENTICAL specificity (0,1,0), and theme.css is the LAST stylesheet
 * loaded, so it won every time: the size menu computed a value and the cascade threw
 * it away. The conformance walk measured exactly that — IGNORES, 12px -> 12px, on
 * three separate runs — while the resolver's own unit tests passed.
 *
 * `.manifold-button.cb-action-button` is (0,2,0), so it wins on specificity and no
 * longer depends on which sheet loads last.
 *
 * It carries ONLY font-size, deliberately. All four declarations in the rule above
 * lose to theme.css, not just the size — measured in the running page, an action
 * button renders sans/500/8px-14px where the rule asks for mono/600/6px-14px. Karl
 * approved one change on 2026-08-01: every existing action button moving 12px -> 13px.
 * Promoting the other three would also change every action button's typeface, weight
 * and padding, which nobody agreed to. They are left exactly as they render today and
 * are recorded as an open finding in 227-VERIFICATION.md.
 * tests/227-action-button-sizing.test.js pins both halves of that.
 * -------------------------------------------------------------------------------- */

.manifold-button.cb-action-button {
  font-size: var(--cb-action-button-font-size, 13px);
}

/* === Action error badge (Phase 52 Plan 03, D-03) =========================
 * The persistent inline badge that sits under a write-action button after a
 * failed restart/search. It is deliberately LOUD (error colour, a solid dot)
 * so a failure is hard to miss, but it clears itself on the next healthy poll
 * or successful action — so a transient hiccup never leaves it lingering.
 * Reuses the theme's --mt-error token like the rest of the error surface; no
 * inline aesthetic values on the element (v1.4 CSS contract). Transparent
 * background — the badge tints text + border only, never fills the cell. */
.cb-action-error-badge {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  padding: 3px 8px;
  border: 1px solid color-mix(in srgb, var(--mt-error) 40%, transparent);
  border-radius: 6px;
  background: transparent;
  color: var(--mt-error);
  font-family: var(--m-font-mono);
  font-size: 11px;
  line-height: 1.3;
}
.cb-action-error-badge__dot {
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--mt-error);
}
.cb-action-error-badge__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============================================================================
 * Phase 19 Plan 04c — CardTabRenderer / section chrome
 * ----------------------------------------------------------------------------
 * These classes host the section-layout chrome that wraps every dashboard
 * card cluster: the section frame, the section header bar (label + toolbar
 * buttons + HEADER pin badge), the inline section editor frame + close
 * button, the empty-section drop placeholder, the cell-hover drop overlay,
 * the loose-zone caption + grid, and the "+Add section" / "+Add a card here"
 * family of edit-mode buttons.
 *
 * CardTabRenderer is the wrapper every dashboard card walks through in the
 * read path, so any regression here shows on every card simultaneously. Keep
 * the structural classes pure (no per-state values) and route per-section /
 * per-state values through the --cb-section-* / --cb-drop-* / --cb-overlay-*
 * CSS custom properties so a single class set covers every section.
 * ============================================================================ */

/* === CardTabRenderer top-level loading message ======================== *
 * The "Loading cards…" placeholder shown while CardTabRenderer's initial
 * fetchTabSections / fetchTabLooseCards are in flight. One-shot, short-
 * lived; mono + muted + xs to match the rest of the inspector's loading
 * idiom (cb-binding-drift-empty / cb-header-placeholder are siblings).
 * --------------------------------------------------------------------- */

.cb-card-tab-loading {
  padding: var(--mt-space-lg, 16px);
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  line-height: var(--mt-text-xs-lh, 1.4);
}

/* === Empty-tab placeholder ============================================ *
 * Rendered when totalCards === 0 in read mode (no manage perms). Dashed
 * frame with centered "No cards on this tab yet." headline + optional
 * caller-supplied hint paragraph. Larger than the warn-frame chrome:
 * 24px vertical / 18px horizontal padding, 10px radius, larger surface
 * because the tab is otherwise empty.
 * --------------------------------------------------------------------- */

.cb-empty-tab-placeholder {
  padding: 24px 18px;
  margin: var(--mt-space-md, 14px);
  border: 1px dashed var(--mt-border-strong, var(--mt-border));
  border-radius: 10px;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 12px);
  text-align: center;
}

.cb-empty-tab-placeholder__headline {
  font-weight: var(--mt-weight-bold, 700);
  margin-bottom: var(--mt-space-xs, 6px);
  color: var(--mt-dim);
}

/* === Section wrap (the per-section outer frame) ======================== *
 * Every section in CardTabRenderer is wrapped in a div whose three
 * aesthetic keys (margin-top, border, background) are state-driven by the
 * section's optional .style block (border on/off, marginTop number,
 * backgroundGradient css). State flows through --cb-section-* custom
 * props the JSX writes into the inline-style block; the sticky/zIndex
 * keys stay inline (state-driven layout, not aesthetic).
 *
 * Defaults are no-op so a section with no .style renders identical to
 * pre-migration. The .has-border modifier paints the actual border +
 * top-corner radius (border:none plus radius>0 would draw nothing but
 * still claim a paint layer — modifier-gated keeps the wrapper truly
 * transparent until border is requested).
 * --------------------------------------------------------------------- */

.cb-section-wrap {
  background: var(--cb-section-bg, transparent);
  /* Optional negative-or-positive offset between adjacent sections. Default
   * is 0 (no gap) to match pre-migration `marginTop: sMargin || undefined`
   * — when the JSX omits the custom property the cascade falls through. */
  margin-top: var(--cb-section-margin-top, 0);
}

.cb-section-wrap.has-border {
  border: 1px solid var(--cb-section-border-color, var(--mt-border));
  border-top-left-radius: var(--cb-section-radius, 0);
  border-top-right-radius: var(--cb-section-radius, 0);
}

/* overflow:hidden is only painted when the radius is non-zero (matches the
 * pre-migration `overflow: borderOn && sRadius > 0 ? 'hidden' : undefined`).
 * A 0-radius border doesn't need clipping; a positive-radius border does so
 * the inner grid content doesn't paint past the rounded corners. */
.cb-section-wrap.has-border.has-radius {
  overflow: hidden;
}

/* === Section header bar (label + toolbar) ============================ *
 * Flex row above each section: section label on the left, toolbar
 * buttons on the right (move-up / move-down / edit / delete). The label
 * typography is Space Grotesk uppercase tracked label-style; same shape
 * Karl uses elsewhere for section names. Italic when no label is set
 * (placeholder "(unnamed section)"); normal when labeled.
 * --------------------------------------------------------------------- */

.cb-section-header-bar {
  display: flex;
  align-items: center;
  gap: var(--mt-space-sm, 8px);
  padding: 10px 18px 4px;
}

.cb-section-header-label {
  font-family: var(--m-font-sans);
  font-size: 13px;
  font-weight: var(--mt-weight-bold, 700);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--mt-dim);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cb-section-header-label.is-unlabeled {
  color: var(--mt-muted);
  font-style: italic;
}

/* HEADER pin badge sits inline with the label when the section is
 * pinned to the top of the tab. */
.cb-section-header-pin-badge {
  margin-left: var(--mt-space-sm, 8px);
  font-size: 9px;
  font-weight: var(--mt-weight-semibold, 600);
  padding: 1px 5px;
  border-radius: 3px;
  color: var(--mt-muted);
  border: 1px solid var(--mt-border);
}

/* === Cell-hover drop overlay ========================================== *
 * Painted at the cell the user is currently hovering with a dragged
 * card. Position/left/top/width/height/zIndex stay inline (state-driven
 * layout — allowed). The aesthetic ring + background tint + text color
 * + mono typography all come from this class; the colour token is the
 * accent token (always var(--mt-accent)) so no custom-prop wiring needed.
 * --------------------------------------------------------------------- */

.cb-cell-hover-overlay {
  border: 2px dashed var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 20%, transparent);
  border-radius: var(--m-radius-sm, 4px);
  color: var(--mt-accent);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  font-weight: var(--mt-weight-bold, 700);
}

/* === Empty-section drop placeholder =================================== *
 * Rendered in place of card cells when a section is empty AND the user
 * has edit perms. Dashed-frame drop target with an idle + hover state.
 * The "+Add a card here" button sits inside it (uses .cb-add-card-here).
 * State (idle vs hovered) flips border colour + background tint + text
 * colour via the .is-hovered modifier.
 * --------------------------------------------------------------------- */

.cb-empty-section-drop {
  margin: 4px 18px 14px;
  padding: 18px 14px;
  border: 1px dashed var(--mt-border);
  border-radius: var(--m-radius-md, 8px);
  background: transparent;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  transition: border-color 80ms, background 80ms, color 80ms;
}

.cb-empty-section-drop.is-hovered {
  border-color: var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
  color: var(--mt-accent);
}

/* === "+Add a card here" affordance inside empty-section drop ========== *
 * Subdued mono button. Same chrome family as "+Add section": transparent
 * bg, dim text, 1px border, small radius, mono typography. 5/12 padding
 * (slightly tighter than .cb-add-section).
 * --------------------------------------------------------------------- */

.cb-add-card-here {
  padding: 5px 12px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  cursor: pointer;
}

/* === Loose-zone caption ============================================== *
 * Tiny italic subdued caption that introduces the loose-zone (cards
 * that live outside any section). Only shown in edit mode so users
 * know the unlabeled grid below is the "outside-sections" zone, not a
 * glitch.
 * --------------------------------------------------------------------- */

.cb-loose-zone-caption {
  padding: 10px 18px 4px;
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  font-style: italic;
  letter-spacing: 0.04em;
}

/* ============================================================================
 * Phase 19 Plan 04d (2026-05-26). Catalog tree (CatalogTree / LeafRow /
 * ActionRow), PreviewPane chrome (header, empty-cell ghost, linked-cell
 * toolbar, diagnostic row), SeriesEditor rows, LabelIconPickerInline grid,
 * and the BrandInspectorForm / ClockInspectorForm mode-switch buttons get
 * structural classes here so the JSX drops its inline aesthetic-key sites.
 *
 * Hover affordances on catalog rows and the +Add series button move from
 * inline onMouseEnter/onMouseLeave handlers to :hover pseudo-classes —
 * same pattern 19-04c established for the section toolbar.
 * ============================================================================
 */

/* === CatalogTree left-pane wrapper =================================== *
 * The outer pane that hosts the search input, add-* buttons, and the
 * scrollable provider list. Right border + bg-deep so the picker reads
 * as a distinct rail from the preview area.
 * --------------------------------------------------------------------- */

.cb-catalog {
  border-right: 1px solid var(--mt-border);
  background: var(--mt-bg-deep);
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.cb-catalog-loading {
  padding: var(--mt-space-md, 12px);
  color: var(--mt-muted);
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
}

.cb-catalog-search-bar {
  padding: 8px 10px;
  border-bottom: 1px solid var(--mt-border);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.cb-catalog-search-input {
  width: 100%;
  padding: 5px 8px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  box-sizing: border-box;
}

/* === CatalogTree add-* buttons (Static text / Range picker / SVG / …) === *
 * Base class is the dashed-border / transparent-bg / dim-text shape; two
 * modifiers cover the accent variants ("+ Header widget" uses the accent
 * dashed style; "+ Service Table…" uses a filled-accent style).
 * --------------------------------------------------------------------- */

/* Phase 92 (CARD-03 / KARL-DECISION-1): all add-buttons promoted to the
 * accent treatment. Previously the base class was dim/dashed and only
 * "+ Header widget" carried is-accent-dashed. Now the base class IS the
 * accent style — all seven buttons read uniformly without needing the
 * modifier. is-accent-dashed is left in place (harmless; still applied to
 * "+ Header widget" in the JSX) so no JSX change is needed. */
.cb-catalog-add-button {
  width: 100%;
  padding: 5px 8px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-semibold);
  background: transparent;
  color: var(--mt-accent);
  border: 1px dashed color-mix(in srgb, var(--mt-accent) 33%, transparent);
  border-radius: var(--m-radius-sm, 4px);
  cursor: pointer;
  text-align: left;
}

.cb-catalog-add-button.is-accent-dashed {
  /* Redundant now — base class carries the accent style. Kept so the JSX
   * class attribute on "+ Header widget" keeps working without change. */
  color: var(--mt-accent);
  border: 1px dashed color-mix(in srgb, var(--mt-accent) 33%, transparent);
}

.cb-catalog-add-button.is-accent-solid {
  background: color-mix(in srgb, var(--mt-accent) 6%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 40%, transparent);
}

/* "+ Header widget" submenu: 6-row sub-picker beneath the toggle */
.cb-catalog-header-picker {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 4px 4px 6px 12px;
}

/* Phase 92 (CARD-03): converged from solid-border/dim to accent-dashed so
 * header sub-picker items share the same visual treatment as the top-level
 * add-buttons — removes the third inconsistent style. */
.cb-catalog-header-picker-item {
  width: 100%;
  padding: 4px 8px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-accent);
  border: 1px dashed color-mix(in srgb, var(--mt-accent) 33%, transparent);
  border-radius: 3px;
  cursor: pointer;
  text-align: left;
}

.cb-catalog-header-picker-item__name {
  font-weight: var(--mt-weight-semibold);
}

.cb-catalog-header-picker-item__desc {
  opacity: 0.6;
  margin-left: 8px;
  font-size: 10px;
}

/* Phase 92 (CARD-02 / KARL-DECISION-2): static informational note for the
 * npm public registry source. Non-clickable; tells users the plugin exists
 * but needs per-user configuration before data appears in the catalog. Uses
 * dim/muted style to distinguish it from the add-action buttons above it. */
.cb-catalog-needs-setup {
  width: 100%;
  padding: 5px 8px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
  border-left: 2px solid var(--mt-border);
  background: transparent;
  cursor: default;
  text-align: left;
  opacity: 0.8;
}

/* === CatalogTree filter no-match empty state ========================= *
 * Rendered below the scrollable list when the search box is non-empty
 * but every provider was hidden by the filter (IN-01, Phase 92 code
 * review). Matches the shape of .cb-leaf-picker-empty-state so the two
 * picker trees read consistently on a no-match result.
 * --------------------------------------------------------------------- */
.cb-catalog-empty-filter {
  padding: 20px 12px;
  text-align: center;
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
}

/* === CatalogTree scrollable list ===================================== */

.cb-catalog-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 0;
}

/* === CatalogTree provider row (top-level expand/collapse header) ===== */

/* === §999.44 — shared catalog section-header look ==================== *
 * Both the provider row (top level) and the subsection row (nested) are
 * collapsible section headers in the catalog picker tree. They used to set
 * color + weight independently — provider at 700/--mt-fg, subsection at the
 * default weight/--mt-dim — so the two header tiers read as different kinds
 * of thing. This shared class is now the single source for header color +
 * weight; each row class below keeps only its own padding/indent. One
 * approach (the class), per the project's unified-CSS thread.
 * --------------------------------------------------------------------- */
.cb-catalog-section-header {
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-semibold);
  color: var(--mt-fg);
}

.cb-catalog-section-header__name {
  /* the header label itself — color/weight inherit from the shared class
   * above so provider and subsection names match. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cb-catalog-provider-row {
  padding: 5px 12px;
  font-size: var(--mt-text-xs-size, 11px);
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 6px;
}

.cb-catalog-provider-chev {
  color: var(--mt-muted);
  font-size: 9px;
  width: 8px;
}

.cb-catalog-provider-count {
  color: var(--mt-muted);
  font-size: 9px;
  font-weight: var(--mt-weight-regular);
}

/* === CatalogTree Leaves/Actions sub-tab strip ======================== */

.cb-catalog-subtab-strip {
  display: flex;
  gap: 0;
  padding: 0 12px;
  border-bottom: 1px solid var(--mt-border);
  margin-bottom: 2px;
}

.cb-catalog-subtab {
  padding: 3px 8px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-semibold);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--mt-muted);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
  user-select: none;
}

.cb-catalog-subtab.is-active {
  color: var(--m-accent, var(--mt-accent));
  border-bottom: 2px solid var(--m-accent, var(--mt-accent));
}

/* === CatalogTree subsection row (nested expand/collapse header) ====== */

.cb-catalog-subsection-row {
  /* nested under a provider — keeps the deeper left indent, but color +
   * weight now come from .cb-catalog-section-header so it reads as the same
   * kind of header as the provider row above it (§999.44). */
  padding: 4px 12px 4px 28px;
  font-size: var(--mt-text-xs-size, 11px);
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 6px;
}

.cb-catalog-bulk-pill {
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  font-family: var(--m-font-mono);
  letter-spacing: 0.03em;
  background: color-mix(in srgb, var(--mt-accent) 9%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 33%, transparent);
}

.cb-catalog-subsection-count {
  color: var(--mt-muted);
  font-size: 9px;
}

/* === Catalog row (LeafRow + ActionRow share base chrome) ============= *
 * Indent is state-driven (depends on tree depth), so it rides --cb-row-
 * indent. The :hover affordance moves from inline JS handlers to a CSS
 * :hover so the cascade owns it (matches 19-04c .cb-section-toolbar
 * pattern).
 * --------------------------------------------------------------------- */

.cb-catalog-row {
  padding: 3px 12px 3px var(--cb-row-indent, 28px);
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 6px;
  background: transparent;
}

.cb-catalog-row:hover {
  background: color-mix(in srgb, var(--mt-accent) 9%, transparent);
}

.cb-catalog-row.is-static {
  cursor: default;
}

/* Phase 202.1 (GREY-01): installed-but-not-configured leaves. Distinct from
 * .is-static above (cursor-only, no dim) — this state needs to read as
 * "not ready yet", not merely "not clickable", so it dims opacity too.
 * not-allowed (rather than default) signals the row is disabled, not just
 * inert. */
.cb-catalog-row.is-unconfigured {
  opacity: 0.45;
  cursor: not-allowed;
}

.cb-catalog-row.is-unconfigured:hover {
  background: transparent;
}

/* Per-provider "check the configuration" message (Phase 202.1, GREY-01,
 * D-05) — one line under the provider header, not per leaf. */
.cb-catalog-provider-message {
  padding: 2px 12px 4px 28px;
}

.cb-catalog-row__glyph {
  display: inline-block;
  width: 14px;
  text-align: center;
  color: var(--mt-muted);
  font-size: 10px;
  font-weight: var(--mt-weight-bold);
}

.cb-catalog-row__name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cb-catalog-row__min-role {
  color: var(--mt-muted);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 4px;
  border-radius: 2px;
  border: 1px solid var(--mt-border);
}

.cb-catalog-row__unit {
  color: var(--mt-muted);
  font-size: 9px;
}

/* Frequency pill on LeafRow — colour rides --cb-freq-color so the
 * fast/live distinction can flip per leaf without per-state classes.
 * §210: the "Hist" history-availability badge selector below shares this
 * exact block — --cb-freq-color is left unset for it, so it falls back
 * to the neutral var(--mt-muted).
 * #202: the "variant" badge (a go2rtc transcode listed as a peer of the
 * camera it comes from) joins the same block for the same reason. It is
 * deliberately given no colour of its own — a derived stream is ordinary
 * information, not a warning, and three differently-coloured pills on one
 * row would read as three unrelated alarms. */
.cb-catalog-row__freq,
.cb-catalog-row__hist,
.cb-catalog-row__variant {
  color: var(--cb-freq-color, var(--mt-muted));
  font-size: 8px;
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 4px;
  border-radius: 2px;
  border: 1px solid color-mix(in srgb, var(--cb-freq-color, var(--mt-muted)) 33%, transparent);
}

/* === PreviewPane outer wrapper ======================================= *
 * Centers the preview content vertically + horizontally so a narrow
 * card doesn't pool against the top-left of the preview area.
 * --------------------------------------------------------------------- */

.cb-preview-pane {
  flex: 1;
  min-height: 0;
  padding: var(--mt-space-lg, 16px);
  overflow: auto;
  background: var(--mt-bg);
  display: flex;
  flex-direction: column;
  gap: 8px;
  justify-content: center;
}

.cb-preview-pane.is-dragging {
  user-select: none;
}

.cb-preview-header {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  margin-bottom: 4px;
  display: flex;
  gap: 12px;
}

.cb-preview-header__status {
  font-weight: var(--mt-weight-bold);
  color: var(--cb-status-color, var(--mt-muted));
}

/* PreviewPane grid frame — dashed-border / radius / padding are static.
 * Background flows through --cb-preview-grid-bg (per-card gradient or
 * token); grid-template-columns / width / gridAutoRows stay inline
 * (state-driven layout, allowed). Default falls back to bg-elev so a
 * card with no background prop paints the same as pre-migration. */
.cb-preview-grid {
  border: 1px dashed var(--mt-border);
  border-radius: 10px;
  padding: 6px;
  display: grid;
  gap: 6px;
  align-self: center;
  position: relative;
  background: var(--cb-preview-grid-bg, var(--mt-bg-elev));
}

.cb-preview-empty-cell {
  border: 1px dashed var(--mt-border);
  border-radius: var(--m-radius-md, 6px);
  background: color-mix(in srgb, var(--mt-bg-deep) 33%, transparent);
  pointer-events: none;
}

/* Per-cell gradient backdrop (rendered behind widgets at the same cell).
 * Background rides --cb-cell-grad-bg from the JSX; pointer-events:none +
 * zIndex:0 so it never intercepts widget interactions. */
.cb-preview-cell-grad {
  background: var(--cb-cell-grad-bg, transparent);
  border-radius: var(--m-radius-md, 6px);
  pointer-events: none;
  z-index: 0;
}

/* Per-widget wrapper inside PreviewPane. Background / radius / border /
 * shadow are all state-driven (PiP vs transparent vs normal; dragging;
 * selected); they ride --cb-widget-* custom props the JSX writes. The
 * structural pointer-events / position keys stay on the class. */
.cb-preview-widget {
  pointer-events: auto;
  position: relative;
  background: var(--cb-widget-bg, var(--mt-bg));
  border-radius: var(--cb-widget-radius, var(--m-radius-md, 6px));
  border: var(--cb-widget-border, none);
  box-shadow: var(--cb-widget-shadow, none);
}

/* contentScale wrapper (mirrors live CardRenderer behavior). Border only
 * paints when the modifier is present (--cb-content-border-color set). */
.cb-preview-content-scale {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

.cb-preview-content-scale.has-border {
  border: 1px solid var(--cb-content-border-color, currentColor);
  border-radius: var(--m-radius-md, 6px);
}

/* Resize handles — only mount on the selected widget. Three variants
 * cover right edge / bottom edge / bottom-right corner. */
.cb-preview-resize-handle {
  position: absolute;
  background: color-mix(in srgb, var(--mt-accent) 50%, transparent);
}

.cb-preview-resize-handle.is-r {
  top: 30%;
  bottom: 30%;
  right: 0;
  width: 8px;
  cursor: ew-resize;
  border-radius: 3px 0 0 3px;
}

.cb-preview-resize-handle.is-b {
  left: 30%;
  right: 30%;
  bottom: 0;
  height: 8px;
  cursor: ns-resize;
  border-radius: 3px 3px 0 0;
}

.cb-preview-resize-handle.is-br {
  right: 0;
  bottom: 0;
  width: 14px;
  height: 14px;
  cursor: nwse-resize;
  background: var(--mt-accent);
  border-radius: 4px 0 0 0;
}

.cb-preview-repeat-msg {
  padding: var(--mt-space-md, 12px);
  color: var(--mt-muted);
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
}

/* PreviewPane linked-cell toolbar (cut / copy / paste / delete) ====== *
 * Three variants: default linked-cell toolbar, placement-mode banner
 * (live-accent ring), clipboard-ready fallback. They share padding /
 * radius / mono-typography so the surface reads consistent.
 * --------------------------------------------------------------------- */

.cb-preview-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-md, 6px);
}

.cb-preview-toolbar.is-placement {
  background: color-mix(in srgb, var(--mt-live) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-live) 33%, transparent);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  color: var(--mt-live);
}

.cb-preview-toolbar-label {
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.cb-preview-toolbar-hint {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
}

.cb-preview-toolbar-clipboard-label {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  color: var(--mt-dim);
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.cb-preview-toolbar-link-label {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
  cursor: pointer;
}

.cb-preview-toolbar-link-label.is-linked {
  color: var(--mt-live);
}

.cb-preview-toolbar-link-text {
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* === Linked-cell toolbar buttons ====================================== *
 * Base shape shared across cut/copy/paste/delete/cancel/discard. Modifier
 * classes flip background + colour + border for accent/error/dim/dim-soft.
 * Disabled state for Paste uses :disabled.
 * --------------------------------------------------------------------- */

.cb-preview-toolbar-btn {
  padding: 5px 10px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  cursor: pointer;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.cb-preview-toolbar-btn.is-accent {
  background: color-mix(in srgb, var(--mt-accent) 15%, transparent);
  color: var(--mt-accent);
  border-color: color-mix(in srgb, var(--mt-accent) 33%, transparent);
}

.cb-preview-toolbar-btn.is-error {
  background: color-mix(in srgb, var(--mt-error) 8%, transparent);
  color: var(--mt-error);
  border-color: color-mix(in srgb, var(--mt-error) 33%, transparent);
}

.cb-preview-toolbar-btn:disabled {
  background: transparent;
  color: var(--mt-muted);
  border-color: var(--mt-border);
  cursor: not-allowed;
  opacity: 0.5;
}

/* === PreviewPane diagnostic row (cell / widgets / leaves / renderers) === */

.cb-preview-diag-row {
  display: grid;
  grid-template-columns: auto auto auto 1fr;
  gap: 24px;
  padding: 10px 12px;
  margin-top: 6px;
  border-top: 1px solid var(--mt-border);
  width: 100%;
  max-width: 100%;
}

.cb-preview-diag-block {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.cb-preview-diag-key {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  color: var(--mt-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.cb-preview-diag-value {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  color: var(--mt-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* === SeriesEditor (multi-series widget row list) ===================== */

.cb-series-empty-msg {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
}

.cb-series-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 6px 8px;
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  background: var(--mt-bg);
}

.cb-series-row__head {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Six-dot drag handle on each series row. Mirrors the card .cb-drag-handle
 * (cursor:grab, mono six-dot glyph, muted) but pulled tighter to fit the
 * series row's denser 10px type scale and pinned to the leading edge of the
 * head row. Token-only — the dots ride --mt-muted and brighten to --mt-fg on
 * hover so the row reads as draggable without a permanent loud accent. The
 * grabbing cursor on :active matches the card handle's drag feel. */
.cb-series-row .cb-drag-handle {
  font-size: 11px;
  padding: 0 2px;
  margin-left: -2px;
  color: var(--mt-muted);
  transition: color 0.12s ease;
}

.cb-series-row .cb-drag-handle:hover {
  color: var(--mt-fg);
}

.cb-series-row .cb-drag-handle:active {
  cursor: grabbing;
}

/* Color swatch on each series row. Background rides --cb-series-swatch-bg
 * (resolved palette colour, per-series). 10×10 square, 2px radius. */
.cb-series-row__swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex-shrink: 0;
  background: var(--cb-series-swatch-bg, var(--mt-accent));
}

/* The remove-× button on each series row. Inherits the iconBtn() shape
 * (mono / muted / pill) but pins padding + fontWeight so the series-row
 * version is slightly tighter than the inspector default. */
.cb-series-row__remove {
  width: auto;
  padding: 1px 6px;
  font-weight: var(--mt-weight-bold);
}

/* Zebra-stripe row background (service-table repeat mode). State-driven
 * paint colour rides --cb-zebra-paint; pointer-events:none + zIndex:0
 * so it never intercepts widget clicks. */
.cb-preview-zebra {
  background: var(--cb-zebra-paint, transparent);
  z-index: 0;
  pointer-events: none;
}

.cb-series-row__name {
  flex: 1;
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cb-series-checkbox-row {
  display: flex;
  gap: 4px;
  align-items: center;
  font-size: 10px;
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
}

.cb-series-fill-opacity-row {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: 10px;
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
}

.cb-series-params-stack {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 6px;
  margin-left: 12px;
}

/* "+ Add series" button — hover affordance via :hover not JS handlers. */
.cb-series-add-btn {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  font-weight: var(--mt-weight-semibold);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  padding: 7px 12px;
  margin-top: 6px;
  background: transparent;
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 33%, transparent);
  border-radius: 3px;
  cursor: pointer;
  text-align: center;
  transition: background 120ms linear, border-color 120ms linear;
}

.cb-series-add-btn:hover {
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  border-color: var(--mt-accent);
}

/* === LabelIconPickerInline (label-icon picker grid) ================== */

.cb-icon-picker-label {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  margin-top: 6px;
  margin-bottom: 3px;
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.cb-icon-picker-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  padding: 5px;
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  background: var(--mt-bg-deep);
  max-height: 144px;
  overflow-y: auto;
}

/* Icon-picker button (one square per icon). Selected variant has accent
 * tint + accent border; unselected is transparent + border. */
.cb-icon-btn {
  width: 24px;
  height: 24px;
  padding: 0;
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--mt-dim);
}

.cb-icon-btn.is-selected {
  background: color-mix(in srgb, var(--mt-accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--mt-accent) 53%, transparent);
  color: var(--mt-accent);
}

.cb-icon-btn.is-none {
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: 9px;
}

.cb-icon-picker-break {
  flex-basis: 100%;
  height: 0;
}

.cb-icon-picker-imported-header {
  font-family: var(--m-font-mono);
  font-size: 8px;
  color: var(--mt-muted);
  width: 100%;
  margin-top: 4px;
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0.7;
  display: flex;
  align-items: center;
  gap: 6px;
}

.cb-icon-picker-upload-btn {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 33%, transparent);
  border-radius: 3px;
  padding: 1px 6px;
  cursor: pointer;
}

.cb-icon-picker-upload-btn:disabled {
  background: transparent;
  color: var(--mt-muted);
  border-color: var(--mt-border);
  cursor: default;
}

.cb-icon-imported-thumb {
  width: 14px;
  height: 14px;
  display: block;
  object-fit: contain;
}

/* Overlay × on imported assets — flips to error colour on hover so users
 * realise it's destructive. */
.cb-icon-imported-delete {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--mt-bg);
  color: var(--mt-muted);
  border: 1px solid var(--mt-border);
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  line-height: 10px;
  text-align: center;
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.cb-icon-imported-delete:hover {
  color: var(--mt-error);
  border-color: var(--mt-error);
}

/* === IconChooserField — Phase 120 additions ========================== *
 * New classes for the unified IconChooserField component. Reuses all
 * existing .cb-picker-* and .cb-icon-* classes above; these fill the
 * gaps for inline-compact variant and hidden file input. No inline
 * aesthetic styles (eslint gate). */

/* Hides the native file input — replaces style={{display:'none'}} on
 * hidden <input type="file"> elements. Functionally invisible; click is
 * dispatched programmatically via ref.click(). */
.cb-icon-file-input-hidden {
  display: none;
}

/* Imported-asset thumb inside the tile-grid Imported tab. Replaces
 * style={{objectFit:'contain', maxWidth:'100%', maxHeight:'100%'}}. */
.cb-icon-thumb-img {
  object-fit: contain;
  max-width: 100%;
  max-height: 100%;
  display: block;
}

/* Spacer in the inline-compact Imported header (between the count label
 * and the Upload button). Same role as .cb-icon-imported-spacer (flex:1). */
.cb-icon-picker-spacer {
  flex: 1;
}

/* Wrapper around an inline-compact imported-asset thumb + delete-× overlay.
 * Mirrors the original `style={{position:'relative', display:'inline-flex'}}`. */
.cb-icon-imported-inline-wrap {
  position: relative;
  display: inline-flex;
}

/* === BrandInspectorForm / ClockInspectorForm shared mini-label ======= *
 * Tiny uppercase mono header used above every prop field in those two
 * forms ("Mode", "Text", "Format", "Timezone", "HTML").
 * --------------------------------------------------------------------- */

.cb-inspector-mini-label {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  color: var(--mt-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 4px;
  margin-top: 8px;
}

.cb-inspector-mode-btn {
  flex: 1;
  padding: 6px 10px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  cursor: pointer;
  border-radius: var(--m-radius-sm, 4px);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
}

.cb-inspector-mode-btn.is-active {
  background: color-mix(in srgb, var(--mt-accent) 15%, transparent);
  color: var(--mt-accent);
  border-color: var(--mt-accent);
}

.cb-inspector-mode-checkbox-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: var(--mt-text-xs-size, 11px);
  font-family: var(--m-font-mono);
  color: var(--mt-fg);
  cursor: pointer;
}

/* === BrandInspector custom-html textarea =========================== *
 * Overrides the standard fieldInput() spread to make the textarea read
 * as a code block (mono, smaller text, larger min-height, vertical-only
 * resize). The two FORBIDDEN keys (fontSize / fontFamily already in
 * fieldInput via mfont) get baked into this class so the inline-style
 * block can drop them. */
.cb-inspector-html-textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: 120px;
  resize: vertical;
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  line-height: 1.4;
}

/* === ClockInspector timezone input ================================= *
 * Just a width:100% + mono-family override on the standard fieldInput
 * shape so a timezone string ("America/New_York") fits comfortably. */
.cb-inspector-tz-input {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
}

/* === SeriesEditor per-row label input ============================== *
 * Tightens fieldInput's padding so the inline label override sits closer
 * to its neighbours in the per-series card. */
.cb-series-row__label-input {
  padding: 3px 6px;
  font-size: var(--mt-text-xs-size, 11px);
}

/* === Service-picker status messages =================================
 * Three states share the same mono / 10px / dim or warn / 6px-margin
 * shape: catalog-loading, auth-required, server-side load-errors. The
 * colour rides --cb-svc-msg-color so the same class covers both dim
 * (loading) and warn (auth/load-error) variants without per-state
 * duplication. */
.cb-svc-picker-msg {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--cb-svc-msg-color, var(--mt-dim));
  margin-top: 6px;
}

.cb-svc-picker-msg__errors {
  margin: 3px 0 0 14px;
  padding: 0;
}

/* === Section 5 (19-04e) — inspector / pickers / forms ============== *
 * Shared chrome for the icon-picker family (ServiceIconPickerField,
 * BundledIconPickerField, IconPickerField), ParameterPicker /
 * _InstancePickerInput, the _Rd*Badge cluster, SectionedInspector toggle
 * button, and LeafPicker header.
 *
 * Picker grid scroll area: shared frame for the four picker-grid scrolls
 * (bundled / brand / imported / service-icon). Padding + radius + dim
 * bg + 1px border are the same 6px+4px+--mt-bg-deep+--mt-border shape;
 * vertical max-height (240 vs 280) flows through --cb-picker-grid-max so
 * one class skins both heights.
 * --------------------------------------------------------------------- */

.cb-picker-scroll {
  max-height: var(--cb-picker-grid-max, 280px);
  overflow-y: auto;
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  padding: 6px;
  background: var(--mt-bg-deep);
}

/* Imported-tab variant — no max-height on the outer (the inner has its
 * own scroll); same border/radius/padding/bg. */
.cb-picker-frame {
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  padding: 6px;
  background: var(--mt-bg-deep);
}

/* Filter row above a picker scroll (filter input + optional Clear).
 * Pre-migration: display:flex, gap:6, alignItems:center, marginBottom:6.
 * 3 sites — Service/Bundled/IconPicker. */
.cb-picker-filter-row {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
}

/* Filter input override on top of fieldInput(t): tighter padding +
 * smaller text. Mounts on the type=text inside .cb-picker-filter-row. */
.cb-picker-filter-input {
  flex: 1;
  padding: 3px 6px;
  font-size: 10px;
}

/* Clear button beside the filter input — sits on top of btnGhost(t)
 * spread; just dials padding + font-size down. */
.cb-picker-clear-btn {
  padding: 3px 8px;
  font-size: 10px;
}

/* Uppercase mono category header above each grouped section in a
 * picker (Bundled, Brand, Imported, etc.). The {...mfont} spread on
 * the JSX still wins for font-family if mfont differs across themes;
 * but in practice mfont resolves to var(--m-font-mono) everywhere. */
.cb-picker-category {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 4px;
  margin-top: 2px;
}

/* Wrap around a picker category + its tile row — pre-migration
 * marginBottom:8 to gap successive groups. */
.cb-picker-category-wrap {
  margin-bottom: 8px;
}

/* Inline count "(12)" sitting after a category title. Dim text, no
 * uppercase, normal weight (overrides .cb-picker-category bold). */
.cb-picker-category-count {
  color: var(--mt-dim);
  font-weight: var(--mt-weight-regular);
  text-transform: none;
  letter-spacing: 0;
}

/* Tile row inside a picker — wrap, gap 4px. */
.cb-picker-tile-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* Mono dim status row (loading… / no source / empty caption). 10px
 * mono, --mt-dim, no padding. Pre-migration sites: ParameterPicker
 * loading/empty rows, _InstancePickerInput "no instances configured",
 * picker "loading /icons/ list…" / "loading /feed/svg/ uploads…". */
.cb-picker-status {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-dim);
  padding: 4px 0;
}

/* Italic-muted variant — used by the "no icons match …" emptyState
 * inside a picker frame. Adds padding:8 and italic. */
.cb-picker-empty {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  font-style: italic;
  padding: 8px;
}

/* Centered-loading variant of the empty state (IconPickerField
 * Imported tab "Loading…" + the empty-tab onboarding prose). */
.cb-picker-empty-centered {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  padding: 8px;
  text-align: center;
}

/* Larger empty-prose variant with line-height for the "Nothing
 * imported yet" onboarding block. */
.cb-picker-empty-prose {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  padding: 8px;
  text-align: center;
  line-height: 1.6;
}

/* === IconPickerField — tab strip ==================================== *
 * The 5-tab strip (Bundled / Brand / Imported / URL / Inline) at top
 * of the picker. Each tab inherits segButton(t, active) shape and
 * adds padding + font-size. */
.cb-icon-tab-strip {
  display: flex;
  gap: 4px;
  margin-bottom: 8px;
  flex-wrap: wrap;
}

/* Per-tab override on top of segButton — smaller padding + 10px text. */
.cb-icon-tab-btn {
  padding: 4px 10px;
  font-size: 10px;
}

/* Clear-everything button at the right end of the tab strip. */
.cb-icon-tab-clear {
  padding: 4px 8px;
  font-size: 10px;
  margin-left: auto;
}

/* Imported-tab header bar (uppercase title + upload + refresh).
 * Same vocabulary as .cb-picker-category but adds flex layout +
 * marginBottom:6 (vs 4) so the controls sit on one row with the
 * title. */
.cb-icon-imported-header {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Flex spacer between title and the upload+refresh buttons in the
 * header bar. Replaces inline <span style={{flex:1}}/>. */
.cb-icon-imported-spacer {
  flex: 1;
}

/* Upload button in the Imported-tab header. Two-state via .is-busy
 * modifier: idle = accent-tinted; busy = transparent + muted text +
 * neutral border + default cursor. */
.cb-icon-upload-btn {
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 35%, transparent);
  border-radius: 3px;
  padding: 2px 6px;
  cursor: pointer;
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.cb-icon-upload-btn.is-busy {
  background: transparent;
  color: var(--mt-muted);
  border-color: var(--mt-border);
  cursor: default;
}

/* Refresh button — muted dim text, neutral border, smaller font. */
.cb-icon-refresh-btn {
  background: transparent;
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  padding: 2px 6px;
  cursor: pointer;
  color: var(--mt-dim);
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-semibold);
}

/* Per-thumb override on top of thumb(selected): tighten padding +
 * fix to 38×38 (used by Imported + Brand grids). */
.cb-icon-thumb-fixed {
  padding: 4px;
  width: 38px;
  height: 38px;
}

/* Inner scroll wrapper inside the Imported tab — fixed max-height +
 * overflow so the tile grid scrolls without taking over the panel. */
.cb-icon-imported-scroll {
  max-height: 240px;
  overflow-y: auto;
}

/* Import-result message line — colour rides --cb-icon-msg-color so
 * the same class covers warn (fail) and live (ok) variants. */
.cb-icon-msg {
  font-family: var(--m-font-mono);
  font-size: 9px;
  margin-top: 6px;
  color: var(--cb-icon-msg-color, var(--mt-muted));
}

/* Status bar at the bottom of IconPickerField — "icon: X" / "url: …"
 * / "inline (N chars)" / "no source set". */
.cb-icon-status-bar {
  margin-top: 6px;
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  padding: 4px 6px;
  border-top: 1px solid var(--mt-border);
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

/* Inline-flex pill inside the status bar showing the picked bundled
 * icon + its name. */
.cb-icon-status-icon-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* Dim text inside status bar. */
.cb-icon-status-dim {
  color: var(--mt-dim);
}

/* Word-break variant for long URLs in status bar. */
.cb-icon-status-url {
  color: var(--mt-dim);
  word-break: break-all;
}

/* "no source set" italic placeholder. */
.cb-icon-status-empty {
  font-style: italic;
}

/* URL-tab prose block — multi-line muted help text with bold accents. */
.cb-icon-url-help {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  margin-top: 6px;
  line-height: 1.5;
}

.cb-icon-url-help > div {
  margin-bottom: 3px;
}

.cb-icon-url-help > div:last-child {
  margin-bottom: 0;
}

/* Inline-tab prose (smaller, no per-line gap). */
.cb-icon-inline-help {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  margin-top: 3px;
  line-height: 1.4;
}

/* === IconPickerField input overrides ================================ *
 * Filter / URL / inline-textarea inputs each spread fieldInput(t) and
 * then add a couple of tweaks (smaller padding, mono font on the
 * textarea, smaller line-height). One class per shape so the JSX
 * inline-style block can drop the FORBIDDEN keys. */

/* "Filter brand icons…" / "filter…" 3px/6px input. Mounts on top of
 * .cb-picker-filter-input shape but adds margin-bottom:6 — used when
 * the input is standalone (not inside .cb-picker-filter-row). */
.cb-icon-standalone-filter {
  padding: 3px 6px;
  font-size: 10px;
  margin-bottom: 6px;
}

/* URL-tab input — 4/6 padding. */
.cb-icon-url-input {
  padding: 4px 6px;
}

/* Inline-tab textarea — mono / 10px / 1.4 line-height / 80px min. */
.cb-icon-inline-textarea {
  min-height: 80px;
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1.4;
}

/* Custom-SVG tab live-preview wrapper — sits between the textarea and the
 * help text.  Shows the sanitized SVG output at 32×32 in a bordered box
 * so the user sees exactly what will render on the card (theme color + fit).
 * Uses currentColor so the preview tracks the dashboard theme, matching
 * how BundledIcon renders in the bundled-icon grid. */
.cb-icon-inline-preview-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 4px 0 2px;
}

.cb-icon-inline-preview {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  padding: 4px;
  box-sizing: border-box;
  color: var(--mt-fg);          /* currentColor in the SVG tracks this */
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Ensure the pasted SVG fills the preview box and scales with it. */
.cb-icon-inline-preview svg {
  width: 100%;
  height: 100%;
  display: block;
}

.cb-icon-inline-preview-label {
  font-size: 9px;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
}

/* Brand-tab grid scroll — same .cb-picker-scroll bones plus inline
 * flex/wrap because the brand tab paints thumbnails directly (no
 * grouped subsections). */
.cb-picker-scroll.is-flat {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* Inputs with mono+fontSize:11 override on top of fieldInput(t).
 * Used by ServiceElementPicker's Custom-icon-name / Custom-icon-URL /
 * audit-template inputs at 6500-6600 + the wide-width variant. */
.cb-mono-input {
  font-family: var(--m-font-mono);
  font-size: 11px;
}

/* Wide variant (sets width:100% + box-sizing). Used by the audit
 * template input. */
.cb-mono-input.is-wide {
  width: 100%;
  box-sizing: border-box;
}

/* === ServiceElementPicker checkbox rows ============================= *
 * "Icon only — hide the text label" / "record an audit entry every
 * time this link is clicked". Same shape: flex row, 6px gap, 11px
 * mono text, dim colour, marginTop:6. */
.cb-mini-checkbox-row {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: 11px;
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
  margin-top: 6px;
}

/* === SectionedInspector — section toggle button ===================== *
 * The "DATA ▼ 7" / "FORMAT ▶ 3" disclosure buttons in the sectioned
 * inspector view. Two states via .is-open: closed (transparent bg,
 * dim text), open (accent-tinted bg, fg text). */
.cb-inspector-section-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 5px 8px;
  font-size: 10px;
  font-weight: var(--mt-weight-bold);
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0;
}

.cb-inspector-section-toggle.is-open {
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  color: var(--mt-fg);
  margin-bottom: 6px;
}

/* Right-side count + chevron inside the toggle. */
.cb-inspector-section-count {
  font-size: 9px;
  color: var(--mt-muted);
}

/* === SectionedInspector — always-open section label (999.20-A) ======= *
 * The pre-999.20 collapsible "DATA ▼ 7" toggle button is retired. Sections
 * are now always-open organizing labels: a quiet mono caption with a hair
 * line under it, sitting above its fields. No chevron, no click target —
 * the Essentials/All/Changed switch + Find-a-setting box do all the
 * showing and hiding. A section with no visible fields under the current
 * mode/search isn't rendered at all. */
.cb-inspector-section-label {
  font-family: var(--m-font-mono);
  font-size: 10px;
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--mt-dim);
  padding: 0 0 5px 1px;
  margin-top: 4px;
  border-bottom: 1px solid var(--mt-border);
}

/* Space each section block from the next so the labels read as dividers. */
.cb-inspector-section + .cb-inspector-section {
  margin-top: 14px;
}

/* Wrapper around an advanced Field shown in "All" mode. A hook for the
 * per-field `adv` tag plan 03 attaches; carries no visual of its own yet
 * (it must not shift the Field's own layout), so it's display:contents. */
.cb-field-adv-wrap {
  display: contents;
}

/* === _Rd*Badge cluster (Phase 26 Plan 02 status badges) =============
 * Pending / Unreachable / Missing / Stale badges painted alongside
 * rest_discover-backed cards. Same 11px mono base; per-variant overrides
 * for colour, alignment, and padding. */
.cb-rd-badge {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
  padding: 4px 8px;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Unreachable = orange. Last-known-good payload is still available;
 * card is degraded, not dead. */
.cb-rd-badge.is-unreachable {
  color: var(--mt-warn);
}

/* Missing = centered italic (vanished row caption). */
.cb-rd-badge.is-missing {
  display: block;
  text-align: center;
  font-style: italic;
}

/* Spinner dot inside the pending badge. */
.cb-rd-spinner {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 2px solid var(--mt-muted);
  border-top-color: var(--mt-accent);
  animation: spin 1s linear infinite;
}

/* Solid colored dot inside the unreachable badge. */
.cb-rd-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--mt-warn);
  flex-shrink: 0;
}

/* Inline "stale · 5m ago" pill overlaid on a value tile. */
.cb-rd-stale-pill {
  display: inline-block;
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-semibold);
  color: var(--mt-muted);
  background: color-mix(in srgb, var(--mt-warn) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-warn) 35%, transparent);
  border-radius: 3px;
  padding: 1px 4px;
  margin-left: 4px;
  vertical-align: middle;
}

/* === ParameterPicker / _InstancePickerInput ========================= *
 * Uppercase mono parameter label above the select. 10px / muted /
 * tracked. Same shape used twice (once per component). */
.cb-param-label {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Wrap around the label + select + status messages. 4px gap, marginTop:6. */
.cb-param-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
}

/* Status line below the parameter select (italic for loading/empty;
 * non-italic for "pick instance to continue"). Colour rides
 * --cb-param-msg-color so warn variant (ghost-removed) and default
 * muted variant share one class. */
.cb-param-status {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--cb-param-msg-color, var(--mt-muted));
}

.cb-param-status.is-italic {
  font-style: italic;
}

/* Marker class on the parameter <select>; only the .is-ghost modifier
 * paints aesthetics (warn colour + warn border) when the saved value
 * is no longer in the current valid-options list. Mounting it on top
 * of fieldInput(t) so the base shape stays consistent with sibling
 * inputs. */
.cb-param-select.is-ghost {
  color: var(--mt-warn);
  border-color: var(--mt-warn);
}

/* === LeafPicker — header band + search summary =====================
 * Title row (sectionLabel spread + accent override + bottom border)
 * and search summary line. */
.cb-leaf-picker-header {
  padding: 12px 14px 6px 14px;
  border-bottom: 1px solid var(--mt-border);
}

.cb-leaf-picker-subtitle {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  margin-top: 3px;
  letter-spacing: 0.04em;
}

.cb-leaf-picker-search-row {
  padding: 10px 14px 8px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.cb-leaf-picker-summary {
  display: flex;
  justify-content: space-between;
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  padding-top: 2px;
}

/* LeafPickerEmpty (Leaves rail mode with no open caller). Soft-muted
 * onboarding panel — centered, mono, generous line-height. */
.cb-leaf-picker-empty {
  padding: 28px 20px;
  text-align: center;
  font-family: var(--m-font-mono);
  font-size: 12px;
  color: var(--mt-muted);
  line-height: 1.7;
}

.cb-leaf-picker-empty__title {
  margin-bottom: 10px;
  color: var(--mt-accent);
}

/* Per-group leaf-row wrap inside the LeafPicker body. Border-left
 * accent for indented group rows. */
.cb-leaf-picker-group-body {
  padding-left: 18px;
  margin-left: 6px;
  border-left: 1px solid color-mix(in srgb, var(--mt-accent) 19%, transparent);
  padding-top: 3px;
  padding-bottom: 6px;
}

/* Group header inside LeafPicker. */
.cb-leaf-picker-group-row {
  display: grid;
  grid-template-columns: 12px 1fr auto;
  gap: 8px;
  align-items: center;
  padding: 7px 8px;
  cursor: pointer;
  border-bottom: 1px solid var(--mt-border);
  transition: background 120ms linear;
}

.cb-leaf-picker-group-wrap {
  margin-bottom: 2px;
}

/* Group chevron — colour flips by .is-open. */
.cb-leaf-picker-group-chev {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
  text-align: center;
}

.cb-leaf-picker-group-chev.is-open {
  color: var(--mt-accent);
}

/* Group name (uppercase tracked mono). */
.cb-leaf-picker-group-name {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  color: var(--mt-fg);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* Right-side count pill. */
.cb-leaf-picker-group-count {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  background: var(--mt-bg-deep);
  border: 1px solid var(--mt-border);
  border-radius: 2px;
  padding: 1px 6px;
  min-width: 24px;
  text-align: center;
}

/* Per-provider "check the configuration" message (Phase 202.1, GREY-01,
 * D-05) — one line under the group header, not per leaf. Mirrors
 * .cb-catalog-provider-message's spacing so the two pickers read as one
 * coherent state; indent matches this picker's own group-body indent
 * (18px) rather than the Catalog tab's row indent, since the two trees
 * don't share a layout grid. */
.cb-leaf-picker-group-message {
  padding: 2px 8px 6px 18px;
}

/* RailFrame outer (background + left-border separator from preview).
 * Layout keys (display:grid, grid-template-rows, min-height) stay inline
 * since they're structural — the class hosts only the FORBIDDEN background
 * + border-left. */
.cb-rail-frame {
  background: var(--mt-bg);
  border-left: 1px solid var(--mt-border);
}

/* RailFrame tab strip — bottom border + elevated bg. */
.cb-rail-tab-strip {
  border-bottom: 1px solid var(--mt-border);
  background: var(--mt-bg-elev);
}

/* SectionedInspector advanced-disclosure body wrapper — flex column with
 * 10px gap + small bottom margin. */
.cb-inspector-section-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 6px;
}

/* Per-leaf row inside LeafPicker. Layout (grid template + gap) stays
 * inline since it's structural; this class hosts the cursor / padding /
 * transition + the highlight-and-selection state via custom-prop hooks. */
.cb-leaf-picker-row {
  padding: 5px 8px;
  border-radius: 2px;
  transition: background 120ms linear;
}

/* Status hint inside the LeafPicker body for "No leaves match …" /
 * "No compatible leaves" empty states. */
.cb-leaf-picker-empty-state {
  padding: 20px 12px;
  text-align: center;
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
}

/* Bold accent inside the search-summary line ("showing X of Y"). */
.cb-leaf-picker-summary-strong {
  color: var(--mt-fg);
}

/* LeafPicker body — scrollable group list container. */
.cb-leaf-picker-body {
  overflow: auto;
  padding: 0 6px 10px 6px;
}

/* D-02 (Phase 118): un-cram the leaf picker inside the Data drawer by
 * constraining the WHOLE picker container (cb-leaf-picker-root), not just
 * the inner list body. The picker's outer div uses grid-template-rows:
 * auto auto 1fr auto — header / search row / scrollable list / footer.
 * Without a height constraint on the outer container, the grid expands to
 * natural content height and the search row can overflow the visible drawer
 * area. Giving the outer container a max-height lets the grid distribute
 * space correctly so all four rows stay visible (header + search at top,
 * list scrollable in the middle, footer pinned at bottom).
 *
 * The Phase 117 inner-list cap (260px on .cb-leaf-picker-body) is raised to
 * 360px below so the list can use most of the 420px outer budget. The inner
 * cap is no longer the primary constraint — it's a safety net to prevent the
 * list alone from overflowing if the outer container somehow has no height.
 *
 * NOTE: overflow:hidden and overscroll-behavior:contain must NOT be added to
 * .cb-leaf-picker-body. The Phase-111 fix depends on picker→body chaining:
 * when the list bottoms out, the wheel must chain UP to .cb-inspector-body.
 * Containing the inner list would re-strand the controls below the picker.
 * Guarded by tests/117-data-section-scroll.test.js and
 * tests/111-inspector-scroll-chaining.test.js. */
.cb-layer-drawer-body-inner .cb-leaf-picker-root {
  /* Give the whole picker a real height so the grid-template-rows can place
   * all four rows without clipping. 420px keeps the picker compact inside the
   * Data drawer (which is now max-height:none — INSP-01 #2, Phase 118). */
  max-height: 420px;
}

.cb-layer-drawer-body-inner .cb-leaf-picker-body {
  /* overflow:auto is inherited from the base .cb-leaf-picker-body rule above via
   * the cascade (IN-03, Phase 117 review — restating it here was redundant). The
   * base rule's overflow:auto + this max-height together make it a bounded scroll
   * container inside the Data drawer. Raised from 260px (Phase 117) to 360px now
   * that the outer cb-leaf-picker-root container governs the total picker height. */
  max-height: 360px;
}

/* Per-leaf clickable row — wrapper around name + parameterized-badge +
 * kind chip + add/remove action. Hover state painted via JS handlers
 * (different colour per highlighted-state). Border + background are
 * state-driven via inline custom-prop hatch so the highlighted-and-not
 * cases share one class. */
.cb-leaf-picker-leaf-row {
  padding: 5px 8px;
  border-radius: 2px;
  border: 1px solid var(--cb-leaf-row-border, transparent);
  background: var(--cb-leaf-row-bg, transparent);
  transition: background 120ms linear;
}

/* Leaf name typography — mono, 12px, fg by default, muted when added.
 * Multi-line clamp + line-height stay inline since they're structural
 * (WebkitLineClamp / WebkitBoxOrient are ALLOWED display-related keys
 * and don't trigger the audit). */
.cb-leaf-picker-leaf-name {
  font-family: var(--m-font-mono);
  font-size: 12px;
  color: var(--cb-leaf-name-color, var(--mt-fg));
}

/* Inline muted "subsection · " prefix inside the leaf name. */
.cb-leaf-picker-leaf-sub {
  color: var(--mt-muted);
}

/* Parameterized-leaf badge {·} — small bordered pill in muted colour
 * with 0.85 opacity. */
.cb-leaf-picker-param-badge {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  border: 1px solid var(--mt-muted);
  border-radius: 3px;
  padding: 0 4px;
  margin-left: 6px;
}

/* LeafPicker footer — Done button row + selection count. Border-top,
 * elevated bg, 10/14 padding, two-column grid. */
.cb-leaf-picker-footer {
  border-top: 1px solid var(--mt-border);
  padding: 10px 14px;
  background: var(--mt-bg-elev);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px;
  align-items: center;
}

/* Status line inside the footer ("N selected · keep adding, or click Done"). */
.cb-leaf-picker-footer-msg {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
}

/* Accent bold for the count number inside the footer message. */
.cb-leaf-picker-footer-strong {
  color: var(--mt-accent);
}

/* ════════════════════════════════════════════════════════════════════
 * Plan 19-04f — Section 6: leaf-picker + preset modal chrome
 * (lines 8200-9500 of card-builder.jsx)
 *
 * Covers: ScopePicker (grouped checkbox panels), PresetModalShell
 * (sizing/flex chrome inside the .manifold-modal-* className stack
 * which D-19-02 swaps to <Modal> in 19-09 — class names stay),
 * SavePresetModal (name input + scope picker form), ApplyPresetPreview
 * (Before/After scaled card preview), ManagePresetsModal (list with
 * inline rename / JSON expander / delete), StylePresetsSection (three-
 * state reveal), and the InspectorPane edges within band (card-frame
 * section wrapper, gradient preview, row-alignment buttons, visibility
 * checkbox, widget list rows). Reuses existing .cb-mini-label-style
 * vocabulary where possible. New classes scoped .cb-* per 19-04 family.
 * ════════════════════════════════════════════════════════════════════ */

/* ── ScopePicker (SavePresetModal embed) ─────────────────────────── */

/* Outer vertical stack of group panels. mono font carried in. */
.cb-scope-picker {
  display: flex;
  flex-direction: column;
  gap: 12px;
  font-family: var(--m-font-mono);
}

/* One group panel = bordered box with elevated bg, padding for the
 * header row + key rows inside. */
.cb-scope-group {
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  padding: 8px 10px;
  background: var(--mt-bg-elev);
}

/* Header row: three-state checkbox + label + on/total count.
 * Border-bottom separates it from key rows below. */
.cb-scope-group__header {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  font-weight: var(--mt-weight-bold);
  color: var(--mt-fg);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--mt-border);
  margin-bottom: 8px;
}

/* Native checkbox styled with the accent token. Used for both group
 * header (three-state) and per-key rows. */
.cb-scope-checkbox {
  accent-color: var(--mt-accent);
  cursor: pointer;
}

/* Right-side "3/7" pill in the group header. tabular-nums for stable
 * alignment as counts change. */
.cb-scope-group__count {
  color: var(--mt-muted);
  font-size: 10px;
  font-weight: var(--mt-weight-medium);
  font-variant-numeric: tabular-nums;
}

/* Body container for per-key rows (vertical stack inside a panel). */
.cb-scope-group__body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* One key row = checkbox + label + level chip. Color flips with state
 * via .is-on modifier (fg) vs default (muted). */
.cb-scope-key {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  font-size: 12px;
  color: var(--mt-muted);
  padding: 2px 0;
}
.cb-scope-key.is-on {
  color: var(--mt-fg);
}

/* Right-aligned "level" chip ('card' / 'widget' / etc.) inside a key row. */
.cb-scope-key__level {
  font-size: 9px;
  color: var(--mt-muted);
  opacity: 0.7;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}

/* Flex-1 label inside scope header / key rows. */
.cb-scope-flex-label {
  flex: 1;
}

/* ── Phase 60: selective-apply controls (ApplyPiecesChecklist +
 *    ApplyTargetPicker), and the two-column ApplyPresetPreviewModal body.
 *    All token-only, zero hex — restyles for free across every theme. ── */

/* Helper line under each section heading (the "Everything's on…" /
 * "Picks how far…" guidance). Sans, muted, small. */
.cb-apply-helper {
  font-family: var(--m-font-sans);
  font-size: 11px;
  color: var(--mt-muted);
  margin: 4px 0 8px;
  line-height: 1.4;
}

/* Sub-note under the target picker (shown only for widget / all). Slightly
 * dimmer than the helper, italic to read as a contextual aside. */
.cb-apply-subnote {
  font-family: var(--m-font-sans);
  font-size: 11px;
  color: var(--mt-muted);
  opacity: 0.85;
  font-style: italic;
  margin-top: 6px;
  line-height: 1.4;
}

/* The checklist reuses the .cb-scope-* family verbatim; this just tightens the
 * gap so the filtered (often short) bucket list sits close under its heading. */
.cb-apply-checklist {
  gap: 8px;
}

/* Disclosure caret on each bucket header — reveals the per-piece rows. Borderless
 * text-glyph button, muted until hover. Pushed to the row's right edge. */
.cb-apply-caret {
  background: transparent;
  border: none;
  color: var(--mt-muted);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  padding: 2px 4px;
  border-radius: var(--m-radius-sm, 4px);
}
.cb-apply-caret:hover {
  color: var(--mt-fg);
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
}

/* Target picker block — sits below the checklist in the controls column. */
.cb-apply-target {
  margin-top: 14px;
}

/* Segmented control: three buttons sharing one bordered track. */
.cb-apply-segments {
  display: flex;
  gap: 0;
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  overflow: hidden;
  margin-top: 4px;
}

/* One segment. Transparent until active; the active segment carries the accent
 * treatment from the inspector's existing segmented switch. Disabled segment
 * ("This widget" with nothing selected) reads dimmed + not-allowed. */
.cb-apply-segment {
  flex: 1;
  padding: 6px 8px;
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: transparent;
  color: var(--mt-dim);
  border: none;
  border-right: 1px solid var(--mt-border);
  cursor: pointer;
}
.cb-apply-segment:last-child {
  border-right: none;
}
.cb-apply-segment.is-active {
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
  color: var(--mt-accent);
}
.cb-apply-segment:disabled {
  color: var(--mt-muted);
  opacity: 0.5;
  cursor: not-allowed;
}

/* Two-region modal body: left controls column + right Before/After preview.
 * Columns stack on a narrow viewport (controls above preview). */
.cb-apply-modal-body {
  display: flex;
  gap: 20px;
  align-items: flex-start;
}
.cb-apply-controls-col {
  flex: 0 0 280px;
  max-width: 280px;
}
.cb-apply-preview-col {
  flex: 1;
  min-width: 0;
}
@media (max-width: 720px) {
  .cb-apply-modal-body {
    flex-direction: column;
  }
  .cb-apply-controls-col {
    flex: 1 1 auto;
    max-width: none;
    width: 100%;
  }
}

/* Empty-state helper under the footer when nothing is checked (or the target
 * carries no pieces) — reads as a gentle block, not an error. */
.cb-apply-empty-note {
  font-family: var(--m-font-sans);
  font-size: 11px;
  color: var(--mt-muted);
  margin-top: 10px;
  line-height: 1.4;
}

/* ── PresetModalShell + form chrome ──────────────────────────────── */

/* The .manifold-modal box inside the shell needs flex column for
 * header / body / footer sizing. maxWidth varies per modal so it
 * stays inline (custom-property below). */
.cb-preset-modal-box {
  width: 92vw;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  max-width: var(--cb-preset-modal-max-width, 560px);
}

/* The modal body needs auto overflow + flex:1 so it scrolls inside
 * the height-capped shell while header/footer stay pinned. */
.cb-preset-modal-body {
  overflow: auto;
  flex: 1;
}

/* Disabled primary button in SavePresetModal (and friends): half-opacity
 * + not-allowed cursor without retouching the manifold-button class. */
.cb-preset-btn-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Vertical form stack inside SavePresetModal body. Larger gap (16) than
 * default since rows are bigger (label + input, scope picker block). */
.cb-preset-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Single column label + input pair (Name field). Smaller gap (6) than
 * the form rows themselves. */
.cb-preset-form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* The "NAME" / "WHICH PROPERTIES TO INCLUDE" small uppercase header
 * above each form row. Mono, 10px, bold, muted. */
.cb-preset-form-label {
  font-family: var(--m-font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  font-weight: var(--mt-weight-bold);
  color: var(--mt-muted);
}
.cb-preset-form-label.has-mb {
  margin-bottom: 8px;
}

/* Primary text input inside SavePresetModal. Sans-serif (matches the
 * preset-name surface elsewhere). */
.cb-preset-name-input {
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  padding: 8px 10px;
  font-family: var(--m-font-sans);
  font-size: 14px;
  color: var(--mt-fg);
}

/* ── ApplyPresetPreviewModal ─────────────────────────────────────── */

/* Two-column Before/After row inside the preview modal. */
.cb-preset-preview-row {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

/* Each Before/After column (flex:1, vertical stack of label + preview). */
.cb-preset-preview-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* "BEFORE" / "AFTER" small heading above each preview. Same shape as
 * cb-preset-form-label but kept separate so the form-label can carry
 * its own .has-mb modifier without leaking. */
.cb-preset-preview-label {
  font-family: var(--m-font-mono);
  font-size: 10px;
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  color: var(--mt-muted);
}

/* Scaled-card preview surface: 160px tall, rounded, bordered, deep bg. */
.cb-preset-preview-frame {
  height: 160px;
  overflow: hidden;
  border-radius: 4px;
  border: 1px solid var(--mt-border);
  background: var(--mt-bg-deep);
}

/* Inner scale wrapper (0.6× transform, 167% size to compensate). */
.cb-preset-preview-scale {
  transform: scale(0.6);
  transform-origin: top left;
  width: 167%;
  height: 167%;
}

/* ── ManagePresetsModal ──────────────────────────────────────────── */

/* Empty-state row when no presets saved. Centered, mono, italic. */
.cb-presets-empty {
  padding: 24px 12px;
  text-align: center;
  font-family: var(--m-font-mono);
  font-size: 12px;
  color: var(--mt-muted);
  font-style: italic;
}

/* Vertical list of preset rows. */
.cb-presets-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* One preset row = bordered card on elev bg. Inner header row + optional
 * expanded JSON block below. */
.cb-presets-row {
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  background: var(--mt-bg-elev);
}

/* Header inside a preset row: name button (or rename input) +
 * meta + JSON toggle + delete. */
.cb-presets-row__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
}

/* Inline rename input. Sans-serif, accent border for "you're editing"
 * affordance. flex:1 to fill the row. */
.cb-presets-rename-input {
  flex: 1;
  background: var(--mt-bg);
  border: 1px solid var(--mt-accent);
  border-radius: 3px;
  padding: 4px 6px;
  font-size: 13px;
  font-family: var(--m-font-sans);
  color: var(--mt-fg);
}

/* The clickable preset/theme-name button (transparent, looks like text). It IS
 * a button — click to inline-rename. cursor:pointer + the hover cue below make
 * that discoverable; the old cursor:text made it read as static, non-clickable
 * text, so people (Karl) didn't know it was clickable (Phase 140 THEME-04 follow-up). */
.cb-presets-name-btn {
  flex: 1;
  background: transparent;
  border: none;
  text-align: left;
  cursor: pointer;
  padding: 0;
  font-family: var(--m-font-sans);
  font-size: 12px;
  color: var(--mt-fg);
  font-weight: var(--mt-weight-regular);
  transition: color 0.12s ease;
}
/* Discoverability cue: on hover / keyboard-focus the name shifts to the accent
 * color with a dotted underline and a trailing pencil, clearly reading as
 * "click to rename" (reinforced by the title="Click to rename" tooltip).
 * Tokens only — re-skins with every theme. */
.cb-presets-name-btn:hover,
.cb-presets-name-btn:focus-visible {
  color: var(--mt-accent);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  outline: none;
}
.cb-presets-name-btn:hover::after,
.cb-presets-name-btn:focus-visible::after {
  content: " \270E"; /* pencil */
  opacity: 0.7;
}

/* Meta line ("3 keys · 5/26/2026") in the preset row. mono, tabular. */
.cb-presets-meta {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  font-variant-numeric: tabular-nums;
}

/* Compact .manifold-button override for the JSON toggle. Same shape
 * as cb-preset-row-btn but kept separate so future styling can
 * differentiate "view" vs "destructive". */
.cb-preset-row-btn {
  padding: 4px 8px;
  font-size: 10px;
}

/* Destructive variant: error-color border + text. Applied as a
 * modifier on .cb-preset-row-btn. */
.cb-preset-row-btn.is-danger {
  color: var(--mt-error);
  border-color: var(--mt-error);
}

/* ── #200: what a saved theme says about the theme it came from ─────────────
 *
 * A second line under the row head, on the same terms as .cb-presets-json
 * below — same border-top, same padding — because it is the same kind of thing:
 * more about this row, revealed under it.
 *
 * NOT A WARNING COLOUR ANYWHERE IN THIS BLOCK. Karl, 2026-08-15: a saved theme
 * falling behind its base is not a fault, it is the normal consequence of
 * saving one, and the press is an offer rather than a repair. So the sentence
 * sits in --mt-dim (a step under the row's own name, a step over its mono meta
 * line) and the button is the row's plain .cb-preset-row-btn — never .is-danger,
 * never the accent fill. The one thing that carries weight here is the swatch
 * pair, which is the only part a person actually has to compare.
 *
 * The sentence is prose and the row's meta line is data, which is why this is
 * sans at 11px and that is mono at 10px. */
.manifold-te-drift {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-top: 1px solid var(--mt-border);
}

.manifold-te-drift-said {
  flex: 1;
  min-width: 0;
  font-family: var(--m-font-sans);
  font-size: 11px;
  line-height: 1.4;
  color: var(--mt-dim);
}

/* The settle list, for a theme with nothing written down. Full-row flex basis
 * so it drops below the sentence and its button rather than squeezing between
 * them. */
.manifold-te-drift-list {
  flex: 1 0 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 2px;
  padding-top: 8px;
  border-top: 1px solid var(--mt-border);
}

.manifold-te-drift-why {
  margin: 0 0 6px;
  font-family: var(--m-font-sans);
  font-size: 11px;
  line-height: 1.5;
  color: var(--mt-muted);
}

/* A <label>, so the whole line is the tick target — these rows are 14px tall
 * and a bare checkbox is a small thing to hit. */
.manifold-te-drift-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  cursor: pointer;
}

.manifold-te-drift-row input[type="checkbox"] {
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--mt-accent);
}

.manifold-te-drift-name {
  flex: 1;
  min-width: 0;
  font-family: var(--m-font-sans);
  font-size: 11px;
  color: var(--mt-fg);
}

/* The two colours being compared, side by side. The border matters: without it
 * a swatch the same colour as the row it sits on disappears, which is exactly
 * the case somebody is here to look at. */
.manifold-te-drift-swatch {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border: 1px solid var(--mt-border);
  /* The colour is per-row data, handed in as --drift-swatch by the JSX. Painted
     here rather than as an inline `background` for the reason ThemeSwatch does
     the same: an inline aesthetic key is a lint error, and a value that varies
     per row cannot be a class. */
  background: var(--drift-swatch, transparent);
}

/* The column heading, said once, sitting over the swatch pair it describes.
   Right-aligned rather than positioned: the pair is the last thing in every row
   below, so the end of the line is already the right place and no fixed column
   width is needed to hold a theme name of unknown length. */
.manifold-te-drift-head {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  text-align: right;
  padding: 0 0 2px;
}

/* yours → theirs. The arrow carries what a tick DOES, which is why the pair is
   one element rather than two swatches with a word between them. */
.manifold-te-drift-pair {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.manifold-te-drift-arrow {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
}

.manifold-te-drift-actions {
  display: flex;
  gap: 8px;
  padding-top: 8px;
}

/* Expanded JSON block under a preset row. Inherits border-top so it
 * visually separates from the row head; max-height scroll for long
 * presets. */
.cb-presets-json {
  margin: 0;
  padding: 8px 10px;
  border-top: 1px solid var(--mt-border);
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-all;
  max-height: 240px;
  overflow: auto;
}

/* ── StylePresetsSection (three-state reveal inside Inspector) ───── */

/* Outer vertical stack: button OR (select + buttons) row. */
.cb-style-presets {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Full-width Save-style button (zero-preset state). */
.cb-style-presets__save-full {
  width: 100%;
}

/* Compact horizontal row: select + Save another + Manage. */
.cb-style-presets__row {
  display: flex;
  gap: 6px;
  align-items: center;
}

/* The Apply <select>. Flex:1 to fill the row leaving room for the
 * adjacent buttons. */
.cb-style-presets__select {
  flex: 1;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  padding: 6px 8px;
  color: var(--mt-fg);
  font-family: var(--m-font-sans);
  font-size: 12px;
  cursor: pointer;
}

/* Compact "Save another" / "Manage" buttons inside the row. */
.cb-style-presets__btn {
  padding: 6px 10px;
}

/* ═══ Phase 107 — Skin bar (SKIN-02, D-01, D-04) ════════════════════
 *
 * The compact save/apply/scope/manage bar at the top of the four-drawer
 * styling inspector. Every value references a --mt-* / --m-* token; no
 * raw aesthetic values (the eslint deploy gate). Shapes copied from the
 * cb-style-presets-* / cb-inspector-mode-btn / cb-inspector-section-label
 * / cb-inspector-delete-btn families per 107-UI-SPEC §Component Inventory. */

/* Block container — flex column, hairline separator under it. */
.cb-skin-bar {
  display: flex;
  flex-direction: column;
  gap: var(--mt-space-sm, 8px);
  padding-bottom: var(--mt-space-sm, 8px);
  border-bottom: 1px solid var(--mt-border);
}

/* "SKINS" section label — matches cb-inspector-section-label. */
.cb-skin-bar__label {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--mt-dim);
}

/* SKIN-05 status line — "Based on {name}" / "No saved skin". Muted by
 * default, upgrades to fg when the card was given a look (same pattern as
 * .cb-theme-pin.is-pinned's accent wash). Never accent (D-02).
 *
 * Phase 224 (D-15) reworded the words this line renders. It used to claim the
 * card was wearing the look, which is a live link the product does not have —
 * the look is copied onto the card at apply time. */
.cb-skin-bar__status {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-regular);
  line-height: 1.5;
  color: var(--mt-muted);
}

.cb-skin-bar__status.is-active {
  color: var(--mt-fg);
}

/* Phase 224 (D-15) — the " · edited" suffix, shown only when the card has moved
 * away from the look it was given. One colour step quieter than the name beside
 * it, because the suffix is a note ABOUT the name rather than part of it and the
 * colour step is what says so. --mt-muted rather than the Style panel's
 * --m-text-muted: this line already speaks in --mt-*, and the two lines carry
 * the same meaning in each surface's own token family. */
.cb-skin-bar__status-edited {
  color: var(--mt-muted);
}

/* SKIN-05 reason line — "On because the {Theme} theme is active." Only
 * ever renders in its one active state, so no is-active variant needed. */
.cb-skin-bar__reason {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-regular);
  line-height: 1.5;
  color: var(--mt-muted);
}

/* Full-width "Save as skin…" button (zero-skin state). */
.cb-skin-bar__save-full {
  width: 100%;
}

/* Apply-select + Save-as-skin row (one-or-more-skin state). */
.cb-skin-bar__actions {
  display: flex;
  gap: 6px;
  align-items: center;
}

/* The Apply <select> — copy of cb-style-presets__select (bg-elev, border,
 * 12px sans). flex:1 to fill the row beside the save button. */
.cb-skin-bar__apply-select {
  flex: 1;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  padding: 6px 8px;
  color: var(--mt-fg);
  font-family: var(--m-font-sans);
  font-size: var(--mt-text-sm-size, 12px);
  cursor: pointer;
}

/* Compact "Save as skin…" button inside the actions row. */
.cb-skin-bar__save-btn {
  padding: 6px 10px;
}

/* Scope row — "scope:" label + two toggle buttons + Manage. */
.cb-skin-bar__scope {
  display: flex;
  gap: var(--mt-space-xs, 4px);
  align-items: center;
}

/* "scope:" prefix — 10px mono dim. */
.cb-skin-bar__scope-label {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-bold);
  color: var(--mt-dim);
}

/* Scope toggle buttons — copy of cb-inspector-mode-btn (.is-active accent
 * tint). NOT flex:1 here — they sit inline beside the label, sized to text. */
.cb-skin-bar__scope-btn {
  padding: 4px 8px;
  font-size: var(--mt-text-xs-size, 10px);
  font-family: var(--m-font-mono);
  cursor: pointer;
  border-radius: var(--m-radius-sm, 4px);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
}

.cb-skin-bar__scope-btn.is-active {
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  color: var(--mt-fg);
  border-color: var(--mt-accent);
}

/* "Manage" ghost button — pushed to the row's right edge. */
.cb-skin-bar__manage-btn {
  margin-left: auto;
  padding: 4px 8px;
}

/* Boundary note — what a skin transfers vs. keeps (SKIN-01 legibility). */
.cb-skin-bar__note {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-regular);
  line-height: 1.5;
  color: var(--mt-muted);
}

/* Skin name input (save modal + library rename) — copy of the preset
 * name input shape (bg-elev, border, 12px). */
.cb-skin-bar__name-input {
  width: 100%;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  padding: 6px 8px;
  color: var(--mt-fg);
  font-family: var(--m-font-sans);
  font-size: var(--mt-text-sm-size, 12px);
}

/* ── Broad-apply confirm (D-01) ──────────────────────────────────── */

/* Confirm body copy — 10px mono muted, generous line-height. */
.cb-skin-bar__confirm-body {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-regular);
  line-height: 1.5;
  color: var(--mt-muted);
}

/* The card-count badge inside the confirm body — amber (warn), not red.
 * The action is guarded + reversible (auto-snapshot), so it warns, not alarms. */
.cb-skin-bar__confirm-count {
  color: var(--mt-warn);
  font-weight: var(--mt-weight-bold);
}

/* Broad-apply primary button — --mt-warn border+text, bg-elev fill. NOT
 * .primary, NOT .danger (107-UI-SPEC). */
.cb-skin-bar__confirm-broad-btn {
  padding: 6px 12px;
  font-family: var(--m-font-sans);
  font-size: var(--mt-text-sm-size, 12px);
  cursor: pointer;
  border-radius: 4px;
  background: var(--mt-bg-elev);
  color: var(--mt-warn);
  border: 1px solid var(--mt-warn);
}

.cb-skin-bar__confirm-broad-btn:hover {
  border-color: var(--mt-border-strong, var(--mt-warn));
}

.cb-skin-bar__confirm-broad-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ── Manage library (D-04) ───────────────────────────────────────── */

/* Empty state — centered muted mono. */
.cb-skin-bar__library-empty {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  color: var(--mt-muted);
  text-align: center;
  padding: var(--mt-space-lg, 20px);
}

/* The skins list — column of rows. */
.cb-skin-bar__library-list {
  display: flex;
  flex-direction: column;
  gap: var(--mt-space-sm, 8px);
}

/* One skin row — name (flex:1) + rename + delete inline. */
.cb-skin-bar__library-row {
  display: flex;
  gap: 6px;
  align-items: center;
}

.cb-skin-bar__library-name {
  flex: 1;
  color: var(--mt-fg);
  font-family: var(--m-font-sans);
  font-size: var(--mt-text-sm-size, 12px);
}

/* Rename input inside a row — same name-input shape but flex:1 to fill. */
.cb-skin-bar__library-rename {
  flex: 1;
  width: auto;
}

/* "Really delete?" inline label. */
.cb-skin-bar__library-confirm-label {
  flex: 1;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
}

/* Compact Rename / Save / Cancel buttons in a library row. */
.cb-skin-bar__library-btn {
  padding: 4px 8px;
}

/* Delete button in a row — copy of cb-inspector-delete-btn, but NOT flex:1
 * (it sits inline beside the name). */
.cb-skin-bar__library-del {
  flex: 0 0 auto;
}

/* ── Theme pin panel (Phase 148 SKIN-06/07) ──────────────────────── */
/*
 * ThemePinPanel sits between the SkinBar and LayerAccordion inside
 * InspectorPane, in the exact slot the retired per-theme-override banner
 * used to occupy. It lets a card be pinned to auto-wear a saved skin
 * whenever a chosen theme is active, and offers the "Keep this card's own
 * look" bypass switch.
 *
 * Two states:
 *   .cb-theme-pin           — no active theme-pinned skin
 *   .cb-theme-pin.is-pinned — a theme-pinned skin is actively resolving the look
 *
 * All values are --mt-* tokens — no hardcoded hex or pixel sizes. The
 * is-pinned accent wash matches the cb-skin-bar__scope-btn.is-active
 * pattern (8% accent tint + accent border) for visual parity. The dropdown
 * and checkbox reuse .cb-skin-bar__apply-select / .cb-pin-row-label /
 * .cb-pin-row-checkbox verbatim — no second select or checkbox style here.
 */

/* Outer container — column layout, stacked rows. */
.cb-theme-pin {
  display: flex;
  flex-direction: column;
  gap: var(--mt-space-sm, 8px);
  padding: var(--mt-space-sm, 8px);
  border-bottom: 1px solid var(--mt-border);
  background: var(--mt-bg-elev);
}

/* Pinned state: accent wash background + accent border signal the mode. */
.cb-theme-pin.is-pinned {
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  border-bottom-color: var(--mt-accent);
}

/* "theme pin:" prefix label — 10px mono 700 dim, uppercase. */
.cb-theme-pin__label {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--mt-dim);
}

/* Row holding the "When {theme} is active, wear:" prefix + the select. */
.cb-theme-pin__row {
  display: flex;
  align-items: center;
  gap: var(--mt-space-xs, 4px);
  flex-wrap: wrap;
}

/* Hint copy — covers the disabled-select hint, the bypass helper line, and
 * (Plan 06) the D-03 acknowledgment line; all three share this visual role. */
.cb-theme-pin__hint {
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 10px);
  font-weight: var(--mt-weight-regular);
  line-height: 1.5;
  color: var(--mt-muted);
}

/* ── InspectorPane edges within the 8200-9500 band ───────────────── */

/* Section wrapper above Card frame and Style presets blocks inside
 * the InspectorPane. mt + pt + border-top form the separator. */
.cb-inspector-block {
  margin-top: 8px;
  padding-top: 10px;
  border-top: 1px solid var(--mt-border);
}

/* Gradient preview swatch inside Card frame > Background controls.
 * 42px tall, rounded, bordered, deep bg backplate. The actual gradient
 * paints into background-image via inline custom property because the
 * value is computed per-card from gradientToCss(). */
.cb-card-grad-preview {
  margin-top: 4px;
  height: 42px;
  border-radius: 6px;
  border: 1px solid var(--mt-border);
  background-color: var(--mt-bg-deep);
  background-image: var(--cb-grad-preview-image);
  background-size: var(--cb-grad-preview-size, auto);
}

/* Pin-to-row-height checkbox row inside Card frame. Flex row with
 * top-aligned checkbox + label. */
.cb-pin-row-label {
  display: flex;
  gap: 6px;
  align-items: flex-start;
  font-size: 11px;
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
}

/* The checkbox itself gets margin-top:2 so it visually centers against
 * the wrapping label text. */
.cb-pin-row-checkbox {
  margin-top: 2px;
}

/* Row of left/center/right alignment buttons. */
.cb-align-row {
  display: flex;
  gap: 4px;
}

/* One alignment button. Reads --cb-align-bg / --cb-align-fg /
 * --cb-align-border to switch between active and inactive states. */
.cb-align-btn {
  flex: 1;
  padding: 4px 8px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: var(--cb-align-bg, transparent);
  color: var(--cb-align-fg, var(--mt-muted));
  border: 1px solid var(--cb-align-border, var(--mt-border));
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

/* Glyph (arrow icon) inside the alignment button. */
.cb-align-btn__glyph {
  font-size: 13px;
}

/* Visibility (requires-auth) checkbox row inside Card frame. Same
 * shape as cb-pin-row-label but center-aligned (no wrap). */
.cb-visibility-row {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: 11px;
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
}

/* ── Widget list (above the bodyJsx) ─────────────────────────────── */

/* Scroll container for the list of widgets in the inspector. */
.cb-widget-list {
  max-height: 140px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* "(empty card)" placeholder line. */
.cb-widget-list__empty {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  padding: 4px 0;
}

/* One widget row: button (label + z-pill) + optional up/down arrows. */
.cb-widget-list__row {
  display: flex;
  align-items: stretch;
  gap: 2px;
  padding: 0;
}

/* Up/down arrow stack on the right side of a widget row. */
.cb-widget-list__arrows {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* Label text inside the widget-row button. Ellipsis on overflow. */
.cb-widget-list__label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Right-aligned "z3" indicator showing the widget's z-index. */
.cb-widget-list__z {
  color: var(--mt-muted);
  font-size: 9px;
  flex-shrink: 0;
}

/* "Widgets (N)" header row + filter pill. */
.cb-widget-list-header {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
}

/* Stretch spacer inside the header (pushes pill to right). */
.cb-widget-list-header__spacer {
  flex: 1;
}

/* Empty-inspector wrapper (when no widget selected). Border-left,
 * deep bg, padding, vertical stack. */
.cb-inspector-empty {
  border-left: 1px solid var(--mt-border);
  background: var(--mt-bg-deep);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  min-height: 0;
}

/* "Click a widget above…" hint inside the empty inspector. */
.cb-inspector-hint {
  color: var(--mt-muted);
  font-size: 11px;
  font-family: var(--m-font-mono);
  line-height: 1.6;
  margin-top: 8px;
}

/* ── Frequency advisory + Leaf header (inside bodyJsx, in band) ──── */

/* "Bind to leaf (optional)" sub-label row used inside text/hyperlink
 * renderer body. Uses subLabel() pattern but as a class to remove the
 * inline-style fragment. */
.cb-leaf-bind-label {
  margin-top: 6px;
  margin-bottom: 3px;
}

/* Mono input shared by text inputs in the renderer bodies (text-area,
 * URL field, link text). Differs from cb-mono-input by carrying
 * width:100% + box-sizing — sized for full-row form inputs. */
.cb-form-input-mono {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--m-font-mono);
  font-size: 11px;
}

/* Sans-serif full-width form input variant (link text field). */
.cb-form-input-sans {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--m-font-sans);
  font-size: 12px;
}

/* Phase 53-01 D-03 — type-to-confirm gate (the Stop modal in confirm-modal.jsx).
 * The operator must type the machine's name before the confirm button turns on,
 * so the name has to be unmistakable and the input has to read as a real field
 * on every theme. All structural (spacing/border) using shared --m*-tokens so
 * the theme cascade colors it correctly without a per-theme rule.
 *
 * Recovery if the input looks borderless on a theme: check --mt-border in that
 * theme.css — this file consumes the token, it never declares it. */
.manifold-confirm-typed {
  margin-top: var(--m-pad-md);
}
.manifold-confirm-typed-prompt {
  margin-bottom: var(--m-pad-sm);
  color: var(--m-text-muted);
}
/* The exact text to type — weight 600 in the primary text color so it stands
 * apart from the muted prompt around it and can be copied character-for-char. */
.manifold-confirm-typed-name {
  font-family: var(--m-font-mono);
  font-weight: var(--m-font-weight-semibold, 600);
  color: var(--m-text);
}
/* Reuses .cb-form-input-sans for width/font; this adds the field chrome. */
.manifold-confirm-typed-input {
  padding: var(--m-pad-sm);
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  background: var(--mt-bg);
  color: var(--m-text);
}
.manifold-confirm-typed-input:focus {
  outline: none;
  border-color: var(--mt-border-strong);
}

/* Multi-line text area shared by text + hyperlink fallback. */
.cb-form-textarea {
  min-height: 60px;
  resize: vertical;
  font-family: var(--m-font-sans);
  font-size: 12px;
  line-height: 1.4;
}

/* Generic checkbox-row pattern: row of checkbox + label, mono dim
 * text. Used by Invisible / Audit-clicks / Pin-to-row / Visibility /
 * "use a gradient". */
.cb-form-check-row {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: 11px;
  font-family: var(--m-font-mono);
  color: var(--mt-dim);
}

/* Wrap-on-top variant (used when the label can span 2 lines). */
.cb-form-check-row.is-top {
  align-items: flex-start;
}

/* "use a gradient" row sits between Background sub-label and the
 * gradient block — needs 6px bottom margin to space cleanly. */
.cb-form-check-row.has-mb-sm {
  margin-bottom: 6px;
}

/* Audit-clicks template input wrapper (extra mt-top so it doesn't
 * touch the parent checkbox row). */
.cb-form-audit-template-wrap {
  margin-top: 6px;
}

/* "Chrome widget placeholder" hint line inside gear/auth_state/spacer
 * renderer info-only field. */
.cb-chrome-hint {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
  line-height: 1.5;
}

/* Action widget header: adapter accent + bullet + action-name. */
.cb-action-header {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-dim);
  line-height: 1.5;
}
.cb-action-header__adapter {
  color: var(--mt-accent);
  font-weight: var(--mt-weight-bold);
}
.cb-action-header__name {
  color: var(--mt-fg);
}

/* Action widget parameter list — vertical stack with mt-top. */
.cb-action-params {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}

/* One action parameter row (fallback inputs for non-instance_picker kinds). */
.cb-action-param {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
}

/* The PARAM-NAME label above an action-param input. */
.cb-action-param__label {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

/* Single-leaf inspector leaf-header (the leaf-name + leaf-id display
 * inside the Leaf field). Reads --cb-leaf-header-fg for the leaf-name
 * bold color (default fg, warn when missing). */
.cb-leaf-inspector-header {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-dim);
  line-height: 1.4;
  word-break: break-all;
}
.cb-leaf-inspector-header__name {
  color: var(--mt-fg);
}
.cb-leaf-inspector-header__missing {
  color: var(--mt-warn);
}

/* Frequency advisory chip (fast/normal/slow) under the leaf header.
 * Reads --cb-freq-color and --cb-freq-bg for the per-state coloring
 * (fast = live, normal/slow = muted). */
.cb-freq-chip {
  padding: 2px 6px;
  border-radius: 3px;
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.4px;
  text-transform: uppercase;
  background: var(--cb-freq-bg, color-mix(in srgb, var(--mt-muted) 13%, transparent));
  color: var(--cb-freq-color, var(--mt-muted));
  border: 1px solid var(--cb-freq-border, color-mix(in srgb, var(--mt-muted) 33%, transparent));
}

/* Frequency wrap (margin-top:4 above the chip). */
.cb-freq-wrap {
  margin-top: 4px;
}

/* Inline ParameterPicker stack inside single-leaf Leaf field. ml:12
 * indents the picker rows under the leaf header. */
.cb-leaf-param-stack {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 6px;
  margin-left: 12px;
}

/* Walkthrough <p> spacing inside hint blocks. .has-mb-md applies the
 * 8px bottom margin between paragraphs; default has no margin (used
 * by trailing paragraph in a multi-p walkthrough). */
.cb-walkthrough-p {
  margin: 0;
}
.cb-walkthrough-p.has-mb-md {
  margin: 0 0 8px 0;
}

/* Sub-label margin pair used by gradient/border/corner sub-headings
 * inside Card frame. Pairs with subLabel(t) inline-style spread —
 * .cb-sub-label-margin replaces the `marginTop:8, marginBottom:3`
 * pair (mt-mt-sm + 3px-bottom). Visual tolerance: 3px → 3px (same). */
.cb-sub-label-margin {
  margin-top: 8px;
  margin-bottom: 3px;
}

/* First sub-label in a section — no top margin (the section header
 * already separates), only the 3px bottom margin. */
.cb-sub-label-margin.is-first {
  margin-top: 0;
}

/* Angle sub-label has the HelpHint icon next to it, so it needs
 * flex/center on top of the margin pair. */
.cb-sub-label-margin.is-row {
  display: flex;
  align-items: center;
}

/* Chrome-widget info-only placeholder is a flush mono row inside
 * the gear/auth_state/spacer/tab_bar Field bodies. */
.cb-chrome-placeholder {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
  line-height: 1.5;
}

/* Tiny override for segButton-style buttons (this-cell-only toggle inside
 * widget-list header). Wraps the segButton(t,active) inline-style without
 * adding a Pass-1 inline override. */
.cb-seg-btn-tiny {
  padding: 2px 6px !important;
  font-size: 9px !important;
}

/* ── 19-04g (InspectorPane / Section 7, lines 9500-11900) ────────────────────
 *
 * InspectorPane is the right-hand pane of the card builder modal — every
 * field of every card kind is edited here. The classes below collapse the
 * dense, repetitive inline-style aesthetic blocks that the per-Field bodies
 * carried (segmented choice rows, 3×3 anchor grids, content-scale preview,
 * threshold textareas, accent-tinted CTA buttons, warn strips, etc.) onto a
 * coherent vocabulary. The InspectorPane shape didn't change — only the
 * style aesthetics moved off inline. Every state-driven color treatment
 * (.is-active) injects the accent token via CSS-defined fallbacks so the
 * cascade owns light/dark theme swaps without per-site re-render.
 */

/* Segmented-choice rows used by sweep / sortDir / direction / showAs / stack
 * flow toggles. Two-or-more buttons in a flex row with a small gap. Each
 * .cb-toggle-pill cell is uniform; .is-active swaps to the accent-tinted
 * treatment. Distinct from the seg-button helper (segButton(t,active))
 * because this is a more compact rendering used inside per-Field bodies. */
.cb-toggle-pill-row {
  display: flex;
  gap: 4px;
}
.cb-toggle-pill {
  flex: 1;
  padding: 4px 6px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  cursor: pointer;
  border-radius: 4px;
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  transition: background 120ms linear, color 120ms linear,
              border-color 120ms linear;
}
.cb-toggle-pill.is-active {
  background: color-mix(in srgb, var(--mt-accent) 19%, transparent);
  color: var(--mt-accent);
  border-color: var(--mt-accent);
}

/* Compact variant used by the stack-flow row (smaller padding/font for the
 * cell-stacking sub-row). */
.cb-toggle-pill.is-compact {
  padding: 3px 8px;
}

/* 9-position anchor grid — used by PiP-anchor (Picture-in-picture position)
 * and the Alignment field. Both render as a 3×3 grid of small square cells
 * with a dot indicator showing the active position. */
.cb-anchor-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 3px;
  width: 80px;
}
.cb-anchor-cell {
  width: 24px;
  height: 18px;
  padding: 0;
  cursor: pointer;
  border-radius: 3px;
  background: transparent;
  border: 1px solid var(--mt-border);
  transition: background 120ms linear, border-color 120ms linear;
}
.cb-anchor-cell.is-active {
  background: color-mix(in srgb, var(--mt-accent) 19%, transparent);
  border-color: var(--mt-accent);
}
.cb-anchor-dot {
  display: inline-block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--mt-muted);
}
.cb-anchor-cell.is-active .cb-anchor-dot {
  background: var(--mt-accent);
}

/* PiP container — vertical stack of PiP sub-controls with gap:5 and an
 * mt-top:6 separator from the parent "render as overlay" checkbox. */
.cb-pip-stack {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 6px;
}

/* % vs px toggle inside PiP size sub-row. Two buttons in a flex row with
 * tight bottom margin to the slider beneath. */
.cb-unit-toggle-row {
  display: flex;
  gap: 4px;
  margin-bottom: 4px;
}
.cb-unit-toggle {
  flex: 1;
  padding: 3px 6px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  cursor: pointer;
  border-radius: 4px;
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  transition: background 120ms linear, color 120ms linear,
              border-color 120ms linear;
}
.cb-unit-toggle.is-active {
  background: color-mix(in srgb, var(--mt-accent) 19%, transparent);
  color: var(--mt-accent);
  border-color: var(--mt-accent);
}

/* PiP / scale advisory text — small muted footnote under a slider. */
.cb-pip-hint {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  margin-top: 3px;
  line-height: 1.4;
}

/* Z-index controls (Stacking field) — back/front buttons + numeric input
 * in a single flex row. */
.cb-z-row {
  display: flex;
  gap: 4px;
  align-items: center;
}
.cb-z-btn {
  flex: 1;
  padding: 4px 8px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
}
.cb-z-input {
  width: 50px;
  padding: 4px 6px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  box-sizing: border-box;
}

/* --menu variant: used inside CardContextMenu for the Portrait order field.
 * Inherits the elevated menu surface token so it reads as part of the glass
 * menu rather than a raw browser input dropped in from somewhere else.
 * The native number spinner is hidden (appearance:textfield + ::-webkit-inner
 * spin-button) because the increment is done via direct typing; the spinner
 * chrome clashes with the compact 28px row height. Focus gets the same
 * accent-glow ring the other menu rows and toolbar buttons use. */
.cb-z-input--menu {
  width: 56px;
  background: color-mix(in srgb, var(--mt-bg-deep) 60%, var(--mt-bg-elev));
  color: color-mix(in srgb, var(--mt-fg) 90%, transparent);
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--m-radius-sm, 2px);
  outline: none;
  appearance: textfield;
  -moz-appearance: textfield;
}
.cb-z-input--menu::-webkit-inner-spin-button,
.cb-z-input--menu::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.cb-z-input--menu:focus {
  border-color: var(--mt-accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--mt-accent) 40%, transparent);
}

/* Allow the portrait-order row to exceed the standard 28px cap so the input
 * fits without clipping; keeps the same horizontal padding as other rows.
 * §999.179: chained with .cb-card-menu-item (0,2,0 specificity) so this wins
 * over the base rule's fixed height:28px (7984) regardless of source order —
 * a bare .cb-card-menu-item--input-row (0,1,0) was losing the cascade tie to
 * the later, equal-weight base rule and squashing the 9-spot explode grid. */
.cb-card-menu-item.cb-card-menu-item--input-row {
  height: auto;
  min-height: 28px;
  cursor: default;
}

/* D-02 (Plan 04): overflow-mode dropdown in the card context menu.
 * Theme tokens only — no inline aesthetic styles in JSX (deploy.sh ESLint gate). */
.cb-overflow-select {
  font-family: var(--m-font-sans);
  font-size: 13px;
  background: var(--mt-bg-elev);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  padding: 2px 4px;
  cursor: pointer;
}

/* Position grid (col / row / w / h) — 4-column grid for the four ints. */
.cb-pos-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  gap: 4px;
}
.cb-pos-input {
  width: 100%;
  padding: 4px 6px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  box-sizing: border-box;
}

/* Position legend ("col, row, w, h · drag widget body to move…"). */
.cb-pos-legend {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  margin-top: 2px;
}

/* Compact numeric input — used by the precision / multiplyBy / addOffset /
 * unit / max-rows / row-gap / heatmap-range fields. Same shape as
 * fieldInput but a touch narrower on padding. */
.cb-num-input {
  width: 100%;
  padding: 4px 6px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  box-sizing: border-box;
}
.cb-num-input.is-narrow {
  max-width: 80px;
}
/* LIST-04 (Karl 2026-06-20): the "Custom…" row-count input reveals flush under
 * the Max-rows picker select (0px gap). A small top margin separates them so
 * the input doesn't read as part of the select. Mirrors the reveal-input
 * spacing convention of .cb-derived-custom (which uses 6px on a heavier
 * multi-line block; a single narrow input wants a touch less). */
.cb-maxrows-custom-input {
  margin-top: 4px;
}
.cb-num-input.is-medium {
  max-width: 90px;
}

/* Range slot — the paired min→max input cluster ("min → max" with an
 * arrow span between). Used by Range, Heatmap value range. */
.cb-range-row {
  display: flex;
  gap: 6px;
  align-items: center;
}
.cb-range-arrow {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
}

/* Slow-on-fast warning strip — shown when a slow renderer is bound to a
 * fast-frequency leaf. Warn-tinted bordered card under the renderer select. */
.cb-warn-strip {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-warn);
  margin-top: 4px;
  line-height: 1.4;
  padding: 4px 6px;
  background: color-mix(in srgb, var(--mt-warn) 8%, transparent);
  border-radius: 3px;
  border: 1px solid color-mix(in srgb, var(--mt-warn) 25%, transparent);
}

/* Phase 64: stashed-sources note. Shown under the Renderer picker when a
 * single-source view is holding sources kept from an earlier multi→single
 * switch. Informational, NOT a warning — calm accent tint (not --mt-warn), so
 * it reads as "your data is safe" rather than "something is wrong". Token-only;
 * restyles per theme for free. */
.cb-stash-note {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-dim);
  margin-top: 4px;
  line-height: 1.45;
  padding: 5px 7px;
  background: color-mix(in srgb, var(--mt-accent) 7%, transparent);
  border-radius: 3px;
  border: 1px solid color-mix(in srgb, var(--mt-accent) 22%, transparent);
}

/* Phase 65: "Calculate / convert" (derived value) controls. Token-only, calm —
 * matches the rest of the inspector. */
.cb-derived-b { margin-top: 6px; display: flex; flex-direction: column; gap: 5px; }
.cb-derived-b__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 11px; color: var(--mt-dim); }
.cb-derived-b__label { color: var(--mt-muted); }
.cb-derived-b__radio { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.cb-derived-b__current { font-size: 11px; color: var(--mt-dim); margin-bottom: 4px; }
.cb-derived-b__current--empty { font-style: italic; opacity: 0.75; }
.cb-derived-custom { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
.cb-derived-custom__lbl { font-size: 10px; color: var(--mt-muted); line-height: 1.4; }
.cb-derived-custom__lbl code,
.cb-derived-note code {
  font-family: var(--m-font-mono);
  font-size: 0.92em;
  padding: 0 2px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--mt-accent) 10%, transparent);
  color: var(--mt-fg);
}
.cb-derived-note {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-warn);
  margin-top: 5px;
  line-height: 1.45;
  padding: 5px 7px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--mt-warn) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-warn) 25%, transparent);
}

/* Phase 69 (69-03): "Add a calculated line" letter -> series-name legend.
 * Token-only, calm — matches the rest of the inspector. Each row pairs the
 * spreadsheet cell letter (A, B, C…) with the source line's name so the user
 * knows what their formula references. */
.cb-calc-legend {
  margin-top: 6px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 5px 7px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--mt-accent) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 18%, transparent);
}
.cb-calc-legend__empty {
  font-size: 10px;
  color: var(--mt-dim);
  font-style: italic;
}
.cb-calc-legend__row {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11px;
}
.cb-calc-legend__letter {
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-semibold);
  min-width: 16px;
  text-align: center;
  color: var(--mt-fg);
  background: color-mix(in srgb, var(--mt-accent) 14%, transparent);
  border-radius: 2px;
}
.cb-calc-legend__name {
  color: var(--mt-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* States / labels chip row — used by the Possible-states and
 * Possible-labels read-only previews. Each chip is a small pill with
 * accent-tinted background, set via inline CSS custom prop so the chip's
 * color can be data-driven. */
.cb-state-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.cb-state-chip {
  padding: 2px 7px;
  border-radius: 999px;
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  background: var(--cb-state-chip-bg, color-mix(in srgb, var(--mt-muted) 13%, transparent));
  color: var(--cb-state-chip-fg, var(--mt-muted));
  border: 1px solid var(--cb-state-chip-border, color-mix(in srgb, var(--mt-muted) 33%, transparent));
}
.cb-state-chip.is-icon-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 6px;
  text-transform: none;
  letter-spacing: 0;
}
.cb-state-chip-overflow {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
}

/* Time range — leaf-default trigger button + reset chevron. Mainly used
 * inside the "Time range" Field. */
.cb-time-range-row {
  display: flex;
  gap: 4px;
  align-items: center;
}
.cb-time-range-btn {
  flex: 1;
  padding: 5px 9px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
}
.cb-time-range-btn__label {
  flex: 1;
  text-align: left;
}
.cb-time-range-reset {
  padding: 5px 9px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-muted);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
}

/* Thresholds / iconMap / valueMap / heatmap-colors JSON textareas. Mono
 * with tight line-height; minHeight varies by field. */
.cb-json-textarea {
  width: 100%;
  padding: 5px 7px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  box-sizing: border-box;
  line-height: 1.4;
  resize: vertical;
  min-height: 46px;
}
.cb-json-textarea.is-tall {
  min-height: 90px;
  font-size: 11px;
}
.cb-json-textarea.is-short {
  min-height: 36px;
}

/* Accent-tinted CTA pill — used by the "wire up live weather icon" button
 * and "Use weather codes" preset trigger. Wider treatment than .cb-toggle-pill
 * (carries the persuasive accent fill). */
.cb-cta-pill {
  width: 100%;
  padding: 6px 10px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 33%, transparent);
  border-radius: 4px;
  cursor: pointer;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  margin-bottom: 8px;
}
.cb-cta-pill.is-inline {
  width: auto;
  padding: 4px 10px;
  margin-bottom: 0;
}

/* Muted ghost-CTA — secondary "Override with WMO preset", "Clear",
 * "Clear override" buttons sitting beside an accent CTA. */
.cb-ghost-cta {
  padding: 4px 10px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}

/* Map-button-row — "Use weather codes" + "Clear" row under the JSON
 * textarea. flex with gap:6 and wrap. */
.cb-cta-row {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  flex-wrap: wrap;
}

/* "Using leaf's built-in iconMap" live notice — small live-tinted strip
 * under the JSON textarea. */
.cb-iconmap-notice {
  margin-top: 6px;
  margin-bottom: 6px;
  padding: 5px 8px;
  background: color-mix(in srgb, var(--mt-live) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-live) 33%, transparent);
  border-radius: 4px;
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-live);
  line-height: 1.4;
}

/* Content-scale preview cluster — left tile + right slider column. */
.cb-content-scale-row {
  display: flex;
  gap: 10px;
  align-items: center;
}
.cb-content-scale-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
/* Schematic preview tile. Outer cell stays at a fixed 70×44 box with
 * dashed-border + clipping; inner Aa renders at the current scale
 * (consumed by --cb-content-scale-transform / --cb-content-scale-border). */
.cb-content-scale-tile {
  width: 70px;
  height: 44px;
  flex-shrink: 0;
  border: 1px dashed var(--mt-border);
  border-radius: 4px;
  overflow: hidden;
  position: relative;
  background: var(--mt-bg);
}
.cb-content-scale-inner {
  width: 100%;
  height: 100%;
  transform: var(--cb-content-scale-transform, scale(1));
  transform-origin: center center;
  border: var(--cb-content-scale-border, none);
  border-radius: var(--cb-content-scale-radius, 0);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
  color: var(--mt-accent);
  font-size: 16px;
  font-weight: var(--mt-weight-bold);
  font-family: var(--m-font-sans);
  letter-spacing: -0.5px;
}

/* "Send to back / Bring to front" muted button row inside z-index editor —
 * already covered by .cb-z-btn above. (Kept the doc here for grep.) */

/* Aggregate / format / dedup / fit / chartAspect / defaultAfter / iconSize /
 * heatmap sortMode / size /headerStyle / valueRefreshHz / textSize / label-size /
 * lineThickness — all use the same `fieldInput(t)` chrome with a small padding
 * tweak. Many sites still spread fieldInput(t) for typography; the structural
 * tweaks below carry the consistent padding/font-size variant. */
.cb-select-compact {
  padding: 4px 6px !important;
}

/* Small "px" / "Hz" advisory label sat next to the slider value. */
.cb-axis-suffix {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
}
.cb-axis-suffix.is-narrow {
  min-width: 32px;
}

/* Centered 64px fixed-width numeric input used by the icon-size px field
 * (paired with a range slider). */
.cb-num-input-fixed {
  width: 64px;
  padding: 4px 6px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  text-align: center;
}

/* InspectorPane outer flex column — borderLeft + deep bg. Does NOT scroll;
 * overflow lives in .cb-inspector-body so the footer stays pinned.
 * EDITOR-02 / D-04: overflow-y removed from shell, moved to inner body. */
.cb-inspector-shell {
  border-left: 1px solid var(--mt-border);
  background: var(--mt-bg-deep);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
}

/* InspectorPane scrollable content region — all inspector blocks above the
 * footer. flex:1 fills remaining shell height; overflow-y:auto lets it scroll
 * when content (widgets + presets + ThemePinPanel + LayerAccordion) is tall.
 * EDITOR-02 / D-04: new class, paired with .cb-inspector-shell losing its scroll. */
.cb-inspector-body {
  overflow-y: auto;
  /* Contain the scroll: the Data section embeds a nested scroll list (the
   * leaf/source picker, .cb-leaf-picker-body / .cb-catalog-list). Without this,
   * once the wheel is over that inner list and it bottoms out, the scroll
   * chains PAST the inspector to the dashboard page behind it, and the body
   * can't be scrolled to the controls below the picker (Phase-111 regression
   * from moving the scroll onto this body). `contain` stops the chain at the
   * inspector body — the inner picker still chains UP to here (so the body
   * scrolls and the lower controls become reachable), but nothing escapes OUT
   * to the document. Guarded by tests/111-inspector-scroll-chaining.test.js. */
  overscroll-behavior: contain;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* InspectorPane footer — Reset style + Delete bottom row. flex-shrink:0 pins
 * it at the bottom of the shell regardless of content height above it.
 * EDITOR-02 / D-04: was margin-top:auto (scroll-relative), now flex-shrink:0 (viewport-pinned). */
.cb-inspector-footer {
  flex-shrink: 0;
  display: flex;
  gap: 6px;
}

/* Ghost-style "Reset style" button (flex:1, muted outline). */
.cb-inspector-reset-btn {
  flex: 1;
  padding: 6px 10px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
}

/* Error-tinted "Delete" button (flex:1, error-color outline + tint). */
.cb-inspector-delete-btn {
  flex: 1;
  padding: 6px 10px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: color-mix(in srgb, var(--mt-error) 13%, transparent);
  color: var(--mt-error);
  border: 1px solid color-mix(in srgb, var(--mt-error) 33%, transparent);
  border-radius: 4px;
  cursor: pointer;
}

/* PiP-size slider row — flex row of range slider + numeric input. */
.cb-pip-size-row {
  display: flex;
  gap: 6px;
  align-items: center;
}

/* ShowWhenEditor / VisibilityEditor enable button — ghost button used to
 * open the conditional-visibility flow. */
.cb-visibility-enable {
  padding: 5px 12px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}

/* Help-text muted line at top of conditional-visibility editor. */
.cb-visibility-help {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  line-height: 1.5;
  margin-bottom: 8px;
}

/* ShowWhenEditor constraint sub-label ("Bind to leaf" / "Constraints"). */
.cb-visibility-sublabel {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  color: var(--mt-muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-bottom: 3px;
}
.cb-visibility-sublabel.has-mt {
  margin-top: 10px;
  margin-bottom: 4px;
}

/* Constraint row inside ShowWhenEditor. 4-column grid: comparator |
 * operand-mode | operand-input | remove button. */
.cb-constraint-row {
  display: grid;
  grid-template-columns: 90px 80px 1fr auto;
  gap: 4px;
  margin-bottom: 4px;
  align-items: center;
}

/* ConstraintRow remove (×) button. */
.cb-constraint-remove {
  padding: 4px 8px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  cursor: pointer;
}

/* Constraint row "Add constraint" + "Remove rule" footer buttons. */
.cb-visibility-cta-row {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  flex-wrap: wrap;
}
.cb-visibility-add-btn {
  padding: 4px 10px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  border-radius: 3px;
  cursor: pointer;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.cb-visibility-add-btn.is-enabled {
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 33%, transparent);
}
.cb-visibility-add-btn.is-disabled {
  color: var(--mt-muted);
  border: 1px solid var(--mt-border);
  cursor: not-allowed;
}
.cb-visibility-remove-btn {
  padding: 4px 10px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  cursor: pointer;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}

/* Live-evaluation preview strip below constraint editor. Three tone
 * variants: pending (muted), passing (accent/success), failing (warn). */
.cb-visibility-preview {
  margin-top: 10px;
  padding: 7px 10px;
  border-radius: 4px;
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1.4;
  background: var(--cb-visibility-preview-bg, color-mix(in srgb, var(--mt-muted) 8%, transparent));
  border: 1px solid var(--cb-visibility-preview-border, var(--mt-border));
}
.cb-visibility-preview-pill {
  margin-left: 8px;
  padding: 1px 8px;
  border-radius: 99px;
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  background: var(--cb-visibility-pill-bg, color-mix(in srgb, var(--mt-muted) 19%, transparent));
  color: var(--cb-visibility-pill-fg, var(--mt-muted));
}

/* LeafIconMapDefaultEditor outer wrapper — dashed-top border separator. */
.cb-leaf-iconmap-shell {
  margin-top: 12px;
  padding-top: 8px;
  border-top: 1px dashed var(--mt-border);
}

/* Small-value modifiers used to pin a margin onto an existing classed
 * element without re-introducing the inline-style aesthetic. Pair with
 * `.cb-form-check-row`, `.cb-chrome-placeholder`, `.cb-cta-row`, etc. */
.has-mt-0  { margin-top:    0  !important; }
.has-mt-3  { margin-top:    3px !important; }
.has-mt-4  { margin-top:    4px !important; }
.has-mt-6  { margin-top:    6px !important; }
.has-mb-3  { margin-bottom: 3px !important; }
.has-mb-4  { margin-bottom: 4px !important; }
.has-mb-6  { margin-bottom: 6px !important; }

/* Tweaks for cb-chrome-placeholder used in iconMap header (smaller font +
 * tighter line height than the default). */
.cb-chrome-placeholder.is-tight {
  font-size: 9px;
  line-height: 1.4;
}

/* Tweak for cb-pip-hint when it sits inside an editor footer (smaller
 * fonts; same family). */

/* Constraint-row stack-flow opacity wrapper (cell-stacking flow row when
 * the widget is not the anchor — dims controls). */
.cb-stack-row-dim {
  opacity: 0.55;
}

/* Primary CTA pill used inside LeafIconMapDefaultEditor's "Save user
 * default" button — accent-fill rather than accent-tint. One-off; if more
 * primary actions land here, this becomes the canonical accent-fill. */
.cb-primary-cta {
  padding: 4px 10px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: var(--mt-accent);
  color: var(--mt-on-accent, #fff);
  border: none;
  border-radius: 4px;
  cursor: pointer;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}

/* ColorSwatch component — small square accent-tinted swatch button used
 * in the ColorTokenSelect row. Reads --cb-swatch-fg / --cb-swatch-bg /
 * --cb-swatch-border for the active state. */
.cb-color-swatch {
  padding: 4px 6px;
  font-size: 9px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-semibold);
  cursor: pointer;
  border-radius: 4px;
  background: var(--cb-swatch-bg, transparent);
  color: var(--cb-swatch-fg, var(--mt-dim));
  border: 1px solid var(--cb-swatch-border, var(--mt-border));
  display: flex;
  align-items: center;
  gap: 4px;
  justify-content: center;
}
.cb-color-swatch__dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--cb-swatch-fg, var(--mt-dim));
}

/* HexColorInput's .cb-hex-row / .cb-hex-picker / .cb-hex-text went with the
 * component (issue 100). ColorTokenSelect's "custom hex…" option was
 * replaced by the constants tier — red, blue, yellow, green, black, white, grey — so no control
 * in the card editor takes a typed colour any more. The theme editor keeps its
 * own hex box in manifold.jsx, with its own classes; that one is where a theme
 * DEFINES what a colour is, which is the one place a literal belongs. */

/* ============================================================================
 * Phase 19-04h: Chrome helpers — Slider / _ColorBar / ColorTokenSelect /
 *               CardLibrary class vocabulary.
 *
 * Replaces ~189 in-band inline-style sites in card-builder.jsx lines
 * 11900-13400. Each rule below has a doc comment identifying its
 * consumer site. State-driven values (per-card tab tints, per-instance
 * sizes, dynamic widths) flow through --cb-* custom properties.
 * ============================================================================ */

/* === Slider — paired range + number input row ============================= *
 *
 * Used by the `Slider` component (card-builder.jsx ~11946). The number
 * input's width is per-instance (caller's `inputWidth` prop), so it flows
 * via --cb-slider-input-width. The unit suffix and number input read
 * `fontSize` from props as --cb-slider-font-size.
 */
.cb-slider-row {
  display: flex;
  gap: 6px;
  align-items: center;
  flex: 1;
  min-width: 0;
}
.cb-slider-range {
  flex: 1;
  min-width: 0;
}
.cb-slider-input {
  width: var(--cb-slider-input-width, 48px);
  padding: 2px 4px;
  font-size: var(--cb-slider-font-size, 10px);
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  text-align: right;
}
.cb-slider-unit {
  font-size: var(--cb-slider-unit-font-size, 9px);
  font-family: var(--m-font-mono);
  color: var(--mt-muted);
  min-width: 14px;
}

/* === _ColorBar — small square swatch with placeholder fallback =========== *
 *
 * Used by ColorTokenSelect's row and trigger. (It also backed the "custom
 * hex" button until issue 100 replaced that with the constants tier.)
 * `color` flows via --cb-color-bar-bg (null → diagonal-stripe fallback).
 * `size` flows via --cb-color-bar-size for the per-instance pixel size.
 */
.cb-color-bar {
  display: inline-block;
  width: var(--cb-color-bar-size, 14px);
  height: var(--cb-color-bar-size, 14px);
  border-radius: 3px;
  flex-shrink: 0;
  background: var(--cb-color-bar-bg, transparent);
  border: 1px solid var(--mt-border);
}
/* When no color is set, paint the diagonal-stripe placeholder so users
 * can distinguish "auto / off / transparent" from a real color. */
.cb-color-bar.is-empty {
  background-image: linear-gradient(135deg,
    transparent 45%,
    var(--mt-muted) 45%,
    var(--mt-muted) 55%,
    transparent 55%);
}

/* === ColorTokenSelect — dropdown shell + popover ========================= *
 *
 * Used by the ColorTokenSelect component (card-builder.jsx ~11988).
 * State-driven values: hover background passes through --cb-cts-row-hover-bg
 * (computed from theme fg as `${t.fg}10`). The popover's "selected" tint
 * is a fixed token mix, so no custom prop needed.
 */
.cb-cts-shell {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.cb-cts-trigger-wrap {
  display: flex;
  gap: 6px;
  align-items: center;
  position: relative;
}
.cb-cts-trigger {
  flex: 1;
  padding: 3px 24px 3px 6px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  cursor: pointer;
  text-align: left;
  position: relative;
}
.cb-cts-chevron {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%) rotate(0deg);
  transition: transform 120ms;
  font-size: 9px;
  color: var(--mt-muted);
  pointer-events: none;
}
.cb-cts-chevron.is-open {
  transform: translateY(-50%) rotate(180deg);
}
/* #135: the list is portaled to <body> and positioned in VIEWPORT coordinates
 * by `fitColorPopover` (card-builder.jsx), which writes left / top / width /
 * max-height inline per open. It was `position:absolute` anchored to
 * .cb-cts-trigger-wrap, which made .cb-layer-drawer-body's `overflow:hidden`
 * its ceiling — in the Edge drawer that left two of twenty-three colours
 * reachable. Do not put left/top/max-height back here: two authorities on the
 * same box is how the flip-upward case silently stops working. */
.cb-cts-popover {
  position: fixed;
  /* Above .cb-modal-overlay (1000). The old 1000 only worked because this
     node was a descendant of the modal; on <body> it is a sibling. */
  z-index: 10000;
  overflow-y: auto;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border-strong, var(--mt-border));
  border-radius: 4px;
  padding: 4px 0;
  box-shadow: 0 6px 18px rgba(0,0,0,0.35);
}
.cb-cts-row {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 4px 8px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  cursor: pointer;
  font: inherit;
  font-size: 11px;
  font-family: var(--m-font-mono);
  color: var(--mt-fg);
}
.cb-cts-row.is-selected {
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
}
.cb-cts-row-label {
  flex: 1;
}
.cb-cts-group-label {
  padding: 6px 8px 2px;
  font-size: 9px;
  font-family: var(--m-font-mono);
  color: var(--mt-muted);
  font-weight: var(--mt-weight-bold);
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
/* .cb-cts-hex-btn — the popover's "custom hex…" escape hatch — went with issue 100.
 * The escape hatch is now a TIER in the same list rather than a button below it:
 * picking `red` is a row like any other, and lib/color-tiers.js recognises it,
 * so the card can carry an `off theme` mark saying it follows no theme. An
 * escape hatch styled as a separate thing was an escape hatch nothing could
 * read back. */

/* === CardLibrary — library list, header bar, filter, rows, deploy menu ==== *
 *
 * Used by the CardLibrary component (card-builder.jsx ~12176). The
 * library is the top-of-Custom-tab list and is the catalog-side surface
 * section 8 owns. Per-card row-active tints flow through inline
 * `style={{background: …}}` only where a per-row state (expanded /
 * menu-open / current-header) requires it — those use coupled
 * --cb-cl-row-* custom properties.
 */
.cb-cl-shell {
  padding: 14px 14px 0;
}
.cb-cl-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
/* Tighter bottom margin when the placement-hint subtitle is rendered
 * right below the header (so the hint sits adjacent to the title). */
.cb-cl-header.has-hint {
  margin-bottom: 4px;
}
.cb-cl-title {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.cb-cl-filter-wrap {
  position: relative;
  flex: 1 1 200px;
  min-width: 140px;
  max-width: 360px;
}
.cb-cl-filter-input {
  width: 100%;
  padding: 5px 26px 5px 10px;
  font-family: var(--m-font-mono);
  font-size: 11px;
  background: var(--mt-bg-elev);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  outline: none;
  box-sizing: border-box;
}
/* Accent border when filter has a term — visual cue that filter is
 * active. */
.cb-cl-filter-input.is-active {
  border-color: var(--mt-accent);
}
.cb-cl-filter-clear {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  padding: 0;
  background: transparent;
  color: var(--mt-muted);
  border: none;
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
}
.cb-cl-new-btn {
  padding: 6px 12px;
  font-size: 12px;
  font-weight: var(--mt-weight-bold);
  font-family: var(--m-font-mono);
  background: var(--mt-accent);
  color: var(--mt-on-accent, #fff);
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
.cb-cl-placement-hint {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-dim);
  margin-bottom: 10px;
  font-style: italic;
}
.cb-cl-error {
  padding: 8px 12px;
  background: color-mix(in srgb, var(--mt-error) 15%, transparent);
  color: var(--mt-error);
  border: 1px solid color-mix(in srgb, var(--mt-error) 33%, transparent);
  border-radius: 4px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  margin-bottom: 10px;
}
.cb-cl-loading {
  padding: 20px 0;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: 11px;
}
.cb-cl-empty {
  padding: 24px 18px;
  border: 1px dashed var(--mt-border-strong, var(--mt-border));
  border-radius: 10px;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: 12px;
  text-align: center;
}
.cb-cl-empty__title {
  font-weight: var(--mt-weight-bold);
  margin-bottom: 4px;
  color: var(--mt-dim);
}
.cb-cl-no-match {
  padding: 18px 14px;
  border: 1px dashed var(--mt-border);
  border-radius: 8px;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: 11px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.cb-cl-no-match__term {
  color: var(--mt-dim);
}
.cb-cl-no-match__clear {
  padding: 4px 10px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
}
.cb-cl-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  border: 1px solid var(--mt-border);
  border-radius: 6px;
  /* No overflow:hidden — that would clip the deploy dropdown when it
   * extends past the row's bottom edge. */
  background: var(--mt-bg-elev);
}
.cb-cl-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-top: 1px solid var(--mt-border);
  background: transparent;
}
/* First row in the list — no top border (the list wrapper carries it). */
.cb-cl-row.is-first {
  border-top: none;
}
/* Expanded row — light accent tint shows which row's preview is open. */
.cb-cl-row.is-expanded {
  background: color-mix(in srgb, var(--mt-accent) 3%, transparent);
}
/* Inline preview-toggle chevron button (▸ / ▾). Reused base from
 * the row's ICON_BTN local const that the JSX previously defined inline. */
.cb-cl-preview-toggle {
  padding: 4px 7px;
  font-size: 13px;
  line-height: 1;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-muted);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  min-width: 28px;
}
.cb-cl-preview-toggle.is-expanded {
  color: var(--mt-accent);
  border-color: var(--mt-accent);
}
.cb-cl-row-name {
  flex: 1;
  font-family: var(--m-font-sans);
  font-weight: var(--mt-weight-semibold);
  font-size: 13px;
  color: var(--mt-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-cl-row-size {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  font-weight: var(--mt-weight-semibold);
  flex-shrink: 0;
}
.cb-cl-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  justify-content: flex-end;
  max-width: 220px;
  min-width: 0;
}
.cb-cl-tab-unassigned {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  font-style: italic;
}
.cb-cl-tab-chip {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  padding: 1px 6px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--mt-accent) 15%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 25%, transparent);
}
.cb-cl-actions {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
}
/* "+ Place here" primary button — only mounts when CardLibrary is in
 * placement mode. Accent fill, same shape as the cb-cl-new-btn but with
 * the per-row icon-button height. */
.cb-cl-place-btn {
  padding: 4px 10px;
  font-size: 11px;
  font-weight: var(--mt-weight-bold);
  font-family: var(--m-font-mono);
  background: var(--mt-accent);
  color: var(--mt-on-accent, #fff);
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
/* Per-row icon button (✎ edit / ⎘ duplicate / × delete). Same as the
 * preview toggle chrome but without the min-width override. */
.cb-cl-icon-btn {
  padding: 4px 7px;
  font-size: 13px;
  line-height: 1;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
}
/* Delete variant uses a muted color (vs the row's normal dim) so it
 * reads as the destructive action without screaming red. */
.cb-cl-icon-btn.is-delete {
  color: var(--mt-muted);
}
.cb-cl-deploy-wrap {
  position: relative;
}
.cb-cl-deploy-btn {
  padding: 4px 10px;
  font-size: 11px;
  font-weight: var(--mt-weight-bold);
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
}
.cb-cl-deploy-btn.is-open {
  background: color-mix(in srgb, var(--mt-accent) 15%, transparent);
  color: var(--mt-accent);
  border-color: var(--mt-accent);
}
.cb-cl-deploy-arrow {
  font-size: 9px;
}
.cb-cl-deploy-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 50;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border-strong, var(--mt-border));
  border-radius: 6px;
  padding: 6px 4px;
  min-width: 200px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.cb-cl-deploy-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 10px;
  cursor: pointer;
  font-size: 11px;
  font-family: var(--m-font-mono);
  color: var(--mt-fg);
  background: transparent;
  border-radius: 3px;
}
.cb-cl-deploy-row.is-checked {
  background: color-mix(in srgb, var(--mt-accent) 9%, transparent);
}
.cb-cl-deploy-row__spacer {
  flex: 1;
}
.cb-cl-deploy-row__hint {
  color: var(--mt-muted);
  font-size: 9px;
}
.cb-cl-deploy-divider {
  height: 1px;
  background: var(--mt-border);
  margin: 4px 6px;
}
.cb-cl-preview-body {
  border-top: 1px solid var(--mt-border);
  background: var(--mt-bg-deep);
  padding: 14px;
}
.cb-cl-preview-inner {
  /* Cap the preview height so big cards don't run off-screen. */
  max-height: 420px;
  overflow: auto;
}
.cb-cl-preview-loading {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
  text-align: center;
  padding: 12px 0;
}

/* ── Recently deleted (Phase 66-03, D-06) ────────────────────────────────────
 * The durable-trash section that hangs off the bottom of the Card library. A
 * subtle top divider sets it apart from the live card list above. Mirrors the
 * .cb-cl-row / .cb-cl-icon-btn vocabulary so it reads as native chrome; tokens
 * only (zero hex) so it restyles cleanly per theme. */
.cb-cl-trash-section {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--mt-border);
}
.cb-cl-trash-heading {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 8px;
}
.cb-cl-trash-title {
  font-family: var(--m-font-sans);
  font-weight: var(--mt-weight-semibold);
  font-size: 12px;
  color: var(--mt-dim);
  letter-spacing: 0.02em;
}
.cb-cl-trash-count {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  color: var(--mt-muted);
}
/* Empty state — same dashed, quiet treatment as the library's other empties. */
.cb-cl-trash-empty {
  padding: 16px 14px;
  border: 1px dashed var(--mt-border);
  border-radius: 8px;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: 11px;
  text-align: center;
}
.cb-cl-trash-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  border: 1px solid var(--mt-border);
  border-radius: 6px;
  background: var(--mt-bg-elev);
}
.cb-cl-trash-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-top: 1px solid var(--mt-border);
}
/* First row carries no top border — the list wrapper draws the edge. */
.cb-cl-trash-row:first-child {
  border-top: none;
}
.cb-cl-trash-name {
  flex: 1;
  font-family: var(--m-font-sans);
  font-weight: var(--mt-weight-semibold);
  font-size: 13px;
  color: var(--mt-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-cl-trash-when {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  flex-shrink: 0;
}
.cb-cl-trash-daysleft {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  padding: 1px 6px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--mt-accent) 12%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 22%, transparent);
  flex-shrink: 0;
}
/* Restore button — the section's primary action. Accent-outlined (not filled)
 * so it reads as recovery rather than the destructive delete it reverses. */
.cb-cl-trash-restore {
  padding: 4px 12px;
  font-size: 11px;
  font-weight: var(--mt-weight-bold);
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 45%, transparent);
  border-radius: 4px;
  cursor: pointer;
  flex-shrink: 0;
}
.cb-cl-trash-restore:hover {
  background: color-mix(in srgb, var(--mt-accent) 12%, transparent);
  border-color: var(--mt-accent);
}


/* ============================================================================
 * Section 9 — CardBuilderModal orchestrator + final pickers
 * (Phase 19 Plan 04i — band 13400-14275 of card-builder.jsx)
 * ----------------------------------------------------------------------------
 * Component anchors: CardBuilderModal (outer chrome + body), TabsPicker,
 * AlignSelector, PlacementSelector, LinkBelowIndicator, SizeSelector,
 * LeafOverridesBrowser. All theme-agnostic; reads --mt-* tokens.
 * ============================================================================ */

/* === CardBuilderModal — outer chrome ====================================== */

/* Full-viewport scrim. Click outside the modal box closes the modal
 * (CardBuilderModal's onClick handler). */
.cb-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.72);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
/* Modal frame. Width clamped to keep the picker rails readable on
 * narrow viewports while still using the screen on wide displays. */
.cb-modal-box {
  width: min(96vw, 1800px);
  height: min(92vh, 1000px);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--mt-border-strong);
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}
/* Sticky header strip — name input + size/align/placement/tabs pickers +
 * Save / Cancel cluster. flex-shrink:0 keeps it pinned when body scrolls. */
.cb-modal-header {
  padding: 10px 14px;
  border-bottom: 1px solid var(--mt-border);
  background: var(--mt-bg-elev);
  display: flex;
  gap: 10px;
  align-items: center;
  flex-shrink: 0;
  flex-wrap: wrap;
}
/* "NEW CARD" / "EDIT" badge — mono caps. */
.cb-modal-mode-badge {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-muted);
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
}
/* Undo/Redo button cluster — two buttons sharing a border. */
.cb-modal-undo-group {
  display: flex;
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  overflow: hidden;
}
.cb-modal-undo-btn {
  padding: 6px 10px;
  font-size: 13px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-fg);
  border: none;
  cursor: pointer;
}
.cb-modal-undo-btn.is-disabled {
  color: var(--mt-muted);
  cursor: not-allowed;
  opacity: 0.4;
}
/* Right-most undo/redo button gets a left border (the +1 in the group). */
.cb-modal-undo-btn.is-second {
  border-left: 1px solid var(--mt-border);
}
/* Name input — flex:1 fills remaining space between badge and pickers. */
.cb-modal-name-input {
  flex: 1;
  padding: 6px 10px;
  font-size: 13px;
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  font-family: var(--m-font-sans);
  min-width: 200px;
}
/* Save / Save-as / Open-existing / Cancel button row — base shape lives
 * in btnPrimary(t) / btnGhost(t) helpers (out-of-band; see 19-04i deferral
 * note). These classes only carry the per-instance sizing the chrome adds. */
.cb-modal-save-btn {
  padding: 5px 16px;
  font-size: 11px;
}
.cb-modal-save-btn.is-disabled {
  cursor: not-allowed;
  opacity: 0.4;
}
.cb-modal-saveas-btn,
.cb-modal-openexist-btn,
.cb-modal-cancel-btn {
  padding: 5px 12px;
  font-size: 11px;
}
.cb-modal-saveas-btn {
  color: var(--mt-accent);
  border-color: color-mix(in srgb, var(--mt-accent) 33%, transparent);
}
.cb-modal-saveas-btn.is-disabled,
.cb-modal-openexist-btn.is-disabled {
  cursor: not-allowed;
  opacity: 0.4;
}
.cb-modal-cancel-btn {
  padding: 5px 14px;
}
/* INSP-07: keep all four action buttons from shrinking below their label
 * width at narrow viewport. Without flex-shrink:0 the flex algo squeezes
 * them until overflow:hidden on .cb-modal-box clips them off the right edge.
 * white-space:nowrap prevents multi-line label breaks inside the button. */
.cb-modal-save-btn,
.cb-modal-saveas-btn,
.cb-modal-openexist-btn,
.cb-modal-cancel-btn {
  flex-shrink: 0;
  white-space: nowrap;
}
/* INSP-07: cluster wrapper groups Save / Save-as / Open-existing / Cancel
 * into one wrappable unit. margin-left:auto pushes the whole cluster to the
 * right side of the header flex row; flex-shrink:0 keeps it intact under
 * pressure (same pattern as .cb-cl-trash-restore); wraps to a new line as a
 * unit when the header items collectively exceed the viewport width. */
.cb-modal-save-cluster {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-shrink: 0;
  margin-left: auto;
}
/* Visual / JSON view-mode segment buttons in the header. Sit on top of
 * segButton(t, active) helper output — the helper owns theme-driven
 * background + border + color; these classes only carry the modal-header
 * sizing override (segGroup default padding/font-size is too generous for
 * the header). */
.cb-modal-segbtn {
  padding: 4px 10px;
  font-size: 11px;
}
/* "Save as…" name prompt strip — appears below the header when the user
 * clicks "Save as…". Sits above the error strip so both can show at once
 * (e.g. "name already exists" error with the prompt still open). */
.cb-modal-saveas-prompt {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--mt-accent) 25%, transparent);
  flex-shrink: 0;
}
.cb-modal-saveas-prompt__label {
  font-size: 11px;
  font-family: var(--m-font-mono);
  color: var(--mt-accent);
  white-space: nowrap;
  flex-shrink: 0;
}
/* The name input reuses .cb-modal-name-input shape; this modifier
 * removes flex:1 so it doesn't consume all remaining space in the row. */
.cb-modal-saveas-prompt__input {
  flex: 1;
  min-width: 180px;
  max-width: 340px;
}
/* Create copy and Dismiss buttons — sizing only; shape/color from
 * btnPrimary(t) / btnGhost(t) helpers passed as inline style. */
.cb-modal-saveas-prompt__create,
.cb-modal-saveas-prompt__dismiss {
  padding: 5px 12px;
  font-size: 11px;
  flex-shrink: 0;
}
.cb-modal-saveas-prompt__create.is-disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

/* Error strip under header. */
.cb-modal-error {
  padding: 7px 14px;
  background: color-mix(in srgb, var(--mt-error) 15%, transparent);
  color: var(--mt-error);
  border-bottom: 1px solid color-mix(in srgb, var(--mt-error) 33%, transparent);
  font-size: 11px;
  font-family: var(--m-font-mono);
  flex-shrink: 0;
}

/* === CardBuilderModal — Visual / JSON body ================================ */

/* 2-pane grid for visual mode: preview on the left, rail on the right.
 * Rail width scales with viewport between 380px and 480px. */
.cb-modal-body-visual {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr clamp(380px, 26vw, 480px);
}
/* The left preview column holds the widget-grid PreviewPane (which itself now
 * carries the card-level resizable preview, AdvancedPreviewCanvas). It fills the
 * grid cell and owns its own scroll. (999.58 plan 03: the old stacked wizard
 * mini-canvas that used to sit above it here is gone.) */
.cb-modal-preview-col {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.cb-modal-preview-col > :last-child {
  flex: 1 1 auto;
  min-height: 0;
}
/* Full-width JSON editor. */
.cb-modal-body-json {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 14px;
  gap: 10px;
  background: var(--mt-bg-deep);
}
/* "Local card JSON" disambiguation banner — warning-tinted info strip
 * at the top of JSON mode. */
.cb-modal-json-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 4px;
  font-family: var(--m-font-mono);
  font-size: 11px;
  background: color-mix(in srgb, var(--mt-warn) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-warn) 40%, transparent);
  color: var(--mt-fg);
  line-height: 1.4;
}
.cb-modal-json-banner-icon {
  font-size: 14px;
}
.cb-modal-json-banner-msg {
  flex: 1;
}
/* "↓ Export bundle" pill — sits in the banner. */
.cb-modal-json-export-btn {
  padding: 4px 12px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: var(--mt-accent);
  color: var(--mt-on-accent, #fff);
  border: none;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}
/* Toolbar row above the textarea — "CARD JSON" label + Reload + Apply + Cancel. */
.cb-modal-json-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
}
.cb-modal-json-toolbar-label {
  font-weight: var(--mt-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.cb-modal-json-toolbar-spacer {
  flex: 1;
}
.cb-modal-json-toolbar-btn {
  padding: 4px 10px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
}
.cb-modal-json-toolbar-btn.is-primary {
  padding: 4px 12px;
  background: var(--mt-accent);
  color: var(--mt-on-accent, #fff);
  border: none;
}
/* JSON parse-error strip — multi-line preformatted error. */
.cb-modal-json-error {
  padding: 8px 12px;
  background: color-mix(in srgb, var(--mt-error) 15%, transparent);
  color: var(--mt-error);
  border: 1px solid color-mix(in srgb, var(--mt-error) 33%, transparent);
  border-radius: 4px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  white-space: pre-wrap;
  word-break: break-word;
}
/* Main JSON textarea — fills remaining vertical space. */
.cb-modal-json-textarea {
  flex: 1;
  min-height: 0;
  resize: none;
  padding: 12px;
  font-family: var(--m-font-mono);
  font-size: 12px;
  line-height: 1.5;
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 6px;
  white-space: pre;
  overflow-wrap: normal;
  overflow: auto;
  tab-size: 2;
}
/* Footer hint under textarea. */
.cb-modal-json-hint {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  line-height: 1.5;
}

/* === TabsPicker =========================================================== */

/* Anchor for the popover; trigger is a single button inside it. */
.cb-tabs-picker {
  position: relative;
}
.cb-tabs-picker-trigger {
  padding: 5px 10px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
}
.cb-tabs-picker-trigger-label {
  color: var(--mt-muted);
  font-size: 10px;
}
.cb-tabs-picker-trigger-count {
  color: var(--mt-fg);
}
.cb-tabs-picker-trigger-chev {
  color: var(--mt-muted);
  font-size: 9px;
}
/* Floating popover with the checkbox list. */
.cb-tabs-picker-popover {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 50;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border-strong);
  border-radius: 6px;
  padding: 8px 4px;
  min-width: 180px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.cb-tabs-picker-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 10px;
  cursor: pointer;
  font-size: 11px;
  font-family: var(--m-font-mono);
  color: var(--mt-fg);
  background: transparent;
  border-radius: 3px;
}
.cb-tabs-picker-row.is-checked {
  background: color-mix(in srgb, var(--mt-accent) 9%, transparent);
}
.cb-tabs-picker-row-spacer {
  flex: 1;
}
.cb-tabs-picker-row-id {
  color: var(--mt-muted);
  font-size: 9px;
}
.cb-tabs-picker-hint {
  font-family: var(--m-font-mono);
  font-size: 9px;
  color: var(--mt-muted);
  margin-top: 6px;
  padding: 0 10px;
  line-height: 1.4;
}

/* === AlignSelector ======================================================== */

.cb-align-selector {
  display: flex;
  gap: 0;
  align-items: center;
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-dim);
}
.cb-align-selector-label {
  color: var(--mt-muted);
  font-size: 10px;
  margin-right: 4px;
}
/* Compact segmented group — wraps segGroup() but overrides radius/padding. */
.cb-align-selector-group {
  border-radius: 3px;
  overflow: hidden;
  padding: 0;
  gap: 0;
}
.cb-align-selector-btn {
  width: 28px;
  height: 22px;
  padding: 0;
  flex: none;
  font-size: 13px;
  font-weight: var(--mt-weight-bold);
  border-radius: 0;
}
/* First button has no left border; subsequent ones get a 1px divider. */
.cb-align-selector-btn.is-not-first {
  border-left: 1px solid var(--mt-border);
}

/* === PlacementSelector ==================================================== */

/* Wrapping row: "AT [row] , [col] [clear]". Accent-tinted when placed. */
.cb-placement-selector {
  display: flex;
  gap: 4px;
  align-items: center;
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-dim);
}
.cb-placement-selector.is-placed {
  color: var(--mt-accent);
}
.cb-placement-selector-label {
  color: var(--mt-muted);
  font-size: 10px;
}
.cb-placement-selector-sep {
  color: var(--mt-muted);
}
.cb-placement-input {
  width: 38px;
  padding: 3px 4px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  background: var(--mt-bg);
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  text-align: center;
}
.cb-placement-input.is-placed {
  border-color: var(--mt-accent);
}
.cb-placement-clear-btn {
  padding: 2px 6px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-muted);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  cursor: pointer;
}

/* === LinkBelowIndicator =================================================== */

.cb-linkbelow {
  display: flex;
  gap: 4px;
  align-items: center;
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-accent);
}
.cb-linkbelow-arrow {
  color: var(--mt-muted);
  font-size: 10px;
}
.cb-linkbelow-chip {
  padding: 2px 6px;
  border-radius: 3px;
  border: 1px solid var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 10%, transparent);
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-linkbelow-unlink-btn {
  padding: 2px 6px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  background: transparent;
  color: var(--mt-muted);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  cursor: pointer;
}

/* === SizeSelector ========================================================= */

.cb-size-selector {
  display: flex;
  gap: 4px;
  align-items: center;
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-dim);
}
.cb-size-selector-label {
  color: var(--mt-muted);
  font-size: 10px;
}
.cb-size-selector-x {
  color: var(--mt-muted);
}
/* ± stepper button — square 22x22, mono, sits beside the select. */
.cb-size-stepper {
  width: 22px;
  height: 22px;
  line-height: 20px;
  padding: 0;
  font-size: 12px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
  cursor: pointer;
}
.cb-size-stepper.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* === PixelSizeControl (999.58 — D-02 W × H px) ============================= */
/* The exact-pixel SIZE control for the canvas case. Reuses the .cb-size-selector
 * row chrome (mono, dim, 11px) so it reads as a sibling of the cell dropdowns
 * sitting next to it. Token-only, zero hex — restyles per theme for free. */
.cb-px-size {
  display: flex;
  gap: 4px;
  align-items: center;
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-dim);
}
.cb-px-size-label {
  color: var(--mt-muted);
  font-size: 10px;
}
.cb-px-size-x,
.cb-px-size-unit {
  color: var(--mt-muted);
}
/* Numeric W / H box — wide enough for a 4-digit canvas dimension (1600px max).
 * Borrows the stepper's border/radius so the pair looks of-a-piece. */
.cb-px-size-input {
  width: 52px;
  height: 22px;
  padding: 0 4px;
  font-size: 11px;
  font-family: var(--m-font-mono);
  text-align: right;
  background: transparent;
  color: var(--mt-fg);
  border: 1px solid var(--mt-border);
  border-radius: 3px;
}
.cb-px-size-input:focus {
  outline: none;
  border-color: var(--mt-accent);
}

/* === LeafOverridesBrowser ================================================= */

/* The browser sits inside a manifold-modal-* shell — these classes
 * only own the body content (intro hint, filter input, per-leaf row,
 * "customized" badge, edit-toggle button). */
.cb-leafov-intro {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-muted);
  margin-bottom: 10px;
  line-height: 1.5;
}
.cb-leafov-filter {
  width: 100%;
  margin-bottom: 10px;
}
.cb-leafov-empty {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-dim);
  padding: 16px;
  text-align: center;
}
.cb-leafov-row {
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  margin-bottom: 6px;
  padding: 8px 10px;
  background: transparent;
}
.cb-leafov-row.is-open {
  background: color-mix(in srgb, var(--mt-accent) 3%, transparent);
}
.cb-leafov-row-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.cb-leafov-row-text {
  flex: 1;
  min-width: 0;
}
.cb-leafov-row-id {
  font-family: var(--m-font-mono);
  font-size: 12px;
  color: var(--mt-fg);
  font-weight: var(--mt-weight-semibold);
}
.cb-leafov-row-meta {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-dim);
  margin-top: 2px;
}
/* "CUSTOMIZED" pill next to the leaf id when an override is active. */
.cb-leafov-badge {
  margin-left: 8px;
  font-size: 9px;
  font-weight: var(--mt-weight-bold);
  padding: 1px 6px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 33%, transparent);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
/* Edit / Close toggle. */
.cb-leafov-toggle-btn {
  padding: 4px 12px;
  font-size: 10px;
  font-family: var(--m-font-mono);
  font-weight: var(--mt-weight-bold);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  cursor: pointer;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.cb-leafov-toggle-btn.is-open {
  background: color-mix(in srgb, var(--mt-accent) 15%, transparent);
  color: var(--mt-accent);
  border-color: color-mix(in srgb, var(--mt-accent) 55%, transparent);
}

/* === ColumnsPanel (Phase 36 Plan 03) ====================================== *
 *
 * Inspector sub-panel for per-column presentation editing on list cards.
 * Lives inside a <Field label="Columns"> shell; these classes only own the
 * panel-body content (two groups + per-row controls + Advanced reveal +
 * Reset button).
 *
 * Token-only colors via var(--mt-*) per Phase 19 (no hex). Spacing follows
 * the locked Phase 15 scale: 4 / 8 / 14 / 20 px. UI-SPEC §Spacing Scale and
 * §Color hold the contract; this file is the visual landing point.
 *
 * Why no <hr> between Visible and Hidden groups: 20px (space.lg) of vertical
 * gap reads as a sub-grouping; an <hr> would compete with the existing
 * inspector section dividers and read as a new top-level section.
 *
 * Why the Reset button uses color-mix on accent: matches the existing
 * cb-leafov-toggle-btn.is-open / cb-leafov-badge patterns above — the single
 * accent affordance in the panel, per UI-SPEC §Color "60/30/10 split". */
.cb-cols-panel {
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* Visible / Hidden groups. The Hidden group sits below with 20px of top
 * margin (space.lg) — the visual separator distance, no horizontal rule. */
.cb-cols-group {
  display: flex;
  flex-direction: column;
  gap: 14px; /* space.md — between rows within a group */
}
.cb-cols-group--hidden {
  margin-top: 20px; /* space.lg — the two-group separator distance */
}

/* Group label. Same shape as the existing cb-* group/section headings.
 * text.sm / weight.semibold per UI-SPEC §Typography. */
.cb-cols-group-label {
  font-family: var(--m-font-mono);
  font-size: 12px;
  font-weight: var(--mt-weight-semibold);
  color: var(--mt-fg);
  letter-spacing: 0.4px;
  text-transform: uppercase;
  margin-bottom: 4px;
}
.cb-cols-group-label--hidden {
  color: var(--mt-dim);
}

/* Empty-state line ("(none visible)" / "(none hidden)") — same dim treatment
 * as the rest of the inspector's empty-state copy. */
.cb-cols-empty {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-dim);
  padding: 6px 8px;
}
.cb-cols-empty--hidden {
  color: var(--mt-dim);
  opacity: 0.7;
}

/* One row. Holds the primary controls (Show / Header / Format / annotation)
 * on a single line, then the Advanced trigger button, then the conditional
 * Advanced reveal panel below. */
.cb-cols-row {
  display: flex;
  flex-direction: column;
  gap: 4px; /* space.xs — between primary line and the Advanced trigger */
  padding: 6px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--mt-border) 50%, transparent);
}
.cb-cols-row:last-child {
  border-bottom: none;
}
.cb-cols-row--hidden {
  opacity: 0.78;
}
.cb-cols-row__main {
  display: flex;
  align-items: center;
  gap: 8px; /* space.sm — between Show / Header / Format / annotation */
  flex-wrap: wrap;
}

/* Each control cell. Header takes the most width (the operator types into
 * it); Format is a compact dropdown; Show is just a checkbox + word. */
.cb-cols-cell {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.cb-cols-cell-show {
  cursor: pointer;
  flex: 0 0 auto;
}
.cb-cols-cell-show__text {
  font-family: var(--m-font-mono);
  font-size: 11px;
  color: var(--mt-fg);
}
.cb-cols-cell-header {
  flex: 1 1 200px;
  min-width: 140px;
}
.cb-cols-cell-header__input {
  flex: 1 1 auto;
  min-width: 0;
}
.cb-cols-cell-format {
  flex: 0 1 180px;
  min-width: 120px;
}
.cb-cols-cell-format__select {
  width: 100%;
}
/* The field-key chip — shows the raw publisher field name next to the
 * editable Header input so the operator knows what they're aliasing.
 * Monospace, dim, small. */
.cb-cols-cell__key {
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-dim);
  white-space: nowrap;
  max-width: 100px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Inline annotation on hidden rows. text.xs per UI-SPEC §Typography. Two
 * variants (rowlink vs unused) per UI-SPEC §Color. */
.cb-cols-annotation {
  font-family: var(--m-font-mono);
  font-size: 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 0 1 auto;
}
.cb-cols-annotation--rowlink {
  color: var(--mt-warn);
}
.cb-cols-annotation--unused {
  color: var(--mt-dim);
}

/* The "▸ Advanced" / "▾ Advanced" disclosure trigger. Small ghost button —
 * matches the inspector's other inline reveal patterns. Chevron is
 * character-substitution in JSX (▸ vs ▾), no CSS transform. */
.cb-cols-adv-trigger {
  align-self: flex-start;
  margin-top: 2px;
  padding: 2px 8px;
  font-family: var(--m-font-mono);
  font-size: 10px;
  font-weight: var(--mt-weight-medium);
  background: transparent;
  color: var(--mt-dim);
  border: 1px solid color-mix(in srgb, var(--mt-border) 70%, transparent);
  border-radius: 3px;
  cursor: pointer;
  letter-spacing: 0.4px;
}
.cb-cols-adv-trigger:hover {
  color: var(--mt-fg);
  border-color: var(--mt-border);
}
.cb-cols-adv-trigger.is-open {
  color: var(--mt-fg);
  background: color-mix(in srgb, var(--mt-fg) 6%, transparent);
}

/* The expanded Advanced panel — sits under the row's primary line.
 * 8px (space.sm) left inset + subtle bgElev tint per UI-SPEC §Color. */
.cb-cols-adv-panel {
  margin-top: 4px;
  padding: 8px 10px;
  background: color-mix(in srgb, var(--mt-fg) 4%, transparent);
  border-radius: 4px;
  border-left: 2px solid color-mix(in srgb, var(--mt-border) 60%, transparent);
}
.cb-cols-adv-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
}
.cb-cols-adv-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 80px;
  flex: 0 1 auto;
}
.cb-cols-adv-cell--check {
  flex-direction: row;
  align-items: center;
  gap: 4px;
  min-width: 60px;
  cursor: pointer;
}
.cb-cols-adv-label {
  font-family: var(--m-font-mono);
  font-size: 10px;
  font-weight: var(--mt-weight-medium);
  color: var(--mt-dim);
  letter-spacing: 0.3px;
  text-transform: uppercase;
}
.cb-cols-adv-input {
  font-family: var(--m-font-mono);
  font-size: 11px;
  min-width: 70px;
}
/* D-10 hint — muted one-line note below the advanced checkbox row.
 * Mirrors the dim/mono scale of the other adv elements; stays visually
 * subordinate so it reads as a footnote, not a control label. */
.cb-cols-adv-hint {
  margin: 6px 0 0;
  font-family: var(--m-font-mono);
  font-size: 10px;
  color: var(--mt-dim);
  letter-spacing: 0.2px;
}

/* Reset-to-catalog-defaults button. Single accent affordance in the panel,
 * per UI-SPEC §Color "60/30/10 split" — every other element is fg/dim/warn.
 * The is-flash variant keeps the same color and just swaps the rendered
 * text to "Reset." in JSX. 800ms timer in the component. */
.cb-cols-reset-wrap {
  display: flex;
  justify-content: flex-end;
  margin-top: 16px;
}
.cb-cols-reset {
  padding: 6px 14px;
  font-family: var(--m-font-mono);
  font-size: 11px;
  font-weight: var(--mt-weight-semibold);
  background: transparent;
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 50%, transparent);
  border-radius: 4px;
  cursor: pointer;
  letter-spacing: 0.3px;
}
.cb-cols-reset:hover:not(:disabled) {
  background: color-mix(in srgb, var(--mt-accent) 12%, transparent);
  border-color: var(--mt-accent);
}
.cb-cols-reset:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.cb-cols-reset.is-flash {
  background: color-mix(in srgb, var(--mt-accent) 20%, transparent);
  border-color: var(--mt-accent);
}

/* === v1.7 "Restoring…" overlay (MIGRATE-06) =========================== *
 * Full-page cover shown after the gear → Design hub "Restore previous
 * layout" tile POSTs the rollback. The backend stops + restarts itself
 * to apply the rollback, so this overlay is plain DOM (built in
 * manifold.jsx with createElement, NOT a React component) — the React
 * tree behind it is about to be torn down by the full-page reload.
 *
 * z-index 2000 sits above the modal overlay (1000) and the edit FAB
 * (900) so nothing peeks through while the dashboard is mid-restart.
 * All aesthetics route through --m-*/--mt-* tokens (deploy-gate-safe:
 * no inline aesthetic styles in the JS that builds this). The accent
 * spinner reuses the canvas-chrome --mt-accent so the wait reads as
 * "Manifold is working," not a generic browser spinner.
 * --------------------------------------------------------------------- */

.manifold-restoring-overlay {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(13, 15, 18, 0.82);
  padding: var(--m-pad-xl, 32px);
}

.manifold-restoring-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  max-width: 420px;
  padding: 32px 36px;
  background: var(--m-surface-2);
  border: 1px solid var(--m-border-hi);
  border-radius: var(--m-radius-md, 8px);
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.7);
  text-align: center;
}

.manifold-restoring-spinner {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--mt-accent) 22%, transparent);
  border-top-color: var(--mt-accent);
  animation: manifold-restoring-spin 0.8s linear infinite;
}

@keyframes manifold-restoring-spin {
  to { transform: rotate(360deg); }
}

.manifold-restoring-title {
  color: var(--m-text);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-md-size, 13px);
  font-weight: var(--mt-weight-bold, 700);
}

.manifold-restoring-detail {
  color: var(--m-text-muted);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-sm-size, 12px);
  line-height: 1.5;
}

@media (prefers-reduced-motion: reduce) {
  .manifold-restoring-spinner { animation-duration: 2.4s; }
}

/* === v1.7 section→gap notice banner (MIGRATE-07) ====================== *
 * One-time inline strip across the top of the canvas viewport, shown on
 * first canvas-mode load to explain that section labels became whitespace
 * gaps. Dismiss flips _meta.canvas_section_notice_dismissed server-side
 * so it never re-fires — here or on Karl's other device. The banner is a
 * module-scope React component (CanvasSectionNoticeBanner in
 * card-builder.jsx); these classes carry every aesthetic so the JSX stays
 * inline-aesthetic-style-free (deploy-gate-safe).
 *
 * Tone: informational, not alarming. A left accent rule + tinted accent
 * wash on the canvas surface, mono type, matching the canvas chrome. Not
 * a red/error banner — nothing broke, the layout just changed shape.
 * --------------------------------------------------------------------- */

.cb-canvas-notice {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 16px;
  padding: 12px 16px;
  background: color-mix(in srgb, var(--mt-accent) 12%, var(--m-surface-2));
  border: 1px solid color-mix(in srgb, var(--mt-accent) 35%, var(--mt-border));
  border-left-width: 3px;
  border-radius: var(--m-radius-md, 8px);
}

.cb-canvas-notice__text {
  flex: 1 1 auto;
  color: var(--m-text);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-sm-size, 12px);
  line-height: 1.5;
}

.cb-canvas-notice__dismiss {
  flex: 0 0 auto;
  padding: 5px 14px;
  background: transparent;
  color: var(--mt-accent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 45%, transparent);
  border-radius: var(--m-radius-sm, 4px);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  font-weight: var(--mt-weight-bold, 700);
  cursor: pointer;
  transition: background 80ms, border-color 80ms;
}

.cb-canvas-notice__dismiss:hover {
  background: color-mix(in srgb, var(--mt-accent) 18%, transparent);
  border-color: var(--mt-accent);
}

.cb-canvas-notice__dismiss:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* === Canvas render (v1.7 Phase 42, view-only) ========================== *
 * The freeform layout. .cb-canvas-viewport scales the whole canvas as one
 * unit so it shrinks to fit narrow screens; .cb-canvas-surface is the
 * positioning context the cards sit on; .cb-canvas-empty is the friendly
 * hint shown when a tab has no cards.
 * --------------------------------------------------------------------- */

/* The fit-and-clip shell. A CSS transform shrinks the canvas visually but
 * leaves its real layout box at the full 1600px width and full height — that
 * leftover box is what would otherwise put a sideways scrollbar on a phone
 * and a tall run of empty space below the cards. width:100% + overflow:hidden
 * crop that dead box to the screen; the wrapper's height is pinned inline (in
 * the JS) to the shrunk height so the page scrolls to exactly the content. */
.cb-canvas-viewport {
  width: 100%;
  margin: 0 auto;
  overflow: hidden;
}

/* FIX B (v1.7 Phase 50-06) — in edit mode the inline-pinned height tracks the
 * lowest SAVED card, but a card dragged below that bottom moves via a DOM-only
 * transform with no per-frame state, so the saved-height clip would crop it
 * mid-drag until commit. The .is-editing class (added by CanvasViewport only
 * when showCanvasChrome is true) drops the clip so a low card stays visible
 * DURING the drag. View mode keeps the pinned height + overflow:hidden above
 * (cards there only render once their box is saved — nothing is ever clipped),
 * so the fit-and-clip behaviour on phones/narrow read-only views is unchanged. */
.cb-canvas-viewport.is-editing {
  overflow: visible;
}

/* The surface every card is absolutely positioned against — and the element
 * that actually scales. Built at a fixed 1600px width: on wide screens it
 * keeps that size and centers (margin auto) with empty side gaps; on narrow
 * screens it shrinks as one unit. transform-origin top-left keeps the shrunk
 * content pinned to the visible top-left corner so it never slides off the
 * side of a phone screen. The scale factor arrives as --cb-canvas-scale from
 * the wrapper (a state-driven layout number); the cap-at-1.0 logic lives in
 * the JS. Height is set inline from the lowest card's bottom edge. */
.cb-canvas-surface {
  position: relative;
  width: 1600px;
  margin: 0 auto;
  transform: scale(var(--cb-canvas-scale, 1));
  transform-origin: top left;
  transition: transform 120ms ease;
}

/* Honour a reader's "reduce motion" setting — no animated resizing. */
@media (prefers-reduced-motion: reduce) {
  .cb-canvas-surface {
    transition: none;
  }
}

/* === Portrait single-column reflow (v1.7 Phase 50, MOBILE-03 / D-13) === *
 * On a portrait phone a canvas tab renders CanvasColumn instead of the
 * scaled CanvasViewport: one full-width stack of cards in reading order.
 * Layout/flow only here — the per-item min-height (the card's designed h)
 * is a state-driven number set inline in the JSX, so the deploy aesthetic
 * gate stays green. View-only: no drag/resize/select chrome lives in this
 * branch. No new <link> tag, so no dist-template parity step needed. */
.cb-canvas-column {
  display: flex;
  flex-direction: column;
  width: 100%;
  gap: var(--mt-space-md, 14px);
  box-sizing: border-box;
}
.cb-canvas-column-item {
  width: 100%;
  /* Portrait height is set inline per card by _portraitCardSizing (999.37):
   * content cards stay height:auto (size to content); visual cards get an
   * inline height; video gets an inline aspect-ratio. */
  height: auto;
  box-sizing: border-box;
}

/* Empty-tab hint — same muted, low-key, mono look as the section-flow
 * empty placeholder, centered within the canvas. */
.cb-canvas-empty {
  max-width: 320px;
  margin: 64px auto;
  padding: 24px 18px;
  border: 1px dashed var(--mt-border-strong, var(--mt-border));
  border-radius: 10px;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 12px);
  text-align: center;
}

/* === Drag chrome (v1.7 Phase 43) ====================================== *
 * The three classes the drag mechanic (Plan 02) and the snap/grid plan
 * (Plan 03) wire onto. Defined here, in the already-loaded sheet, so no new
 * <link> tag and no dist-template parity step is needed.
 *
 * The whole card is the grab handle in edit mode (CONTEXT D-01) — there is
 * NO top-edge handle bar. The always-on affordance is just the move cursor.
 * --------------------------------------------------------------------- */

/* Move cursor over a card when edit chrome is on — the always-on "you can
 * grab this" affordance. Applied to the per-card box in edit mode only
 * (gated on showCanvasChrome by the JSX), so view-mode widgets keep their
 * normal cursor. */
.cb-card-grab {
  cursor: move;
}

/* Transparent full-card overlay that sits above the widget in edit mode so
 * a press lands on "drag the card," not "interact with the widget" (e.g. a
 * chart's click-to-isolate). It catches the pointer but paints nothing — no
 * colour token needed because it is fully transparent by design. In view
 * mode the JSX omits it entirely, so widgets stay interactive. */
.cb-card-clickguard {
  position: absolute;
  inset: 0;
  background: transparent;
  cursor: move;
  /* Above the card's own content but below any future selection chrome. */
  z-index: 1;
}

/* Hyperlink (and any widget) edit-mode inertness — v1.7 Phase 50, the complete
 * fix for "a hyperlink card launches its link mid-edit" (todo raised at the
 * Phase 44 human-verify).
 *
 * WHY a pointer-events guard and NOT a z-index bump: the Phase 43 clickguard
 * above (z-index:1, a later SIBLING of <CardRenderer>) is meant to swallow
 * widget clicks, and it does for most widgets. But the hyperlink renderer paints
 * an invisible full-card overlay anchor (position:absolute; inset:0 —
 * card-builder-renderers.jsx:2399) plus a visible inline anchor (:2427). Through
 * the anchor's own stacking context / the browser's anchor-activation path, the
 * navigation still fires past the guard. Raising the guard's z is a band-aid the
 * next overlay-renderer defeats again (Karl's no-band-aid rule). The robust fix:
 * in edit mode the box / guard / handles own ALL interaction, so the rendered
 * widget CONTENT is made inert.
 *
 * SCOPE — deliberately narrow so the three live things stay live:
 *   • the box itself (.cb-card-grab) keeps pointer-events (stays draggable) —
 *     no rule on it here;
 *   • the chrome siblings (.cb-card-clickguard, .cb-resize-handle*,
 *     .cb-card-kebab, .cb-card-lock-glyph) are SIBLINGS of <CardRenderer>, not
 *     descendants, so a rule scoped to the renderer subtree never touches them.
 * <CardRenderer> is the FIRST child of the box (card-builder.jsx ~:5735, before
 * any chrome), so `.cb-card-grab > :first-child` targets exactly the rendered
 * widget. We kill pointer events on it and — belt-and-suspenders — on its
 * a/button descendants. The clickguard still catches the press and turns it into
 * a drag; the link simply never activates.
 *
 * CSS-file rule (NOT an inline style) — the deploy aesthetic gate aborts on
 * inline aesthetic keys; pointer-events on the content subtree is the correct
 * seam (50-RESEARCH § 5, Pitfall 6). */
.cb-card-grab > :first-child {
  pointer-events: none;
}
.cb-card-grab > :first-child a,
.cb-card-grab > :first-child button {
  pointer-events: none;
}

/* Optional grid-line overlay on the canvas surface (default OFF, DRAG-05).
 * A pure-CSS background gradient — NO per-cell DOM nodes. The cell size
 * arrives as --cb-grid-size (Plan 03 drives 4/8/16) and the line colour is a
 * low-contrast theme token so it reads on any theme without an aesthetic
 * literal. Two repeating gradients draw the vertical + horizontal lines. */
.cb-canvas-grid {
  background-image:
    repeating-linear-gradient(
      to right,
      var(--mt-border-lo, var(--mt-border)) 0,
      var(--mt-border-lo, var(--mt-border)) 1px,
      transparent 1px,
      transparent var(--cb-grid-size, 4px)
    ),
    repeating-linear-gradient(
      to bottom,
      var(--mt-border-lo, var(--mt-border)) 0,
      var(--mt-border-lo, var(--mt-border)) 1px,
      transparent 1px,
      transparent var(--cb-grid-size, 4px)
    );
}

/* Snap indicator (v1.7 Phase 43, DRAG-04). Shown only WHILE a card is being
 * dragged: a faint full-surface grid hint sized to the active snap grid so
 * the snap target is visible as the card moves. It reuses the .cb-canvas-grid
 * gradient (pure CSS, no per-cell DOM) and sits absolutely behind the cards.
 * pointer-events:none so it never intercepts the drag. Its own opacity gives
 * the "indicator on" look even when the persistent grid overlay is off. */
.cb-snap-indicator {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0.5;
  pointer-events: none;
  transition: opacity 120ms ease;
}

/* Alt held during a drag turns snap OFF; the indicator visibly dims so the
 * user can see snapping is disabled (D-04). Lower opacity than the active
 * state above — the computed value is strictly smaller, which the browser
 * assertion checks. */
.cb-snap-indicator-dimmed {
  opacity: 0.15;
}

@media (prefers-reduced-motion: reduce) {
  .cb-snap-indicator { transition: none; }
}

/* ── Resize handles (v1.7 Phase 44, RESIZE-01 / RESIZE-02) ──────────────────
 *
 * Eight small grab targets pinned to a card box in edit mode — four corners
 * (nw/ne/sw/se, resize both dimensions) and four edge midpoints (n/s/e/w,
 * resize one dimension). Plan 02 wires the pointer math; this sheet only
 * paints the affordance and sets the directional cursor.
 *
 * D-01 — handles appear on HOVER OR FOCUS of the card box, not on a selection
 * state (selection is Phase 45). The card box already carries .cb-card-grab +
 * tabIndex from Phase 43, so :hover / :focus-within on the box is the natural
 * reveal trigger — no JS selection class is referenced here.
 *
 * Z-order — the base handle sits at z-index:2, strictly ABOVE the Phase 43
 * .cb-card-clickguard (z-index:1), so a press on a handle is hit-tested as a
 * resize, not a drag.
 *
 * Colour contract (v1.4) — fill via var(--mt-accent), edge via
 * var(--mt-border); NO hex / rgb literals. */
.cb-resize-handle {
  position: absolute;
  width: 10px;
  height: 10px;
  box-sizing: border-box;
  background: var(--mt-accent);
  border: 1px solid var(--mt-border);
  border-radius: 2px;
  /* Above the click-guard (z-index:1) so a handle press wins over the drag. */
  z-index: 2;
  /* Hidden until the card box is hovered or focused (revealed below, D-01). */
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
}

/* D-01 reveal: any handle inside a hovered or keyboard-focused card box shows
 * and becomes grabbable. The card box is .cb-card-grab (edit-mode only), so
 * view-mode cards never reveal handles. */
.cb-card-grab:hover .cb-resize-handle,
.cb-card-grab:focus-within .cb-resize-handle {
  opacity: 1;
  pointer-events: auto;
}

/* Four corners — pinned to the box corner, centred on it (half the 10px size),
 * each with the diagonal resize cursor it drives. */
.cb-resize-handle--nw { top: -5px; left: -5px; cursor: nwse-resize; }
.cb-resize-handle--se { bottom: -5px; right: -5px; cursor: nwse-resize; }
.cb-resize-handle--ne { top: -5px; right: -5px; cursor: nesw-resize; }
.cb-resize-handle--sw { bottom: -5px; left: -5px; cursor: nesw-resize; }

/* Four edge midpoints — centred on each edge, each with its single-axis
 * resize cursor. */
.cb-resize-handle--n { top: -5px; left: 50%; transform: translateX(-50%); cursor: ns-resize; }
.cb-resize-handle--s { bottom: -5px; left: 50%; transform: translateX(-50%); cursor: ns-resize; }
.cb-resize-handle--e { right: -5px; top: 50%; transform: translateY(-50%); cursor: ew-resize; }
.cb-resize-handle--w { left: -5px; top: 50%; transform: translateY(-50%); cursor: ew-resize; }

/* "Can't shrink further" limit cursor (D-04). Plan 02's handler toggles this
 * class onto the resize surface when a dimension hits the renderer's pixel
 * floor. Hitting a floor is NORMAL, not an error — so this is ONLY a cursor
 * change: no red / alarm colour, no outline or border change (the section
 * editor's red valid=false treatment is deliberately NOT reused). */
.cb-resize-limit {
  cursor: not-allowed;
}
/* The limit cursor must WIN during the active drag. The box's own children
 * carry explicit cursors (each handle's directional resize cursor, the
 * click-guard's `move`) and the pointer sits over one of them while you push
 * inward past the floor — an explicit child cursor beats the box's inherited
 * one, so without these overrides the user keeps seeing the resize/move cursor
 * exactly when the floor is hit (the bug WR-01 caught: the not-allowed cursor
 * never actually showed). Force not-allowed on the cursor-bearing children of a
 * limited box too. Still cursor-only — no red, no outline (D-04). */
.cb-resize-limit .cb-resize-handle,
.cb-resize-limit .cb-card-clickguard {
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .cb-resize-handle { transition: none; }
}

/* ── AdvancedPreviewCanvas (Phase 999.58 ADV-RESIZE-01 / ADV-RESIZE-02) ──────
 *
 * The advanced editor's big center preview, turned into a one-card slice of the
 * freeform canvas. This is the relocation of the rejected phase-67 wizard
 * mini-canvas (its CSS was removed in 999.58 plan 03) onto the surface where
 * cards are actually built. It reuses the shared canvas chrome deliberately: the
 * same .cb-card-grab + .cb-resize-handle* affordances, a dot-grid stage, the
 * same token-only palette — so it reads as the REAL canvas in miniature, no new
 * visual language. The card renders at 1:1 since the advanced preview has room.
 *
 * HANDLE-CLIP TRAP (commit bb04717, the lesson this whole rule exists to honor):
 * the resize handles are children positioned 5px OUTSIDE the box edge
 * (top:-5px etc., ~6355). Two things would clip them and make the card
 * "un-resizable":
 *   1. an overflow:hidden/auto on the card box itself (so the content clip is
 *      moved onto an inner .cb-adv-card-body, NOT the box);
 *   2. the .cb-adv-canvas stage clipping a handle when the card sits near the
 *      stage origin (so the handles are pulled ONTO the card edge below, like
 *      the mini-canvas does).
 * The host .cb-preview-pane itself is overflow:auto (~1370); the bounded
 * .cb-adv-canvas stage sits inside it as its own clip context so the pane's
 * scroll never reaches in and cuts a handle.
 *
 * Colour is token-only (no hex/rgb literals): inset surface from --mt-bg, grid
 * dots + border from --mt-border, all theme-driven so it restyles per theme. */
.cb-adv-canvas-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--mt-space-xs, 6px);
  margin: 0 0 var(--mt-space-sm, 8px);
  flex: 0 0 auto;
}

.cb-adv-canvas-label {
  font-size: 11px;
  line-height: 1.3;
  letter-spacing: 0.02em;
  color: var(--mt-fg);
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
}

.cb-adv-canvas {
  position: relative;
  width: 100%;
  /* Taller than the mini-canvas — the advanced preview has the room and the card
   * renders 1:1, so give the gesture a generous stage. Aspect mirrors the wide
   * live canvas (1600 design px) so proportions match what lands on the tab. */
  aspect-ratio: 16 / 7;
  min-height: 200px;
  max-height: 360px;
  box-sizing: border-box;
  border: 1px solid var(--mt-border);
  border-radius: 8px;
  /* The stage owns the clip context so the host .cb-preview-pane's overflow:auto
   * scroll can't reach in and cut a -5px handle. Handles are pulled onto the card
   * edge below so this clip never hides them either. */
  overflow: hidden;
  /* Inset stage one shade darker than the modal body so the card reads as raised
   * off it, plus a faint 16px dot-grid echoing the GRID=8 snap in the JSX. */
  background-color: var(--mt-bg);
  background-image: radial-gradient(var(--mt-border) 1px, transparent 1px);
  background-size: 16px 16px;
  background-position: -1px -1px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.18);
}

/* The draft card box on the stage. Already carries .cb-card-grab (move cursor +
 * hover-reveals the resize handles); this only adds the "raised card" skin so it
 * stands off the dotted stage. NO overflow:hidden here — the -5px handles are
 * children outside the box edge, so clipping the box would make every handle
 * invisible AND unclickable (the bb04717 trap). The content clip lives on
 * .cb-adv-card-body instead. */
.cb-adv-card {
  border-radius: 6px;
  background: var(--mt-card-bg, var(--mt-bg));
  border: 1px solid var(--mt-border);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  /* Eases the React-reconciled rest position after a gesture; the per-frame drag
   * clears transform directly so this only smooths the settle. */
  transition: box-shadow 120ms ease;
}
.cb-adv-card:hover {
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.32);
}

/* The rendered card content fills the box (the box IS the pixel size you chose).
 * overflow:hidden + the matching radius clip the CardRenderer to the rounded box
 * — kept HERE off .cb-adv-card so the resize handles outside the box survive.
 * pointer-events:none so the click-guard sibling owns the move press and the
 * card's own widgets can't swallow the drag.
 *
 * HEIGHT FIX (POLISH-04 / Phase 96 plan 03): top/left/width/height instead of
 * inset:0. CardRenderer uses fillHeight=true which sets height:100% on its
 * .cb-card-frame grid container. Per CSS2.1 §10.5, height:100% on a
 * non-positioned element resolves as height:auto when the parent's height is
 * derived from inset constraints (top/bottom) rather than an explicit height
 * property. With height:auto the grid's 1fr row tracks collapse to 0px and
 * all card content disappears. Explicit width:100% + height:100% gives
 * .cb-adv-card-body a definite height so the child percentage chain resolves
 * correctly. Geometry is identical to inset:0 because .cb-adv-card itself
 * carries no padding, so top:0 + height:100% = top:0 + bottom:0. */
.cb-adv-card-body {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 6px;
  pointer-events: none;
}

/* Pull the handles ONTO the card's own edge (zero the -5px off-box offset) so all
 * eight stay inside the bounded stage and grabbable regardless of card position —
 * the same fix the mini-canvas applies (~6485). Edge-midpoint transforms are
 * preserved; only the off-box offset is zeroed. */
.cb-adv-card .cb-resize-handle--nw { top: 0; left: 0; }
.cb-adv-card .cb-resize-handle--ne { top: 0; right: 0; }
.cb-adv-card .cb-resize-handle--sw { bottom: 0; left: 0; }
.cb-adv-card .cb-resize-handle--se { bottom: 0; right: 0; }
.cb-adv-card .cb-resize-handle--n { top: 0; }
.cb-adv-card .cb-resize-handle--s { bottom: 0; }
.cb-adv-card .cb-resize-handle--e { right: 0; }
.cb-adv-card .cb-resize-handle--w { left: 0; }

@media (prefers-reduced-motion: reduce) {
  .cb-adv-card { transition: none; }
}

/* === Phase 45 selection chrome START === */
/* ── Selection + marquee + always-on-top overlay (v1.7 Phase 45) ────────────
 *
 * The visual chrome for multi-select on the freeform canvas (SELECT-01/03,
 * D-01/D-02/D-04/D-10). Plan 02 (shift-click + selection state) and Plan 03
 * (marquee gesture) toggle these classes; this sheet only paints them.
 *
 * WHY a separate overlay layer (D-10): a selected card can be stacked UNDER
 * another card. If we drew the accent border on the card box itself, the
 * card on top would clip the highlight and the user couldn't tell what's
 * selected. So all selection chrome paints on ONE always-on-top layer
 * (.cb-canvas-overlay) that sits above every card but changes NO real card
 * z-order (real stacking is Phase 47). The overlay lives INSIDE the scaled
 * .cb-canvas-surface, so it shrinks with the canvas and its coordinates line
 * up with the cards with zero per-element scale math.
 *
 * Colour contract (v1.4, enforced by ESLint + deploy.sh): every value below
 * is var(--mt-accent) or a color-mix() of it — NO hex / rgb / hsl literals.
 * All four themes define --mt-accent, so selection re-skins on theme switch
 * for free (SELECT-03 / D-01 "survives theme switch").
 * ------------------------------------------------------------------------- */

/* (a) The always-on-top overlay layer (D-10). Fills the whole surface,
 * paints nothing of its own, and never intercepts a press — it is purely
 * the canvas that the outlines, the marquee rectangle, AND the alignment
 * toolbar are drawn on.
 *
 * WHY z-index:40 (gap-closure UAT #1, corrected Phase 47): the selection
 * rings, marquee, and alignment toolbar all paint on this one overlay, and
 * they MUST show over any card no matter how the user stacked it. A card's
 * "bring to front" raises that card's own STORED z (the sparse 10/20/110…
 * value persisted in layout[tabId].z), which has no real ceiling — the stored
 * value only clamps to 0..9999. There is NO 30-value card-render cap constant
 * anywhere (the old comment here claimed one; it never existed). Phase 47
 * closes the seam at the
 * RENDER step instead: DraggableCard renders its box at `Math.min(z, 39)`
 * (card-builder.jsx ~4914) so a fronted card's RENDERED zIndex never exceeds
 * 39 while its stored order survives reload. The overlay and the card boxes
 * are siblings inside .cb-canvas-surface, which is position:relative with NO
 * z-index, so it does NOT open its own stacking context — the children compete
 * on z directly. With the card render capped at 39, this overlay at 40 always
 * wins. FAILURE MODE if you drop this under 40 (or remove the Math.min(z,39)
 * render cap): the toolbar (and selection ring) vanish behind a fronted card
 * and the user can't reach them.
 *
 * Still safely UNDER the modal + the restoring overlay: the app modal is
 * z-index:1000 (position:fixed, root context) and the v1.7 restoring overlay
 * is z-index:2000 — both far above 40, so this canvas chrome never covers a
 * modal. (Selection chrome only renders in canvas edit mode anyway, which is
 * never meaningfully on-screen at the same time as an open modal.)
 *
 * (The +5px outline still shows over a higher-z card without touching that
 * card's real stacking order — real card stacking shipped in Phase 47.) */
.cb-canvas-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none; /* visual layer only (D-10); never intercepts a press */
  z-index: 40;          /* card boxes render at Math.min(z,39); 40 always wins. Under modal(1000)+restoring(2000) */
}

/* (b) One rectangle per selected card, positioned on the overlay in canvas
 * coords (Plan 02 sets left/top/width/height + border-radius inline from
 * placed[], so the ring hugs each card's own corner radius instead of reading
 * as a loose square box). Drawn OUTSIDE the card box (a small negative inset in
 * the JS) so it never covers content or shifts layout.
 *
 * UNMISTAKABLE-LOOK FIX (Karl 2026-05-29): the old 2px border sat right on the
 * card's own border and merged into the bounding box — too subtle. The selection
 * is now a banded HALO built entirely from box-shadow rings, lifted off the card
 * edge by a 2px gap in the canvas background so the accent never blends into the
 * card's border:
 *   ring 1 — 2px var(--mt-bg) gap        → separates the accent from the card edge
 *   ring 2 — 3px solid var(--mt-accent)  → the bold "this is selected" band
 *   ring 3 — soft 24px outer halo        → stays visible when a higher-z card
 *                                           overlaps the selected one (D-10)
 * Bold band + strong glow reads instantly and can NOT be confused with the thin
 * hover resize-dots or the keyboard focus ring. No fill (D-01: outline + glow,
 * not fill — a fill would fight live chart/gauge content; fill is the marquee's
 * job). Every value is var(--mt-accent)/var(--mt-bg) or a color-mix of accent,
 * so the whole ring re-skins on a theme switch (SELECT-03 / D-01). */
.cb-card-selected {
  position: absolute;
  /* Hug the card's own corner radius (set inline as --cb-sel-radius from
   * cardCornerRadius(card.style) + outset — #137; it used to come from the
   * pre-v1.14 card.style.borderRadius, which stopped being the card's real
   * corner at Phase 105); fall back to a gentle 8px if unset. */
  border-radius: var(--cb-sel-radius, 8px);
  box-shadow:
    0 0 0 2px var(--mt-bg),
    0 0 0 5px var(--mt-accent),
    0 0 24px 6px color-mix(in srgb, var(--mt-accent) 55%, transparent);
  pointer-events: none;
  transition: box-shadow 120ms ease-out;
}

/* Primary-card pip (CANVAS-03 / D-05). A 6×6px accent dot at the top-right
 * corner of the selection ring marks which card is the match-size reference
 * (the last-clicked card, or the largest-area fallback for marquee selects).
 * Applied alongside .cb-card-selected on the reference card only, and only
 * while 2+ cards are selected (the toolbar pill is visible).
 * pointer-events: none so it never intercepts drags or clicks
 * (project_manifold_canvas_overlap_interaction). */
.cb-card-selected--primary::after {
  content: '';
  position: absolute;
  top: -3px;
  right: -3px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mt-accent);
  pointer-events: none;
}

/* (c) The rubber-band rectangle (D-02 / SELECT-01). One reused node, moved by
 * setting left/top/width/height inline during the sweep — no DOM allocation
 * per frame. 1px accent border (thinner than the 2px selection outline so the
 * two never read alike) + a 12% translucent accent fill (Finder/Figma style:
 * reads as "swept" without obscuring the cards underneath). */
.cb-marquee {
  position: absolute;
  border: 1px solid var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 12%, transparent);
  pointer-events: none;
}

/* (d) The 1-vs-2+ resize-dot gate (D-04 / Pitfall 5). The Phase 44 hover /
 * :focus-within reveal at :5744 stays as-is for the 0-or-1-selected case.
 * This adds two things on TOP of that:
 *
 *  - .cb-card-selected--solo : Plan 02 adds this to the single selected card
 *    when EXACTLY ONE is selected, forcing its resize dots visible even
 *    without a hover (selecting one card = ready to resize it). Mirrors the
 *    Phase 44 reveal rule; geometry untouched (the -5px handle insets stay
 *    Phase 44 values — D-04 only changes WHEN the dots show, never their size
 *    or position). */
.cb-card-selected--solo .cb-resize-handle {
  opacity: 1;
  pointer-events: auto;
}

/*  - .cb-group-mode : Plan 02 adds this to the surface when 2+ cards are
 *    selected. Group mode is move/align, not resize, so the dots must HIDE on
 *    every card — including a card that is also hovered or solo-flagged. These
 *    rules win because they are more specific (.cb-group-mode ancestor) than
 *    the bare hover/focus/solo reveals above, so a stray hover during a group
 *    drag can't flash a card's dots back on. */
.cb-group-mode .cb-card-grab:hover .cb-resize-handle,
.cb-group-mode .cb-card-grab:focus-within .cb-resize-handle,
.cb-group-mode .cb-card-selected--solo .cb-resize-handle {
  opacity: 0;
  pointer-events: none;
}

/* (e) The floating alignment toolbar (v1.7 Phase 46, ALIGN-01/02/03, D-02/D-03).
 *
 * A small elevated-glass pill of 8 icon-only buttons that floats above a
 * multi-card selection. It deliberately reads as a first-class member of the
 * existing edit chrome — same --mt-bg-elev material, border, and accent
 * language as the gear / tab-picker popovers and the selection ring above — so
 * it belongs to the same family rather than looking like a bolted-on widget.
 *
 * Colour contract (same as the rest of this block): every value is a theme
 * token or a color-mix() of one — NO hex / rgb / hsl literals (ESLint
 * manifold-local/no-inline-aesthetic-styles + deploy.sh enforced). All four
 * themes define these tokens, so the whole toolbar re-skins on a theme switch.
 *
 * The component sets ONLY layout/position numbers inline (left/top/zIndex/
 * display/opacity); every aesthetic value lives here on the classes.
 *
 * QUIET-UNTIL-REACHED: accent (--mt-accent) lights up ONLY the control under
 * the pointer (hover icon + wash, active wash, focus ring). The pill body,
 * dividers, resting icons, and tooltip never use accent — mirroring how the
 * selection ring uses accent only on what's selected. */

/* The pill itself. The overlay layer it sits on is pointer-events:none, so the
 * pill re-enables pointer events for its buttons. fade is opacity-only (D-05);
 * the instant hide during a gesture is the inline display:none (D-02). */
.cb-align-toolbar {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  pointer-events: auto;             /* the overlay is none; the pill is clickable */
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--cb-tb-radius, 8px);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--mt-bg-deep) 50%, transparent);
  transition: opacity 120ms ease-out;
}

/* The thin band between clusters (4 aligns | 2 centers | 2 distributes). */
.cb-tb-divider {
  width: 1px;
  height: 16px;
  margin: 0 8px;
  background: var(--mt-border);
}

/* One icon button. 28px dense-chrome hit target (mouse-only — the toolbar is
 * hidden below 768px, so the 44px touch minimum doesn't apply). Resting icons
 * are --mt-fg at 70% so they read as "available" not "shouting." position:
 * relative anchors the custom tooltip. */
.cb-tb-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: var(--m-radius-sm, 4px);
  background: transparent;
  color: color-mix(in srgb, var(--mt-fg) 70%, transparent);  /* icon = currentColor */
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}
/* The glyph fills/strokes from the button's color so one rule skins all 8. */
.cb-tb-btn svg { fill: currentColor; }
.cb-tb-btn svg line { stroke: currentColor; }

/* Hover: full accent on the icon under the pointer + the same subtle wash the
 * existing .is-active buttons use. */
.cb-tb-btn:hover {
  color: var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
}
/* Pressed: wash deepens for the moment the mouse is down. */
.cb-tb-btn:active {
  background: color-mix(in srgb, var(--mt-accent) 22%, transparent);
}
/* Keyboard focus: the same accent-glow language as the selection ring, so focus
 * reads as "this is the live tool" consistently across the canvas. */
.cb-tb-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--mt-accent) 60%, transparent);
}

/* Disabled (the two distribute buttons below 3 selected, D-10). Clearly dimmer
 * than the 70% resting icons, no hover wash, not-allowed cursor. Pointer events
 * stay on (the component drops the click handler, not the events) so the
 * teaching tooltip can still explain WHY it's off. */
.cb-tb-btn--disabled {
  color: color-mix(in srgb, var(--mt-fg) 28%, transparent);
  cursor: not-allowed;
}
.cb-tb-btn--disabled:hover {
  color: color-mix(in srgb, var(--mt-fg) 28%, transparent);
  background: transparent;
}

/* The custom tooltip (not the browser title= attribute — that's slow,
 * unstyleable, and can't be theme-tokenized). Sits below the button, centered,
 * and never intercepts the pointer. */
.cb-tb-tooltip {
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 4px 8px;
  font-size: 12px;
  font-weight: var(--mt-weight-medium);
  color: var(--mt-fg);
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 4px);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--mt-bg-deep) 50%, transparent);
  pointer-events: none;
}

/* The align/distribute MOVE tween (D-04). A transient class added to each moved
 * card box at commit and removed ~160ms later. It transitions left/top ONLY —
 * NEVER transform: the live drag path writes transform per frame, so a transform
 * transition here would lag every drag (Pitfall 2). Scoping the tween to this
 * transient, commit-only class is the belt-and-suspenders guarantee that the
 * 150ms ease can never bleed into a drag. */
.cb-aligning {
  transition: left 150ms cubic-bezier(0.2, 0, 0, 1),
              top  150ms cubic-bezier(0.2, 0, 0, 1);
}

/* Honour "reduce motion" for the new chrome, mirroring :5703 / :5785. The
 * selection outline and marquee carry no transition by default; the Phase 46
 * toolbar fade, button accent, and align tween must all go instant for a
 * reduced-motion user.
 *
 * Phase 224 adds .cb-card-own-mark__word here rather than to a block of its
 * own: the own-look mark's word is revealed by the kebab's reveal, so it is
 * switched off in the one place the kebab's is. One reveal, one off switch. */
@media (prefers-reduced-motion: reduce) {
  .cb-card-selected,
  .cb-marquee,
  .cb-align-toolbar,
  .cb-tb-btn,
  .cb-aligning,
  .cb-card-menu,
  .cb-card-menu-item,
  .cb-card-kebab,
  .cb-card-own-mark__word {
    transition: none;
  }
}
/* === Phase 45 selection chrome END === */

/* === Phase 47 card chrome: context menu, kebab, lock glyph, overlap ===
 *
 * Z-02 / LOCK-01 / LOCK-03 / Z-03. This is NOT a new surface style — it is the
 * established Phase 46 edit-chrome vocabulary (the .cb-align-toolbar glass
 * material + .cb-tb-btn--disabled dimming + .cb-tb-tooltip) applied to a new
 * per-card menu. Every value is a theme token or a color-mix() off one; there
 * are NO hex/rgb/hsl literals (ESLint manifold-local/no-inline-aesthetic-styles
 * + deploy.sh enforce this). Inline JSX styles for this chrome are limited to
 * layout/position numbers (left/top/zIndex/minWidth/opacity) — Plans 02/03 wire
 * the interactivity against these classes.
 *
 * The four tokens these reuse, same as the alignment toolbar:
 *   --mt-bg-elev      the elevated-glass surface
 *   --mt-border-strong  the popover border
 *   --mt-border       structural lines (the divider)
 *   --mt-fg / --mt-accent  resting text / pointer accent
 */

/* (1) The popover body. Same glass material as .cb-align-toolbar (:5942):
 * --mt-bg-elev surface, --mt-border-strong edge, --cb-tb-radius corner,
 * --mt-bg-deep shadow. z-index:50 puts it ABOVE the canvas-overlay (40) so the
 * menu always paints over the selection chrome — and still safely under the
 * modal (1000). JS sets left/top from the click point (inline, allowed). */
.cb-card-menu {
  /* fixed (not absolute): the menu is portaled to <body> and anchored at the
   * viewport clientX/clientY of the right-click or kebab press, so it must NOT
   * inherit the scaled .cb-canvas-surface transform (which would shift + scale
   * it). position:fixed reads left/top as viewport coords — exactly the
   * gear/tab-picker popover pattern (helpers.css:4618). */
  position: fixed;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--cb-tb-radius, 8px);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--mt-bg-deep) 50%, transparent);
  padding: 4px;
  min-width: 180px;
  z-index: 50;
  transition: opacity 120ms ease-out;
  /* §999.61 belt-and-suspenders: the menu can grow tall once the "Cards on
   * this tab" list is expanded. The JS clamp in CardContextMenu keeps the box
   * on-screen, but if the list ever exceeds the viewport height the menu must
   * still be fully reachable — cap the height and let the column scroll
   * internally rather than spilling off the bottom edge. */
  max-height: calc(100vh - 16px);
  overflow-y: auto;
}

/* (2) A menu row at rest. 28px dense-chrome target (same as .cb-tb-btn);
 * 13px/400 label a hair under body so a 5-row column reads as compact edit
 * chrome. Resting label at --mt-fg 85% — slightly stronger than the toolbar's
 * 70% resting icons because text needs more contrast than a glyph to read. */
.cb-card-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 28px;
  padding: 6px 8px;
  border-radius: var(--m-radius-sm, 2px);
  font-size: 13px;
  font-weight: var(--mt-weight-regular);
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  background: transparent;
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}
/* One rule skins every inline-SVG glyph in a row (currentColor drives the
 * resting / hover / disabled color rules with zero per-icon color code). */
.cb-card-menu-item svg {
  fill: currentColor;
  stroke: currentColor;
}
/* Hover: accent label + the same 13% accent wash the Phase 46 buttons use. */
.cb-card-menu-item:hover {
  color: var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
}
/* Pressed: the wash deepens to 22% (same as .cb-tb-btn:active). */
.cb-card-menu-item:active {
  background: color-mix(in srgb, var(--mt-accent) 22%, transparent);
}
/* Keyboard focus: the accent-glow ring shared with the toolbar + selection. */
.cb-card-menu-item:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--mt-accent) 60%, transparent);
}
/* Disabled = the four z-order rows on a LOCKED card. Dimmed to --mt-fg 35%
 * (35% not 28%: text needs marginally more presence than a glyph to stay
 * legible-but-off), not-allowed cursor, NO accent wash. pointer-events stay
 * ON (the component drops the click handler, not the events) so the
 * "unlock first" teaching tooltip still fires — exactly the Phase 46
 * .cb-tb-btn--disabled pattern. The Lock/Unlock row is NEVER disabled. */
.cb-card-menu-item.is-disabled {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  cursor: not-allowed;
}
.cb-card-menu-item.is-disabled:hover {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  background: transparent;
}
/* Destructive row (Remove from tab, issue C). Resting state stays neutral so the
 * menu isn't a wall of red; intent turns it red on hover / press / focus. Scoped
 * :not(.is-disabled) so a LOCKED card's greyed remove row keeps the neutral dim
 * above instead of flashing red. --mt-err is the same danger token the
 * binding-drift error uses (this file, ~line 229). */
.cb-card-menu-item--danger:not(.is-disabled):hover {
  color: var(--mt-err, #ef4444);
  background: color-mix(in srgb, var(--mt-err, #ef4444) 14%, transparent);
}
.cb-card-menu-item--danger:not(.is-disabled):active {
  background: color-mix(in srgb, var(--mt-err, #ef4444) 22%, transparent);
}
.cb-card-menu-item--danger:not(.is-disabled):focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--mt-err, #ef4444) 60%, transparent);
}

/* (3) The cluster divider between the 4 z-order rows and Lock/Unlock. A
 * horizontal counterpart of .cb-tb-divider. */
.cb-card-menu-divider {
  height: 1px;
  margin: 4px 0;
  background: var(--mt-border);
}

/* (4) The ⋮ kebab button — discoverability + touch path. 24px corner
 * affordance, top-right, hidden until the card is hovered or selected (mirrors
 * the Phase 44/45 resize-handle reveal). Resting glyph at --mt-fg 70%; hover
 * lights it accent like a toolbar button. */
.cb-card-kebab {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  color: color-mix(in srgb, var(--mt-fg) 70%, transparent);
  opacity: 0;
  cursor: pointer;
  /* Above the Phase 43 .cb-card-clickguard (z-index:1) — the same tier, and for
   * the same reason, as .cb-resize-handle. Without it the kebab is z-index:auto,
   * the transparent full-card guard paints over it, and every press inside this
   * 24px corner is hit-tested as "drag the card" instead of "open the menu":
   * elementFromPoint at the button's centre returned .cb-card-clickguard on every
   * card, hovered or solo-selected (#106, found during the #101 walk). Hit-testing
   * does not care whether the press is trusted, so this was equally broken for a
   * mouse — right-clicking the card body opens the same menu, which is why nobody
   * reported it. NOT pointer-events-gated the way the handles are: the kebab is
   * also the touch path, and a tap arrives with no hover to reveal it first. */
  z-index: 2;
  transition: opacity 120ms ease-out, background 120ms ease-out, color 120ms ease-out;
}
.cb-card-kebab svg {
  fill: currentColor;
}
/* Reveal on card hover or when the card is the solo selection (same selectors
 * the resize handles use). */
.cb-card-grab:hover .cb-card-kebab,
.cb-card-selected--solo .cb-card-kebab {
  opacity: 1;
}
.cb-card-kebab:hover {
  color: var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
}
.cb-card-kebab:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--mt-accent) 60%, transparent);
}

/* (5) The lock glyph (LOCK-03) — a small filled padlock, top-left, edit mode
 * only. Passive: non-interactive, quiet --mt-fg 70%, never accent (accent is
 * reserved for pointer interaction; this is a status mark). */
.cb-card-lock-glyph {
  position: absolute;
  top: 4px;
  left: 4px;
  width: 16px;
  height: 16px;
  pointer-events: none;
  color: color-mix(in srgb, var(--mt-fg) 70%, transparent);
}
.cb-card-lock-glyph svg {
  fill: currentColor;
}

/* (Phase 61 gap fix) The z-badge — the card's STORED stacking value, edit-mode
 * only. Passive (pointer-events:none) so it never eats a press in the corner.
 * Neutral, token-only chrome (NOT accent) so it reads as a debug/legibility tag,
 * never as the selection ring. Sits bottom-left to clear the top-left lock glyph
 * and the top-right kebab. tabular-nums keeps the digits from jittering as the
 * value jumps on Bring to Front / Send to Back. The --mt-bg-elev backing keeps it
 * legible over arbitrary card content. */
.cb-card-zbadge {
  position: absolute;
  bottom: 4px;
  left: 4px;
  pointer-events: none;
  font-size: 10px;
  font-weight: var(--mt-weight-semibold);
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  padding: 1px 5px;
  border-radius: var(--m-radius-sm, 2px);
  color: color-mix(in srgb, var(--mt-fg) 75%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-border) 90%, transparent);
  background: color-mix(in srgb, var(--mt-bg-elev) 82%, transparent);
}

/* (Phase 224, D-13 / D-14) The own-look mark — "the gear will not reach this
 * card", because the card carries colours or fonts of its own. Edit-mode only,
 * bottom-right: the card's one always-free corner, and the z-badge's own
 * baseline at the other end of the same edge, so the two read as one status
 * line across the bottom rather than glyphs scattered through four corners.
 *
 * Every value here is the z-badge's above, mirrored to the opposite corner,
 * with ONE deliberate difference: the colour is the LOCK GLYPH's 70%, not the
 * z-badge's 75%. This is a status mark; 75% is the z-badge's debug-tag
 * register. Do not invent a third grey — 221's 35% stays the only greyed-out
 * value, and neither of this phase's marks ever takes accent (an accent dot on
 * 46 cards at once would read as 46 selected cards).
 *
 * The --mt-bg-elev backing is LOAD-BEARING, not decoration. The mark sits over
 * arbitrary card content — a chart, a camera feed, a photo — and without it the
 * mark is legible on some cards and invisible on others, per theme. That is the
 * same reason the z-badge carries it.
 *
 * pointer-events:none so it never eats a press. The SE resize handle sits at
 * bottom:-5px / right:-5px with z-index:2 (:7471), so it reaches 5px into a
 * corner the mark starts 4px inside — a 1px kiss while the card is hovered, and
 * the press always resolves to the handle. On .cb-adv-card the handle is
 * overridden flush to 0/0 (:7627) and sits over the mark's trailing corner on
 * hover; the press still resolves to the handle and that SAME hover reveals the
 * word, so what Karl reads is the word rather than the covered corner. DO NOT
 * move the mark to dodge this — shifting it off the z-badge's baseline costs
 * more than the overlap does. It is a live-walk item, not a defect. */
.cb-card-own-mark {
  position: absolute;
  bottom: 4px;
  right: 4px;
  display: flex;
  align-items: center;
  pointer-events: none;
  font-size: 10px;
  font-weight: var(--mt-weight-semibold);
  line-height: 1.2;
  letter-spacing: 0.02em;
  padding: 1px 5px;
  border-radius: var(--m-radius-sm, 2px);
  color: color-mix(in srgb, var(--mt-fg) 70%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-border) 90%, transparent);
  background: color-mix(in srgb, var(--mt-bg-elev) 82%, transparent);
}

/* The same 5px dot as the Style panel's per-element mark, at the same size, so
 * Karl learns one glyph rather than two. currentColor so the dot and the word
 * can never drift apart. --m-radius-full may not exist in every theme, so it
 * carries its fallback the way var(--m-radius-sm, 2px) does everywhere else in
 * this file — without it the dot is a square on the themes that omit it. */
.cb-card-own-mark__dot {
  flex: 0 0 auto;
  width: 5px;
  height: 5px;
  border-radius: var(--m-radius-full, 9999px);
  background: currentColor;
}

/* At rest the mark IS a 5px dot: the word is collapsed to nothing, so the tag
 * is dot-width and no wider. That is deliberate rather than shy — after one
 * *All cards* apply every placed card carries colours and every one is marked,
 * and 46 word tags is noise where 46 dots is a scannable pattern.
 *
 * The word is what makes a single dot legible without a legend, and it appears
 * on the SAME hover pair the kebab and the resize handles already use (:8087),
 * so the mark joins a reveal Karl's hands already know instead of introducing a
 * second idiom. Only the opacity is transitioned — the width snaps and the word
 * fades in over it, which is the kebab's own 120ms fade applied to the one
 * property that has something to fade. */
.cb-card-own-mark__word {
  flex: 0 0 auto;
  width: 0;
  margin-left: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 120ms ease-out;
}
.cb-card-grab:hover .cb-card-own-mark__word,
.cb-card-selected--solo .cb-card-own-mark__word {
  width: auto;
  margin-left: 4px;
  opacity: 1;
}

/* (6) The overlap outline (Z-03) — 1px DASHED, drawn as an outline (not a
 * border) with a 2px offset so it sits just outside the card box and never
 * shifts the card's own layout or content. Dashed + neutral --mt-border-strong
 * (not accent) so it reads as "these are stacked," never as a selection ring. */
.cb-card-overlap {
  outline: 1px dashed color-mix(in srgb, var(--mt-border-strong) 80%, transparent);
  outline-offset: 2px;
}
/* === Phase 47 card chrome END === */

/* === Phase 48 undo/redo feedback (D-03) === *
 * .undo-highlight — the transient flash applied to a card box right after an
 * undo/redo re-applies, so the user SEES which card just changed. The JS adds
 * the class, scrolls the card into view, and strips the class after ~1s; the
 * CSS does the rest with a single keyframed pulse that starts bright and fades
 * to nothing on its own (so even if the strip were missed, the card settles
 * back to a calm ring). Built on --mt-accent — the same accent every pointer
 * interaction uses — so it reads as "the system did this," distinct from the
 * neutral overlap dashes and the passive lock glyph. The 2px outline-offset
 * matches .cb-card-overlap so the ring sits just outside the box without
 * nudging layout, and the box-shadow adds a soft glow an outline alone can't.
 * Honors prefers-reduced-motion: no pulse, just a brief steady ring. */
@keyframes cb-undo-flash {
  0% {
    outline-color: var(--mt-accent, #00d992);
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--mt-accent, #00d992) 60%, transparent),
                0 0 16px 2px color-mix(in srgb, var(--mt-accent, #00d992) 55%, transparent);
  }
  60% {
    outline-color: var(--mt-accent, #00d992);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--mt-accent, #00d992) 30%, transparent),
                0 0 22px 4px color-mix(in srgb, var(--mt-accent, #00d992) 35%, transparent);
  }
  100% {
    outline-color: transparent;
    box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent;
  }
}
.undo-highlight {
  outline: 2px solid var(--mt-accent, #00d992);
  outline-offset: 2px;
  border-radius: 2px;
  animation: cb-undo-flash 1s ease-out forwards;
  /* keep the glow above neighbouring boxes so the ring isn't clipped */
  z-index: 9000;
}
@media (prefers-reduced-motion: reduce) {
  .undo-highlight {
    animation: none;
    outline: 2px solid color-mix(in srgb, var(--mt-accent, #00d992) 70%, transparent);
    box-shadow: none;
  }
}

/* === Phase 53-05 async watch-until-done feedback (D-05/D-06) === *
 * The card shows a "Watching…" line while a Proxmox start/stop/snapshot task
 * runs (the action returns immediately; the card polls the task to completion).
 *
 * .cb-watching-dot — a small pulsing dot in the ACCENT color. The accent is
 * also the primary-button color, so MOTION is the only thing that tells
 * "watching" apart from a static button (UI-SPEC: reuse --m-accent + motion,
 * don't add a token). The pulse is CSS-only and reduced-motion aware, mirroring
 * .undo-highlight above: under prefers-reduced-motion the dot is a steady
 * accent dot with no pulse, so the state still reads without animation.
 *
 * .cb-action-watching / .cb-action-gaveup — the two muted text lines. 'gaveup'
 * (we stopped watching at the 5-min cap) is deliberately NEUTRAL, never the red
 * error treatment — the job may still finish; we just stopped watching (D-06). */
@keyframes cb-watching-pulse {
  0%   { transform: scale(0.7); opacity: 0.55; }
  50%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(0.7); opacity: 0.55; }
}
.cb-action-watching {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  color: var(--mt-text-muted, var(--m-text-muted));
  font-family: var(--m-font-mono);
  font-size: 11px;
  line-height: 1.3;
}
.cb-watching-dot {
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--mt-accent, var(--m-accent));
  animation: cb-watching-pulse 1.2s ease-in-out infinite;
}
.cb-action-watching__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-action-gaveup {
  display: flex;
  align-items: center;
  max-width: 100%;
  color: var(--mt-text-muted, var(--m-text-muted));
  font-family: var(--m-font-mono);
  font-size: 11px;
  line-height: 1.3;
}
.cb-action-gaveup__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  .cb-watching-dot {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* -------------------------------------------------------------------------
 * Recent-changes history panel (Phase 48-05, UNDO-04 / D-04).
 *
 * The gear → Design hub → "Recent changes" modal renders the active tab's
 * last 5 undoable actions as a TIMELINE: newest at the top, a thin vertical
 * spine threading the markers so the depth of a rewind reads at a glance
 * (click row 3 and you can see the two newer entries above it that go with
 * it). Each row is "label … undo-back-here". The revert button stays quiet
 * until you hover/focus the row, so the list reads as a calm changelog, not
 * a wall of buttons — then the action you want lights up in brand-amber.
 *
 * Cohesive with the existing dark / monospace chrome: lives inside the
 * shared .manifold-app-modal-body, uses --mt-accent (the same accent the
 * undo-flash and every pointer interaction use) so "undo" reads as the
 * same system gesture across the modal AND the canvas flash. No new font,
 * no new color scheme — restraint over novelty (the surrounding system is
 * already the design).
 * ------------------------------------------------------------------------- */
.manifold-undo-history-empty {
  font-family: var(--m-font-mono);
  font-size: 13px;
  line-height: 1.5;
  color: color-mix(in srgb, var(--mt-fg, #e5e7eb) 55%, transparent);
  padding: 8px 2px 4px;
}

.manifold-undo-history-list {
  list-style: none;
  margin: 0;
  padding: 4px 0 4px 2px;
  position: relative;
}
/* the timeline spine: a faint vertical line behind the markers, inset to
 * sit under the marker centers. Drawn from the first marker to the last. */
.manifold-undo-history-list::before {
  content: '';
  position: absolute;
  left: 6px;
  top: 14px;
  bottom: 14px;
  width: 1px;
  background: color-mix(in srgb, var(--mt-accent, #00d992) 28%, transparent);
}
/* while a rewind is in flight, dim + lock the whole list so a second click
 * can't fire mid-cascade. */
.manifold-undo-history-list[data-busy='true'] {
  opacity: 0.55;
  pointer-events: none;
}

.manifold-undo-history-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 4px 7px 22px;
  border-radius: 4px;
  transition: background 120ms ease;
}
.manifold-undo-history-row:hover,
.manifold-undo-history-row:focus-within {
  background: color-mix(in srgb, var(--mt-accent, #00d992) 8%, transparent);
}

/* the marker dot on the spine. The newest entry (first row) reads as the
 * "now" point — a filled amber dot; the rest are hollow rings. */
.manifold-undo-history-marker {
  position: absolute;
  left: 2px;
  top: 50%;
  transform: translateY(-50%);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1.5px solid var(--mt-accent, #00d992);
  background: var(--mt-panel, #0c0f14);
  box-sizing: border-box;
}
.manifold-undo-history-row:first-child .manifold-undo-history-marker {
  background: var(--mt-accent, #00d992);
  box-shadow: 0 0 8px 0 color-mix(in srgb, var(--mt-accent, #00d992) 50%, transparent);
}

.manifold-undo-history-label {
  flex: 1 1 auto;
  font-family: var(--m-font-mono);
  font-size: 13px;
  color: var(--mt-fg, #e5e7eb);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.manifold-undo-history-revert {
  flex: 0 0 auto;
  font-family: var(--m-font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--mt-accent, #00d992);
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--mt-accent, #00d992) 40%, transparent);
  border-radius: 3px;
  padding: 3px 8px;
  cursor: pointer;
  /* quiet until the row is hovered/focused — keeps the list a calm changelog */
  opacity: 0;
  transition: opacity 120ms ease, background 120ms ease, border-color 120ms ease;
}
.manifold-undo-history-row:hover .manifold-undo-history-revert,
.manifold-undo-history-row:focus-within .manifold-undo-history-revert,
.manifold-undo-history-revert:focus-visible {
  opacity: 1;
}
.manifold-undo-history-revert:hover {
  background: color-mix(in srgb, var(--mt-accent, #00d992) 18%, transparent);
  border-color: var(--mt-accent, #00d992);
}
.manifold-undo-history-revert:disabled {
  cursor: default;
  opacity: 0.4;
}
/* touch / no-hover devices can't reveal-on-hover; show the buttons always. */
@media (hover: none) {
  .manifold-undo-history-revert { opacity: 1; }
}

/* ════════════════════════════════════════════════════════════════════════
   Recommended-card band + preview tiles (Phase 53.1-02, RCARD-02)
   Sketch 001 winner B — preview tiles. Execution WITHIN Manifold's shipped
   system (dark / IBM-Plex / amber accent; 4/8/14/20/32/48 spacing). Every
   color reads a theme token (var(--m-*)) so all four themes restyle for
   free (project_manifold_css_strategy). Only layout stays here; no hardcoded
   palette. Fallbacks mirror the default theme so a missing token degrades
   gracefully rather than rendering invisible.
   ════════════════════════════════════════════════════════════════════════ */
.cb-recommended-band {
  padding: var(--m-pad-xs, 4px) 0 var(--m-pad-sm, 8px);
}
.cb-recommended-band__label,
.cb-recommended-band__build-label {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: var(--m-pad-xs, 4px) 0;
  font-size: var(--mt-text-sm-size, 12px);
  color: var(--m-text-muted, #9aa0a6);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.cb-recommended-band__star {
  color: var(--m-accent, #f5a623);
}
.cb-recommended-band__divider {
  height: 1px;
  background: var(--m-border, #2a2e33);
  margin: var(--m-pad-sm, 8px) 0;
}
.cb-recommended-band__tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(186px, 1fr));
  gap: var(--m-pad-sm, 8px);
  padding: var(--m-pad-xs, 4px) 0 var(--m-pad-sm, 8px);
}

/* one tile = a small live-looking miniature of the real card */
.cb-recommended-tile {
  position: relative;
  background: var(--m-bg, #0d0f12);
  border: 1px solid var(--m-border-hi, #3a3f45);
  border-radius: var(--m-radius-md, 4px);
  overflow: hidden;
  cursor: pointer;
  transition: border-color var(--m-t-fast, 120ms ease),
              transform var(--m-t-fast, 120ms ease);
}
.cb-recommended-tile:hover,
.cb-recommended-tile:focus-visible {
  border-color: var(--m-accent-line, rgba(245, 166, 35, 0.32));
  transform: translateY(-1px);
  outline: none;
}
.cb-recommended-tile__head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 10px;
  background: var(--m-surface-2, #1a1d21);
  border-bottom: 1px solid var(--m-border, #2a2e33);
  font-family: var(--m-font-display, var(--m-font-sans, sans-serif));
  font-weight: var(--mt-weight-semibold, 600);
  font-size: var(--mt-text-sm-size, 12px);
  color: var(--m-text, #e6e8ea);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cb-recommended-tile__body {
  padding: var(--m-pad-sm, 8px);
  min-height: 64px;
  max-height: 96px;
  overflow: hidden;
}
/* The miniature: render the real CardRenderer, scaled down + clipped. The
   scale wrapper sizes the renderer up then transforms it down so the preview
   reads as a faithful shrink of the finished card, not a redrawn fake. */
.cb-recommended-tile__preview {
  position: relative;
  height: 80px;
  overflow: hidden;
  pointer-events: none; /* the whole tile is the Add button; don't let the
                           preview's inner controls steal the click */
}
.cb-recommended-tile__preview-scale {
  transform: scale(0.62);
  transform-origin: top left;
  width: 161%;  /* 1 / 0.62 — so the scaled content fills the tile width */
}
.cb-recommended-tile__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 64px;
  color: var(--m-text-faint, #6b7178);
  font-size: var(--mt-text-xs-size, 11px);
  font-style: italic;
}
/* per-node stack tag — the "1 / node" pill from sketch B */
.cb-recommended-tile__stack {
  position: absolute;
  top: 6px;
  right: 8px;
  z-index: 2;
  font-family: var(--m-font-mono, ui-monospace, monospace);
  font-size: 9px;
  color: var(--m-accent-hi, #ffc04d);
  background: var(--m-accent-low, rgba(245, 166, 35, 0.12));
  border: 1px solid var(--m-accent-line, rgba(245, 166, 35, 0.32));
  border-radius: var(--m-radius-full, 9999px);
  padding: 1px 6px;
}
/* Add affordance — full-tile scrim revealed on hover/focus */
.cb-recommended-tile__add-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--m-bg, #0d0f12) 72%, transparent);
  opacity: 0;
  transition: opacity var(--m-t-fast, 120ms ease);
}
.cb-recommended-tile:hover .cb-recommended-tile__add-overlay,
.cb-recommended-tile:focus-visible .cb-recommended-tile__add-overlay {
  opacity: 1;
}
.cb-recommended-tile__add-btn {
  font-family: var(--m-font-sans, sans-serif);
  font-size: var(--mt-text-sm-size, 12px);
  font-weight: var(--mt-weight-semibold, 600);
  color: var(--m-bg, #0d0f12);
  background: var(--m-accent, #f5a623);
  border: 1px solid var(--m-accent, #f5a623);
  border-radius: var(--m-radius-md, 4px);
  padding: 5px 14px;
}
/* touch / no-hover devices can't reveal-on-hover — keep the Add scrim faintly
   visible so the affordance is discoverable. */
@media (hover: none) {
  .cb-recommended-tile__add-overlay { opacity: 0.92; }
}

/* === Event-feed note + preview (999.6) =============================== *
 * Shown in the list-card inspector when the bound leaf is an event feed:
 * the per-column checkboxes did nothing for these leaves (the feed always
 * draws its own fixed row layout), so we replace them with a plain note
 * plus a small live preview of the real rows. All colour rides the
 * existing --mt-* tokens so all four themes restyle for free — no hex. */
.cb-feed-note {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  background: var(--mt-bg-deep);
  border: 1px solid var(--mt-border);
  border-left: 3px solid var(--mt-accent);
  border-radius: 6px;
}

.cb-feed-note__head {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Small accent pill that names the surface in one word. */
.cb-feed-note__badge {
  flex: 0 0 auto;
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-accent) 34%, transparent);
  border-radius: 9999px;
  padding: 2px 8px;
}

.cb-feed-note__title {
  font-family: var(--m-font-sans, sans-serif);
  font-size: 13px;
  font-weight: var(--mt-weight-semibold);
  color: var(--mt-fg);
}

.cb-feed-note__body {
  margin: 0;
  font-family: var(--m-font-sans, sans-serif);
  font-size: 12px;
  line-height: 1.5;
  color: var(--mt-muted);
}

/* --- Live preview of the actual feed rows --- */
.cb-feed-preview {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.cb-feed-preview__label {
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mt-muted);
}

/* The preview body hosts the real CardRenderer; cap its height so a long
 * feed doesn't take over the inspector, and clip the overflow. */
.cb-feed-preview__body {
  max-height: 140px;
  overflow: hidden;
  background: var(--mt-bg);
  border: 1px solid var(--mt-border);
  border-radius: 4px;
}

/* === 999.25 — always-on inline leaf search (single-value cards) =======
 * Wraps an embedded LeafPicker (single-select) inside the Data block so a
 * single-value card always has a catalog search, with no rail-mode hunting.
 * The picker is a full-height grid by default; inline we give it a quiet
 * framed box, a small top margin off the read-only leaf header, and a
 * bounded height so a big catalog scrolls inside the frame instead of
 * stretching the inspector. All --mt-* tokens, zero hex — restyles per
 * theme for free. The picker's own header/search/body styles
 * (.cb-leaf-picker-*) carry the look; this is only the inline container. */
.cb-single-leaf-filter {
  margin-top: 8px;
  border: 1px solid var(--mt-border);
  border-radius: 4px;
  background: color-mix(in srgb, var(--mt-fg) 3%, transparent);
  /* Bound the embedded picker so its 1fr body scrolls inside this frame. */
  max-height: 260px;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* The embedded picker fills the frame; its internal body owns the scroll. */
.cb-single-leaf-filter > div {
  min-height: 0;
  flex: 1;
  height: auto;
}

/* === Phase 61 — find/select a hidden card (D-01..D-05) ===================== *
 * Token-only (--mt-* + color-mix), zero raw hex. Two affordances:
 *   (a) .cb-ghost-outline — the on-hover dashed footprint of a buried card.
 *   (b) .cb-cardlist-*    — the "Cards on this tab" list inside CardContextMenu.
 * Both reuse shipped edit-chrome conventions: the outline is the same neutral
 * dashed family as .cb-card-overlap (NOT accent, so it never reads as a
 * selection ring); the list rows resolve to .cb-card-menu-item behavior; the
 * z-buttons reuse the .cb-card-kebab hit-target. Accent is reserved for pointer
 * interaction + the jump flash (.undo-highlight, owned elsewhere). Nothing here
 * is destructive (D-04) — no --mt-err anywhere. */

/* (a) Ghost outline footprint — ALWAYS-ON in edit mode (redesign 2026-06-06).
 * One per buried card: a bold neutral DASHED border + a diagonal HATCH fill so the
 * hidden area always reads as "a card is under here" (neutral --mt-fg / --mt-border,
 * NOT accent — accent stays reserved for the selection ring + pointer intent). The
 * WHOLE box is the click-to-raise target (cursor pointer); accent appears only on
 * hover/focus to confirm the target is live. */
.cb-ghost-outline {
  pointer-events: auto;
  cursor: pointer;
  box-sizing: border-box;
  border: 2px dashed color-mix(in srgb, var(--mt-border-strong) 92%, transparent);
  border-radius: 3px;
  /* diagonal hatch fill, neutral, tints with the token */
  background:
    repeating-linear-gradient(45deg,
      color-mix(in srgb, var(--mt-fg) 10%, transparent) 0 5px,
      transparent 5px 10px);
  transition: border-color 120ms ease-out, background 120ms ease-out;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  overflow: hidden;
}
.cb-ghost-outline:hover {
  border-color: color-mix(in srgb, var(--mt-accent) 70%, transparent);
  background:
    repeating-linear-gradient(45deg,
      color-mix(in srgb, var(--mt-accent) 16%, transparent) 0 5px,
      transparent 5px 10px);
}
.cb-ghost-outline:focus-visible {
  outline: none;
  border-color: color-mix(in srgb, var(--mt-accent) 70%, transparent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--mt-accent) 60%, transparent);
}
/* The buried card's name chip — same neutral token chrome as the z-badge so it
 * reads as a label, not a control. pointer-events:none so the whole footprint
 * (its parent) stays the single click target. */
.cb-ghost-outline-label {
  pointer-events: none;
  margin: 4px;
  max-width: calc(100% - 8px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  font-weight: var(--mt-weight-semibold);
  line-height: 1.3;
  padding: 1px 6px;
  border-radius: var(--m-radius-sm, 2px);
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  background: color-mix(in srgb, var(--mt-bg-elev) 88%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-border) 90%, transparent);
}
/* Reduced motion: drop the hover transitions (mirrors the .undo-highlight guard). */
@media (prefers-reduced-motion: reduce) {
  .cb-ghost-outline {
    transition: none;
  }
}

/* (b) The "Cards on this tab" list. */

/* The disclosure row inherits .cb-card-menu-item; only the triangle needs a
 * rotate when open. The triangle points right (collapsed) → down (open). */
.cb-cardlist-tri {
  transition: transform 120ms ease-out;
}
.cb-cardlist-tri--open {
  transform: rotate(90deg);
}
/* The disclosure label is semibold to distinguish the section from its rows. */
.cb-cardlist-disclosure {
  font-weight: var(--mt-weight-semibold);
}

/* The scrollable sub-list. Bounded so a 20+-card tab never grows the glass menu
 * past the viewport; the list scrolls internally. */
.cb-cardlist-scroll {
  max-height: 240px;
  overflow: auto;
}

/* One list row: the select-target body on the left, the z-button cluster right. */
.cb-cardlist-row {
  display: flex;
  align-items: center;
  gap: 4px;
}
/* The row body = a .cb-card-menu-item, flexed so the name takes the slack and
 * the badge sits beside it. Resting/hover/active/focus mirror the menu rows. */
.cb-cardlist-rowbody {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  min-width: 0;
  height: 28px;
  padding: 6px 8px;
  border-radius: var(--m-radius-sm, 2px);
  font-size: 13px;
  font-weight: var(--mt-weight-regular);
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  background: transparent;
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}
.cb-cardlist-rowbody:hover {
  color: var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
}
.cb-cardlist-rowbody:active {
  background: color-mix(in srgb, var(--mt-accent) 22%, transparent);
}
.cb-cardlist-rowbody:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--mt-accent) 60%, transparent);
}
/* The card-name label — clip a long name so the badge + z-buttons stay put. */
.cb-cardlist-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The "Covered" badge — neutral status pill, semibold, NO accent / NO --mt-err
 * (D-04 — a buried card is a status, not a danger). */
.cb-cardlist-badge {
  flex: none;
  font-size: 11px;
  font-weight: var(--mt-weight-semibold);
  line-height: 1.2;
  padding: 1px 6px;
  border-radius: var(--m-radius-sm, 2px);
  color: color-mix(in srgb, var(--mt-fg) 70%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-border) 90%, transparent);
  background: color-mix(in srgb, var(--mt-fg) 6%, transparent);
}

/* The per-row z-button cluster + buttons — reuse the .cb-card-kebab 24px hit
 * target + accent-wash hover. */
.cb-cardlist-zcluster {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: none;
}
.cb-cardlist-zbtn {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  color: color-mix(in srgb, var(--mt-fg) 70%, transparent);
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}
.cb-cardlist-zbtn svg {
  fill: currentColor;
  stroke: currentColor;
}
.cb-cardlist-zbtn:hover {
  color: var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
}
.cb-cardlist-zbtn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--mt-accent) 60%, transparent);
}
/* Locked row z-button: greyed, not-allowed, pointer-events stay ON so the
 * "unlock first" title still fires (same posture as .cb-card-menu-item.is-disabled). */
.cb-cardlist-zbtn.is-disabled {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  cursor: not-allowed;
}
.cb-cardlist-zbtn.is-disabled:hover {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  background: transparent;
}

/* Empty-tab copy inside the list. */
.cb-cardlist-empty {
  padding: 6px 8px;
  font-size: 13px;
  color: color-mix(in srgb, var(--mt-fg) 55%, transparent);
}

/* === POLISH-03: Heatmap duration-picker / legend separation =============
 *
 * The HeatmapRenderer places a BuiltInRangeControl chip at
 * position:absolute top:6 right:6 inside the card. Without spacing,
 * the chip floats directly over the legend-bar and legend-ticks columns
 * at the top-right corner of the card.
 *
 * Fix: manifold-renderer-heatmap-body (the flex row that holds the grid
 * and the legend strip) gets a padding-top of 26px. That pushes the grid
 * and legend below the ~22px-tall chip (top:6 + 22px height + 4px gap
 * ≈ 32px), so the chip and the topmost legend tick are visibly separated
 * at both the default card size and after a resize. The padding applies
 * unconditionally so the layout is stable whether or not a range-able
 * source is loaded — the space is always reserved in the same slot. */
.manifold-renderer-heatmap-body {
  padding-top: 26px;
}

/* === CHART-02 — MultiSeriesChartRenderer hover-stability fix ============
 *
 * Problem (§999.97): When the user hovers over a MultiSeriesChartRenderer
 * card, the per-series value readout (chip or big-number) changes text as
 * the hovered history index changes. Because the value spans have no fixed
 * width and the header row uses flexWrap:'wrap', a wider value string can
 * push the row over a wrap boundary, adding a line and stealing height from
 * the flex:1 chart pane below — which makes the chart grow/shrink.
 *
 * Fix (shapes 1+2 from 114-UI-SPEC, as locked by the 114-04 live CDP repro):
 *
 * Shape 1 — .cb-series-value-stable:
 *   font-variant-numeric: tabular-nums forces all digits to the same advance
 *   width, so "1.0" and "127.4" occupy the same horizontal space.
 *   min-width: 5ch reserves enough room for typical 5-character numeric
 *   strings (e.g. "127.4", "1.0 G"). This removes the width-change trigger
 *   entirely: if the span can't change pixel width, the row can't re-wrap.
 *
 * Shape 2 — .cb-header-row-static:
 *   flex: 0 0 auto on the header wrapper prevents the flex column from
 *   redistributing header height into the flex:1 chart pane. Even if a
 *   wrap were to occur (e.g. a chip label + value combination the min-width
 *   didn't cover), the header's own height changes stay self-contained.
 *
 * Both classes use only properties not in the ESLint FORBIDDEN_KEYS list,
 * so they can be written here in the CSS file rather than as inline JSX.
 *
 * The Phase-67 / CRSZ-02 canvas-scale ResizeObserver fix (card-builder.jsx
 * ~8158) is a separate code path and is NOT touched by this change.
 * ======================================================================= */

/* Applied to the per-series value span in renderHeaderChips and the
 * number spans in renderHeaderBigNumbers. Keeps digit width stable across
 * hover-index changes so the span pixel width never changes. */
.cb-series-value-stable {
  font-variant-numeric: tabular-nums;
  min-width: 5ch;
}

/* Applied to the flexWrap:'wrap' header row divs. Removes the header from
 * the flex height distribution so it cannot steal height from the chart pane
 * (which is flex:1) when its own content height changes. */
.cb-header-row-static {
  flex: 0 0 auto;
}

/* ======================================================================
 * CHART-04 — off-screen chart scroll culling (Phase 114 plan 07)
 *
 * Applied to the outer wrapper of MultiSeriesChartRenderer. When a chart
 * card scrolls out of the viewport, `content-visibility: auto` tells the
 * browser to skip layout and paint for that subtree entirely — directly
 * targeting the 24 off-screen redraws measured in the 114-06 DevTools
 * trace. Without this, every off-screen multiSeriesChart keeps driving
 * layout during scroll even though the user can't see it.
 *
 * `contain-intrinsic-size`: provides a stable estimated size so the
 * browser can reserve scroll-height space for off-screen cards without
 * actually laying them out. The 0 (inline) + 280px (block) estimate
 * is intentionally conservative — charts are typically taller, so the
 * browser may underestimate, but it will correct itself when the card
 * scrolls back into view. Skipping contain-intrinsic-size would let the
 * scrollbar jump as off-screen cards collapse to 0 height.
 *
 * Why the renderer div (not .cb-card-frame): content-visibility works
 * based on the element's position relative to the viewport, not its
 * containment context. Placing it here scopes the skip to the chart
 * renderer only (not the border/chrome layer) and avoids touching the
 * card-frame contract shared with all other card types.
 * ====================================================================== */
.cb-chart-scroll-contain {
  content-visibility: auto;
  contain-intrinsic-size: 0 280px;
  will-change: transform; /* CHART-08: compositor layer promotion; reduces repaint cost during scroll on passive-cooling hardware */
}

/* ======================================================================
 * Phase 126 FRIG-01 — Frigate filter panel inspector components
 *
 * FrigateFilterPanel: camera + object/who chip rows in the inspector for
 * Frigate-bound list and bbox-overlay cards. All colors via --mt-*
 * tokens — no hex literals — so all themes restyle for free.
 *
 * Visual states (ChipToggle):
 *   unselected: transparent bg, --mt-border outline, --mt-muted text
 *   hover:      color-mix(accent 8%, transparent) bg, --mt-border-strong outline
 *   selected:   color-mix(accent 12%, transparent) bg, --mt-accent outline + text
 *
 * Typography: IBM Plex Mono (var(--m-font-mono)), 10px/400 for chips —
 * matches .cb-series-checkbox-row; 9px/700 section labels handled by
 * the existing .field-label-text class (reused, not redefined here).
 *
 * All aesthetic keys (background, color, padding, margin, font-*) live
 * here — inline aesthetic styles are hard ESLint errors in deploy.sh.
 * Only structural layout inline styles (display, flexDirection, gap,
 * alignItems) are permitted in JSX (feedback_manifold_deploy_lint_gate).
 * ====================================================================== */

/* Container: vertical gap between the two filter dimensions */
.cb-frigate-filter-panel {
  padding: var(--mt-space-md);
  display: flex;
  flex-direction: column;
  gap: var(--mt-space-lg);
}

/* Chip row: wrapping flex row of ChipToggle buttons */
.cb-frigate-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mt-space-sm);
}

/* Individual chip: unselected resting state */
.cb-frigate-chip {
  background: transparent;
  border: 1px solid var(--mt-border);
  color: var(--mt-muted);
  border-radius: var(--m-radius-md);
  padding: 3px 8px;
  font-family: var(--m-font-mono);
  font-size: 10px;
  font-weight: var(--mt-weight-regular);
  cursor: pointer;
  transition: background 120ms linear, border-color 120ms linear;
}

/* Chip hover: accent tint bg + stronger border + fg text */
.cb-frigate-chip:hover {
  background: color-mix(in srgb, var(--mt-accent) 8%, transparent);
  border-color: var(--mt-border-strong);
  color: var(--mt-fg);
}

/* Chip selected: accent fill + accent border + accent text */
.cb-frigate-chip.is-selected {
  background: color-mix(in srgb, var(--mt-accent) 12%, transparent);
  border-color: var(--mt-accent);
  color: var(--mt-accent);
}

/* Empty-buffer state: dim line shown when the event buffer has no events yet */
.cb-frigate-empty {
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1.5;
}

/* Clear filters button: no chrome, accent text, mono font */
.cb-frigate-clear {
  background: none;
  border: none;
  color: var(--mt-accent);
  font-family: var(--m-font-mono);
  font-size: 10px;
  cursor: pointer;
  margin-top: 6px;
  padding: 0;
}

/* ======================================================================
 * Phase 126-06 FRIG-01 — on-card per-column Excel-style filter (ND-02)
 *
 * ListColumnFunnel: a compact ▿ glyph button in each filterable column
 *   header (Camera / Type / Name). Resting state is muted/transparent;
 *   .is-active flips to accent color matching cb-frigate-chip.is-selected
 *   so an active filter column is visible at a glance (ND-03).
 *
 * ListColumnDropdown: a small absolutely-positioned panel of checkbox rows
 *   for the distinct live-buffer values of the column. Elevated background,
 *   hairline border, z-index above the header row.
 *
 * cb-frigate-col-reset: the Clear-filters affordance shown whenever any
 *   column dimension is non-empty (ND-03).
 *
 * All values use --mt-* / --m-* tokens — no hex literals.
 * ====================================================================== */

/* Column funnel button — resting: muted, transparent, no chrome */
.cb-frigate-col-funnel {
  background: transparent;
  border: none;
  color: var(--mt-muted);
  cursor: pointer;
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1;
  padding: 0 2px;
  vertical-align: middle;
  opacity: 0.6;
  transition: color 120ms linear, opacity 120ms linear;
}

/* Funnel hover: full opacity, fg text */
.cb-frigate-col-funnel:hover {
  color: var(--mt-fg);
  opacity: 1;
}

/* Funnel active: accent color, full opacity — mirrors cb-frigate-chip.is-selected */
.cb-frigate-col-funnel.is-active {
  color: var(--mt-accent);
  opacity: 1;
}

/* Column dropdown panel — elevated surface, hairline border */
.cb-frigate-col-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 120;
  background: var(--mt-bg-elev, var(--mt-bg-deep, #1d2128));
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 3px);
  padding: var(--mt-space-xs, 4px) 0;
  min-width: 140px;
  max-width: 220px;
  font-family: var(--m-font-mono);
  font-size: 10px;
}

/* Checkbox row inside the dropdown */
.cb-frigate-col-checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--mt-space-xs, 4px);
  padding: 2px var(--mt-space-sm, 8px);
  color: var(--mt-muted);
  cursor: pointer;
  line-height: 1.5;
  /* FRIG-02: option VALUES are stored raw (Frigate casing, e.g. car / person /
     train) so the case-sensitive server filter matches exactly; capitalize for
     display only. */
  text-transform: capitalize;
}

.cb-frigate-col-checkbox-row:hover {
  color: var(--mt-fg);
}

/* Dropdown footer — cap note, divider, muted mono */
.cb-frigate-col-footer {
  border-top: 1px solid var(--mt-border);
  margin-top: var(--mt-space-xs, 4px);
  padding: var(--mt-space-xs, 4px) var(--mt-space-sm, 8px) 2px;
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1.5;
}

/* Clear-filters reset button — accent text, no chrome, inline with header */
.cb-frigate-col-reset {
  background: transparent;
  border: none;
  color: var(--mt-accent);
  cursor: pointer;
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1;
  padding: 0 var(--mt-space-xs, 4px);
  opacity: 0.85;
  transition: opacity 120ms linear;
}

.cb-frigate-col-reset:hover {
  opacity: 1;
}

/* ======================================================================
 * Phase 126-07 FRIG-01 — bbox card on-card filter funnel (ND-04)
 *
 * BboxFilterFunnel: a compact ▿ glyph button positioned in the top-right
 *   corner of the bbox overlay. .is-active mirrors cb-frigate-col-funnel
 *   accent so an active filter is visible at a glance (ND-03).
 *
 * BboxFilterDropdown: a combined camera+type+name picker in one panel,
 *   with three labelled sections. Reuses cb-frigate-col-checkbox-row and
 *   cb-frigate-col-footer for visual consistency with the list card funnel.
 *
 * cb-frigate-bbox-reset: the Clear affordance inside the dropdown.
 *
 * All values use --mt-* / --m-* tokens — no hex literals.
 * ====================================================================== */

/* Funnel wrap — positions the funnel button + dropdown in the top-right
   corner of the overlay, above boxes (z-index 130). */
.cb-frigate-bbox-funnel-wrap {
  position: absolute;
  top: var(--mt-space-xs, 4px);
  right: var(--mt-space-xs, 4px);
  z-index: 130;
}

/* Funnel button — resting: muted, transparent, no chrome */
.cb-frigate-bbox-funnel {
  background: transparent;
  border: none;
  color: var(--mt-muted);
  cursor: pointer;
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1;
  padding: 0 2px;
  opacity: 0.6;
  transition: color 120ms linear, opacity 120ms linear;
}

.cb-frigate-bbox-funnel:hover {
  color: var(--mt-fg);
  opacity: 1;
}

/* Active: accent color, full opacity — mirrors cb-frigate-col-funnel.is-active */
.cb-frigate-bbox-funnel.is-active {
  color: var(--mt-accent);
  opacity: 1;
}

/* Dropdown panel — opens below the funnel wrap, elevated surface */
.cb-frigate-bbox-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 130;
  background: var(--mt-bg-elev, var(--mt-bg-deep, #1d2128));
  border: 1px solid var(--mt-border);
  border-radius: var(--m-radius-sm, 3px);
  padding: var(--mt-space-xs, 4px) 0;
  min-width: 160px;
  max-width: 240px;
  font-family: var(--m-font-mono);
  font-size: 10px;
}

/* Section label inside the dropdown (CAMERA / TYPE / NAME) */
.cb-frigate-bbox-section-label {
  color: var(--mt-muted);
  font-family: var(--m-font-mono);
  font-size: 9px;
  font-weight: var(--mt-weight-semibold);
  letter-spacing: 0.04em;
  padding: var(--mt-space-xs, 4px) var(--mt-space-sm, 8px) 2px;
  opacity: 0.7;
}

/* Clear button inside the bbox dropdown */
.cb-frigate-bbox-reset {
  background: transparent;
  border: none;
  color: var(--mt-accent);
  cursor: pointer;
  font-family: var(--m-font-mono);
  font-size: 10px;
  line-height: 1;
  padding: var(--mt-space-xs, 4px) var(--mt-space-sm, 8px);
  opacity: 0.85;
  transition: opacity 120ms linear;
  display: block;
  width: 100%;
  text-align: left;
}

.cb-frigate-bbox-reset:hover {
  opacity: 1;
}

/* === Phase 127 (GAUGE-01): Bullet graph renderer classes ====================
 *
 * BarChartMultiRenderer's horizontal arm gains a bullet option (style.bullet).
 * All computed colors flow through --m-renderer-color so a theme switch or
 * per-series color override restyles the element for free.
 * No inline color/backgroundColor/fontSize/margin in JSX — those are eslint
 * errors that silently block deploy.sh (feedback_manifold_deploy_lint_gate).
 * ========================================================================== */

/* Outer row wrapper: flex column (legend on top, track below), full width. */
.manifold-renderer-bullet-row {
  display: flex;
  flex-direction: column;
  width: 100%;
  gap: 2px;
}

/* Always-on legend (D-06): swatch + label + live value in a flex row. */
.manifold-renderer-bullet-legend {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-shrink: 0;
}

/* Color swatch in the legend — a small square in the series color. */
.manifold-renderer-bullet-swatch {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
  background: var(--m-renderer-color, var(--mt-accent));
  align-self: center;
}

/* Legend value — right-aligned, tabular numerics. */
.manifold-renderer-bullet-legend-val {
  flex-shrink: 0;
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

/* Track container: positions the SVG fill layer. */
.manifold-renderer-bullet-track {
  flex: 1;
  min-height: 0;
  width: 100%;
  position: relative;
  min-height: 12px;
}

/* SVG fills the full track area. preserveAspectRatio="none" is set inline
 * (it is an SVG attribute, not a CSS aesthetic property). */
.manifold-renderer-bullet-bands-svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Zone band: a background rect at ~0.22 opacity; color from --m-renderer-color. */
.manifold-renderer-bullet-band {
  fill: var(--m-renderer-color, var(--mt-muted));
  opacity: 0.22;
}

/* Value bar: zone-colored fill, 44% of track height (y/height set inline as
 * SVG attributes because they are geometry, not aesthetic styles). */
.manifold-renderer-bullet-bar {
  fill: var(--m-renderer-color, var(--mt-accent));
  border-radius: 2px;
}

/* Target tick: thin vertical rect at bulletTarget position. */
.manifold-renderer-bullet-tick {
  fill: var(--m-renderer-color, var(--mt-dim));
  opacity: 0.8;
}

/* === Phase 127 (GAUGE-01): Concentric rings renderer classes ================
 *
 * GaugeRenderer's style.variant='rings' path draws ≤3 concentric arcs in the
 * same 200×200 SVG viewBox as the single-needle gauge. Colors flow through
 * --m-renderer-color (set per-arc as an inline SVG custom property, not an
 * aesthetic style) so theme switches auto-recolor. No inline color/fontSize/
 * margin in JSX — those are eslint errors that silently block deploy.sh.
 * ========================================================================== */

/* SVG wrapper: takes available vertical space, compresses before the legend. */
.manifold-renderer-rings-svg-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60px;
  min-width: 0;
}

/* The rings SVG — fills its wrapper. */
.manifold-renderer-rings-svg {
  width: 100%;
  height: 100%;
  max-height: 100%;
}

/* Colored fill arc — stroke color comes from --m-renderer-color. */
.manifold-renderer-rings-arc {
  stroke: var(--m-renderer-color, var(--mt-live, var(--mt-accent)));
}

/* Always-on legend (D-06): flex column, never compresses the arcs. */
.manifold-renderer-rings-legend {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 4px;
}

/* One legend row per series: swatch + name + value right-aligned. */
.manifold-renderer-rings-legend-row {
  display: flex;
  align-items: center;
  gap: 5px;
}

/* Color swatch — small square in the arc color. */
.manifold-renderer-rings-swatch {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
  background: var(--m-renderer-color, var(--mt-accent));
}

/* Series name — grows to fill the row. */
.manifold-renderer-rings-legend-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Live value — right-aligned tabular numerics. */
.manifold-renderer-rings-legend-val {
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/* ============================================================================
 * Phase 130 Plan 03 — Save-status chip + inline failure row
 * ----------------------------------------------------------------------------
 * Eight token-only classes for the transient Saving…/Saved ✓ chip (D-02/D-03)
 * and the inline failure surface (D-01/SAVE-01) that appears when a save fails.
 *
 * Consumes ONLY --m-* / --mt-* tokens per the UI-SPEC Color table and the
 * card-builder-helpers.css convention (no hex literals, no variable declarations
 * — those live in theme.css under the build_tokens.py sentinel).
 *
 * .cb-save-chip        — flex row; display:none when absent so the header strip
 *                        shows no gap. Width max-content so it fits either label.
 * .cb-save-chip.is-saving — neutral/muted: muted bg + strong border + muted text
 * .cb-save-chip.is-saved  — success-tinted via color-mix (same pattern as the
 *                           action-error-badge analog, just green instead of red)
 * .cb-save-chip__dot   — 6px filled circle; only visible in is-saved state
 * .cb-save-chip__text  — text node (mono, 11px)
 *
 * .cb-save-error-row   — flex row for the inline failure: dot + message + button
 * .cb-save-error-dot   — 7px red dot, same as .cb-action-error-badge__dot
 * .cb-save-error-text  — error message in error color, 13px sans
 *
 * Animation: cb-save-chip-in (slide from right, 120ms ease-out) for entrance;
 * cb-save-chip-out (fade-out, 120ms) applied at the 1200ms mark so the chip
 * completes fading before the 1400ms auto-close fires.
 * ============================================================================ */

/* Entrance animation — slides in from the right, 120ms ease-out.
   Adapted from the manifold-history-toast-in pattern (theme.css:580–583)
   but horizontal (translateX) rather than vertical (translateY). */
@keyframes cb-save-chip-in {
  from { opacity: 0; transform: translateX(8px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* Exit animation for the Saved ✓ state — fade-out in 120ms.
   Applied at the 1200ms mark so it completes at 1320ms, before the
   1400ms auto-close fires (no jank from a chip still animating on close). */
@keyframes cb-save-chip-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Base chip — display:none when saveStatus is null (element not rendered
   at all via React conditional, so this rule is a defensive fallback only).
   Width is max-content so the chip grows to fit "Saving…" or "Saved ✓"
   without reserving space when hidden. */
.cb-save-chip {
  display: flex;
  align-items: center;
  gap: var(--mt-space-xs, 4px);
  width: max-content;
  padding: 3px var(--mt-space-sm, 8px);
  border-radius: var(--m-radius-lg, 12px);
  font-family: var(--m-font-mono);
  font-size: var(--mt-text-xs-size, 11px);
  font-weight: var(--mt-weight-medium);
  line-height: 1.3;
  animation: cb-save-chip-in 120ms ease-out;
}

/* Dot — 6px filled circle; hidden in is-saving (no dot for neutral state) */
.cb-save-chip__dot {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: transparent;
}

/* Text node */
.cb-save-chip__text {
  white-space: nowrap;
}

/* Saving… state — neutral/muted: elevated bg, strong border, muted text, no dot */
.cb-save-chip.is-saving {
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border-strong);
  color: var(--mt-muted);
}

.cb-save-chip.is-saving .cb-save-chip__dot {
  display: none;
}

/* Saved ✓ state — success-tinted, same color-mix pattern as the error-badge analog */
.cb-save-chip.is-saved {
  background: color-mix(in srgb, var(--m-success) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--m-success) 32%, transparent);
  color: var(--m-success);
  animation: cb-save-chip-in 120ms ease-out, cb-save-chip-out 120ms ease-in 1200ms forwards;
}

.cb-save-chip.is-saved .cb-save-chip__dot {
  background: var(--m-success);
}

/* Inline failure row — flex row containing dot + message text + Try-again button.
   Appears when error is set and no write is in flight (saving=false).
   The Try-again button uses the existing .manifold-button small class (no new class). */
.cb-save-error-row {
  display: flex;
  align-items: center;
  gap: var(--mt-space-sm, 8px);
}

/* 7px red dot — same shape as .cb-action-error-badge__dot */
.cb-save-error-dot {
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--mt-error, var(--m-error));
}

/* Error message text */
.cb-save-error-text {
  color: var(--mt-error, var(--m-error));
  font-family: var(--m-font-sans);
  font-size: var(--mt-text-sm-size, 13px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --wrap modifier (Phase 141-08 follow-up): the broad-skin-apply confirm dialog
 * shows a partial-failure directive ("Applied to 45 of 47 cards. These couldn't be
 * updated: …") that must stay fully readable inside the narrow modal — a failure
 * message gives direction, it doesn't truncate. Scoped to this row so the
 * single-line Save-bar error (manifold.jsx) keeps its ellipsis. The dot aligns to
 * the first line instead of centering against a multi-line block. */
.cb-save-error-row--wrap {
  align-items: flex-start;
}
.cb-save-error-row--wrap .cb-save-error-text {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  overflow-wrap: anywhere;
}

/* ──────────────────────────────────────────────────────────────────────────
 * Phase 134-04 (OWNER-02): Owner's card-shelf section in the add-card picker.
 * These classes replace the inline aesthetic styles that the
 * no-inline-aesthetic-styles ESLint rule + audit_inline_style_aesthetics --gate
 * forbid (background / padding / paddingLeft / marginBottom / fontSize). They
 * read the same --m-* tokens the inline styles did, so the visual intent from
 * 134-UI-SPEC Surface 1 is preserved exactly (accent stripe, surface-bg rows,
 * small name font). Layout-structural keys (display/flex/gap) stay inline —
 * the gate permits them.
 * ────────────────────────────────────────────────────────────────────────── */

/* Section header: 2px left accent stripe (--m-accent) per UI-SPEC, the one
   element allowed an accent in this surface. */
.cb-cl-shelf-accent {
  border-left: 2px solid var(--m-accent);
  padding-left: var(--m-pad-sm);
  margin-bottom: var(--m-pad-sm);
}

/* Spacing wrapper for the empty-state line and the rows block — replaces the
   inline marginBottom that the gate forbids. */
.cb-cl-shelf-block {
  margin-bottom: var(--m-pad-sm);
}

/* Single shelf row — surface background + compact 2px/8px padding so each
   entry reads as a distinct row (never a tile grid). */
.cb-cl-shelf-row {
  background: var(--m-surface);
  padding: var(--m-pad-2xs) var(--m-pad-sm);
}

/* Card name in a shelf row — small body font (12px) so the row stays compact. */
.cb-cl-shelf-name {
  flex: 1;
  font-size: var(--mt-text-sm-size, 12px);
}
