/*
 * Planner — production components (canonical) · §09 migration Phase 2
 * ------------------------------------------------------------------
 * The consolidated component vocabulary, loaded by gantt-builder.html and
 * people.html (after tokens.css). This is ADDITIVE: it introduces the
 * canonical classes (.pill / .badge / .chip / .modal* / .input* / .avatar* /
 * .panel / .card-sm / .tbl* / .banner* / .toast* / .popover* / .status-dot /
 * .kbd / .count-tag / .state-* + type utilities) WITHOUT touching
 * any existing screen — every name here is net-new in production.
 *
 * INTENTIONALLY OMITTED (they already exist in production CSS; redefining
 * them would silently restyle live screens). These collapse in Phase 3, when
 * the ~200 className callsites are rewritten and the old classes deleted:
 *   .btn / .seg / .tabstrip / .tab   — gantt-builder.css
 *   .po-bar / .ms-strip / .po-chip   — cost-styles.css (feature atoms, kept)
 *
 * The full migration map (which legacy class maps to which canonical one)
 * lives in static/design-system/sections/migration.jsx, §09.
 */

/* ── Type utilities ─────────────────────────────────────────────── */
.mono  { font-family: var(--font-mono); font-feature-settings: "tnum" 1; }
.serif { font-family: var(--font-serif); font-weight: 500; letter-spacing: -0.01em; }
.num   { font-variant-numeric: tabular-nums; }
.muted { color: var(--text-muted); }
.faint { color: var(--text-muted); }

.t-display { font-family: var(--font-serif); font-weight: 500; font-size: 56px; line-height: 1.02; letter-spacing: -0.025em; }
.t-h1      { font-family: var(--font-serif); font-weight: 500; font-size: 38px; line-height: 1.1;  letter-spacing: -0.02em;  }
.t-h2      { font-family: var(--font-serif); font-weight: 500; font-size: 30px; line-height: 1.1;  letter-spacing: -0.02em;  }
.t-h3      { font-family: var(--font-serif); font-weight: 500; font-size: 20px; line-height: 1.2;  letter-spacing: -0.015em; }
.t-h4      { font-family: var(--font-serif); font-weight: 500; font-size: 18px; line-height: 1.25; letter-spacing: -0.01em;  }
.t-lede    { font-size: 16px; line-height: 1.5; color: var(--text-2); }
.t-body    { font-size: 13px; line-height: 1.45; color: var(--text); }
.t-meta    { font-size: 12px; line-height: 1.4;  color: var(--text-2); }
.t-cap     { font-size: 11px; line-height: 1.35; color: var(--text-muted); }
.t-eyebrow { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em; }
.t-label   { font-size: 10.5px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.07em; }

/* Field label — used by inspector + forms */
.field-label {
  display: flex; align-items: center;
  font-size: 11px; font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.08em;
  margin-bottom: 8px;
}
.field-label .right {
  margin-left: auto; text-transform: none; letter-spacing: 0;
  font-weight: 400; color: var(--text-muted);
}

/* ─────────────────────────────────────────────────────────────────
   BUTTON — single API, replaces 8+ production button classes.
   md=30 (canonical) · sm=26 · xs=22
   Variants: .primary, .accent, .ghost, .danger, .icon
   States: [disabled], .is-loading, :hover, :focus-visible
   (§09 Phase 3 — promoted here from gantt-builder.css)
   ───────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; height: 30px;
  background: var(--surface-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font: inherit; font-size: 13px; font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
  box-shadow: var(--shadow-1);
  white-space: nowrap;
}
.btn:hover { border-color: var(--border-hover); color: var(--text); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.btn.primary  { background: var(--text); color: var(--bg); border-color: var(--text); }
.btn.primary:hover {
  background: color-mix(in oklch, var(--text), black 15%);
  border-color: color-mix(in oklch, var(--text), black 15%);
}

.btn.accent  { background: var(--accent); color: #fff; border-color: var(--accent); }
.btn.accent:hover {
  background: color-mix(in oklch, var(--accent), black 15%);
  border-color: color-mix(in oklch, var(--accent), black 15%);
}

.btn.ghost    { background: transparent; border-color: transparent; box-shadow: none; color: var(--text-muted); }
.btn.ghost:hover { background: var(--bg-hover); color: var(--text); }

.btn.danger   { color: var(--danger); border-color: var(--danger-soft); }
.btn.danger:hover { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }

.btn.icon     { padding: 0; width: 30px; justify-content: center; }
.btn.sm       { height: 26px; padding: 4px 10px; font-size: 12px; }
.btn.sm.icon  { width: 26px; padding: 0; }
.btn.xs       { height: 22px; padding: 2px 8px; font-size: 11.5px; border-radius: 4px; box-shadow: none; }

.btn[disabled], .btn.is-disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
.btn.is-loading { position: relative; color: transparent !important; pointer-events: none; }
.btn.is-loading::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 12px; height: 12px; margin: -6px 0 0 -6px;
  border-radius: 50%;
  border: 1.5px solid currentColor; border-top-color: transparent;
  color: var(--text-2);
  animation: ds-spin 0.7s linear infinite;
}
.btn.primary.is-loading::after,
.btn.accent.is-loading::after { color: var(--bg); }
@keyframes ds-spin { to { transform: rotate(360deg); } }

/* ── segmented control ─────────────────────────────────────────────────── */
.seg { background: var(--surface-raised); border-radius: 8px; display: inline-flex; gap: 2px; padding: 3px; }
.seg button { background: transparent; border: 0; border-radius: 5px; color: var(--text-muted); cursor: pointer; font: inherit; font-size: 12px; font-weight: 500; height: 24px; padding: 4px 11px; transition: color 0.12s; display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; }
.seg button:hover { color: var(--text); }
.seg button.active { background: var(--surface); box-shadow: var(--shadow-1); color: var(--text); }
.seg button:disabled { cursor: not-allowed; opacity: 0.4; }

/* ── underline tabs (page-level navigation) ────────────────────────────── */
.tabstrip { align-items: center; background: var(--bg); border-bottom: 1px solid var(--border); display: flex; gap: 8px; height: 42px; padding: 0 18px; }
.tab { align-items: center; background: transparent; border: 0; border-bottom: 2px solid transparent; color: var(--text-muted); cursor: pointer; display: inline-flex; font: inherit; font-size: 13px; font-weight: 500; gap: 7px; height: 42px; margin-bottom: -1px; padding: 0 14px; transition: color 0.12s; }
.tab:hover { color: var(--text); }
.tab.active { border-bottom-color: var(--accent); color: var(--text); }
.tab .count { background: var(--surface-raised); border-radius: 4px; color: var(--text-muted); font-family: var(--font-mono); font-size: 11px; padding: 1px 6px; }
.tab.active .count { background: var(--accent-soft); color: var(--accent); }

/* ─────────────────────────────────────────────────────────────────
   INPUT — single API. .input (text/date/select), .input-sunk (search)
   ───────────────────────────────────────────────────────────────── */
.input {
  height: 30px;
  padding: 6px 10px;
  background: var(--surface-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  font: inherit; font-size: 13px;
  transition: border-color 0.12s;
  width: 100%;
}
.input:focus { outline: none; border-color: var(--accent); }
.input::placeholder { color: var(--text-muted); }
.input.invalid { border-color: var(--danger); background: var(--danger-soft); }
.input.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.input[disabled], .input.is-disabled {
  background: var(--surface-raised); color: var(--text-muted);
  border-color: var(--border); cursor: not-allowed;
}
.input[readonly] { background: var(--surface-raised); color: var(--text-2); }

/* Sunken / inline filter — outline search, table filters */
.input-sunk {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 10px; height: 30px;
  background: var(--surface-raised);
  border-radius: var(--r-sm);
  color: var(--text-muted);
}
.input-sunk input {
  flex: 1; background: transparent; border: 0; outline: 0;
  font: inherit; font-size: 13px; color: var(--text);
}
.input-sunk input::placeholder { color: var(--text-muted); }

/* Inline-edit (text that turns into an input on click) */
.inline-edit {
  cursor: text; border-bottom: 1px dashed transparent;
  display: inline-block; min-width: 40px;
}
.inline-edit:hover { border-bottom-color: var(--border-hover); }
.inline-input {
  background: transparent; border: none;
  border-bottom: 1.5px solid var(--accent);
  outline: none; font: inherit; color: inherit;
  padding: 1px 0; width: 100%;
  caret-color: var(--accent);
}

/* ─────────────────────────────────────────────────────────────────
   AVATAR — 8-hue oklch ramp + warm gray, replaces ag-chip--c0..c7.
   ───────────────────────────────────────────────────────────────── */
/* .avatar* RETIRED in DS-promotion wave 1 — one avatar atom: .ds-avatar
 * (window.__GB.DsAvatar). The .xs size was absorbed into .ds-avatar. */

.av-stack { display: inline-flex; }
.av-stack > * + * { margin-left: -5px; }

/* ─────────────────────────────────────────────────────────────────
   PILL — single API, replaces 10+ production pill / badge classes.
   .ds-pill = capsule (radius 999), 21px tall, semantic variants
     (is-ok / is-warn / is-danger / is-over / is-info / is-neutral),
     plus is-dashed (entity chip) / is-solid (alarm tag). window.__GB.DsPill.
   .badge = solid uppercase tag (radius 4), 18px tall, semantic variants
   .chip = capsule with leading icon, 26px tall (toolbar size)
   ───────────────────────────────────────────────────────────────── */
/* .pill* RETIRED in DS-promotion wave 2 — one status-pill atom: .ds-pill
 * (window.__GB.DsPill). Color names mapped to semantics at call-sites
 * (accent→is-over · amber→is-warn · sage→is-ok · blue→is-info · danger→is-danger
 * · bare→is-neutral); .pill.dashed/.solid absorbed into .ds-pill.is-dashed/.is-solid.
 * (.pill-switch is a separate kept atom — the Commitments cashflow PO switcher.) */

.badge {
  display: inline-flex; align-items: center;
  padding: 1px 7px; height: 18px; border-radius: 4px;
  font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em;
  background: var(--surface-raised); color: var(--text-muted);
}
.badge.blue   { background: var(--blue-soft);   color: var(--blue); }
.badge.sage   { background: var(--sage-soft);   color: var(--sage); }
.badge.amber  { background: var(--amber-soft);  color: var(--amber); }
.badge.accent { background: var(--accent-soft); color: var(--accent); }
.badge.danger { background: var(--danger-soft); color: var(--danger); }

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; height: 26px;
  background: var(--surface-elev); border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 12px; font-weight: 500;
  color: var(--text-2);
  white-space: nowrap; flex-shrink: 0;
}
.chip.accent { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.chip.amber  { background: var(--amber-soft);  border-color: var(--amber);  color: var(--amber); }
.chip.sage   { background: var(--sage-soft);   border-color: var(--sage);   color: var(--sage); }
.chip.danger { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }
.chip .chip-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; background: currentColor; }

/* Count tag (table column counts, tab counts, sidebar counts) */
.count-tag {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted);
  padding: 1px 6px; border-radius: 4px;
  background: var(--surface-raised);
}
.count-tag.accent { background: var(--accent-soft); color: var(--accent); }
.count-tag.danger { background: var(--danger-soft); color: var(--danger); }

/* Keyboard pill */
.kbd {
  font-family: var(--font-mono); font-size: 11px; font-weight: 500;
  background: var(--bg);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  color: var(--text-muted);
  padding: 0 6px; line-height: 16px;
  border-radius: 4px; min-width: 16px; text-align: center;
  display: inline-block;
}

/* ── Status atoms ───────────────────────────────────────────────── */
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; vertical-align: middle; }
.status-dot.todo  { border: 1.5px solid var(--text-faint); background: transparent; box-sizing: border-box; }
.status-dot.doing { background: var(--accent); }
.status-dot.done  { background: var(--sage); }
.status-dot.warn  { background: var(--amber); }
.status-dot.danger{ background: var(--danger); }

.status-diamond {
  width: 8px; height: 8px; background: var(--text); transform: rotate(45deg);
  display: inline-block; vertical-align: middle; border-radius: 1px;
}

/* ── Panel / card ───────────────────────────────────────────────── */
.panel {
  background: var(--surface-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}
.panel-head {
  padding: 14px 16px 12px;
  border-bottom: 1px solid var(--border);
}
.panel-head .id {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted); padding: 2px 6px;
  background: var(--surface-raised); border-radius: 4px;
}
.panel-head .title {
  font-family: var(--font-serif); font-weight: 500;
  font-size: 20px; letter-spacing: -0.015em;
  margin-top: 4px;
}
.panel-body { padding: 14px 16px; }
.panel-foot {
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface);
  display: flex; justify-content: flex-end; gap: 8px;
}

.card-sm {
  background: var(--surface-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 12px 14px;
}
.card-sm.warn {
  background: var(--accent-soft);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px oklch(58% 0.13 38 / 0.10);
}

/* ─────────────────────────────────────────────────────────────────
   MODAL — single shape, replaces .cf-modal / .ag-close-modal / .po-modal
   Radius var(--r-md), padding 18–20 by part, fixed header + body + foot.
   ───────────────────────────────────────────────────────────────── */
.modal-backdrop {
  position: fixed; inset: 0;
  background: var(--backdrop);
  display: flex; align-items: center; justify-content: center;
  z-index: 100;
}
.modal {
  background: var(--surface-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  min-width: 360px; max-width: 640px;
  max-height: 85vh;
  display: flex; flex-direction: column;
}
.modal.wide { max-width: 720px; width: min(720px, 94vw); }   /* a real size, like wide-xl — DsDetailModal size="wide" (no inline widths) */
.modal-head {
  display: flex; align-items: baseline; gap: 12px;
  padding: 16px 20px 14px;
  border-bottom: 1px solid var(--border);
}
.modal-head .eyebrow {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em;
}
.modal-head .title {
  font-family: var(--font-serif); font-weight: 500;
  font-size: 20px; letter-spacing: -0.015em;
}
.modal-head .id {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted); padding: 2px 6px;
  background: var(--surface-raised); border-radius: 4px;
}
.modal-body { padding: 18px 20px; overflow: auto; }
.modal-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 12px 20px; border-top: 1px solid var(--border);
  background: var(--surface);
}
.modal-foot .meta {
  margin-right: auto; font-size: 11.5px; color: var(--text-muted); align-self: center;
}
/* Foot is opaque and square; .modal rounds but does not clip (it hosts in-flow
 * row-expansions whose shadows must escape — so no overflow:hidden here). Round
 * the foot's bottom corners to the inner radius. APP-WIDE §8. */
.modal > .modal-foot {
  border-bottom-left-radius: calc(var(--r-md) - 1px);
  border-bottom-right-radius: calc(var(--r-md) - 1px);
}
.modal-close {
  margin-left: auto; appearance: none;
  background: transparent; border: 0;
  width: 28px; height: 28px;
  font: 600 18px/1 var(--font-sans);
  color: var(--text-muted); cursor: pointer;
  border-radius: 6px;
}
.modal-close:hover { background: var(--bg-hover); color: var(--text); }

/* ── Popover / dropdown ─────────────────────────────────────────────
 * .popover* RETIRED in DS-promotion wave 1 — the menu/dropdown/popover pattern
 * is now the single .ds-menu family (window.__GB.DsMenu). Its active/disabled/sub
 * rules were absorbed into .ds-menu-item. */

/* ── Toast ──────────────────────────────────────────────────────── */
.toast-stack {
  position: fixed; bottom: 24px; left: 50%;
  transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 8px;
  z-index: 200; pointer-events: none;
}
.toast-stack > .toast { pointer-events: auto; }
.toast {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px;
  min-width: 280px; max-width: 480px;
  background: var(--text);
  color: var(--bg);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-2);
  font-size: 13px;
}
.toast.success { background: var(--sage); color: #fff; }
.toast.info    { background: var(--text-2); color: var(--bg); }
.toast.amber   { background: var(--amber); color: #fff; }
.toast.danger  { background: var(--danger); color: #fff; }
.toast .toast-title { font-weight: 600; }
.toast .toast-undo {
  background: transparent; border: 1px solid currentColor;
  color: inherit; padding: 2px 8px; border-radius: 3px;
  font: 500 12px/1 var(--font-sans); cursor: pointer;
}
.toast .toast-close {
  background: transparent; border: 0; color: inherit; cursor: pointer;
  font-size: 14px; opacity: 0.7; line-height: 1; padding: 0 2px;
}
.toast .toast-close:hover { opacity: 1; }

/* Inline save state (header) */
.save-state {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--text-muted); font-size: 12px;
}
.save-state .dot-saved { width: 6px; height: 6px; border-radius: 50%; background: var(--sage); }
.save-state .dot-dirty { width: 6px; height: 6px; border-radius: 50%; background: var(--amber); }
.save-state .dot-error { width: 6px; height: 6px; border-radius: 50%; background: var(--danger); }

/* ── Banner ─────────────────────────────────────────────────────── */
.banner {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  font-size: 12.5px;
}
.banner.accent { background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent); }
.banner.amber  { background: var(--amber-soft);  border: 1px solid var(--amber);  color: var(--text); }   /* ink text: amber on --amber-soft fails 4.5:1 (design_handoff_inspector_parts) — the hue stays on the border + a leading icon */
.banner.sage   { background: var(--sage-soft);   border: 1px solid var(--sage);   color: var(--sage); }
.banner.danger { background: var(--danger-soft); border: 1px solid var(--danger); color: var(--danger); }
.banner .banner-title { font-weight: 600; }
.banner .banner-body  { color: var(--text-2); font-size: 11.5px; line-height: 1.5; margin-top: 2px; }
.banner.amber > svg { color: color-mix(in oklch, var(--amber), black 28%); flex: none; }
/* --accent on --amber-soft is ~3.6:1; links go ink + underline, accent on hover only. */
.banner.amber a { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.banner.amber a:hover { color: var(--accent); }
@media (prefers-color-scheme: dark) {
  .banner.amber > svg { color: #e8b866; }
}

/* Offline banner — full-width strip */
.banner-strip {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 20px;
  background: var(--danger-soft); color: var(--danger);
  border-bottom: 1px solid color-mix(in oklch, var(--danger), transparent 70%);
  font-size: 12px;
}
.banner-strip .msg { flex: 1; }

/* Month-close banner (left-border accent, list-row style) */
.banner-row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 16px;
  background: var(--amber-soft);
  border-left: 3px solid var(--amber);
  font-size: 13px;
}
.banner-row.closed {
  background: var(--surface);
  border-left-color: var(--border);
  color: var(--text-muted);
}
.banner-row .body { flex: 1; min-width: 0; }

/* ── Tables ─────────────────────────────────────────────────────── */
/* .tbl — borderless (settings tables). One cell vocabulary with .tbl-card
 * (design_handoff_table, Part 2): 10.5px sans 600 .06em header labels, 10px row
 * rhythm, mono tabular figures. Horizontal inset differs on purpose: 10 on a
 * borderless table, 14 inside a card edge. */
.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
.tbl th {
  text-align: left; padding: 9px 10px 10px;
  font-size: 10.5px; font-weight: 600; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.06em;
  border-bottom: 1px solid var(--border);
}
.tbl th.num { text-align: right; }
.tbl td {
  padding: 10px 10px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.tbl tr:last-child td { border-bottom: 0; }
/* Figures are mono tabular (GUIDELINE: "all figures mono tabular"). Scoped to
 * td: a th.num is a column LABEL, and labels stay sans. */
.tbl td.num, .tbl-card td.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }

/* .tbl-card — bordered card table (DsTable card: roster, /people, /team). The
 * one card table — the old .tablecard > table.dir (defined twice, in people.css
 * AND team.css, with different values) is retired. */
.tbl-card {
  width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px;
  background: var(--surface-elev); border: 1px solid var(--border);
  border-radius: var(--r-md); overflow: hidden;
}
.tbl-card th {
  text-align: left; padding: 10px 14px; font-weight: 600;
  color: var(--text-muted); background: var(--surface-raised);
  border-bottom: 1px solid var(--border);
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em;
}
.tbl-card th.num { text-align: right; }
.tbl-card td { padding: 10px 14px; border-bottom: 1px solid var(--border); }
.tbl-card tr:last-child td { border-bottom: none; }
.tbl-card tr.total-row td { font-weight: 600; border-top: 1px solid var(--border-hover); }
/* every row tints on hover — directory rows aren't clickable but carry actions,
 * and the tint says which row the (revealed) actions belong to */
.tbl-card tbody tr:hover td { background: var(--bg-hover); }
.tbl-card tbody tr.clickable { cursor: pointer; }   /* DsTable onRowClick */
/* DsTable fullRow: one cell spanning every column (an inline edit form) */
.tbl-card td.full-row, .tbl td.full-row { padding: 12px; }
.tbl-card tbody tr:has(> td.full-row):hover td { background: transparent; }

/* The scroll wrapper — DsTable card always renders it. .tbl-card clips to its
 * radius (overflow:hidden), so a wide table was cut off; the wrapper owns the
 * card chrome + horizontal scroll and the table inside drops its own border.
 * position:relative is load-bearing: it makes the wrapper the containing block
 * for absolutely-positioned descendants (the .sr-only "Actions" header label),
 * so overflow-x clips them instead of letting them widen the page. */
.tbl-card-wrap { position: relative; background: var(--surface-elev); border: 1px solid var(--border);
  border-radius: var(--r-md); overflow-x: auto; }
.tbl-card-wrap > .tbl-card { border: 0; border-radius: 0; overflow: visible; }

/* Row actions — one rule for choosing: icon-only clusters → .reveal (hidden
 * until the row is hovered or the cluster takes focus); text buttons → always
 * visible, no modifier. (hover: none) shows revealed actions on touch devices,
 * where they were invisible but still tappable. */
.tbl-card .col-actions { width: 1%; text-align: right; white-space: nowrap; }
.tbl-card .row-actions { display: inline-flex; gap: 4px; justify-content: flex-end; }
.tbl-card .row-actions.reveal { opacity: 0; transition: opacity .12s; }
.tbl-card tbody tr:hover .row-actions.reveal,
.tbl-card .row-actions.reveal:focus-within { opacity: 1; }
@media (hover: none) { .tbl-card .row-actions.reveal { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .tbl-card .row-actions.reveal { transition: none; } }

/* ── Bars / cost atoms ──────────────────────────────────────────── */


/* ── Forecast atoms — booked (actual) vs projected (forecast) ──────────
   The canonical money-over-time encoding for any surface with a `today`
   divide between settled and projected money (Cashflow, Forecast,
   Allocation footer, burn charts).
   RULE — texture encodes time-state, not hue:
     · SOLID  fill = ACTUAL / booked (settled past)
     · HATCHED fill = FORECAST / committed (projected future)
     · a 2px var(--accent) `.now-rule` always sits on the divide
     · always render the legend when both appear together
   Drive the series hue with --series (defaults to ink var(--text-2)).
   Emphasis-inverted variant: add .invert to the wrapper to swap fills
   (solid forecast) for a forward-looking surface; legend then mandatory. */
.phase-band {
  display: flex; height: 17px; border-radius: 4px; overflow: hidden;
  border: 1px solid var(--border);
  font-family: var(--font-mono); font-size: 8.5px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
}
.phase-band .seg { display: flex; align-items: center; padding: 0 9px; white-space: nowrap; color: var(--text-muted); }
.phase-band .seg.actual { background: var(--surface-raised); }
.phase-band .seg.forecast { justify-content: flex-end; background: var(--surface-elev);
  background-image: repeating-linear-gradient(135deg, color-mix(in oklch, var(--text-faint), transparent 62%) 0 3px, transparent 3px 7px); }
.invert .phase-band .seg.actual { background: var(--surface-elev);
  background-image: repeating-linear-gradient(135deg, color-mix(in oklch, var(--text-faint), transparent 62%) 0 3px, transparent 3px 7px); }
.invert .phase-band .seg.forecast { background: var(--accent); color: #fff; background-image: none; }

/* the today divider — drop into any position:relative timeline */
.now-rule { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--accent); border-radius: 2px; }

/* bar fills — put on the <i> inside a track; set --series for hue */
.fill-actual   { background: var(--series, var(--text-2)); }
.fill-forecast {
  background: color-mix(in oklch, var(--surface-elev), var(--series, var(--text-2)) 30%);
  background-image: repeating-linear-gradient(135deg, var(--series, var(--text-2)) 0 2px, transparent 2px 5px);
}
.invert .fill-actual {
  background: color-mix(in oklch, var(--surface-elev), var(--series, var(--text-2)) 30%);
  background-image: repeating-linear-gradient(135deg, var(--series, var(--text-2)) 0 2px, transparent 2px 5px);
}
.invert .fill-forecast { background: var(--series, var(--text-2)); background-image: none; }

/* split-share bar — one total split into booked vs forecast */
.splitbar { display: flex; height: 6px; border-radius: 3px; overflow: hidden; background: var(--surface-raised); }
.splitbar .seg { height: 100%; }
.splitbar .seg.booked { background: var(--series, var(--text-2)); }
.splitbar .seg.forecast {
  background: color-mix(in oklch, var(--surface-elev), var(--series, var(--text-2)) 22%);
  background-image: repeating-linear-gradient(135deg, var(--series, var(--text-2)) 0 2px, transparent 2px 5px);
}
.invert .splitbar .seg.booked {
  background: color-mix(in oklch, var(--surface-elev), var(--series, var(--text-2)) 22%);
  background-image: repeating-linear-gradient(135deg, var(--series, var(--text-2)) 0 2px, transparent 2px 5px);
}
.invert .splitbar .seg.forecast { background: var(--series, var(--text-2)); background-image: none; }

/* state tags — extend .badge */
.badge.actual   { background: var(--surface-raised); color: var(--text-muted); }
.badge.forecast { background: var(--accent-soft);    color: var(--accent); }
.invert .badge.forecast { background: var(--accent); color: #fff; }

/* legend swatches (12×11) */
.fc-key { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-muted); }
.fc-key i { width: 12px; height: 11px; border-radius: 3px; flex: none; }

/* horizontally-scrollable "pick one of many" pill switcher */
.pill-switch { display: flex; gap: 5px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin; padding-bottom: 5px; }
.pill-switch::-webkit-scrollbar { height: 6px; }
.pill-switch::-webkit-scrollbar-thumb { background: var(--border-hover); border-radius: 3px; }
.pill-switch > button {
  flex: none; max-width: 176px; display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 12px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface-elev);
  font: 600 11px var(--font-sans); color: var(--text-muted); cursor: pointer;
}
.pill-switch > button > span:not(.sub) { flex: none; }
.pill-switch > button .sub { font-weight: 500; color: var(--text-muted); min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pill-switch > button.on { background: var(--text); border-color: var(--text); color: var(--surface); }
.pill-switch > button.on .sub { color: color-mix(in oklch, var(--surface), var(--text) 32%); }

/* ── States ─────────────────────────────────────────────────────── */
.state-empty {
  padding: 16px; font-size: 13px; color: var(--text-muted);
  text-align: center; border: 1px dashed var(--border); border-radius: var(--r-sm);
}
.state-loading {
  padding: 32px; font-size: 13px; color: var(--text-muted);
  text-align: center; line-height: 1.5;
}
.state-error {
  padding: 12px 14px; border-radius: var(--r-sm);
  background: var(--danger-soft); color: var(--danger);
  font-size: 13px;
  border-left: 3px solid var(--danger);
}


/* ============================================================================
 * §10 — Sub-tab alignment: canonical .ds-* component set
 * ----------------------------------------------------------------------------
 * Merged from the 2026-06-01 design handoff (canonical-components.css). One
 * canonical implementation of the metric strip / container / toolbar / sub-tab
 * header / sticky-matrix table that Costs & Resourcing previously reinvented
 * per sub-tab. Every value resolves from tokens.css (no new tokens).
 *
 * Replaces, across Costs & Resourcing sub-tabs:
 *   cf-kpi / po2-stat / ag-cp-kpi / alw-kpi   -> .ds-metricbar / .ds-metric
 *   ag-ov-card / ad-hoc boxes                 -> .ds-card
 *   ag-cp-toolbar / alw-toolbar               -> .ds-toolbar
 *   po2-rollup head / cf-advisor head / etc.  -> .ds-subhead
 *   alw-grid / cost-table / tbl(+total-row)   -> .ds-table (sticky-matrix)
 *
 * Visual spec: GET /design-system §10. Live table: GET /design-system/data-table.
 * ========================================================================== */


/* ── METRIC BAR — one component for every headline-number strip ──────────────
 * Hairline-segmented (no card gaps, no shadow), mono tabular numerals.
 * Set grid-template-columns inline per use, e.g.
 *   <div class="ds-metricbar" style="grid-template-columns:repeat(4,1fr) 150px">
 */
.ds-metricbar {
  display: grid; gap: 1px;
  background: var(--border);                 /* 1px hairline dividers */
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;                          /* flat — intentionally NO shadow */
}
.ds-metric {
  background: var(--surface-elev);
  padding: 14px 16px;
  display: flex; flex-direction: column; gap: 5px;
}
.ds-metric-l {
  font: 500 10.5px/1.2 var(--font-mono);
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-muted);
  display: flex; align-items: center; gap: 6px;
}
.ds-metric-v {
  font: 500 25px/1.05 var(--font-mono);
  letter-spacing: -.01em; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.ds-metric-v.over  { color: var(--accent); }   /* breach: over budget / at-risk — terracotta, not red (§17) */
.ds-metric-v.under { color: var(--sage); }      /* under / healthy */
.ds-metric-v.warn  { color: color-mix(in oklch, var(--amber), black 28%); }   /* caution — raw amber is 2.52:1 on white; −28% = the .ds-pill.is-warn ink (5.62) */
.ds-metric-of { font-size: 15px; color: var(--text-muted); }  /* de-emphasized secondary figure, e.g. "12 / 17" */
.ds-metric-s { font-size: 11.5px; color: var(--text-muted); }
.ds-metric-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--av-c5); display: inline-block; flex: none; }
.ds-metric.clickable { cursor: pointer; transition: background .12s; }
.ds-metric.clickable:hover { background: var(--surface-raised); }
.ds-metric.spark { justify-content: flex-start; }

/* ── COMPACT density (.ds-metricbar.compact) — modal subhead / detail pane ────
 * <DsMetricBar compact>. What the retired .ds-statstrip was, expressed as one
 * modifier: same container, smaller cell (design_handoff_statstrip). Figures
 * stay mono + tabular (serif is for titles, never data). 19px/500 is NOT large
 * text, so a tone needs 4.5:1 on --surface-elev: accent 4.54 ✓ · raw sage 4.50
 * (edge) → darkened 8% (5.43). Compact cells are never .clickable — the hover
 * fill (--surface-raised) drops a 19px accent figure to 3.81. */
.ds-metricbar.compact .ds-metric   { padding: 11px 13px; gap: 3px; }
.ds-metricbar.compact .ds-metric-l { font: 600 9.5px/1.2 var(--font-mono); letter-spacing: .06em; }
.ds-metricbar.compact .ds-metric-v { font-size: 19px; line-height: 1; }
.ds-metricbar.compact .ds-metric-of { font-size: 12.5px; }
.ds-metricbar.compact .ds-metric-s { font-size: 10.5px; }
.ds-metricbar.compact .ds-metric-v.over  { color: var(--accent); }
.ds-metricbar.compact .ds-metric-v.under { color: color-mix(in oklch, var(--sage), black 8%); }
/* breach caption — DsMetric subTone="over", both densities. Part of the
 * "over-cap shows in three places" rule (§17); flex so a leading glyph aligns. */
.ds-metric-s.is-over { color: var(--accent); font-weight: 600; display: flex; align-items: center; gap: 4px; }
@media (prefers-color-scheme: dark) {      /* black-mixes are light-only: same lightened inks as the pills (design_handoff_dark_mode) */
  .ds-metric-v.warn { color: #e8b866; }
  .ds-metricbar.compact .ds-metric-v.under { color: #9ccb88; }
  .ds-metricbar.compact .ds-metric-v.over  { color: #f2a97f; }
}


/* ── SUB-TAB HEADER — eyebrow · serif title · sub · actions ──────────────────
 * <div class="ds-subhead">
 *   <div class="ds-subhead-txt"> eyebrow / title / sub </div>
 *   <button class="btn accent">…</button>          (use canonical .btn)
 * </div>
 */
.ds-subhead {                /* COMPACT default — one row, no redundant eyebrow */
  display: flex; align-items: center; gap: 12px;
  min-height: 34px;          /* parity: pin to the 32px .ds-search control so text-only
                                heads (Overview) match filter-bearing heads — constant 59px band */
  flex-wrap: nowrap;         /* keep crowded heads (Schedule: density seg + Today + filter)
                                on one row so they never wrap to a taller band */
}
.ds-subhead-txt { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 10px; flex-wrap: nowrap; overflow: hidden; }
.ds-subhead-eyebrow {        /* shown by default — section eyebrow (2026-06-08 chrome+headers handoff) */
  display: inline;
  font: 600 10px/1.2 var(--font-mono);
  text-transform: uppercase; letter-spacing: .12em; color: var(--text-muted);
}
.ds-subhead-title {
  font-family: var(--font-serif); font-weight: 600; font-size: 18px;
  letter-spacing: -.01em; color: var(--text);
}
.ds-subhead-sub { font: 400 11px var(--font-mono); color: var(--text-muted);   /* sits inline after the title */
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }  /* ellipsize, never wrap → constant band height even when controls crowd the row */

/* opt-in tall/stacked header for landing or section pages that need presence */
.ds-subhead.is-hero { align-items: flex-end; padding-bottom: 14px; border-bottom: 1px solid var(--border); }
.ds-subhead.is-hero .ds-subhead-txt { flex-direction: column; gap: 3px; }
.ds-subhead.is-hero .ds-subhead-eyebrow { display: block; }
.ds-subhead.is-hero .ds-subhead-title { font-size: 21px; letter-spacing: -.015em; }


/* ── TOOLBAR — search · filter chips · trailing count ───────────────────────
 * Uses canonical .input-sunk for the search if available; .ds-search is a
 * thin wrapper kept here for parity with the existing alw/ag toolbars.
 */
.ds-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ds-search {
  display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 10px;
  min-width: 200px; background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text-muted);
}
.ds-search:focus-within { border-color: var(--border-hover); box-shadow: 0 0 0 3px var(--accent-soft); }
.ds-search input { border: none; background: none; outline: none; font: inherit; font-size: 12.5px; color: var(--text); width: 100%; }
.ds-chip {
  height: 28px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--surface-elev); color: var(--text-2); font-size: 12px; font-weight: 500; cursor: pointer;
}
.ds-chip.on { background: var(--text); border-color: var(--text); color: var(--surface); }
.ds-toolbar-count { margin-left: auto; font-size: 11.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* ── §15 · UNIFIED SEARCH & FILTER BAR ──────────────────────────────────────
 * The ONE filtering model (§15). Retires the per-surface forks (.po2-rail-search,
 * the .input-sunk search variant, .alw-free-pill, .inspector-assign-typeahead-input,
 * raw <input type="search">). Reuses .ds-search / .ds-chip / .ds-menu unchanged.
 * Component: static/ds-filterbar.jsx (window.__DS.FilterBar). tokens.css vars only. */
/* The row itself. Read left->right: find -> narrow -> (overflow + count). */
.ds-filterbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* search add-ons: clear button + "/" keyboard hint */
.ds-search-clear { flex: none; border: 0; background: none; cursor: pointer; color: var(--text-muted);
  font-size: 15px; line-height: 1; padding: 0 2px; }
.ds-search-clear:hover { color: var(--text); }
.ds-search-kbd { flex: none; font-family: var(--font-mono); font-size: 11px; color: var(--text-muted);
  border: 1px solid var(--border); border-radius: 4px; padding: 0 5px; line-height: 16px; }

/* inline facet group: a labelled cluster of .ds-chip toggles */
.ds-facetgroup { display: inline-flex; align-items: center; gap: 6px; padding-left: 8px; border-left: 1px solid var(--border); }
.ds-facetgroup:first-child { padding-left: 0; border-left: 0; }
.ds-facetgroup-label { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted); font-weight: 600; }
.ds-chip .ds-swatch { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.ds-chip.on .ds-swatch { box-shadow: 0 0 0 1px rgba(255,255,255,.4); }
/* optional per-option count inside a mirrored chip (FilterBar group mirrorCounts) */
.ds-chip-n { margin-left: 6px; font-family: var(--font-mono); font-size: 10.5px; font-weight: 600;
  font-variant-numeric: tabular-nums; opacity: .7; }

/* funnel: its own icon-only control in the bar; the result-count anchor.
 * Idle = plain neutral icon, no number. Filtering -> accent + the live match
 * count absorbed into the icon. No standalone "N of M" text anywhere. */
.ds-funnel { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px;
  border-radius: 999px; border: 1px solid var(--border); background: var(--surface-elev);
  color: var(--text-muted); cursor: pointer; }
.ds-funnel:hover { border-color: var(--border-hover); color: var(--text-2); }
.ds-funnel.on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.ds-funnel-count { font-family: var(--font-mono); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }
.ds-funnel-pop { position: fixed; z-index: 1000; }  /* portaled to <body>; coords set inline by FilterBar */

/* menu-item embellishments used inside the funnel popover */
.ds-menu-check { width: 13px; flex: none; color: var(--accent); font-size: 12px; }
.ds-menu-swatch { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.ds-menu-count { margin-left: auto; font-family: var(--font-mono); font-size: 10.5px; color: var(--text-muted); }

/* ── CONTENT CARD — one shell for charts / tables / breakdowns ───────────────
 * Rule of thumb: numbers -> .ds-metricbar (segmented). Everything else (a
 * chart, a table, a by-section breakdown) -> .ds-card. No ad-hoc boxes.
 */
.ds-card {
  background: var(--surface-elev); border: 1px solid var(--border);
  border-radius: var(--r-md); overflow: hidden;
}
.ds-card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.ds-card-title { font-size: 13px; font-weight: 600; color: var(--text); }
.ds-card-meta { font: 400 10.5px/1.3 var(--font-mono); color: var(--text-muted); }
.ds-card-body { padding: 14px 16px; }


/* ════════════════════════════════════════════════════════════════════════════
 * DATA TABLE — sticky-matrix (header + footer + first column + summary column)
 * ----------------------------------------------------------------------------
 * Replaces alw-grid / cost-table / tbl. Pins FOUR edges + the four corners
 * with one scroll container. Markup contract + the ~10-line scroll-shadow JS
 * are in the design handoff README §6 and GET /design-system §10.
 * ════════════════════════════════════════════════════════════════════════════ */

/* The card fills remaining height. flex:1 + min-height:0 is what gives the
   inner scroller a real bounded height — NO magic max-height, NO nesting it
   inside another overflow:auto pane. */
.ds-table {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  background: var(--surface-elev);
  border: 1px solid var(--border); border-radius: var(--r-md);
  overflow: hidden; box-shadow: var(--shadow-1);
}

/* The one-and-only scroll container — owns BOTH axes. Scrollbars stay VISIBLE. */
.ds-table-scroll {
  flex: 1; min-height: 0;
  overflow: auto; overscroll-behavior: contain;
}
.ds-table-scroll::-webkit-scrollbar { width: 12px; height: 12px; }
.ds-table-scroll::-webkit-scrollbar-thumb {
  background: var(--border-hover); border-radius: 7px;
  border: 3px solid var(--surface-elev); background-clip: padding-box;
}
.ds-table-scroll::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
.ds-table-scroll::-webkit-scrollbar-corner { background: var(--surface-elev); }

/* table-layout:fixed keeps thead/tbody/tfoot columns aligned automatically.
   border-collapse:SEPARATE is REQUIRED — `collapse` drops borders on sticky
   cells in most browsers. Gridlines are drawn per-cell via inset box-shadow. */
table.ds-tbl {
  border-collapse: separate; border-spacing: 0;
  table-layout: fixed; width: max-content; min-width: 100%;
  font-variant-numeric: tabular-nums;
}
table.ds-tbl th, table.ds-tbl td {
  box-shadow: inset -1px 0 0 var(--border-faint), inset 0 -1px 0 var(--border-faint);
  background: var(--surface-elev);   /* OPAQUE — or scrolled cells bleed through sticky ones */
  vertical-align: middle;
}
/* GOTCHA: any STATEFUL row background (hover, selected, zebra-stripe) must be
   OPAQUE on the sticky cells too. A translucent tint (e.g. tokens.css
   --bg-hover) lets the scrolling row show through the frozen columns. For the
   frozen first/last columns, blend an opaque hover instead. */

/* The sticky edges. z-ladder: corners(30) > header/footer(20) > side cols(10). */
.ds-stick-h { position: sticky; top: 0;    z-index: 20; }
.ds-stick-f { position: sticky; bottom: 0; z-index: 20; }
.ds-stick-l { position: sticky; left: 0;   z-index: 10; }
.ds-stick-r { position: sticky; right: 0;  z-index: 10; }
.ds-stick-h.ds-stick-l, .ds-stick-h.ds-stick-r,
.ds-stick-f.ds-stick-l, .ds-stick-f.ds-stick-r { z-index: 30; }   /* four corners */

/* header / footer need solid 1px dividers against the body (the faint per-cell
   gridline isn't enough where the frozen pane meets scrolling content). */
.ds-stick-h { box-shadow: inset -1px 0 0 var(--border-faint), inset 0 -1px 0 var(--border); }
.ds-stick-f { box-shadow: inset -1px 0 0 var(--border-faint), inset 0  1px 0 var(--border); }

/* Scroll-shadow affordance — gradient appears on a frozen edge only when there
   is more content hidden under it. JS toggles .atL/.atR/.atT/.atB on the
   scroll container at the extremes. Requires the sticky cells to be positioned
   (they are, via position:sticky). */
.ds-stick-l::after, .ds-stick-r::before, .ds-stick-h::after, .ds-stick-f::before {
  content: ""; position: absolute; pointer-events: none; opacity: 0; transition: opacity .15s;
}
.ds-stick-l::after  { top: 0; bottom: 0; right: -14px; width: 14px;  background: linear-gradient(90deg,  rgba(26,24,20,.10), transparent); }
.ds-stick-r::before { top: 0; bottom: 0; left:  -14px; width: 14px;  background: linear-gradient(270deg, rgba(26,24,20,.10), transparent); }
.ds-stick-h::after  { left: 0; right: 0; bottom: -14px; height: 14px; background: linear-gradient(180deg, rgba(26,24,20,.10), transparent); }
.ds-stick-f::before { left: 0; right: 0; top:    -14px; height: 14px; background: linear-gradient(0deg,   rgba(26,24,20,.10), transparent); }
.ds-table-scroll:not(.atL) .ds-stick-l::after  { opacity: 1; }
.ds-table-scroll:not(.atR) .ds-stick-r::before { opacity: 1; }
.ds-table-scroll:not(.atT) .ds-stick-h::after  { opacity: 1; }
.ds-table-scroll:not(.atB) .ds-stick-f::before { opacity: 1; }

/* ════════════════════════════════════════════════════════════════════════════
 * PAGE SCAFFOLD (.ds-page) — the layer above components
 * ----------------------------------------------------------------------------
 * One page skeleton every Costs & Resourcing sub-tab fills. Fixed head
 * (.ds-subhead -> .ds-metricbar -> .ds-toolbar) over a single scrolling body.
 * Replaces per-tab roots: .cf-pane / .po2-body+.po2-scroll / .alw-main /
 * .cost-table-wrap. See Design System.html §11.
 * ════════════════════════════════════════════════════════════════════════════ */
.ds-page {                  /* fills the tab host */
  height: 100%; min-height: 0;
  display: flex; flex-direction: column;
  background: var(--bg);
}
.ds-page-head {             /* FIXED region — does not scroll */
  flex-shrink: 0;
  padding: 12px 24px;
  display: flex; flex-direction: column; gap: 10px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.ds-page-body {             /* the ONE scroll region */
  flex: 1; min-height: 0; overflow: auto;
  padding: 20px 24px;
  display: flex; flex-direction: column; gap: 16px;
}

/* variant — page is a single .ds-table: the table owns the scroll, body doesn't
   (prevents the double-scrollbar trap that plagues the current grids). */
.ds-page.is-table .ds-page-body { padding: 16px 24px 24px; overflow: hidden; }
.ds-page.is-table .ds-table { flex: 1; min-height: 0; }

/* variant — narrow reading / form column */
.ds-page-body.is-narrow { align-items: center; }
.ds-page-body.is-narrow > * { width: 100%; max-width: 720px; }


/* ════════════════════════════════════════════════════════════════════════════
 * CROSS-PAGE PARITY (§12) — the frame contract every page shares
 * ----------------------------------------------------------------------------
 * Components (§10) + scaffold (§11) weren't enough: pages still drifted on
 * title, background, width, figure font, radius. These rules make the FRAME
 * identical; only the body archetype may vary. See Design System.html §12.
 * ════════════════════════════════════════════════════════════════════════════ */
:root { --page-max: 1320px; }   /* default content cap; .is-wide opts out */

/* width policy — content centers and caps; matrix / master-detail opt full-bleed */
.ds-page-body > * { width: 100%; max-width: var(--page-max); margin-inline: auto; flex-shrink: 0; }
.ds-page.is-wide .ds-page-body > * { max-width: none; }

/* figures — the one rule that kills serif-vs-mono drift in detail panes/footers.
   Apply .ds-figure to every $ / hours / count value, headline OR secondary. */
.ds-figure { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* one radius for every framed surface (retire hardcoded 12 / 10 / r-lg mix) */
.ds-rail, .ds-block { border-radius: var(--r-md); }

/* ── list-rail (§16): one card-framed pick list ───────────────────────────────
   Default = a floating, bordered, rounded card (STANDALONE list: page padding
   around it — e.g. Cashflow supplier list, Allocation roster). In a MASTER-DETAIL
   split-pane (e.g. Commitments → Orders) the consumer scopes the rail to full-bleed
   with a crisp right divider + squared corners instead — both layouts are sanctioned;
   don't force the standalone card framing onto a split-pane. */
.ds-rail { border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-elev); display: flex; flex-direction: column; overflow: hidden; }
.ds-rail-head { padding: 9px 12px; border-bottom: 1px solid var(--border); background: var(--surface); }
.ds-rail-head-top { display: flex; align-items: center; gap: 8px; }
.ds-rail-title { font-size: 12px; font-weight: 600; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-rail-sort { margin-top: 8px; display: flex; }
.ds-rail-sort.seg { width: 100%; }
.ds-rail-sort.seg button { flex: 1; }
.ds-rail-add { margin-left: 6px; }
.ds-rail-hint { font-size: 10px; color: var(--text-muted); margin-top: 5px; }
.ds-rail-list { flex: 1; min-height: 0; overflow: auto; padding: 7px; display: flex; flex-direction: column; gap: 5px; }
.ds-rail-row { display: flex; align-items: center; gap: 9px; padding: 8px 9px;
  border: 1px solid transparent; border-radius: var(--r-sm); cursor: pointer; }
.ds-rail-row:hover { background: var(--surface); border-color: var(--border); }
.ds-rail-row.sel { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); background: var(--surface-elev); }

/* ============================================================================
 * §13 — Shared interaction patterns
 * Added 2026-06. Promotes the controls each surface re-invented into ONE each.
 * Replaces: po2-status/.tag/status-pill (pill) · *-avatar/.avatar (avatar) ·
 * po2-link (linkchip) · dropdown/po2-menu/.popover (menu) · .dialog/po2-modal
 * (modal) · alw-pop/inspector-typeahead/.picker (picker) · alw-editor/po2-payedit
 * (editor) · alw-footer/cm-rail/dock (summary) · alw-sec/po2-tr.open (row-expand).
 * See Design System.html §13.
 * ========================================================================== */

/* ── status pill ─────────────────────────────────────────────────────── */
.ds-pill { display: inline-flex; align-items: center; gap: 5px; height: 21px; padding: 0 9px;
  border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap;
  font-variant-numeric: tabular-nums; background: var(--surface-raised); color: var(--text-2); }
.ds-pill-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.ds-pill.is-ok     { background: var(--sage-soft);   color: color-mix(in oklch, var(--sage), black 16%); }
.ds-pill.is-warn   { background: var(--amber-soft);  color: color-mix(in oklch, var(--amber), black 28%); }
.ds-pill.is-danger { background: var(--danger-soft); color: var(--danger); }
.ds-pill.is-over   { background: var(--accent-soft); color: var(--accent); }  /* breach: over cap / over budget / expired — terracotta, not red (§17) */
.ds-pill.is-over .ds-pill-dot { background: var(--accent); }
.ds-pill.is-info   { background: var(--blue-soft);   color: color-mix(in oklch, var(--blue), black 14%); }
/* Dark (design_handoff_dark_mode). Shifting ink toward black is a light-mode strategy: it puts
   readable ink on a pale tint. In dark the tint is a low-alpha wash of the SAME hue over near-black,
   so the identical declaration lands dark-on-dark (1.1-1.6:1). Lighten the ink; leave the tint alone
   (deepening it moves tint and ink together; re-deriving the hue tokens would move --accent
   everywhere). is-over/is-danger set the raw token and only looked fine on --bg — they failed on
   --surface-elev (a .tbl-card). Verify semantic colour on --surface-elev, not --bg. */
@media (prefers-color-scheme: dark) {
  .ds-pill.is-ok     { color: #9ccb88; }
  .ds-pill.is-warn   { color: #e8b866; }
  .ds-pill.is-info   { color: #95bdea; }
  .ds-pill.is-over   { color: #f2a97f; }
  .ds-pill.is-danger { color: #ee9d8a; }
  .ds-pill.is-over .ds-pill-dot { background: #f2a97f; }
}
.ds-pill.is-btn    { cursor: pointer; border: 0; font-family: inherit; }
.ds-pill.is-btn:hover { filter: brightness(0.96); }
/* absorbed from the retired color-named .pill (wave 2): */
.ds-pill.is-neutral { background: var(--surface-raised); color: var(--text-2); }
.ds-pill.is-dashed  { background: transparent; border: 1px dashed var(--border-hover); color: var(--text-muted); font-weight: 500; }
.ds-pill.is-solid   { background: var(--accent); color: #fff; border-radius: 4px; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }

/* ── avatar + stack — circular person tag · 8-hue oklch ramp ───────────── */
.ds-avatar { width: 26px; height: 26px; border-radius: 50%; flex: none; display: grid; place-items: center;
  color: #fff; font-family: var(--font-mono); font-size: 10.5px; font-weight: 600;
  background: var(--av-cn); border: 1.5px solid var(--surface); }
.ds-avatar.sm { width: 20px; height: 20px; font-size: 9px; }
.ds-avatar.lg { width: 34px; height: 34px; font-size: 13px; }
.ds-avatar.xs { width: 18px; height: 18px; font-size: 9px; border-width: 1px; }  /* absorbed from .avatar (wave 1) */
/* 8-hue ramp + warm gray — hue = hash(personId) % 8; cn = "not a person" */
.ds-avatar.c0 { background: var(--av-c0); }  /* cobalt */
.ds-avatar.c1 { background: var(--av-c1); }  /* rose   */
.ds-avatar.c2 { background: var(--av-c2); }  /* moss   */
.ds-avatar.c3 { background: var(--av-c3); }  /* amber  */
.ds-avatar.c4 { background: var(--av-c4); }  /* violet */
.ds-avatar.c5 { background: var(--av-c5); }  /* teal   */
.ds-avatar.c6 { background: var(--av-c6); }  /* coral  */
.ds-avatar.c7 { background: var(--av-c7); }  /* slate  */
.ds-avatar.cn { background: var(--av-cn); }  /* warm gray */
/* external (supplier-sourced) — dashed ring, hue unchanged */
.ds-avatar.ext { border-style: dashed; border-color: var(--border-hover); }
/* unassigned placeholder — transparent, dashed ring */
.ds-avatar.dashed { background: transparent; border: 1.5px dashed var(--border-hover); color: var(--text-muted); }
/* interactive avatar — DsAvatar asButton (e.g. gantt overflow chip); resets the <button> chrome */
button.ds-avatar { cursor: pointer; padding: 0; }
button.ds-avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ds-avstack { display: inline-flex; }
.ds-avstack > .ds-avatar { box-shadow: 0 0 0 2px var(--surface-elev); }
.ds-avstack > .ds-avatar + .ds-avatar { margin-left: -8px; }

/* ── link / reference chip ───────────────────────────────────────────── */
.ds-linkchip { display: inline-flex; align-items: center; gap: 6px; height: 23px; padding: 0 9px;
  border-radius: 6px; background: var(--surface); border: 1px solid var(--border);
  font-size: 11.5px; color: var(--text-2); cursor: pointer; }
.ds-linkchip:hover { border-color: var(--border-hover); }

/* ── menu / popover surface ──────────────────────────────────────────── */
.ds-menu { background: var(--surface-elev); border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: var(--shadow-pop); padding: 5px; min-width: 200px; }
.ds-menu-cap { font-family: var(--font-mono); font-size: 9.5px; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .07em; padding: 7px 9px 4px; }
.ds-menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 9px;
  border-radius: var(--r-sm); font: inherit; font-size: 12.5px; color: var(--text);
  background: none; border: 0; text-align: left; cursor: pointer; }
.ds-menu-item:hover { background: var(--bg-hover); }
.ds-menu-item.active { background: var(--accent-soft); color: var(--text); }    /* absorbed from .popover (wave 1) */
.ds-menu-item.disabled { opacity: 0.5; pointer-events: none; }                   /* absorbed from .popover (wave 1) */
.ds-menu-item .sub { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); margin-left: auto; } /* absorbed from .popover (wave 1) */
.ds-menu-sep { height: 1px; background: var(--border); margin: 5px 2px; }

/* ── modal / dialog ──────────────────────────────────────────────────── */
.ds-modal-backdrop { position: fixed; inset: 0; background: var(--backdrop); z-index: 40; display: grid; place-items: center; }
.ds-modal { width: min(560px, 92vw); max-height: 86vh; background: var(--surface-elev);
  border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-pop);
  display: flex; flex-direction: column; overflow: hidden; }
.ds-modal-head { padding: 16px 20px 13px; border-bottom: 1px solid var(--border); display: flex; align-items: flex-start; gap: 12px; }
.ds-modal-title { flex: 1; font-family: var(--font-serif); font-weight: 500; font-size: 18px; color: var(--text); }
.ds-modal-body { padding: 16px 20px; overflow: auto; }
.ds-modal-foot { padding: 13px 20px; border-top: 1px solid var(--border); display: flex; gap: 9px; justify-content: flex-end; background: var(--surface); }

/* ── entity picker / typeahead (built on the menu surface) ───────────── */
.ds-picker { display: flex; flex-direction: column; min-width: 260px; max-width: 320px; }
.ds-picker-search { display: flex; align-items: center; gap: 7px; height: 32px; margin: 2px; padding: 0 10px;
  background: var(--surface-raised); border: 1px solid var(--border); border-radius: var(--r-sm); color: var(--text-muted); }
.ds-picker-search input { flex: 1; min-width: 0; border: 0; background: none; outline: none; font: inherit; font-size: 12.5px; color: var(--text); }
.ds-picker-list { max-height: 300px; overflow: auto; padding: 4px; }
.ds-picker-row { display: flex; align-items: center; gap: 10px; padding: 7px 8px; border-radius: var(--r-sm); cursor: pointer; }
.ds-picker-row:hover { background: var(--bg-hover); }
.ds-picker-main { min-width: 0; flex: 1; }
.ds-picker-nm { font-size: 12.5px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-picker-rl { font-size: 10.5px; color: var(--text-muted); }
.ds-picker-end { margin-left: auto; flex: none; }

/* ── weighted-list editor (rows/sliders that sum to a total) ─────────── */
.ds-editor { display: flex; flex-direction: column; }
.ds-editor-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-top: 1px dashed var(--border); }
.ds-editor-row:first-child { border-top: 0; }
.ds-editor-main { min-width: 0; flex: 1; }
.ds-editor-nm { font-size: 12.5px; font-weight: 600; color: var(--text); }
.ds-editor-meta { font-size: 10.5px; color: var(--text-muted); font-family: var(--font-mono); }
.ds-editor-slider { width: 120px; accent-color: var(--accent); }
.ds-editor-val { font-family: var(--font-mono); font-size: 12.5px; font-weight: 600; color: var(--text);
  min-width: 54px; text-align: right; font-variant-numeric: tabular-nums; }
.ds-editor-val-edit { display: inline-flex; align-items: center; justify-content: flex-end; gap: 2px; }
.ds-editor-num { width: 40px; font-family: var(--font-mono); font-size: 12.5px; font-weight: 600;
  color: var(--text); text-align: right; font-variant-numeric: tabular-nums; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r-sm); padding: 2px 4px; -moz-appearance: textfield; }
.ds-editor-num:focus { outline: none; border-color: var(--accent); }
.ds-editor-num::-webkit-outer-spin-button, .ds-editor-num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ds-editor-rm { appearance: none; border: 0; background: none; color: var(--text-muted); font-size: 15px; cursor: pointer; padding: 0 2px; }
.ds-editor-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  padding-top: 10px; margin-top: 2px; border-top: 1.5px solid var(--border); }
.ds-editor-total { font-family: var(--font-mono); font-weight: 600; font-size: 14px; color: var(--text); font-variant-numeric: tabular-nums; }

/* ── chip-metric (avatar + one mono metric) — Tasks board + month modal ───── */
.ds-chip-metric { display: inline-flex; align-items: center; gap: 5px; height: 22px;
  padding: 0 8px 0 2px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface-elev); flex: none; appearance: none; font-family: inherit; }
.ds-chip-metric .ds-avatar.sm { width: 17px; height: 17px; font-size: 7.5px; }
.ds-chip-metric-nm { font-size: 11px; font-weight: 600; color: var(--text-2); }
.ds-chip-metric-v { font: 600 10px var(--font-mono); color: var(--text-muted); font-variant-numeric: tabular-nums; }
button.ds-chip-metric { cursor: pointer; }
button.ds-chip-metric:hover { border-color: var(--accent); background: var(--accent-soft); }
button.ds-chip-metric:hover .ds-chip-metric-v { color: var(--accent); }
button.ds-chip-metric:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ── budget fields (CAPEX + OPEX inputs, derived read-only total) — BudgetFields ── */
.ds-budgetfields { display: flex; flex-direction: column; gap: 6px; }
.ds-budgetfields-head { font: 500 10.5px/1.2 var(--font-mono); text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); }
.ds-budgetfields-head.is-row { font: 400 11px var(--font-sans); text-transform: none; letter-spacing: 0; }
.ds-budgetfields-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; }
.ds-budgetfields-cell { display: flex; flex-direction: column; gap: 4px; }
.ds-budgetfields-sub { font: 500 10px var(--font-mono); letter-spacing: .06em; color: var(--text-muted); }
.ds-budgetfields-total { display: flex; align-items: baseline; gap: 8px; padding-top: 6px; border-top: 1px solid var(--border-faint); }
.ds-budgetfields-meta { font: 11px var(--font-mono); color: var(--text-muted); white-space: nowrap; }
.ds-budgetfields-fig { margin-left: auto; font: 500 12.5px var(--font-mono); font-variant-numeric: tabular-nums; color: var(--text); }
.ds-budgetfields-err { font-size: 11px; color: var(--danger); }

/* ── summary rail (totals + variance + mini bars) ────────────────────── */
.ds-summary { background: var(--surface-elev); border: 1px solid var(--border); border-radius: var(--r-md); padding: 14px 16px; }
.ds-summary-eyebrow { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); }
.ds-summary-total { font-family: var(--font-mono); font-size: 26px; font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; margin-top: 4px; }
.ds-summary-var { font-family: var(--font-mono); font-size: 12px; font-weight: 600; margin-top: 2px; }
.ds-summary-var.is-pos { color: var(--danger); }   /* over plan */
.ds-summary-var.is-neg { color: var(--sage); }       /* under plan */
.ds-summary-bars { display: flex; flex-direction: column; gap: 9px; margin-top: 13px; }
.ds-summary-bar-top { display: flex; align-items: baseline; justify-content: space-between; font-size: 11px; margin-bottom: 4px; }
.ds-summary-bar-l { color: var(--text-2); }
.ds-summary-bar-v { font-family: var(--font-mono); color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* ── METER (.ds-meter) — one value against a maximum ──────────────────────────
 * <DsMeter>. Every progress / burn / load / utilization / share-of-largest bar
 * (design_handoff_meter) — replaces ~15 hand-built tracks at eight heights, four
 * track colours and three "over" colours. NOT for multi-segment bars
 * (.ds-budgetmeter, capex/opex mix, cashflow zones).
 * Fills are non-text that carries meaning → each needs 3:1 on the
 * --surface-raised track (WCAG 1.4.11). Light, measured: neutral --text-2 10.06 ·
 * ok sage 3.78 · info blue 4.17 · over accent 3.81 · warn: raw amber is 2.12 ✗,
 * so −28% (the .ds-pill.is-warn / .ds-metric-v.warn mix) → 4.72. */
.ds-meter { display: block; position: relative; width: 100%; height: 5px;
  border-radius: 999px; background: var(--surface-raised); overflow: hidden; }
.ds-meter > i { display: block; height: 100%; border-radius: inherit;
  background: var(--text-2); transition: width .2s ease-out; }
/* size — xs 3px hairline under a rail row · sm 5px inline/under a figure (default)
 * · md 8px a bar that IS the row */
.ds-meter.xs { height: 3px; }
.ds-meter.md { height: 8px; }
.ds-meter.is-ok   > i { background: var(--sage); }
.ds-meter.is-info > i { background: var(--blue); }
.ds-meter.is-warn > i { background: color-mix(in oklch, var(--amber), black 28%); }
/* over — value > max. Terracotta (a breach, §17) and it beats tone AND the inline
 * categorical `color`, hence the one !important in this block. */
.ds-meter.is-over > i { background: var(--accent) !important; }
@media (prefers-color-scheme: dark) {      /* black-mixes are light-only (design_handoff_dark_mode) */
  .ds-meter.is-warn > i { background: #e8b866; }
}
@media (prefers-reduced-motion: reduce) {
  .ds-meter > i { transition: none; }
}
/* a meter under a KPI figure (DsMetric track) keeps the figure's measure */
.ds-metric .ds-meter { max-width: 230px; margin-top: 2px; }

/* ── expandable row → detail drawer ──────────────────────────────────── */
.ds-row-expand { border-bottom: 1px solid var(--border); }
.ds-row-expand-head { display: flex; align-items: center; gap: 10px; padding: 10px 12px; cursor: pointer; }
.ds-row-expand-head:hover { background: var(--bg-hover); }
.ds-row-expand-chev { width: 10px; flex: none; color: var(--text-muted); transition: transform .15s; font-size: 10px; }
.ds-row-expand.is-open .ds-row-expand-chev { transform: rotate(90deg); color: var(--text); }
.ds-row-expand-detail { padding: 4px 14px 16px 36px; background: var(--surface-elev); border-top: 1px solid var(--border-faint); }
.ds-row-expand:not(.is-open) .ds-row-expand-detail { display: none; }

/* ── stepper (promoted from close-month cm-step-rail · §13 approved) ──── */
.ds-stepper { display: flex; flex-direction: column; gap: 2px; }
.ds-step { display: flex; align-items: flex-start; gap: 11px; padding: 9px 11px; border-radius: var(--r-sm); cursor: pointer; }
.ds-step:hover { background: var(--bg-hover); }
.ds-step.is-active { background: var(--accent-soft); }
.ds-step-dot { flex: none; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  background: var(--surface-raised); color: var(--text-muted); border: 1px solid var(--border); }
.ds-step.is-active .ds-step-dot { background: var(--accent); color: #fff; border-color: var(--accent); }
.ds-step.is-done .ds-step-dot { background: var(--sage); color: #fff; border-color: var(--sage); }
.ds-step-main { min-width: 0; flex: 1; padding-top: 1px; }
.ds-step-name { font-size: 12.5px; font-weight: 600; color: var(--text); }
.ds-step.is-active .ds-step-name { color: var(--text); }
.ds-step-sub { font-size: 10.5px; color: var(--text-muted); margin-top: 1px; }


/* ============================================================================
 * §18 — General atoms (canonical replacements for hand-rolled equivalents)
 * Added 2026-06. Retire bespoke .po2-type → .ds-typebadge · .po2-md-figures →
 * <DsMetricBar compact> (the old .ds-statstrip, §10) · .po2-empty[-state] → .ds-empty. See Design System.html §18.
 * ========================================================================== */

/* ── Empty state (.ds-empty) ─ rails, lists, tables, matrices ───── */
.ds-empty { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 32px 20px; text-align: center; color: var(--text-muted); }
.ds-empty .ds-empty-icon { width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center;
  justify-content: center; background: var(--surface-raised); color: var(--text-muted); }
.ds-empty .ds-empty-title { font-size: 13px; font-weight: 600; color: var(--text-2); }
.ds-empty .ds-empty-sub { font-size: 11.5px; color: var(--text-muted); max-width: 34ch; line-height: 1.5; }

/* ── Category type badge (.ds-typebadge) ─ non-evaluative kind label ─ */
.ds-typebadge { display: inline-flex; align-items: center; font-family: var(--font-mono); font-size: 8.5px;
  font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--text-muted); background: var(--surface-raised); line-height: 15px; }
.ds-typebadge.is-info { color: var(--blue); background: var(--blue-soft); border-color: color-mix(in oklch, var(--blue) 28%, transparent); }
.ds-typebadge.is-ok   { color: var(--sage); background: var(--sage-soft); border-color: color-mix(in oklch, var(--sage) 28%, transparent); }
@media (prefers-color-scheme: dark) {      /* same lightened inks as the pills (design_handoff_dark_mode) */
  .ds-typebadge.is-info { color: #95bdea; }
  .ds-typebadge.is-ok   { color: #9ccb88; }
}

/* ════════ §10 · Costs·Overview v2 atoms (ds-var / ds-spark / ds-budgetmeter) ════════ */

/* .ds-var — signed, tinted variance chip (readout, tables, tooltips) */
.ds-var { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 12.5px; font-weight: 500; }
.ds-var.over  { color: var(--accent); }      /* §17 */
.ds-var.under { color: var(--sage); }
.ds-var.flat  { color: var(--text-muted); }
.ds-var.muted { color: var(--text-muted); font-style: italic; font-family: var(--font-sans); font-size: 12px; font-weight: 400; }
.ds-var-pct { opacity: .72; font-size: 11px; }

/* .ds-spark — canonical sparkline */
.ds-spark { display: block; }
.ds-spark-zero { stroke: var(--border-hover); stroke-width: 1; stroke-dasharray: 2 2; }
.ds-spark-line { fill: none; stroke: var(--text-2); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.ds-spark-dot.over  { fill: var(--accent); }
.ds-spark-dot.under { fill: var(--sage); }
.ds-spark-dot.flat  { fill: var(--text-muted); }

/* .ds-budgetmeter — three-state budget-vs-cap bar (Option A) */
.ds-budgetmeter { display: flex; flex-direction: column; gap: 11px; }
.ds-budgetmeter-head { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
.ds-budgetmeter-fc { display: flex; align-items: baseline; gap: 9px; }
.ds-budgetmeter-v { font-family: var(--font-mono); font-size: 22px; font-weight: 500; letter-spacing: -.01em; color: var(--text); font-variant-numeric: tabular-nums; }
.ds-budgetmeter-l { font-size: 12px; color: var(--text-muted); }
.ds-budgetmeter-cap { font-family: var(--font-mono); font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.ds-budgetmeter-cap b { color: var(--text-2); font-weight: 600; }
.ds-budgetmeter-track { position: relative; height: 30px; border-radius: var(--r-sm); background: var(--surface-raised); overflow: hidden; display: flex; border: 1px solid var(--border); }
.ds-budgetmeter-seg { height: 100%; }
.ds-budgetmeter-seg.spent { background: var(--accent); }
.ds-budgetmeter-seg.proj  { background: repeating-linear-gradient(45deg, color-mix(in oklch, var(--accent) 42%, transparent) 0 4px, transparent 4px 8px); border-left: 1px solid var(--surface-elev); }
.ds-budgetmeter-seg.head  { flex: 1; }
.ds-budgetmeter-track::after { content: ""; position: absolute; right: 0; top: 0; bottom: 0; width: 2px; background: var(--text-2); }
.ds-budgetmeter.is-over .ds-budgetmeter-track::after { background: var(--accent); }
.ds-budgetmeter-legend { display: flex; gap: 18px; flex-wrap: wrap; }
.ds-budgetmeter-key { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--text-muted); }
.ds-budgetmeter-key .sw { width: 11px; height: 11px; border-radius: 3px; flex: none; }
.ds-budgetmeter-key .sw.spent { background: var(--accent); }
.ds-budgetmeter-key .sw.proj { background: repeating-linear-gradient(45deg, color-mix(in oklch, var(--accent) 42%, transparent) 0 3px, transparent 3px 6px); border: 1px solid var(--border-hover); }
.ds-budgetmeter-key .sw.head { background: var(--surface-raised); border: 1px solid var(--border-hover); }
.ds-budgetmeter-key b { color: var(--text-2); font-family: var(--font-mono); font-weight: 600; font-variant-numeric: tabular-nums; }
/* compact density — Cost Dock strip + (later) Commitments spine; legend-less */
.ds-budgetmeter.is-compact { gap: 0; }
.ds-budgetmeter.is-compact .ds-budgetmeter-track { height: 7px; border-radius: 4px; }
.ds-budgetmeter.is-compact .ds-budgetmeter-head,
.ds-budgetmeter.is-compact .ds-budgetmeter-legend { display: none; }

/* =============================================================================
 * Cashflow detail modal (unified Overview + Commitments) — handoff 2026-06-09
 * One canonical detail modal: .modal.wide-xl shell · .ds-metricbar.compact strip ·
 * .ds-schedule grid. Most of the modal REUSES existing atoms (.modal-*,
 * .ds-metricbar.compact, .pill-switch, .pill.accent, the Forecast atoms, .ds-typebadge);
 * only the rules below are new. See the design_handoff_cashflow_modal_unification
 * bundle (DS-DECISION). Land in BOTH components.css copies (drift gate).
 * ========================================================================== */

/* shell: the larger (former Overview) width, centered head, body scroll layout.
 * The head, strip and footer sit OUTSIDE .modal-body; inside the body everything
 * above the rows stays fixed (flex:none) and only the schedule rows scroll —
 * flex:none is load-bearing (a 4px squeeze on the pill row clips the pills). */
.modal.wide-xl { max-width: 900px; width: min(900px, 94vw); }
.modal-head.is-centered { align-items: center; }
.modal-head .mh-titles { flex: 1; min-width: 0; }
/* DsDetailModal identity swatch — a 10px dot before the title, matching the
 * series dots in legends and pills (replaced an inline 11px dot + a 3×24 stripe). */
.modal-head .mh-title-row { display: inline-flex; align-items: center; gap: 9px; min-width: 0; }
.modal-head .mh-swatch { width: 10px; height: 10px; border-radius: 50%; flex: none; }
/* The three OUTER column children (head · strip · footer) must not shrink, or a
 * long schedule lets flexbox squeeze the KPI strip instead of scrolling the rows. */
.modal.wide-xl > .modal-head,
.modal.wide-xl > .modal-foot { flex: none; }
/* The strip is a bordered, self-contained card rendered as the modal SUBHEAD —
 * a direct child of .modal, OUTSIDE .modal-body, so it inherits no gutter. Inset
 * it from the modal edges (sides match the 20px head/body gutter; the body's
 * 18px top padding supplies the gap below). This belongs to EVERY modal size,
 * not just wide-xl — a default-size modal (Resourcing month detail) bled to the
 * edges before. APP-WIDE §7. */
.modal > .ds-metricbar { margin: 14px 20px 0; flex: none; }
.modal.wide-xl .modal-body { flex: 1; display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.modal.wide-xl .modal-body > .pill-switch,
.modal.wide-xl .modal-body > .ds-sched-sechead,
.modal.wide-xl .modal-body > .ds-sched-poline { flex: none; }
.ds-sched-scroll { flex: 1; min-height: 0; overflow: auto; }

/* pill-switch in the detail modal — suppress the scroll track (it still scrolls
 * silently as the many-PO fallback); the FIRST pill is the All-POs aggregate. */
.modal.wide-xl .pill-switch { scrollbar-width: none; padding-bottom: 0; margin-bottom: 14px; }
.modal.wide-xl .pill-switch::-webkit-scrollbar { display: none; }

/* schedule (.ds-schedule) — a GRID, not a <table>: the column header must pin
 * while rows scroll, and position:sticky on a <th> fails silently in several
 * engines. A sticky BLOCK header inside the scroll box is rock-solid and columns
 * still align (header + rows share one scroll box). */
.ds-schedule { font-variant-numeric: tabular-nums; }
.ds-schedule .ds-sched-row { display: grid; grid-template-columns: 64px 84px minmax(70px, 1fr) 144px minmax(84px, 1fr); align-items: center; column-gap: 14px; }
.ds-schedule .ds-sched-headrow { position: sticky; top: 0; z-index: 2; background: var(--surface-elev); box-shadow: inset 0 -1px 0 var(--border); padding-bottom: 7px; }
.ds-schedule .ds-sched-headrow .h { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); font-weight: 600; }   /* 9.5px = the .ds-metricbar.compact label directly above every schedule (was 8px) */
.ds-schedule .ds-sched-headrow .h.r { text-align: right; }
.ds-schedule .ds-sched-body .ds-sched-row { border-top: 1px solid var(--border); padding: 7px 0; font-size: 12px; color: var(--text-2); }
.ds-schedule .ds-sched-body .ds-sched-row.now { border-top: 1.5px solid var(--accent); }
.ds-schedule .ds-sched-body .ds-sched-row.zero { color: var(--text-muted); }
.ds-schedule .mo { font-family: var(--font-mono); font-weight: 600; color: var(--text); }
.ds-schedule .r { text-align: right; font-family: var(--font-mono); }
.ds-schedule .shape { text-align: right; }
.ds-schedule .cum { color: var(--text-muted); }
.ds-sched-bar { display: inline-block; width: 120px; height: 7px; border-radius: 3px; background: var(--surface-raised); overflow: hidden; vertical-align: middle; }
.ds-sched-bar > i { display: block; height: 100%; }

/* section header + legend, and the PO / aggregate label line (both stay fixed) */
.ds-sched-sechead { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); font-weight: 600; margin: 0 0 10px; display: flex; align-items: center; gap: 8px; }
.ds-sched-sechead .leg { margin-left: auto; display: inline-flex; gap: 12px; font-weight: 500; text-transform: none; letter-spacing: 0; font-size: 9.5px; color: var(--text-muted); }
.ds-sched-sechead .leg i { display: inline-block; width: 11px; height: 11px; border-radius: 2px; margin-right: 4px; vertical-align: -1px; }
.ds-sched-poline { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; }
.ds-sched-poline .dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.ds-sched-poline .code { font-family: var(--font-mono); font-size: 11.5px; font-weight: 600; color: var(--text); flex: none; }
.ds-sched-poline .name { font-size: 12px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-sched-poline .agg-badge { font-family: var(--font-mono); font-size: 8.5px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-muted); background: var(--surface-raised); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px; flex: none; }
.ds-sched-poline .figs { margin-left: auto; font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); white-space: nowrap; flex: none; }
.ds-sched-poline .figs b { color: var(--text); font-size: 11.5px; }
.ds-sched-poline .name.is-agg { color: var(--text); font-weight: 600; }
.modal-foot .meta .is-over { color: var(--accent); font-weight: 600; }

/* ═══════════════════════════════════════════════════════════════════════════
   Inspector parts (design_handoff_inspector_parts, 2026-09-26). First consumer:
   the Plan task inspector (task-inspector.jsx, design_handoff_task_inspector).
   Dark values are hex: black-mixed inks never carry into dark. --shadow-drawer
   lives in tokens.css next to --shadow-pop.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── .ds-inspector — fixed right drawer: head / one scrolling body / optional foot.
   top/bottom are set by the host (task-inspector.jsx measureBounds). ── */
.ds-inspector { position: fixed; right: 0; top: 0; bottom: 0; width: 360px; max-width: 90vw; z-index: 80;
  background: var(--surface); border-left: 1px solid var(--border); box-shadow: var(--shadow-drawer);
  display: flex; flex-direction: column; }
.ds-inspector.is-static { position: relative; top: auto; bottom: auto; right: auto; }   /* specimens, cards */
.ds-inspector-head { flex: none; padding: 14px 18px 12px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px; }
.ds-inspector-row { display: flex; align-items: center; gap: 8px; }
.ds-inspector-id { font: 11px var(--font-mono); background: var(--surface-raised); color: var(--text-muted); padding: 2px 8px; border-radius: 4px; white-space: nowrap; }
.ds-inspector-title { font: 500 20px/1.2 var(--font-serif); letter-spacing: -0.015em; padding: 2px 4px; margin: 0 -4px; border-radius: var(--r-sm); cursor: text; transition: background .12s ease-out; }
.ds-inspector-title:hover { background: var(--surface-raised); }
.ds-inspector-meta { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); white-space: nowrap; }
.ds-inspector-meta .sw { width: 4px; height: 12px; border-radius: 2px; flex: none; }
.ds-inspector-meta .sep { color: var(--border-hover); }
.ds-inspector-body { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; }
.ds-inspector-foot { flex: none; display: flex; align-items: center; gap: 8px; padding: 12px 18px; border-top: 1px solid var(--border); }
.ds-inspector-link { font-size: 12px; font-weight: 500; white-space: nowrap; color: var(--accent); text-decoration: none; }

/* ── .ds-props — inline label/value grid; the side-by-side sibling of .field-label ── */
.ds-props { display: grid; grid-template-columns: 84px minmax(0,1fr); gap: 10px 12px; align-items: center; padding: 14px 18px; }
.ds-props > dt, .ds-props-k { font: 600 10px var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); margin: 0; }
.ds-props > dt.top, .ds-props-k.top { align-self: start; padding-top: 4px; }
.ds-props > dd { margin: 0; min-width: 0; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ds-props-hint { grid-column: 2; margin-top: -4px; font-size: 11px; color: var(--text-muted); }
.ds-props-fig { font: 500 12.5px var(--font-mono); font-variant-numeric: tabular-nums; color: var(--text); }
.ds-props .input { height: 28px; font-size: 12px; padding: 4px 8px; }

/* ── .tabstrip.is-pane — sections of one record inside a drawer or card ── */
.tabstrip.is-pane { height: auto; gap: 18px; background: var(--surface-elev); border-top: 1px solid var(--border); flex: none; }
.tabstrip.is-pane .tab { height: 38px; padding: 0; gap: 6px; }

/* ── .ds-lines — unframed list with aligned numeric columns. Consumer sets the tracks:
 *    style="--cols: minmax(0,1fr) 52px 40px 66px" ── */
.ds-lines { display: flex; flex-direction: column; }
.ds-lines-head, .ds-lines-row { display: grid; grid-template-columns: var(--cols, minmax(0,1fr) auto); gap: 8px; align-items: center; }
.ds-lines-head { padding: 4px 0 6px; font: 600 9.5px var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); }
.ds-lines-row { padding: 7px 0; border-top: 1px solid var(--border-faint); }
.ds-lines .num { text-align: right; font: 12px var(--font-mono); font-variant-numeric: tabular-nums; }
.ds-lines .num.muted { color: var(--text-2); }
.ds-lines-cap { font: 600 9.5px var(--font-mono); letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); }

/* ── .input-affix — compact 26px mono input with a muted unit suffix ── */
.input-affix { position: relative; display: block; min-width: 0; }
.input-affix > .input { height: 26px; padding: 3px 16px 3px 6px; text-align: right; font-size: 12px; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.input-affix > .affix { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); font: 11px var(--font-mono); color: var(--text-muted); pointer-events: none; }

/* ── .banner.is-compact — drawer-density notice. Title stays ink; the hue moves to the
 *    icon and a softened border. amber + danger (taskWarnings emits severity "high"). ── */
.banner.is-compact { align-items: center; gap: 8px; padding: 8px 10px; border-radius: var(--r-sm); font-size: 12px; line-height: 1.4; color: var(--text); }
.banner.is-compact .banner-title { color: var(--text); }
.banner.is-compact .banner-body { margin-top: 0; line-height: 1.4; }
.banner.is-compact > svg { flex: none; }
.banner.amber.is-compact  { border-color: color-mix(in oklch, var(--amber), transparent 45%); }
.banner.amber.is-compact > svg  { color: color-mix(in oklch, var(--amber), black 28%); }
.banner.danger.is-compact { border-color: color-mix(in oklch, var(--danger), transparent 55%); }
.banner.danger.is-compact > svg { color: var(--danger); }

/* ── .ds-choice — single-choice row: radio · id + meta · figure. State is
 *    aria-checked="true" on the button (role="radio"), not a class. ── */
.ds-choice { display: grid; grid-template-columns: 12px minmax(0,1fr) auto; gap: 8px; align-items: center; width: 100%;
  padding: 7px 9px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-elev);
  cursor: pointer; text-align: left; font: inherit; color: inherit; transition: border-color .12s ease-out, box-shadow .12s ease-out; }
.ds-choice:hover { border-color: var(--border-hover); }
.ds-choice:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ds-choice-radio { width: 12px; height: 12px; border-radius: 50%; border: 1px solid var(--border-hover); display: grid; place-items: center; }
.ds-choice[aria-checked="true"] { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.ds-choice[aria-checked="true"] .ds-choice-radio::after { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.ds-choice-id { display: block; font: 600 12px var(--font-mono); }
.ds-choice-sub { display: block; font-size: 11px; color: var(--text-muted); }
.ds-choice-fig { font: 11.5px var(--font-mono); font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap; }
.ds-choice-fig.is-over { color: var(--accent); }

@media (prefers-color-scheme: dark) {
  .banner.amber.is-compact > svg { color: #e8b866; }
  .banner.danger.is-compact > svg { color: #ee9d8a; }
  .ds-choice-fig.is-over { color: #f2a97f; }
}
