:root {
  /* Brand */
  --color-primary: #00BA70;
  --color-primary-hover: #009e5f;
  --color-primary-soft: rgba(0, 186, 112, 0.10);
  --color-primary-ring: rgba(0, 186, 112, 0.25);

  /* Text */
  --color-text: #334155;
  --color-text-muted: #64748b;
  --color-text-disabled: #94a3b8;
  --color-heading: #0f172a;
  --color-heading-2: #1e293b;
  --color-text-supporting: #475569;

  /* Surfaces */
  --color-bg-page: #f8f8f8;
  --color-bg-surface: #ffffff;
  --color-bg-subtle: #e2e8f0;
  --color-bg-soft: #f5f7fa;
  --color-bg-disabled: #e1e1e1;

  /* Borders */
  --color-border: #e2e8f0;
  /* Phoenix form-control border (slate-300) — one step darker than the card
     rule; used by the .phx-* boxed-cell skin so controls read as controls. */
  --color-border-strong: #cbd5e1;
  --color-border-focus: #00BA70;

  /* Status */
  --color-error: #DC2626;
  --color-error-fill: #EF4444;
  --color-error-bg: #fef2f2;
  --color-info: #0369a1;
  --color-info-bg: #e0f2fe;
  --color-warning: #b45309;
  --color-warning-bg: #fef3c7;
  --color-success: #15803d;
  --color-success-bg: #dcfce7;

  --shadow-sm: 0 1px 2px 0 rgb(15 23 42 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(15 23 42 / 0.06), 0 2px 4px -2px rgb(15 23 42 / 0.04);
  --shadow-lg: 0 10px 25px -5px rgb(15 23 42 / 0.10), 0 8px 10px -6px rgb(15 23 42 / 0.04);
}

html, body { height: 100%; margin: 0; }
body {
  background: var(--color-bg-page);
  color: var(--color-text);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}
#map { width: 100%; height: 100%; }

/* ===== Typography helpers ===== */
.h1 { font-size: 22px; font-weight: 700; color: var(--color-heading); line-height: 1.2; letter-spacing: -0.01em; }
.h2 { font-size: 16px; font-weight: 600; color: var(--color-heading-2); line-height: 1.3; }
.section-title {
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  margin: 0 0 8px;
}
.muted { color: var(--color-text-muted); }
.text-strong { color: var(--color-heading-2); font-weight: 600; }
.tabular { font-variant-numeric: tabular-nums; }

/* ===== Buttons ===== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0 0.875rem;
  height: 36px;
  border-radius: 0.375rem;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  border: 1px solid transparent;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
  cursor: pointer;
  white-space: nowrap;
}
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--color-bg-surface), 0 0 0 4px var(--color-primary-ring);
}
.btn-primary {
  background: var(--color-primary);
  color: #fff;
}
.btn-primary:hover:not(:disabled) { background: var(--color-primary-hover); }
.btn-primary:disabled,
.btn[disabled] {
  background: var(--color-bg-disabled);
  color: var(--color-text-disabled);
  cursor: not-allowed;
}
.btn-secondary {
  background: var(--color-bg-surface);
  color: var(--color-text);
  border-color: var(--color-border);
}
.btn-secondary:hover:not(:disabled) {
  background: var(--color-bg-soft);
  border-color: var(--color-border-strong);
  color: var(--color-heading-2);
}
.btn-ghost {
  background: transparent;
  color: var(--color-text-muted);
}
.btn-ghost:hover:not(:disabled) {
  background: var(--color-bg-subtle);
  color: var(--color-text);
}
.btn-sm {
  height: 32px;
  padding: 0 0.75rem;
  font-size: 13px;
}
.btn-icon-leading svg,
.btn-icon-leading .ico {
  width: 14px;
  height: 14px;
  display: inline-block;
}

/* ===== Inputs ===== */
.input {
  width: 100%;
  height: 36px;
  padding: 0 0.75rem;
  font-size: 13px;
  color: var(--color-text);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.375rem;
  transition: border-color 120ms ease, box-shadow 120ms ease;
  font-family: inherit;
}
.input::placeholder { color: var(--color-text-disabled); }
.input:focus {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: 0 0 0 3px var(--color-primary-ring);
}
.input:disabled {
  background: var(--color-bg-disabled);
  color: var(--color-text-disabled);
  cursor: not-allowed;
}
.input-lg { height: 40px; padding: 0 0.875rem; font-size: 14px; }

/* ===== Cards ===== */
.card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  box-shadow: var(--shadow-sm);
}

/* ===== Badges ===== */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 2px 8px;
  border-radius: 9999px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  line-height: 1.4;
}
.badge-info { background: var(--color-info-bg); color: var(--color-info); }
.badge-success { background: var(--color-success-bg); color: var(--color-success); }
.badge-warning { background: var(--color-warning-bg); color: var(--color-warning); }
.badge-danger { background: var(--color-error-bg); color: var(--color-error); }
.badge-neutral { background: var(--color-bg-subtle); color: var(--color-text-supporting); }
.badge-primary {
  background: var(--color-primary-soft);
  color: var(--color-primary-hover);
}
.badge-mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
  font-size: 11px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Reserves a fixed height for the per-column header info row (Quote number /
   Line items / Owner / First published) regardless of how many fields a
   given column has — the DB column carries Owner + First published, the
   JSON columns don't, and at 3-column width the extra fields wrap their
   labels onto a second line. Without this, the KPI cards below start at a
   different y-position in each column. 5rem matches the DB column's
   worst-case (2-line-wrapped) height. */
.column-info-row {
  min-height: 5rem;
}

/* ===== Click-and-hover tooltip bubble (public/js/tooltip.js) ===== */
.tooltip-bubble {
  position: fixed;
  z-index: 200;
  max-width: 22rem;
  background: var(--color-text, #1f2937);
  color: #fff;
  font-size: 11.5px;
  line-height: 1.5;
  padding: 0.5rem 0.625rem;
  border-radius: 0.375rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
  pointer-events: none;
  white-space: normal;
}
[data-tooltip] { cursor: help; }

/* ===== KPI grid ===== */
.kpi-card {
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  padding: 0.625rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* A [title] tooltip alone gives no visual signal there's more to see on
   hover — cursor:help plus a dotted underline under the label flags it. */
.kpi-card[data-tooltip] .kpi-label { text-decoration: underline dotted; text-underline-offset: 2px; }
.kpi-label {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
.kpi-value {
  font-size: 22px;
  font-weight: 700;
  color: var(--color-heading);
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
  letter-spacing: -0.01em;
}
.kpi-value-sm {
  font-size: 15px;
  font-weight: 600;
  color: var(--color-heading-2);
  font-variant-numeric: tabular-nums;
}
.landed-cost-card {
  position: relative;
  background:
    linear-gradient(135deg, var(--color-primary-soft) 0%, transparent 70%),
    var(--color-bg-surface);
  border: 1px solid rgba(0, 186, 112, 0.30);
  border-radius: 0.5rem;
  padding: 0.875rem;
  margin-bottom: 1rem;
}
.landed-cost-card .row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.landed-cost-card .landed-label {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--color-primary-hover);
  text-transform: none;
}
.landed-cost-card .kpi-value { color: var(--color-primary-hover); }
.landed-cost-card .formula {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  color: var(--color-text-muted);
  font-style: italic;
}

/* ===== Spinner ===== */
.spinner {
  display: inline-block;
  width: 16px;
  height: 16px;
  border: 2px solid var(--color-bg-subtle);
  border-top-color: var(--color-primary);
  border-radius: 9999px;
  animation: gm-spin 0.7s linear infinite;
  vertical-align: middle;
}
@keyframes gm-spin { to { transform: rotate(360deg); } }

/* ===== Map info bubble (HERE Maps) ===== */
.map-info-bubble {
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 12px;
  line-height: 1.4;
  width: 364px;
  max-width: 364px;
  padding: 4px 6px;
  white-space: normal;
  word-break: break-word;
}
.H_ib .H_ib_body { max-width: 364px; }
.H_ib .H_ib_content { max-width: 364px; }
.map-info-bubble .header {
  font-weight: 600;
  font-size: 13px;
  margin: 0 0 4px;
  color: var(--color-heading);
}
.map-info-bubble .content {
  margin: 0;
  color: var(--color-heading-2);
}
.map-info-bubble .content:empty { display: none; }
.map-info-bubble .footer {
  color: var(--color-text-muted);
  font-size: 11px;
  margin: 4px 0 0;
}
.map-info-bubble .footer:empty { display: none; }

/* ===== Toggle checkbox (vehicle / route visibility) ===== */
.toggle-check {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--color-border-strong);
  border-radius: 4px;
  background: var(--color-bg-surface) center / 60% 60% no-repeat;
  cursor: pointer;
  flex-shrink: 0;
  margin: 0;
  transition: background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease, transform 80ms ease;
}
.toggle-check:hover {
  border-color: var(--color-primary);
}
.toggle-check:checked {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-6.5'/></svg>");
}
.toggle-check:active {
  transform: scale(0.92);
}
.toggle-check:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--color-primary-ring);
}
.toggle-check:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ===== Route swatch ===== */
.route-color-swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 9999px;
  vertical-align: middle;
  background: var(--color-bg-subtle);
  box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgb(15 23 42 / 0.06);
}

/* ===== Vehicle / Route entity card ===== */
.entity-card {
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: var(--color-bg-surface);
  margin-bottom: 0.5rem;
  overflow: hidden;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.entity-card[open] {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-sm);
}
.entity-card > summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  font-size: 13px;
  color: var(--color-heading-2);
}
.entity-card > summary:hover { background: var(--color-bg-soft); }
.entity-card > summary::-webkit-details-marker { display: none; }
/* Shared right-edge chevron used by .entity-card (vehicle cards) and
   .input-products-collapse (input-JSON line-item table). Closed = ▾;
   open = rotate 180° → ▴. The smaller inline chevron on
   .pickup-route .stop-products is kept separate — different size, position,
   and visual role (tree-style disclosure inside a list item). */
.entity-card > summary::after,
.input-products-collapse > summary::after {
  content: "▾";
  margin-left: auto;
  color: var(--color-text-muted);
  font-size: 14px;
  line-height: 1;
  transition: transform 120ms ease;
}
.entity-card[open] > summary::after,
.input-products-collapse[open] > summary::after {
  transform: rotate(180deg);
}
.entity-card .entity-meta {
  font-size: 11.5px;
  color: var(--color-text-muted);
  font-weight: 500;
}
.entity-card-body {
  padding: 0.625rem 0.875rem 0.875rem;
  background: var(--color-bg-soft);
  border-top: 1px solid var(--color-border);
  font-size: 12.5px;
}
.entity-card-body .stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin-bottom: 0.5rem;
  font-size: 11.5px;
  color: var(--color-text-muted);
}
.entity-card-body .stat-row .v {
  color: var(--color-heading-2);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* A stale rate-sheet value (route changed since GoSource last ran) shown
   struck through, right next to the freshly recomputed one. Also used by
   the side-by-side compare table's Global Model row (.side-compare-table). */
.entity-card-body .stat-row .v.v-stale,
.side-compare-table .v.v-stale {
  text-decoration: line-through;
  color: var(--color-text-muted);
  font-weight: 500;
}

details.subtle > summary { cursor: pointer; }
details.subtle > summary::-webkit-details-marker { display: none; }

/* ===== Stop list ===== */
.stop-list { list-style: none; padding: 0; margin: 0; }
.stop-list > li {
  padding: 3px 4px;
  font-size: 12.5px;
  color: var(--color-text);
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  border-radius: 4px;
}
.stop-list > li:hover { background: rgb(255 255 255 / 0.6); }
.stop-pin {
  display: inline-block;
  min-width: 2.6rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  color: var(--color-text-muted);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.stop-drop {
  color: var(--color-success);
  font-weight: 600;
  font-size: 10px;
  padding: 1px 6px;
  background: var(--color-success-bg);
  border-radius: 9999px;
  margin-right: 0.25rem;
}

/* ===== Item list ===== */
.item-list { padding: 0; margin: 0; list-style: none; }
.item-list > li {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
}
.item-list > li:last-child { border-bottom: none; }
.item-name { font-weight: 600; color: var(--color-heading-2); font-size: 13px; }
.item-name .common { color: var(--color-text-muted); font-weight: 400; margin-left: 0.25rem; }
.item-meta {
  font-size: 11.5px;
  color: var(--color-text-muted);
  margin-top: 2px;
  padding-left: 1.6rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
}
.item-meta .sep { color: var(--color-text-disabled); }
.item-seq {
  display: inline-block;
  min-width: 1.25rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--color-text-muted);
  margin-right: 0.25rem;
  text-align: right;
}

/* ===== Tables ===== */
.kv-table {
  width: 100%;
  font-size: 13px;
  color: var(--color-text);
  border-collapse: collapse;
}
.kv-table td { padding: 3px 0; }
.kv-table td:last-child {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--color-heading-2);
  font-weight: 500;
}
.kv-table tr.total td {
  border-top: 1px solid var(--color-border);
  padding-top: 8px;
  font-weight: 700;
  color: var(--color-heading);
  font-size: 13.5px;
}
.kv-table tr.total td:first-child { font-weight: 600; }

/* Small "?" hover-affordance that signals a tooltip on a nearby element.
   Pair with a `title` on the parent so the browser shows the tooltip when
   the cursor is anywhere over the row/label. The icon itself just makes
   the affordance visible — without it, users would never know to hover. */
.info-tip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 13px;
  height: 13px;
  margin-left: 4px;
  border-radius: 9999px;
  background: var(--color-bg-subtle);
  color: var(--color-text-muted);
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  font-style: normal;
  cursor: help;
  vertical-align: middle;
  flex-shrink: 0;
  transition: background-color 100ms ease, color 100ms ease;
}
.info-tip:hover {
  background: var(--color-border-strong);
  color: var(--color-text);
}

.gm-table {
  width: 100%;
  font-size: 12px;
  color: var(--color-text);
  border-collapse: collapse;
  border: 1px solid var(--color-border);
  border-radius: 0.375rem;
  overflow: hidden;
}
.gm-table th {
  text-align: left;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  padding: 6px 8px;
  background: var(--color-bg-soft);
  border-bottom: 1px solid var(--color-border);
}
.gm-table td {
  padding: 5px 8px;
  border-bottom: 1px solid var(--color-border);
}
.gm-table tr:last-child td { border-bottom: none; }
.gm-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.gm-table tr.row-info { background: var(--color-success-bg); }
.gm-table tr.row-warn { background: var(--color-warning-bg); }
.gm-table tr.row-danger { background: var(--color-error-bg); }

/* Phoenix quote-table look (Ops Quote Comparison) — mirrors
   phoenix-admin-frontend's quote-construction table so ops read it like the
   screen they already know. Every value below was read off the live preprod
   page's computed styles (2026-09-10) rather than eyeballed: PrimeNG
   p-datatable header 11px/600 #9b9b9b on #f2f2f2 with 8px outer radii,
   12px/500 #495057 cells at 16px 8px padding (72px rows), even rows #f8f8f8
   with no row rules, p-inputtext boxes 36px / #cbd5e1 / 6px, p-autocomplete
   multiple = one bordered field holding p-chips (#f1f5f9 fill, #c2c2c2
   border, 4px radius, 12px/400 #1e293b). Phoenix-specific greys that aren't
   in this dashboard's palette are written inline, on purpose — they exist to
   match Phoenix, not the dashboard. Opt-in via `.gm-table.phx-table`. */
/* The exact font files phoenix-admin-frontend serves ("Spartan" v3 latin,
   OFL-licensed, ~11KB each) — self-hosted so metrics match Phoenix 1:1
   (Google's current "League Spartan" is the same design but renders
   noticeably heavier/wider). Loaded only where .phx-page is set. */
@font-face { font-family: 'Spartan'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/spartan-v3-latin-400.woff2') format('woff2'); }
@font-face { font-family: 'Spartan'; font-style: normal; font-weight: 500; font-display: swap; src: url('/fonts/spartan-v3-latin-500.woff2') format('woff2'); }
@font-face { font-family: 'Spartan'; font-style: normal; font-weight: 600; font-display: swap; src: url('/fonts/spartan-v3-latin-600.woff2') format('woff2'); }
@font-face { font-family: 'Spartan'; font-style: normal; font-weight: 700; font-display: swap; src: url('/fonts/spartan-v3-latin-700.woff2') format('woff2'); }
/* Scoped to the page's <main> (and the body-level drawer), NOT <body>, so the
   shared app header / nav / user menu keep the dashboard's font. */
.phx-page,
.phx-page button,
.phx-page input,
.phx-drawer {
  font-family: 'Spartan', ui-sans-serif, system-ui, sans-serif;
}
.phx-title {
  font-size: 16px;
  font-weight: 600;
  color: #282828;
}
/* Fixed column widths (set per <th> by the renderer) so the layout is
   Phoenix's, not the browser's content-sizing; the wrapper scrolls if the
   card is narrower than the columns. */
.gm-table.phx-table {
  table-layout: fixed;
  /* Width is set inline by the renderer to the sum of the column widths so a
     widened column grows the table (and the wrapper scrolls) instead of
     squeezing its neighbours. */
  font-size: 12px;
  font-weight: 500;
  color: #495057;
  border: none;
  border-collapse: separate;
  border-spacing: 0;
}
.gm-table.phx-table th {
  position: relative;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: #9b9b9b;
  background: #f2f2f2;
  padding: 16px 8px;
  border-bottom: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Drag handle on each header's right edge (Phoenix: p-datatable-resizable-
   column). Invisible until hovered, then a 2px green rule. */
.gm-table.phx-table th .phx-resizer {
  position: absolute;
  top: 0;
  right: 0;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  user-select: none;
  touch-action: none;
}
.gm-table.phx-table th .phx-resizer::after {
  content: '';
  position: absolute;
  top: 8px;
  bottom: 8px;
  right: 3px;
  width: 2px;
  border-radius: 1px;
  background: transparent;
}
.gm-table.phx-table th .phx-resizer:hover::after,
.gm-table.phx-table th .phx-resizer.is-dragging::after { background: #00ba70; }
body.phx-resizing, body.phx-resizing * { cursor: col-resize !important; user-select: none !important; }
.gm-table.phx-table th:first-child { border-top-left-radius: 8px; border-bottom-left-radius: 8px; }
.gm-table.phx-table th:last-child { border-top-right-radius: 8px; border-bottom-right-radius: 8px; }
/* Sortable headers: click toggles asc/desc; the sorted column is Phoenix's
   green-on-#eefbf5 state and its glyph keeps only the active arrow solid. */
.gm-table.phx-table th.phx-th-sortable { cursor: pointer; }
.gm-table.phx-table th.phx-th-sortable:hover { color: #495057; }
.gm-table.phx-table th.phx-th-active,
.gm-table.phx-table th.phx-th-active:hover { color: #00ba70; background: #eefbf5; }
.gm-table.phx-table th .phx-sort {
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-left: 6px;
  vertical-align: -3px;
  color: #00ba70;
}
.gm-table.phx-table th.phx-th-active.asc .phx-sort-down,
.gm-table.phx-table th.phx-th-active.desc .phx-sort-up { opacity: 0.3; }
.gm-table.phx-table td {
  padding: 16px 8px;
  vertical-align: middle;
  border-bottom: none;
  overflow: hidden;
}
.gm-table.phx-table td.phx-text { color: #495057; font-weight: 500; line-height: 1.3; }
.gm-table.phx-table tbody tr:nth-child(even) td { background: #f8f8f8; }
.gm-table.phx-table tbody tr:hover td { background: #f1f5f9; }
/* Prod tab: rows the solver input disagrees with. Amber row tint + left
   marker; the disagreeing cell's box/field outlined; a row the input lacks
   entirely is marked the same way but with a dashed marker. */
.gm-table.phx-table tbody tr.phx-row-diff td,
.gm-table.phx-table tbody tr.phx-row-diff:nth-child(even) td { background: #fff7ed; }
.gm-table.phx-table tbody tr.phx-row-diff:hover td { background: #ffedd5; }
.gm-table.phx-table tbody tr.phx-row-diff td:first-child { box-shadow: inset 3px 0 0 #f59e0b; }
.gm-table.phx-table tbody tr.phx-row-missing td:first-child::after { content: ' ⚠'; color: #b45309; font-weight: 600; }
.gm-table.phx-table td.phx-cell-diff .phx-box,
.gm-table.phx-table td.phx-cell-diff .phx-chips {
  border-color: #f59e0b;
  box-shadow: inset 0 0 0 1px #f59e0b;
  background: #fffbeb;
}
.gm-table.phx-table td.phx-cell-diff.phx-text { color: #b45309; font-weight: 600; }
.phx-diff-note { color: #b45309; font-weight: 600; }
/* p-inputtext: 36px tall, #cbd5e1 border, 6px radius, 12px text, 12px side
   padding, ellipsis. Fills its cell, so Phoenix's default column widths give
   Phoenix's box widths (64/49/81/69/89px) AND dragging a column wider
   reveals the truncated value. */
.phx-box {
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 36px;
  line-height: 22px;
  padding: 6px 12px;
  border: 1px solid #cbd5e1;
  border-radius: 6px;
  background: #ffffff;
  color: #334155;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
.phx-box.phx-box-name { color: #282828; font-weight: 400; }
.phx-box.phx-box-num { font-variant-numeric: tabular-nums; }
/* p-autocomplete (multiple, nowrap): ONE 36px bordered field, chips in a
   single row; Phoenix clips the overflow — we let it scroll horizontally
   (scrollbar hidden) so every chip stays reachable. */
.phx-chips {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 4px;
  box-sizing: border-box;
  width: 100%;
  height: 36px;
  padding: 4px 4px;
  border: 1px solid #cbd5e1;
  border-radius: 6px;
  background: #ffffff;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.phx-chips::-webkit-scrollbar { display: none; }
.phx-chips.phx-chips-empty { color: #9b9b9b; padding-left: 12px; }
/* p-chip exactly as Phoenix computes it, minus the remove icon (no action here). */
.phx-chip {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  height: 26px;
  padding: 4px 12px;
  border: 1px solid #c2c2c2;
  border-radius: 4px;
  background: #f1f5f9;
  color: #1e293b;
  font-size: 12px;
  font-weight: 400;
  line-height: 1;
  white-space: nowrap;
}
.phx-link { color: var(--color-primary); font-weight: 600; }
/* Phoenix's "Pickup stops" pill: count + eye, light green, solid green on
   hover. As a button it opens the pickup-stops drawer. */
.phx-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 32px;
  height: 40px;
  padding: 0 14px;
  border-radius: 6px;
  background: #d8f3e6;
  color: #1f7a4f;
  font-weight: 600;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.phx-pill-btn {
  appearance: none;
  border: none;
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.phx-pill-btn:hover,
.phx-pill-btn:focus-visible { background: #00875a; color: #fff; outline: none; }

/* Ops Quote Comparison route map card. */
.opsqc-map-slot { width: 100%; }
.opsqc-map {
  width: 100%;
  height: 440px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--color-bg-page, #f8f8f8);
}
.opsqc-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  padding-top: 4px;
  font-size: 12px;
  color: var(--color-text);
}
.opsqc-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.opsqc-legend-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }

/* Pickup-stops drawer (Phoenix's right-hand side panel). */
.phx-drawer-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(15, 23, 42, 0.35);
}
.phx-drawer {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: min(1040px, 92vw);
  background: var(--color-bg-surface);
  box-shadow: -8px 0 32px rgba(15, 23, 42, 0.18);
  display: flex;
  flex-direction: column;
  animation: phx-drawer-in 0.18s ease-out;
}
@keyframes phx-drawer-in { from { transform: translateX(24px); opacity: 0.6; } to { transform: none; opacity: 1; } }
.phx-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px 32px 16px;
}
.phx-drawer-title {
  font-size: 20px;
  font-weight: 600;
  color: #282828;
}
.phx-drawer-close {
  appearance: none;
  border: none;
  background: transparent;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  font-size: 22px;
  line-height: 1;
  color: var(--color-text-muted);
  cursor: pointer;
}
.phx-drawer-close:hover { background: var(--color-bg-subtle, #f1f5f9); color: var(--color-text); }
.phx-drawer-body { flex: 1; overflow: auto; padding: 0 32px 32px; }
.phx-drawer-table td { vertical-align: top; }
body.phx-drawer-open { overflow: hidden; }

/* Collapsible card: <details class="card phx-collapsible"> whose <summary>
   carries the card's h2. Native details/summary — no JS needed to toggle;
   the page only listens to remember the state. Phoenix puts the chevron on
   the FAR RIGHT of the bar as a round outlined button (down = collapsed,
   up = expanded), so the caret is pushed right with margin-left:auto. */
.phx-collapsible > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
}
.phx-collapsible > summary::-webkit-details-marker { display: none; }
/* PrimeNG p-panel toggle: 40px round text button, no border, slate-500 icon. */
.phx-collapsible > summary .phx-caret-btn {
  margin-left: auto;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--color-text-muted);
}
.phx-collapsible > summary:hover .phx-caret-btn { background: var(--color-bg-subtle, #f1f5f9); }
.phx-collapsible > summary .phx-caret {
  width: 16px;
  height: 16px;
  transition: transform 0.15s ease;
}
.phx-collapsible[open] > summary .phx-caret { transform: rotate(180deg); }
.phx-collapsible > summary:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Side-by-side compare tables (Landed cost / Global Model breakdown at the
   bottom of the Comparison Validator) — a .gm-table with a row-label column
   instead of the usual header row, so the leading <th> per row needs to
   read as a label, not a shouty column heading. Named .side-compare-table,
   NOT .compare-table — that name is already taken by an unrelated, pre-
   existing table style (gosource-metrics.js's row-expansion comparison
   table, defined further down this file); reusing the shorter name here
   silently collided with it (same selector, later-wins cascade), fixed by
   giving this one its own name instead. */
.side-compare-table {
  margin-bottom: 0.75rem;
}
.side-compare-table tbody th {
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: normal;
  color: var(--color-text-muted);
  background: transparent;
  white-space: nowrap;
}
.side-compare-table td {
  font-size: 11.5px;
}

/* ===== App header ===== */
.app-header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border);
  box-shadow: 0 1px 0 0 rgb(15 23 42 / 0.02);
}

/* Three-column header: brand on the left, nav centered, user-menu on the
   right. The 1fr/auto/1fr grid keeps the nav perfectly centered regardless
   of how wide the brand or user-menu blocks become. */
.app-header-grid {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1rem;
  padding: 0.625rem 1.25rem;
}
.app-header-brand-slot { justify-self: start; min-width: 0; }
.app-header-user-slot { justify-self: end; min-width: 0; }

/* Optional secondary toolbar that sits below .app-header on pages with
   page-specific controls (Validator: env toggle + quote/Load/Run/Coord).
   Pages without per-page controls (Comparison, Metrics) just omit this. */
.app-subheader {
  position: sticky;
  top: 56px; /* below .app-header */
  z-index: 29;
  background: var(--color-bg-soft);
  border-bottom: 1px solid var(--color-border);
}

/* ===== Brand logo ===== */
/* Sourced live from the marketing site so the wordmark always tracks the
   canonical brand asset. Trade-off: if marketing rotates or removes the
   URL, all internal tools' headers break — but that's an organisation-
   wide breakage, not a per-tool one. */
.gm-brand {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  user-select: none;
}
.gm-brand img {
  display: block;
  height: 28px;
  width: auto;
}
/* Larger size for the login screen — sits above the page title, so it
   should feel like a banner not a chrome element. */
.gm-brand-lg img {
  height: 40px;
}

/* ===== Primary navigation ===== */
/* Same three links on every page; .active marks the current page. Icons
   are inline SVGs in the HTML — keeps the nav self-contained, no font
   dependency. Pill background uses the brand-primary soft tint to match
   the GoMaterials internal-tool look. */
.gm-nav {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.gm-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 14px;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text-muted);
  border: 1px solid transparent;
  border-radius: 0.5rem;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.gm-nav-link:hover {
  background: var(--color-bg-soft);
  color: var(--color-heading-2);
}
.gm-nav-link.active {
  background: var(--color-primary-soft);
  color: var(--color-primary-hover);
  border-color: rgba(0, 186, 112, 0.18);
}
.gm-nav-link.active:hover {
  /* Don't let hover wash out the active state. */
  background: var(--color-primary-soft);
}
.gm-nav-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/* ===== Primary-nav quote search ===== */
/* The "Quote Lookup" nav entry is a GET form, not a link: type a quote
   number anywhere in the app and land on /quote-lookup.html?quote=... The
   box sits at the same 34px height as .gm-nav-link so the nav row stays
   flush, and is sized to hold a full quote number ("Q0319-17010") without
   the text scrolling under the edge. */
.gm-nav-search {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 4px 0 2px;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  /* NOT var(--color-bg): that token is undefined (the palette has
     --color-bg-page/-surface/-subtle/-soft/-disabled). Four older rules
     still reference it and resolve to transparent; don't copy them. */
  background: var(--color-bg-surface);
  transition: border-color 120ms ease, background-color 120ms ease, box-shadow 120ms ease;
}
.gm-nav-search:hover { border-color: var(--color-text-muted); }
/* No busy styling that removes an affordance: the form gets aria-busy
   during a lookup (public/js/quote-lookup.js) but stays fully usable. */
.gm-nav-search[aria-busy='true'] .gm-nav-search-btn { opacity: 0.55; }
/* Focus lives on the input, but the ring belongs on the whole pill. */
.gm-nav-search:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-soft);
}
/* On /quote-lookup.html itself, the same green the active nav link gets. */
.gm-nav-search.active {
  background: var(--color-primary-soft);
  border-color: rgba(0, 186, 112, 0.28);
}
.gm-nav-search.active .gm-nav-search-input { background: transparent; }
.gm-nav-search-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  padding: 0;
  border: 0;
  border-radius: 0.375rem;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color 120ms ease, background-color 120ms ease;
}
.gm-nav-search-btn:hover { color: var(--color-primary-hover); background: var(--color-bg-soft); }
.gm-nav-search.active .gm-nav-search-btn { color: var(--color-primary-hover); }
.gm-nav-search-input {
  /* Sized against a real quote number ("Q0319-17010", 11 chars) plus room
     for the caret and Chrome's search-cancel button, measured rather than
     guessed: at 12.5ch the standard id overflowed by 2px. Monospace so the
     width is stable whatever the digits are. */
  width: 15ch;
  height: 100%;
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--color-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
}
.gm-nav-search-input::placeholder {
  color: var(--color-text-muted);
  font-weight: 500;
  opacity: 0.7;
  text-transform: none;
}
/* Chrome draws its own clear affordance; keep it, just stop it adding
   width the field wasn't sized for. */
.gm-nav-search-input::-webkit-search-cancel-button { margin-left: 2px; }

/* ===== User menu (avatar button + dropdown) ===== */
.user-menu { position: relative; }
.user-menu-trigger {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background: var(--color-bg-soft);
  color: var(--color-text-supporting);
  border: 1px solid var(--color-border);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.user-menu-trigger:hover {
  background: var(--color-bg-subtle);
  color: var(--color-heading-2);
  border-color: var(--color-border-strong);
}
.user-menu-trigger:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--color-bg-surface), 0 0 0 4px var(--color-primary-ring);
}
.user-menu-trigger svg { width: 18px; height: 18px; }

.user-menu-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 220px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  box-shadow: var(--shadow-md);
  padding: 4px;
  z-index: 40;
}
.user-menu-email {
  padding: 8px 12px;
  font-size: 12px;
  color: var(--color-text-muted);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: 4px;
  word-break: break-all;
}
.user-menu-email:empty { display: none; }
.user-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text);
  background: transparent;
  border: none;
  border-radius: 0.375rem;
  text-align: left;
  cursor: pointer;
}
.user-menu-item:hover { background: var(--color-bg-soft); }
.user-menu-item svg { width: 16px; height: 16px; color: var(--color-text-muted); }

/* ===== Map toolbar ===== */
.map-toolbar {
  display: inline-flex;
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border);
  border-radius: 9999px;
  padding: 3px;
}
.map-toggle {
  padding: 5px 14px;
  border-radius: 9999px;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text-muted);
  background: transparent;
  border: none;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
.map-toggle:hover { color: var(--color-text); }
.map-toggle.active {
  background: var(--color-primary);
  color: #fff;
  box-shadow: 0 1px 2px 0 rgb(15 23 42 / 0.10);
}

/* ===== Environment toggle (GsDev / Prod on the main page header) ===== */
.env-toggle {
  display: inline-flex;
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border);
  border-radius: 9999px;
  padding: 3px;
}
.env-btn {
  padding: 5px 14px;
  border-radius: 9999px;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text-muted);
  background: transparent;
  border: none;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
.env-btn:hover { color: var(--color-text); }
.env-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--color-bg-surface), 0 0 0 4px var(--color-primary-ring);
}
.env-btn.active[data-env="default"] {
  background: var(--color-primary);
  color: #fff;
  box-shadow: 0 1px 2px 0 rgb(15 23 42 / 0.10);
}
.env-btn.active[data-env="prod"] {
  background: var(--color-error);
  color: #fff;
  box-shadow: 0 1px 2px 0 rgb(15 23 42 / 0.10);
}

/* ===== Generic pill-toggle (radio group styled like env-toggle) —
   Transport Calculation's delivery-type switch. ===== */
.pill-toggle {
  display: inline-flex;
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border);
  border-radius: 9999px;
  padding: 3px;
}
.pill-btn {
  padding: 5px 14px;
  border-radius: 9999px;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
.pill-btn:has(input:checked) {
  background: var(--color-primary);
  color: #fff;
  box-shadow: 0 1px 2px 0 rgb(15 23 42 / 0.10);
}
.pill-btn:has(input:focus-visible) {
  outline: none;
  box-shadow: 0 0 0 2px var(--color-bg-surface), 0 0 0 4px var(--color-primary-ring);
}

/* ===== Logo ===== */
.gm-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  font-weight: 700;
  font-size: 15px;
  color: var(--color-heading);
}
.gm-logo-mark {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--color-primary), #00d27f);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: -0.03em;
  box-shadow: 0 1px 2px 0 rgb(15 23 42 / 0.10);
  flex-shrink: 0;
}
.gm-logo-mark.lg {
  width: 48px;
  height: 48px;
  border-radius: 12px;
  font-size: 19px;
}

/* ===== Login (auth) screen ===== */
.auth-page {
  min-height: 100vh;
  place-items: center;
  background:
    radial-gradient(900px 480px at 85% -8%, var(--color-primary-soft), transparent 60%),
    radial-gradient(700px 380px at 5% 110%, rgba(232, 235, 224, 0.55), transparent 60%),
    var(--color-bg-page);
  padding: 1.5rem;
}
.auth-page:not(.hidden) { display: grid; }
.auth-card {
  width: 100%;
  max-width: 420px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.875rem;
  box-shadow: var(--shadow-lg);
  padding: 2rem;
}
.auth-card label > span {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text);
}

/* ===== Modal ===== */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgb(15 23 42 / 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  backdrop-filter: blur(2px);
}
.modal-card {
  background: var(--color-bg-surface);
  border-radius: 0.75rem;
  box-shadow: var(--shadow-lg);
  max-width: 56rem;
  width: 100%;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.875rem 1.25rem;
  border-bottom: 1px solid var(--color-border);
}
.modal-body {
  flex: 1;
  overflow-y: auto;
  padding: 1.25rem;
}

.icon-btn {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  border: none;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
  font-size: 22px;
  line-height: 1;
}
.icon-btn:hover {
  background: var(--color-bg-subtle);
  color: var(--color-text);
}

/* ===== Run-status text ===== */
.run-status { font-size: 12px; color: var(--color-text-muted); display: inline-flex; align-items: center; gap: 0.375rem; }
.run-status-running { color: var(--color-info); }
.run-status-success { color: var(--color-success); }
.run-status-error { color: var(--color-error); }

/* ===== Callout ===== */
.callout {
  border-radius: 0.5rem;
  padding: 0.625rem 0.75rem;
  font-size: 12px;
  border: 1px solid;
}
.callout-info { background: var(--color-info-bg); border-color: rgba(3, 105, 161, 0.25); color: var(--color-info); }
.callout-warning { background: var(--color-warning-bg); border-color: rgba(180, 83, 9, 0.25); color: var(--color-warning); }
.callout-danger { background: var(--color-error-bg); border-color: rgba(220, 38, 38, 0.25); color: var(--color-error); }

/* ===== Code blocks ===== */
.code-block {
  background: #0f172a;
  color: #e2e8f0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  padding: 0.5rem 0.625rem;
  border-radius: 0.375rem;
  overflow-x: auto;
  line-height: 1.4;
}
.code-inline {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border);
  padding: 1px 5px;
  border-radius: 4px;
  color: var(--color-heading-2);
}

/* ===== Empty / loading state ===== */
.empty-state {
  text-align: center;
  padding: 1.5rem 0.5rem;
  color: var(--color-text-muted);
  font-size: 13px;
}
.empty-state .ico {
  width: 36px;
  height: 36px;
  border-radius: 9999px;
  background: var(--color-bg-subtle);
  color: var(--color-text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  margin-bottom: 0.5rem;
}
.loading-state {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--color-text-muted);
  font-size: 13px;
  padding: 0.75rem 0;
}

/* ===== Misc ===== */
hr.divider {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: 0.875rem 0;
}
hr.divider-tight {
  border: none;
  border-top: 1px dashed var(--color-border-strong);
  margin: 0.625rem 0;
}

/* ===== Comparison-validator: pickup-stops route timeline ===== */
/* Used in the Comparison Validator vehicle cards to make pickup stops read
   visually as a sequenced path (vs. products which are an unordered list). */
.pickup-route {
  list-style: none;
  margin: 0;
  padding: 0 0 0 0.875rem;
  position: relative;
}
.pickup-route::before {
  content: "";
  position: absolute;
  left: 0.32rem;
  top: 0.55rem;
  bottom: 0.55rem;
  border-left: 2px dashed rgba(0, 186, 112, 0.45);
}
.pickup-route > li {
  position: relative;
  padding: 0.25rem 0 0.25rem 0.625rem;
  display: block;
  font-size: 12px;
  color: var(--color-text);
}
.pickup-route > li::before {
  content: "";
  position: absolute;
  left: -0.625rem;
  top: 0.55rem;
  width: 9px;
  height: 9px;
  border-radius: 9999px;
  background: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-bg-soft);
}
.pickup-route .stop-num {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--color-primary-hover);
  background: var(--color-primary-soft);
  padding: 1px 6px;
  border-radius: 4px;
  letter-spacing: 0.02em;
  margin-right: 0.4rem;
  vertical-align: 1px;
}
.pickup-route .stop-name {
  color: var(--color-heading-2);
  font-weight: 500;
}

/* Per-stop products (collapsed by default; click summary to expand) */
.pickup-route .stop-products {
  margin-top: 0.375rem;
  margin-left: 0.125rem;
}
.pickup-route .stop-products > summary {
  font-size: 11px;
  color: var(--color-text-muted);
  cursor: pointer;
  user-select: none;
  padding: 2px 0;
  list-style: none;
}
.pickup-route .stop-products > summary::-webkit-details-marker { display: none; }
.pickup-route .stop-products > summary::before {
  content: "▸";
  display: inline-block;
  margin-right: 0.25rem;
  width: 0.7rem;
}
.pickup-route .stop-products[open] > summary::before { content: "▾"; }

/* Collapsible wrapper around the input JSON's line-item table — visual
   treatment matches .entity-card (the per-vehicle cards in columns 1–3):
   bordered card, hover/open border darkens, soft shadow when open. */
.input-products-collapse {
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: var(--color-bg-surface);
  overflow: hidden;
  transition: border-color 120ms ease, box-shadow 120ms ease;
  margin-bottom: 0.5rem;
}
.input-products-collapse[open] {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-sm);
}
.input-products-collapse > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-heading-2);
  padding: 0.5rem 0.875rem;
}
.input-products-collapse > summary:hover { background: var(--color-bg-soft); }
.input-products-collapse > summary::-webkit-details-marker { display: none; }
/* ::after chevron rule lives in the shared block above — alongside .entity-card. */
/* Inner table inherits the wrapper's border — strip the gm-table's own
   outer border to avoid double-bordering, keep only the row separators. */
.input-products-collapse > .gm-table {
  border: none;
  border-radius: 0;
  border-top: 1px solid var(--color-border);
  margin-top: 0;
}

/* Two-column layout inside the input JSON card: table on the left,
   sellers map on the right. Stacks vertically below ~1024px. */
.input-json-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: minmax(0, 1fr) 360px;
  align-items: start;
}
@media (max-width: 1024px) {
  .input-json-grid { grid-template-columns: 1fr; }
}
.input-json-map { height: 320px; }

/* Compact info-bubble for the seller-dots map. The default `.map-info-bubble`
   is sized for the main dashboard's wide map (364px); the input-JSON map is
   only 360px column-wide so the default tooltip extends off-screen. */
.seller-tooltip {
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 11px;
  line-height: 1.35;
  width: 200px;
  max-width: 200px;
  padding: 4px 6px;
  word-break: break-word;
}
.seller-tooltip .header {
  font-weight: 600;
  font-size: 12px;
  margin: 0 0 3px;
  color: var(--color-heading);
}
.seller-tooltip .content {
  margin: 0;
  color: var(--color-heading-2);
  max-height: 160px;
  overflow-y: auto;
}
.seller-tooltip .content > div {
  padding: 1px 0;
}
.seller-tooltip .content:empty { display: none; }
.pickup-route .stop-products .product-list {
  margin-top: 0.25rem;
  padding-left: 0.875rem;
}

/* ===== Comparison-validator: per-column map widget ===== */
.map-card {
  margin-top: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  overflow: hidden;
  background: var(--color-bg-surface);
}
.map-card-header {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  padding: 6px 10px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg-soft);
}
.map-card-body {
  width: 100%;
  height: 280px;
  position: relative;
}
.map-card-body .map-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  text-align: center;
  padding: 0 1rem;
  font-size: 12px;
  color: var(--color-text-muted);
}

/* ===== Comparison-validator: product list with row separators ===== */
.product-list { list-style: none; margin: 0; padding: 0; }
.product-list > li {
  padding: 0.375rem 0;
  border-bottom: 1px solid rgba(226, 232, 240, 0.7);
}
.product-list > li:last-child { border-bottom: none; }
.product-list > li > div:first-child {
  font-weight: 500;
  color: var(--color-heading-2);
  font-size: 12.5px;
}

/* ===== Comparison-validator: transport-cost source badge =====
   Where a vehicle's transport cost came from: Excel rate sheet (info blue),
   ML model (orange, matching the metrics variance hue), or a human (neutral
   slate). Tones from the gm palette. */
.cost-src-badge {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: 9999px;
  font-size: 10.5px;
  font-weight: 600;
  vertical-align: 1px;
}
.cost-src-excel  { background: #e0f2fe; color: #0369a1; }
.cost-src-model  { background: #fff7ed; color: #c2410c; }
.cost-src-manual { background: #e2e8f0; color: #475569; }

/* Comparison Column 1: which MySQL env answered (prod-first, GsDev fallback) */
.db-env-badge {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: 9999px;
  font-size: 10.5px;
  font-weight: 600;
  font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif);
  vertical-align: 1px;
  margin-left: 0.25rem;
}
.db-env-prod  { background: #fee2e2; color: #b91c1c; }
.db-env-gsdev { background: #dcfce7; color: #15803d; }

/* ===== Dashboard 3-col layout ===== */
.dashboard-grid {
  display: grid;
  gap: 1rem;
  padding: 1rem;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr) minmax(0, 1fr);
  flex: 1;
  min-height: 0;
}
@media (max-width: 1024px) {
  .dashboard-grid {
    grid-template-columns: 1fr;
    grid-auto-rows: minmax(360px, auto);
  }
}

/* ===== GoSource Metrics page ===== */
/* Status palette is keyed to MSS_SCORE bands. Tints reuse the global
   --color-success/--color-warning/--color-error tokens so the page reads
   as part of the same design system. */
:root {
  --mss-pass-bg: #ecfdf5;
  --mss-pass-border: #6ee7b7;
  --mss-pass-fg: #047857;
  --mss-warn-bg: #fffbeb;
  --mss-warn-border: #fcd34d;
  --mss-warn-fg: #b45309;
  --mss-fail-bg: #fef2f2;
  --mss-fail-border: #fca5a5;
  --mss-fail-fg: #b91c1c;
  --mss-unknown-bg: #f1f5f9;
  --mss-unknown-border: #cbd5e1;
  --mss-unknown-fg: #475569;
}

.mss-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: var(--mss-unknown-bg);
  border: 1px solid var(--mss-unknown-border);
}
.mss-swatch.mss-pass { background: var(--mss-pass-bg); border-color: var(--mss-pass-border); }
.mss-swatch.mss-warn { background: var(--mss-warn-bg); border-color: var(--mss-warn-border); }
.mss-swatch.mss-fail { background: var(--mss-fail-bg); border-color: var(--mss-fail-border); }

.mss-summary {
  display: flex;
  align-items: stretch;
  gap: 1rem;
  flex-wrap: wrap;
}
.mss-summary:empty { display: none; }

.mss-summary-tile {
  flex: 0 0 220px;
  border-radius: 0.5rem;
  border: 1px solid var(--mss-unknown-border);
  background: var(--mss-unknown-bg);
  color: var(--mss-unknown-fg);
  padding: 0.875rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
.mss-summary-tile.mss-pass { border-color: var(--mss-pass-border); background: var(--mss-pass-bg); color: var(--mss-pass-fg); }
.mss-summary-tile.mss-warn { border-color: var(--mss-warn-border); background: var(--mss-warn-bg); color: var(--mss-warn-fg); }
.mss-summary-tile.mss-fail { border-color: var(--mss-fail-border); background: var(--mss-fail-bg); color: var(--mss-fail-fg); }
.mss-summary-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  opacity: 0.85;
}
.mss-summary-value {
  font-size: 30px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.mss-summary-sub {
  font-size: 12px;
  opacity: 0.8;
}

.mss-summary-counts {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
  flex-wrap: wrap;
}
.mss-count {
  flex: 1 1 80px;
  border-radius: 0.5rem;
  border: 1px solid var(--mss-unknown-border);
  background: var(--mss-unknown-bg);
  color: var(--mss-unknown-fg);
  padding: 0.625rem 0.75rem;
  font-size: 22px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  display: flex;
  flex-direction: column;
  line-height: 1.1;
}
.mss-count > span {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.8;
}
.mss-count.mss-pass { border-color: var(--mss-pass-border); background: var(--mss-pass-bg); color: var(--mss-pass-fg); }
.mss-count.mss-warn { border-color: var(--mss-warn-border); background: var(--mss-warn-bg); color: var(--mss-warn-fg); }
.mss-count.mss-fail { border-color: var(--mss-fail-border); background: var(--mss-fail-bg); color: var(--mss-fail-fg); }

/* Horizontal scroller — wraps the table so wide column sets don't blow
   out the card width. The table itself keeps all cells on a single line
   (nowrap below) so column text fits as the user requested. */
.mss-table-scroll {
  width: 100%;
  overflow-x: auto;
  border-radius: 0.375rem;
  border: 1px solid var(--color-border);
}
.mss-table {
  border: none;
  border-radius: 0;
  width: auto;
  min-width: 100%;
}
.mss-table td {
  white-space: nowrap;
  padding: 7px 10px;
}
/* Headers are allowed to wrap onto a 2nd line — keeps overall table width
   under control with short, vertical labels like "VEH COUNT". Replace
   underscores with spaces in displayColumnName() so the wrap point is on
   actual whitespace rather than relying on word-break. */
.mss-table th {
  white-space: normal;
  word-spacing: 0;
  line-height: 1.15;
  padding: 6px 10px;
  vertical-align: bottom;
  max-width: 96px;
}
.mss-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
}
.mss-table th.sortable,
.outlier-table th.sortable {
  cursor: pointer;
  user-select: none;
}
.mss-table th.sortable:hover,
.outlier-table th.sortable:hover {
  background: var(--color-bg-hover, rgba(0, 0, 0, 0.04));
}
.mss-table th.is-sorted,
.outlier-table th.is-sorted {
  color: var(--color-accent, #1f6feb);
}
/* Row tints keyed to MSS_SCORE band — same palette as the summary tiles so
   the page reads consistently. */
.mss-table tr.mss-row-pass td { background: var(--mss-pass-bg); }
.mss-table tr.mss-row-warn td { background: var(--mss-warn-bg); }
.mss-table tr.mss-row-fail td { background: var(--mss-fail-bg); }
.mss-table tr.mss-row-unknown td { background: var(--mss-unknown-bg); }

.mss-cell-score { text-align: center; }
.mss-score-badge {
  display: inline-block;
  min-width: 48px;
  padding: 2px 10px;
  border-radius: 999px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--mss-unknown-bg);
  color: var(--mss-unknown-fg);
  border: 1px solid var(--mss-unknown-border);
}
.mss-score-badge.mss-pass { background: var(--mss-pass-bg); color: var(--mss-pass-fg); border-color: var(--mss-pass-border); }
.mss-score-badge.mss-warn { background: var(--mss-warn-bg); color: var(--mss-warn-fg); border-color: var(--mss-warn-border); }
.mss-score-badge.mss-fail { background: var(--mss-fail-bg); color: var(--mss-fail-fg); border-color: var(--mss-fail-border); }
a.mss-score-link {
  text-decoration: none;
  cursor: pointer;
  transition: filter 0.12s ease, transform 0.12s ease;
}
a.mss-score-link:hover { filter: brightness(1.05); transform: translateY(-1px); }
a.mss-score-link:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

#mss-content.is-refreshing { opacity: 0.6; transition: opacity 0.15s ease; }

/* ---- Expandable rows ---- */
.mss-row-expandable { cursor: pointer; }
.mss-row-expandable:hover td { filter: brightness(0.96); }
.mss-row-expandable.is-expanded td:first-child {
  border-left: 3px solid var(--color-primary);
  padding-left: 7px; /* compensate for the 3px border so text doesn't jump */
}

/* The detail row sits beneath an expanded data row. The single td uses
   colspan=N so we get a full-width canvas for the two-panel grid. */
.mss-row-detail > td {
  background: var(--color-bg-soft);
  border-top: 1px solid var(--color-border) !important;
  padding: 0.875rem 1rem !important;
  white-space: normal;
}
.mss-expand-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
@media (max-width: 1100px) {
  .mss-expand-grid { grid-template-columns: 1fr; }
}
.mss-expand-panel {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  overflow: hidden;
}
.mss-expand-header {
  font-weight: 700;
  font-size: 13px;
  color: var(--color-heading-2);
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg-soft);
}
/* HERE Maps canvas inside an expansion panel. Fixed height so the map has
   a non-zero canvas to render into. */
.mss-expand-map {
  height: 260px;
  width: 100%;
  background: var(--color-bg-soft);
}

/* Comparison table sits below the two-map grid, full width. */
.mss-expand-compare {
  margin-top: 1rem;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  overflow: hidden;
}
.compare-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.compare-table th,
.compare-table td {
  padding: 0.5rem 0.75rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--color-border);
}
.compare-table thead th {
  background: var(--color-bg-soft);
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}
.compare-table tbody th {
  font-weight: 600;
  background: var(--color-bg-soft);
  width: 10rem;
  min-width: 8rem;
  line-height: 1.3;
}
.compare-table tbody tr:last-child th,
.compare-table tbody tr:last-child td {
  border-bottom: none;
}
.compare-table .compare-delta {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--color-heading);
}
.compare-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.compare-list > li {
  padding: 1px 0;
  line-height: 1.35;
}
.compare-list > li + li { border-top: 1px dashed var(--color-border); padding-top: 3px; margin-top: 3px; }
.compare-count {
  margin-top: 4px;
  font-size: 10.5px;
  letter-spacing: 0.02em;
}
.compare-seq-label {
  display: inline-block;
  min-width: 1.5rem;
  font-weight: 700;
  color: var(--color-heading-2);
}
/* Pickup-sequence rows wrap at arrow boundaries: each "name →" chunk is
   nowrap, the row is flex-wrap, so long sequences break onto new lines
   instead of forcing the column wide. */
.compare-seq-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem;
}
.compare-stop {
  white-space: nowrap;
}
.compare-truck-list > li + li {
  border-top: 1px solid var(--color-border);
  margin-top: 6px;
  padding-top: 6px;
}
.compare-truck-head {
  margin-bottom: 2px;
}
.compare-truck-items {
  list-style: none;
  margin: 0;
  padding: 0 0 0 0.75rem;
}
.compare-truck-items > li {
  padding: 1px 0;
  font-size: 11.5px;
  line-height: 1.3;
  /* Re-enable wrapping: the expansion row sits inside .mss-table whose td
     sets nowrap, and "name | size | spec" lines are long enough to clip. */
  white-space: normal;
}

/* Dropped-items section under the expansion compare table. Hidden entirely
   when the input-vs-solution diff is empty (the JS never appends it). */
.compare-dropped {
  margin-top: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  overflow: hidden;
}
.compare-dropped-head {
  padding: 0.5rem 0.75rem;
  background: var(--color-warning-bg);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-heading-2);
}
.compare-dropped .loading-state { padding: 0.5rem 0.75rem; }
.compare-dropped-table td { white-space: normal; }
.compare-dropped-none {
  color: #b91c1c;
  font-weight: 600;
}
.compare-dropped-reason {
  margin-top: 2px;
  font-size: 10.5px;
  color: var(--color-text-muted);
}

/* ===== Metrics dashboard: filter bar, scorecards, widgets ============ */

.filter-bar { padding: 0.625rem 0.875rem; }
.filter-bar-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex-wrap: wrap;
}
.filter-bar-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
.filter-input {
  font-size: 13px;
  padding: 0.3125rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 0.375rem;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: inherit;
}
.filter-input:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: -1px;
}
.filter-bar-custom { display: flex; align-items: center; gap: 0.375rem; }
.filter-bar-display { font-size: 12px; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.filter-bar-spacer { flex: 1 1 auto; }
/* Subtle vertical separator between filter groups. */
.filter-bar-divider {
  display: inline-block;
  width: 1px;
  height: 18px;
  background: var(--color-border);
  margin: 0 0.25rem;
}
.filter-bar-checkboxes { display: inline-flex; align-items: center; gap: 0.625rem; }
.filter-checkbox {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 12.5px;
  cursor: pointer;
  user-select: none;
  color: var(--color-text);
}
.filter-checkbox input { margin: 0; cursor: pointer; }

/* Empty-state variant shown inside the row-expansion solution map when
   /api/gosource/:n/result returns {available: false}. Overrides the
   parent .mss-expand-map's map-tile styling so the tiles don't peek
   through — otherwise a blank grey HERE map background bleeds around
   the message. */
.solution-empty-state {
  background: var(--color-bg);
  border: 1px dashed var(--color-border);
  border-radius: 0.375rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 1rem;
  text-align: center;
}

/* Mode toggle — segmented control at top-left of the filter bar that
   switches between GoSource and GoTransport metrics. Active button gets
   the primary bg; hover on inactive nudges it. Sized to match the
   filter-input height so the bar line-height stays clean. */
.mode-toggle {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: 0.375rem;
  overflow: hidden;
  background: var(--color-bg);
}
.mode-btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  padding: 0.3125rem 0.75rem;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.mode-btn + .mode-btn { border-left: 1px solid var(--color-border); }
.mode-btn:hover:not(.is-active) { background: var(--color-bg-subtle, #f3f4f6); color: var(--color-text); }
.mode-btn.is-active {
  background: var(--color-primary);
  color: #fff;
}
.mode-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -1px;
}

/* Scorecards row — 4 KPI tiles, responsive grid. */
.scorecards-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.625rem;
}
.kpi-value-lg {
  font-size: 28px;
  font-weight: 700;
  color: var(--color-heading);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.kpi-sub {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  min-height: 14px; /* reserve space so tiles align without sub-text shift */
}
.kpi-desc {
  font-size: 11px;
  line-height: 1.3;
  margin-top: 2px;
}
/* Colored scorecard tones — pale fills + matching left accent so the four
   headline KPIs are visually distinct at a glance. */
.kpi-tone-blue   { background: #eff6ff; border-color: #bfdbfe; }
.kpi-tone-green  { background: #ecfdf5; border-color: #a7f3d0; }
.kpi-tone-orange { background: #fff7ed; border-color: #fed7aa; }
.kpi-tone-red    { background: #fef2f2; border-color: #fecaca; }
.kpi-tone-gray   { background: #f5f5f5; border-color: #d4d4d4; }
.kpi-tone-blue   .kpi-value-lg { color: #1e40af; }
.kpi-tone-green  .kpi-value-lg { color: #047857; }
.kpi-tone-orange .kpi-value-lg { color: #c2410c; }
.kpi-tone-red    .kpi-value-lg { color: #b91c1c; }
.kpi-tone-gray   .kpi-value-lg { color: #525252; }

/* MSS Category counts row — smaller tiles, one per outcome bucket. */
.mss-category-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0.5rem;
}
.kpi-card-sm {
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border);
  border-radius: 0.375rem;
  padding: 0.4rem 0.625rem;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}
.kpi-label-sm {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}
.kpi-value-md {
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-heading);
  line-height: 1;
}
/* MSS category tiles are intentionally neutral grey — the headline
   scorecards above already carry the visual emphasis, and operators
   read the count, not the color, off these small tiles. */

/* Widget shell — each report section gets a header (title + tools row)
   and a body. The .widget class layers on top of the .card primitive. */
.widget { padding: 0; overflow: visible; }
.widget-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.75rem 1rem 0.5rem;
  border-bottom: 1px solid var(--color-border);
  flex-wrap: wrap;
}
.widget-title { margin: 0; }
.widget-tools {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.widget-body { padding: 0.75rem 1rem; }
.widget-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  border-top: 1px solid var(--color-border);
}

/* Per-table column-visibility toggle: gear icon → dropdown of checkboxes.
   Anchored to the .col-toggle wrapper so multiple instances on one page
   don't interfere. */
.col-toggle { position: relative; }
.col-toggle-btn {
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: 0.375rem;
  width: 28px;
  height: 28px;
  font-size: 14px;
  cursor: pointer;
  color: var(--color-text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.col-toggle-btn:hover { background: var(--color-bg-soft); color: var(--color-text); }
.col-toggle-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 50;
  min-width: 220px;
  max-height: 360px;
  overflow-y: auto;
  /* Explicit solid white — earlier `var(--color-bg)` could resolve to a
     translucent value depending on theme, letting the row text below
     bleed through. Solid plus a stronger shadow makes the dropdown
     visually pop off the page. */
  background: #ffffff;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
  padding: 0.375rem 0;
}
.col-toggle-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3125rem 0.75rem;
  font-size: 12.5px;
  cursor: pointer;
  user-select: none;
}
.col-toggle-item:hover { background: var(--color-bg-soft); }
.col-toggle-item input { margin: 0; cursor: pointer; }

/* Duration Search (wrench) — reuses .col-toggle-btn / .col-toggle-menu
   geometry; adds a form-style panel and an "active" indicator dot. */
.duration-search { position: relative; }
.duration-search .ds-btn { position: relative; }
.duration-search .ds-btn.is-active { color: var(--color-accent, #2563eb); border-color: var(--color-accent, #2563eb); }
.duration-search .ds-btn.is-active::after {
  content: '';
  position: absolute;
  top: -2px; right: -2px;
  width: 7px; height: 7px;
  border-radius: 9999px;
  background: var(--color-accent, #2563eb);
  border: 1px solid #ffffff;
}
.ds-menu { min-width: 248px; padding: 0.625rem 0.75rem; }
.ds-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.5rem; }
.ds-title { font-size: 12px; font-weight: 600; color: var(--color-text); }
.ds-preset {
  font-size: 11px;
  color: var(--color-accent, #2563eb);
  background: transparent;
  border: 1px solid var(--color-accent, #2563eb);
  border-radius: 0.375rem;
  padding: 0.125rem 0.5rem;
  cursor: pointer;
  white-space: nowrap;
}
.ds-preset:hover { background: var(--color-bg-soft); }
.ds-row { display: flex; align-items: center; gap: 0.5rem; font-size: 12.5px; padding: 0.25rem 0; cursor: pointer; user-select: none; }
.ds-row input[type="checkbox"] { margin: 0; cursor: pointer; }
.ds-row-col { flex-direction: column; align-items: stretch; gap: 0.25rem; cursor: default; }
.ds-label { font-size: 11.5px; color: var(--color-text-muted); }
.ds-select { font-size: 12.5px; padding: 0.25rem 0.375rem; border: 1px solid var(--color-border); border-radius: 0.375rem; background: #ffffff; }
.ds-range { display: flex; align-items: center; gap: 0.375rem; }
.ds-num { width: 100%; font-size: 12.5px; padding: 0.25rem 0.375rem; border: 1px solid var(--color-border); border-radius: 0.375rem; font-variant-numeric: tabular-nums; }
.ds-dash { color: var(--color-text-muted); }
.ds-foot { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.625rem; padding-top: 0.5rem; border-top: 1px solid var(--color-border); }
.ds-count { font-size: 11.5px; font-variant-numeric: tabular-nums; }

/* Pagination footer */
.pagination-status { font-size: 12px; font-variant-numeric: tabular-nums; }
.pagination-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.pagination-page-info {
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  min-width: 100px;
  text-align: center;
}

/* Pivot table for widget #2 */
.pivot-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.pivot-table th, .pivot-table td {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.pivot-table th { background: var(--color-bg-soft); font-weight: 600; text-align: center; }
.pivot-table th:first-child, .pivot-table td:first-child {
  text-align: left;
  font-weight: 600;
}
.pivot-table tr.totals-row { background: var(--color-bg-soft); font-weight: 600; }
.pivot-cell-sub { display: block; font-size: 10.5px; color: var(--color-text-muted); font-weight: 400; }

/* Timeline viz (widget #3): vertical layout — one row per interval, plus
   a separated Full Estimation Time row at the bottom as the baseline
   (100% bar). Each row's bar scales relative to Full Estimation so the
   reader can eyeball how each interval contributes to the total. */
.timeline-viz {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}
.timeline-row {
  display: grid;
  grid-template-columns: 180px 1fr 130px;
  align-items: center;
  gap: 0.75rem;
  font-size: 12.5px;
}
.timeline-row-label {
  color: var(--color-heading);
  font-weight: 600;
}
.timeline-row-label .timeline-row-sub {
  display: block;
  font-size: 10.5px;
  color: var(--color-text-muted);
  font-weight: 400;
  margin-top: 2px;
}
.timeline-row-track {
  position: relative;
  height: 22px;
  background: var(--color-bg-soft);
  border-radius: 4px;
  overflow: hidden;
}
.timeline-row-bar {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  border-radius: 4px;
}
.timeline-row-bar.is-trigger { background: #fca5a5; }
.timeline-row-bar.is-load    { background: #93c5fd; }
.timeline-row-bar.is-review  { background: #86efac; }
.timeline-row-bar.is-full    { background: #94a3b8; }
/* Sub-row bars are patterned diagonal stripes so they read as visually
   distinct from the solid #93c5fd parent "Time to Load" bar. Data prep +
   Runtime share a blue palette; Idle stays neutral gray. */
.timeline-row-bar.is-data-prep {
  background:
    repeating-linear-gradient(135deg,
      #93c5fd 0 5px,
      #bfdbfe 5px 10px);
}
.timeline-row-bar.is-runtime {
  background:
    repeating-linear-gradient(135deg,
      #1d4ed8 0 5px,
      #60a5fa 5px 10px);
}
.timeline-row-bar.is-idle { background: #cbd5e1; }

/* Sub-rows: indented label, thinner track, faded color so the parent
   Time to Load row still reads as the dominant element. */
.timeline-row.is-subrow {
  font-size: 11.5px;
}
.timeline-row.is-subrow .timeline-row-label {
  padding-left: 16px;
  font-weight: 500;
  color: var(--color-text);
}
.timeline-row.is-subrow .timeline-row-track {
  height: 14px;
}
.timeline-row.is-subrow .timeline-row-value {
  color: var(--color-text-muted);
}
.timeline-row-value {
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--color-text);
}
.timeline-row-value .timeline-row-n {
  display: block;
  font-size: 10.5px;
  color: var(--color-text-muted);
}
.timeline-divider {
  border: 0;
  border-top: 1px dashed var(--color-border);
  margin: 0.25rem 0;
}

/* ===== Pivot Count → SQL popover =================================== */
.pivot-count-link {
  cursor: pointer;
  text-decoration: underline dotted var(--color-text-muted);
  text-underline-offset: 2px;
}
.pivot-count-link:hover {
  color: var(--color-accent, #1f6feb);
  text-decoration-color: var(--color-accent, #1f6feb);
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.modal-backdrop.hidden { display: none; }
.modal {
  background: var(--color-bg-surface, #fff);
  border-radius: 8px;
  min-width: 480px;
  max-width: 760px;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}
.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 14px;
  border-bottom: 1px solid var(--color-border);
}
.modal-title { font-size: 13px; font-weight: 700; margin: 0; }
.modal-close {
  background: none;
  border: none;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: var(--color-text-muted);
  padding: 0 4px;
}
.modal-body { padding: 14px; overflow-y: auto; }
.modal-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-top: 1px solid var(--color-border);
}
.flex-1 { flex: 1; }
.sql-popover-text {
  margin: 0;
  padding: 10px 12px;
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 11.5px;
  line-height: 1.45;
  white-space: pre;
  overflow-x: auto;
  user-select: text;
}

/* ===== Excluded-outliers table ===================================== */
.outlier-table-scroll {
  overflow-x: auto;
}
.outlier-table th, .outlier-table td {
  white-space: nowrap;
  font-size: 12px;
}
.outlier-table thead th {
  position: sticky;
  top: 0;
  background: var(--color-bg-soft);
  z-index: 1;
}

/* ===== MSS-over-time line chart ==================================== */

.info-tip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-left: 0.35rem;
  border: 1px solid var(--color-text-muted);
  border-radius: 50%;
  font-size: 9.5px;
  font-weight: 700;
  font-family: ui-sans-serif, system-ui, sans-serif;
  color: var(--color-text-muted);
  background: transparent;
  cursor: help;
  vertical-align: 1px;
  user-select: none;
  line-height: 1;
}
.info-tip:hover, .info-tip:focus {
  background: var(--color-accent, #1f6feb);
  border-color: var(--color-accent, #1f6feb);
  color: #fff;
  outline: none;
}

.monthly-mss-chart { width: 100%; }
.monthly-mss-svg {
  width: 100%;
  height: 220px;
  display: block;
  font-family: inherit;
}
.monthly-mss-gridline { stroke: var(--color-border); stroke-width: 1; stroke-dasharray: 2 3; }
.monthly-mss-axis     { stroke: var(--color-border); stroke-width: 1; }
.monthly-mss-line     { stroke: #1e40af; stroke-width: 2; fill: none; }
.monthly-mss-dot      { fill: #1e40af; }
.monthly-mss-axis-label { font-size: 10.5px; fill: var(--color-text-muted); }
.monthly-mss-value      { font-size: 10.5px; fill: var(--color-heading); font-variant-numeric: tabular-nums; }

/* Trends row: score-over-time (left) + variance trend (right) share one
   row 50/50; stacks on narrow screens. The variance line gets its own
   hue (orange, from the kpi-tone palette) so the two side-by-side charts
   never read as the same measure. */
.trend-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  align-items: stretch;
}
@media (max-width: 1100px) {
  .trend-split { grid-template-columns: 1fr; }
}
/* Expanded trend chart: row goes single-column, expanded card moves to
   the top at double height (the viewBox height tracks this in JS). */
.trend-split.has-expanded { grid-template-columns: 1fr; }
.trend-split .is-expanded { order: -1; }
.trend-split .is-expanded .monthly-mss-svg { height: 440px; }
.trend-expand-btn { font-size: 15px; line-height: 1; }
.trend-expand-btn[aria-pressed="true"] { color: var(--color-primary); border-color: var(--color-primary); }
/* GS release markers on the score-over-time chart. The label gets a
   background-coloured halo so it stays legible over gridlines/the line. */
.trend-marker-line  { stroke: #64748b; stroke-width: 1; stroke-dasharray: 4 3; }
.trend-marker-label {
  font-size: 10px; font-weight: 600; fill: #475569;
  paint-order: stroke; stroke: var(--color-bg, #fff); stroke-width: 3px; stroke-linejoin: round;
}
.trend-marker-hit   { stroke: transparent; stroke-width: 9; }

/* Each series sets --series-color on its <g>; orange is the fallback. */
.variance-line { stroke: var(--series-color, #c2410c); stroke-width: 2; fill: none; }
.variance-dot  { fill: var(--series-color, #c2410c); }

/* Variance metric multi-select: a select-looking button over the shared
   .col-toggle-menu checklist. */
.variance-picker-btn {
  min-width: 170px;
  text-align: left;
  cursor: pointer;
  padding-right: 1.5rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%2364748b'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.5rem center;
}
.variance-picker-all { border-bottom: 1px solid var(--color-border); margin-bottom: 0.25rem; padding-bottom: 0.4375rem; font-weight: 600; }
.variance-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; flex: none; }
.variance-legend { display: flex; flex-wrap: wrap; gap: 0.25rem 0.875rem; margin-bottom: 0.375rem; font-size: 11.5px; color: var(--color-text-muted); }
.variance-legend-item { display: inline-flex; align-items: center; gap: 0.375rem; }

/* ===== Primary-nav dropdowns ("Comparison", "Adhoc") ===== */
/* CSS-only open/close: hover for mouse, :focus-within for keyboard — no JS
   so every page gets the dropdown by markup alone. The trigger is a button
   styled as a .gm-nav-link; it reads .active when the current page lives
   inside the dropdown (e.g. /gt-analysis.html). */
.gm-nav-dropdown { position: relative; }
.gm-nav-drop-trigger { cursor: pointer; background: transparent; }
.gm-nav-caret {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  margin-left: -2px;
  transition: transform 120ms ease;
}
.gm-nav-dropdown:hover .gm-nav-caret,
.gm-nav-dropdown:focus-within .gm-nav-caret { transform: rotate(180deg); }
.gm-nav-drop-menu {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 180px;
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  box-shadow: var(--shadow-md);
  padding: 4px;
  z-index: 40;
}
.gm-nav-dropdown:hover .gm-nav-drop-menu,
.gm-nav-dropdown:focus-within .gm-nav-drop-menu { display: block; }
/* Invisible hover bridge over the 4px visual gap between the trigger and
   the menu — without it the pointer leaves the dropdown's hover area while
   crossing the gap and the menu closes before it can be clicked. */
.gm-nav-drop-menu::before {
  content: '';
  position: absolute;
  top: -8px;
  left: 0;
  right: 0;
  height: 8px;
}
.gm-nav-drop-item {
  display: block;
  padding: 8px 12px;
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text);
  border-radius: 0.375rem;
  text-decoration: none;
  white-space: nowrap;
}
.gm-nav-drop-item:hover { background: var(--color-bg-soft); }
.gm-nav-drop-item.active {
  background: var(--color-primary-soft);
  color: var(--color-primary-hover);
}

/* ===== GT Analysis (adhoc) table ===== */
/* Sheet-shaped layout: a two-band header (loaded solution | published),
   bold per-quote totals rows, indented per-truck rows. Band tints echo the
   source spreadsheet so it reads as the same artifact. */
.gt-analysis-table .gt-band {
  text-align: left;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-heading-2);
}
.gt-analysis-table .gt-band-loaded { background: var(--color-primary-soft); }
.gt-analysis-table .gt-band-published { background: var(--color-warning-bg); }
.gt-analysis-table .gt-quote-row td {
  border-top: 2px solid var(--color-border-strong);
  background: var(--color-bg-soft);
}
.gt-analysis-table .gt-quote-cell {
  font-weight: 700;
  color: var(--color-heading-2);
  white-space: nowrap;
}
.gt-analysis-table .gt-total { font-weight: 700; }
.gt-analysis-table .gt-truck-row td { font-size: 12.5px; }
.gt-run-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  border-radius: 9999px;
  background: var(--color-primary-soft);
  color: var(--color-primary-hover);
  vertical-align: 1px;
}
.gt-deleted-tag {
  display: inline-block;
  margin-left: 4px;
  padding: 0 5px;
  font-size: 10px;
  font-weight: 600;
  border-radius: 9999px;
  background: var(--color-error-bg);
  color: #b91c1c;
}
.gt-error { color: #b91c1c; font-size: 12.5px; }
.gt-items-same {
  display: inline-block;
  padding: 0 7px;
  font-size: 11px;
  font-weight: 600;
  border-radius: 9999px;
  background: var(--color-success-bg);
  color: #15803d;
}
.gt-items-changed {
  display: inline-block;
  padding: 0 7px;
  font-size: 11px;
  font-weight: 600;
  border-radius: 9999px;
  background: var(--color-warning-bg);
  color: #b45309;
  cursor: help;
}

/* ===== Truck Splitting (adhoc) — merged-group layout ===== */
/* Groups read top-aligned: the merged truck's cells span all of its
   absorbed solver-truck rows via rowspan, so cells must anchor to the top
   or the merge relationship gets visually ambiguous. */
.ts-table td { vertical-align: top; }
.ts-table .ts-group-start td { border-top: 1px solid var(--color-border-strong); }
.ts-table .ts-merged-cell { background: rgb(254 249 233 / 0.55); }
.ts-items {
  margin: 0;
  padding: 0;
  list-style: none;
  max-width: 340px;
}
.ts-items li {
  font-size: 11px;
  color: var(--color-text-supporting);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ts-stops {
  margin: 0;
  padding-left: 16px;
  max-width: 240px;
}
.ts-stops li {
  font-size: 11px;
  color: var(--color-text-supporting);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ts-merged-from {
  font-size: 11px;
  font-weight: 700;
  color: #b45309;
  white-space: nowrap;
  margin-top: 2px;
}
/* Stronger break between merge sets so multi-merge quotes read as
   distinct "these → that" blocks. */
.ts-table .ts-group-start td {
  border-top: 3px double var(--color-border-strong);
  padding-top: 10px;
}
/* Scenario chips + split-item rows on the Truck Splitting report. */
.ts-chip {
  display: inline-block;
  padding: 1px 8px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  border-radius: 9999px;
}
.ts-chip-consolidation { background: var(--color-primary-soft); color: var(--color-primary-hover); }
.ts-chip-split { background: #e0e7ff; color: #4338ca; }
.ts-split-row td {
  font-size: 12px;
  color: var(--color-text-supporting);
  background: rgb(224 231 255 / 0.25);
}
.ts-split-left {
  font-size: 12px;
  color: var(--color-text-supporting);
  max-width: 420px;
}
.ts-split-qty {
  font-size: 11px;
  font-weight: 700;
  color: #4338ca;
  white-space: nowrap;
  margin-top: 2px;
}
.ts-items li.ts-split-hit {
  background: #e0e7ff;
  color: #3730a3;
  font-weight: 600;
  border-radius: 3px;
  padding: 0 3px;
}
.ts-loaded-no {
  font-size: 11px;
  color: var(--color-text-muted);
  white-space: nowrap;
  margin-top: 1px;
}

/* ===== Quote Rejection (Adhoc) win-rate chart ===== */
/* Standalone anatomy — see public/js/win-rate-chart.js header comment for
   why this isn't shared with .monthly-mss-* (gosource-metrics.js). */
.qr-chart { width: 100%; position: relative; }
.qr-chart-svg {
  width: 100%;
  height: 240px;
  display: block;
  font-family: inherit;
}
.qr-chart-gridline  { stroke: var(--color-border); stroke-width: 1; stroke-dasharray: 2 3; }
.qr-chart-axis       { stroke: var(--color-border); stroke-width: 1; }
.qr-chart-line        { stroke-width: 2; fill: none; }
.qr-chart-dot          { }
.qr-chart-dot-hit { fill: transparent; cursor: pointer; }
.qr-chart-axis-label    { font-size: 10.5px; fill: var(--color-text-muted); }
.qr-chart-tooltip {
  position: absolute;
  pointer-events: none;
  z-index: 10;
  background: var(--color-heading-2, #111827);
  color: #fff;
  font-size: 11.5px;
  font-weight: 500;
  padding: 4px 8px;
  border-radius: 4px;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
.qr-legend {
  display: flex;
  gap: 1rem;
  justify-content: center;
  margin-top: 0.5rem;
  font-size: 12px;
  color: var(--color-text-muted);
}
.qr-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }
.qr-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}
.qr-page-num {
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface, #fff);
  color: var(--color-text-muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.qr-page-num:hover { background: var(--color-bg-soft); }
.qr-page-num.active {
  background: var(--color-accent, #1f6feb);
  border-color: var(--color-accent, #1f6feb);
  color: #fff;
  cursor: default;
  font-weight: 600;
}
.qr-page-ellipsis {
  padding: 0 2px;
  color: var(--color-text-muted);
}
.qr-preset-btn {
  height: 22px;
  padding: 0 8px;
  border: 1px solid var(--color-border);
  border-radius: 9999px;
  background: var(--color-surface, #fff);
  color: var(--color-text-muted);
  font-size: 11px;
  cursor: pointer;
}
.qr-preset-btn:hover { background: var(--color-bg-soft); }
.qr-preset-btn.active {
  background: var(--color-accent, #1f6feb);
  border-color: var(--color-accent, #1f6feb);
  color: #fff;
  font-weight: 600;
}
.qr-setup-banner {
  background: var(--color-warning-bg);
  border: 1px solid #fed7aa;
  border-radius: 6px;
  padding: 0.75rem 1rem;
  font-size: 12px;
}
.qr-filter-hint {
  font-weight: 400;
  opacity: 0.75;
}
.qr-checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}
.qr-checkbox-group label {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 12px;
  color: var(--color-text);
  cursor: pointer;
  white-space: nowrap;
}
.qr-about-body {
  margin-top: 0.5rem;
  padding: 0.75rem 1rem;
  background: var(--color-bg-soft);
  border-radius: 6px;
  line-height: 1.5;
}
.qr-about-body p { margin: 0 0 0.5rem; }
.qr-about-body p:last-child { margin-bottom: 0; }
.qr-about-list { margin: 0.25rem 0 0; padding-left: 1.25rem; list-style: disc; }
.qr-about-list li { margin-bottom: 0.35rem; }
.qr-about-list li:last-child { margin-bottom: 0; }
.qr-th-badge-wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
}
.qr-th-sort {
  font: inherit;
  color: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
}
.qr-th-sort-idle { opacity: 0.45; }
.qr-th-sort:hover .qr-th-sort-idle { opacity: 0.8; }
.qr-th-sort:hover { color: var(--color-text); }
.qr-th-sort.active { color: var(--color-primary); }

/* Lost-reason comment cell (Quote Rejection drill-down). HubSpot's
   "Quote Lost Reason Comment" is a free-text textarea — real values run to
   several sentences ("We lost the bid. We were at 40k and the winning
   bidder was ... at 29k."), while every other cell on the row is a short
   value. Left unconstrained one long comment sets the width of the whole
   table, so the cell is fixed-width, wraps, and clamps to 3 lines; the
   full text is on `title` for a hover read and one click expands the cell
   in place (toggled in quote-rejection.js's delegated table handler). */
.qr-lost-comment {
  width: 320px;
  max-width: 320px;
  white-space: normal;
  line-height: 1.35;
  cursor: pointer;
  /* -webkit-line-clamp is the only cross-browser line clamp today; it works
     in Chrome/Edge/Safari AND Firefox (implemented since FF 68) despite the
     vendor prefix. A browser that ignores it simply shows the full comment,
     which is a graceful degradation, not a broken cell. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.qr-lost-comment.is-expanded {
  -webkit-line-clamp: unset;
  display: block;
  overflow: visible;
}

/* Quote-number combobox (Ops Quote Comparison). The option list is every
   quote the Dropbox folder holds solver files for — too many for a native
   <select>, so it's the input + filtered listbox pattern. The list is
   absolutely positioned so opening it never reflows the filter bar. */
.opsqc-combo { position: relative; }
.opsqc-combo .filter-input { padding-right: 30px; }
.opsqc-combo-caret {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-text-muted);
  cursor: pointer;
}
.opsqc-combo-caret:hover { color: var(--color-text); }
.opsqc-combo-caret svg { width: 14px; height: 14px; }
.opsqc-combo-list {
  position: absolute;
  z-index: 40;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 4px;
  list-style: none;
  max-height: 280px;
  overflow-y: auto;
  /* Same surface/shadow/stacking as the nav's dropdown menu (.gm-nav-drop-menu)
     — it's the same kind of floating panel, and an opaque background is what
     makes it a panel rather than text laid over the page. (--color-bg does
     not exist in this stylesheet; --color-bg-surface is the white one.) */
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.375rem;
  box-shadow: var(--shadow-md);
}
.opsqc-combo-option {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 5px 8px;
  border-radius: 0.25rem;
  cursor: pointer;
}
/* Hover and keyboard highlight share one look, so the arrow keys and the
   mouse never disagree about which row is "current". */
.opsqc-combo-option:hover,
.opsqc-combo-option.is-active { background: var(--color-bg-soft); }
.opsqc-combo-num {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--color-text);
}
.opsqc-combo-meta { font-size: 11px; color: var(--color-text-muted); white-space: nowrap; }
.opsqc-combo-empty { padding: 6px 8px; font-size: 12px; color: var(--color-text-muted); }

/* MSS-style score card (Ops Quote Comparison). One row per component
   metric; the roll-up badge lives in the card header and on the tab label.
   `approx` marks the two metrics that depend on pairing Prod vehicles to
   solver routes by item overlap (they share no id) — see
   src/services/mss-score.js. */
.opsqc-score-approx {
  display: inline-block;
  margin-left: 4px;
  padding: 0 4px;
  border: 1px solid var(--color-border);
  border-radius: 3px;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  cursor: help;
}
/* A metric that could not be scored stays visible (so the reader can see
   WHY the roll-up is a floor) but reads as inactive. */
.opsqc-score-unscored { background: var(--color-bg-soft); }
.opsqc-score-notes { margin: 0.25rem 0 0.5rem; padding-left: 1.1rem; list-style: disc; }
.opsqc-score-notes li { margin-bottom: 0.25rem; }

/* Per-quote notes panel (Ops Quote Comparison). Sits to the RIGHT of the
   lookup controls on a wide screen and drops below them on a narrow one, so
   the note is visible beside the quote it belongs to without competing with
   the tab strip for vertical space. */
.opsqc-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 320px);
  grid-template-areas:
    "main note"
    "input note"
    "status note";
  gap: 0.75rem 1.5rem;
  align-items: start;
}
.opsqc-head-main { grid-area: main; }
.opsqc-head > .flex { grid-area: input; }
.opsqc-head > #status-line { grid-area: status; }
/* Shared per-quote note panel (Ops Quote Comparison + Quote Lookup). The
   panel itself is layout-neutral; each page places it. */
.qnote {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}
/* Ops Quote Comparison puts it in the header grid beside the controls. */
.opsqc-head .qnote { grid-area: note; }
/* One column below the breakpoint: a 320px note column next to a 280px input
   would squeeze both. */
@media (max-width: 900px) {
  .opsqc-head {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "main" "input" "status" "note";
  }
}
.qnote-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.qnote-status { font-size: 11px; color: var(--color-text-muted); }
.qnote-status.is-dirty { color: var(--color-warning-text, #92400e); }
.qnote-status.is-error { color: var(--color-error-text, #b91c1c); }
.qnote-text {
  width: 100%;
  min-height: 120px;
  resize: vertical;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--color-text);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.375rem;
}
.qnote-text:focus { outline: 2px solid var(--color-primary); outline-offset: -1px; }
.qnote-actions { display: flex; align-items: center; gap: 0.5rem; }
.qnote-path { font-size: 10.5px; color: var(--color-text-muted); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; overflow-wrap: anywhere; }

/* Side-by-side compare region (Ops Quote Comparison). Map + transport plan
   for every source at once, so routes and truck lists can be read across
   columns. Independent column stacks (per TJ, 2026-09-11) — each column
   owns its own cards rather than the page owning aligned section rows.
   Everything wide (line items, score, QA) stays behind the tab strip. */
.opsqc-compare {
  display: grid;
  grid-template-columns: repeat(var(--opsqc-cols, 3), minmax(0, 1fr));
  gap: 0.75rem;
  align-items: start;
}
/* `minmax(0, 1fr)` above is load-bearing: with the default `1fr` (min-content)
   a wide transport table refuses to shrink and the columns overflow the page
   instead of the table scrolling inside its own card. */
.opsqc-col { display: flex; flex-direction: column; gap: 0.75rem; min-width: 0; }
.opsqc-col > * { min-width: 0; }
/* The transport table is `table-layout: fixed` + `w-full`, so in a ~340px
   column it SHRINKS to fit rather than overflowing — headers collapse to
   "T... T... Fill C..." and values get cut mid-number. Its wrapper is
   already `overflow-x: auto`, so all it needs is a floor wide enough for
   nine columns; then the wrapper scrolls and every value stays legible. */
/* Only the single-line layout needs the floor; the stacked one is built to
   fit the column, so forcing 720px on it would reintroduce the very scroll
   it exists to remove. */
.opsqc-col table.phx-table:not(.is-stacked) { min-width: 720px; }
.opsqc-col-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0 2px;
  min-width: 0;
}
.opsqc-col-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--color-heading-2);
  white-space: nowrap;
}
.opsqc-col-head .muted { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Three map+table columns stop being readable well before phone width. */
@media (max-width: 1100px) {
  .opsqc-compare { grid-template-columns: minmax(0, 1fr); }
}

/* Two-line transport rows (Ops Quote Comparison's compare columns). One
   truck = two <tr>: line 1 keeps the identity fields as real columns so they
   align across the three panels, line 2 is a self-labelled metrics strip
   spanning the row. Nine columns cannot fit ~340px without sideways
   scrolling, which is what this replaces. */
.gm-table.phx-table.is-stacked th { padding: 10px 8px; }
.gm-table.phx-table.is-stacked td { padding: 8px 8px 2px; }
/* Zebra striping is per-<tr>, so with two rows per truck it would stripe
   half a record. Off entirely; the pair is separated by a rule instead. */
.gm-table.phx-table.is-stacked tbody tr:nth-child(even) td { background: transparent; }
.gm-table.phx-table.is-stacked tbody tr:hover td { background: transparent; }
/* Hover the whole record, not one of its two lines. */
.gm-table.phx-table.is-stacked tbody tr.phx-stack-a:hover td,
.gm-table.phx-table.is-stacked tbody tr.phx-stack-a:hover + tr.phx-stack-b td { background: #f1f5f9; }
.gm-table.phx-table.is-stacked tbody tr.phx-stack-b td {
  padding: 0 8px 10px;
  border-bottom: 1px solid var(--color-border);
}
.gm-table.phx-table.is-stacked tbody tr.phx-stack-b:last-child td { border-bottom: none; }
/* `table-layout: fixed` splits the four columns evenly (76px each at a 338px
   column), which clips the truck number — the identity field, and the one
   that must stay readable. Pin the three narrow columns and let Type take
   what's left, truncating with the full value on hover. */
/* 110px fits Phoenix's longest real truck number ("QD0709-43906") plus the
   cell's own padding; at 96px it clipped. */
.gm-table.phx-table.is-stacked th:nth-child(1) { width: 110px; }
/* 42px clipped the "Stops" header itself to "Sto…". */
.gm-table.phx-table.is-stacked th:nth-child(3) { width: 54px; }
.gm-table.phx-table.is-stacked tbody td:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
}
.phx-stack-type {
  max-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Load weight vs rated capacity, beside the truck type. It has to survive a
   ~340px compare column, where the type alone already truncates, so the type
   keeps its own ellipsis and the weight sits after it as an unbreakable
   segment that WRAPS to a second line rather than being cut off — an
   over-capacity truck that only shows when the column is wide enough is
   worse than no indicator at all. On the wide single-tab table both stay on
   one line, reading "Box/Cube Trucks · 26ft | 15,832 lb / 10,000 lb". */
.phx-truck-weight {
  white-space: nowrap;
  font-size: 11px;
  color: var(--color-text-muted);
}
.phx-truck-weight::before {
  content: '|';
  margin: 0 5px;
  color: var(--color-border);
}
.phx-truck-weight.is-over {
  color: var(--color-error);
  font-weight: 600;
}
.phx-truck-type { white-space: nowrap; }
/* Overrides the nowrap/ellipsis default so the weight can drop to a second
   line in a narrow column instead of disappearing. */
.gm-table.phx-table.is-stacked td.phx-stack-type {
  white-space: normal;
  text-overflow: clip;
}
.gm-table.phx-table.is-stacked td.phx-stack-type .phx-truck-type {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* A stop pill sized for a 40px single-line row is too tall once each record
   is two lines. */
.gm-table.phx-table.is-stacked .phx-pill { height: 26px; min-width: 26px; padding: 0 8px; gap: 5px; }

.phx-metrics {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 1px 7px;
}
.phx-metric { display: inline-flex; align-items: baseline; gap: 3px; white-space: nowrap; }
.phx-metric-label {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.phx-metric-value {
  font-size: 11px;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

/* "Transport plan … Total budget: $2,650.00" — the title row with the total
   pushed right, mirroring Phoenix's own Transport Budget card. */
.phx-plan-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  flex-wrap: wrap;
  min-width: 0;
}
.phx-plan-head > h2 { min-width: 0; }
.phx-plan-total {
  font-size: 12px;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.phx-plan-total strong {
  font-size: 13px;
  font-weight: 700;
  color: var(--color-heading-2);
  font-variant-numeric: tabular-nums;
}

/* Truck items expanded IN PLACE under their own row, so the same truck can be
   open in every compare column at once and read across — rather than a side
   panel that shows one truck at a time. */
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items > td {
  padding: 0 8px 10px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg-soft);
}
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items table { min-width: 0; font-size: 11px; }
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items th { padding: 6px; font-size: 9.5px; }
/* `table-layout: fixed` + `overflow: hidden` on phx cells CLIPS a long word
   with no ellipsis, so "Rhaphiolepis" read as "Rhaphiole" in a ~300px column.
   Wrap instead — a silently truncated latin name is unreadable AND misleading
   when the point is comparing the same item across three columns. */
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items th,
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items td { overflow-wrap: anywhere; }
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items td { padding: 4px 6px; }
/* The drawer table pins wide columns; inside a ~300px card they must give. */
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items th[style] { width: auto !important; }
/* A product's alias list can run to a paragraph ("Southern Wax Myrtle, Morella
   cerifera, Jasmine Wax Myrtle, …"), which in a narrow column makes one row
   taller than the whole truck and knocks the three columns out of alignment.
   Two lines, full text on hover. */
.phx-item-sub {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Stop rows in the compact inline table: the pickup a run of items belongs to,
   said once instead of repeated on every row. */
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items tr.phx-stop-head > td {
  background: #eef2f6;
  color: var(--color-text-supporting);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.02em;
  padding: 4px 6px;
}
.gm-table.phx-table.is-stacked tbody tr.phx-stack-items tr.phx-stop-head.phx-row-diff > td { background: #fff7ed; color: #b45309; }
/* An open pill stays visibly "on" so it's obvious which trucks are expanded. */
.phx-pill-btn.is-open { background: #00ba70; color: #fff; }

/* ── Quote-Order Reconciliation (/quote-order-reconciliation.html) ───────
   A pivot that can run to 40 columns (R0159-64003 has 39 phases), so the
   quantity columns are deliberately narrow and tabular, and the two colour
   states — substitution and addition — are carried on the ROW rather than
   on a badge, since the point is to scan down a long table and see where
   the order stopped agreeing with the quote. Yellow and blue are the colours
   TJ named; they map to the existing warning/info tokens so the page stays
   in the dashboard's palette rather than introducing two new hues. */

.qor-table th,
.qor-table td { vertical-align: top; }
.qor-table td.num,
.qor-table th.num { white-space: nowrap; }

.qor-phase-th { min-width: 68px; }
.qor-phase-date {
  font-weight: 400;
  font-size: 10px;
  color: var(--color-text-muted);
  margin-top: 1px;
}

.qor-item-name { font-weight: 600; color: var(--color-heading-2); line-height: 1.3; }
.qor-seller { font-size: 11px; color: var(--color-text-muted); margin-top: 2px; }
.qor-seller-inline { font-weight: 400; }
.qor-spec { font-size: 11px; color: var(--color-text-supporting); line-height: 1.35; }
.qor-spec-text { color: var(--color-text-muted); }
.qor-empty { color: var(--color-text-muted); }

.qor-tag {
  display: inline-block;
  margin-top: 3px;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 10.5px;
  line-height: 1.45;
  font-weight: 500;
}
.qor-tag-sub { background: var(--color-warning-bg); color: var(--color-warning); }
.qor-tag-add { background: var(--color-info-bg); color: var(--color-info); }

/* Row tints. The left marker is what survives when a reader is scrolled
   sideways through the phase columns. */
.qor-table tbody tr.qor-row-sub td { background: var(--color-warning-bg); }
.qor-table tbody tr.qor-row-add td { background: var(--color-info-bg); }

/* The specific cell a substitute quantity landed in, inside an already
   tinted row — a deeper ring rather than a deeper fill, so the row tint
   still reads as one band. */
.qor-table tbody td.qor-cell-sub {
  box-shadow: inset 0 0 0 1px var(--color-warning);
  font-weight: 600;
}

.qor-total-th,
.qor-total-td {
  border-left: 1px solid var(--color-border);
  font-weight: 600;
}
.qor-delta-up { color: var(--color-info); font-weight: 600; }
.qor-delta-down { color: var(--color-warning); font-weight: 600; }

.qor-table tfoot tr.qor-foot td {
  border-top: 1px solid var(--color-border);
  font-weight: 600;
  color: var(--color-heading-2);
  background: var(--color-bg-subtle);
}

/* Inline legend chips used in the page's intro paragraph. */
.qor-chip {
  display: inline-block;
  padding: 0 5px;
  border-radius: 3px;
  font-weight: 600;
}
.qor-chip-sub { background: var(--color-warning-bg); color: var(--color-warning); }
.qor-chip-add { background: var(--color-info-bg); color: var(--color-info); }

/* Spec-only differences: same product, same size, ops reworded the spec.
   Deliberately quieter than a substitution — on a long order most rows have
   one, so they are a footnote rather than a state. */
.qor-spec-note {
  margin-top: 3px;
  font-size: 10.5px;
  line-height: 1.4;
  color: var(--color-text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.qor-table tbody td.qor-cell-spec { text-decoration: underline dotted var(--color-text-muted); }

/* Legend swatches — the two row tints, shown as small blocks so the legend
   reads as the same colours the table uses rather than as words about them. */
.qor-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  display: inline-block;
  flex: none;
}
.qor-swatch-sub { background: var(--color-warning-bg); box-shadow: inset 2px 0 0 var(--color-warning); }
.qor-swatch-add { background: var(--color-info-bg); box-shadow: inset 2px 0 0 var(--color-info); }

/* Wide-order layout. R0159-64003 has 39 phases, so the table is far wider
   than any screen. Left to itself the browser honours w-full and steals the
   width back from the text columns, breaking the item name one word per line
   and making rows hundreds of pixels tall — so the table is allowed to exceed
   its container, which the card already scrolls.

   .gm-table's own `overflow: hidden` (there to clip its corner radius) would
   also make the TABLE the nearest scrollport, so a sticky cell inside it
   would stick to a box that never scrolls — i.e. not at all. This page gives
   that up; border-collapse goes with it, since collapsed borders and sticky
   cells have a long history of disagreeing. */
.qor-table {
  width: auto;
  min-width: 100%;
  overflow: visible;
  border-collapse: separate;
  border-spacing: 0;
  border-radius: 0;
}

/* The identity block — item, size/spec and the quote's own quantity — is
   frozen, so a phase column 30 across can still be read against the item it
   belongs to and the number it is being compared with. Widths are fixed
   rather than min/max because each sticky column's `left` offset is the sum
   of the ones before it, and a column that could resize would drag those
   offsets out of alignment. 482px total, which still leaves room for a
   dozen-odd phases on a laptop. */
.qor-item-th, .qor-table td.qor-item-td { width: 280px; min-width: 280px; max-width: 280px; left: 0; }
.qor-spec-th, .qor-table td.qor-spec { width: 130px; min-width: 130px; max-width: 130px; left: 280px; }
.qor-original-th, .qor-table td.qor-original-td { width: 72px; min-width: 72px; max-width: 72px; left: 410px; }

.qor-item-th, .qor-table td.qor-item-td,
.qor-spec-th, .qor-table td.qor-spec,
.qor-original-th, .qor-table td.qor-original-td {
  position: sticky;
  z-index: 1;
  /* A sticky cell can't inherit the row's tint, so every state repaints it. */
  background: var(--color-bg-surface, #fff);
}
.qor-table thead th.qor-item-th,
.qor-table thead th.qor-spec-th,
.qor-table thead th.qor-original-th { z-index: 2; background: var(--color-bg-soft); }

.qor-table tbody tr.qor-row-sub td.qor-item-td,
.qor-table tbody tr.qor-row-sub td.qor-spec,
.qor-table tbody tr.qor-row-sub td.qor-original-td { background: var(--color-warning-bg); }
.qor-table tbody tr.qor-row-add td.qor-item-td,
.qor-table tbody tr.qor-row-add td.qor-spec,
.qor-table tbody tr.qor-row-add td.qor-original-td { background: var(--color-info-bg); }
.qor-table tfoot tr.qor-foot td.qor-item-td,
.qor-table tfoot tr.qor-foot td.qor-original-td { background: var(--color-bg-subtle); }

/* The frozen block's trailing edge, so it reads as one panel the phases
   slide under rather than three columns that happen to stop moving. */
.qor-table td.qor-original-td,
.qor-table th.qor-original-th { box-shadow: inset -2px 0 0 var(--color-border); }
.qor-table tbody tr.qor-row-sub td.qor-item-td { box-shadow: inset 3px 0 0 var(--color-warning); }
.qor-table tbody tr.qor-row-add td.qor-item-td { box-shadow: inset 3px 0 0 var(--color-info); }

/* The recorded substitution, shown in the phase column it happened in.
   Sits under the quantity and is left-aligned against the right-aligned
   number so the two don't read as one value. Clamped to three lines with the
   full before/after and the date on hover — a long spec must not be allowed
   to set the column's width. */
.qor-change {
  margin-top: 2px;
  font-size: 10px;
  line-height: 1.3;
  font-weight: 500;
  text-align: left;
  white-space: normal;
  color: var(--color-warning);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.qor-change-product { font-weight: 700; }

/* The table is its own viewport rather than growing the page. This is what
   lets the header row lock: `overflow-x: auto` on a wrapper forces the other
   axis to compute to `auto` too, so that wrapper is ALWAYS the nearest
   scrollport — a `top: 0` sticky header inside it would stick to a box that
   never scrolls vertically, i.e. not at all. Giving the wrapper a real
   height makes it scroll in both directions, and the header (top) and the
   identity columns (left) then lock against the same box. */
.qor-scroll {
  overflow: auto;
  max-height: calc(100vh - 230px);
  min-height: 220px;
}

.qor-table thead th {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--color-bg-soft);
}
/* The four corner cells are sticky on BOTH axes, so they have to outrank
   the row and column they belong to or they'd be painted over at the join. */
.qor-table thead th.qor-item-th,
.qor-table thead th.qor-spec-th,
.qor-table thead th.qor-original-th { z-index: 4; }

/* Totals stay in view too — on a 40-row order the footer is the number you
   are scrolling to check the columns against. */
.qor-table tfoot td {
  position: sticky;
  bottom: 0;
  z-index: 3;
  background: var(--color-bg-subtle);
}
.qor-table tfoot td.qor-item-td,
.qor-table tfoot td.qor-original-td { z-index: 4; }

/* A sticky cell paints its own background, so the table's own borders no
   longer show through at the frozen edges; these put them back. */
.qor-table thead th { box-shadow: inset 0 -1px 0 var(--color-border); }
.qor-table tfoot td { box-shadow: inset 0 1px 0 var(--color-border); }
.qor-table thead th.qor-original-th { box-shadow: inset -2px 0 0 var(--color-border), inset 0 -1px 0 var(--color-border); }
.qor-table tfoot td.qor-original-td { box-shadow: inset -2px 0 0 var(--color-border), inset 0 1px 0 var(--color-border); }
.qor-resolved {
  position: absolute;
  top: 100%;
  left: 0;
  font-size: 10px;
  color: var(--color-text-muted);
  font-weight: 400;
  line-height: 1.1;
  white-space: nowrap;
}

/* Cancelled / Unavailable quantity. Struck through and muted, sitting under
   the live number in the same cell — a cell is often a mix (some lines
   Complete, some Unavailable), so striking the whole figure would misreport
   it. Every struck number on this page means the same thing: on the order,
   not shipping, excluded from every total. */
.qor-void {
  display: block;
  text-decoration: line-through;
  color: var(--color-text-muted);
  font-weight: 400;
  opacity: 0.75;
}
/* In the legend the swatch is inline, not a stacked cell value. */
.qor-table ~ * .qor-void,
.flex > .qor-void { display: inline; }

/* The page blurb, collapsed. It is reference text read once; expanded it
   costs ~55px of table. */
.qor-about { font-size: 11px; }
.qor-about > summary {
  cursor: pointer;
  color: var(--color-text-muted);
  list-style: none;
  user-select: none;
}
.qor-about > summary::-webkit-details-marker { display: none; }
.qor-about > summary::before { content: '\25B8 '; }
.qor-about[open] > summary::before { content: '\25BE '; }
.qor-about > summary:hover { color: var(--color-text-supporting); }

/* The spec-changed cell marks its NUMBER, not everything inside it — the
   dotted underline otherwise inherits into the change label and the struck
   void quantity below, which reads as a different state than it is. */
.qor-table tbody td.qor-cell-spec > .qor-change,
.qor-table tbody td.qor-cell-spec > .qor-void { text-decoration-line: none; }
.qor-table tbody td.qor-cell-spec > .qor-void { text-decoration: line-through; }

/* ── The one bar ────────────────────────────────────────────────────────
   Search field and order detail on a single line, replacing what used to be
   a title card stacked on a facts card. Two cards' padding plus the gap
   between them was ~130px of permanent chrome, and every pixel of it was a
   row of the table the user came for (per TJ, 2026-09-17).

   Slots are rendered only when they have a value, so an un-searched page is
   just the input and a sparse order gives a shorter bar rather than a row of
   em-dashes. */
/* The extra bottom padding is where the "from <what you typed>" note sits.
   It is absolutely positioned so that appearing and disappearing cannot
   change the bar's height, which would otherwise shove the table down. */
.qor-bar { padding: 8px 12px 14px; }
.qor-bar-row {
  display: flex;
  flex-wrap: wrap;
  /* flex-START, not flex-end: every slot is label-over-value with the same
     fixed heights, so aligning the tops puts all the labels on one line and
     all the values on another. Aligning the bottoms instead let the taller
     input slot drag its label out of line with the rest. */
  align-items: flex-start;
  gap: 4px 22px;
}
.qor-facts {
  display: contents; /* the slots join the bar's flex row directly, so they
                        wrap with it instead of as one indivisible block */
}
.qor-fact { display: flex; flex-direction: column; min-width: 0; }
.qor-fact-label {
  height: 14px;
  line-height: 14px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  white-space: nowrap;
}
/* One band for every slot's value, tall enough for the 30px input, with text
   values centred in it. This is what puts the input on the same line as
   QUOTE, STATUS, BUYER and the rest instead of hanging below them. */
.qor-fact-value,
.qor-fact-control {
  min-height: 30px;
  display: flex;
  align-items: center;
}
.qor-fact-control { gap: 8px; }
.qor-fact-value {
  font-size: 13px;
  color: var(--color-heading-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Job names run long and are the least load-bearing value on the bar, so
   that one slot is allowed to truncate rather than push the rest to a
   second line. */
.qor-fact-value { max-width: 320px; }

.qor-fact-input { position: relative; }
.qor-fact-input .filter-input { width: 210px; height: 30px; }
.qor-bar-btn { height: 30px; padding: 0 14px; flex: none; }
/* Bottom-right corner of the bar, not the middle of it. */
.qor-bar-about { margin-left: auto; align-self: flex-end; }
/* Expanded, the blurb needs the full width — it drops below the bar rather
   than squeezing the detail slots. */
.qor-bar-about[open] { margin-left: 0; flex-basis: 100%; }

/* ── Column focus ───────────────────────────────────────────────────────
   Reading one phase across 40 columns means tracking a 68px strip down the
   table. Hovering a phase header previews that column; clicking pins it.

   The darkening is a translucent LAYER (background-image), never a
   background-colour: the rows underneath already carry the substitution and
   addition tints, and a colour would replace those instead of deepening
   them. It also leaves `box-shadow` free, which the substituted-cell ring
   and the frozen-column dividers already use. */
.qor-table th[data-col] { cursor: pointer; }

.qor-table [data-col].qor-col-focus {
  background-image: linear-gradient(rgba(15, 23, 42, 0.07), rgba(15, 23, 42, 0.07));
}
.qor-table thead th[data-col].qor-col-focus {
  background-image: linear-gradient(rgba(15, 23, 42, 0.11), rgba(15, 23, 42, 0.11));
}
.qor-table thead th[data-col].qor-col-focus .qor-phase-label {
  font-size: 12px;
  font-weight: 800;
  color: var(--color-heading-2);
}
.qor-table thead th[data-col].qor-col-focus .qor-phase-date { color: var(--color-text-supporting); }

/* The header label reserves its focused size at rest, so the bump changes
   weight and colour without nudging the column width as the pointer moves
   across the row. */
.qor-phase-label { font-size: 12px; line-height: 1.1; }

/* A pinned column keeps a marker even while another is being previewed, so
   it is clear the pin is still set and where it is. */
.qor-table thead th[data-col].qor-col-pinned { box-shadow: inset 0 3px 0 var(--color-primary, #00ba70), inset 0 -1px 0 var(--color-border); }
.qor-table tfoot td[data-col].qor-col-pinned { box-shadow: inset 0 -3px 0 var(--color-primary, #00ba70), inset 0 1px 0 var(--color-border); }

/* ===== Market Price Tester (/market-price-tester.html) ===== */
/* Four inputs on one row on a wide screen, two on a laptop, one on a phone.
   The combobox panels (.opsqc-combo*) are shared with Ops Quote Comparison. */
.mpt-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.75rem;
}
@media (max-width: 1200px) { .mpt-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px)  { .mpt-grid { grid-template-columns: 1fr; } }
.mpt-hint { margin-top: 3px; font-size: 11px; color: var(--color-text-muted); min-height: 1em; }
.mpt-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.5rem;
}
.mpt-newbuyer { border-top: 1px solid var(--color-border); padding-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.mpt-subhead { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mpt-reasons { margin: 0; padding-left: 1.1rem; list-style: disc; font-size: 12px; color: var(--color-text-muted); }
.mpt-table { font-size: 12px; }
.mpt-scroll { max-height: 420px; overflow: auto; }
.mpt-specs { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mpt-th-sort {
  font: inherit; color: inherit; text-transform: inherit; letter-spacing: inherit;
  background: none; border: none; padding: 0; cursor: pointer; white-space: nowrap;
}
.mpt-th-sort-idle { opacity: 0.45; }
.mpt-th-sort:hover .mpt-th-sort-idle { opacity: 0.8; }
.mpt-th-sort:hover { color: var(--color-text); }
.mpt-th-sort.active { color: var(--color-primary); }
.mpt-scroll thead th { position: sticky; top: 0; z-index: 1; }
#mpt-form[aria-busy="true"] #run-btn { opacity: 0.7; cursor: progress; }

/* ---- Market Price Scoring, Results tab (/market-price-scoring.html#results) — mpl- prefix ---- */
.mpl-muted { color: var(--color-text-muted); font-weight: 400; }
.mpl-guide { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
.mpl-guide-card {
  border: 1px solid var(--color-border); border-radius: 8px; padding: 12px;
  display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: var(--color-text);
}
.mpl-guide-card header { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.mpl-guide-card h3 { font-size: 13px; font-weight: 600; color: var(--color-heading-2); }
.mpl-guide-card .mpl-best { padding: 6px 8px; border-radius: 6px; background: var(--color-bg-page); }
.mpl-guide-card .mpl-catch { color: var(--color-text-supporting); }
.mpl-guide-card.is-absent { opacity: 0.55; }
.mpl-grids { display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 16px; }
.mpl-grid h3 { font-size: 13px; font-weight: 600; color: var(--color-heading-2); margin-bottom: 6px; }
.mpl-grid table { width: 100%; }
.mpl-grid th[scope="row"] { font-weight: 500; white-space: nowrap; }
.mpl-grid td { white-space: nowrap; }
.mpl-sub { display: block; font-size: 10px; color: var(--color-text-muted); line-height: 1.1; }
.mpl-foot { font-size: 11px; margin-top: 4px; }
.is-lowconf td, td.is-lowconf, .is-lowconf th { color: var(--color-text-disabled); font-style: italic; }
.mpl-bar-col { width: 160px; }
.mpl-bar-track { position: relative; display: block; height: 10px; border-radius: 5px; background: var(--color-bg-subtle); }
.mpl-bar { position: absolute; top: 0; bottom: 0; border-radius: 5px; }
.mpl-bar.is-pos { background: var(--color-primary); }
.mpl-bar.is-neg { background: var(--color-error-fill); }
.mpl-bar-zero { position: absolute; left: 50%; top: -2px; bottom: -2px; width: 1px; background: var(--color-text-muted); }
.mpl-breakdown-scroll { max-height: 520px; overflow: auto; border: 1px solid var(--color-border); border-radius: 8px; }
.mpl-breakdown-scroll .gm-table { border: none; border-radius: 0; }
.mpl-breakdown-scroll thead th { position: sticky; top: 0; z-index: 1; }
@media (max-width: 768px) { .mpl-grids { grid-template-columns: 1fr; } .mpl-grid { overflow-x: auto; } }
.mpl-grid tbody th, .mpl-breakdown-scroll tbody th {
  text-transform: none; letter-spacing: 0; font-size: 12px; font-weight: 500;
  color: var(--color-text); background: transparent; border-bottom: 1px solid var(--color-border);
}
