/* ==================================================================
   Canonical component imports. These stylesheets are the single source
   for these components' CSS — app.css must not fork or hand-copy their
   rules. Any rule below that restyles a class one of these imports owns
   is a bug unless it is documented as site-chrome-only. grid.css is
   deliberately NOT imported (see CANONICAL-CSS-MIGRATION-CONTRACT.md,
   grid section): canonical grid is an unrelated data-grid component;
   this site's icon-thumbnail grid only needed a defensive rename.
   ================================================================== */
@import url("../../components_mockups/_examples/badge/badge.css");
@import url("../../components_mockups/_examples/breadcrumb/breadcrumb.css");
@import url("../../components_mockups/_examples/chip/chip.css");
@import url("../../components_mockups/_examples/field/field.css");
@import url("../../components_mockups/_examples/select/select.css");
@import url("../../components_mockups/_examples/tree/tree.css");
@import url("../../components_mockups/_examples/tile/tile.css");
@import url("../../components_mockups/_examples/card/card.css");
@import url("../../components_mockups/_examples/button/button.css");
@import url("../../components_mockups/_examples/toast/toast.css");
@import url("../../components_mockups/_examples/table/table.css");
@import url("../../components_mockups/_examples/link/link.css");
@import url("../../components_mockups/_examples/code/code.css");

/* ==================================================================
   App styles. Every color/size flows from --ui-* design tokens with
   dark-glass fallbacks. Component styling follows /components/docs.
   ================================================================== */
:root {
  /* Frequently used aliases (token, dark-glass fallback) */
  --sfc-app: var(--ui-page-fill, rgb(14, 15, 17));
  --sfc-panel: var(--ui-panel-fill, rgba(251, 253, 255, 0.05));
  --sfc-raised: var(--ui-panel-fill-raised, rgba(251, 253, 255, 0.1));
  --sfc-sunken: var(--ui-panel-fill-sunken, rgba(0, 0, 0, 0.2));
  --txt-primary: var(--ui-text-content-heading, rgb(251, 253, 255));
  --txt-body: var(--ui-text-content-body, rgb(210, 212, 214));
  --txt-muted: var(--ui-text-content-body-muted, rgb(147, 149, 151));
  --brd: var(--ui-field-stroke, rgb(117, 119, 121));
  --accent: var(--ui-button-standard-enabled-primary-fill, rgb(111, 189, 100));
  --accent-hover: var(--ui-button-standard-hover-fill, rgb(80, 157, 69));
  --focus-ring: var(--ui-focus-ring, 0 0 0 2px rgb(80, 157, 69));
  --dur: var(--ui-motion-duration-fast, 150ms);
  --ease: var(--ui-motion-ease-out, cubic-bezier(0, 0, 0, 1));
  --font: var(--ui-font-family-body, Inter), system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: var(--ui-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  /* Browser-local: makes currentColor icon sets legible on dark thumbs.
     No ui.* token exists for this yet. */
  --mono-invert: invert(92%) hue-rotate(180deg);
}
* { box-sizing: border-box; }
/* The UA's [hidden] { display: none } is a low-specificity rule that ANY
   author `display` declaration silently beats — so `el.hidden = true` on a
   flex/grid/inline-flex element (e.g. .icon-edit__usecase-remove) does
   nothing. Restate it here so the hidden property means hidden everywhere. */
[hidden] { display: none !important; }
/* html fills the viewport; body grows with its content. Using height:100%
   on body would pin its box to one viewport, which caps the sticky
   .app-header's containing block and unsticks it partway down long pages. */
html { height: 100%; }
body { min-height: 100%; }
body {
  margin: 0;
  /* Fallback: solid page color (ui.page.fallback) in case the gradient
     stack cannot render or theme CSS has not loaded. */
  background: var(--ui-page-fallback, rgb(0, 10, 0));
  /* LINQX page background: accent wash (ui.page.accent) layered over the
     base gradient (ui.page.background); flips per brand/mode/style axes;
     fixed so it anchors to the viewport and covers the full scroll height. */
  background:
    var(--ui-page-accent, radial-gradient(140% 100% at 100% 0%, rgba(80, 157, 69, 0.1), rgba(80, 157, 69, 0) 55%)),
    var(--ui-page-background, linear-gradient(135deg, rgb(0, 10, 0), rgb(2, 29, 0)));
  background-attachment: fixed;
  color: var(--txt-body);
  font-family: var(--font);
  font-size: var(--ui-font-body-font-size, 0.9375rem);
  line-height: var(--ui-font-body-line-height, 1.4);
}
button { font: inherit; color: inherit; }
/* Bare <a> color/hover removed: canonical .link owns that now (see
   CANONICAL-CSS-MIGRATION-CONTRACT.md link section). The generic
   focus-visible ring below stays — it isn't link-specific and canonical
   supplies no fallback for non-.link focusable elements. */
:is(a, button, input, select, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--ui-field-radius, 0.5rem);
}
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
.skip-link {
  position: absolute; left: 0.75rem; top: -3rem; z-index: 500;
  padding: 0.5rem 0.75rem; border-radius: var(--ui-field-radius, 0.5rem);
  background: var(--ui-menu-bg, rgb(37, 38, 40)); color: var(--txt-primary);
  transition: top var(--dur) var(--ease);
}
.skip-link:focus { top: 0.75rem; }

/* Glass panel chrome (.panel -> .card) now comes from the import
   (components_mockups/_examples/card/card.css) — confirmed byte-for-byte
   equivalent rim/shadow technique, so this was a pure fork, not a
   variant. No .card > * stacking reset is needed: canonical carries none,
   and the same absolute-positioned/masked ::before technique doesn't
   require one (see CANONICAL-CSS-MIGRATION-CONTRACT.md card section). */

/* ---------------------------- Layout ------------------------------ */
/* Header shell: three detached glass modules (ops-dashboard pattern) —
   brand + search + tools bookends, flush to the top screen edge, with
   gaps between them revealing the app background. See archetype shell:
   components/archetype shells/Operations Shell.standalone.html */
.app-header {
  display: flex; align-items: stretch;
  /* ui.header.height (components/examples/layout-grid/layout-grid.md) —
     single source of the header's height; sidebar/content offsets below
     derive from the same var so they never drift from it. */
  height: var(--ui-header-height, 4rem); gap: 0.875rem; margin: 0 0 0.75rem;
  position: sticky; top: 0; z-index: 200;
}
/* Solid app-background strip behind the detached tiles so scrolled
   content doesn't show through the bookend gaps while stuck. */
.app-header::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  /* Fallback: solid page color (ui.page.fallback), matching the body's
     fallback so the backing strip is seamless even before theme CSS loads. */
  background: var(--ui-page-fallback, rgb(0, 10, 0));
  /* Match the fixed body background (ui.page.background + ui.page.accent)
     exactly so the backing strip is seamless. */
  background:
    var(--ui-page-accent, radial-gradient(140% 100% at 100% 0%, rgba(80, 157, 69, 0.1), rgba(80, 157, 69, 0) 55%)),
    var(--ui-page-background, linear-gradient(135deg, rgb(0, 10, 0), rgb(2, 29, 0)));
  background-attachment: fixed;
}
/* Shared glass-tile + beveled rim, adapted from the ops-shell
   .glass-tile/.glass-rim onto this file's panel tokens/fallbacks
   (card doc: rim is a containers-only treatment). */
.hdr-tile {
  position: relative; overflow: hidden;
  background: var(--sfc-panel);
  -webkit-backdrop-filter: var(--ui-backdrop-filter-panel, blur(1rem));
  backdrop-filter: var(--ui-backdrop-filter-panel, blur(1rem));
  box-shadow:
    var(--ui-material-skin-inset, inset 0 1px 1px rgba(251, 253, 255, 0.1)),
    var(--ui-material-skin-crescent, inset 0 -1px 1px rgba(0, 0, 0, 0.15)),
    var(--ui-material-skin-halo, 0 1px 6px rgba(251, 253, 255, 0.16)),
    var(--ui-shadow-raised, 0 8px 32px rgba(0, 0, 0, 0.5));
}
.hdr-tile::before {
  content: ""; position: absolute; inset: 0; z-index: 0; border-radius: inherit; padding: var(--ui-material-rim-stroke-width);
  background: var(--ui-material-rim, linear-gradient(145deg,
    rgba(251, 253, 255, 0.35) 0%, rgba(251, 253, 255, 0.08) 38%,
    rgba(251, 253, 255, 0.04) 62%, rgba(251, 253, 255, 0.12) 100%));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
.hdr-tile > * { position: relative; z-index: 1; }

/* LEFT BOOKEND — flush top+left screen edges; only the free bottom-right
   corner rounds. Rim ring is shifted past both flush edges and clipped by
   the tile's own overflow:hidden (mirrors ops .brand-block). */
.brand-block {
  flex-shrink: 0; display: flex; align-items: center; gap: 0.625rem;
  padding: 0 1rem; border-radius: 0 0 0.875rem 0; text-decoration: none;
}
.brand-block::before { inset: -1px 0 0 -1px; }
/* MANIFOLD BY LINQX lockup, inlined artwork rather than live type. Source of truth is
   assets/logos/manifold/svg/Manifold_logo_currentColor.svg; the header inlines the same
   geometry so it can bind two tokens instead of the file's single-colour approximation.
   Height drives it: 2rem tall keeps the brandmark at the 32px it has always been, and
   the 201.3099 x 48 viewBox gives width for free. MANIFOLD and the mark ride
   currentColor; the endorsement line is bound to --txt-muted inside the markup.
   Type is outlined, so nothing here depends on Oxanium loading. */
.brand-block .brand-logo {
  height: 2rem; width: auto; aspect-ratio: 201.3099 / 48; flex-shrink: 0;
  color: var(--txt-primary); /* mark strokes + MANIFOLD are currentColor */
}

/* CENTER — flush top screen edge only; both bottom corners round. */
.search-block {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 0.5rem;
  padding: 0 1rem; border-radius: 0 0 0.875rem 0.875rem; color: var(--txt-muted);
}
.search-block::before { inset: -1px 0 0 0; }
.search-block input {
  flex: 1; min-width: 0; background: none; border: none; outline: none;
  color: var(--txt-primary); font: inherit;
}
.search-block input::placeholder { color: var(--ui-field-placeholder, var(--txt-muted)); }
.search-block input:focus-visible { box-shadow: none; }
.search-block .hint {
  color: var(--txt-muted); font-size: 0.75rem;
  border: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65));
  border-radius: var(--ui-chip-radius, 0.25rem); padding: 0.1rem 0.35rem;
}

/* RIGHT BOOKEND — mirror of the brand block: flush top+right screen
   edges; only the free bottom-left corner rounds. Holds the Style
   select + Mode and Brand button-bars (see components/docs/button-bar.md). */
/* Unlike the other .hdr-tile bookends, this row hosts a select menu
   that overflows its fixed 64px box; .hdr-tile's overflow:hidden would
   make it a scroll container and auto-scroll the open menu out of view. */
/* 2026-08-11: row shrinks and select values ellipsize (see
   .select__value-text) rather than the header overflowing the viewport;
   deliberately no @media breakpoint — see rules-layout.md #19.
   The four selects are mounted into plain wrapper divs (#style-sel-mount
   etc. — see mountSelect() in app.js); those wrappers, not .select
   itself, are the actual .header-tools flex items, so the shrinkable
   width + floor live there (width, not flex-basis, so the value is still
   honoured when an ancestor needs this row's own intrinsic size) and
   .select just fills whatever width its wrapper is given. min-width:0
   (the original value) let the row shrink past what its contents can
   actually render, so real content spilled past .header-tools' own box
   (overflow:visible let that bleed into body.scrollWidth instead of
   being contained). Content-derived keywords don't fix it: min-content,
   fit-content, and omitting the property all measured IDENTICAL in
   Chromium (861px) because they all resolve through the flex item's
   AUTOMATIC minimum size, which Chromium derives from children's
   SPECIFIED widths (the select wrappers' width:8.5rem), not their
   min-width:6rem shrink floor — so none of them let the row shrink past
   ~861px. An EXPLICIT min-width length bypasses the automatic-minimum
   path entirely, so it's the only mechanism that can set a real,
   content-informed floor while still letting the mounts sit at their
   preferred 8.5rem when there's room. 44rem (704px) is that floor,
   measured: four select mounts at their 6rem shrink floor + the mode
   and brand button bars' intrinsic widths + the row's five 0.75rem gaps
   + 1rem*2 padding — rounded up slightly so nothing spills at the edge. */
.header-tools {
  flex-shrink: 1; min-width: 44rem; display: flex; align-items: center; gap: 0.75rem;
  padding: 0 1rem; border-radius: 0 0 0 0.875rem;
  overflow: visible;
}
.header-tools::before { inset: -1px -1px 0 0; }
.header-tools label.ctl { display: inline-flex; align-items: center; gap: 0.4rem; color: var(--txt-muted); font-size: 0.8125rem; }
/* Reverted the width/min-width inversion: with .header-tools' own
   min-width now an explicit length (above), the row's floor no longer
   depends on the mounts' specified width, so width:8.5rem can go back to
   being the preferred size and min-width:6rem back to being each
   mount's real shrink floor as the row is squeezed toward 44rem. */
.header-tools #style-sel-mount,
.header-tools #vision-sel-mount,
.header-tools #motion-sel-mount,
.header-tools #contrast-sel-mount {
  width: 8.5rem; flex-shrink: 1; min-width: 6rem;
}
.header-tools .select { width: 100%; }

.shell {
  display: grid; grid-template-columns: minmax(240px, 320px) 1fr;
  gap: 0.75rem; padding: 0 0.75rem 0.75rem 0; align-items: start;
  min-height: 0;
}
.sidebar {
  /* The panel rim (::before) is absolutely positioned, so the sidebar
     itself must NOT be the scroll container: an abs-pos ring inside a
     scroller is sized to the scrollport but anchored to the CONTENT's top
     edge, so it scrolls away with the tree — its rounded bottom edge then
     sweeps through the middle of the list as a stray hairline box (and the
     panel's real edges lose their rim). The tree scrolls instead; the
     panel — and its rim — stay put. */
  display: flex; flex-direction: column;
  padding: 0.75rem 0.5rem; overflow: hidden;
  /* Offset below the sticky header: ui.header.height + 0.75rem gap. */
  max-height: calc(100vh - var(--ui-header-height, 4rem) - 1.5rem);
  position: sticky; top: calc(var(--ui-header-height, 4rem) + 0.75rem);
  /* Flush to the viewport's left edge: only the free right corners round. */
  border-radius: 0 var(--ui-card-radius, 1rem) var(--ui-card-radius, 1rem) 0;
}
.sidebar > .tree { overflow: auto; min-height: 0; }
/* Shift the rim ring past the flush left edge so no border paints there;
   the off-viewport strip is clipped by the viewport (mirrors .brand-block). */
.sidebar::before { inset: 0 0 0 calc(-1 * var(--ui-material-rim-stroke-width)); }
/* Second grid column: breadcrumb stacked above the main panel. Deliberately
   bare — no fill, no rim, no ui.backdrop-filter.panel. Adding a backdrop
   filter here would make this a backdrop root again and re-suppress the
   breadcrumb pills' frost (see the nesting note in browse.html). */
.content-col { display: grid; grid-template-rows: auto 1fr; gap: 0.5rem; min-width: 0; min-height: 0; }
.content { padding: 1.25rem 1.5rem; min-width: 0; margin-bottom: 0.75rem; scroll-margin-top: calc(var(--ui-header-height, 4rem) + 0.75rem); }
@media (max-width: 900px) {
  .shell { grid-template-columns: 1fr; }
  .sidebar { position: static; max-height: 40vh; }
}

/* --------------------------- Controls ----------------------------- */
/* .btn chrome (base/primary/secondary/neutral/prominent/disabled) now comes
   from the import (components_mockups/_examples/button/button.css). The
   old single-element .btn here painted fill straight onto .btn itself;
   canonical scopes fill to .btn__front inside a three-layer __back/__front/
   __content structure the current markup doesn't have yet — see the
   contract's button hazard note. Nothing kept site-local: none identified. */

/* Button bar (components/docs/button-bar.md): stroke-outlined segment
   group — no padded track. Outer edges carry the full stroke width, shared
   inner edges a hairline divider each; selection is surfaced by the sliding
   indicator (overshoot ease). Sizes: default + compact modifier. */
.button-bar {
  display: inline-flex;
  position: relative;
  isolation: isolate;
}
.button-bar__segment {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  height: var(--ui-button-default-height, 3rem);
  min-width: 75px;
  padding: 0 var(--ui-button-default-pad-x, 1rem);
  font-family: var(--ui-button-default-text-font-family, inherit);
  font-size: var(--ui-button-default-text-font-size, 1rem);
  font-weight: var(--ui-button-default-text-font-weight, 400);
  line-height: var(--ui-button-default-text-line-height, 1.5);
  color: var(--ui-button-standard-secondary-content, rgb(251, 253, 255));
  background: transparent; cursor: pointer;
  border: 0 solid var(--ui-button-standard-enabled-stroke, rgb(111, 189, 100));
  border-top-width: var(--ui-button-default-stroke-width, 2px);
  border-bottom-width: var(--ui-button-default-stroke-width, 2px);
  border-left-width: var(--ui-segment-divider-width, 1px);
  border-right-width: var(--ui-segment-divider-width, 1px);
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.button-bar__segment--left {
  border-left-width: var(--ui-button-default-stroke-width, 2px);
  border-top-left-radius: var(--ui-button-default-radii, 0.5rem);
  border-bottom-left-radius: var(--ui-button-default-radii, 0.5rem);
}
.button-bar__segment--right {
  border-right-width: var(--ui-button-default-stroke-width, 2px);
  border-top-right-radius: var(--ui-button-default-radii, 0.5rem);
  border-bottom-right-radius: var(--ui-button-default-radii, 0.5rem);
}
.button-bar__segment[aria-checked="true"] {
  color: var(--ui-button-standard-primary-content, rgb(0, 0, 0));
}
/* Deferred segment fill, ported from the design-system button-bar component
   (components/examples/button-bar) — this file previously had no selected
   fill at all, so the indicator was the only thing painting the selected
   segment. The selected segment's OWN background is the declarative resting
   state: present on a no-JS / pre-hydration render (aria-checked alone
   drives it), but it must NEVER preempt the indicator's arrival on a live,
   JS-driven switch. Gating on `:not(.is-moving)` (a class only buttonBar()
   adds, only for the duration of a traveling indicator) gives both for
   free — nothing to gate with no JS, and on a live click the fill drops out
   the instant .is-moving is added and re-applies the instant settle()
   removes it, which is when the indicator finishes its transform transition
   (or the 450ms safety net fires). On the reduced-motion path .is-moving is
   never added, so the fill commits immediately — there is no travel. */
.button-bar:not(.is-moving) .button-bar__segment[aria-checked="true"] {
  background: var(--ui-button-standard-enabled-primary-fill, rgb(111, 189, 100));
}
/* Disabled + selected fill: same deferred-fill gate as the enabled rule
   above, plus `:disabled` and the extra `.button-bar` ancestor class so the
   two tie exactly on specificity (four class-level selectors each) — placed
   after, so source order breaks the tie in the disabled rule's favor and a
   disabled bar's selected segment can't pick up the live brand fill. This
   rule IS the disabled fill (see the disabled block below): per the ruled
   design, only the SELECTED disabled segment takes disabled-fill — the
   unselected ones stay transparent and shift content/stroke only. */
.button-bar .button-bar__segment:disabled[aria-checked="true"] {
  background: var(--ui-button-standard-disabled-fill);
}
/* :not(:disabled) per the component — a disabled segment must not pick up
   the live hover fill. Belt-and-braces given .button-bar--disabled's
   pointer-events: none, but required for the per-segment disabled case,
   where the rest of the bar stays live and hoverable. */
.button-bar__segment:not(:disabled):hover {
  background: var(--ui-button-standard-hover-fill, rgb(80, 157, 69));
  color: var(--ui-button-standard-primary-content, rgb(0, 0, 0));
}
.button-bar__segment:focus-visible {
  box-shadow: var(--focus-ring);
}
.button-bar__indicator {
  position: absolute; z-index: 0; top: 0; bottom: 0; left: 0;
  background: var(--ui-segment-indicator-fill, rgb(111, 189, 100));
  border-radius: var(--ui-segment-indicator-radii, 0.5rem);
  /* Travel overshoots the destination edge and settles back (Figma spec);
     only transform overshoots, width eases normally. */
  transition: transform var(--ui-motion-duration-moderate, 300ms) var(--ui-motion-ease-overshoot, cubic-bezier(0.68, -0.55, 0.265, 1.55)),
    width var(--ui-motion-duration-moderate, 300ms) var(--ui-motion-ease-out, cubic-bezier(0, 0, 0, 1)),
    border-radius var(--dur) var(--ease);
}
/* The indicator fills the selected segment completely: only the group's
   outer corners stay rounded, matching the segment under it. */
.button-bar[data-sel="left"] .button-bar__indicator {
  border-top-right-radius: 0; border-bottom-right-radius: 0;
}
.button-bar[data-sel="middle"] .button-bar__indicator { border-radius: 0; }
.button-bar[data-sel="right"] .button-bar__indicator {
  border-top-left-radius: 0; border-bottom-left-radius: 0;
}
/* Compact size (ui.button.compact.*) */
.button-bar--compact .button-bar__segment {
  height: var(--ui-button-compact-height, 2rem);
  min-width: 50px;
  padding: 0 var(--ui-button-compact-pad-x, 0.75rem);
  font-family: var(--ui-button-compact-text-font-family, inherit);
  font-size: var(--ui-button-compact-text-font-size, 0.75rem);
  font-weight: var(--ui-button-compact-text-font-weight, 400);
  line-height: var(--ui-button-compact-text-line-height, 1.5);
  border-top-width: var(--ui-button-compact-stroke-width, 2px);
  border-bottom-width: var(--ui-button-compact-stroke-width, 2px);
}
.button-bar--compact .button-bar__segment--left {
  border-left-width: var(--ui-button-compact-stroke-width, 2px);
  border-top-left-radius: var(--ui-button-compact-radii, 0.5rem);
  border-bottom-left-radius: var(--ui-button-compact-radii, 0.5rem);
}
.button-bar--compact .button-bar__segment--right {
  border-right-width: var(--ui-button-compact-stroke-width, 2px);
  border-top-right-radius: var(--ui-button-compact-radii, 0.5rem);
  border-bottom-right-radius: var(--ui-button-compact-radii, 0.5rem);
}
.button-bar--compact .button-bar__indicator {
  border-radius: var(--ui-segment-indicator-radii-compact, 0.5rem);
}
/* While traveling, the indicator is a fully rounded pill-corner rect; the
   position-aware squaring applies once it settles after the overshoot.
   These come last so they beat the data-sel rules during motion. */
.button-bar.is-moving .button-bar__indicator {
  border-radius: var(--ui-segment-indicator-radii, 0.5rem);
}
.button-bar--compact.is-moving .button-bar__indicator {
  border-radius: var(--ui-segment-indicator-radii-compact, 0.5rem);
}
/* No indicator (or fill) motion until the initial placement has painted —
   the default selection must appear already in place and already filled,
   not slide in / fade in on load. The segment rule is the second half of
   this gate (matching the component): without it the deferred fill above
   would fade in on first paint. */
.button-bar:not(.is-ready) .button-bar__indicator { transition: none; }
.button-bar:not(.is-ready) .button-bar__segment { transition: none; }
/* Disabled state (touch-only component pages force this bar to a
   non-interactive Touch-selected Density control). Reconciled to the
   design-system button-bar component (components/examples/button-bar):
   these rules key off each segment's own native `:disabled` rather than a
   bar-level modifier class, which is what makes BOTH ruled levels work off
   one rule set — bar-level (buttonBar(disabled=true) stamps `disabled` on
   every segment) and per-segment (one inert segment, the rest live). The
   `.button-bar--disabled` class survives only as the pointer-events guard;
   it no longer carries any appearance.

   Content/stroke shift on every disabled segment; the FILL does not. Per
   the ruling, only the selected disabled segment takes disabled-fill (that
   rule lives with its enabled sibling above, so the two tie on specificity
   and source order settles it) and unselected disabled segments stay
   transparent. app.css previously painted disabled-fill across ALL
   segments, which is what diverged. The measured DISABLED_BONDED pair
   (disabled-content on disabled-fill: LINQX 3.36 light / 3.27 dark, XIQ
   3.35 light / 3.28 dark) is the selected segment, so it is unchanged. */
.button-bar--disabled { pointer-events: none; }
/* Placed after the [aria-checked="true"] colour rule on purpose: both sit
   at equal specificity (one class + one pseudo-class/attribute), so a
   disabled+selected segment only resolves to disabled-content because this
   rule comes later in source. */
.button-bar__segment:disabled {
  border-color: var(--ui-button-standard-disabled-stroke);
  color: var(--ui-button-standard-disabled-content);
  cursor: not-allowed;
}
/* The indicator can't sit brand-coloured under an inert segment. :has()
   keys off the SELECTED segment's own :disabled state, so this one rule
   covers bar-level and per-segment alike with no modifier class and no JS.
   ui.segment.disabled-indicator-fill (-> action.disabled.fill) is the token
   minted for exactly this; app.css was borrowing disabled-stroke. */
.button-bar:has(.button-bar__segment:disabled[aria-checked="true"]) .button-bar__indicator {
  background: var(--ui-segment-disabled-indicator-fill);
}
/* Reduced-motion tier (docs/taxonomy-motion.md §3/§6), aligned to the
   design-system button-bar component (components/examples/button-bar):
   the indicator's slide is a structural/feedback, kinetic (transform +
   width) effect, which the reduced tier forces to its tonal equivalent
   rather than dropping outright — the transform/width travel (with its
   overshoot ease) is replaced by a brief opacity crossfade. `.is-switching`
   (JS-driven, see buttonBar()) drops the indicator to 0 opacity while it
   is repositioned instantly underneath, then fades it back in. There is
   no `data-motion` attribute wired yet (per §6), so this is gated on the
   OS `prefers-reduced-motion` media query; a future data-motion hook can
   slot in alongside it. */
@media (prefers-reduced-motion: reduce) {
  .button-bar__indicator {
    transition: opacity var(--ui-motion-duration-fast, 150ms) var(--ui-motion-ease-standard, cubic-bezier(0.2, 0, 0, 1));
  }
  .button-bar__indicator.is-switching { opacity: 0; }
}

select.ctl-select {
  background: var(--ui-field-bg, rgba(251, 253, 255, 0.05));
  color: var(--txt-primary); border: 1px solid var(--brd);
  border-radius: var(--ui-field-radius, 0.5rem); padding: 0.3rem 0.5rem;
  font: inherit; font-size: 0.8125rem;
}

/* Badge chrome (.badge + intent modifiers) now comes from the import
   (components_mockups/_examples/badge/badge.css). Keep site-local: none —
   badge has no site-only states. */

/* ------------------------------ Tree ------------------------------ */
/* Tree chrome (.tree/.tree__node/.tree__row/.tree__twist/etc.) now comes
   from the import (components_mockups/_examples/tree/tree.css). Only the
   doc/link muted-label treatment below has no canonical equivalent
   (tree.md defines no doc/link row distinction) — kept site-local,
   renamed onto canonical's own .tree__row/.tree__title/.tree__node parts. */
.tree__row.is-doc .tree__title, .tree__row.is-link .tree__title { color: var(--txt-muted); }
.tree__row.is-doc:hover .tree__title, .tree__row.is-link:hover .tree__title,
.tree__node[aria-selected="true"] > .tree__row.is-doc .tree__title { color: inherit; }

/* --------------------------- Breadcrumbs -------------------------- */
/* .breadcrumb / .breadcrumb__* is now the imported canonical component
   (components_mockups/_examples/breadcrumb/breadcrumb.css) — the former
   hand-rolled .crumbs/.sep/.here fork and its layout rule lived here;
   canonical owns layout (margin, list gap, tuck) as well as appearance,
   so nothing site-local remains to keep. */

/* ------------------------------ Grid ------------------------------ */
.section-head {
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
  margin: 1.5rem 0 0.75rem;
}
.section-head h2 {
  margin: 0; color: var(--txt-primary);
  font-family: var(--ui-font-heading-3-font-family, inherit), var(--font);
  font-size: var(--ui-font-heading-3-font-size, 1.125rem);
  font-weight: var(--ui-font-heading-3-font-weight, 600);
}
.section-head .meta { color: var(--txt-muted); font-size: 0.8125rem; }
/* Clearable active-filter pill (icon Family chips, first user — see
   renderFamilyFilterPill). Neutral badge palette: informational "this
   filter is on" register, not a status color. Reusable across asset types
   as more filterable dimensions grow their own dismissible pills. */
.filter-pill {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.3rem 0.3rem 0.3rem 0.6rem;
  border-radius: var(--ui-badge-radii, 9999px);
  border: var(--ui-badge-stroke-width, 1px) solid var(--ui-badge-neutral-stroke, rgb(89, 91, 92));
  background: var(--ui-badge-neutral-fill, rgb(89, 91, 92));
  color: var(--ui-badge-neutral-content, rgb(210, 212, 214));
  font-size: 0.8125rem;
}
/* 24x24px CSS-pixel hit target (WCAG 2.2 AA 2.5.8) — the box grows via
   padding around the glyph rather than scaling the × up, so the visible
   symbol keeps its optical size inside the enlarged target. */
.filter-pill__clear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; padding: 0;
  border: none; border-radius: 50%;
  background: none; color: inherit; font-size: 0.9rem; line-height: 1;
  cursor: pointer;
}
/* No dedicated badge hover token exists in the built tokens (checked
   design-system-tokens/dist/css/tokens.css — --ui-badge-* has no -hover
   variant); mix a fraction of the pill's own content color into
   transparent instead of hardcoding an rgb/black overlay. */
.filter-pill__clear:hover { background: color-mix(in srgb, currentColor 15%, transparent); }
.divider {
  border: none; height: var(--ui-divider-stroke-width, 2px); margin: 1.25rem 0;
  background: var(--ui-divider-neutral);
}
/* Icon grid toolbar (Phase 3): two rows of labeled button-bars. Field
   layout (label above control) reuses the .bg-field recipe already used for
   the file-preview stage's background-surface toggle — see that rule's
   selector list further down. */
.icon-grid-toolbar { display: flex; flex-direction: column; gap: 0.85rem; margin: 0 0 1.35rem; }
.tb-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 1.5rem; }
/* Grouped grid (Group By != None): header row + divider, no filled section
   band (containers-only rim rule — a stacked grey surface here would
   violate it). Tiles sit directly on the page surface below the divider. */
.icon-grid-group { margin: 1.5rem 0; }
.icon-grid-group__head {
  display: flex; align-items: center; gap: 0.5rem; width: 100%;
  padding: 0.3rem 0; border: none; background: none; cursor: pointer;
  text-align: left; color: var(--txt-primary); font: inherit;
}
.icon-grid-group__twist {
  width: 1em; flex: none; color: var(--txt-muted);
  transition: transform var(--dur) var(--ease);
}
.icon-grid-group__head[aria-expanded="true"] .icon-grid-group__twist { transform: rotate(90deg); }
.icon-grid-group__title {
  font-family: var(--ui-font-heading-3-font-family, inherit), var(--font);
  font-size: var(--ui-font-heading-3-font-size, 1.125rem);
  font-weight: var(--ui-font-heading-3-font-weight, 600);
}
.icon-grid-group__count { color: var(--txt-muted); font-size: 0.8125rem; margin-left: 0.2rem; }
/* Renamed from bare .grid: canonical grid.css (an unrelated data-grid/
   spreadsheet component) also targets a bare .grid — see the contract's
   grid section. This is the icon-thumbnail layout, not that component. */
.icon-grid {
  display: grid; gap: 0.6rem;
  grid-template-columns: repeat(auto-fill, minmax(var(--cell, 108px), 1fr));
}
/* Virtualized icon grid (large lists): a full-height spacer holds the scroll
   extent while only the rows intersecting the viewport are materialized into
   the absolutely-positioned inner .icon-grid, translated into place. See
   createVirtualGrid(). */
.icon-vgrid { position: relative; width: 100%; }
.icon-vgrid > .icon-vgrid__grid { position: absolute; top: 0; left: 0; right: 0; will-change: transform; }
/* Inside a virtual grid the DOM only ever holds the ~visible rows, so the
   .tile content-visibility hint gives nothing — and worse, it swaps in the
   110px intrinsic height for any tile that scrolls off, making grid rows
   jump height mid-scroll and breaking the uniform-row-height math (blank
   trailing row). Force real heights here; createVirtualGrid pins
   grid-auto-rows to the measured row height so every row is deterministic. */
.icon-vgrid__grid > .tile { content-visibility: visible; }
/* Tile, grid orientation — canonical chrome comes from the import
   (components_mockups/_examples/tile/tile.css); only site-local
   extensions with no canonical home remain below, re-scoped from the old
   .cell class onto .tile per the contract's tile section. */
.tile img { width: var(--thumb, 32px); height: var(--thumb, 32px); object-fit: contain; }
[data-mode="dark"] .tile.mono img { filter: var(--mono-invert); }
/* MIGRATION: review — content-visibility/contain-intrinsic-size has no
   canonical tile equivalent; kept so the virtualized icon grid
   (createVirtualGrid) doesn't jank when off-screen tiles are skipped. */
.tile { content-visibility: auto; contain-intrinsic-size: var(--cell, 108px) 110px; }
/* Single status slot, top-right corner only (tile.md "Status flag"): every
   condition (avoid/duplicate/no-intent/preferred) renders as the same
   compact badge pill, so a tile never carries two status marks in
   different corners or mixes a bare dot with a labeled pill (revision:
   "preferred" is a real success-badge pill now, not a dot — see
   gridStatusFlag). Needs `!important` on position/z-index because
   `.tile.tile--floating > *` (below) matches every direct child at equal
   specificity and, being later in source order, was winning the cascade —
   it flattened the flag from absolute (corner-pinned) to relative
   (in-flow). No canonical home (tile.md's only sanctioned corner slot is
   status-indicator) — kept site-local, re-scoped from .cell to .tile. */
.tile .flag { position: absolute !important; top: 0.3rem; right: 0.3rem; z-index: 1 !important; }
/* Fakes a smaller badge footprint — canonical badge.md's .badge--compact
   size modifier is the eventual replacement (see the contract's badge
   section) once the markup carries it; left as an inline override here. */
.tile .flag.badge { height: 1.05rem; min-width: 0; padding: 0 0.4rem; font-size: 0.6rem; }
/* Template-set-only tile anatomy: two quiet corner slots, independent of the
   single .flag status slot above (gridStatusFlag/.flag stays exactly as-is
   for every non-template set — see iconCell). TL carries the corner-suffix
   chip (BR/TL/TR/BL) when the template has one; otherwise TL is free and
   carries the size label instead. TR stacks the no-intent warning badge
   above the size label when a corner chip pushed the size label there and
   the template also lacks intent (badge alone otherwise). Same !important
   corner-pin rationale as .flag above. No canonical home — kept
   site-local, re-scoped from .cell to .tile. */
.tile .corner-tl, .tile .corner-tr {
  position: absolute !important; top: 0.3rem; z-index: 1 !important;
  display: flex; flex-direction: column; gap: 0.2rem;
}
.tile .corner-tl { left: 0.3rem; align-items: flex-start; }
.tile .corner-tr { right: 0.3rem; align-items: flex-end; }
/* Corner-suffix chip: neutral badge tokens — quieter than the warning/
   error/success status pills, per the tile anatomy spec. */
.tile .corner-chip {
  display: inline-flex; align-items: center; height: 1.05rem;
  padding: 0 0.4rem; font-size: 0.6rem; font-weight: 600; line-height: 1;
  border-radius: var(--ui-badge-radii, 9999px);
  border: var(--ui-badge-stroke-width, 0.0625rem) solid var(--ui-badge-neutral-stroke, rgb(89, 91, 92));
  background: var(--ui-badge-neutral-fill, rgb(89, 91, 92));
  color: var(--ui-badge-neutral-content, rgb(210, 212, 214));
}
/* Canvas-size label: plain muted tile-label text, quieter still — it never
   competes with the neutral chip or the warning badge it may sit under. */
.tile .corner-size {
  font-family: var(--ui-tile-label-text-font-family, inherit);
  font-size: 0.6rem; line-height: 1; color: var(--txt-muted);
}
.tile .corner-tr .badge { height: 1.05rem; min-width: 0; padding: 0 0.4rem; font-size: 0.6rem; }
/* Two stacked TR items (no-intent badge + size label, corner-suffixed
   templates only — non-corner templates put their size label in TL, so TR
   there is a lone badge) need more headroom above the glyph than the
   single-line case the base --tile-pad-top ramp was tuned for — bumped by
   one dimension step, token-driven, scoped via the adjacent-sibling
   combinator to only the tiles where a badge actually precedes a size label
   (never a lone badge). Kept site-local per the contract (no canonical
   counterpart regardless of which corner-slot option is chosen). */
.tile:has(.corner-tr .badge + .corner-size) {
  padding-top: calc(var(--tile-pad-top, var(--ui-tile-pad-top, 0.5rem)) + var(--ui-dimension-m, 1rem));
}
/* Excludes .flag/.corner-tl/.corner-tr (the status/corner slots) — each sets
   its own position/z-index above with !important so they stay corner-pinned
   regardless of this rule's cascade order relative to their own. Canonical
   .tile--floating supplies the shadow + rim; this stacking reset for the
   corner-slot extension has no canonical equivalent and stays site-local. */
.tile.tile--floating > *:not(.flag):not(.corner-tl):not(.corner-tr) { position: relative; z-index: 1; }
.foldergrid { display: grid; gap: 0.6rem; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
/* MIGRATION: review — the contract's tile section renames .foldercard to
   .tile.tile--list and .foldercard .count to .tile__meta, but this pass's
   explicit deletion list did not include it, so it is left untouched
   pending confirmation this list-orientation tile is in scope too. */
/* Tile, list orientation (components/examples/tile/tile.md) */
.foldercard {
  display: flex; align-items: center; gap: var(--ui-tile-gap, 0.6rem);
  padding: var(--ui-tile-pad-top, 0.5rem) var(--ui-tile-pad-x, 0.9rem) var(--ui-tile-pad-bottom, 0.6rem);
  background: var(--sfc-raised); border-radius: var(--ui-tile-radii, 0.5rem);
  border: 1px solid transparent; text-decoration: none; color: var(--txt-primary);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.foldercard:hover {
  border-color: var(--ui-tile-stroke-hover, rgb(111, 189, 100));
  background: var(--ui-tile-fill-hover, rgba(111, 189, 100, 0.2));
  color: var(--txt-primary);
}
.foldercard .count { margin-left: auto; color: var(--txt-muted); font-size: 0.75rem; }
.filelist { list-style: none; margin: 0; padding: 0; }
.filelist li { border-bottom: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65)); }
.filelist li:last-child { border-bottom: none; }
/* MIGRATION: review — the contract flags .filelist a -> .link.link--inline
   as inference only ("no documented whole-row-is-the-link pattern"; a
   title-scale .link--title.link--h4 may fit file names better), and asks
   for sign-off before stage 2 locks it in. Left fully as-is (color and
   decoration included) pending that decision, unlike the confidently
   mapped .doc-content a below. Breadcrumb links are no longer part of
   this comparison — they moved off .link entirely onto the canonical
   .breadcrumb__link component (see the Breadcrumbs section above). */
.filelist a {
  display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 0.4rem;
  text-decoration: none; color: var(--txt-body);
  border-radius: var(--ui-field-radius, 0.5rem);
  transition: background var(--dur) var(--ease);
}
.filelist a:hover { background: var(--ui-menu-item-hover, rgba(111, 189, 100, 0.2)); color: var(--txt-primary); }
.filelist .ext {
  flex: none; min-width: 3.2rem; text-align: center; padding: 0.15rem 0.4rem;
  border-radius: var(--ui-chip-radius, 0.25rem);
  background: var(--sfc-sunken); color: var(--txt-muted);
  font-family: var(--mono); font-size: 0.7rem; text-transform: uppercase;
}
.filelist .sz { margin-left: auto; color: var(--txt-muted); font-size: 0.78rem; font-family: var(--mono); }
.more { margin: 0.9rem auto 0; display: block; }

/* ----------------------------- Preview ---------------------------- */
.pv-head { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1rem; }
/* Right-aligns the "Edit curation" trigger in the title row — same
   margin-left:auto pattern as .foldercard .count / .na-link elsewhere. */
.pv-head__edit-curation { margin-left: auto; }
/* Copy-the-name affordance (icon pages only — see showFile). ONE button holds
   the heading text plus the copy glyph, so the whole title is the target and
   the hover box is the whole title. It isn't a .btn: it has to keep the h1's
   exact type and colour so the title still reads as a title, so it resets the
   UA button box and inherits font + colour wholesale, taking only the neutral
   button's hover/press states. Resting state is deliberately unadorned — no
   border, no fill — so an untouched page still looks like a heading. */
.pv-head__title { display: inline-flex; }
.pv-head__name {
  display: inline-flex; align-items: center; gap: 0.4rem;
  margin: -0.15rem -0.4rem; padding: 0.15rem 0.4rem;
  border: 1px solid transparent; border-radius: var(--ui-button-compact-radii, 0.5rem);
  background: none; cursor: pointer;
  font: inherit; color: inherit; letter-spacing: inherit; text-align: left;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.pv-head__name > svg {
  flex: none; color: var(--txt-muted);
  width: var(--ui-icon-size-compact, 1.25rem);
  height: var(--ui-icon-size-compact, 1.25rem);
}
/* Resting stays bare heading (no fill, no stroke) — the button only appears on
   hover, and when it does it wears the same neutral-secondary hover state as
   .btn.neutral so the two copy triggers on this page read as one control type.
   Border width is declared transparent at rest so revealing the stroke can't
   shift the title's position. */
.pv-head__name:hover {
  background: var(--ui-button-neutral-hover-fill, var(--sfc-raised));
  border-color: var(--ui-button-neutral-enabled-stroke, var(--brd));
  color: var(--ui-button-neutral-primary-content, var(--txt-primary));
}
.pv-head__name:active {
  background: var(--ui-button-neutral-pressed-fill, var(--sfc-sunken));
  color: var(--ui-button-neutral-primary-content, var(--txt-primary));
  transform: translateY(var(--ui-button-compact-press-inset, 1px));
}
.pv-head__name:hover > svg, .pv-head__name:active > svg { color: inherit; }
.pv-head h1 {
  margin: 0; color: var(--txt-primary); word-break: break-all;
  font-family: var(--ui-font-heading-2-font-family, inherit), var(--font);
  font-size: var(--ui-font-heading-2-font-size, 1.375rem);
  font-weight: var(--ui-font-heading-2-font-weight, 700);
}
/* Actions row: Copy URL (primary) left, Copy SVG source (secondary,
   "Designer use only") right-aligned — see asset-browser-v2-build-plan.md
   Phase 4. */
/* Bottom-align both action groups: the designer group's label sits above
   its button, making that column taller than the primary group's — without
   flex-end here, the two buttons land on different baselines. */
.pv-actions { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin: 0.75rem 0 1rem; }
.pv-actions__primary { display: flex; flex-direction: column; gap: 0.4rem; }
.pv-actions__designer { display: flex; flex-direction: column; align-items: flex-end; gap: 0.35rem; }
.pv-actions__designer-label {
  color: var(--ui-button-warning-enabled-stroke, rgb(255, 144, 91)); font-size: 0.7rem; font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase;
}
/* Restricted-set Copy URL: disabled button + a plain error-text message
   (rejected/templates icons — Phase 4). Plain text, not a filled badge —
   --ui-field-label-error is the design system's standalone error-foreground
   token (same role it plays for field/select validation text). */
.pv-restricted-msg {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  margin: -0.5rem 0 1.1rem;
  color: var(--ui-field-label-error, rgb(255, 121, 135));
  font-size: 0.8125rem; font-weight: 600;
}
/* Missing-intent warning callout, above the metadata panels. margin-top
   is the 4px dimension token (revision: Jonathen wanted a small gap above
   the callout, separating it from the button bar immediately above it —
   first spec'd 8px, then revised down to 4px). */
.pv-callout {
  display: flex; align-items: flex-start; gap: 0.6rem; flex-wrap: wrap;
  margin: var(--ui-dimension-2-xs, 4px) 0 1.1rem; padding: 0.6rem 0.9rem;
  border-radius: var(--ui-field-radius, 0.5rem);
  background: var(--ui-badge-warning-fill, rgb(240, 115, 48));
  border: var(--ui-badge-stroke-width, 1px) solid var(--ui-badge-warning-stroke, rgb(255, 213, 194));
  color: var(--ui-badge-warning-content, rgb(0, 0, 0));
  font-size: 0.8125rem;
}
.pv-callout p { margin: 0; flex: 1 1 auto; min-width: 0; }
.pv-callout a { color: inherit; font-weight: 700; text-decoration: underline; }
/* .pv-callout code duplicated the canonical bare code{} rule (import
   above) with only a differing font-size — dropped. */
.stage {
  display: flex; align-items: center; justify-content: center;
  min-height: 240px; padding: 2rem; overflow: auto;
  border-radius: var(--ui-field-radius, 0.5rem);
  background: var(--sfc-sunken);
  border: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65));
}
/* Contrast surface flips against the active mode: light panel in dark
   mode, dark panel in light mode. */
.stage.bg-contrast { background: rgb(244, 246, 248); color: rgb(20, 24, 28); }
[data-mode="light"] .stage.bg-contrast { background: rgb(20, 24, 28); color: rgb(244, 246, 248); }
.stage.bg-checker {
  background: repeating-conic-gradient(rgba(127,127,127,0.25) 0% 25%, transparent 0% 50%) 0 0 / 20px 20px;
}
/* Opaque app surface: the solid canvas color, no translucency (unlike the
   default sunken surface, which lets the page gradient show through). */
.stage.bg-app { background: var(--sfc-app); }
.stage svg { color: currentColor; }
/* Icon size ramp: bottom-aligned icons, exactly ui.dimension.m (8px) apart;
   labels center under each icon and may overhang narrow ones. */
.svg-ramp {
  display: flex; align-items: flex-end; justify-content: center;
  flex-wrap: wrap; gap: var(--ui-dimension-m, 8px);
}
.svg-spec {
  display: flex; flex-direction: column; align-items: center;
  gap: 0.4rem; flex: none;
}
.svg-spec svg, .svg-spec img { display: block; flex: none; }
.svg-spec .lbl {
  font-family: var(--mono); font-size: 0.7rem; line-height: 1;
  white-space: nowrap; opacity: 0.7;
}
.stage:not(.bg-contrast) { color: var(--txt-primary); }
.stage img.mono-img { filter: none; }
[data-mode="dark"] .stage:not(.bg-contrast) img.mono-img { filter: var(--mono-invert); }
[data-mode="light"] .stage.bg-contrast img.mono-img { filter: var(--mono-invert); }
.stage pre {
  margin: 0; width: 100%; max-height: 480px; overflow: auto;
  font-family: var(--mono); font-size: 0.8125rem; color: var(--txt-body);
  white-space: pre-wrap; word-break: break-word;
}
.stage-tools { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 0.6rem 0 0; }
.bg-field, .tb-field { display: flex; flex-direction: column; gap: 0.35rem; }
.bg-field__label, .tb-field__label { color: var(--txt-muted); font-size: 0.8125rem; }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 0.35rem 1rem; margin: 0; font-size: 0.875rem; }
.kv dt { color: var(--txt-muted); }
.kv dd { margin: 0; overflow-wrap: break-word; color: var(--txt-body); }
/* .kv dd code duplicated the canonical bare code{} rule — dropped. */
.chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }
/* .chip chrome now comes from the import
   (components_mockups/_examples/chip/chip.css). */
/* Detail-page metadata panels (Icon Details / Usage / File Info): the
   containers-only rim treatment from examples/card, via the shared .panel
   recipe (see .stat-tile/.needs-attention/.doc-content for the same
   "panel + layout modifier class" split). */
/* Icon Details + Usage share a row; File Info stays full-width below it
   (rendered as a separate sibling in showFile). Stacks under the same
   narrow-viewport breakpoint the shell itself collapses at. */
.meta-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 1rem; }
.meta-row .meta-card { margin-top: 0; }
@media (max-width: 700px) {
  .meta-row { grid-template-columns: 1fr; }
}
.meta-card { padding: 1rem 1.1rem; margin-top: 1rem; }
.meta-card h3 {
  margin: 0 0 0.7rem; color: var(--txt-primary); font-size: 0.95rem;
  font-family: var(--ui-font-heading-3-font-family, inherit), var(--font);
  font-weight: var(--ui-font-heading-3-font-weight, 600);
}
.meta-card__sub { margin: 0.9rem 0 0.4rem; color: var(--txt-primary); font-size: 0.85rem; font-weight: 600; }
.pv-url-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.pv-url-row .pv-url { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.usecases { margin: 0.4rem 0 0; padding-left: 1.1rem; font-size: 0.85rem; }
.usecases li { margin-bottom: 0.25rem; }
.usecases__source { margin-left: 0.4rem; font-size: 0.6875rem; vertical-align: middle; }
.note-empty { color: var(--txt-muted); font-style: italic; }

/* -------------------- Curate mode: icon edit card ------------------- */
/* Localhost-only inline editor for icon-registry.json overlay entries
   (see scripts/dev-preview-server.mjs --curate + renderIconEditCard in
   app-fresh.js). Swaps in for the read-only .meta-row; reuses .panel
   .meta-card for its own chrome and the .cmp-search/.glossary-filter__input
   field recipe (ui.field.* tokens) for its controls — no new field tokens. */
/* Stacked layout (2026-07-30 mockup, revised): Status and the five CSV list
   fields share one responsive row up top, then the two repeatable-row
   editors — Intention, then use cases (recorded read-only + new rows) — and
   Note last. */
.icon-edit__stack { display: flex; flex-direction: column; gap: 1.1rem; margin: 0 0 1rem; }
/* Column min is 180px, not 150px: the canonical field is 16px type, which
   overflows a 150px track. (2026-07-30: the floated label now stays inside
   the well, so this row no longer needs extra headroom above it.) */
.icon-edit__lists-row {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.85rem 1rem;
}
/* Status now sits alone on this row (the five CSV list fields that shared it
   became the chip section below), so the auto-fit track would stretch one
   select the full card width. Cap it instead. */
.icon-edit__lists-row--status { grid-template-columns: minmax(180px, 16rem); }
/* -------- Curate chip fields: family / tags / aliases / categories --------
   Curate-mode only — every selector here is scoped to .icon-edit__chip or
   its section, so the site chip recipe above and every non-curate chip
   (icon detail card, usage card, browse) are untouched.
   Geometry, radius and type come from that base .chip rule as-is; these
   modifiers set fill, border-colour and ink only.
   Colour is provenance, derived at render time (see buildChipField in
   app.js): info = the term comes from the icon's vendored sidecar,
   neutral = registry-authored, warning = a sidecar term pending
   suppression, error = a registry term pending deletion. The two pending
   states are struck through as well as recoloured, so the state reads
   without relying on hue alone. */
.icon-edit__chip-fields { display: flex; flex-direction: column; gap: 0.9rem; }
.icon-edit__chip-field { display: flex; flex-direction: column; gap: 0.35rem; }
.icon-edit__chip-label { color: var(--txt-muted); font-size: 0.8125rem; }
.icon-edit__chips { align-items: center; }
.icon-edit__chip-empty { margin: 0; font-size: 0.8125rem; }
.icon-edit__chip-add-group { max-width: 22rem; margin-top: 0.15rem; }
.icon-edit__chip[role="button"] { cursor: pointer; user-select: none; }
.icon-edit__chip[role="button"]:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
/* Fill/border/color for the four intents are dropped — canonical
   .chip--info/--neutral/--warning/--error (via the import) supplies those
   once the markup carries real chip classes (see the contract's chip
   section). The pending-deletion strikethrough has no canonical
   equivalent (chip.md defines no strikethrough variant), so it stays as a
   site-local addition on the two intents that use it. */
.icon-edit__chip--warning, .icon-edit__chip--error { text-decoration: line-through; }
.icon-edit__section-title { margin: 0 0 0.45rem; }
/* Section header: title on the left, its section-scoped clear action on the
   right (e.g. "Clear intent" — resets the rows only, unlike the card-level
   "Clear entry" in .icon-edit__actions). The title's own bottom margin moves
   to the header row so the two sit on a shared baseline. */
.icon-edit__section-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.6rem; margin: 0 0 0.45rem;
}
.icon-edit__section-head .icon-edit__section-title { margin: 0; }
.icon-edit__section-clear { flex: 0 0 auto; }
.icon-edit__section-hint { margin: 0 0 0.6rem; }
.icon-edit__section--intention {
  border-radius: var(--ui-field-radius, 0.5rem); padding: 0.5rem 0.6rem;
  border: 1px solid transparent;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
/* "shown prominently when status=avoid" — reuses the same error-adjacent
   palette as the avoid badge (.badge.error) elsewhere on this page. */
.icon-edit__section--intention.icon-edit__section--prominent {
  border-color: var(--ui-badge-error-stroke, rgb(255, 161, 168));
  background: var(--ui-badge-error-fill, rgb(255, 121, 135));
}
.icon-edit__section--intention.icon-edit__section--prominent .icon-edit__section-title,
.icon-edit__section--intention.icon-edit__section--prominent .icon-edit__section-hint { color: inherit; }
/* ---- Field / select: canonical chrome comes from the import ----
   (components_mockups/_examples/field/field.css and .../select/select.css).
   The hand-transcribed .field/.field__input/.field__label/.field__hint/
   .select__control/.select__menu/etc. blocks that used to live here used
   invented/stale variable and token names (--ui-field-bg, --ui-field-height,
   --ui-menu-bg, etc.) that don't match the canonical recipe's --control
   naming — a merge risk, not a real duplicate — so they were deleted
   wholesale rather than reconciled (see the contract's field/select
   sections). Only true site-local extensions remain below. */
/* NOT part of the field anatomy — a section-description paragraph class
   (single dash), reused by .icon-edit__section-hint. Unrelated to
   .field__description (BEM double-underscore); kept as-is. */
.field-hint { color: var(--ui-text-content-body-muted); font-size: .75rem; }
/* Local extension: field.md scopes multi-line entry out ("future textarea"),
   so this is the field container with auto height and the label parked
   permanently in its floated position/color — a textarea has no vertical
   center to rest at, so there's no "resting" state to animate from.
   Renamed onto canonical's own part name (.field__control, was
   .field__input) and its --field-label-float-top / -content-floated- names
   now that the import supplies both. */
.field--textarea { height: auto; align-items: stretch; padding-block: var(--ui-dimension-xs, 0.5rem); }
.field--textarea .field__control { height: auto; resize: vertical; min-height: 4.5rem; padding-top: 0; }
.field--textarea .field__label {
  top: var(--field-label-float-top, 0.5rem);
  font-family: var(--ui-field-label-floated-font-family, inherit), sans-serif;
  font-size: var(--ui-field-label-floated-font-size, .75rem);
  font-weight: var(--ui-field-label-floated-font-weight, 400);
  line-height: 1;
  color: var(--ui-field-label-content-floated);
}
/* Site-local addition, not a canonical part: select.md's .select__value has
   no truncation of its own now that the old .select__value-text wrapper is
   gone (canonical puts the selected text directly in .select__value as a
   plain text node). The header selects still need an ellipsis so a long
   value can't spill the narrow header row — see the contract's select
   hazard note, which asks for exactly this supplemental rule. */
.header-tools .select .select__value {
  flex-wrap: nowrap; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Not part of select.md's anatomy — a page/demo layout helper for a
   required-error form grouping (same shape as .field-group); no canonical
   equivalent, so it stays as-is. */
.select-group { display: flex; flex-direction: column; gap: var(--ui-dimension-2-xs); }
.icon-edit__errors {
  margin: 0 0 1rem; padding: 0.6rem 0.9rem;
  border-radius: var(--ui-field-radius, 0.5rem);
  background: var(--ui-badge-error-fill, rgb(255, 121, 135));
  border: var(--ui-badge-stroke-width, 1px) solid var(--ui-badge-error-stroke, rgb(255, 161, 168));
  color: var(--ui-badge-error-content, rgb(0, 0, 0));
  font-size: 0.8125rem;
}
.icon-edit__errors p { margin: 0 0 0.25rem; }
.icon-edit__errors p:last-child { margin-bottom: 0; }
.icon-edit__actions { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
/* Use cases: repeatable row editor (see addUseCaseRow in app.js). Each row
   is its own mini field-grid — app/purpose/component/addedBy/date plus a
   remove control — inside the wide "Use cases" field slot. */
.icon-edit__usecases { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.35rem; }
.icon-edit__usecase-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(160px, 1fr)) minmax(150px, 0.8fr) minmax(150px, 0.7fr) auto;
  gap: 0.75rem; align-items: end;
  padding: 0.7rem;
  border: 1px solid var(--ui-field-stroke);
  border-radius: var(--ui-field-radius);
  background: var(--sfc-raised);
}
/* Same 24x24 CSS-pixel hit-target recipe as .filter-pill__clear (WCAG 2.2
   AA 2.5.8): the box is sized via padding, not by scaling the × glyph. */
.icon-edit__usecase-remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; padding: 0; align-self: end;
  border: none; border-radius: 50%;
  background: none; color: var(--txt-muted); font-size: 0.9rem; line-height: 1;
  cursor: pointer;
}
.icon-edit__usecase-remove:hover, .icon-edit__usecase-remove:focus-visible {
  background: color-mix(in srgb, currentColor 15%, transparent);
  color: var(--txt-body); outline: none;
}
.icon-edit__usecase-empty { margin: 0; }
/* Shared "Add …" control for both repeatable-row editors. The sections are
   block containers, so the standoff from the row stack above is a margin,
   not a flex gap (align-self is a no-op here but harmless if a section ever
   becomes a flex column). */
.icon-edit__usecase-add { align-self: flex-start; margin-top: 0.75rem; }
/* Intention: repeatable row editor (see addIntentionRow in app.js) — same
   chrome recipe as the use-case rows, but each row is a Type toggle (Icon |
   Component — reuses the buttonBar() segmented control, see button-bar.md)
   plus a Replacement Icon/Component (optional, bound to the datalist that
   matches the toggle; icon-less row = the entry's own intent) plus its
   Intention (reuses .icon-edit__usecase-remove / -add for the controls).
   Row order is meaningful: the first replacement row is the default target.
   The type column reuses .tb-field/.tb-field__label (see the toolbar field
   label rule above) rather than a bespoke label treatment. */
.icon-edit__intentions { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.35rem; }
.icon-edit__intention-row {
  display: grid;
  grid-template-columns: auto minmax(180px, 1fr) minmax(180px, 1fr) auto;
  gap: 0.75rem; align-items: end;
  padding: 0.7rem;
  border: 1px solid var(--ui-field-stroke);
  border-radius: var(--ui-field-radius);
  background: var(--sfc-raised);
}
.icon-edit__intention-type { flex-shrink: 0; }
/* Replaces: the derived INBOUND side of replacedBy (see inboundReplacements
   and the Replaces section in app.js). The link rows reuse the recorded
   use-case row recipe below verbatim (.icon-edit__usecase-existing plus its
   -text / -badge / -remove parts), so only two rules are new here. The add
   row needs its own track list because it carries three controls plus the
   button, and .icon-edit__intention-row's two-field grid would wrap that
   button onto a second line. */
.icon-edit__replaces { display: flex; flex-direction: column; gap: 0.45rem; }
.icon-edit__replaces-add {
  display: grid;
  grid-template-columns: minmax(180px, 1fr) minmax(180px, 1fr) minmax(140px, auto) auto;
  gap: 0.75rem; align-items: end;
  margin-top: 0.6rem;
  padding: 0.7rem;
  border: 1px solid var(--ui-field-stroke);
  border-radius: var(--ui-field-radius);
  background: var(--sfc-raised);
}
/* The status select is a bare control, not a .field, so it has to be sized to
   the field height beside it rather than sitting short on the row's baseline. */
.icon-edit__replaces-add .ctl-select { height: var(--ui-field-height); }
/* Recorded use cases: read-only (removable) rows above the New use case
   editor — ideally scanner-written; badge marks auto vs manual provenance. */
.icon-edit__usecases-existing { display: flex; flex-direction: column; gap: 0.45rem; }
.icon-edit__usecase-existing {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--ui-field-stroke);
  border-radius: var(--ui-field-radius);
  background: var(--sfc-raised);
  font-size: 0.8125rem;
}
.icon-edit__usecase-existing .icon-edit__usecase-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.icon-edit__usecase-badge { flex: 0 0 auto; }
/* The old persistent #toast/.show div is gone — canonical toast is a
   .toast-region insert/remove lifecycle (.is-entering/.is-exiting), a
   behavioral rewrite, not a class rename (see the contract's toast
   section, its largest scope-increase hazard). Keep site-local: nothing —
   the entire concept is being upgraded, not preserved. */
.error-card { padding: 1rem 1.2rem; border-radius: var(--ui-field-radius, 0.5rem); background: var(--sfc-raised); }
/* .error-card code duplicated the canonical bare code{} rule — dropped. */

/* ------------------------------ Home ------------------------------- */
.home-intro { color: var(--txt-muted); margin: -0.5rem 0 1rem; max-width: 60ch; }
.stat-grid {
  display: grid; gap: 0.75rem; margin: 0.25rem 0 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
/* Stat tile (examples/stat-tile): non-interactive KPI readout. Container
   chrome borrows the same .card rim/glass recipe used everywhere else in
   this file — examples/stat-tile.md flags dedicated ui.stat-tile.* container
   tokens (border/radius/padding/elevation) as a gap, so this reuses the
   confirmed containers-only card treatment rather than inventing new chrome. */
.stat-tile {
  padding: 1rem 1.1rem 1.3rem; display: flex; flex-direction: column; gap: 0.2rem;
}
.stat-tile__value {
  font-family: var(--ui-stat-tile-value-text-font-family, inherit), var(--font);
  font-size: var(--ui-stat-tile-value-text-font-size, 2rem);
  font-weight: var(--ui-stat-tile-value-text-font-weight, 700);
  line-height: var(--ui-stat-tile-value-text-line-height, 1.1);
  color: var(--txt-primary);
}
.stat-tile__label {
  font-family: var(--ui-stat-tile-label-text-font-family, inherit), var(--font);
  font-size: var(--ui-stat-tile-label-text-font-size, 0.75rem);
  font-weight: var(--ui-stat-tile-label-text-font-weight, 700);
  line-height: var(--ui-stat-tile-label-text-line-height, 1.3);
  letter-spacing: var(--ui-stat-tile-label-text-letter-spacing, 0.04em);
  text-transform: var(--ui-stat-tile-label-text-text-case, uppercase);
  color: var(--txt-muted);
}
.stat-tile__sublabel {
  font-size: var(--ui-font-body-s-font-size, 0.78rem);
  color: var(--txt-muted);
}
.stat-tile__underline {
  display: block; margin-top: 0.5rem; height: 0.2rem; border-radius: var(--ui-chip-radius, 0.25rem);
  background: var(--ui-stat-tile-accent-teal-underline, var(--accent));
}
.stat-tile--blue .stat-tile__underline { background: var(--ui-stat-tile-accent-blue-underline, var(--accent)); }
.stat-tile--purple .stat-tile__underline { background: var(--ui-stat-tile-accent-purple-underline, var(--accent)); }
.stat-tile--gold .stat-tile__underline { background: var(--ui-stat-tile-accent-gold-underline, var(--accent)); }

.needs-attention { padding: 1.1rem 1.25rem 1.25rem; }
.na-title {
  margin: 0 0 0.75rem; color: var(--txt-primary);
  font-family: var(--ui-font-heading-3-font-family, inherit), var(--font);
  font-size: var(--ui-font-heading-3-font-size, 1.125rem);
  font-weight: var(--ui-font-heading-3-font-weight, 600);
}
.na-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 0.5rem 0; }
.na-text { color: var(--txt-body); font-size: 0.875rem; }
.na-link { margin-left: auto; font-size: 0.8125rem; text-decoration: none; white-space: nowrap; }

/* ------------------------------- Docs ------------------------------ */
/* Card uses most of the reading pane; prose blocks keep a readable measure
   (~90ch) while wide media (diagrams, tables, images, code) fill the card. */
.doc-content { padding: 1.25rem 1.5rem; max-width: min(100%, 1120px); margin-inline: auto; }
/* When a section doc is split into per-section cards, space the stack. */
.doc-content + .doc-content { margin-top: 1.25rem; }
.doc-content .doc-p, .doc-content .doc-h, .doc-content .doc-list, .doc-content .doc-quote { max-width: 90ch; }
.doc-content .doc-h {
  color: var(--txt-primary); line-height: 1.2; letter-spacing: -0.01em;
  font-family: var(--ui-font-heading-3-font-family, inherit), var(--font);
  font-weight: var(--ui-font-heading-3-font-weight, 700);
}
/* Renderer emits h{level+1}: '#'->h2, '##'->h3, '###'->h4. Give each a
   distinct step + rhythm so long docs are scannable. */
.doc-content h2.doc-h {
  font-size: 1.6rem; margin: 2.3rem 0 1rem; padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--ui-panel-outline);
}
.doc-content h3.doc-h { font-size: 1.2rem; margin: 1.9rem 0 0.6rem; }
.doc-content h4.doc-h {
  font-size: 0.82rem; margin: 1.5rem 0 0.5rem; color: var(--accent);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.doc-content .doc-h:first-child { margin-top: 0; }
.doc-content .doc-p { color: var(--txt-body); line-height: 1.55; margin: 0 0 0.85rem; }
/* This scoped code recipe duplicated the canonical bare code{} rule
   (import above) property-for-property — dropped in favor of it. */
.doc-content .doc-list { color: var(--txt-body); line-height: 1.6; margin: 0 0 0.85rem; padding-left: 1.3rem; }
.doc-content .doc-list li { margin-bottom: 0.3rem; }
.doc-content .doc-list li::marker { color: var(--accent); }
/* Nested lists: no extra block margin (they sit inside an <li>), and a
   per-depth marker shape so nesting is legible without relying on indent
   alone. Markers step down in emphasis as depth increases. */
.doc-content .doc-list .doc-list { margin: 0.3rem 0 0; }
.doc-content ul.doc-list { list-style: disc; }
.doc-content ul.doc-list ul.doc-list { list-style: circle; }
.doc-content ul.doc-list ul.doc-list ul.doc-list { list-style: square; }
.doc-content .doc-list .doc-list li::marker { color: var(--txt-muted); }
/* Corrective, not decorative. markdown-it wraps blockquote content and LOOSE
   list-item content in <p class="doc-p"> (correct CommonMark structure; the
   old hand-rolled renderer dropped the wrapper). .doc-p's bottom margin is
   right between paragraphs but reads as stray trailing space at the end of a
   quote or a list item, so drop it on the last one only. */
.doc-content .doc-quote > .doc-p:last-child { margin-bottom: 0; }
.doc-content .doc-list > li > .doc-p:last-child { margin-bottom: 0; }
/* Strikethrough (~~text~~) — parseable for the first time, so it had no
   styling. markdown-it's GFM strikethrough emits <s>; <del> is listed too so
   the rule holds if the markup ever changes. Line-through is the semantic of
   the element itself, not a themed value; the de-emphasis colour is the
   existing muted text tier. */
.doc-content s, .doc-content del { text-decoration: line-through; color: var(--txt-muted); }
/* Hard line breaks (two trailing spaces) also render for the first time.
   <br> needs no rule of its own: it inherits the line-height of whichever
   .doc-p / li / th / td it sits in, which is the intended rhythm. */
/* Inline links (renderer emits bare <a>): canonical .link.link--inline
   takes over color, hover underline and focus-visible ring — this whole
   rule set duplicated that (a different underline technique, but the same
   job), so it's dropped rather than kept alongside it. */
.doc-content .doc-quote {
  margin: 0 0 1rem; padding: 0.7rem 1.1rem; color: var(--txt-body);
  background: var(--sfc-sunken);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--ui-field-radius, 0.5rem) var(--ui-field-radius, 0.5rem) 0;
}
.doc-content .doc-code {
  margin: 0 0 1rem; padding: 0.85rem 1rem; overflow-x: auto;
  background: var(--sfc-sunken);
  border: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65));
  border-radius: var(--ui-field-radius, 0.5rem);
  font-family: var(--mono); font-size: 0.8125rem; line-height: 1.6; color: var(--txt-primary);
}
.doc-content .doc-mermaid {
  display: block; margin: 0 0 0.85rem; padding: 0.85rem 1rem; overflow-x: auto;
  background: transparent; border-radius: var(--ui-field-radius, 0.5rem);
}
.doc-content .doc-mermaid svg { width: 100%; max-width: 100%; height: auto; background: transparent; }
/* Edge colour/width/dash are handled inside mermaid via themeVariables.lineColor
   and themeCSS (see initMermaidTheme) rather than overridden from here. */
/* hover-swap trigger nodes are keyboard-toggle controls: show a focus ring for
   keyboard only (SVG can't render the box-shadow --focus-ring, so use outline),
   and suppress the stray UA outline on mouse click. */
.doc-content .doc-mermaid g.node[tabindex]:focus { outline: none; }
.doc-content .doc-mermaid g.node[tabindex]:focus-visible {
  outline: 3px solid var(--ui-button-standard-enabled-primary-fill, rgb(80, 157, 69)); outline-offset: 2px;
}
/* Subgraph container titles: mermaid's default renders them faint on the
   glass panels. Force full-contrast, bold text. */
.doc-content .doc-mermaid .cluster-label,
.doc-content .doc-mermaid .cluster-label span,
.doc-content .doc-mermaid .cluster-label p {
  color: var(--txt-primary) !important; font-weight: 700; font-size: 0.9rem;
}
.doc-content .doc-mermaid-error { margin: 0.4rem 0 0.85rem; font-size: 0.8125rem; color: var(--txt-muted); }
/* .doc-content .doc-table's frame/header/cell/zebra rules duplicated
   canonical .table/.table__* (import above) — including the zebra rule,
   which canonical also stripes structurally via :nth-child(odd/even), so
   nothing needed to survive the rename. Dropped wholesale; the markdown
   renderer emits bare th/td with no BEM parts, so this table is unstyled
   until the canonical .table__header-row/.table__header-cell/.table__row/
   .table__cell classes land on it (stage 2). */
.doc-empty { display: flex; flex-direction: column; align-items: flex-start; gap: 0.6rem; }
/* Icon Registry table (Phase 5): the border/color/typography recipe above
   duplicated canonical .table/.table__* and is dropped the same way.
   MIGRATION: review — row-hover has no canonical equivalent (table.css
   defines no hover state at all); the contract flags it as worth keeping
   "if row-hover feedback is still wanted" on this table, which is a call
   this pass isn't making. Kept, renamed onto canonical's own row/cell
   parts exactly as the contract's text scopes it, pending that decision. */
.table__row:hover .table__cell { background: var(--ui-menu-item-hover, rgba(111, 189, 100, 0.2)); }

/* --------------------------- Doc images (v3) ----------------------- */
.doc-content .doc-img {
  display: block; max-width: 100%; height: auto;
  border-radius: var(--ui-field-radius, 0.5rem);
  margin: 0.25rem 0 0.9rem;
}

/* ---------------------- Hub pages (v3 Phase 2) ---------------------- */
.hub-intro { margin-bottom: 1.25rem; }
.hub-grid {
  display: grid; gap: var(--ui-tile-gap, 0.75rem); margin: 0 0 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
/* Doc-hub tile (v3 Phase 2 → canonicalized 2026-09-02): root is now
   canonical .tile.tile--grid.tile--floating (examples/tile/tile.css) — fill,
   border, radius, padding, gap, shadow, the rim ::before, and the hover
   fill/border/label-color swap all come from the import; do not restate them
   here. .hub-tile only scopes the parts canonical has no slot for
   (__desc, __badge — see tile.md "Trailing meta (list orientation only)",
   grid orientation carries no description/badge slot) and the disabled-ish
   --empty modifier, which tile.md flags as an undesigned gap. .tile--grid
   centers/ellipsizes by anatomy; this consumer's card content reads better
   left-aligned and full-width, so .hub-grid .tile overrides that geometry —
   consumer layout, not a restatement of fill/border/shadow anatomy. */
.hub-grid .tile { width: 100%; min-height: 6rem; align-items: flex-start; text-align: left; }
.hub-tile__desc { color: var(--txt-muted); font-size: 0.8125rem; line-height: 1.4; flex: 1 1 auto; }
.hub-tile__badge { align-self: flex-start; }
.hub-tile--empty { opacity: 0.6; box-shadow: none; }
.hub-callout { margin-top: 0.5rem; }
/* Components index (v4 → canonicalized 2026-09-02): live preview thumbnails
   on canonical .tile.tile--grid.tile--floating cards + a filter box,
   mirroring the icon grid's "Previews" gallery. The tile stays a single <a>;
   the scaled example iframe is pointer-events:none so clicks fall through to
   the anchor. .cmp-tile is this consumer's site-local packaging (zero base
   tile padding/gap so the preview viewport and body own their own box) —
   canonical fill/border/radius/shadow/rim/hover still come from .tile
   itself, only reset here. */
/* Fixed height so every virtualized row is uniform (createVirtualGrid pins
   grid-auto-rows to a single measured cell — see that function). */
.cmp-tile { padding: 0; overflow: hidden; gap: 0; height: 238px; }
/* .tile--grid centers/ellipsizes and fixes width: 120px by anatomy — none of
   that suits a 280px preview card whose media/body need to run full width,
   so the override lives here, under the grid selector, not on .tile. */
.cmp-vgrid .tile { width: 100%; align-items: stretch; text-align: left; }
/* .tile__media carries no rules of its own in tile.css (media sizing/styling
   is consumer-owned per tile.md) — everything below is this consumer's own
   fixed-height scaling viewport for the live example iframe. */
.cmp-tile__preview {
  position: relative; width: 100%; height: 150px; overflow: hidden;
  background: var(--sfc-sunken, var(--sfc-raised));
  border-radius: var(--ui-tile-radii, 0.5rem) var(--ui-tile-radii, 0.5rem) 0 0;
  border-bottom: 1px solid var(--ui-panel-outline);
}
.cmp-tile__preview iframe {
  position: absolute; top: 0; left: 0; border: 0;
  transform-origin: top left; pointer-events: none; background: transparent;
}
.cmp-tile__preview-note {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--txt-muted); font-size: 0.75rem; text-align: center; padding: 0.5rem;
}
.cmp-tile__body {
  box-sizing: border-box; height: 88px; overflow: hidden;
  display: flex; flex-direction: column; gap: 0.35rem;
  padding: var(--ui-tile-pad-top, 0.85rem) var(--ui-tile-pad-x, 1rem) var(--ui-tile-pad-bottom, 0.9rem);
}
/* v4: per-framework availability chips on a Components-index tile (subtle —
   .badge.neutral's palette at a much smaller scale, since the full .badge
   sizing reads oversized next to .tile__meta's small text). Omitted from
   the DOM entirely when a component has no framework artifacts. */
.cmp-tile__fw { display: flex; gap: 0.3rem; margin-top: 0.4rem; }
.fw-chip {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.3rem; height: 1.1rem; padding: 0 0.35rem;
  border-radius: var(--ui-badge-radii, 9999px);
  border: 1px solid var(--ui-badge-neutral-stroke, rgb(89, 91, 92));
  background: var(--ui-badge-neutral-fill, rgb(89, 91, 92));
  color: var(--ui-badge-neutral-content, rgb(210, 212, 214));
  font-size: 0.62rem; font-weight: 600; line-height: 1;
}
/* Components virtualized gallery: fixed-width columns (not the icon ramp's
   stretch-to-fill minmax) so each preview iframe scales by a constant factor
   — no per-cell resize math as rows recycle on scroll. */
.cmp-vgrid { grid-template-columns: repeat(auto-fill, var(--cell, 280px)); justify-content: start; }
.cmp-search { margin: 0 0 1.25rem; max-width: 22rem; }
.cmp-search input {
  width: 100%; box-sizing: border-box;
  padding: var(--ui-field-pad-y, 0.5rem) var(--ui-field-pad-x, 0.75rem);
  color: var(--ui-field-content, var(--txt-body));
  background: var(--ui-field-fill, var(--sfc-raised));
  border: 1px solid var(--ui-field-stroke, rgba(255, 255, 255, 0.14));
  border-radius: var(--ui-field-radii, 0.5rem);
  font: inherit;
}
.cmp-search input::placeholder { color: var(--ui-field-placeholder, var(--txt-muted)); }
.cmp-search input:focus-visible {
  outline: none;
  border-color: var(--ui-field-stroke-focus, rgb(111, 189, 100));
}
/* Icon Previews filter: .cmp-search plus a clear affordance. Until 2026-08-17
   .icon-search was a dead modifier with no rule anywhere in this file — the
   field was getting its entire appearance from .cmp-search. It earns real
   rules now that it carries URL state (?q=) and can land pre-filled from a
   shared link, where a bare field gives no hint a filter is on and no way to
   drop it. Wider than the components filter because this one matches tags and
   aliases, not just names, so queries are longer. */
.icon-search {
  display: flex; align-items: stretch; gap: 0.5rem;
  max-width: 30rem; /* field + Clear, so wider than .cmp-search's field-only 22rem */
}
.icon-search input { flex: 1 1 auto; min-width: 0; }
/* WebKit's built-in cancel button is suppressed because it clears the field
   directly, bypassing the handler that syncs ?q= and repaints the grid — the
   Clear button is the only sanctioned way out of a query. */
.icon-search input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
/* Height comes from the field, not from .btn's compact height token: the field
   is the reference in this pairing and a 2rem button beside a taller well
   reads as misaligned. The token stays as the floor via min-height, so this
   never renders shorter than a compact button would. */
.icon-search__clear { height: auto; min-height: var(--ui-button-compact-height, 2rem); }

/* ------------------ Section doc TOC (v3 Phase 3) -------------------- */
/* Chip row on wide viewports, collapsible <details> dropdown below the
   `M` breakpoint (1024px, per taxonomy-sizing.md) — --ui-menu-* / --ui-link-*
   patterns, no new tokens. */
.doc-toc { margin: 0 0 1.25rem; }
.doc-toc__summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 0.35rem;
  color: var(--txt-muted); font-size: 0.8125rem; padding: 0.35rem 0.6rem;
  border: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65)); border-radius: var(--ui-field-radius, 0.5rem);
}
.doc-toc__summary::-webkit-details-marker { display: none; }
.doc-toc__summary::before { content: '▸'; font-size: 0.7em; transition: transform var(--dur) var(--ease); }
.doc-toc[open] .doc-toc__summary::before { transform: rotate(90deg); }
.doc-toc__list {
  display: none; flex-direction: column; gap: 0.15rem; margin-top: 0.4rem; max-width: 22rem;
  padding: 0.5rem; background: var(--ui-menu-bg, rgb(37, 38, 40));
  border: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65)); border-radius: var(--ui-field-radius, 0.5rem);
}
.doc-toc[open] .doc-toc__list { display: flex; }
.doc-toc__chip {
  padding: 0.3rem 0.5rem; border-radius: var(--ui-field-radius, 0.5rem);
  color: var(--ui-link-enabled-content, var(--accent)); text-decoration: none; font-size: 0.8125rem;
}
.doc-toc__chip:hover { background: var(--ui-menu-item-hover, rgba(111, 189, 100, 0.2)); color: var(--ui-link-hover, var(--accent-hover)); }
@media (min-width: 1024px) {
  .doc-toc__summary { display: none; }
  .doc-toc__list {
    display: flex !important; flex-direction: row; flex-wrap: wrap;
    background: none; border: none; padding: 0; max-width: none; margin-top: 0;
  }
  .doc-toc__chip { border: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65)); }
}

/* ------------------- Prev/next + hub link (v3 Phase 3) -------------- */
.doc-prevnext {
  margin: 2rem auto 0; padding-top: 1rem; max-width: min(100%, 1120px);
  border-top: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65));
  display: flex; flex-direction: column; gap: 0.75rem;
}
/* MIGRATION: review — the contract marks .doc-prevnext a -> .link not
   covered by link.md ("inference only"); left fully as-is pending
   sign-off, same treatment as .filelist a above. */
.doc-prevnext a { color: var(--ui-link-enabled-content, var(--accent)); text-decoration: none; font-size: 0.875rem; }
.doc-prevnext a:hover { color: var(--ui-link-hover, var(--accent-hover)); text-decoration: underline; }
.doc-prevnext__row { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }

/* -------------------- Glossary filter (v3 Phase 3) ------------------ */
.glossary-filter {
  display: flex; align-items: center; gap: var(--ui-field-gap, 0.5rem);
  height: var(--ui-field-height, 2.75rem); padding-inline: var(--ui-field-pad-x, 0.9rem);
  background: var(--ui-field-bg, rgba(251, 253, 255, 0.05));
  border: var(--ui-field-stroke-width, 1px) solid var(--ui-field-stroke, rgb(117, 119, 121));
  border-radius: var(--ui-field-radius, 0.5rem);
  margin: 0 0 1.1rem; max-width: 26rem;
}
.glossary-filter:hover { border-color: var(--ui-field-stroke-hover, rgb(147, 149, 151)); }
.glossary-filter:has(.glossary-filter__input:focus) { border-color: var(--ui-field-stroke-focus, var(--accent)); }
.glossary-filter__input {
  flex: 1; min-width: 0; background: none; border: none; outline: none;
  color: var(--txt-primary); font: inherit;
}
.glossary-filter__input::placeholder { color: var(--ui-field-placeholder, var(--txt-muted)); }
.glossary-count { color: var(--txt-muted); font-size: 0.7em; font-weight: 400; }

/* ------------- Component example pages (v3 Phase 4) ----------------- */
.cmp-tools { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin: 0 0 1rem; }
.cmp-standalone { text-decoration: none; }
.cmp-tools-right { display: flex; align-items: center; gap: 0.75rem; margin-left: auto; flex-wrap: wrap; }
/* Sized to the content region via a viewport calc (header + crumbs/title/
   tools budget) rather than polled against the iframe's scrollHeight (hard
   constraint) — the example scrolls within this box, the outer page does
   not grow with it. Approximate, not pixel-perfect: the crumbs/title/tools
   block above it is variable height, so the calc uses a fixed budget for
   that zone rather than measuring it live. */
.cmp-frame-wrap {
  position: relative; overflow: hidden;
  height: calc(100vh - 16rem); min-height: 420px;
  border-radius: var(--ui-card-radius, 1rem);
  background: var(--sfc-sunken);
  border: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65));
}
.cmp-frame-wrap iframe { display: block; width: 100%; height: 100%; border: 0; background: transparent; }
.cmp-frame-wrap--degraded { height: auto; min-height: 0; background: none; border: none; }
.cmp-degraded { display: flex; flex-direction: column; align-items: flex-start; gap: 0.85rem; }

@media (prefers-reduced-motion: reduce) {
  /* .button-bar__indicator is excluded: its own reduced-tier rule (above,
     near .is-switching) needs its real ui.motion.duration.fast crossfade
     to survive — this blanket kill-switch would otherwise force it to
     0.01ms and erase the tonal fade the component defines for this tier. */
  *:not(.button-bar__indicator), *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* ==================== Primitive Colors page ======================= */
/* Bespoke doc view built by showPrimitiveColors() in app.js from
   design-system-tokens/dist/json/primitives.json. Appearance lives here, in
   ui.* tokens, exactly like every other view — the JS sets only the three
   things that ARE the data: a swatch's background (the authored oklch), its
   label colour (the build-resolved WCAG-passing ink), and an alpha value. */
/* No .card and no .doc-content on this page (see showPrimitiveColors): the
   content sits directly on #main-panel's glass, the same surface as the
   breadcrumb and <h1>. Only a small top gap is needed to clear the title —
   #main-panel already supplies the outer padding. */
.pc-page { display: flex; flex-direction: column; gap: 1.75rem; }
.pc-lede { color: var(--ui-text-content-body); max-width: 68ch; margin: 0; }
.pc-loading { color: var(--ui-text-content-body-muted); margin: 0; }

.pc-legend {
  display: flex; flex-direction: column; gap: 0.5rem;
  padding: 0.875rem 1rem;
  background: var(--ui-panel-fill-sunken);
  border: 1px solid var(--ui-panel-outline);
  border-radius: var(--ui-card-radius);
}
.pc-legend p {
  margin: 0; max-width: 78ch;
  color: var(--ui-text-content-body-muted);
  font-size: var(--ui-font-body-s-font-size);
  line-height: 1.5;
}

.pc-set { display: flex; flex-direction: column; gap: 0.75rem; }
.pc-set__title {
  margin: 0;
  font-family: var(--ui-font-heading-2-font-family);
  font-size: var(--ui-font-heading-2-font-size);
  font-weight: var(--ui-font-heading-2-font-weight);
  color: var(--ui-text-content-heading);
}
.pc-set__note { margin: 0; max-width: 78ch; color: var(--ui-text-content-body); }
.pc-set__note--sub { color: var(--ui-text-content-body-muted); font-size: var(--ui-font-body-s-font-size); }
.pc-set__src { margin: 0; }
/* .pc-set__src code duplicated the canonical bare code{} rule — dropped. */

/* A row holds one or more labelled groups. The GROUP’S CARDS are the scroll
   container — deliberately not the row and not the group. The heading has to
   sit outside the scroller, or it slides away the moment you scroll a 13-stop
   ramp and you lose track of which ramp you are reading. Scrolling the cards
   alone keeps the heading pinned while the ramp still moves as one continuous
   light-to-dark progression (wrapping it into two lines would destroy that
   reading, which is why this scrolls rather than wraps).
   The 2rem row gap is what separates two groups sharing a row (LINQX Primary
   / Secondary) — wide enough to read as two clusters, tight enough to read as
   one identity block. */
.pc-row { display: flex; gap: 2rem; align-items: flex-start; }
/* shrink-to-fit + min-width:0 so a group never forces the row wider than the
   content area: a lone group narrows to the row and scrolls its cards inside,
   and two paired groups keep their natural width until the viewport is too
   narrow, then shrink and scroll independently rather than clipping. */
.pc-group { flex: 0 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0.375rem; }
.pc-group__name {
  display: flex; align-items: baseline; gap: 0.5rem; margin: 0.25rem 0 0;
  font-family: var(--ui-font-heading-3-font-family);
  font-size: var(--ui-font-heading-3-font-size);
  font-weight: var(--ui-font-heading-3-font-weight);
  color: var(--ui-text-content-heading);
  white-space: nowrap;
}
.pc-group__count {
  font-family: var(--ui-font-family-body);
  font-size: var(--ui-font-body-s-font-size);
  font-weight: 400;
  color: var(--ui-text-content-body-muted);
}
.pc-group__cards {
  display: flex; gap: 2px;
  overflow-x: auto; overflow-y: hidden;
  padding-bottom: 0.375rem;
  scrollbar-width: thin;
}
.pc-group__cards > * { flex: 0 0 auto; }

.pc-card {
  /* 200px is the measured floor: the longest authored value is
     `oklch(0.8536 0.090775 229.72)` at 29 characters, which needs ~174px at
     this mono size plus the 16px of padding. Narrower and the oklch clips —
     which it did at 176px, on 193 of the 212 cards. */
  width: 200px; min-height: 116px;
  display: flex; flex-direction: column; gap: 1px;
  padding: 0.5rem 0.5rem 0.4rem;
  border-radius: var(--ui-card-radius);
  /* No token border: a border in a theme colour would sit between two
     swatches and misrepresent the step-to-step delta. The gap does the
     separating. */
  border: none;
}
.pc-card__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.375rem; margin-bottom: 0.125rem;
}
.pc-card__step {
  font-family: var(--ui-font-family-body);
  font-size: 1rem; font-weight: 700; letter-spacing: 0.01em;
  color: inherit;
}
.pc-card__gamut {
  font-family: var(--ui-font-family-mono);
  font-size: 0.5625rem; font-weight: 700; letter-spacing: 0.06em;
  padding: 0.05rem 0.3rem;
  border: 1px solid currentColor;
  border-radius: var(--ui-chip-radius);
  opacity: 0.8;
  color: inherit;
}

/* Value rows are buttons (click to copy) but must not look like chrome — any
   fill or border here would sit on top of the swatch and change the colour
   the reader is judging. */
.pc-val {
  appearance: none; background: none; border: 0; padding: 0;
  display: flex; align-items: baseline; gap: 0.3rem;
  width: 100%; text-align: left; cursor: pointer;
  font-family: var(--ui-font-family-mono);
  font-size: 0.625rem; line-height: 1.45;
  color: inherit;
}
/* Kept as an ellipsis guard only. At 200px nothing should clip; if a future
   token pushes past that, it degrades to an ellipsis rather than reflowing
   the whole ramp. */
.pc-val__v {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pc-val:hover .pc-val__v { text-decoration: underline; }
.pc-val:focus-visible { outline: var(--ui-focus-ring); outline-offset: 2px; border-radius: 2px; }

.pc-skipped {
  margin: 0.25rem 0 0; padding-left: 1.1rem;
  color: var(--ui-text-content-body-muted);
  font-size: var(--ui-font-body-s-font-size);
}
/* .pc-skipped code duplicated the canonical bare code{} rule — dropped. */

/* Alpha strip. The bar is a fixed neutral (from the token data, currently
   #808080) and deliberately NOT a theme token: the entire purpose is to read
   each alpha against a constant, so it must not move when the theme does.

   --pc-alpha-overhang is the distance the swatches extend past the bar at the
   top and the bottom. Those exposed ends show the same alpha over the live
   page surface, which is what the reader will actually be compositing over;
   the covered middle shows it over the known neutral. Both readings at once
   is the reason the bar is inset rather than full height. */
.pc-alpha {
  --pc-alpha-w: 64px;
  --pc-alpha-h: 104px;
  --pc-alpha-overhang: 12px;   /* >= 8px, per spec */
  overflow-x: auto; scrollbar-width: thin;
  padding-bottom: 0.25rem;
}
/* The track holds both rows and is what scrolls, so the bar (absolutely
   positioned inside it) travels with the swatches instead of staying pinned
   to the viewport when the strip overflows. */
.pc-alpha__track { width: max-content; }
.pc-alpha__row { position: relative; }
.pc-alpha__bar {
  position: absolute; z-index: 0;
  left: 0; right: 0;
  top: var(--pc-alpha-overhang); bottom: var(--pc-alpha-overhang);
}
.pc-alpha__swatches { position: relative; z-index: 1; display: flex; gap: 0; }
.pc-alpha__swatch {
  appearance: none; border: 0; padding: 0; margin: 0;
  flex: 0 0 auto; width: var(--pc-alpha-w); height: var(--pc-alpha-h);
  cursor: pointer;
}
.pc-alpha__swatch:focus-visible { outline: var(--ui-focus-ring); outline-offset: -3px; z-index: 2; }
/* Second row, same column width, so labels line up under their swatch
   without a per-cell wrapper that would chop the bar into 25 segments.
   The top margin matters: without it the labels butt against the swatches'
   bottom edge and the lower overhang reads as the end of the bar rather than
   as swatch-over-page, which defeats the inset. */
.pc-alpha__labels { display: flex; gap: 0; margin-top: 0.5rem; }
.pc-alpha__meta {
  flex: 0 0 auto; width: var(--pc-alpha-w);
  display: flex; flex-direction: column; align-items: center; gap: 0;
  padding: 0.3rem 0.15rem;
}
.pc-alpha__pct {
  font-family: var(--ui-font-family-mono); font-size: 0.6875rem; font-weight: 600;
  color: var(--ui-text-content-heading);
}
.pc-alpha__step {
  font-family: var(--ui-font-family-mono); font-size: 0.5625rem;
  color: var(--ui-text-content-body-muted);
}

/* No narrow-viewport width override: shrinking the card would clip the oklch
   again, and a clipped colour value is worse than a horizontal scroll. The
   ramp already scrolls, so small screens simply scroll further. */

/* ===================== Marketing section (2026-09-03) ================= */
/* Site-local chrome for the generated Brand Kit / Colors / Typography
   pages. No bespoke skin (ruled): everything reuses the doc/tile/filelist
   recipes above; these hooks only lay out the kit summary list. */
.marketing-summary { display: grid; grid-template-columns: max-content 1fr; gap: 0.35rem 1rem; margin: 0.5rem 0 0; }
.marketing-summary dt { color: var(--txt-muted); }
.marketing-summary dd { margin: 0; }
.marketing-listing { margin-top: 0.75rem; }
.marketing-listing summary { cursor: pointer; }
.marketing-guidelines { margin-bottom: 1rem; }
.marketing-downloads .pv-actions { margin-top: 0.5rem; }
.mk-swatch { min-height: 150px; }
.mk-usage-wrap { overflow-x: auto; }
.mk-usage { border-collapse: collapse; font-size: var(--ui-font-body-s-font-size); width: 100%; }
.mk-usage th, .mk-usage td { text-align: left; padding: 0.3rem 0.6rem; border-bottom: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65)); white-space: nowrap; }
.mk-usage__chip { display: inline-block; width: 0.9rem; height: 0.9rem; border-radius: 2px; vertical-align: middle; margin-right: 0.4rem; border: 1px solid var(--ui-menu-stroke, rgb(62, 64, 65)); }
.mk-usage__pass { color: var(--ui-text-content-success); }
.mk-usage__large { color: var(--ui-text-content-warning); }
.mk-usage__fail { color: var(--ui-text-content-body-muted); }
.mk-gradient { height: 3rem; border-radius: var(--ui-card-radius); margin: 0.25rem 0 0.5rem; }

/* Brand config edit card */
/* Localhost-only inline editor for design-system-tokens/brand.config.json
   (renderBrandEditCard, browser/js/app.js) — the Marketing pages'
   equivalent of the icon detail page's .icon-edit card above. Reuses
   .icon-edit/.meta-card for its outer chrome, .icon-edit__errors/
   __actions for its error area and Save/Cancel row, and .mk-usage__chip
   (above) for every colour swatch, so only the layout hooks .icon-edit* has
   no equivalent for — the collapsible field groups and their generic
   repeatable rows — are new, all prefixed brand-edit__ per the site's
   site-local-class convention. */
.brand-edit__body { display: flex; flex-direction: column; gap: 0.9rem; margin: 0 0 1rem; }
.brand-edit__group {
  border: 1px solid var(--ui-field-stroke); border-radius: var(--ui-field-radius);
  padding: 0.7rem 0.9rem; background: var(--sfc-raised);
}
.brand-edit__group-title { cursor: pointer; margin: 0; }
.brand-edit__group[open] > .brand-edit__group-title { margin-bottom: 0.6rem; }
/* Generic repeatable-row container (ramp, anchor, gradient, extra, print,
   scale rows): same bordered/raised chrome as .icon-edit__usecase-row /
   .icon-edit__intention-row, but flex-wrap instead of a fixed grid template
   since the field count varies per group. */
.brand-edit__row {
  display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: end;
  padding: 0.7rem; margin-bottom: 0.6rem;
  border: 1px solid var(--ui-field-stroke); border-radius: var(--ui-field-radius);
  background: var(--sfc-raised);
}
.brand-edit__row:last-child { margin-bottom: 0; }
.brand-edit__row > .field, .brand-edit__row > .select { flex: 1 1 160px; min-width: 160px; }
.brand-edit__row-fields { display: flex; flex-wrap: wrap; gap: 0.75rem; flex: 1 1 auto; }
.brand-edit__row-title { display: flex; align-items: center; gap: 0.4rem; flex: 0 0 auto; font-size: 0.8125rem; }
/* Colour picker: a fieldSelect() combobox (see _brandColorField /
   _brandAnchorTokenField) plus its live .mk-usage__chip preview. */
.brand-edit__color-field { display: flex; align-items: center; gap: 0.5rem; flex: 1 1 200px; min-width: 200px; }
.brand-edit__swatch { flex: 0 0 auto; }
.brand-edit__checkbox { display: inline-flex; align-items: center; gap: 0.4rem; cursor: pointer; font-size: 0.8125rem; }
.brand-edit__checkbox-group { display: flex; flex-wrap: wrap; gap: 0.5rem 0.9rem; margin: 0.3rem 0; }
.brand-edit__checkbox-row { align-items: center; }
.brand-edit__cmyk { display: flex; gap: 0.5rem; flex: 1 1 auto; }
.brand-edit__cmyk .field { max-width: 5rem; min-width: 4rem; }
.brand-edit__anchors { display: flex; flex-direction: column; gap: 0.5rem; margin: 0.4rem 0; }
.brand-edit__anchors-label { margin: 0; }
.brand-edit__anchor-preview { margin: 0; flex: 1 1 220px; }
.brand-edit__ramp-row { margin-bottom: 0.9rem; padding-bottom: 0.6rem; border-bottom: 1px dashed var(--ui-field-stroke); }
.brand-edit__ramp-row:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
.brand-edit__family-row { align-items: start; }
.brand-edit__family-weights { display: flex; flex-direction: column; gap: 0.3rem; flex: 1 1 220px; min-width: 220px; }
.mk-specimen { padding: 1rem 1.1rem; }
.mk-specimen__head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.mk-specimen__label { margin: 0; }
.mk-specimen__token { font-size: var(--ui-font-body-s-font-size); color: var(--ui-text-content-body-muted); }
.mk-specimen__sample { margin: 0.5rem 0; overflow-wrap: anywhere; }
.mk-specimen__facts { margin: 0; }
.mk-specimen__use { margin: 0.25rem 0 0; }
.mk-face-sample { font-size: 1.25rem; }
.mk-matrix td, .mk-matrix th { white-space: normal; min-width: 4rem; font-size: 0.75rem; }
.mk-matrix__used { color: var(--ui-text-content-success); }
.mk-matrix__kit-only { color: var(--ui-text-content-body-muted); }
