/* ═══════════════════════════════════════════════════════════════════════════
   LumaRef platform tokens (Slice A2, finalized Slice H)

   Layer 1: PRIMITIVES — the raw palette, mirroring marketing's @theme
            (app/assets/tailwind/application.css). Never consumed directly by
            components; only referenced by the semantic layer below.
   Layer 2: SEMANTIC TOKENS — purpose-named colors consumed by components.
   Layer 3: ONE-OFF LEGACY ACCENTS — exact-value colors with no reusable
            semantic role (see that section's own header for detail).

   This file is the ONLY place under platform/ (any nesting, any .css file)
   allowed to contain a raw color literal (hex/rgb/hsl/lab/lch/oklch/named) —
   enforced by the CSS half
   of test/integration/no_inline_styles_test.rb. Every other platform CSS
   file must consume one of these tokens via var(), optionally through
   color-mix(). The DEPRECATED MIGRATION ALIASES that used to live below
   (--green, --ink, --muted, etc., repointing the old green variable system at
   these semantic tokens) were removed in Slice H once every reference was
   rewritten directly to a semantic token.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Layer 1: primitive palette ─────────────────────────────────────────── */
:root {
  /* Blue (LumaRef brand — 600 = #2563EB, mirrors marketing brand-*) */
  --color-blue-50:  #eff6ff;
  --color-blue-100: #dbeafe;
  --color-blue-200: #bfdbfe;
  --color-blue-300: #93c5fd;
  --color-blue-400: #60a5fa;
  --color-blue-500: #3b82f6;
  --color-blue-600: #2563eb;
  --color-blue-700: #1d4ed8;
  --color-blue-800: #1e40af;
  --color-blue-900: #1e3a8a;
  --color-blue-950: #172554;

  /* Navy (heading ink, mirrors marketing brand-900) */
  --color-navy-900: #0b132b;

  /* Green (success only — values preserved from the legacy success palette) */
  --color-green-50:  #f2fbf2;
  --color-green-100: #dcfce7;
  --color-green-200: #b7dfc0;
  --color-green-300: #a8d5b5;
  --color-green-600: #0aad0a;
  --color-green-700: #059405;
  --color-green-800: #155228;

  /* Amber / warning (values preserved from legacy --warn / --amber) */
  --color-amber-50:  #fffbeb;
  --color-amber-100: #fff7ed;
  --color-amber-200: #fed7aa;
  --color-amber-600: #c2410c;
  --color-amber-700: #b45309;

  /* Red / danger (values preserved from legacy --bad and friends) */
  --color-red-50:  #fff5f5;
  --color-red-100: #fee2e2;
  --color-red-200: #fecaca;
  --color-red-500: #ef4444;
  --color-red-600: #e53e3e;
  --color-red-700: #b91c1c;

  /* Purple (values preserved from legacy --purple) */
  --color-purple-50:  #faf5ff;
  --color-purple-100: #f3e8ff;
  --color-purple-600: #9333ea;
  --color-purple-700: #7e22ce;

  /* Indigo (annotation_profiles "Shared" badge — a distinct hue with no
     close existing match; values preserved from that badge's legacy hex) */
  --color-indigo-50:  #eef2ff;
  --color-indigo-100: #dde4ff;
  --color-indigo-200: #c5d0f5;
  --color-indigo-600: #3b5bdb;
  --color-indigo-700: #2f4ac4;

  /* Zinc neutrals (mirrors marketing surface/text neutrals) */
  --color-zinc-50:  #fafafa;
  --color-zinc-100: #f4f4f5;
  --color-zinc-200: #e4e4e7;
  --color-zinc-300: #d4d4d8;
  --color-zinc-400: #a1a1aa;
  --color-zinc-500: #71717a;
  --color-zinc-600: #52525b;
  --color-zinc-700: #3f3f46;
  --color-zinc-800: #27272a;
  --color-zinc-900: #18181b;

  /* True black/white — used only via color-mix() for translucent
     overlays/shadows/borders (no page ever sets a flat black background) */
  --color-black: #000000;
}

/* ── Layer 2: semantic tokens ───────────────────────────────────────────── */
:root {
  /* Action / interaction */
  --color-action-primary:       var(--color-blue-600);
  --color-action-primary-hover: var(--color-blue-700);
  --color-text-on-action:       #ffffff;
  --color-focus-ring:           color-mix(in srgb, var(--color-blue-500) 40%, transparent);

  /* Surfaces */
  --color-canvas:  var(--color-zinc-50);
  --color-surface: #ffffff;
  --color-border:  var(--color-zinc-200);

  /* Text */
  --color-text-primary: var(--color-navy-900);
  --color-text-muted:   var(--color-zinc-500);
  --color-text-subtle:  var(--color-zinc-400);

  /* Status — success stays green; the rest keep their legacy hues */
  --color-status-success:      var(--color-green-700);
  --color-status-success-tint: var(--color-green-50);
  --color-status-danger:       var(--color-red-600);
  --color-status-danger-tint:  var(--color-red-50);
  --color-status-warning:      var(--color-amber-600);
  --color-status-warning-tint: var(--color-amber-100);
  --color-status-info:         var(--color-blue-600);
  --color-status-info-tint:    var(--color-blue-50);
  --color-status-purple:       var(--color-purple-700);
  --color-status-purple-tint:  var(--color-purple-50);

  /* Status tint borders (a shade between the tint bg and the solid status
     color — used by tinted pill/panel border companions across pages/*.css) */
  --color-status-success-tint-border: var(--color-green-200);
  --color-status-warning-tint-border: #ffd77a;
  --color-status-danger-tint-border:  #f5c6cb;

  /* Links */
  --color-link: var(--color-action-primary);

  /* Overlay backdrop (modal scrim — distinct from the near-black lightbox
     scrim below, this one carries a faint navy tint) */
  --color-backdrop: #141a24;

  /* ── Foundation extras ── */
  /* Spacing scale (consumed by the Slice G u- utility layer) */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* Radii */
  --radius-sm:   6px;
  --radius-md:   8px;
  --radius-lg:   12px;
  --radius-xl:   16px;
  --radius-full: 9999px;

  /* Elevation */
  --shadow-card:       0 1px 3px 0 rgb(0 0 0 / 0.08), 0 1px 2px -1px rgb(0 0 0 / 0.06);
  --shadow-card-hover: 0 4px 16px 0 rgb(0 0 0 / 0.10), 0 2px 4px -2px rgb(0 0 0 / 0.06);

  /* Type families — Satoshi via Fontshare (matches marketing) */
  --font-sans:    "Satoshi", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Satoshi", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono:    ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Monaco, "Courier New", monospace;
}

/* ── Layer 3: one-off legacy accents (Slice H) ─────────────────────────────
   Exact-value captures of colors that were flagged across `pages/*.css` as
   "kept as literal, deferred to Slice H" during the G3 burn-down — each one
   is a genuine one-off (a distinct hue with no clean semantic-token mapping,
   or a page-specific micro-palette) rather than a reusable semantic role.
   Values are preserved byte-for-byte from their prior literal so moving them
   here is zero visual change; they intentionally are NOT promoted to
   Layer 2 semantic names because nothing else in the app shares their role. */
:root {
  /* Annotation-review SVG overlay + status-dot palette (sections/rescan-style
     detection markup) — a deliberately distinct, high-contrast system for
     boxes/dots over scanned page images, tuned for visibility rather than
     matching the standard status tokens (see annotation_reviews.css header) */
  --color-annot-approved:        #28b450;
  --color-annot-approved-fill:   #7dda73;
  --color-annot-rejected:        #b42828;
  --color-annot-exclusion:       #dc5050;
  --color-annot-exclusion-tint:  #fff0f0;
  --color-annot-subdelete-hover: #fde8e8;
  --color-annot-detected:        #c89b00;
  --color-annot-detected-fill:   #ffc107;
  --color-annot-detected-stroke: #b48c00;
  --color-annot-spinner:         #c8a000;
  --color-annot-code-chip-bg:    #eef;

  /* Export-packages OEM-sync "stalled" banner — a warm one-off treatment
     distinct enough from the standard warning token to keep literal-exact */
  --color-stalled-bg:     #fef9e7;
  --color-stalled-border: #f9e79f;
  --color-stalled-text:   #7d6608;

  /* Pairing-maps "all clear" empty state + warning-panel override — distinct
     tint pairings that don't exactly match any existing status token */
  --color-pair-all-clear-bg:     #e8f5ee;
  --color-pair-all-clear-border: #bfe5cf;
  --color-pm-warn-bg:            #fffdf7;
  --color-pm-warn-border:        #f0e0bb;

  /* Shared by pdf_ingestions/web_ingestions "content unchanged" flash — a
     distinct gold border with no nearby primitive */
  --color-flash-warning-border: #d4a700;
}
/* ═══════════════════════════════════════════════════════════════════════════
   LumaRef platform typography (Slice B)

   Canonical type scale, extracted from platform/legacy.css and re-expressed
   against semantic tokens (no DEPRECATED MIGRATION ALIASES — see
   platform/tokens.css). Loads after tokens, before components/legacy so the
   base element styles form the foundation of the cascade.
   ═══════════════════════════════════════════════════════════════════════════ */

h1 {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.02em;
  margin: 0 0 20px;
  color: var(--color-text-primary);
}

h2 {
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
  margin: 0 0 12px;
  color: var(--color-text-primary);
}

h3 {
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.3;
  margin: 0 0 8px;
  color: var(--color-text-primary);
}

h4 {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  margin: 0 0 6px;
  color: var(--color-text-muted);
}

/* Eyebrow / section label */
h5 {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.3;
  margin: 0 0 6px;
  color: var(--color-text-muted);
}

p {
  font-size: 14px;
  line-height: 1.6;
  margin: 0 0 12px;
  color: var(--color-text-primary);
}

blockquote {
  margin: 12px 0;
  padding: 4px 16px;
  border-left: 3px solid var(--color-border);
  font-size: 14px;
  line-height: 1.6;
  color: var(--color-text-muted);
}

strong {
  font-weight: 700;
  color: var(--color-text-primary);
}

small {
  font-size: 12px;
  color: var(--color-text-muted);
}

label {
  display: block;
  font-weight: 600;
  font-size: 13px;
  margin: 14px 0 4px;
  color: var(--color-text-primary);
}

/* Inline code / part numbers — brand-tinted pill */
code {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  background: var(--color-blue-50);
  color: var(--color-blue-700);
  border: 1px solid var(--color-blue-100);
  border-radius: var(--radius-full);
  padding: 1px 8px;
}

/* ── Text color utilities ─────────────────────────────────────────────── */
.text-muted   { color: var(--color-text-muted); }
.text-subtle  { color: var(--color-text-subtle); }
.text-danger  { color: var(--color-status-danger); }
.text-success { color: var(--color-status-success); }
.text-info    { color: var(--color-status-info); }
.text-brand   { color: var(--color-action-primary); }
/* ═══════════════════════════════════════════════════════════════════════════
   Badges / status pills (Slice C)

   Compound state classes are test-referenced by exact selector — DO NOT
   rename (see test/integration/ai_indexing_test.rb, section_rescan_test.rb,
   curation_editing_test.rb, terminology_test.rb).
   ═══════════════════════════════════════════════════════════════════════════ */

.badge {
  display: inline-block;
  border-radius: var(--radius-full);
  padding: 2px 10px;
  font-size: 11px;
  font-weight: 600;
  font-family: var(--font-sans);
}

/* Success */
.badge.matched, .badge.ok, .badge.complete, .badge.processed {
  background: var(--color-status-success-tint);
  color: var(--color-status-success);
}

/* Danger */
.badge.unmatched, .badge.failed, .badge.danger {
  background: var(--color-status-danger-tint);
  color: var(--color-status-danger);
}

/* Warning */
.badge.ambiguous, .badge.skipped {
  background: var(--color-amber-50);
  color: var(--color-amber-700);
}

/* Neutral */
.badge.uploaded, .badge.pending, .badge.queued {
  background: var(--color-zinc-100);
  color: var(--color-text-muted);
}

/* Info */
.badge.processing, .badge.running {
  background: var(--color-status-info-tint);
  color: var(--color-status-info);
}

/* Purple */
.badge.suspect, .badge.needs-verification {
  background: var(--color-status-purple-tint);
  color: var(--color-status-purple);
}

/* Neutral + border */
.badge.source-pending {
  background: var(--color-zinc-100);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
}
/* ═══════════════════════════════════════════════════════════════════════════
   Buttons (Slice C)

   Base .btn IS the primary action style — no separate .btn-primary variant
   selector is needed. .btn.btn-primary is kept only as a legacy alias for
   older markup and resolves to the exact same declarations.
   ═══════════════════════════════════════════════════════════════════════════ */

.btn {
  display: inline-block;
  background: var(--color-action-primary);
  color: var(--color-text-on-action) !important;
  border: none;
  border-radius: var(--radius-lg);
  padding: 8px 16px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  line-height: 1.4;
}
.btn:hover { opacity: 0.9; text-decoration: none !important; }
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--color-focus-ring);
}
.btn:active { transform: scale(0.98); }
.btn:disabled, .btn[disabled] { opacity: 0.45; cursor: not-allowed; pointer-events: none; }

.btn.secondary {
  background: var(--color-surface);
  color: var(--color-zinc-700) !important;
  border: 1px solid var(--color-border);
}
.btn.secondary:hover { background: var(--color-zinc-100); }

.btn.ghost {
  background: transparent;
  color: var(--color-text-muted) !important;
  border: none;
}
.btn.ghost:hover { background: var(--color-zinc-100); color: var(--color-text-primary) !important; }

.btn.danger {
  background: var(--color-status-danger-tint);
  color: var(--color-status-danger) !important;
  border: 1px solid var(--color-red-200);
}
.btn.danger:hover { background: var(--color-red-100); }

.btn.small { padding: 5px 12px; font-size: 12px; }

/* Legacy alias — .btn-primary markup maps to the same declarations as base .btn */
.btn.btn-primary {
  background: var(--color-action-primary);
  color: var(--color-text-on-action) !important;
}
/* ═══════════════════════════════════════════════════════════════════════════
   Cards / panels (Slice C)
   ═══════════════════════════════════════════════════════════════════════════ */

.panel,
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  padding: 20px 24px;
  margin-bottom: 20px;
}

.run-review-stat {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 12px 18px;
  min-width: 96px;
}
.run-review-stat .val { font-size: 22px; font-weight: 700; line-height: 1.1; }
.run-review-stat .lbl {
  font-size: 11px;
  color: var(--color-text-muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  margin-top: 2px;
}
.run-review-stat.warn { border-color: var(--color-amber-200); background: var(--color-status-warning-tint); }
.run-review-stat.warn .val { color: var(--color-status-warning); }
.run-review-stat.ok .val { color: var(--color-status-success); }

/* ═══════════════════════════════════════════════════════════════════════════
   Stat cards (Slice D) — used by shared/_stat_card.html.erb
   ═══════════════════════════════════════════════════════════════════════════ */
.stat-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 16px 18px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.stat-value {
  font-size: 30px;
  font-weight: 700;
  line-height: 1;
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.5px;
}
.stat-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
.stat-sub {
  font-size: 11px;
  color: var(--color-text-subtle);
  margin-top: 1px;
}
.stat-card--info { border-color: var(--color-blue-200); background: var(--color-blue-50); }
.stat-card--info .stat-value { color: var(--color-action-primary-hover); }
.stat-card--success { border-color: var(--color-blue-100); background: var(--color-blue-50); }
.stat-card--success .stat-value { color: var(--color-action-primary-hover); }
.stat-card--purple { border-color: var(--color-purple-100); background: var(--color-purple-50); }
.stat-card--purple .stat-value { color: var(--color-status-purple); }
.stat-card--muted { opacity: 0.45; }

/* ═══════════════════════════════════════════════════════════════════════════
   Page header (Slice D) — used by shared/_page_header.html.erb
   ═══════════════════════════════════════════════════════════════════════════ */
.page-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 20px;
}
.page-header h1 { margin: 0; }
.page-header-subtitle {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--color-text-muted);
}
.page-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--color-text-muted);
}

/* ═══════════════════════════════════════════════════════════════════════════
   Panel header (Slice D) — used by the shared/_panel.html.erb layout partial
   ═══════════════════════════════════════════════════════════════════════════ */
.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: -20px -24px 16px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--color-border);
}
.panel-title { font-size: 13px; font-weight: 700; color: var(--color-text-primary); }
.panel-link { font-size: 11px; font-weight: 700; color: var(--color-action-primary-hover); text-decoration: none; }
.panel-link:hover { text-decoration: underline; }

/* ═══════════════════════════════════════════════════════════════════════════
   Empty state (Slice D) — used by shared/_empty_state.html.erb
   ═══════════════════════════════════════════════════════════════════════════ */
.empty-state {
  text-align: center;
  padding: 40px 20px;
  color: var(--color-text-muted);
}
.empty-state-icon { font-size: 28px; margin-bottom: 8px; }
.empty-state-heading { font-size: 14px; font-weight: 700; color: var(--color-text-primary); margin: 0 0 4px; }
.empty-state-body { font-size: 13px; color: var(--color-text-muted); margin: 0 0 12px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Forms (Slice C)
   ═══════════════════════════════════════════════════════════════════════════ */

input[type=text], input[type=email], input[type=password], select, textarea {
  width: 100%; max-width: 480px;
  padding: 9px 12px;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  font-family: var(--font-sans);
  font-size: 14px;
  background: var(--color-surface);
  color: var(--color-text-primary);
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--color-action-primary);
  box-shadow: 0 0 0 3px var(--color-focus-ring);
}

.hint { color: var(--color-text-muted); font-size: 13px; margin-top: 2px; }
.muted { color: var(--color-text-muted); }
.field-errors { color: var(--color-status-danger); font-size: 13px; margin: 8px 0; }

.checkboxes label { font-weight: 400; display: flex; gap: 8px; align-items: flex-start; margin: 8px 0; max-width: 640px; }
.checkboxes input { margin-top: 4px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Shared image lightbox chrome (Slice G3)

   Extracted from catalog_products/image_gallery.html.erb + the identical
   duplicate that was in annotation_reviews/show.html.erb's inline <style> —
   the two views' lightbox markup/JS (#lgx-overlay, `.lgx-*`, `[data-lightbox-*]`)
   is byte-for-byte the same feature, so it becomes one shared component
   instead of two page-scoped copies. Both pages still own their own inline
   `<script>` wiring (open/close/prev/next) — only the chrome CSS moved here.

   The pure-black scrim and drop-shadow use the --color-black primitive via
   color-mix() (Slice H) — color-mix(in srgb, black P%, transparent)
   reproduces the original rgba(0,0,0,P) exactly.
   ═══════════════════════════════════════════════════════════════════════════ */

.lgx-overlay {
  display: none; position: fixed; inset: 0; z-index: 9000;
  background: color-mix(in srgb, var(--color-black) 88%, transparent); align-items: center; justify-content: center;
}
.lgx-overlay.open { display: flex; }
.lgx-inner {
  position: relative; display: flex; flex-direction: column;
  align-items: center; max-width: 90vw;
}
.lgx-img-wrap { position: relative; line-height: 0; }
.lgx-img-wrap img {
  max-width: 88vw; max-height: 80vh; border-radius: 4px;
  box-shadow: 0 8px 40px color-mix(in srgb, var(--color-black) 60%, transparent); object-fit: contain; display: block;
}
.lgx-close {
  position: absolute; top: -14px; right: -14px;
  background: color-mix(in srgb, var(--color-surface) 15%, transparent); border: none; color: var(--color-surface);
  width: 32px; height: 32px; border-radius: 50%; font-size: 20px; line-height: 1;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.lgx-close:hover { background: color-mix(in srgb, var(--color-surface) 30%, transparent); }
.lgx-caption { color: var(--color-surface); font-size: 13px; margin-top: 10px; text-align: center; opacity: 0.85; }
.lgx-counter { color: color-mix(in srgb, var(--color-surface) 45%, transparent); font-size: 11px; margin-top: 4px; }
.lgx-prev, .lgx-next {
  position: fixed; top: 50%; transform: translateY(-50%);
  background: color-mix(in srgb, var(--color-surface) 12%, transparent); border: none; color: var(--color-surface);
  width: 44px; height: 44px; border-radius: 50%; font-size: 22px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background 0.15s;
}
.lgx-prev:hover, .lgx-next:hover { background: color-mix(in srgb, var(--color-surface) 28%, transparent); }
.lgx-prev { left: 14px; }
.lgx-next { right: 14px; }
[data-lightbox-src] { cursor: zoom-in; }
/* ═══════════════════════════════════════════════════════════════════════════
   Shared lightbox/zoom modal chrome (Slice G3)

   Extracted from duplicate copies of the same rules in pairing_maps/show and
   run_previews/show's inline <style> blocks (both pages render an
   `#zoom-modal` with `.lg-modal.lg-zoom`) — genuinely shared markup, so it
   becomes one component class here instead of two page-scoped duplicates.
   Feature-specific modal content (pairing_maps' page-picker grid) stays
   page-scoped in platform/pages/pairing_maps.css.
   ═══════════════════════════════════════════════════════════════════════════ */

.lg-modal {
  position: fixed; inset: 0; background: color-mix(in srgb, var(--color-backdrop) 62%, transparent); z-index: 200;
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.lg-modal[hidden] { display: none; }
.lg-zoom img { max-width: 92vw; max-height: 90vh; display: block; border-radius: 8px; background: var(--color-surface); }
/* ═══════════════════════════════════════════════════════════════════════════
   Navigation — sidebar, workflow nav, doc stepper, run-review tabs (Slice C)

   Selectors below are test-referenced by exact class name — DO NOT rename:
   nav.workflow .step / .step.active (terminology_test.rb), .doc-stepper .step
   / .step.active (terminology_test.rb), nav.run-review-tabs
   (advanced_tools_test.rb), #nav-review-count (review_queue_test.rb).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Sidebar ──────────────────────────────────────────── */
.sidebar {
  width: 224px;
  flex-shrink: 0;
  background: var(--color-surface);
  border-right: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0; left: 0; bottom: 0;
  z-index: 40;
  overflow-y: auto;
}
.sidebar-header {
  padding: 20px 16px 12px;
  border-bottom: 1px solid var(--color-zinc-100);
}
.sidebar-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
}
.sidebar-logo:hover { text-decoration: none; }
.sidebar-biz {
  display: block;
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  color: var(--color-text-muted);
  margin-top: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sidebar-upload {
  margin: 10px 12px 0;
}
.sidebar-upload a {
  display: block;
  background: var(--color-action-primary);
  color: var(--color-text-on-action) !important;
  text-align: center;
  border-radius: 10px;
  padding: 7px 12px;
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
}
.sidebar-upload a:hover { opacity: 0.9; text-decoration: none; }

/* ── Sidebar nav (workflow) ─────────────────────────── */
nav.workflow {
  flex: 1;
  padding: 12px 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
nav.workflow .nav-group-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-zinc-300);
  padding: 10px 8px 4px;
  display: block;
}
nav.workflow .nav-group-label:first-child {
  padding-top: 4px;
}
nav.workflow .step {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 10px;
  color: var(--color-text-muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  position: relative;
}
nav.workflow .step:hover {
  background: var(--color-zinc-100);
  color: var(--color-text-primary);
  text-decoration: none;
}
nav.workflow .step.active {
  background: var(--color-blue-50);
  color: var(--color-action-primary-hover);
  font-weight: 600;
}
nav.workflow .step .n { display: none; } /* legacy compat, hidden in sidebar */
nav.workflow .step svg { flex-shrink: 0; opacity: 0.7; }
nav.workflow .step.active svg { opacity: 1; }
nav.workflow .arrow { display: none; } /* hide horizontal arrows */
nav.workflow .divider { display: none; }
nav.workflow .sub-nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-left: 16px;
  margin-top: 2px;
}
nav.workflow .sub-nav a {
  display: block;
  padding: 5px 10px;
  border-radius: 8px;
  color: var(--color-text-muted);
  font-size: 12px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
nav.workflow .sub-nav a:hover {
  background: var(--color-zinc-100);
  color: var(--color-text-primary);
  text-decoration: none;
}
nav.workflow .sub-nav a.active {
  background: var(--color-blue-50);
  color: var(--color-action-primary-hover);
  font-weight: 600;
}
/* Icon glyph inside a .sub-nav link (e.g. the "+" before "Add Manual") —
   .sub-nav a is display:block, not flex, so flex-shrink:0 has no visual
   effect here (kept anyway to match the exact declarations the original
   inline style= had). */
nav.workflow .sub-nav-icon { vertical-align: -1px; margin-right: 3px; flex-shrink: 0; }
nav.workflow .plain { /* same as .step */
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 10px;
  color: var(--color-text-muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
}
nav.workflow .plain:hover { background: var(--color-zinc-100); color: var(--color-text-primary); text-decoration: none; }
nav.workflow .plain.active { background: var(--color-blue-50); color: var(--color-action-primary-hover); font-weight: 600; }
nav.workflow .nav-divider {
  height: 1px;
  background: var(--color-zinc-100);
  margin: 6px 8px;
}

/* Attention badge in nav */
.nav-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px; height: 18px;
  border-radius: 999px;
  padding: 0 5px;
  font-size: 11px;
  font-weight: 700;
  background: var(--color-red-100);
  color: var(--color-status-danger);
  margin-left: auto;
  flex-shrink: 0;
}
.nav-count.zero {
  background: var(--color-zinc-100);
  color: var(--color-text-subtle);
}

/* Sidebar footer */
.sidebar-footer {
  padding: 12px 10px;
  border-top: 1px solid var(--color-zinc-100);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sidebar-footer-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--color-text-muted);
}
.sidebar-footer-row a { color: var(--color-text-muted); }
.sidebar-footer-row a:hover { color: var(--color-text-primary); text-decoration: none; }
/* layouts/platform.html.erb sidebar footer — the "·" separator, the "Sign
   out" button, and the truncated email line below it. Color decision:
   var(--faint) → --color-zinc-300, the standard alias resolution used
   throughout Slice G3. */
.sidebar-footer-dot { color: var(--color-zinc-300); }
/* .btn.small.sidebar-signout-btn (not just .sidebar-signout-btn) matches the
   specificity of components/buttons.css's .btn.small rule so this override
   wins regardless of require_tree load order, same specificity fix applied
   to every other button/input override earlier in Slice G3. */
.btn.small.sidebar-signout-btn { padding: 2px 6px; font-size: 11px; }
.sidebar-footer-email { font-size: 11px; color: var(--color-zinc-300); }

/* Workspace switcher in sidebar */
.sidebar-switcher {
  padding: 8px 10px;
  border-bottom: 1px solid var(--color-zinc-100);
}
.sidebar-switcher select {
  width: 100%;
  font-size: 12px;
  padding: 5px 8px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-zinc-700);
}

/* ── Document lifecycle stepper ─────────────────────── */
.doc-stepper { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.doc-stepper .step {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 12px; border-radius: 10px;
  color: var(--color-text-muted); font-weight: 500; font-size: 13px; white-space: nowrap;
}
.doc-stepper .step .n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%;
  font-size: 11px; font-weight: 700;
  background: var(--color-zinc-100); color: var(--color-text-muted); border: 1px solid var(--color-border);
}
.doc-stepper .step.done .n { background: var(--color-blue-50); color: var(--color-action-primary-hover); border-color: var(--color-blue-100); }
.doc-stepper .step.active { background: var(--color-blue-50); color: var(--color-action-primary-hover); font-weight: 600; }
.doc-stepper .step.active .n { background: var(--color-action-primary); color: var(--color-text-on-action); border-color: var(--color-action-primary); }
.doc-stepper .arrow { color: var(--color-zinc-300); font-size: 12px; padding: 0 1px; user-select: none; }

/* ── Run review tabs ──────────────────────────────────── */
nav.run-review-tabs {
  display: flex; gap: 0; border-top: 1px solid var(--color-border); padding: 0 8px;
  margin-top: 16px; overflow-x: auto;
}
nav.run-review-tabs .tab {
  display: flex; align-items: center; gap: 8px; padding: 13px 16px;
  margin-bottom: -1px; border-bottom: 2px solid transparent;
  color: var(--color-text-muted); font-weight: 600; font-size: 13px;
  text-decoration: none; white-space: nowrap;
}
nav.run-review-tabs .tab:hover { color: var(--color-text-primary); background: var(--color-canvas); text-decoration: none; }
nav.run-review-tabs .tab.active { color: var(--color-action-primary-hover); border-bottom-color: var(--color-action-primary); }
nav.run-review-tabs .tab .step-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%; font-size: 11px; font-weight: 700;
  background: var(--color-zinc-100); color: var(--color-text-muted); flex-shrink: 0;
}
nav.run-review-tabs .tab.active .step-num { background: var(--color-action-primary); color: var(--color-text-on-action); }
nav.run-review-tabs .tab.done .step-num { background: var(--color-blue-50); color: var(--color-action-primary-hover); }
/* ═══════════════════════════════════════════════════════════════════════════
   Run-review shared chrome (Slice G3) — mechanically extracted from the
   shared/_run_review_header, shared/_run_review_actions, and
   shared/_run_review_results_entry partials' inline style= attributes.

   These are true shared *components* (not page-scoped): all three
   partials are rendered from multiple controllers —
   shared/_run_review_header and shared/_run_review_actions from
   annotation_reviews/show, run_previews/show, pairing_maps/show, and
   pipeline_runs/show; shared/_run_review_results_entry from
   documents/show — so this lives under platform/components/ rather than
   platform/pages/<controller>.css, consistent with how the shared
   lightbox chrome was handled earlier in Slice G3.

   No color decisions were needed — every style= here was pure
   layout/spacing (flex, margin, gap), no hex/var() color values.
   ═══════════════════════════════════════════════════════════════════════════ */

/* _run_review_header */
.rr-title-col { flex: 1; min-width: 0; }

/* _run_review_results_entry */
.rr-entry-actions-row { margin: 14px 0 0; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
/* ═══════════════════════════════════════════════════════════════════════════
   Tables (Slice C)
   ═══════════════════════════════════════════════════════════════════════════ */

table.list { width: 100%; border-collapse: collapse; font-size: 14px; }
table.list th {
  text-align: left;
  color: var(--color-text-subtle);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 10px 12px;
  border-bottom: 1px solid var(--color-border);
}
table.list td { padding: 11px 12px; border-bottom: 1px solid var(--color-zinc-100); vertical-align: middle; }
table.list td.catalog-cell { vertical-align: middle; width: 220px; max-width: 240px; }
table.list tr:last-child td { border-bottom: none; }
table.list tr:hover td { background: var(--color-canvas); }

.actions { white-space: nowrap; }
/* ═══════════════════════════════════════════════════════════════════════════
   Platform legacy CSS body (Slice A3)

   Moved verbatim from platform.css, preserving EXACT rule order so cascade and
   specificity resolution stay identical to the post-A2 baseline. Do not reorder,
   dedupe, or tidy. Slices B/C incrementally extract typography and component
   rules from here into their canonical files; Slice H removes what remains or
   ensures it only consumes semantic tokens.
   ═══════════════════════════════════════════════════════════════════════════ */

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--color-canvas);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.55;
}
a { color: var(--color-action-primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── App shell: sidebar + main ─────────────────────────── */
.app-shell {
  display: flex;
  min-height: 100vh;
}

/* ── Main content area ─────────────────────────────────── */
.app-main {
  margin-left: 224px;
  flex: 1;
  min-width: 0;
}
main {
  max-width: 1200px;
  margin: 0 auto;
  padding: 28px 28px;
}

/* ── Flash messages ────────────────────────────────────── */
.flash { padding: 10px 16px; border-radius: 10px; margin-bottom: 16px; font-size: 13px; }
.flash.notice { background: var(--color-status-success-tint); color: var(--color-status-success); border: 1px solid var(--color-green-100); }
.flash.alert { background: var(--color-status-danger-tint); color: var(--color-status-danger); border: 1px solid var(--color-red-200); }

/* Indeterminate progress-bar animation (used by export_packages/index inline style) */
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* ── Auth card ────────────────────────────────────────── */
.auth-card { max-width: 420px; margin: 60px auto; }

/* ── Run review header ───────────────────────────────── */
.run-review-header { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 16px; margin-bottom: 20px; overflow: hidden; }
.run-review-header-top { padding: 18px 24px 0; }
.run-review-crumb { font-size: 13px; color: var(--color-text-muted); margin: 0 0 8px; }
.run-review-crumb a { color: var(--color-text-muted); }
.run-review-crumb a:hover { color: var(--color-action-primary); }
.run-review-title-row { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.run-review-title { font-family: var(--font-display); font-size: 20px; font-weight: 400; margin: 0; line-height: 1.3; color: var(--color-text-primary); }
.run-review-meta { font-size: 13px; color: var(--color-text-muted); margin: 4px 0 0; }

/* ── Run review stats / banners ─────────────────────── */
.run-review-page-intro { margin-bottom: 18px; }
.run-review-page-intro h2 { font-size: 17px; margin: 0 0 6px; }
.run-review-stats { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px; }
.run-review-next {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  background: var(--color-status-warning-tint); border: 1px solid var(--color-amber-200); border-radius: 12px;
  padding: 16px 20px; margin-bottom: 20px;
}
.run-review-next.all-clear { background: var(--color-status-success-tint); border-color: var(--color-green-100); }
.run-review-next-body { flex: 1; min-width: 200px; }
.run-review-next-body strong { display: block; margin-bottom: 2px; font-size: 15px; }
.run-review-actions {
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 12px;
  padding: 18px 24px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 4px;
}
.run-review-actions-body { flex: 1; min-width: 180px; }
.run-review-actions-body strong { display: block; font-size: 15px; }

/* Review workflow card */
.review-workflow-card { background: var(--color-blue-50); border: 1px solid var(--color-blue-100); border-radius: 12px; padding: 16px 20px; margin-bottom: 16px; }
.review-workflow-card .steps { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.review-workflow-card .mini-step {
  display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600;
  color: var(--color-text-muted); background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: 999px; padding: 4px 12px;
}

/* ── Section workspace ───────────────────────────────── */
.whole-manual-tools { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border); display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.workspace { display: grid; grid-template-columns: minmax(300px, 36%) 1fr; gap: 20px; align-items: start; }
@media (max-width: 980px) { .workspace { grid-template-columns: 1fr; } }
table.list td[data-field="match"] { min-width: 180px; }
table.list td[data-field="description"] { min-width: 200px; }
.section-status-strip { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; padding: 10px 12px; background: var(--color-canvas); border: 1px solid var(--color-border); border-radius: 10px; }
.status-track { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; }
.status-track-label { font-weight: 700; color: var(--color-text-primary); }
.bom-tools-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--color-border); }
@media (max-width: 820px) { .bom-tools-grid { grid-template-columns: 1fr; } }
.bom-tool-card { border: 1px solid var(--color-border); border-radius: 10px; padding: 12px 14px; background: var(--color-surface); display: flex; flex-direction: column; gap: 10px; }
.bom-tool-card-head strong { display: block; font-size: 14px; }
.bom-tool-card-head .hint { margin: 2px 0 0; display: block; }
.bom-tool-card-status { min-height: 1.4em; }
.bom-tool-card-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.section-quality-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--color-border); }
.section-quality-bar-label { flex: 1; min-width: 220px; }
.section-quality-bar-label strong { display: block; font-size: 14px; }
.section-quality-bar-label .hint { margin: 2px 0 0; }
.section-quality-bar-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
tr[data-bbox] { cursor: pointer; }
tr.row-source-locked td { background: var(--color-blue-50); }
.source-column-hint { font-weight: 400; font-size: 11px; color: var(--color-text-muted); }
.view-bom-link { font-size: 13px; font-weight: 600; white-space: nowrap; }
.section-bom-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border); }
.section-bom-toolbar .bom-toolbar-spacer { flex: 1; min-width: 8px; }
.bom-context { font-size: 13px; }
.verification-status { display: block; margin-top: 6px; }
.verification-status .hint { display: inline; margin-left: 6px; }
.bom-tools-busy { display: block; margin-top: 6px; }
.workspace-pages { position: sticky; top: 28px; display: flex; flex-direction: column; gap: 12px; max-height: calc(100vh - 56px); overflow-y: auto; }
.page-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 12px; overflow: hidden; }
.page-card-head { display: flex; gap: 8px; align-items: baseline; padding: 10px 14px; border-bottom: 1px solid var(--color-border); font-size: 13px; }
.page-card img { display: block; width: 100%; height: auto; }
.page-image-wrap { position: relative; }
.region-overlay { position: absolute; border: 2px solid var(--color-action-primary); background: color-mix(in srgb, var(--color-action-primary) 12%, transparent); border-radius: 3px; pointer-events: none; }

/* ── Filter chips ─────────────────────────────────────── */
.filter-chips { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.chip { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--color-border); border-radius: 999px; padding: 4px 12px; font-size: 13px; font-weight: 600; color: var(--color-text-muted); background: var(--color-surface); }
.chip:hover { text-decoration: none; border-color: var(--color-action-primary); color: var(--color-action-primary-hover); }
.chip.active { background: var(--color-blue-50); color: var(--color-action-primary-hover); border-color: var(--color-blue-100); }
.chip .count { background: var(--color-zinc-100); border-radius: 999px; padding: 0 7px; font-size: 11px; }
.chip.active .count { background: var(--color-blue-100); }

/* ── Row highlight ───────────────────────────────────── */
tr.row-highlight td, tr:target td { background: var(--color-amber-50); }

/* ── Needs-attention inbox ───────────────────────────── */
.queue-item { display: flex; gap: 14px; align-items: flex-start; padding: 12px 10px; border-top: 1px solid var(--color-zinc-100); flex-wrap: wrap; }
.queue-item:focus { outline: 2px solid var(--color-action-primary); outline-offset: -2px; border-radius: 8px; background: var(--color-blue-50); }
.queue-item-body { flex: 1; min-width: 260px; }
.queue-item-actions { display: flex; gap: 6px; align-items: center; }
.queue-fix-form { width: 100%; display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; background: var(--color-canvas); border-radius: 8px; padding: 10px 12px; margin-top: 8px; }

/* ── Progress bar ─────────────────────────────────────── */
.progress-track { background: var(--color-zinc-100); border-radius: 999px; height: 8px; overflow: hidden; flex: 1; min-width: 140px; max-width: 320px; }
.progress-fill { display: block; background: var(--color-action-primary); height: 100%; width: var(--pr-fill, 0); border-radius: 999px; transition: width .45s ease; }
.phase-row { display: flex; align-items: center; gap: 12px; padding: 8px 0; }
.phase-name { width: 160px; font-weight: 600; font-size: 13px; }
.phase-row [data-role="counts"] { flex-shrink: 0; min-width: 48px; text-align: right; font-variant-numeric: tabular-nums; }

/* ── Misc ─────────────────────────────────────────────── */
.identity-preview { margin: 12px 0 16px; padding: 12px 14px; background: var(--color-canvas); border: 1px solid var(--color-border); border-radius: 10px; font-size: 13px; line-height: 1.55; }
.identity-preview-body code { font-size: 12px; }
.identity-preview-body > div { margin-top: 4px; }
.convert-again-panel { border: 1px solid var(--color-border); border-radius: 10px; padding: 10px 14px; background: var(--color-canvas); margin-bottom: 16px; }
.convert-again-panel summary { cursor: pointer; color: var(--color-text-muted); font-size: 13px; font-weight: 600; list-style: none; }
.convert-again-panel summary::-webkit-details-marker { display: none; }
.convert-again-panel[open] { background: var(--color-surface); }
.convert-again-panel[open] summary { color: var(--color-text-primary); margin-bottom: 10px; }
.convert-again-body { padding-top: 10px; border-top: 1px solid var(--color-border); }
.catalog-status { font-size: 13px; line-height: 1.4; }
.catalog-status .badge { margin-bottom: 4px; }
.catalog-status-line { margin: 0; font-variant-numeric: tabular-nums; }
.catalog-status-line + .catalog-status-line { margin-top: 2px; }
.catalog-attention { color: var(--color-status-warning); font-weight: 600; }
.catalog-status-link { display: inline-block; margin-top: 6px; font-size: 13px; font-weight: 600; }
.catalog-status-retry { margin-top: 8px; }
/* ═══════════════════════════════════════════════════════════════════════════
   AI indexing (Slice G3) — mechanically extracted from
   ai_indexing/show.html.erb's inline style= attributes on the intro/footer
   hints, the per-row visibility form, and the actions cell.

   margin-left: 4px on the stale badge / public-index button / "publish
   page first" hint used the u-ml-1 utility directly (exact 4px scale
   match) rather than page-scoped classes.

   Two one-off, non-scale values kept as documented literals rather than
   utilities: the 6px form gap and the 5px vertical select padding — both
   are original visual values with no exact match on the 4/8/12/16 space
   scale, so a new off-scale utility wasn't warranted for a single
   consumer.
   ═══════════════════════════════════════════════════════════════════════════ */

.ai-idx-intro-hint { margin: 0 0 var(--space-4); max-width: 720px; }
.ai-idx-footer-hint { max-width: 720px; }

.ai-idx-visibility-form { display: flex; align-items: center; gap: 6px; margin: 0; }
.ai-idx-visibility-select { max-width: 150px; padding: 5px 8px; }
/* ═══════════════════════════════════════════════════════════════════════════
   AI Lab (Slice G3) — mechanically extracted from ai_lab/show.html.erb's
   inline style= attributes on the title badge, query form, generated-answer
   card, and the per-result source cards (attribution header, score/keyword
   badges, page references, content excerpt).

   Color decisions: var(--accent, #3b82f6) (answer card's left accent
   border) → --color-blue-500 (exact hex match; --accent itself was never
   defined in tokens.css, so the fallback was always what actually
   rendered). var(--ink) (chunk excerpt text) → --color-text-primary, the
   same alias resolution as every other Slice G3 commit.

   The keyword-match badge's conditional "margin-left: auto" (present only
   when there's no score badge already claiming it) is now a conditional
   u-ml-auto utility class in the view instead of a ternary inside a style
   attribute.
   ═══════════════════════════════════════════════════════════════════════════ */

.ai-lab-title-badge { font-size: 12px; vertical-align: middle; }

.ai-lab-intro-hint { margin: 0 0 var(--space-4); max-width: 640px; }

/* input.ai-lab-query-input (not just .ai-lab-query-input) ties specificity
   with the base input[type="text"] rule (components/forms.css); source
   order (pages/ loads after components/) then lets this win. */
input.ai-lab-query-input { max-width: 640px; width: 100%; }
.ai-lab-submit-btn { margin-top: var(--space-2); display: block; }

.ai-lab-answer-panel { margin-bottom: var(--space-4); border-left: 3px solid var(--color-blue-500); }
.ai-lab-answer-header { display: flex; align-items: center; gap: var(--space-2); margin-bottom: 10px; }
.ai-lab-answer-label { font-size: 14px; }
.ai-lab-answer-tag { font-size: 11px; }
.ai-lab-answer-body { font-size: 14px; line-height: 1.65; }

.ai-lab-sources-hint { margin: 0 0 var(--space-3); }

.ai-lab-result-header { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; margin-bottom: 6px; }
.ai-lab-result-title { font-size: 15px; }
.ai-lab-result-section { font-size: 14px; }
.ai-lab-score-badge { margin-left: auto; font-size: 12px; }
.ai-lab-keyword-badge { font-size: 12px; }

.ai-lab-page-refs { margin: 0 0 var(--space-2); }

.ai-lab-chunk-preview {
  margin: 0;
  white-space: pre-wrap;
  font-size: 13px;
  line-height: 1.55;
  color: var(--color-text-primary);
  font-family: inherit;
  overflow: hidden;
  max-height: 180px;
}
/* ═══════════════════════════════════════════════════════════════════════════
   AI searches / Insights (Slice G3) — mechanically extracted from
   ai_searches/show.html.erb's inline style= attributes on the "At a
   glance" stat cards, the semantic-search form, and the JS-generated
   result cards (built as string-concatenated HTML in the <script> block —
   inline style= there is just as much a guard violation as in the ERB
   markup, so those got page-scoped classes too).

   Color decisions: var(--bg) (stat card background) → --color-canvas;
   var(--line) (stat card border, JS-generated result card border) →
   --color-border. Both are the standard alias resolutions used throughout
   Slice G3.
   ═══════════════════════════════════════════════════════════════════════════ */

.ais-stats-row { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.ais-stat-card {
  flex: 1;
  min-width: 150px;
  background: var(--color-canvas);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 14px 16px;
}
.ais-stat-label { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
.ais-stat-value { font-size: 22px; font-weight: 700; margin-top: 2px; }

.ais-search-intro { margin-top: -6px; }

.ais-search-form { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.ais-query-field { flex: 1; min-width: 280px; }
/* input.ais-query-input (not just .ais-query-input) ties specificity with
   the base input[type="text"] rule (components/forms.css); source order
   (pages/ loads after components/) then lets this win, matching the
   specificity the original inline style= had. */
input.ais-query-input { max-width: none; }

.ais-results { margin-top: 18px; }

.ais-result-card { border: 1px solid var(--color-border); border-radius: 10px; padding: 12px 16px; margin-bottom: 10px; }
.ais-result-meta { font-size: 12px; margin-bottom: 4px; }
.ais-result-content { white-space: pre-wrap; font-size: 14px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Annotation profiles (Slice G3) — mechanically extracted from
   annotation_profiles/index.html.erb + _form.html.erb's inline <style>
   blocks, with every deprecated alias rewritten to a semantic token and
   every color literal replaced by a token.

   Color decisions:
     var(--muted)→--color-text-muted, var(--line)→--color-border,
     var(--ink)→--color-text-primary, var(--panel)→--color-surface.
     dead var(--panel-alt, #f8f9fa) → var(--color-zinc-50) (nudge, same
     call as the businesses/catalog_products/match_files commits).
     #fff (form input backgrounds) → var(--color-surface) (exact).
     .ap-shared-badge's #eef2ff/#3b5bdb/#c5d0f5 indigo trio has no match in
     the existing LumaRef palette (not blue-info, not purple-status — a
     distinct hue) and the closest existing tokens (purple-50/purple-700)
     are a real, noticeable hue shift, not an imperceptible nudge. Added
     three new indigo primitives to tokens.css's primitive layer (Layer 1
     only — the deprecated-alias block was not touched) for pixel-exact
     fidelity on this one badge.
     .ap-swatch / .ap-color-preview borders now use color-mix(in srgb, black
     P%, transparent) via the --color-black primitive (Slice H), reproducing
     the original rgba(0,0,0,.12)/.15 exactly. .ap-swatch's own background
     (rgba(var(--ap-swatch-rgb), var(--ap-swatch-alpha))) is genuinely
     per-record dynamic data (an admin-chosen highlight color/opacity set via
     inline style in _profile_card.html.erb) — it has no fixed pigment to
     tokenize, so the CSS guard allows rgb()/rgba() calls whose arguments are
     exclusively var() references.
   ═══════════════════════════════════════════════════════════════════════════ */

.ap-page-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }
.ap-page-head h2 { margin: 0; }

.ap-section-head {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--color-text-muted); margin: 20px 0 8px;
}

.ap-list { display: flex; flex-direction: column; gap: 8px; }

.ap-card {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 14px 16px; border: 1px solid var(--color-border); border-radius: 10px;
  background: var(--color-surface);
}
.ap-card-name { font-weight: 600; font-size: 14px; flex: 1; min-width: 0; }
.ap-card-slug { font-size: 12px; color: var(--color-text-muted); font-family: monospace; }
.ap-card-swatches { display: flex; gap: 4px; align-items: center; }
.ap-swatch {
  width: 14px; height: 14px; border-radius: 3px; border: 1px solid color-mix(in srgb, var(--color-black) 12%, transparent);
  flex-shrink: 0;
  background: rgba(var(--ap-swatch-rgb), var(--ap-swatch-alpha));
}
.ap-shared-badge {
  display: inline-block; padding: 2px 8px; border-radius: 8px;
  font-size: 11px; font-weight: 700; background: var(--color-indigo-50); color: var(--color-indigo-600);
  border: 1px solid var(--color-indigo-200);
}
.ap-card-actions { display: flex; gap: 6px; flex-shrink: 0; }

.ap-config-preview { font-size: 12px; color: var(--color-text-muted); flex-basis: 100%; margin-top: 4px; }
.ap-config-pair { display: inline-block; margin-right: 12px; }
.ap-config-pair span { color: var(--color-text-primary); font-weight: 500; }

.ap-empty { color: var(--color-text-muted); font-size: 13px; font-style: italic; padding: 16px 0; }

/* ── Form ── */
.ap-form { max-width: 680px; }
.ap-form-section {
  border: 1px solid var(--color-border); border-radius: 10px;
  background: var(--color-surface); overflow: hidden; margin-bottom: 16px;
}
.ap-form-section-head {
  padding: 12px 16px; border-bottom: 1px solid var(--color-border);
  background: var(--color-zinc-50); font-size: 13px; font-weight: 700;
}
.ap-form-section-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 14px; }

.ap-field { display: flex; flex-direction: column; gap: 4px; }
.ap-field label { font-size: 13px; font-weight: 600; }
.ap-field input[type="text"],
.ap-field input[type="number"],
.ap-field select {
  font: inherit; font-size: 14px; padding: 7px 10px;
  border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface);
  width: 100%; max-width: 340px;
}
.ap-field .hint { font-size: 12px; color: var(--color-text-muted); margin: 0; }

.ap-field-row { display: flex; gap: 16px; flex-wrap: wrap; }
.ap-field-row .ap-field { flex: 1; min-width: 140px; }

.ap-color-preview {
  display: inline-block; width: 24px; height: 24px; border-radius: 4px;
  border: 1px solid color-mix(in srgb, var(--color-black) 15%, transparent); vertical-align: middle; margin-left: 8px;
}

/* ═══ Inline-style burn-down (Slice G3) ═══════════════════════════════════════ */
.ap-intro-hint.hint    { max-width: 660px; margin: 0 0 20px; }
.ap-form-flash         { margin-bottom: 14px; }
.ap-form-errors        { margin: 0; padding-left: 18px; }
.ap-form-actions       { display: flex; gap: 10px; align-items: center; }

.ap-page-header  { margin-bottom: 16px; }
.ap-page-header h2 { margin: 0 0 4px; }
.ap-page-header .hint { margin: 0; }
/* ═══════════════════════════════════════════════════════════════════════════
   Annotation reviews (Slice G3) — mechanically extracted from
   annotation_reviews/show.html.erb's inline <style> block, with every
   deprecated alias rewritten to a semantic token and every color literal
   replaced by a token / token-derived color where a clean mapping exists.

   Color decisions:
     var(--panel)→--color-surface, var(--line)→--color-border,
     var(--muted)→--color-text-muted, var(--ink)→--color-text-primary,
     var(--brand)→--color-action-primary, #fff→--color-surface (exact).
     dead var(--accent, #4f82f3) → --color-action-primary, and every literal
     rgb(79,130,243)/rgba(79,130,243,X) elsewhere in this file is the exact
     same hex — converted directly / via color-mix().
     dead var(--panel-alt, #f8f9fa) → --color-zinc-50 (nudge, same call as
     every prior G3 commit).
     var(--ok) (itself a deprecated alias for --color-status-success) written
     directly as --color-status-success. var(--error) doesn't exist anywhere
     in tokens.css — a latent bug where the "rejected" count silently lost
     its color — now fixed as --color-status-danger, matching its obvious
     intent next to the equivalent --ok/warning spans.
     #fafbfc (page-image placeholder bg) → --color-zinc-50 (nudge).

     Status/asset/publish-package pill pairs harmonized to the LumaRef status
     palette, same table as every prior G3 commit — including this view's
     several near-miss panel-background siblings, which were always meant to
     *be* the tint, just hand-tuned a few points off it per panel:
       success #d4edda/#1a6632, #f6fff8, #f3faf4      → status-success-tint / status-success
       info    #cce5ff/#004085                        → status-info-tint / status-info
       warning #fff3cd/#7a5f00, #fffdf0                → status-warning-tint / status-warning
       danger  #f8d7da/#842029, #fff8f8, #fff3f3       → status-danger-tint / status-danger
     Their border companions (#f5c6cb danger, #ffd77a warning, #b7dfc0
     success, #c8d8ff info) now consume the Slice H
     --color-status-{success,warning,danger}-tint-border tokens (#c8d8ff
     info already had a home — --color-blue-200, used directly below).

     #eef2ff/#3b5bdb/#c5d0f5 (pkg-publisher pill, thumb-xmanual-btn) is the
     exact same trio as the annotation_profiles "Shared" badge — reuses the
     --color-indigo-50/600/200 primitives added in that commit. Their hover
     companions (#dde4ff/#2f4ac4) now use the Slice H --color-indigo-100/700
     primitives, and pkg-download-btn's hover/border (#155228) uses the new
     --color-green-800 primitive.
     #f0f4ff (draw-btn hover, publish-option checked, global banner) →
     --color-blue-50 (nudge, same call as match_files.css). #e8f0fe
     (draw-btn active) → color-mix(action-primary 10%, surface) — computed
     to land within 1/255 per channel of the original. #c8d8ff (global
     banner border) → --color-blue-200 (closest primitive, ~9/255 nudge).
     #fff0f0 (exclusion-mode active bg) has no derivation from the
     exclusion-red literal below that reaches its value, so it got its own
     Slice H primitive, --color-annot-exclusion-tint.

     The "annotation overlay" micro-palette — rgba(125,218,115,X)/
     rgb(40,180,80) (approved), rgba(220,80,80,X)/rgb(180,40,40)/
     rgb(220,80,80) (rejected/exclusion), rgba(255,193,7,X)/rgb(180,140,0)/
     rgb(200,155,0) (detected), and the spinner accent rgb(200,160,0) —
     is a deliberately distinct, high-contrast color system for SVG boxes
     and status dots overlaid on scanned document page images (tuned for
     visibility against varied scan backgrounds, not decorative UI chrome)
     and doesn't cleanly map to any LumaRef status token (each is 10–20/255
     off per channel, a real shift, not a nudge). Slice H captured each
     exact value as its own `--color-annot-*` primitive in tokens.css
     (Layer 3 — "one-off legacy accents") rather than nudging it onto an
     unrelated status token, so every rgb()/rgba() call below now reads
     `color-mix(in srgb, var(--color-annot-*) P%, transparent)` for the
     translucent variants and `var(--color-annot-*)` directly for the solid
     ones — byte-identical rendering, zero visual change.
     rgba(0,0,0,.12/.15) borders now use color-mix(in srgb, var(--color-black)
     P%, transparent). #eef (publish-option code chip bg) → the Slice H
     --color-annot-code-chip-bg primitive (still the same one-off value).

     JS note: the approve/reject fetch handler and the manual draw-box
     creation flow used to write these same literal colors directly via
     `element.style.background = "rgba(...)"` / `rect.setAttribute("fill",
     "rgba(...)")`. Presentation attributes (SVG fill=/stroke=, whether
     server-rendered or JS-set) sit below author CSS in the cascade, so the
     JS now just toggles a `data-box-status` attribute / status class instead
     of writing color strings, and the `[data-box-status="…"]` /
     `.annot-box-swatch.…` rules below reproduce the exact same values —
     this is what let the <script> block's embedded color literals (which
     the ERB guard's regex scans regardless of being inside <script>, not
     just <style>) go away entirely, with zero visual change.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Page chrome ── */
.annot-intro { margin-bottom: 20px; }
.annot-intro h2 { margin: 0 0 4px; }

/* ── Toolbar (profile picker + global actions) ── */
.annot-toolbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 10px;
  padding: 14px 18px; margin-bottom: 20px;
}
.annot-toolbar label { font-size: 13px; font-weight: 600; white-space: nowrap; }
.annot-toolbar select { font: inherit; font-size: 14px; padding: 5px 10px;
                         border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface); }
.annot-toolbar-sep { flex: 1; }

/* ── Section cards ── */
.annot-section { margin-bottom: 24px; }
.annot-section-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 14px 18px; background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: 10px 10px 0 0; border-bottom: none;
}
.annot-section-head h3 { margin: 0; font-size: 15px; }
.annot-section-head .annot-meta { font-size: 13px; color: var(--color-text-muted); }
.annot-section-actions {
  display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; align-items: center;
}
.annot-section-body {
  border: 1px solid var(--color-border); border-radius: 0 0 10px 10px;
  background: var(--color-surface); padding: 16px 18px;
}

/* ── Page images with SVG overlay ── */
.annot-pages { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 16px; }
.annot-pages.single-page { grid-template-columns: 1fr; max-width: 560px; }
@media (max-width: 720px) { .annot-pages { grid-template-columns: 1fr; } }

.annot-page-figure { margin: 0; border: 1px solid var(--color-border); border-radius: 8px; overflow: hidden; background: var(--color-surface); }
.annot-page-figure figcaption {
  display: flex; align-items: center; gap: 8px; padding: 6px 10px;
  border-bottom: 1px solid var(--color-border); font-size: 12px; font-weight: 600; color: var(--color-text-muted);
}
.annot-page-wrap { position: relative; display: block; line-height: 0; }
.annot-page-wrap img { display: block; width: 100%; height: auto; }
.annot-page-wrap svg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; overflow: visible;
}
/* Allow individual rects to capture hover while the SVG background stays click-through */
.annot-page-wrap svg rect { pointer-events: all; cursor: default; }
.annot-page-placeholder {
  height: 120px; display: flex; align-items: center; justify-content: center;
  color: var(--color-text-muted); font-size: 13px; background: var(--color-zinc-50);
}

/* ── Box list ── */
.annot-boxes { margin-top: 4px; }
.annot-box-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 0; border-bottom: 1px solid var(--color-border); font-size: 13px;
}
.annot-box-row:last-child { border-bottom: none; }
.annot-box-swatch {
  width: 14px; height: 14px; border-radius: 3px; flex-shrink: 0;
  border: 1px solid color-mix(in srgb, var(--color-black) 15%, transparent);
  background: var(--annot-swatch);
}
.annot-box-label { flex: 1; min-width: 0; }
.annot-box-label strong { font-size: 13px; }
.annot-box-label span { color: var(--color-text-muted); margin-left: 4px; }
.annot-box-actions { display: flex; gap: 6px; flex-shrink: 0; }

/* Status badges for boxes */
.box-badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 700; }
.box-badge.detected  { background: var(--color-status-warning-tint); color: var(--color-status-warning); }
.box-badge.approved  { background: var(--color-status-success-tint); color: var(--color-status-success); }
.box-badge.rejected  { background: var(--color-status-danger-tint);  color: var(--color-status-danger); }

.annot-no-boxes { color: var(--color-text-muted); font-size: 13px; font-style: italic; padding: 8px 0; }

/* ── Empty state ── */
.annot-empty { text-align: center; padding: 40px 20px; color: var(--color-text-muted); }
.annot-empty h3 { margin: 0 0 8px; color: var(--color-text-primary); }

/* ── Draw mode overlay ── */
/* When a page-wrap is in draw mode the cursor changes and we capture mouse events */
.annot-page-wrap.draw-mode { cursor: crosshair; }
.annot-page-wrap.draw-mode svg { pointer-events: all; }

/* Ghost rect shown while dragging before release — same brand-blue as the
   dead var(--accent, #4f82f3) fallback used elsewhere in this file */
.annot-draw-ghost { fill: color-mix(in srgb, var(--color-action-primary) 18%, transparent); stroke: var(--color-action-primary); stroke-width: 0.004; }
.annot-draw-ghost.exclusion { fill: color-mix(in srgb, var(--color-annot-exclusion) 18%, transparent); stroke: var(--color-annot-exclusion); }

/* Draw-mode toggle buttons */
.annot-draw-btn {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 10px; border-radius: 5px; font-size: 12px; font-weight: 600;
  border: 1px solid var(--color-border); background: var(--color-surface); cursor: pointer; white-space: nowrap;
  transition: background 0.1s, border-color 0.1s;
}
.annot-draw-btn:hover { background: var(--color-blue-50); border-color: var(--color-action-primary); }
.annot-draw-btn.active { background: color-mix(in srgb, var(--color-action-primary) 10%, var(--color-surface)); border-color: var(--color-action-primary); color: var(--color-action-primary); }
.annot-draw-btn.exclusion-mode.active { background: var(--color-annot-exclusion-tint); border-color: var(--color-annot-rejected); color: var(--color-annot-rejected); }

/* Delete button on box cards */
.annot-sub-delete {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 4px; border: 1px solid transparent;
  background: none; cursor: pointer; color: var(--color-text-muted); font-size: 14px; line-height: 1;
  transition: background 0.1s, color 0.1s;
}
.annot-sub-delete:hover { background: var(--color-annot-subdelete-hover); color: var(--color-annot-rejected); border-color: var(--color-status-danger-tint-border); }

/* JS-driven runtime status colors (approve/reject fetch handler, manual
   draw-box creation) — see this file's header note. These reproduce the
   exact values the JS used to write directly via style/setAttribute. */
.annot-page-wrap svg rect[data-box-status="approved"] { fill: color-mix(in srgb, var(--color-annot-approved-fill) 55%, transparent); stroke: var(--color-annot-approved); }
.annot-page-wrap svg rect[data-box-status="rejected"] { fill: color-mix(in srgb, var(--color-annot-exclusion) 45%, transparent);  stroke: var(--color-annot-rejected); }
.annot-page-wrap svg rect[data-box-status="detected"] { fill: color-mix(in srgb, var(--color-annot-detected-fill) 45%, transparent);  stroke: var(--color-annot-detected-stroke); }
.annot-box-swatch.approved { background: color-mix(in srgb, var(--color-annot-approved-fill) 55%, transparent); }
.annot-box-swatch.rejected { background: color-mix(in srgb, var(--color-annot-exclusion) 45%, transparent); }
.annot-box-swatch.detected { background: color-mix(in srgb, var(--color-annot-detected-fill) 45%, transparent); }

/* Exclusion box SVG rect style — placed after the status rules above (equal
   specificity, later source order wins) so an exclusion box always renders
   red regardless of its data-box-status="approved" (manual boxes are
   auto-approved, so every manually-drawn exclusion box carries both
   attributes) */
.annot-page-wrap svg rect[data-kind="exclusion"] {
  fill: color-mix(in srgb, var(--color-annot-exclusion) 18%, transparent); stroke: var(--color-annot-exclusion);
}

/* ── Grouped box list ── */
.annot-part-group { border-bottom: 1px solid var(--color-border); }
.annot-part-group:last-child { border-bottom: none; }
.annot-part-group-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 6px 4px; border-radius: 4px;
}
.annot-part-group-head strong { font-size: 13px; }
.annot-part-group-sub { padding: 0 6px 8px 22px; display: flex; flex-direction: column; gap: 3px; }

.annot-sub-row {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 6px; font-size: 13px; border-radius: 4px;
}
.annot-sub-kind {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  color: var(--color-text-muted); width: 52px; flex-shrink: 0;
}
.annot-sub-page { color: var(--color-text-muted); font-size: 12px; flex-shrink: 0; }
.annot-sub-conf { color: var(--color-text-muted); font-size: 11px; flex-shrink: 0; }
.annot-sub-actions { display: flex; gap: 4px; margin-left: auto; flex-shrink: 0; }

.annot-group-dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
  border: 1px solid color-mix(in srgb, var(--color-black) 12%, transparent);
}
.annot-group-dot.approved  { background: var(--color-annot-approved); }
.annot-group-dot.detected  { background: var(--color-annot-detected); }
.annot-group-dot.rejected  { background: var(--color-annot-rejected); }
.annot-group-dot.mixed     { background: linear-gradient(135deg, var(--color-annot-approved) 50%, var(--color-annot-detected) 50%); }

/* ── Cross-highlight on hover ── */
/* While a group is hovered, dim everything in the section */
.annot-section[data-hovering] .annot-page-wrap svg rect {
  opacity: 0.12; transition: opacity 0.12s;
}
/* Highlighted rects pop back to full + thicker outline */
.annot-section[data-hovering] .annot-page-wrap svg rect[data-highlighted] {
  opacity: 1; stroke-width: 0.008 !important;
}
/* Dim non-highlighted groups in the list */
.annot-section[data-hovering] .annot-part-group { opacity: 0.3; transition: opacity 0.12s; }
.annot-section[data-hovering] .annot-part-group[data-highlighted] {
  opacity: 1;
  background: color-mix(in srgb, var(--color-action-primary) 6%, transparent);
  border-radius: 6px;
}

/* ── Asset gallery (Phase 3b) ── */
.annot-gallery { margin-top: 20px; border-top: 1px solid var(--color-border); padding-top: 14px; }
.annot-gallery-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap;
}
.annot-gallery-head strong { font-size: 13px; }

/* Page-annotated: constrained-width thumbnail + caption */
.annot-gallery-page { margin-bottom: 16px; }
.annot-gallery-page-fig {
  display: inline-block; max-width: 380px; width: 100%;
  border: 1px solid var(--color-border); border-radius: 8px; overflow: hidden; background: var(--color-surface);
}
.annot-gallery-page-fig img { display: block; width: 100%; height: auto; }
.annot-gallery-page-cap {
  padding: 6px 10px; font-size: 12px; color: var(--color-text-muted); border-top: 1px solid var(--color-border);
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
}

/* Part-composite thumbnails */
.annot-gallery-parts-head { font-size: 12px; font-weight: 600; color: var(--color-text-muted);
  margin-bottom: 8px; text-transform: uppercase; letter-spacing: .04em; }
.annot-gallery-parts { display: flex; flex-wrap: wrap; gap: 10px; }
.annot-gallery-thumb {
  width: 148px; border: 1px solid var(--color-border); border-radius: 6px; overflow: hidden;
  background: var(--color-surface); display: flex; flex-direction: column;
}
.annot-gallery-thumb a { display: block; line-height: 0; }
.annot-gallery-thumb img { display: block; width: 100%; height: auto; }
.annot-gallery-thumb-cap {
  padding: 5px 7px; font-size: 11px; color: var(--color-text-primary); line-height: 1.3;
  display: flex; flex-direction: column; gap: 3px; border-top: 1px solid var(--color-border);
}
.annot-gallery-thumb-cap .thumb-part-link {
  font-size: 11px; color: var(--color-text-primary); text-decoration: none;
}
.thumb-xmanual-btn {
  display: inline-flex; align-items: center; gap: 3px;
  margin-top: 3px; padding: 2px 7px; border-radius: 5px; font-size: 10px; font-weight: 600;
  background: var(--color-indigo-50); color: var(--color-indigo-600); border: 1px solid var(--color-indigo-200); text-decoration: none;
  white-space: nowrap;
}
.thumb-xmanual-btn:hover { background: var(--color-indigo-100); color: var(--color-indigo-700); }

/* Asset status badges */
.asset-badge { display: inline-block; padding: 2px 7px; border-radius: 8px;
  font-size: 10px; font-weight: 700; }
.asset-badge.rendered   { background: var(--color-status-success-tint); color: var(--color-status-success); }
.asset-badge.published  { background: var(--color-status-info-tint);    color: var(--color-status-info); }
.asset-badge.partial    { background: var(--color-status-warning-tint); color: var(--color-status-warning); }
.asset-badge.failed     { background: var(--color-status-danger-tint);  color: var(--color-status-danger); }

/* Failed-assets notice under gallery */
.annot-failed-notice {
  margin-top: 10px; padding: 8px 12px; border-radius: 6px;
  background: var(--color-status-danger-tint); border: 1px solid var(--color-status-danger-tint-border); font-size: 12px; color: var(--color-status-danger);
}

/* ── Publish / Export panel (Phase 4) ── */
.annot-publish-panel {
  margin-top: 32px; border: 1px solid var(--color-border); border-radius: 10px;
  background: var(--color-surface); overflow: hidden;
}
.annot-publish-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 14px 18px; border-bottom: 1px solid var(--color-border);
  background: var(--color-zinc-50);
}
.annot-publish-head strong { font-size: 15px; }
.annot-publish-empty { padding: 20px 18px; color: var(--color-text-muted); font-size: 13px; margin: 0; }
.annot-publish-body { padding: 16px 18px; }

.annot-publish-options { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
.annot-publish-option {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 14px; border: 1px solid var(--color-border); border-radius: 8px;
  cursor: pointer; background: var(--color-surface); transition: border-color 0.1s;
}
.annot-publish-option:hover:not(.disabled) { border-color: var(--color-action-primary); }
.annot-publish-option:has(input:checked) { border-color: var(--color-action-primary); background: var(--color-blue-50); }
.annot-publish-option.disabled { opacity: 0.55; cursor: default; }
.annot-publish-option input[type="radio"] { margin-top: 3px; flex-shrink: 0; }
.annot-publish-option-label { display: flex; flex-direction: column; gap: 3px; }
.annot-publish-option-label strong { font-size: 13px; }
.annot-publish-option-label span { font-size: 12px; color: var(--color-text-muted); }
.annot-publish-option-label code { font-size: 11px; background: var(--color-annot-code-chip-bg); padding: 1px 4px; border-radius: 3px; }

.annot-publish-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.annot-publish-hint {
  font-size: 12px; color: var(--color-text-muted);
}
.annot-publish-hint.busy {
  color: var(--color-status-warning); font-weight: 600;
}

.annot-publish-results { margin-top: 16px; border-top: 1px solid var(--color-border); padding-top: 14px; }
.annot-publish-results-head {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--color-text-muted); margin-bottom: 10px;
}
.annot-publish-results-head a {
  font-weight: 600; font-size: 11px; text-transform: none; letter-spacing: 0;
  color: var(--color-action-primary); cursor: pointer; text-decoration: none;
}
.annot-publish-pkg {
  border: 1px solid var(--color-border); border-radius: 7px;
  background: var(--color-surface); margin-bottom: 8px; font-size: 13px; overflow: hidden;
}
.annot-publish-pkg--complete { border-color: var(--color-status-success-tint-border); background: var(--color-status-success-tint); }
.annot-publish-pkg--failed   { border-color: var(--color-status-danger-tint-border); background: var(--color-status-danger-tint); }
.annot-publish-pkg--running,
.annot-publish-pkg--queued   { border-color: var(--color-status-warning-tint-border); background: var(--color-status-warning-tint); }

.pkg-row1 {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 12px;
}
.pkg-row2  { padding: 0 12px 10px; }
.pkg-row-warn {
  padding: 6px 12px 8px;
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--color-status-warning);
  border-top: 1px solid var(--color-status-warning-tint-border);
}

.annot-publish-pkg .pkg-status { font-weight: 700; font-size: 12px; white-space: nowrap; }
.annot-publish-pkg .pkg-status.complete  { color: var(--color-status-success); }
.annot-publish-pkg .pkg-status.running,
.annot-publish-pkg .pkg-status.queued    { color: var(--color-status-warning); }
.annot-publish-pkg .pkg-status.failed    { color: var(--color-status-danger); }

/* Publisher pill — inline left, right after status badge */
.pkg-publisher {
  display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 11px;
  font-weight: 600; background: var(--color-indigo-50); color: var(--color-indigo-600); border: 1px solid var(--color-indigo-200);
  white-space: nowrap; flex-shrink: 0;
}
.pkg-publisher.shopify { background: var(--color-status-success-tint); color: var(--color-status-success); border-color: var(--color-status-success-tint-border); }

.annot-publish-pkg .pkg-time  { font-size: 12px; color: var(--color-text-muted); }
/* Stats float right — only visible on completed packages */
.annot-publish-pkg .pkg-stats { font-size: 12px; color: var(--color-text-muted); margin-left: auto; }

.annot-publish-pkg .pkg-files { display: flex; gap: 8px; flex-wrap: wrap; }
.pkg-download-btn {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 5px 12px; border-radius: 5px; font-size: 12px; font-weight: 600;
  background: var(--color-status-success); color: var(--color-surface); text-decoration: none; white-space: nowrap;
  border: 1px solid var(--color-green-800);
}

.pkg-retry-btn {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 9px; border-radius: 4px; font-size: 11px; font-weight: 600;
  background: var(--color-surface); color: var(--color-status-danger); border: 1px solid var(--color-status-danger-tint-border); cursor: pointer;
  white-space: nowrap; vertical-align: middle;
}
.pkg-retry-btn:hover { background: var(--color-status-danger-tint); }
.pkg-download-btn:hover { background: var(--color-green-800); }
.pkg-filesize { font-weight: 400; opacity: 0.8; }
.annot-publish-pkg .pkg-error { font-size: 12px; color: var(--color-status-danger); margin-top: 2px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.annot-publish-spinner { display: inline-block; width: 14px; height: 14px;
  border: 2px solid var(--color-border); border-top-color: var(--color-annot-spinner);
  border-radius: 50%; animation: annot-spin 0.7s linear infinite; vertical-align: middle; flex-shrink: 0; }

/* ── Detecting spinner ── */
@keyframes annot-spin { to { transform: rotate(360deg); } }
.annot-spinner {
  display: inline-block; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--color-border); border-top-color: var(--color-action-primary);
  animation: annot-spin 0.7s linear infinite; vertical-align: middle; margin-right: 5px;
}
.annot-detecting-badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--color-text-muted); font-style: italic;
}
.annot-global-banner {
  display: flex; align-items: center; gap: 10px;
  background: var(--color-blue-50); border: 1px solid var(--color-blue-200); border-radius: 8px;
  padding: 10px 16px; font-size: 13px; margin-bottom: 16px;
}
/* Rendering variant (green, "positive action in progress") vs. the default
   detecting variant (blue, informational) */
.annot-global-banner--render {
  background: var(--color-status-success-tint); border-color: var(--color-green-300);
}
.annot-global-banner--render .annot-spinner { border-top-color: var(--color-status-success); }

/* ═══ Inline-style burn-down (Slice G3) ═══════════════════════════════════════ */
.annot-intro-hint.hint { max-width: 680px; margin: 0; }

/* Static per-status count colors in a section's header meta line */
.annot-count-ok     { color: var(--color-status-success); }
.annot-count-warn   { color: var(--color-status-warning); }
.annot-count-danger { color: var(--color-status-danger); }

/* "Opt-in" pill next to the Shopify product media publish option label */
.annot-optin-badge {
  font-size: 11px; font-weight: 600; padding: 1px 6px; border-radius: 8px;
  background: var(--color-status-warning-tint); color: var(--color-status-warning); border: 1px solid var(--color-status-warning-tint-border);
}

/* Inline error count in the publish-results stats line (JS-generated) */
.ar-stat-error { color: var(--color-status-danger); }
/* ═══════════════════════════════════════════════════════════════════════════
   Business settings (Slice G3) — mechanically extracted from
   businesses/edit.html.erb's inline <style> block, with every deprecated
   migration alias (platform/tokens.css) rewritten to its semantic-token
   equivalent, plus page-scoped helper classes replacing the view's former
   static style= attributes. Zero intended visual change.

   Alias rewrites applied to the moved block:
     var(--border)         → var(--color-border)
     var(--card-bg)        → var(--color-surface)
     var(--surface-muted)  → var(--color-zinc-100)
     var(--ink)            → var(--color-text-primary)
     var(--muted)          → var(--color-text-muted)
     var(--green-dark)     → var(--color-action-primary-hover)
     var(--green-tint)     → var(--color-blue-50)
     var(--faint)          → var(--color-zinc-300)
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Settings accordion ──────────────────────────────────────── */
.settings-accordion {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 24px;
}

.settings-section {
  border: 1px solid var(--color-border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--color-surface);
}

.settings-section summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px;
  cursor: pointer;
  user-select: none;
  list-style: none;
}
.settings-section summary::-webkit-details-marker { display: none; }
.settings-section summary::marker { display: none; }

.settings-section[open] summary {
  border-bottom: 1px solid var(--color-border);
}

.settings-section summary:hover {
  background: var(--color-zinc-100);
}

.settings-section-meta {
  flex: 1;
  min-width: 0;
}

.settings-section-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--color-text-primary);
  display: flex;
  align-items: center;
  gap: 8px;
}

.settings-section-desc {
  font-size: 12px;
  color: var(--color-text-muted);
  margin-top: 2px;
}

.settings-section-link {
  font-size: 11px;
  font-weight: 500;
  color: var(--color-action-primary-hover);
  text-decoration: none;
  white-space: nowrap;
  background: var(--color-blue-50);
  padding: 3px 8px;
  border-radius: 6px;
  flex-shrink: 0;
}
.settings-section-link:hover { text-decoration: underline; }

.settings-chevron {
  flex-shrink: 0;
  color: var(--color-zinc-300);
  transition: transform 0.15s ease;
}
.settings-section[open] .settings-chevron {
  transform: rotate(180deg);
}

.settings-body {
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.settings-body label {
  display: block;
  margin-bottom: 4px;
  margin-top: 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text-primary);
}
.settings-body label:first-child { margin-top: 0; }

.settings-body input[type="text"],
.settings-body input[type="email"],
.settings-body input[type="number"],
.settings-body input[type="url"],
.settings-body textarea,
.settings-body select {
  margin-top: 0;
}

.settings-body .hint {
  margin-top: 4px;
  margin-bottom: 0;
}

.settings-body .checkboxes { margin-top: 8px; }
.settings-body .checkboxes label { margin-top: 0; font-weight: 400; }

.settings-body h3 {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-zinc-300);
  margin: 20px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--color-border);
}
.settings-body h3:first-child { margin-top: 0; }

.settings-detected-box {
  background: var(--color-zinc-100);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 13px;
  margin-top: 4px;
}

/* ═══ Inline-style burn-down (Slice G3) ═══════════════════════════════════════
   Page-scoped helpers replacing the view's former static style= attributes.
   The base `.settings-body .hint` rule (margin-top:4px/bottom:0, 0-2-0) forces
   the hint margins, so the hint overrides below are scoped to beat it on
   specificity rather than relying on plain u- utilities (0-1-0). */

.settings-body .hint.hint-flush   { margin-top: 0; }
.settings-body .hint.hint-mb-1h   { margin-bottom: 6px; }
.settings-body .hint.hint-mb-2    { margin-bottom: 8px; }

/* Danger-colored hint: .hint (components/forms.css) loads after typography's
   .text-danger, so a page-scoped rule is needed to win the color. Was a dead
   var(--danger, #c0392b) fallback; repointed to the danger semantic token. */
.hint.settings-danger { color: var(--color-status-danger); }

/* Inline label note (advisory text) — was font-weight:400; color:var(--muted) */
.settings-label-note { font-weight: 400; color: var(--color-text-muted); }

/* Copyable / display code boxes (formerly inline flex + surface-muted box) */
.settings-id-code {
  flex: 1;
  font-size: 12px;
  background: var(--color-zinc-100);   /* was var(--surface-muted) */
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 7px 10px;
  user-select: all;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.settings-url-code {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}
.settings-copy-btn { flex-shrink: 0; white-space: nowrap; }

/* Section-link cluster + disabled state (gap:6px is off the --space scale) */
.settings-link-cluster { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }
.settings-section-link--disabled { opacity: 0.4; cursor: default; }

/* Badges trailing a section title (margin-left:6px is off the --space scale) */
.settings-section-title .badge { margin-left: 6px; }

/* Small bold sub-heading inside a checkbox/label (font-size:13px) */
.settings-strong-sm { font-size: 13px; }

/* Monospace textarea (system prompt) */
.settings-mono-input { font-family: var(--font-mono); font-size: 13px; }

/* Detected-vocab box hint lines */
.settings-detected-box--flush { padding: 0; overflow: hidden; }
.settings-detected-box .hint { display: block; }
.settings-detected-box .hint.hint-tight { margin-top: 2px; }

/* Model-variant editor grid (rows are also created client-side by the script
   below, so .ai-variant-row must be self-contained) */
#ai-variants-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.ai-variant-grid {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 8px;
}
.ai-variant-head {
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text-muted);   /* was #6c757d — nudged to the muted token */
  padding: 0 2px;
}
.ai-variant-row {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 8px;
  align-items: center;
}
.ai-variant-row input { margin: 0; }
.ai-variant-remove {
  background: none;
  border: 1px solid var(--color-border);   /* was #dee2e6 — nudged to border token */
  border-radius: 4px;
  padding: 4px 8px;
  cursor: pointer;
  color: var(--color-status-danger);        /* was #dc3545 — nudged to danger token */
  font-size: 14px;
  line-height: 1;
}

/* Part-type detection table (read-only) */
.ai-part-type-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.ai-part-type-table thead tr {
  background: var(--color-zinc-100);
  border-bottom: 1px solid var(--color-border);
}
.ai-part-type-table th { padding: 8px 14px; text-align: left; }
.ai-part-type-table th.col-canonical { font-weight: 600; width: 30%; }
.ai-part-type-table th.col-triggers  { font-weight: 400; color: var(--color-text-muted); }
.ai-part-type-table tbody tr + tr { border-top: 1px solid var(--color-border); }
.ai-part-type-table td { padding: 7px 14px; }
.ai-part-type-table td.col-canonical { font-weight: 600; }
.ai-part-type-table td.col-triggers  { color: var(--color-text-muted); }

/* Shopify integration section */
.settings-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-zinc-300);   /* was var(--faint) */
  margin: 0 0 8px;
}
.settings-divider-top {
  border-top: 1px solid var(--color-border);
  margin-top: 10px;
  padding-top: 12px;
}
.settings-disabled-row { display: flex; align-items: flex-start; gap: 8px; opacity: 0.55; }
.settings-disabled-row input[type="checkbox"] { margin-top: 3px; flex-shrink: 0; }

.settings-btn-icon { vertical-align: -2px; margin-right: 4px; }

.settings-advanced { margin-top: 20px; }
.settings-advanced-summary {
  cursor: pointer;
  font-size: 0.85em;
  color: var(--color-text-muted);
  user-select: none;
}
.settings-advanced-body { padding-top: 12px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Catalog product / image gallery (Slice G3) — mechanically extracted from
   catalog_products/image_gallery.html.erb's inline <style> block, with every
   deprecated alias rewritten to a semantic token and every color literal
   replaced by a token / token-derived color.

   Color decisions:
     var(--muted)→--color-text-muted, var(--line)→--color-border,
     var(--ink)→--color-text-primary, var(--panel)→--color-surface.
     dead var(--accent, #4f82f3) → var(--color-action-primary) (interaction).
     rgba(79,130,243,0.15) → color-mix(action-primary 15%).
     dead var(--panel-alt, #f8f9fa) → var(--color-zinc-50) (nudge).
     #fff → var(--color-surface) (exact).
     Status/asset pills harmonized to the LumaRef status palette:
       success #d4edda/#1a6632 → status-success-tint/status-success
       info    #cce5ff/#004085 → status-info-tint/status-info
       warning #fff3cd/#7a5f00 → status-warning-tint/status-warning
       danger  #f8d7da/#842029 → status-danger-tint/status-danger
     Lightbox chrome (.lgx-*) was later extracted to the shared
     platform/components/lightbox.css (Slice G3, annotation_reviews pass) —
     see that file for its own color-decision notes.
     "Edit in Shopify" secondary-button text #3d3d3d → --color-zinc-700
     (#3f3f46, a ~2/255-per-channel nudge) and its border #d1d5db →
     --color-zinc-300 (#d4d4d8, a ~3/255-per-channel nudge) — both
     imperceptible, reusing existing primitives rather than adding two new
     one-off grays for a single secondary button.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Layout ── */
.cp-back   { font-size:13px; margin:0 0 14px; }
.cp-back a { color:var(--color-text-muted); text-decoration:none; }

.cp-header {
  display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
  margin-bottom: 0;
}
.cp-header-main { flex: 1; min-width: 0; }
.cp-header-main h1 { margin: 0 0 2px; font-size: 26px; }
.cp-header-subtitle { font-size: 14px; color: var(--color-text-muted); margin: 0 0 12px; }


/* ── Tab bar ── */
.cp-tabs {
  display: flex; gap: 0; border-bottom: 2px solid var(--color-border);
  margin: 20px 0 24px; overflow-x: auto;
}
.cp-tab {
  padding: 9px 18px; font-size: 13px; font-weight: 600; cursor: pointer;
  color: var(--color-text-muted); border: none; background: none;
  border-bottom: 2px solid transparent; margin-bottom: -2px;
  white-space: nowrap; transition: color 0.1s;
  display: flex; align-items: center; gap: 7px;
}
.cp-tab:hover { color: var(--color-text-primary); }
.cp-tab.active { color: var(--color-action-primary); border-bottom-color: var(--color-action-primary); }
.cp-tab .cp-tab-count {
  background: var(--color-border); color: var(--color-text-muted);
  font-size: 11px; font-weight: 700; padding: 1px 6px;
  border-radius: 10px; min-width: 20px; text-align: center;
}
.cp-tab.active .cp-tab-count {
  background: color-mix(in srgb, var(--color-action-primary) 15%, transparent); color: var(--color-action-primary);
}

/* ── Tab panels ── */
.cp-panel { display: none; }
.cp-panel.active { display: block; }

.cp-badge {
  display: inline-block; padding: 3px 9px; border-radius: 8px;
  font-size: 11px; font-weight: 700; vertical-align: middle; margin-left: 8px;
}
.cp-badge.active  { background: var(--color-status-success-tint); color: var(--color-status-success); }
.cp-badge.retired { background: var(--color-status-danger-tint); color: var(--color-status-danger); }

/* ── Section card ── */
.cp-section {
  border: 1px solid var(--color-border); border-radius: 10px;
  background: var(--color-surface); margin-bottom: 24px; overflow: hidden;
}
.cp-section-head {
  padding: 12px 18px; border-bottom: 1px solid var(--color-border);
  background: var(--color-zinc-50);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.cp-section-head h2 {
  margin: 0; font-size: 13px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--color-text-muted);
}
.cp-section-body { padding: 16px 18px; }

/* ── Details grid ── */
.cp-details-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 14px 24px;
}
.cp-detail-item label {
  display: block; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--color-text-muted);
  margin-bottom: 3px;
}
.cp-detail-item .cp-detail-val {
  font-size: 13px; color: var(--color-text-primary); word-break: break-all;
}
.cp-detail-item .cp-detail-val.mono { font-family: monospace; font-size: 12px; }
.cp-detail-item .cp-detail-val.muted { color: var(--color-text-muted); }

/* ── Appearances table ── */
.cp-appearances-run { margin-bottom: 16px; }
.cp-appearances-run-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 6px;
}
.cp-appearances-run-head strong { font-size: 13px; }
.cp-appearances-run-head .cp-run-meta { font-size: 12px; color: var(--color-text-muted); }

.cp-appearances-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.cp-appearances-table th {
  text-align: left; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--color-text-muted); padding: 0 10px 6px; border-bottom: 1px solid var(--color-border);
}
.cp-appearances-table td {
  padding: 8px 10px; border-bottom: 1px solid var(--color-border); vertical-align: top;
}
.cp-appearances-table tr:last-child td { border-bottom: none; }
.cp-appearances-table tr:hover td { background: var(--color-zinc-50); }
.cp-appearances-empty { color: var(--color-text-muted); font-size: 13px; }

/* ── Image gallery ── */
.catalog-run-group { margin-bottom: 28px; }
.catalog-run-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 12px 16px; background: var(--color-zinc-50);
  border: 1px solid var(--color-border); border-radius: 10px 10px 0 0; border-bottom: none;
}
.catalog-run-head h3 { margin: 0; font-size: 14px; }
.catalog-run-head .run-meta { font-size: 12px; color: var(--color-text-muted); }
.catalog-run-body {
  padding: 16px; border: 1px solid var(--color-border); border-radius: 0 0 10px 10px;
  background: var(--color-surface);
}

.catalog-gallery-empty {
  text-align: center; padding: 48px 24px;
  color: var(--color-text-muted); border: 1px solid var(--color-border); border-radius: 10px;
  background: var(--color-surface);
}
.catalog-gallery-empty h3 { margin: 0 0 8px; color: var(--color-text-primary); }

.catalog-thumb-grid { display: flex; flex-wrap: wrap; gap: 12px; }
.catalog-thumb {
  width: 160px; border: 1px solid var(--color-border); border-radius: 8px;
  overflow: hidden; background: var(--color-surface); display: flex; flex-direction: column;
}
.catalog-thumb a { display: block; line-height: 0; }
.catalog-thumb img { display: block; width: 100%; height: auto; }
.catalog-thumb-cap {
  padding: 7px 9px; font-size: 11px; color: var(--color-text-primary); line-height: 1.4;
  display: flex; flex-direction: column; gap: 3px; border-top: 1px solid var(--color-border);
}
.catalog-thumb-cap .thumb-muted { color: var(--color-text-muted); }

.asset-badge { display: inline-block; padding: 2px 7px; border-radius: 8px;
  font-size: 10px; font-weight: 700; }
.asset-badge.rendered   { background: var(--color-status-success-tint); color: var(--color-status-success); }
.asset-badge.published  { background: var(--color-status-info-tint); color: var(--color-status-info); }
.asset-badge.partial    { background: var(--color-status-warning-tint); color: var(--color-status-warning); }
.asset-badge.failed     { background: var(--color-status-danger-tint); color: var(--color-status-danger); }

/* ── Lightbox ── */
/* Chrome (.lgx-*, [data-lightbox-src]) now lives in the shared
   platform/components/lightbox.css — this page's markup/JS is unchanged,
   only the duplicate CSS moved out (see that file's header for why). */

/* ═══ Inline-style burn-down (Slice G3) ═══════════════════════════════════════ */
.cp-detail-item--full { grid-column: 1 / -1; }
.cp-external-links { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 2px; }

.cp-ext-link {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 600; padding: 5px 12px; border-radius: 6px;
  text-decoration: none; white-space: nowrap;
}
.cp-ext-link--primary {
  background: var(--color-action-primary); color: var(--color-text-on-action);
  box-shadow: 0 1px 3px color-mix(in srgb, var(--color-black) 12%, transparent);
}
.cp-ext-link--secondary {
  background: var(--color-surface); color: var(--color-zinc-700);   /* was #3d3d3d */
  border: 1px solid var(--color-zinc-300);                          /* was #d1d5db */
  box-shadow: 0 1px 3px color-mix(in srgb, var(--color-black) 8%, transparent);
}
.cp-ext-icon { flex-shrink: 0; }

.cp-badge--flush { margin: 0; }
.cp-detail-val--id { font-size: 11px; color: var(--color-text-muted); }

.cp-section-meta { font-size: 12px; color: var(--color-text-muted); margin-left: auto; }
.cp-open-run-link { font-size: 12px; color: var(--color-action-primary); text-decoration: none; margin-left: auto; }

.cp-catalog-link { color: var(--color-action-primary); }

.cp-td-mono  { font-family: monospace; font-size: 12px; }
.cp-td-muted { color: var(--color-text-muted); }

.cp-match-badge { display: inline-block; padding: 2px 6px; border-radius: 6px; font-size: 10px; font-weight: 700; }
.cp-match-badge--ok   { background: var(--color-status-success-tint); color: var(--color-status-success); }
.cp-match-badge--warn { background: var(--color-status-warning-tint); color: var(--color-status-warning); }

.catalog-gallery-empty--bare { border: none; background: transparent; padding: 24px 0; }
/* ═══════════════════════════════════════════════════════════════════════════
   Dashboard (Slice G3) — mechanically extracted from the view's inline
   <style> block, with every deprecated migration alias (platform/tokens.css)
   rewritten to its semantic-token equivalent. Zero intended visual change
   except the two deliberate calls noted inline below.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Dashboard stat cards ──────────────────────────── */
.dash-stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}
.dash-soon {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  border-radius: 999px;
  padding: 1px 7px;
  margin-top: 4px;
  background: var(--color-zinc-100);
  color: var(--color-text-subtle);
  border: 1px solid var(--color-border);
  letter-spacing: 0.03em;
}

/* ── Two-column layout ─────────────────────────────── */
.dash-panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 20px;
}
@media (max-width: 860px) { .dash-panels { grid-template-columns: 1fr; } }

.dash-panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  overflow: hidden;
}
/* Deliberate one-off (not a u- utility — 20px matches neither --space-4 (16px)
   nor --space-5 (24px)): the live-manuals panel keeps its own exact spacing
   below the two-column funnel/questions grid rather than nudging to a token. */
.dash-panel--manuals {
  margin-bottom: 20px;
}
.dash-panel-head {
  padding: 13px 18px 11px;
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.dash-panel-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--color-text-primary);
}
.dash-panel-link {
  font-size: 11px;
  font-weight: 700;
  color: var(--color-action-primary-hover);
  text-decoration: none;
}
.dash-panel-link:hover { text-decoration: underline; }
.dash-panel-meta { font-size: 11px; color: var(--color-text-subtle); }
.dash-panel-body { padding: 4px 0; }
.dash-panel-empty {
  padding: 20px 18px;
  color: var(--color-text-subtle);
  font-size: 13px;
}

/* ── Funnel rows ───────────────────────────────────── */
.funnel-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--color-zinc-100);
}
.funnel-row:last-child { border-bottom: none; }
.funnel-row.dim { opacity: 0.38; }
.funnel-icon {
  width: 30px; height: 30px;
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.funnel-label { flex: 1; min-width: 0; }
.funnel-label strong { display: block; font-size: 13px; font-weight: 600; color: var(--color-text-primary); }
.funnel-label span   { display: block; font-size: 11px; color: var(--color-text-muted); margin-top: 1px; }
.funnel-count {
  font-size: 20px; font-weight: 700; color: var(--color-text-primary);
  font-variant-numeric: tabular-nums; flex-shrink: 0; min-width: 36px; text-align: right;
}
.funnel-pct {
  font-size: 11px; font-weight: 600; color: var(--color-text-muted);
  flex-shrink: 0; min-width: 44px; text-align: right;
}
/* Deliberate one-off: the "Soon" label in the dimmed funnel row is smaller
   than the standard .funnel-pct size; no u-text-* utility is this small
   (smallest is 12px), so it stays a page-scoped rule rather than a forced fit. */
.funnel-row.dim .funnel-pct { font-size: 10px; }

/* Semantic icon-tint modifiers (replace the former inline
   style="background:#…"/var(--surface-muted) on .funnel-icon).
   Each sets both background AND color so the child SVG's
   stroke="currentColor" resolves to the same value the old literal/alias
   stroke did — see the per-variant note for the exact-match check. */
.funnel-icon--info {
  background: var(--color-blue-50);      /* was #eff6ff — exact match */
  color: var(--color-action-primary);    /* was stroke="#2563eb" — exact match (blue-600) */
}
.funnel-icon--purple {
  background: var(--color-purple-100);   /* was #f3e8ff — exact match */
  color: var(--color-status-purple);     /* was stroke="#7e22ce" — exact match (purple-700) */
}
.funnel-icon--success {
  /* was #f0fdf4; reusing the existing success tint (--color-green-50 /
     #f2fbf2, a ~2/255-per-channel nudge, imperceptible) rather than adding a
     new one-off primitive for a single pixel-exact background. */
  background: var(--color-status-success-tint);
  color: var(--color-status-success);    /* was stroke="#059405" — exact match (green-700) */
}
.funnel-icon--warning {
  background: var(--color-amber-50);     /* was #fffbeb — exact match */
  color: var(--color-amber-700);         /* was stroke="#b45309" — exact match (amber-700; NOT --color-status-warning, which is amber-600/#c2410c) */
}
.funnel-icon--muted {
  background: var(--color-zinc-100);     /* was var(--surface-muted) — same alias target */
  color: var(--color-text-subtle);       /* was stroke="var(--placeholder)" — same alias target */
}

/* ── Recent questions ─────────────────────────────── */
.question-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--color-zinc-100);
  font-size: 13px;
}
.question-row:last-child { border-bottom: none; }
.question-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--color-action-primary); flex-shrink: 0; margin-top: 5px;
}
.question-text { flex: 1; color: var(--color-zinc-700); line-height: 1.45; }
.question-date { font-size: 11px; color: var(--color-text-subtle); flex-shrink: 0; padding-top: 1px; }

/* ── Published manuals strip ───────────────────────── */
.manual-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 18px;
  border-bottom: 1px solid var(--color-zinc-100);
  font-size: 13px;
}
.manual-row:last-child { border-bottom: none; }
.manual-icon {
  width: 28px; height: 28px; border-radius: 8px;
  background: var(--color-blue-50); flex-shrink: 0;
  color: var(--color-action-primary-hover); /* was stroke="var(--green-dark)" — same alias target, drives currentColor */
  display: flex; align-items: center; justify-content: center;
}
.manual-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-zinc-700); }
.manual-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
/* Deliberate one-off: the "Stale" badge here is smaller than the standard
   11px .badge size; no u-text-* utility is this small, so it stays scoped
   to this panel rather than forcing a mismatched utility. */
.manual-actions .badge { font-size: 10px; }

/* ── Revenue block (locked) ────────────────────────── */
.dash-revenue {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  padding: 20px 24px 22px;
  margin-bottom: 8px;
  position: relative;
  overflow: hidden;
}
.dash-revenue::after {
  content: "";
  position: absolute;
  inset: 0;
  /* was rgba(250,250,250,0.6) — --color-zinc-50 is #fafafa, i.e. rgb(250,250,250),
     so this reproduces the exact same scrim color/opacity from a token. */
  background: color-mix(in srgb, var(--color-zinc-50) 60%, transparent);
  pointer-events: none;
}
.dash-revenue-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  font-weight: 700;
  color: var(--color-text-subtle);
  background: var(--color-zinc-100);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 2px 10px;
  margin-bottom: 14px;
}
.dash-revenue-title {
  font-size: 15px; font-weight: 700; color: var(--color-text-primary); margin-bottom: 14px;
}
.dash-revenue-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}
.dash-revenue-card {
  background: var(--color-canvas);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 12px 14px;
}
.dash-revenue-card .r-val { font-size: 22px; font-weight: 700; color: var(--color-text-subtle); }
.dash-revenue-card .r-lbl { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--color-text-subtle); margin-top: 2px; }
.dash-revenue-hint { font-size: 12px; color: var(--color-text-subtle); }
/* ═══════════════════════════════════════════════════════════════════════════
   Design system reference page (Slice F) — page-specific demo styles.

   These classes exist ONLY to render static token/component demonstrations
   on /design-system without inline styles or color literals in ERB. Every
   rule below consumes a token (var(--color-…)) rather than a pigment
   literal, so this file passes the Slice H CSS color guard (which allows
   raw hex/rgb/hsl only inside platform/tokens.css).

   Swatches and fixed progress fills are demo-only: the inline
   `style="--fill: <%= pct %>%"` pattern used elsewhere for real runtime
   data is deliberately NOT used here — see platform/legacy.css for the
   real .progress-fill consumer.
   ═══════════════════════════════════════════════════════════════════════════ */

.ds-section {
  margin-bottom: 36px;
  padding-bottom: 32px;
  border-bottom: 1px solid var(--color-border);
}
.ds-section:last-child { border-bottom: none; padding-bottom: 0; }
.ds-section-intro { max-width: 640px; }

.ds-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.ds-col { display: flex; flex-direction: column; gap: 10px; }
.ds-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
@media (max-width: 860px) { .ds-grid-2 { grid-template-columns: 1fr; } }
.ds-stat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 12px; }

/* ── Token swatches ──────────────────────────────────────────────────────── */
.ds-swatch-list { display: flex; flex-direction: column; gap: 10px; }
.ds-swatch-row { display: flex; align-items: center; gap: 12px; }
.ds-swatch {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  flex-shrink: 0;
}
.ds-hue-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; margin-bottom: 14px; }
.ds-hue-swatch { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.ds-hue-swatch .ds-swatch { width: 2.75rem; height: 2.75rem; }
.ds-hue-swatch small { text-align: center; }

/* Primitive swatch fills — each references its token, never a literal. */
.ds-swatch--blue-50   { background: var(--color-blue-50); }
.ds-swatch--blue-100  { background: var(--color-blue-100); }
.ds-swatch--blue-200  { background: var(--color-blue-200); }
.ds-swatch--blue-300  { background: var(--color-blue-300); }
.ds-swatch--blue-400  { background: var(--color-blue-400); }
.ds-swatch--blue-500  { background: var(--color-blue-500); }
.ds-swatch--blue-600  { background: var(--color-blue-600); }
.ds-swatch--blue-700  { background: var(--color-blue-700); }
.ds-swatch--blue-800  { background: var(--color-blue-800); }
.ds-swatch--blue-900  { background: var(--color-blue-900); }
.ds-swatch--blue-950  { background: var(--color-blue-950); }
.ds-swatch--navy-900  { background: var(--color-navy-900); }
.ds-swatch--green-50  { background: var(--color-green-50); }
.ds-swatch--green-100 { background: var(--color-green-100); }
.ds-swatch--green-600 { background: var(--color-green-600); }
.ds-swatch--green-700 { background: var(--color-green-700); }
.ds-swatch--amber-50  { background: var(--color-amber-50); }
.ds-swatch--amber-100 { background: var(--color-amber-100); }
.ds-swatch--amber-200 { background: var(--color-amber-200); }
.ds-swatch--amber-600 { background: var(--color-amber-600); }
.ds-swatch--amber-700 { background: var(--color-amber-700); }
.ds-swatch--red-50    { background: var(--color-red-50); }
.ds-swatch--red-100   { background: var(--color-red-100); }
.ds-swatch--red-200   { background: var(--color-red-200); }
.ds-swatch--red-500   { background: var(--color-red-500); }
.ds-swatch--red-600   { background: var(--color-red-600); }
.ds-swatch--red-700   { background: var(--color-red-700); }
.ds-swatch--purple-50  { background: var(--color-purple-50); }
.ds-swatch--purple-100 { background: var(--color-purple-100); }
.ds-swatch--purple-600 { background: var(--color-purple-600); }
.ds-swatch--purple-700 { background: var(--color-purple-700); }
.ds-swatch--zinc-50   { background: var(--color-zinc-50); }
.ds-swatch--zinc-100  { background: var(--color-zinc-100); }
.ds-swatch--zinc-200  { background: var(--color-zinc-200); }
.ds-swatch--zinc-300  { background: var(--color-zinc-300); }
.ds-swatch--zinc-400  { background: var(--color-zinc-400); }
.ds-swatch--zinc-500  { background: var(--color-zinc-500); }
.ds-swatch--zinc-600  { background: var(--color-zinc-600); }
.ds-swatch--zinc-700  { background: var(--color-zinc-700); }
.ds-swatch--zinc-800  { background: var(--color-zinc-800); }
.ds-swatch--zinc-900  { background: var(--color-zinc-900); }

/* Semantic token swatch fills — token name + role are shown next to these
   in the ERB; the color always comes from here, never from a literal. */
.ds-swatch--action-primary       { background: var(--color-action-primary); }
.ds-swatch--action-primary-hover { background: var(--color-action-primary-hover); }
.ds-swatch--text-on-action       { background: var(--color-text-on-action); }
.ds-swatch--canvas               { background: var(--color-canvas); }
.ds-swatch--surface              { background: var(--color-surface); }
.ds-swatch--border               { background: var(--color-border); }
.ds-swatch--text-primary         { background: var(--color-text-primary); }
.ds-swatch--text-muted           { background: var(--color-text-muted); }
.ds-swatch--text-subtle          { background: var(--color-text-subtle); }
.ds-swatch--status-success       { background: var(--color-status-success); }
.ds-swatch--status-success-tint  { background: var(--color-status-success-tint); }
.ds-swatch--status-danger        { background: var(--color-status-danger); }
.ds-swatch--status-danger-tint   { background: var(--color-status-danger-tint); }
.ds-swatch--status-warning       { background: var(--color-status-warning); }
.ds-swatch--status-warning-tint  { background: var(--color-status-warning-tint); }
.ds-swatch--status-info          { background: var(--color-status-info); }
.ds-swatch--status-info-tint     { background: var(--color-status-info-tint); }
.ds-swatch--status-purple        { background: var(--color-status-purple); }
.ds-swatch--status-purple-tint   { background: var(--color-status-purple-tint); }

/* ── Isolated nav-component demo containers ─────────────────────────────── */
.ds-nav-demo { max-width: 420px; }
.ds-tabs-demo { max-width: 520px; }

/* ── Progress bar fixed demo fills (static, NOT inline --fill data) ─────── */
.progress-demo--25 { width: 25%; }
.progress-demo--50 { width: 50%; }
.progress-demo--75 { width: 75%; }
/* ═══════════════════════════════════════════════════════════════════════════
   Devise (Slice G3) — mechanically extracted from
   devise/sessions/new.html.erb's one inline style= attribute on the
   "Remember me" checkbox label.

   No color decisions were needed — the only style= here was
   font-weight:400 (overriding the default <label> weight).
   ═══════════════════════════════════════════════════════════════════════════ */

.auth-remember-me { font-weight: 400; }
/* ═══════════════════════════════════════════════════════════════════════════
   Documents (Slice G3) — mechanically extracted from documents/index,
   documents/new, and documents/show's inline style= attributes.

   Color decisions: #b00 (required-field asterisk on documents/new) →
   .text-danger (--color-status-danger, an existing typography utility —
   not an exact hex match, but the correct semantic meaning for a required
   indicator, consistent with error/danger usage elsewhere in the design
   system). var(--line) (documents/show's re-link divider) → --color-border,
   same alias resolution as every other Slice G3 commit.

   Two non-scale literal spacings are kept as documented one-offs rather
   than forced onto the --space-* scale: the manual-search bar's
   margin-bottom:20px (index) and the "Publish & deliver" heading's
   margin-top:20px (show) — both pre-existing 20px choices, between
   --space-4 (16px) and --space-5 (24px), used only once each.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── documents/index ──────────────────────────────────────────────────── */
.doc-add-icon { vertical-align: -1px; margin-right: var(--space-1); }

.doc-search-bar { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-bottom: 20px; }
.doc-search-input { flex: 1; min-width: 180px; max-width: none; width: auto; padding: 7px 10px; }
.doc-filter-select { width: auto; max-width: none; flex-shrink: 0; padding: 7px 10px; }
.doc-clear-filters { flex-shrink: 0; white-space: nowrap; }

.doc-remove-disabled-hint { font-size: 12px; margin-left: var(--space-2); }
.doc-remove-form { display: inline-block; margin-left: var(--space-2); }

/* ── documents/new ────────────────────────────────────────────────────── */
.doc-source-toggle-row { display: flex; gap: var(--space-2); margin-bottom: var(--space-4); }

/* .btn already sets font-weight:600 as its base; the inactive toggle state
   dims to 400 and JS (setSource) toggles is-active to restore 600 —
   replaces the old inline .style.fontWeight assignment. */
.doc-source-toggle { font-weight: 400; }
.doc-source-toggle.is-active { font-weight: 600; }

/* ── documents/show ───────────────────────────────────────────────────── */
.doc-page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}
.doc-cancel-hint { margin: 0 0 var(--space-2); font-size: 13px; }

.doc-webpage-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.doc-webpage-meta { margin: 0 0 var(--space-1); }
.doc-webpage-chunk-count { margin: var(--space-3) 0 0; }

.doc-stepper-row { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }

.doc-version-label { font-weight: 400; font-size: 14px; }
.doc-publish-heading { margin-top: 20px; }

.doc-hosting-form { display: inline-flex; gap: 6px; align-items: center; margin-left: var(--space-2); }
.doc-hosting-select { padding: 5px 8px; }
/* input.doc-hosting-external-input (not just .doc-hosting-external-input)
   ties specificity with the base input[type="text"] rule
   (components/forms.css); source order (pages/ loads after components/)
   then lets this win. */
input.doc-hosting-external-input { padding: 5px 8px; min-width: 280px; }

.doc-relink-divider { margin: var(--space-4) 0; border: none; border-top: 1px solid var(--color-border); }
/* ═══════════════════════════════════════════════════════════════════════════
   Export packages / delivery (Slice G3) — mechanically extracted from
   export_packages/index.html.erb's many inline style= attributes on the
   Shopify Sync progress widget (two-phase progress bars, stalled banner,
   idle summary) plus the two top-level panel/heading spacing rules.

   Color decisions:
     var(--muted,#888) / var(--surface-muted,#eee) — --muted and
     --surface-muted are BOTH already-defined deprecated aliases in
     tokens.css, so their literal fallbacks never actually applied at
     runtime (dead code); rewritten as --color-text-muted /
     --color-zinc-100 directly, same as every prior G3 commit's treatment
     of a live-alias-with-dead-fallback.
     var(--border) (no fallback) — already-defined alias, → --color-border.
     var(--warn, #856404) — --warn is also an already-defined alias
     (→ --color-status-warning); the fallback here never fires either →
     --color-status-warning directly.
     var(--accent, #2e7d32) / var(--danger, #c0392b) — unlike --muted/
     --surface-muted/--border/--warn, neither --accent nor --danger exist
     anywhere in tokens.css, so these fallbacks (a Material-ish green, a
     brick red) are what actually renders. Both play an obvious semantic
     role (progress-bar fill for a successful sync in progress; an error
     message) and are harmonized to --color-status-success /
     --color-status-danger respectively, consistent with how every prior
     G3 commit treats an undefined-var fallback with a clear status intent.
     The OEM-stalled banner's warm palette is a one-off "stalled" treatment
     distinct enough from the standard warning token (the text color in
     particular is a much darker olive-brown, not a nudge) to warrant its
     own Slice H primitives: --color-stalled-bg (#fef9e7),
     --color-stalled-border (#f9e79f), --color-stalled-text (#7d6608).

   Behavior note: the OEM "loading" progress bar's shimmer (indeterminate
   striped animation, shown while the row-count sentinel is still unknown)
   used to be driven by a fully static inline style (no ERB in it at all)
   plus JS toggling `bar.style.animation`/`bar.style.width` directly once
   the real count became known. Moving the shimmer's background/animation
   into a `.ep-progress-bar--shimmer` modifier class meant the JS's old
   `bar.style.animation = ""` trick would no longer fully stop it (clearing
   an inline override falls back to the class's own `animation:` rule, not
   to "none") — so the JS now toggles the modifier class itself instead of
   touching `.style.animation` directly. This incidentally fixes a latent
   bug: previously, once the OEM phase left the loading state, the bar's
   inline `background: repeating-linear-gradient(...)` was never reset (JS
   only ever touched `.width`/`.animation`), so the bar visually froze at a
   striped pattern instead of going solid — it now correctly returns to a
   plain solid fill.
   ═══════════════════════════════════════════════════════════════════════════ */

.ep-panel { margin-bottom: 1.5rem; }
.ep-h2 { margin-top: 0; }

.ep-sync-progress { margin-bottom: 10px; }

.ep-phase-row {
  display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 5px;
}
.ep-phase-label { font-size: 13px; font-weight: 600; }
.ep-phase-sublabel { font-weight: 400; font-size: 11px; color: var(--color-text-muted); margin-left: 4px; }
.ep-phase-meta { font-size: 12px; color: var(--color-text-muted); }
.ep-breakdown { font-size: 11px; color: var(--color-text-muted); }

.ep-progress-track {
  height: 6px; background: var(--color-zinc-100); border-radius: 3px; overflow: hidden; margin-bottom: 5px;
}
.ep-progress-bar {
  height: 100%; width: var(--ep-bar-width, 0%); background: var(--color-status-success); transition: width .4s ease;
}
.ep-progress-bar--shimmer {
  width: 100%;
  background: repeating-linear-gradient(90deg, var(--color-status-success) 0, var(--color-status-success) 30%, var(--color-zinc-100) 30%, var(--color-zinc-100) 100%);
  background-size: 200% 100%;
  animation: shimmer 1.4s infinite linear;
}

.ep-stalled-banner {
  display: flex; align-items: center; gap: 6px; padding: 7px 10px;
  background: var(--color-stalled-bg); border: 1px solid var(--color-stalled-border); border-radius: 4px;
  margin-bottom: 8px; font-size: 12px; color: var(--color-stalled-text);
}
.ep-stalled-label { font-weight: 600; }

.ep-last-sync { font-size: 11px; color: var(--color-text-muted); margin: 0 0 6px; }

.ep-summary-grid {
  display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px;
  align-items: baseline; margin-bottom: 10px; font-size: 13px;
}
.ep-summary-label { font-weight: 600; white-space: nowrap; }

.ep-conflict-warn.hint { color: var(--color-status-warning); margin-bottom: 6px; }
.ep-error-text.hint { color: var(--color-status-danger); }

.ep-divider { border-top: 1px solid var(--color-border); margin: 1.25rem 0 0.75rem; }
.ep-section-label {
  margin: 0 0 0.5rem; font-size: 12px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--color-text-muted);
}
.ep-section-label-count { font-weight: 400; text-transform: none; letter-spacing: 0; margin-left: 4px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Match-file products (Slice G3) — mechanically extracted from
   match_files/products.html.erb's inline <style> block, with every deprecated
   migration alias rewritten to its semantic-token equivalent and every color
   literal replaced by a token / token-derived color.

   Color decisions:
     var(--line)   → var(--color-border)
     var(--muted)  → var(--color-text-muted)
     var(--ink)    → var(--color-text-primary)
     var(--panel)  → var(--color-surface)
     var(--accent, #4f82f3)      → var(--color-action-primary)   (dead-var fallback
                                    replaced with the real action token)
     rgba(79,130,243,0.15)       → color-mix(action-primary 15%)  (focus ring)
     #fff                        → var(--color-surface)           (exact)
     var(--panel-alt, #f8f9fa)   → var(--color-zinc-50)           (nudge ~2/255)
     #f0f4ff (current-page bg)   → var(--color-blue-50)           (nudge)
     Active pill  #d4edda/#1a6632 → status-success-tint / status-success
     Retired pill #f8d7da/#842029 → status-danger-tint / status-danger
     (the two legacy Bootstrap status pills are deliberately harmonized to the
      LumaRef semantic status palette.)
   ═══════════════════════════════════════════════════════════════════════════ */

.mf-products-header {
  display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
  margin-bottom: 20px;
}
.mf-products-header h1 { margin: 0; }
.mf-products-header .mf-subtitle { font-size: 13px; color: var(--color-text-muted); }

.mf-search-bar {
  display: flex; gap: 8px; margin-bottom: 20px;
}
.mf-search-bar input[type="search"] {
  flex: 1; max-width: 360px;
  padding: 7px 12px; font-size: 13px;
  border: 1px solid var(--color-border); border-radius: 6px;
  background: var(--color-surface); color: var(--color-text-primary);
}
.mf-search-bar input[type="search"]:focus {
  outline: none; border-color: var(--color-action-primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-action-primary) 15%, transparent);
}

.mf-products-table { width: 100%; border-collapse: collapse; }
.mf-products-table th {
  text-align: left; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  color: var(--color-text-muted); padding: 0 12px 8px;
  border-bottom: 1px solid var(--color-border);
}
.mf-products-table td {
  padding: 10px 12px; font-size: 13px;
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
}
.mf-products-table tr:last-child td { border-bottom: none; }
.mf-products-table tr:hover td { background: var(--color-zinc-50); }

.mf-part-number { font-weight: 600; color: var(--color-text-primary); }
.mf-title       { color: var(--color-text-muted); }
.mf-handle      { font-family: var(--font-mono); font-size: 12px; color: var(--color-text-muted); }
.mf-retired     { opacity: 0.5; }

.mf-badge-active  { display:inline-block; padding:2px 7px; border-radius:8px;
  font-size:10px; font-weight:700; background:var(--color-status-success-tint); color:var(--color-status-success); }
.mf-badge-retired { display:inline-block; padding:2px 7px; border-radius:8px;
  font-size:10px; font-weight:700; background:var(--color-status-danger-tint); color:var(--color-status-danger); }

.mf-actions { display: flex; gap: 6px; justify-content: flex-end; }

.mf-empty {
  text-align: center; padding: 48px 24px;
  color: var(--color-text-muted); border: 1px solid var(--color-border);
  border-radius: 10px; background: var(--color-surface);
}
.mf-empty h3 { margin: 0 0 8px; color: var(--color-text-primary); }

.mf-pagination {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 16px; font-size: 13px; color: var(--color-text-muted);
}
.mf-pagination .mf-page-links { display: flex; gap: 6px; }
.mf-pagination a {
  padding: 4px 10px; border: 1px solid var(--color-border);
  border-radius: 5px; color: var(--color-text-primary); text-decoration: none;
  background: var(--color-surface);
}
.mf-pagination a:hover { background: var(--color-zinc-50); }
.mf-pagination .current-page {
  padding: 4px 10px; border: 1px solid var(--color-action-primary);
  border-radius: 5px; color: var(--color-action-primary);
  background: var(--color-blue-50); font-weight: 600;
}

/* Inline-style burn-down: back-link row + flush table panel */
.mf-back-link { color: var(--color-text-muted); text-decoration: none; }
.mf-table-wrap { padding: 0; overflow: hidden; }
/* ═══════════════════════════════════════════════════════════════════════════
   Pairing maps — "check page matches" (Slice G3) — mechanically extracted
   from pairing_maps/show.html.erb's inline <style> block, with every
   deprecated alias rewritten to a semantic token and every color literal
   replaced by a token. The shared `.lg-modal`/`.lg-zoom` lightbox chrome
   (also used by run_previews/show) moved to
   platform/components/modals.css instead of duplicating here — everything
   below is genuinely pairing_maps-only (verified with grep across
   app/views before extraction).

   Color decisions:
     var(--warn)→--color-status-warning, var(--line)→--color-border,
     var(--muted)→--color-text-muted, var(--panel)→--color-surface,
     var(--brand)→--color-action-primary, var(--ink)→--color-text-primary,
     var(--ok)→--color-status-success.
     #fff (figure/lightbox backgrounds) → --color-surface (exact).
     #fafbfc (thumbnail letterbox backgrounds) → --color-zinc-50 (#fafafa,
     an imperceptible ~2/255 nudge — same call already made for
     --panel-alt's #f8f9fa across earlier G3 commits; #fafbfc is even
     closer to zinc-50 than that one was).
     .all-clear banner's bg/border are a distinct, lighter tint pairing
     than any existing status-success token (status-success-tint is
     --color-green-50 #f2fbf2, noticeably different). Renamed the rule to
     .pair-all-clear (its own `<div class="all-clear">` doesn't collide
     with the *shared* `.run-review-next.all-clear` component in
     legacy.css — confirmed via grep that no view combines both classes on
     one element — but keeping the same bare class name in two unrelated
     stylesheets invited confusion, so this page's version got a
     page-scoped name). Slice H captured the exact pairing as
     --color-pair-all-clear-bg/-border in tokens.css rather than picking a
     nudge that wouldn't match either exactly.
     .panel's inline warning-callout override now reads
     --color-pm-warn-border/-bg (same values, same reasoning).
   ═══════════════════════════════════════════════════════════════════════════ */

.pair-card { padding: 16px 20px; }
.pair-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.pair-head h3 { margin: 0; font-size: 15px; }
.pair-flags { margin: 0 0 10px; padding: 0; list-style: none; }
.pair-flags li { color: var(--color-status-warning); font-size: 13px; font-weight: 600; margin: 2px 0; }
.pair-flags li::before { content: "\26A0\FE0E  "; }
.pair-images { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 720px) { .pair-images { grid-template-columns: 1fr; } }
.pair-figure { margin: 0; border: 1px solid var(--color-border); border-radius: 10px; overflow: hidden; background: var(--color-surface); }
.pair-figure figcaption { display: flex; align-items: center; gap: 8px; padding: 8px 12px;
                          border-bottom: 1px solid var(--color-border); font-size: 13px; }
.pair-figure img { display: block; width: 100%; height: auto; cursor: zoom-in; }
.pair-card.compact .pair-figure img { max-height: 170px; object-fit: contain; background: var(--color-zinc-50); }
.pair-placeholder { padding: 26px 12px; text-align: center; color: var(--color-text-muted); font-size: 13px; }
.pair-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 12px; }
.pair-actions form { display: inline; }
.pair-all-clear { display: flex; align-items: center; gap: 12px; background: var(--color-pair-all-clear-bg); border: 1px solid var(--color-pair-all-clear-border);
             color: var(--color-status-success); border-radius: 12px; padding: 18px 22px; font-weight: 600; margin-bottom: 20px; }
.pair-all-clear .mark { font-size: 22px; }
.unused-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 12px; }
.unused-grid figure { margin: 0; border: 1px solid var(--color-border); border-radius: 8px; overflow: hidden; text-align: center; }
.unused-grid img { display: block; width: 100%; height: 130px; object-fit: contain; background: var(--color-zinc-50); cursor: zoom-in; }
.unused-grid figcaption { font-size: 12px; color: var(--color-text-muted); padding: 5px; }

.lg-modal-box { background: var(--color-surface); border-radius: 12px; padding: 18px 22px;
                max-width: 880px; width: 100%; max-height: 92vh; overflow-y: auto; }
.picker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin: 14px 0; }
.picker-page { border: 2px solid var(--color-border); border-radius: 10px; background: var(--color-surface); padding: 0 0 6px;
               cursor: pointer; font: inherit; text-align: center; }
.picker-page:hover { border-color: var(--color-action-primary); }
.picker-page img { display: block; width: 100%; height: 160px; object-fit: contain; background: var(--color-zinc-50);
                    border-radius: 8px 8px 0 0; }
.picker-page span { font-size: 13px; font-weight: 600; color: var(--color-text-primary); }
.picker-page.current { border-color: var(--color-status-success); }
.picker-page.current span::after { content: " (current)"; color: var(--color-status-success); font-weight: 400; }

/* ═══ Inline-style burn-down (Slice G3) ═══════════════════════════════════════ */
.pm-intro-hint.hint      { max-width: 680px; margin: 0; }
.panel.pm-warn-panel     { border-color: var(--color-pm-warn-border); background: var(--color-pm-warn-bg); }
.pm-warn-panel h2        { color: var(--color-status-warning); }
.pm-figcaption-spacer    { flex: 1; }
.pm-looksgood-details    { margin: 20px 0; }
.pm-looksgood-summary    { cursor: pointer; font-weight: 600; font-size: 15px; }
.pm-looksgood-body       { margin-top: 14px; }
.pm-picker-title         { margin-bottom: 4px; }
/* ═══════════════════════════════════════════════════════════════════════════
   PDF ingestions (Slice G3) — mechanically extracted from
   pdf_ingestions/show.html.erb's inline style= attributes on the header row,
   the warning flash, the approve/reject form (title input, visibility
   radios, chunk cards with heading-path breadcrumbs), and the read-only
   chunk preview list. pdf_ingestions/index.html.erb's only two inline
   styles were a title+action header row, replaced with the existing
   shared/_page_header partial (same treatment as web_ingestions/index).

   Structurally this view is PDF's near-twin of web_ingestions/show — same
   chunk-card / chunk-preview pattern — but it has no client-side
   select/deselect-all JS and one extra field (heading_path breadcrumb), so
   its classes are kept page-scoped here (pi-*) rather than retrofitted onto
   the already-committed wi-* classes.

   Color decisions: #e0e0e0 (chunk card border) → --color-border (zinc-200,
   same nudge already accepted for web_ingestions); #999 (chunk meta:
   page/char count) → --color-text-subtle (zinc-400); #555 (chunk body
   preview) → --color-zinc-600 directly, matching the equivalent
   web_ingestions decision. #fffbea/#d4a700 (warning flash) → same treatment
   as web_ingestions' wi-flash-unchanged: background exact-matches
   --color-amber-50, border → the Slice H --color-flash-warning-border
   primitive (no nearby existing primitive; shared with web_ingestions.css,
   same exact literal).
   ═══════════════════════════════════════════════════════════════════════════ */

.pi-flash-warning { background: var(--color-amber-50); border-color: var(--color-flash-warning-border); margin-bottom: var(--space-2); }

.pi-title-input { width: 100%; max-width: 480px; }

.pi-visibility-options { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.pi-visibility-label { display: flex; align-items: center; gap: 8px; cursor: pointer; }

.pi-chunk-card { border: 1px solid var(--color-border); border-radius: 6px; padding: 12px; }
.pi-chunk-title-row {
  margin: 0 0 4px;
  font-weight: 600;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
}
.pi-chunk-heading-path { font-weight: 400; }
.pi-chunk-meta { font-weight: 400; color: var(--color-text-subtle); font-size: 13px; white-space: nowrap; }
.pi-chunk-preview { margin: 0; white-space: pre-wrap; font-size: 13px; color: var(--color-zinc-600); }
/* ═══════════════════════════════════════════════════════════════════════════
   Pipeline runs (Slice G3) — mechanically extracted from
   pipeline_runs/index.html.erb and pipeline_runs/show.html.erb's inline
   style= attributes.

   No color decisions were needed here — every style= was pure
   typography/spacing except the two progress-fill width= attributes,
   which were genuinely data-driven (server-computed percentages, not
   static values) and were converted to the `--pr-fill: <%= pct %>%`
   custom-property pattern instead of a page-scoped class, matching the
   data-driven-value allowance used by other progress/position
   indicators across Slice G3 (see platform/legacy.css's shared
   `.progress-fill { width: var(--pr-fill, 0); }`, changed once to serve
   both files below plus every other `.progress-fill` consumer, which all
   set width via modifier classes that still win the cascade since
   platform/pages/ loads after platform/legacy.css).

   The one JS-built phase-row (pipeline_runs/show's ensurePhaseRow) that
   used to hard-code style="width:0%" on creation now omits it entirely —
   the shared `.progress-fill` rule's `var(--pr-fill, 0)` fallback already
   renders 0 width with no custom property set, and the existing
   `row.querySelector(...).style.width = pct + "%"` JS write (unrelated
   to the guard — a `.style.width =` assignment, not a `style=` string)
   still updates it on the next poll, unchanged.
   ═══════════════════════════════════════════════════════════════════════════ */

/* pipeline_runs/index */
.pr-index-error { max-width: 280px; }

/* pipeline_runs/show */
.pr-section-hint { margin-top: -6px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Review queue (Slice G3) — mechanically extracted from
   review_queue/index.html.erb's inline style= attributes on the section
   warnings list, per-row meta line, and the inline fix form.

   No color decisions were needed — every style= here was purely
   typographic/spacing (font-size, margin), no hex/var() color values.

   The fix-form input max-width overrides are nested under
   .queue-fix-form to out-specify the base input[type="text"] rule
   (components/forms.css) — same specificity fix as sections/show's
   row editor and the standalone fix landed for 3 earlier commits.
   ═══════════════════════════════════════════════════════════════════════════ */

.rq-warnings-list { margin: 6px 0 0; font-size: 13px; }
.rq-row-meta { margin-top: 3px; }

.queue-fix-form .rq-fix-input--part-number { max-width: 170px; }
.queue-fix-form .rq-fix-input--handle { max-width: 200px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Run previews — "preview & edit" (Slice G3) — mechanically extracted from
   run_previews/show.html.erb's inline <style> block, with every deprecated
   alias rewritten to a semantic token and every color literal replaced by
   a token. This view's `.lg-modal`/`.lg-zoom` rules were a verbatim
   duplicate of pairing_maps/show's copy (confirmed by grep) and now
   consume the shared platform/components/modals.css component instead of
   re-declaring them here.

   Color decisions:
     var(--panel)→--color-surface, var(--line)→--color-border,
     var(--ink)→--color-text-primary, var(--muted)→--color-text-muted,
     var(--brand)→--color-action-primary, var(--ok)→--color-status-success.
     #fff (diagram background) → --color-surface (exact).
     #eef1f6 (toc-link hover bg) and #f4f8ff (editable-cell hover bg) both
     → --color-blue-50 (#eff6ff) — the same light-blue hover/active tint
     already used throughout the platform (nav.css, cards.css, dashboard/
     businesses/match_files page CSS) for this exact treatment; both are
     small (~2-7/255 per channel) imperceptible nudges onto an
     already-established primitive rather than two new one-off near-white
     blues.
   ═══════════════════════════════════════════════════════════════════════════ */

.preview-layout { display: grid; grid-template-columns: 230px 1fr; gap: 20px; align-items: start; }
@media (max-width: 900px) { .preview-layout { grid-template-columns: 1fr; } .preview-toc { display: none; } }
.preview-toc { position: sticky; top: 74px; max-height: calc(100vh - 90px); overflow-y: auto;
               background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 12px; padding: 12px 0; }
.preview-toc a { display: block; padding: 6px 16px; font-size: 13px; color: var(--color-text-primary); }
.preview-toc a:hover { background: var(--color-blue-50); text-decoration: none; }
.preview-toc .count { color: var(--color-text-muted); font-size: 12px; }
.preview-section { scroll-margin-top: 74px; }
.preview-section h2 { display: inline; font-size: 18px; }
.section-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.pencil { background: none; border: none; cursor: pointer; color: var(--color-text-muted); font-size: 14px; padding: 2px 4px; }
.pencil:hover { color: var(--color-action-primary); }
.preview-diagram { margin: 0 0 14px; text-align: center; }
.preview-diagram img { max-width: 100%; max-height: 460px; border: 1px solid var(--color-border);
                       border-radius: 10px; background: var(--color-surface); cursor: zoom-in; }
td.editable { cursor: pointer; }
td.editable:hover { background: var(--color-blue-50); box-shadow: inset 0 0 0 1px var(--color-action-primary); border-radius: 4px; }
td.editable:hover::after { content: " \270E"; color: var(--color-action-primary); font-size: 12px; }
td.editable input { width: 100%; max-width: none; padding: 4px 6px; font-size: 14px; }
.saved-tick { color: var(--color-status-success); font-size: 12px; font-weight: 700; margin-left: 6px;
              opacity: 1; transition: opacity 1.2s ease 1s; }
.saved-tick.fade { opacity: 0; }

/* ═══ Inline-style burn-down (Slice G3) ═══════════════════════════════════════ */
.rp-intro-hint.hint  { max-width: 680px; margin: 0; }
.rp-th-item          { width: 60px; }
.rp-th-part-number   { width: 180px; }
.rp-th-qty           { width: 70px; }
.rp-th-product-link  { width: 150px; }
.rp-count-spacer     { flex: 1; }
.rp-part-count       { font-size: 13px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Sections controller (Slice G3) — one page CSS file shared by every
   sections/* view. Rules below are from sections/rescan.html.erb and
   sections/show.html.erb.

   Color decisions (rescan):
     var(--warn)→--color-status-warning for the "carries manual edits"
     callout. The re-scan progress bar reuses the platform's existing
     shared `.progress-track`/`.progress-fill` components (legacy.css) —
     those still carry deprecated var(--surface-muted)/var(--green)
     aliases, but that's Slice H's cross-cutting alias-removal work, not
     this per-view G3 pass; nothing here needed a new color decision.
     This bar's width:50% + pulse animation is a static "indeterminate
     progress" indicator (no ERB percentage expression backs it — this
     status page always shows the same half-filled pulse while polling),
     so it became a plain page-scoped modifier class rather than the
     `--fill: <%= pct %>%` custom-property pattern used by the *other*
     (genuinely data-driven) progress bars in pipeline_runs/show,
     pipeline_runs/index, and sections/show's bbox overlays.

   Color decisions (show):
     var(--brand) (the "Linking rows…" busy-hint text color, previously
     set via JS busyEl.style.color/fontWeight — moved to a toggled
     .bom-tools-busy--active class) → --color-action-primary, the
     standard alias resolution used throughout Slice G3. This wasn't
     technically caught by the STYLE_ATTR_RE guard regex (JS
     `el.style.color = "..."` doesn't match `style\s*[:=]\s*["']`), but
     was cleaned up anyway for design-system consistency, matching the
     same treatment given to other JS-driven inline styling in earlier
     Slice G3 groups (documents/new, export_packages).
     sections/show's region-overlay left/top/width/height JS assignments
     are genuinely data-driven (computed from each row's bbox JSON at
     runtime) — intentionally left as direct .style property writes,
     same rationale as the other dynamic progress bars noted above.
   ═══════════════════════════════════════════════════════════════════════════ */

@keyframes rescan-pulse { from { opacity: .5; } to { opacity: 1; } }

.rescan-progress-track { width: 280px; }
.rescan-progress-fill  { width: 50%; animation: rescan-pulse 1.2s ease-in-out infinite alternate; }
.rescan-edited-warning { color: var(--color-status-warning); }
.rescan-compare-grid   { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.rescan-actions        { margin-top: 18px; }
.rescan-cancel-ml       { margin-left: 8px; }

/* sections/show */
.sec-title { font-size: 19px; }
.sec-no-page-msg { padding: 14px; }
.sec-footnotes-heading { margin-top: 18px; }
/* #section-title scoping bumps specificity above the base
   input[type="text"] rule (legacy.css), matching the specificity the
   original inline style= had over that same rule. */
#section-title .sec-rename-input { font-size: 17px; min-width: 320px; }
.bom-tools-busy--active { color: var(--color-action-primary); font-weight: 600; }

/* JS-built row editor (openEditor() in show.html.erb). The max-width
   overrides are nested under .sec-row-editor-fields to out-specify the
   base input[type="text"] rule (legacy.css), matching the specificity
   the original inline style= had over that same rule. */
.sec-row-editor-fields { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; padding: 6px 0; }
.sec-row-editor-label { margin: 0; }
.sec-row-editor-label--grow { flex: 1; }
.sec-row-editor-fields .sec-row-editor-input--part-number { max-width: 170px; }
.sec-row-editor-fields .sec-row-editor-input--qty { max-width: 70px; }
.sec-row-editor-fields .sec-row-editor-input--full { max-width: none; }
.sec-row-editor-fields .sec-row-editor-input--md { max-width: 200px; }
/* ═══════════════════════════════════════════════════════════════════════════
   Sidekiq setup (Slice G3) — mechanically extracted from
   sidekiq_setup/show.html.erb's inline style= attributes on the two
   option headings and the setup steps list.

   No color decisions were needed — every style= here was pure
   typography/spacing. The 20px margin-top (used identically by both
   option headings) doesn't match the --space-* scale (16/24px on either
   side) and is kept as a documented literal rather than forced onto the
   scale, since it's a pre-existing visual choice with a single-purpose
   consumer.
   ═══════════════════════════════════════════════════════════════════════════ */

.ss-option-heading { margin-top: 20px; }
.ss-steps-list { margin: 0; padding-left: 20px; line-height: 1.7; }
/* ═══════════════════════════════════════════════════════════════════════════
   Web ingestions (Slice G3) — mechanically extracted from
   web_ingestions/show.html.erb's inline style= attributes on the
   approve/reject form (title input, visibility radios, chunk cards) and the
   read-only chunk preview list. web_ingestions/index.html.erb's only two
   inline styles were a title+action header row, replaced with the existing
   shared/_page_header partial instead of new CSS.

   Color decisions: #e0e0e0 (chunk card border) → --color-border (zinc-200,
   a ~4/255 nudge); #999 (chunk anchor/char-count meta) → --color-text-subtle
   (zinc-400, ~8/255 nudge — this token exists specifically for this kind of
   secondary meta text); #666 (chunk-count label) → --color-text-muted
   (zinc-500, ~11/255 nudge); #555 (chunk body preview) → --color-zinc-600
   directly (no semantic alias sits at this darkness, so referencing the
   primitive is the same call prior G3 commits make for indigo/blue nudges).
   #fffbea (content-unchanged flash background) → --color-amber-50 (exact to
   1/255). Its border #d4a700 is a distinct gold with no nearby primitive —
   captured as the Slice H --color-flash-warning-border primitive (shared
   with the identical border in pdf_ingestions.css).
   ═══════════════════════════════════════════════════════════════════════════ */

.wi-flash-unchanged { background: var(--color-amber-50); border-color: var(--color-flash-warning-border); }

.wi-title-input { width: 100%; max-width: 480px; }

.wi-visibility-options { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.wi-visibility-label { display: flex; align-items: center; gap: 8px; cursor: pointer; }

.wi-chunk-count { font-weight: 400; font-size: 14px; color: var(--color-text-muted); }

.wi-select-all-label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; }

.wi-chunk-card { border: 1px solid var(--color-border); border-radius: 6px; padding: 12px; }
.wi-chunk-label { display: flex; gap: 10px; cursor: pointer; align-items: flex-start; }
.wi-chunk-checkbox { margin-top: 3px; flex-shrink: 0; }
.wi-chunk-body { flex: 1; }
.wi-chunk-title { display: block; font-weight: 600; margin: 0 0 4px; }
.wi-chunk-meta { font-weight: 400; color: var(--color-text-subtle); }
.wi-chunk-meta--right { float: right; }
.wi-chunk-preview { display: block; margin: 0; white-space: pre-wrap; font-size: 13px; color: var(--color-zinc-600); }
/* ═══════════════════════════════════════════════════════════════════════════
   LumaRef platform utilities (Slice G1)

   Small `u-`-prefixed set for purely geometric/layout concerns, kept
   deliberately tiny. Component classes (.card, .badge, ...) stay unprefixed;
   `u-` avoids collisions with the legacy body. Loads LAST in platform.css so
   utilities win equal-specificity cascade conflicts (see the manifest's
   cascade invariant). The token-reading `.text-*` color utilities from Slice B
   (platform/typography.css) are the semantic set and are NOT duplicated here.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Spacing (from --space-*) ───────────────────────────────────────────── */
.u-m-0 { margin: 0; }
.u-mt-0 { margin-top: 0; }
.u-mt-1 { margin-top: var(--space-1); }
.u-mt-2 { margin-top: var(--space-2); }
.u-mt-3 { margin-top: var(--space-3); }
.u-mt-4 { margin-top: var(--space-4); }
.u-mt-5 { margin-top: var(--space-5); }

.u-mb-0 { margin-bottom: 0; }
.u-mb-1 { margin-bottom: var(--space-1); }
.u-mb-2 { margin-bottom: var(--space-2); }
.u-mb-3 { margin-bottom: var(--space-3); }
.u-mb-4 { margin-bottom: var(--space-4); }

.u-gap-1 { gap: var(--space-1); }
.u-gap-2 { gap: var(--space-2); }
.u-gap-3 { gap: var(--space-3); }

.u-ml-1 { margin-left: var(--space-1); }
.u-ml-2 { margin-left: var(--space-2); }

/* ── Flex ────────────────────────────────────────────────────────────────── */
.u-row { display: flex; align-items: center; }
.u-row-between { display: flex; align-items: center; justify-content: space-between; }
.u-row-wrap { display: flex; align-items: center; flex-wrap: wrap; }
.u-col { display: flex; flex-direction: column; }
.u-flex-1 { flex: 1; }
.u-ml-auto { margin-left: auto; }

/* ── Text ────────────────────────────────────────────────────────────────── */
.u-text-xs { font-size: 12px; }
.u-text-sm { font-size: 13px; }
.u-text-md { font-size: 14px; }
.u-text-right { text-align: right; }
.u-text-center { text-align: center; }
.u-text-nowrap { white-space: nowrap; }
.u-tabular { font-variant-numeric: tabular-nums; }

/* ── Misc ────────────────────────────────────────────────────────────────── */
.u-dim { opacity: 0.6; }
.u-w-full { width: 100%; }
.u-hidden { display: none; }
.u-inline { display: inline; }
.u-inline-block { display: inline-block; }
.u-display-contents { display: contents; }
/*
 * LumaRef platform stylesheet — Sprockets manifest (directives only).
 *
 * There is exactly one browser-loaded platform asset (platform.css). Modular
 * source lives under platform/ and is concatenated here in explicit cascade
 * order. Uses the CSS block-comment directive syntax (not the //= JS/SCSS form),
 * matching app/assets/stylesheets/application.css.
 *
 * This file holds NO CSS body — that would trigger the require_self trap and put
 * the manifest's own rules at the end of the cascade. Keep it directives-only.
 *






 */
