/* =============================================================================
   crm-v3.css — CRM V3 (workspace + client record)

   Loaded by crm_v3.html and client_v3.html. The two pages are one module: the
   workspace answers "what should happen next across the book", the client
   record answers "what is true about this one relationship". They share a
   vocabulary, so they share a stylesheet.

   Everything shared (card, table, badge, switch, fchip, subtab, slideover)
   comes from css/app.css untouched. What is here is only what those two pages
   add on top.

   Two ideas drive the visual language:

   1. A number is never separated from the word that names it. Stats read
      [value][label] tight, left to right, and a setting row reads
      [switch][label][value] with no gap opened between them.
   2. Nothing a person can type into, press or pick sits on the panel's own
      white. Controls and nested boxes take --color-control.
   ========================================================================== */

/* -----------------------------------------------------------------------------
   Page header — the role switch
   Two jobs, so the tab strip swaps entirely rather than hiding a few controls.
   -------------------------------------------------------------------------- */
/* Title and role switch read as one line: the switch says whose CRM this is,
   which is the same job the word CRM is doing beside it. */
.page-titles { display: flex; align-items: center; gap: 16px; min-width: 0; }
.page-titles .page-title { flex: none; }

.crm-role { display: flex; align-items: center; gap: 8px; }
.crm-role__label {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--color-text-muted); white-space: nowrap;
}
/* A subtab strip normally sits above the panel it controls and carries 8px of
   air under it for the join. In the header it controls nothing below it, and
   that margin was doing three visible things at once: sitting the switch 4px
   high against its own label, adding 8px to a row app.css stretches, and so
   growing the gear beside it past the button size every other page gives it.
   The row is 36px — one button height — with this off. */
.crm-role .subtabs { margin-bottom: 0; }

/* The walkthrough is the first thing in the right-hand group on every page that
   has one, and its glyph is the only coloured one there. Stated here rather
   than with a style attribute on the icon. */
.icon-play { color: var(--color-primary); }

/* Export is the platform's XLS mark, not a labelled button — the same control,
   at the same size, as the one on Catalog and Activities. Those pages size it
   with a style attribute; this states it once instead. */
.export-xls { display: inline-flex; align-items: center; }
.export-xls img { height: 34px; width: auto; }

/* A row of buttons in a card header. Replaces a repeated inline gap. */
.crm-hd-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* -----------------------------------------------------------------------------
   Card rhythm
   Sibling cards carry no gap of their own in app.css, so this page states one.
   Anything already inside a flex .stack keeps the stack gap instead of both.
   -------------------------------------------------------------------------- */
.page .card + .card { margin-top: 16px; }
.page .stack > .card + .card { margin-top: 0; }
/* A grid already spaces its children, in both directions, so a stacking margin
   inside one is the gap counted twice — and counted on four of five columns
   rather than all of them, because the first card has no card before it. That
   is what dropped every Pipeline column except New sixteen pixels below its
   neighbours. The board is the only grid of cards on the page; the four KPI
   grids hold buttons and never saw this. */
.page .grid > .card + .card { margin-top: 0; }
.page .toolbar + .card,
.page .crm-filters + .card,
.page .crm-glance + .card,
.page .crm-scope + .card,
.page .alert + .card { margin-top: 0; }
.page .card + .crm-filters,
.page .card + .toolbar,
.page .card + .crm-scope { margin-top: 16px; }

/* -----------------------------------------------------------------------------
   Small corrections to shared components used in a new place here
   -------------------------------------------------------------------------- */
/* Every KPI on these pages opens the records behind it, so the card is a button.
   It keeps the .kpi look and gains only what a button needs. */
button.kpi { text-align: left; font: inherit; color: inherit; cursor: pointer; width: 100%; }
button.kpi .value { display: block; }

/* An alert that ends in an action needs the spacer app.css only defines inside
   toolbars and dialog footers. */
.alert .spacer { flex: 1; }
.alert strong { font-weight: 600; }

/* A note in a panel footer carries an icon at label size, not Lucide's default. */
.slideover-foot .muted { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
.slideover-foot .muted svg { width: 14px; height: 14px; flex: none; }

/* A tab's own options ride the right end of the tab row. One control, one look,
   on every tab that has any — so where to change what a list is showing is the
   same answer wherever you are, and the choices stop taking the top off the
   list they apply to. `margin-left: auto` is what banks them right; the strip
   scrolls on a narrow screen and they scroll with it rather than being cut. */
.tabs__tools { margin-left: auto; display: flex; align-items: center; gap: 6px; flex: none; }
/* It sits ON the strip, not in it: the tabs draw a join with the panel below and
   a button must not look like it is trying to. */
.tab-tool { align-self: center; margin-bottom: 6px; }

/* The count on a tab in this module is a plain number, not a pill of its own. */
.tabs .count { font-variant-numeric: tabular-nums; }

/* Other pages give the header's notification dropdown its containing block with
   a style attribute. Style attributes are forbidden, so it is stated here
   instead — without it the menu anchors to the header and lands off to one side. */
.topbar-notif { position: relative; }
.topbar-notif .dropdown-menu { min-width: 288px; }
.topbar-notif .dropdown-item svg { width: 15px; height: 15px; flex: none; }

/* -----------------------------------------------------------------------------
   Glance bar — the count line that sits above a list
   -------------------------------------------------------------------------- */
.crm-glance {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px;
  padding: 9px 12px; margin-bottom: 12px;
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  border-radius: 7px;
}
.crm-glance__note {
  margin-left: auto; font-size: 11px; color: var(--color-text-subtle);
}

/* A stat is [value][label], tight, in that order. Never a two-column row with
   the number pushed to the far edge — the eye would have to travel back. */
.crm-stat { display: inline-flex; align-items: baseline; gap: 5px; font-size: 12px; }
.crm-stat__v {
  font-size: 14px; font-weight: 600; color: var(--color-value-tnum);
  font-variant-numeric: tabular-nums;
}
.crm-stat__l { color: var(--color-text-muted); }
.crm-stat--bad .crm-stat__v  { color: var(--color-danger); }
.crm-stat--warn .crm-stat__v { color: var(--color-warn); }
.crm-stat--ok .crm-stat__v   { color: var(--color-success); }
.crm-glance__sep {
  width: 1px; height: 15px; background: var(--color-border-strong);
}

/* -----------------------------------------------------------------------------
   Filter and scope bars
   -------------------------------------------------------------------------- */
.crm-filters,
.crm-scope {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px;
  padding: 9px 12px; margin-bottom: 12px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 7px;
}
.crm-filters .fg-label,
.crm-scope .fg-label { margin: 0; white-space: nowrap; }
.crm-filters__sep {
  width: 1px; align-self: stretch; background: var(--color-border);
}
.crm-scope .spacer { flex: 1; }

/* A scope control is sized to its own widest option, and several sit on one
   row — a near-empty full-width select says nothing. */
.crm-scope .select { min-width: 128px; width: auto; }
.crm-scope__field { display: flex; align-items: center; gap: 6px; }
.crm-scope__field > .fg-label { font-size: 12px; }

/* -----------------------------------------------------------------------------
   A name that opens a record
   Colour and weight carry the affordance — never an underline at rest.
   -------------------------------------------------------------------------- */
.crm-link {
  background: none; border: 0; padding: 0; font: inherit; text-align: left;
  color: var(--color-primary-700); font-weight: 500; cursor: pointer;
}
.crm-link:hover { color: var(--color-primary); text-decoration: none; }

/* -----------------------------------------------------------------------------
   Subsection heading inside a card body
   The heading and its description live INSIDE the card, never floating above.
   -------------------------------------------------------------------------- */
.crm-sub-h {
  margin: 18px 0 10px; font-size: 13px; font-weight: 600; color: var(--color-text);
}
.card-body > .crm-sub-h:first-child { margin-top: 0; }
.crm-sub-d { margin: -6px 0 10px; font-size: 12px; color: var(--color-text-muted); }
.crm-note { margin: 8px 0 0; font-size: 11px; color: var(--color-text-subtle); }
.slideover-body :is(.field, .field-grid, .form-row, .fchips) + .crm-note { margin-top: 12px; }

/* -----------------------------------------------------------------------------
   Meter — target against actual, coverage, conversion
   -------------------------------------------------------------------------- */
/* `display: block` is load-bearing: the meter is a <span> inside a table cell,
   and an inline box ignores height — the bar would draw as a hairline. */
.crm-meter {
  display: block; position: relative; height: 8px; border-radius: 4px; overflow: hidden;
  background: var(--color-control); border: 1px solid var(--color-control-border);
}
.crm-meter > span {
  display: block; height: 100%; background: var(--color-primary); border-radius: 3px;
}
.crm-meter--ok > span   { background: var(--color-success); }
.crm-meter--warn > span { background: var(--color-warn); }
.crm-meter--bad > span  { background: var(--color-danger); }

/* -----------------------------------------------------------------------------
   Pace / health / status words
   -------------------------------------------------------------------------- */
.crm-pace {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 500; padding: 2px 7px; border-radius: 7px;
  border: 1px solid transparent; white-space: nowrap;
}
.crm-pace--ahead  { background: var(--color-success-50); color: var(--color-success); border-color: var(--color-success); }
.crm-pace--on     { background: var(--color-info-50);    color: var(--color-info);    border-color: var(--color-info); }
.crm-pace--risk   { background: var(--color-warn-50);    color: var(--color-warn);    border-color: var(--color-warn); }
.crm-pace--behind { background: var(--color-danger-50);  color: var(--color-danger);  border-color: var(--color-danger); }
.crm-pace--gone   { background: var(--color-control);    color: var(--color-text-muted); border-color: var(--color-control-border); }

/* -----------------------------------------------------------------------------
   The feed — a signal, an alert, an exception. One shape, three uses.
   -------------------------------------------------------------------------- */
.crm-feed { display: flex; flex-direction: column; gap: 10px; }
.sig {
  display: flex; gap: 12px; padding: 12px 14px;
  background: var(--color-surface);
  border: 1px solid var(--color-border); border-left: 3px solid var(--color-border-strong);
  border-radius: 7px;
}
.sig:hover { border-color: var(--color-border-strong); }
.sig--urgent { border-left-color: var(--color-danger); }
.sig--warn   { border-left-color: var(--color-warn); }
.sig--done   { opacity: 0.62; }
.sig__ico {
  flex: 0 0 30px; height: 30px; display: grid; place-items: center;
  border-radius: 7px; background: var(--color-control); color: var(--color-text-muted);
}
.sig__ico svg { width: 16px; height: 16px; }
.sig--urgent .sig__ico { background: var(--color-danger-50); color: var(--color-danger); }
.sig--warn .sig__ico   { background: var(--color-warn-50);   color: var(--color-warn); }
.sig__body { flex: 1; min-width: 0; }
.sig__head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 4px; }
.sig__kind {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em;
  color: var(--color-text-subtle);
}
.sig__due { font-size: 11px; color: var(--color-text-muted); }
.sig__title { margin: 0 0 3px; font-size: 13px; font-weight: 600; line-height: 1.35; }
.sig__why { margin: 0; font-size: 12px; color: var(--color-text-muted); line-height: 1.5; }
.sig__evidence {
  margin: 5px 0 0; display: flex; align-items: center; gap: 5px;
  font-size: 11px; color: var(--color-text-subtle);
}
.sig__evidence svg { width: 12px; height: 12px; flex: 0 0 12px; }
.sig__side {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 5px;
}
.sig__side .btn { white-space: nowrap; }

/* -----------------------------------------------------------------------------
   Report tiles — a grid of things you can run
   -------------------------------------------------------------------------- */
.crm-reports {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 10px;
}
.crm-report {
  display: flex; gap: 9px; padding: 11px 12px; text-align: left;
  background: var(--color-control); border: 1px solid var(--color-control-border);
  border-radius: 7px; cursor: pointer; font: inherit; color: inherit;
}
.crm-report:hover { border-color: var(--color-primary); background: var(--color-surface); }
.crm-report svg { width: 15px; height: 15px; flex: 0 0 15px; margin-top: 2px; color: var(--color-primary); }
.crm-report__n { font-size: 12px; font-weight: 600; display: block; }
.crm-report__d { font-size: 11px; color: var(--color-text-muted); display: block; margin-top: 2px; }

/* -----------------------------------------------------------------------------
   Pipeline stage board
   -------------------------------------------------------------------------- */
.crm-stages { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
.crm-stage {
  flex: 1 0 128px; padding: 9px 10px; text-align: left; font: inherit; color: inherit;
  background: var(--color-control); border: 1px solid var(--color-control-border);
  border-radius: 7px; cursor: pointer;
}
.crm-stage:hover, .crm-stage.is-on { border-color: var(--color-primary); background: var(--color-surface); }
.crm-stage__n { font-size: 11px; color: var(--color-text-muted); display: block; white-space: nowrap; }
.crm-stage__v {
  display: flex; align-items: baseline; gap: 5px; margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
.crm-stage__c { font-size: 15px; font-weight: 600; color: var(--color-value-tnum); }
.crm-stage__s { font-size: 11px; color: var(--color-text-muted); }

/* -----------------------------------------------------------------------------
   Exception / prepared-brief list
   A short mark between rows, not a full-width hairline.
   -------------------------------------------------------------------------- */
.crm-list { list-style: none; margin: 0; padding: 0; }
.crm-list > li {
  position: relative; display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 0;
}
.crm-list > li::after {
  content: ''; position: absolute; left: 0; bottom: 0; width: 50%; height: 1px;
  background: var(--color-border-strong);
}
.crm-list > li:last-child { padding-bottom: 0; }
.crm-list > li:last-child::after { display: none; }
.crm-list__ico {
  flex: 0 0 26px; height: 26px; display: grid; place-items: center; border-radius: 7px;
  background: var(--color-control); color: var(--color-text-muted);
}
.crm-list__ico svg { width: 14px; height: 14px; }
.crm-list__ico--bad  { background: var(--color-danger-50); color: var(--color-danger); }
.crm-list__ico--warn { background: var(--color-warn-50);   color: var(--color-warn); }
.crm-list__ico--ok   { background: var(--color-success-50); color: var(--color-success); }
.crm-list__text { flex: 1; min-width: 0; font-size: 12px; line-height: 1.5; }
.crm-list__t { font-weight: 600; font-size: 12px; }
.crm-list__d { color: var(--color-text-muted); }
.crm-list__acts { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }

/* -----------------------------------------------------------------------------
   Setting rows — [switch] [label] [value], read left to right
   Real columns, no space-between, no gap opened between a label and the value
   that answers it. Two settings across, because one per row wastes the row.
   -------------------------------------------------------------------------- */
.crm-settings {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 28px;
}
.crm-settings--one { grid-template-columns: minmax(0, 1fr); }
.crm-setting {
  position: relative;
  display: grid; grid-template-columns: 34px max-content max-content 1fr;
  align-items: center; gap: 10px; padding: 9px 0;
}
.crm-setting::after {
  content: ''; position: absolute; left: 0; bottom: 0; width: 50%; height: 1px;
  background: var(--color-border-strong);
}
.crm-setting:last-child::after,
.crm-settings > .crm-setting:nth-last-child(-n+2)::after { display: none; }
.crm-setting__l { font-size: 12px; font-weight: 400; }
.crm-setting__v {
  font-size: 12px; font-weight: 600; color: var(--color-value-tnum);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.crm-setting__v .input,
.crm-setting__v .select { padding: 3px 7px; font-size: 12px; }
.crm-setting__num { width: 62px; }
.crm-setting__sel { min-width: 132px; }

/* -----------------------------------------------------------------------------
   Fact rows — a stored value with no control on it
   The same rhythm as a setting row, minus the switch. A field the reader cannot
   toggle must not wear a toggle: a switch beside "Payment terms" claims the
   terms can be turned off. Two across, label then value, tight, and the
   leftover width absorbed by a trailing track rather than opened as a gap.
   -------------------------------------------------------------------------- */
.crm-facts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 28px;
}
.crm-fact-row {
  position: relative;
  display: grid; grid-template-columns: max-content max-content 1fr;
  align-items: baseline; gap: 10px; padding: 9px 0;
}
.crm-fact-row::after {
  content: ''; position: absolute; left: 0; bottom: 0; width: 50%; height: 1px;
  background: var(--color-border-strong);
}
.crm-facts > .crm-fact-row:nth-last-child(-n+2)::after { display: none; }
.crm-fact-row__l { font-size: 12px; color: var(--color-text-muted); }
.crm-fact-row__v {
  font-size: 12px; font-weight: 600; color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

/* -----------------------------------------------------------------------------
   Confidentiality — what does not leave the building
   -------------------------------------------------------------------------- */
.crm-private {
  display: flex; align-items: flex-start; gap: 8px; padding: 9px 11px;
  background: var(--color-warn-50); border: 1px solid var(--color-warn);
  border-radius: 7px; font-size: 12px; color: var(--color-text); line-height: 1.5;
}
.crm-private svg { width: 15px; height: 15px; flex: 0 0 15px; margin-top: 1px; color: var(--color-warn); }
.crm-locked {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; color: var(--color-text-subtle);
}
.crm-locked svg { width: 12px; height: 12px; }

/* -----------------------------------------------------------------------------
   The client record — hero
   -------------------------------------------------------------------------- */
.cv-hero {
  display: flex; align-items: flex-start; gap: 14px; padding: 14px 16px;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: 7px; margin-bottom: 12px;
}
.cv-hero__mark {
  flex: 0 0 48px; height: 48px; display: grid; place-items: center; border-radius: 7px;
  background: var(--color-primary-50); color: var(--color-primary-700);
  font-size: 16px; font-weight: 600;
}
.cv-hero__body { flex: 1; min-width: 0; }
.cv-hero__name { margin: 0; font-size: 18px; font-weight: 600; }
.cv-hero__tags { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.cv-hero__facts {
  display: flex; flex-wrap: wrap; gap: 4px 20px; margin-top: 9px;
}
.cv-hero__acts { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* A fact is [label][value] with the label right-aligned against its value —
   tight in the middle, so the pair reads as one thing. */
.cv-fact { display: flex; align-items: baseline; gap: 6px; font-size: 12px; }
.cv-fact__k { color: var(--color-text-muted); text-align: right; }
.cv-fact__v { color: var(--color-text); font-weight: 500; }

/* -----------------------------------------------------------------------------
   KPI strip on the client record
   -------------------------------------------------------------------------- */
.cv-kpis {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(146px, 1fr)); gap: 8px;
  margin-bottom: 12px;
}
.cv-kpi {
  padding: 10px 11px; text-align: left; font: inherit; color: inherit; cursor: pointer;
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 7px;
}
.cv-kpi:hover { border-color: var(--color-primary); }
.cv-kpi__k {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--color-text-muted); display: block;
}
.cv-kpi__row { display: flex; align-items: baseline; gap: 5px; margin-top: 4px; }
.cv-kpi__v {
  font-size: 17px; font-weight: 600; color: var(--color-value-tnum);
  font-variant-numeric: tabular-nums;
}
.cv-kpi__s { font-size: 11px; color: var(--color-text-muted); }

/* -----------------------------------------------------------------------------
   Timeline
   -------------------------------------------------------------------------- */
.cv-tl { list-style: none; margin: 0; padding: 0 0 0 18px; position: relative; }
.cv-tl::before {
  content: ''; position: absolute; left: 4px; top: 6px; bottom: 6px; width: 1px;
  background: var(--color-border-strong);
}
.cv-tl > li { position: relative; padding: 0 0 16px; }
.cv-tl > li:last-child { padding-bottom: 0; }
.cv-tl__dot {
  position: absolute; left: -18px; top: 4px; width: 9px; height: 9px; border-radius: 50%;
  background: var(--color-surface); border: 2px solid var(--color-border-strong);
}
.cv-tl__dot--ok   { border-color: var(--color-success); }
.cv-tl__dot--warn { border-color: var(--color-warn); }
.cv-tl__dot--bad  { border-color: var(--color-danger); }
.cv-tl__head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.cv-tl__kind {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--color-text-muted);
}
.cv-tl__when { font-size: 11px; color: var(--color-text-subtle); }
.cv-tl__t { margin: 3px 0 0; font-size: 13px; font-weight: 500; }
.cv-tl__d { margin: 2px 0 0; font-size: 12px; color: var(--color-text-muted); line-height: 1.5; }
.cv-tl__meta {
  margin: 4px 0 0; display: flex; flex-wrap: wrap; gap: 4px 12px;
  font-size: 11px; color: var(--color-text-subtle);
}

/* -----------------------------------------------------------------------------
   Preferences — what to put in front of them, and what never to
   -------------------------------------------------------------------------- */
.crm-prefs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
.crm-prefs__h {
  display: flex; align-items: center; gap: 6px; margin: 0 0 8px;
  font-size: 12px; font-weight: 600;
}
.crm-prefs__h svg { width: 14px; height: 14px; }
.crm-prefs__h--like    { color: var(--color-success); }
.crm-prefs__h--dislike { color: var(--color-danger); }
.crm-pref {
  display: grid; grid-template-columns: max-content 1fr max-content;
  align-items: baseline; gap: 8px; padding: 6px 0; font-size: 12px;
}
.crm-pref__k {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--color-text-muted);
}
.crm-pref__v { font-weight: 500; }
.crm-pref__src { font-size: 10px; color: var(--color-text-subtle); }
.crm-pref--dislike .crm-pref__v { color: var(--color-danger); }

/* -----------------------------------------------------------------------------
   Dates that matter
   -------------------------------------------------------------------------- */
.crm-dates { list-style: none; margin: 0; padding: 0; }
.crm-dates li {
  position: relative; display: grid; grid-template-columns: max-content max-content 1fr max-content;
  align-items: baseline; gap: 10px; padding: 8px 0; font-size: 12px;
}
.crm-dates li::after {
  content: ''; position: absolute; left: 0; bottom: 0; width: 50%; height: 1px;
  background: var(--color-border-strong);
}
.crm-dates li:last-child::after { display: none; }
.crm-dates__k { color: var(--color-text-muted); }
.crm-dates__v { font-weight: 500; }
.crm-dates__d { font-size: 11px; color: var(--color-text-subtle); }

/* -----------------------------------------------------------------------------
   Documents
   -------------------------------------------------------------------------- */
.crm-docs { display: grid; grid-template-columns: repeat(auto-fill, minmax(212px, 1fr)); gap: 10px; }
.crm-doc {
  display: flex; gap: 9px; padding: 10px 11px;
  background: var(--color-control); border: 1px solid var(--color-control-border);
  border-radius: 7px;
}
.crm-doc svg { width: 15px; height: 15px; flex: 0 0 15px; margin-top: 2px; color: var(--color-primary); }
.crm-doc__n { font-size: 12px; font-weight: 600; display: block; }
.crm-doc__m { font-size: 11px; color: var(--color-text-muted); display: block; margin-top: 2px; }

/* -----------------------------------------------------------------------------
   Live now — the client is in the storefront while you read this
   -------------------------------------------------------------------------- */
.crm-live {
  display: flex; align-items: center; gap: 7px; padding: 8px 11px;
  background: var(--color-success-50); border: 1px solid var(--color-success);
  border-radius: 7px; font-size: 12px;
}
.crm-live__dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--color-success); flex: 0 0 7px;
}

/* -----------------------------------------------------------------------------
   The agenda — reorder, discuss, defer
   -------------------------------------------------------------------------- */
.crm-agenda { list-style: none; margin: 0; padding: 0; }
.crm-agenda > li {
  display: grid; grid-template-columns: 22px 1fr max-content;
  align-items: center; gap: 10px; padding: 9px 10px; margin-bottom: 6px;
  background: var(--color-control); border: 1px solid var(--color-control-border);
  border-radius: 7px; font-size: 12px;
}
.crm-agenda > li:last-child { margin-bottom: 0; }
.crm-agenda > li.is-done { opacity: 0.6; }
.crm-agenda__grip { color: var(--color-text-subtle); cursor: grab; display: grid; place-items: center; }
.crm-agenda__grip svg { width: 14px; height: 14px; }
.crm-agenda__t { font-weight: 500; }
.crm-agenda__l { font-size: 11px; color: var(--color-text-muted); display: block; margin-top: 2px; }

/* =============================================================================
   Table column widths
   Percentages plus the inherited `table-layout: fixed`, so every column keeps
   its share of the table as the viewport changes and none of them resize with
   the length of the data inside them. The rightmost column is right-aligned
   through .data-table's own `num`/last-cell handling on the th and the td.
   ========================================================================== */
.crm-tbl-clients .col-client { width: 22%; }
/* Type and Rep each give up a point so Last Order can hold a date and its ageing
   line without either one wrapping. They are the two columns with room to give:
   the longest value in Type is "Fabricator" and in Rep a first initial and a
   surname, both well short of their share. The eight must still total 100. */
.crm-tbl-clients .col-type   { width: 9%; }
.crm-tbl-clients .col-rep    { width: 10%; }
.crm-tbl-clients .col-last   { width: 12%; }
.crm-tbl-clients .col-sales  { width: 11%; }
.crm-tbl-clients .col-open   { width: 8%; }
.crm-tbl-clients .col-next   { width: 16%; }
.crm-tbl-clients .col-status { width: 12%; }
/* Centred rather than right-aligned. Right alignment is for figures a reader
   compares down the column — these three are read one row at a time, against the
   client on that row, so they sit under their own headers instead. Written with
   the table's own class in the selector to outrank `.data-table td.num`, which
   still supplies the tabular figures. */
.data-table.crm-tbl-clients .col-last,
.data-table.crm-tbl-clients .col-sales,
.data-table.crm-tbl-clients .col-open { text-align: center; }

.crm-tbl-fu .col-due      { width: 12%; }
.crm-tbl-fu .col-client   { width: 17%; }
.crm-tbl-fu .col-contact  { width: 13%; }
.crm-tbl-fu .col-type     { width: 12%; }
.crm-tbl-fu .col-related  { width: 15%; }
.crm-tbl-fu .col-owner    { width: 10%; }
.crm-tbl-fu .col-priority { width: 9%; }
.crm-tbl-fu .col-status   { width: 12%; }

.crm-tbl-opps .col-opp    { width: 24%; }
.crm-tbl-opps .col-stage  { width: 14%; }
.crm-tbl-opps .col-value  { width: 11%; }
.crm-tbl-opps .col-close  { width: 11%; }
.crm-tbl-opps .col-prob   { width: 8%; }
.crm-tbl-opps .col-next   { width: 18%; }
.crm-tbl-opps .col-status { width: 14%; }

.crm-tbl-seg .col-seg   { width: 24%; }
.crm-tbl-seg .col-built { width: 28%; }
.crm-tbl-seg .col-uses  { width: 24%; }
.crm-tbl-seg .col-owner { width: 12%; }
.crm-tbl-seg .col-n     { width: 12%; }

/* Rebalanced for the rail: the manager's tables lost ~176px to it, and the
   columns that felt it were the ones ending in a badge. The progress meter
   gives up the width — it is read as a length, and a shorter bar is still a
   length — so the pace badge and the change sentence get theirs back. */
.crm-tbl-perf .col-rep    { width: 19%; }
.crm-tbl-perf .col-target { width: 11%; }
.crm-tbl-perf .col-actual { width: 11%; }
.crm-tbl-perf .col-bar    { width: 13%; }
.crm-tbl-perf .col-fc     { width: 11%; }
.crm-tbl-perf .col-change { width: 18%; }
.crm-tbl-perf .col-pace   { width: 17%; }

.crm-tbl-cmp .col-metric { width: 26%; }
.crm-tbl-cmp .col-cur    { width: 14%; }
.crm-tbl-cmp .col-tgt    { width: 14%; }
.crm-tbl-cmp .col-prev   { width: 14%; }
.crm-tbl-cmp .col-ly     { width: 14%; }
.crm-tbl-cmp .col-trend  { width: 18%; }

.crm-tbl-act .col-act   { width: 34%; }
.crm-tbl-act .col-vol   { width: 16%; }
.crm-tbl-act .col-prog  { width: 34%; }
.crm-tbl-act .col-ratio { width: 16%; }

.crm-tbl-targets .col-rep    { width: 14%; }
.crm-tbl-targets .col-metric { width: 18%; }
.crm-tbl-targets .col-period { width: 10%; }
.crm-tbl-targets .col-target { width: 11%; }
.crm-tbl-targets .col-actual { width: 11%; }
.crm-tbl-targets .col-pct    { width: 8%; }
.crm-tbl-targets .col-diff   { width: 11%; }
.crm-tbl-targets .col-pace   { width: 17%; }

.crm-tbl-thist .col-rep     { width: 15%; }
.crm-tbl-thist .col-metric  { width: 17%; }
.crm-tbl-thist .col-was     { width: 11%; }
.crm-tbl-thist .col-now     { width: 11%; }
.crm-tbl-thist .col-reason  { width: 25%; }
.crm-tbl-thist .col-by      { width: 21%; }

.crm-tbl-fc .col-item   { width: 24%; }
.crm-tbl-fc .col-rep    { width: 11%; }
.crm-tbl-fc .col-value  { width: 11%; }
.crm-tbl-fc .col-close  { width: 10%; }
.crm-tbl-fc .col-stage  { width: 13%; }
.crm-tbl-fc .col-last   { width: 10%; }
.crm-tbl-fc .col-next   { width: 10%; }
.crm-tbl-fc .col-cat    { width: 11%; }

.crm-tbl-fcacc .col-rep    { width: 20%; }
.crm-tbl-fcacc .col-fc     { width: 14%; }
.crm-tbl-fcacc .col-closed { width: 14%; }
.crm-tbl-fcacc .col-slip   { width: 13%; }
.crm-tbl-fcacc .col-lost   { width: 13%; }
.crm-tbl-fcacc .col-late   { width: 13%; }
.crm-tbl-fcacc .col-acc    { width: 13%; }

.crm-tbl-pipe .col-opp   { width: 22%; }
.crm-tbl-pipe .col-rep   { width: 10%; }
.crm-tbl-pipe .col-value { width: 10%; }
.crm-tbl-pipe .col-stage { width: 13%; }
.crm-tbl-pipe .col-in    { width: 8%; }
.crm-tbl-pipe .col-age   { width: 7%; }
.crm-tbl-pipe .col-close { width: 10%; }
.crm-tbl-pipe .col-next  { width: 11%; }
.crm-tbl-pipe .col-risk  { width: 13%; }

.crm-tbl-lost .col-reason { width: 26%; }
.crm-tbl-lost .col-n      { width: 12%; }
.crm-tbl-lost .col-value  { width: 16%; }
.crm-tbl-lost .col-rep    { width: 16%; }
.crm-tbl-lost .col-prod   { width: 18%; }
.crm-tbl-lost .col-share  { width: 12%; }

.crm-tbl-commit .col-what     { width: 25%; }
.crm-tbl-commit .col-owner    { width: 11%; }
.crm-tbl-commit .col-related  { width: 15%; }
.crm-tbl-commit .col-due      { width: 10%; }
.crm-tbl-commit .col-priority { width: 9%; }
.crm-tbl-commit .col-outcome  { width: 13%; }
.crm-tbl-commit .col-status   { width: 17%; }

.crm-tbl-meet .col-when   { width: 13%; }
.crm-tbl-meet .col-kind   { width: 14%; }
.crm-tbl-meet .col-who    { width: 20%; }
.crm-tbl-meet .col-fc     { width: 12%; }
.crm-tbl-meet .col-dec    { width: 12%; }
.crm-tbl-meet .col-commit { width: 17%; }
.crm-tbl-meet .col-next   { width: 12%; }

.crm-tbl-cover .col-rep     { width: 15%; }
.crm-tbl-cover .col-accts   { width: 10%; }
.crm-tbl-cover .col-active  { width: 10%; }
.crm-tbl-cover .col-quiet   { width: 12%; }
.crm-tbl-cover .col-nonext  { width: 12%; }
.crm-tbl-cover .col-under   { width: 14%; }
.crm-tbl-cover .col-bar     { width: 16%; }
.crm-tbl-cover .col-pct     { width: 11%; }

.crm-tbl-assign .col-client { width: 22%; }
.crm-tbl-assign .col-from   { width: 15%; }
.crm-tbl-assign .col-to     { width: 15%; }
.crm-tbl-assign .col-by     { width: 15%; }
.crm-tbl-assign .col-reason { width: 21%; }
.crm-tbl-assign .col-when   { width: 12%; }

.crm-tbl-appr .col-who    { width: 13%; }
.crm-tbl-appr .col-client { width: 15%; }
.crm-tbl-appr .col-txn    { width: 13%; }
.crm-tbl-appr .col-ask    { width: 15%; }
.crm-tbl-appr .col-why    { width: 18%; }
.crm-tbl-appr .col-value  { width: 10%; }
.crm-tbl-appr .col-due    { width: 8%; }
.crm-tbl-appr .col-state  { width: 8%; }

.crm-tbl-coach .col-rep    { width: 14%; }
.crm-tbl-coach .col-topic  { width: 20%; }
.crm-tbl-coach .col-ev     { width: 26%; }
.crm-tbl-coach .col-action { width: 20%; }
.crm-tbl-coach .col-review { width: 10%; }
.crm-tbl-coach .col-out    { width: 10%; }

.crm-tbl-perm .col-cap { width: 40%; }
.crm-tbl-perm .col-rep { width: 15%; }
.crm-tbl-perm .col-mgr { width: 15%; }
.crm-tbl-perm .col-fin { width: 15%; }
.crm-tbl-perm .col-adm { width: 15%; }

.crm-tbl-fields .col-field { width: 24%; }
.crm-tbl-fields .col-scope { width: 15%; }
.crm-tbl-fields .col-kind  { width: 15%; }
.crm-tbl-fields .col-opts  { width: 26%; }
.crm-tbl-fields .col-use   { width: 20%; }

.crm-tbl-runs .col-report { width: 30%; }
.crm-tbl-runs .col-scope  { width: 24%; }
.crm-tbl-runs .col-by     { width: 16%; }
.crm-tbl-runs .col-when   { width: 16%; }
.crm-tbl-runs .col-out    { width: 14%; }

/* ---- the client record's own tables ---- */
.cv-tbl-contacts .col-name    { width: 17%; }
.cv-tbl-contacts .col-title   { width: 15%; }
.cv-tbl-contacts .col-role    { width: 14%; }
.cv-tbl-contacts .col-reach   { width: 20%; }
.cv-tbl-contacts .col-pref    { width: 12%; }
.cv-tbl-contacts .col-loc     { width: 12%; }
.cv-tbl-contacts .col-state   { width: 10%; }

.cv-tbl-loc .col-loc    { width: 20%; }
.cv-tbl-loc .col-addr   { width: 27%; }
.cv-tbl-loc .col-who    { width: 17%; }
.cv-tbl-loc .col-sales  { width: 12%; }
.cv-tbl-loc .col-open   { width: 12%; }
.cv-tbl-loc .col-state  { width: 12%; }

.cv-tbl-comm .col-when    { width: 12%; }
.cv-tbl-comm .col-kind    { width: 11%; }
.cv-tbl-comm .col-who     { width: 13%; }
.cv-tbl-comm .col-detail  { width: 30%; }
.cv-tbl-comm .col-link    { width: 12%; }
.cv-tbl-comm .col-follow  { width: 11%; }
.cv-tbl-comm .col-outcome { width: 11%; }

.cv-tbl-views .col-prod  { width: 30%; }
.cv-tbl-views .col-where { width: 16%; }
.cv-tbl-views .col-times { width: 12%; }
.cv-tbl-views .col-last  { width: 14%; }
.cv-tbl-views .col-time  { width: 14%; }
.cv-tbl-views .col-act   { width: 14%; }

.cv-tbl-fin .col-doc   { width: 20%; }
.cv-tbl-fin .col-date  { width: 14%; }
.cv-tbl-fin .col-terms { width: 16%; }
.cv-tbl-fin .col-amt   { width: 14%; }
.cv-tbl-fin .col-open  { width: 14%; }
.cv-tbl-fin .col-state { width: 22%; }

.cv-tbl-hist .col-when   { width: 14%; }
.cv-tbl-hist .col-what   { width: 22%; }
.cv-tbl-hist .col-was    { width: 18%; }
.cv-tbl-hist .col-now    { width: 18%; }
.cv-tbl-hist .col-by     { width: 16%; }
.cv-tbl-hist .col-reason { width: 12%; }

/* =============================================================================
   The morning grid — Today

   Six boxes, one per kind of work, three across. A box is a count, the shape
   of what is in it, and the top three rows; it is not worked from, so nothing
   in it scrolls and no row carries a button. Clicking the box slides the full
   list in from the right, and that is where the evidence and the actions are.

   Three columns rather than two, because the day has to be readable without
   scrolling — six boxes in two rows is the whole brief in one screen.
   ========================================================================== */
/* The two rows are told to match each other. `1fr` on an implicit row resolves
   to the tallest row's content, so all six tiles end on the same edge — and the
   base rule above "See all N", which rides `margin-top: auto`, therefore lands
   at one height straight across the grid. That shared horizontal is most of
   what will read as designed rather than stamped out, and it costs one
   declaration. A flexible track still has `auto` for its minimum, so a tile
   that genuinely needs more height is never clipped. */
.dgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: 16px; }

/* A tile on the page ground, so it takes --color-control: a white tile on a
   white page is a hairline pretending to be a box.

   The uniform gap is gone and every child states its own joint below, because
   the flat 7px is what left the box without a spine: title, count, flags, rows
   and link were spaced identically, so nothing grouped with anything and the
   eye walked five equal steps with no reason to stop anywhere. The spacing is
   the zoning — which is why there is no divider between the count and the rows
   and no second ground inside the tile.

   `--dcard-pad-x` exists so the base rule's bleed cannot drift from the side
   padding it has to cancel: two numbers that must stay equal, kept as one.

   The radius comes off the 7px ceiling and onto --radius-md, the 5px every
   .card and .kpi on the platform is drawn with. 7 was never a decision about
   this tile, it was the maximum used as a default, and it is part of why six of
   these read as a framework's cards dropped onto a page of ours. */
.dcard {
  --dcard-pad-x: 14px;
  display: flex; flex-direction: column; gap: 0;
  padding: 12px var(--dcard-pad-x) 10px;
  background: var(--color-control);
  border: 1px solid var(--color-control-border); border-radius: var(--radius-md);
  font: inherit; text-align: left; text-decoration: none; color: inherit;
  cursor: pointer;
  transition: border-color 120ms, box-shadow 120ms;
}
.dcard:hover { border-color: var(--color-primary); box-shadow: var(--shadow-sm); }
.dcard:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.dcard__h { display: flex; align-items: center; gap: 8px; }

/* THE PLINTH — the tile's anchor, and the one place its temperature shows.

   Six bare glyphs on flat grey gave the eye nowhere to land, so the grid read
   as one texture rather than as six things. Seating the glyph fixes that
   without enlarging it: the icon comes DOWN 16 → 14 while the header row gains
   mass, because what the row was short of was weight, not stroke. A 26px well
   was the alternative and it out-masses the 22px count it is meant to
   introduce.

   White on the tile's control grey — the same inversion the filter chips make
   on a tinted bar (memory.md §13), three levels the eye can count instead of
   two greys it cannot; repeating --color-control here would be a box hiding on
   its own ground. content-box, because the global reset is border-box and a
   22px border-box plinth would leave a 10px drawing. Its corner is tighter than
   the tile's 5px, the way a nested radius runs.

   `:first-child` is load-bearing. `.dcard__h > svg` also matches the arrow and
   outweighs `.dcard__go`, which is why the arrow has been rendering 16px
   primary instead of the 15px subtle its own rule asks for; without the
   qualifier every tile would get a second plinth around its arrow. */
.dcard__h > svg:first-child {
  flex: none; box-sizing: content-box;
  width: 14px; height: 14px; padding: 3px;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: 4px;
  color: var(--color-primary);
}

/* The three tiles with a clock actually running already say so, in a --hot flag
   they already print: a hold closing tomorrow, a call overdue, an invoice about
   to hit credit hold. `:has()` reads that flag off the tile, so the anchor and
   the sentence under the count cannot drift apart and crm.html is not touched —
   no modifier class, no data attribute, nothing to keep in sync.

   The other three deliberately get nothing. An amber plinth on the calm tiles
   would put colour on all six and take the meaning straight back out of the
   red; half the grid hot and half of it quiet is the whole point. Rejected with
   it: washing the tile's own ground 4% red, which is under the perception floor
   on the screens this runs on and says the tile is late when it is the one line
   inside it that is. The recipe is the danger badge's own, so a hot plinth and
   a danger badge elsewhere on the page are demonstrably the same red. */
.dcard:has(.dcard__flag--hot) .dcard__h > svg:first-child {
  background: var(--color-danger-50);
  border-color: color-mix(in oklab, var(--color-danger) 34%, var(--color-danger-50));
  color: var(--color-danger-ink);
}
.dcard__t { font-size: 13px; font-weight: 600; }
.dcard__go { flex: none; width: 15px; height: 15px; margin-left: auto; color: var(--color-text-subtle); }
.dcard:hover .dcard__go { color: var(--color-primary); }

/* Value first, then what it counts, tight. 8px lifts the pair as a unit under
   the name that introduces it — it opens no distance between the number and
   its noun. */
.dcard__n { margin-top: 8px; display: flex; align-items: baseline; gap: 7px; font-size: 13px; color: var(--color-text-muted); }
.dcard__v { font-size: 22px; font-weight: 700; line-height: 1; color: var(--color-text); }

/* The breakdown under the number is one muted sentence, not a row of chips.
   Red is spent on the thing that is actually late and on nothing else.

   4px, not the 8 above it: a qualifier sitting as far from its value as the
   value sits from the title stops qualifying it and becomes the next heading
   down. Tight, it reads as the count's footnote. */
.dcard__sub { margin-top: 4px; display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px 7px; font-size: 11px; color: var(--color-text-subtle); }
.dcard__flag { display: inline-flex; align-items: baseline; gap: 7px; }
.dcard__flag + .dcard__flag::before { content: "\00B7"; color: var(--color-text-subtle); opacity: 0.5; }
.dcard__flag--hot  { color: var(--color-danger); font-weight: 600; }
.dcard__flag--warn { color: var(--color-text-muted); font-weight: 600; }

/* Three rows, one line each, and the left or right edge carries the field the
   box is sorted on — which is why no box needs its sort explained in words.

   This is the tile's one real paragraph break: the count and its footnote are
   the claim, these three are what it stands on. The margins read short because
   the rows' own 5px padding is part of the joint — 8 + 5 above and 7 + 5 below
   is the 13 and 12 the eye sees. Stripping the outer row padding to state the
   number honestly would have cost two more rules to change nothing visible. */
.dcard__list { display: flex; flex-direction: column; margin-top: 8px; margin-bottom: 7px; }
.dcard__r { position: relative; display: flex; align-items: center; gap: 8px; min-width: 0; padding: 5px 0; font-size: 12px; }
.dcard__r + .dcard__r::before {
  content: ""; position: absolute; left: 0; top: 0;
  width: 50%; height: 1px; background: var(--color-border-strong);
}
/* 10px, not 9: these are initials somebody reads, not decoration, and 9 was
   under the platform's floor (memory.md §4). Two letters still fit the same
   20px circle, so no row moves. */
.dcard__r .avatar { flex: none; width: 20px; height: 20px; font-size: 10px; }
.dcard__rn { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Tabular figures down both flanking columns. These columns ARE each box's
   sort — 38 / 12 / 4 days, $31,600 / $18,400 / $9,800 — and in proportional
   numerals the digits shift a pixel a row, so the column reads as three
   unrelated values instead of a ranking. Not mono: this is the body face's own
   numeral set, the same thing `.tnum` already does to the count above. */
.dcard__w { flex: none; width: 60px; font-size: 11px; font-weight: 600; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.dcard__c { flex: none; font-size: 11px; font-weight: 600; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.dcard__c--late { color: var(--color-danger); }

/* Pinned to the bottom so six boxes of different content still line up — and
   now given something to be pinned to. The link on its own was a sixth line in
   the stack with nothing to say it was the way out; a rule turns it into the
   tile's base, and the tile gains a bottom the eye can rest on.

   This is not a row separator and must not be mistaken for one. A row mark sits
   between peers inside a list, so it is short (50%) and drawn in the light
   hairline; this belongs to the tile's frame, so it is drawn in the frame's own
   colour and it BLEEDS past the content column to the tile's inner edges, which
   no row mark can ever do. Two properties tell them apart rather than one,
   because in Evening --color-border-strong and --color-control-border resolve
   to the same value and length alone would carry nothing.

   No second rule above the count: two horizontals in a five-line tile is a
   form, and a headline rule's height moves the moment one tile's flags wrap, so
   three tiles in a row would show it at three heights. This one is measured
   from the bottom by `margin-top: auto`, so it cannot go out of level. */
.dcard__more {
  margin-top: auto;
  margin-left: calc(var(--dcard-pad-x) * -1);
  margin-right: calc(var(--dcard-pad-x) * -1);
  padding: 9px var(--dcard-pad-x) 0;
  border-top: 1px solid var(--color-control-border);
  font-size: 12px; font-weight: 600; color: var(--color-primary-700);
}

/* The panel a box opens. Wider than a form panel — these are lists that have
   to hold a title, a reason and a row of buttons on one row. */
.so-list .slideover { width: min(760px, 100%); }
.so-list .slideover-head h2 { margin: 0; font-size: 17px; }
.so-list .slideover-foot { align-items: center; }
.so-list .slideover-foot .muted { font-size: 12px; }

/* =============================================================================
   THE PIPELINE TAB — the five buckets a relationship moves through

   The rep's Pipeline tab holds this board and nothing else: five columns, the
   whole book laid out across them, and a client moved on by dragging. It came
   over whole from the old CRM, where the same board sat under a tab called
   Warmth — which is why everything in it is still named `wm-`. The reading did
   not change, only the word above it: Warmth is what the board measures,
   Pipeline is what a rep calls the thing being measured.

   A column is 233px wide at 1440, so the client name is the one thing on the
   board that can decide a row's height. Left alone it does: "Marble & Co.
   Design Studio" wraps to three lines there, takes the row from 21px to 71px,
   and drags every row under it out of line with the four columns beside it.
   The name is therefore one line and clips; the whole name is in the title.
   ========================================================================== */
/* The temperature runs left to right: grey for a client with no reading yet,
   blue once somebody has reached out, amber as they start answering, green when
   they are buying. Green rather than the hot orange the ramp would otherwise
   end on, because Active is the healthy state and green is what says so
   everywhere else in the portal — the flame beside it is what still carries the
   heat. Dormant is not the cold end of that ramp: it is a client who was warm
   and went out, so it sits outside the run in ice, with a frosted edge and a
   snowflake rather than a fifth step.

   Every hue is mixed against `--color-surface` rather than hard-coded as a
   tint, so the board holds up on the evening theme without a second palette.
   Colour is never the only carrier: each column states its temperature with an
   icon as well. */
.wm-col { --wm: var(--wm-new); position: relative; overflow: hidden; }
.wm-col--new       { --wm: var(--wm-new); }   /* no reading yet */
.wm-col--contacted { --wm: var(--wm-contacted); }   /* cool, but alive */
.wm-col--engaged   { --wm: var(--wm-engaged); }   /* warming */
.wm-col--active    { --wm: var(--wm-active); }   /* hot, and healthy */
.wm-col--dormant   { --wm: var(--wm-dormant); }   /* frozen */

/* The strip along the top edge is the reading itself. */
.wm-col::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 3px;
  background: var(--wm);
}
/* Frost, not a solid bar — the one column that is a state rather than a step. */
.wm-col--dormant::before {
  background: repeating-linear-gradient(90deg, var(--wm) 0 5px, transparent 5px 9px);
}

/* The lighting: a tint that falls off down the header, and a shadow carrying the
   column's own hue so a warm card sits on warm ground. */
.wm-col .card-header {
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--wm) 13%, var(--color-surface)), var(--color-surface));
  border-bottom-color: color-mix(in oklab, var(--wm) 22%, var(--color-border));
}
.wm-col {
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05),
              0 12px 22px -13px color-mix(in oklab, var(--wm) 85%, transparent);
}
/* Dormant reads through the whole card, not just its edge: a client who went
   out is a different state from a client who has not warmed up yet, and the two
   sit at opposite ends of a row of five. */
/* The board is three deep — shell, column, client — so the middle one carries
   the ground and the two either side of it stay light. Dormant is the one
   column that reads through, so its own tint is mixed over that ground rather
   than over the white it used to sit on. */
.wm-col { background: var(--color-control); }
.wm-col--dormant { background: color-mix(in oklab, var(--wm) 9%, var(--color-control)); }
.wm-col--dormant .wm-card__n { color: var(--color-text-muted); }
.wm-col .card-header h3 { display: flex; align-items: center; gap: 7px; }
.wm-col .card-header h3 svg { width: 15px; height: 15px; flex: none; color: var(--wm); }

/* The count reads as part of the temperature, so it takes the hue too. The ink
   is mixed 45/55 towards the theme's own text colour rather than sitting on the
   hue: at 72% the pale end of the ramp — the ice of Dormant — went unreadable on
   its own tint. Mixing towards `--color-text` rather than a fixed near-black is
   what makes it darken on the light theme and lighten on evening. */
.wm-col__n {
  padding: 1px 8px; border-radius: 7px;
  font-size: 12px; font-weight: 700;
  background: color-mix(in oklab, var(--wm) 18%, var(--color-surface));
  border: 1px solid color-mix(in oklab, var(--wm) 38%, transparent);
  color: color-mix(in oklab, var(--wm) 45%, var(--color-text));
}

/* A column is a column, not a box that stops at its last name. All five run to
   the same depth — down the screen rather than down to the shortest list — so
   the five are read against each other: the space under New's eighth name is
   the reading, and it only says anything while Active is the same height.

   The depth is the screen's, less the chrome above the board (header, page
   title, search, KPI row, tab strip). The 420px floor is what keeps a laptop in
   a browser window from collapsing the board back into the boxes it replaced. */
.wm-col {
  display: flex; flex-direction: column;
  height: max(420px, calc(100vh - 340px));
}
.wm-col .card-header { flex: none; }

/* Only Active outruns the height at 46 names. The scroll sits on the list, not
   on the card, so the header, the count and the temperature strip hold still
   while the names move under them.

   The gap is what makes a client a THING you can pick up rather than a line in
   a list: eight pixels is enough to read each box as separate and not so much
   that a column of eight stops being a column. The list keeps a floor under it
   so a column emptied by dragging is still somewhere you can drop. */
.wm-list {
  display: flex; flex-direction: column; gap: 8px;
  flex: 1 1 auto; min-height: 60px; overflow-y: auto;
}

/* ---------- A client, as a thing you can move ----------------------------
   The column is the card's ground, so the box on it takes the control colour
   rather than the surface it is sitting on — a white box on a white panel is
   not a box. The border is the control's own, which is what carries the
   separation on the evening theme where the two grounds sit closer. */
/* The column is the tinted one now, so the client on it goes the other way up —
   the inversion memory.md §13 allows, and the same separation as before read
   from the other side. */
.wm-card {
  display: flex; align-items: flex-start; gap: 6px;
  min-width: 0; padding: 6px 8px;
  border: 1px solid color-mix(in oklab, var(--color-control-border) 45%, transparent);
  border-radius: 7px;
  background: var(--color-surface);
  cursor: grab;
}
/* Picking a card lifts it TOWARDS the primary, not towards white. White was
   the wrong direction: the column behind it is already white, so the card that
   was being pointed at was the one that disappeared into its own ground. A
   blue tint is a step away from both the card's resting grey and the column,
   and it is the same lift every other pickable row in the portal takes. */
.wm-card:hover {
  border-color: color-mix(in oklab, var(--wm) 55%, var(--color-control-border));
  background: color-mix(in oklab, var(--color-primary) 18%, var(--color-surface));
}
/* The card being carried stays in place and goes quiet, so the column it came
   from still reads while the cursor is somewhere else. */
.wm-card.is-dragging { opacity: 0.4; cursor: grabbing; }

/* The grip says the box can be picked up. It is not the only way to pick one
   up — the whole card drags — so it stays quiet until the cursor is on the
   card that owns it. */
.wm-card__grip { display: inline-flex; flex: none; padding-top: 1px; color: var(--color-border-strong); }
.wm-card__grip svg { width: 13px; height: 13px; }
.wm-card:hover .wm-card__grip { color: var(--color-text-subtle); }

.wm-card__t { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* The name absorbs the clipping and the qualifier does not: "Dallas" truncated
   to "Da..." tells nobody anything, while a clipped company name is still read
   from its first three words. */
.wm-card__n,
.wm-card__q { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wm-card__n { font-size: 13px; font-weight: 500; color: var(--color-text); text-decoration: none; }
.wm-card__q { font-size: 11px; color: var(--color-text-subtle); }
.wm-card:hover .wm-card__n { color: var(--color-primary-700); }

/* The column a card is over answers in its own colour, so where it will land is
   read off the board rather than off the cursor. */
.wm-list.is-over {
  outline: 2px dashed color-mix(in oklab, var(--wm) 60%, transparent);
  outline-offset: -4px;
  border-radius: 7px;
}

/* =============================================================================
   Inside the shell, the shell is the white one

   The panels used to sit straight on the page's grey, so a white filter bar or
   a white KPI tile was a box. On the shell they are white on white — a hairline
   pretending to be a box (memory.md §13) — so they take the control ground and
   let the shell keep the surface.
   ========================================================================== */
.tab-shell :is(.crm-filters, .crm-scope, .kpi) {
  background: var(--color-control);
  border-color: var(--color-control-border);
}
/* The left edge is the signal row's urgency, so only the fill moves. */
.tab-shell .sig { background: var(--color-control); }

/* A chip on a tinted bar inverts to white — three readable levels (shell, bar,
   chip) instead of two near-identical greys. Same inversion the dialog filter
   grid already uses. */
.tab-shell :is(.crm-filters, .crm-scope) .fchip:not(.on) {
  background: var(--color-surface);
  color: var(--color-text);
}

/* =============================================================================
   FIND A CLIENT — the one control that is always on the page

   The box and the last three clients opened share one line. On its own the box
   caps at 560px and leaves the right half of the page empty, which is the first
   thing anybody notices; the chips fill it with the only other thing worth
   reaching for before a name is typed.
   ========================================================================== */
.ct-find { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 18px; margin-bottom: var(--page-gap); }

/* The wrapper the match list and the clear button hang off. The FIELD inside it
   is the platform's .search / .input, unchanged — this page used to draw a
   bigger one of its own, with its own fill, its own height and a 19px icon
   where every other search on the platform has a 14px one. A search that looks
   different on one page reads as a different kind of search. */
.cs { position: relative; display: flex; flex: 1 1 240px; min-width: 240px; max-width: 320px; }
.cs .search { max-width: none; }
.cs .input::-webkit-search-cancel-button { display: none; }

/* Sits over the right end of the field, so clearing does not cost the field a
   slice of its width. Room is left for it by the padding on the input. */
.cs__clear {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0;
  border: 0; border-radius: 5px; background: transparent; cursor: pointer;
  color: var(--color-text-subtle);
}
.cs__clear:hover { background: var(--color-border); color: var(--color-text); }
.cs__clear svg { width: 14px; height: 14px; }
.cs:has(#cs-clear:not([hidden])) .input { padding-right: 32px; }

/* Above the tab strip and the sticky table headers under it, so the list of
   matches is never cut off by whatever the page is showing behind it.

   The ceiling is what makes it survive the book it now searches. The index is
   built off every [data-client] on the page, and the Pipeline board put
   eighty-eight of them there — a one-letter query used to paint nine rows and
   now paints seventy-six, off the bottom of the screen with no way to reach
   them. The list scrolls rather than the count being capped: a cap drops
   matches with nothing on screen to say it did. */
.cs__menu {
  position: absolute; z-index: 60; top: calc(100% + 6px); left: 0; right: 0;
  padding: 5px;
  max-height: 340px; overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border); border-radius: 7px;
  box-shadow: var(--shadow-lg);
}
.cs__row {
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 9px 10px; border: 0; border-radius: 5px;
  background: transparent; font: inherit; text-align: left; cursor: pointer;
}
.cs__row:hover { background: var(--color-primary-50); }
.cs__row .avatar { width: 32px; height: 32px; flex: none; }
.cs__row-t { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.cs__row-n { font-size: 14px; font-weight: 600; color: var(--color-text); }
.cs__row-s { font-size: 12px; color: var(--color-text-muted); }
.cs__row-go { flex: none; color: var(--color-text-subtle); display: inline-flex; }
.cs__row-go svg { width: 16px; height: 16px; }
.cs__row:hover .cs__row-go { color: var(--color-primary); }
.cs__none { padding: 12px 10px; font-size: 13px; color: var(--color-text-muted); }

/* Sits directly beside the box, not out at the right edge: the two are read as
   one thing — a name you type, or a name you already opened — and a chip a whole
   page away from the field it stands in for does not read as the shorter route. */
.ct-recent { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0; }
.ct-recent__l {
  margin-right: 2px;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--color-text-subtle);
  /* The row centres its items by their BOXES, and this label's box inherited the
     page's 1.5 line-height — five and a half pixels of it below the baseline,
     empty, because a word in caps has no descenders. Centring the box therefore
     sat the letters high against the chips. Closing the box to the text is what
     centres what you can actually see. */
  line-height: 1;
}
.ct-recent__chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 12px 6px 6px; border-radius: 7px;
  background: var(--color-control); border: 1px solid var(--color-control-border);
  font: inherit; font-size: 13px; cursor: pointer; color: var(--color-text);
}
.ct-recent__chip:hover { border-color: var(--color-primary); color: var(--color-primary-700); }
.ct-recent__chip .avatar { width: 24px; height: 24px; font-size: 10px; }
/* A long company name shortens rather than pushing the search box off its own
   row. The initials beside it stay whole, so the chip is still identifiable at
   its shortest. */
.ct-recent__n {
  max-width: 15ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- Viewing As, on the top bar -----------------------------------------
   The switch sits in the shell's own action cluster, ahead of the search, so
   these rules are scoped to the top bar and the old CRM — which still carries
   the switch beside its page title — keeps its own sizing untouched.

   The label takes the Recent label's treatment: 11px, 600, on the subtle grey.
   At 12px in the muted grey it sat a point above every other small label in the
   bar and read as a heading for the cluster rather than as the caption of the
   one control after it. The row is 56px and the switch is 36, so nothing here
   needs to change the bar's height. */
.header .crm-role { flex: none; }
.header .crm-role__label {
  font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
  color: var(--color-text-subtle);
  line-height: 1;
}

/* A KPI's second line is a few words on ONE line, always. It qualifies the
   number — the records behind it are a click away in the panel the tile opens,
   so nothing here has to earn its place by saying more. */
.kpi .meta { white-space: nowrap; overflow: hidden; }


/* =============================================================================
   THE MANAGER'S TWO SCREENS

   Twelve tiles and twelve tabs on one page means scrolling past the numbers to
   reach the work. So the numbers are the first screen and the work is the
   second, and the twelve views stand up as a rail on the left of their own
   content — twelve names down the side are readable at once, which twelve
   names across the top never were.

   The rep is untouched by all of this: one row of tiles, one strip of tabs,
   both on one screen.
   ========================================================================== */

/* Rail and content. The rail is as narrow as its longest name — every pixel it
   takes comes off tables that are already wide. `align-items: start` keeps it
   its own height instead of stretching it down the side of a long panel. */
.crm-work--rail { display: grid; grid-template-columns: 162px minmax(0, 1fr); gap: 14px; align-items: start; }

/* The strip stands up. Everything the horizontal version needs — the scroll,
   the negative margin that laps it onto the panel, the bottom rule — is what a
   rail must not have. */
.crm-work--rail > .tabs {
  flex-direction: column; gap: 1px;
  margin: 0; padding: 0;
  border-bottom: 0;
  overflow: visible;
  /* The rail follows a long panel down rather than scrolling off the top of
     it: the point of the second screen is that the twelve views stay reachable. */
  position: sticky; top: 12px;
}
/* A rail item is a row of text, not a target in a crowded strip: the fluid
   sizing that keeps twelve tabs on one line is what makes twelve rows tall. */
.crm-work--rail > .tabs .tab {
  width: 100%; justify-content: flex-start; gap: 9px;
  padding: 7px 9px;
  font-size: 13px;
  border-bottom: 0; border-radius: 5px;
}
.crm-work--rail > .tabs .tab svg { width: 16px; height: 16px; }
/* The count goes to the far end. Read down a list, the name is what you are
   looking for and the number is what you check once you have found it — in
   front of the name it is neither. */
.crm-work--rail > .tabs .tab .count {
  order: 2; margin-left: auto;
  font-size: 11px; font-weight: 600; color: var(--color-text-subtle);
}
.crm-work--rail > .tabs .tab.active .count { color: var(--color-primary-700); }
/* The active mark moves from under the tab to its leading edge, which is where
   the eye reads a vertical list from. */
.crm-work--rail > .tabs.tabs--attached .tab.active {
  background: var(--color-primary-50);
  border-bottom-color: transparent;
  box-shadow: inset 2px 0 0 0 var(--color-primary);
}

/* Nothing is above the panel any more, so it closes its top corners. */
.crm-work--rail > .tab-shell { border-radius: var(--radius-md); }

/* Back to the numbers. It is not a tab — it does not open a panel — so it sits
   apart from the twelve, quiet, with air under it rather than a rule: a full
   line across a 162px rail is heavier than anything it separates. */
.crm-rail__back {
  display: inline-flex; align-items: center; gap: 8px;
  width: 100%; padding: 6px 9px; margin-bottom: 8px;
  border: 0; border-radius: 5px;
  background: transparent; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 500;
  color: var(--color-text-subtle);
}
.crm-rail__back:hover { color: var(--color-primary-700); }
.crm-rail__back svg { width: 14px; height: 14px; flex: none; }


/* =============================================================================
   CLIENT TYPES DIALOG
   Two columns, because a client type is two things: what it is called and how
   many clients say they are one. The name is the only one entered — a count is
   arrived at, not typed. Nothing about price appears here; a price list names
   the client types it applies to, and that is stated on Pricing > Price Lists.

   The other two Page Settings dialogs need nothing of their own: `.vis-pick`
   and its parts are in app.css, shared with Quotes.
   ========================================================================== */
.ct-head { display: flex; align-items: center; gap: 10px; margin: 0 0 8px; }
.ct-head .dropdown-section-label { padding: 0; }

#grid-ctypes { min-width: 340px; }
#grid-ctypes .ct-c-name { width: 70%; }
#grid-ctypes .ct-c-n    { width: 30%; text-align: center; }
#grid-ctypes .input { width: 100%; }


/* =============================================================================
   Responsive
   The tab strip wraps, the grids collapse, and every table keeps its own
   horizontal scroll rather than dragging the page sideways.
   ========================================================================== */
@media (max-width: 1200px) {
  .crm-prefs { grid-template-columns: minmax(0, 1fr); }
  .crm-settings { grid-template-columns: minmax(0, 1fr); }
  .crm-settings > .crm-setting:nth-last-child(-n+2)::after { display: block; }
  .crm-settings > .crm-setting:last-child::after { display: none; }
}

@media (max-width: 900px) {
  .dgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cv-hero { flex-wrap: wrap; }
  .cv-hero__acts { width: 100%; }
  .crm-glance__note { margin-left: 0; width: 100%; }
  .sig { flex-wrap: wrap; }
  .sig__side { flex-direction: row; width: 100%; justify-content: flex-start; }
  /* The board stops being one row here, so a full-height column stops being a
     column and becomes a screen of its own. Each one keeps a ceiling instead
     and scrolls under it — five of these stacked is a page, not a board. */
  .wm-col { height: auto; max-height: 340px; }
}

/* Touch targets. Nothing tappable on either page goes under 44px on a phone or
   a tablet — the shared components are sized for a mouse, so the floor is
   stated here rather than in app.css, where it would move every other screen. */
@media (max-width: 768px) {
  .page .btn,
  .page .btn-sm,
  .page .subtab,
  .page .fchip,
  .page .tab,
  .page .crm-report,
  .page .crm-stage,
  .page .add-row-btn,
  .page .crm-link { min-height: 44px; }
  .page .add-row-btn { width: 44px; }
  .page .crm-link { display: inline-flex; align-items: center; }
  /* A name on the Pipeline board is not a tap target of its own — the card
     around it is, and it already clears 44px. Left in the rule above, the name
     takes a 44px floor that doubles every card, and the flex box that comes
     with it silently kills the ellipsis the ported block relies on to keep a
     long company name on one line. */
  .page .wm-card__n { min-height: 0; display: block; }
  .modal .btn, .slideover .btn, .modal .fchip, .slideover .fchip { min-height: 44px; }
  /* The header's action row is four controls and a segmented switch; on a phone
     it wraps instead of running off the side. */
  .page-header { flex-wrap: wrap; }
  .page-actions { width: 100%; flex-wrap: wrap; }
  .page-header .crm-role { flex: 1 1 100%; }
  .page-header .crm-role .subtabs { flex: 1; }
  .page-header .crm-role .subtab { flex: 1; justify-content: center; }
  /* On a phone the bar keeps the two buttons and drops the caption: "Sales Rep"
     and "Sales Manager" say what they are without a word over them, and the
     word is what the narrow bar cannot spare. */
  .header .crm-role__label { display: none; }
  /* The find row stops sharing its line: the box takes the width and the
     recently-opened chips wrap under it. */
  .cs { flex-basis: 100%; max-width: none; }
  .cs__row,
  .ct-recent__chip { min-height: 44px; }
}

@media (max-width: 640px) {
  /* One across, so there is no row of peers to line the base rule up with, and
     the equal-height track stops paying for itself: it would inflate all six to
     whichever tile's flag line wraps first and leave five of them with dead
     space above the rule. Handed back to auto. */
  .dgrid { grid-template-columns: minmax(0, 1fr); grid-auto-rows: auto; }
  .crm-reports { grid-template-columns: minmax(0, 1fr); }
  .crm-docs { grid-template-columns: minmax(0, 1fr); }
  .cv-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .crm-scope { flex-direction: column; align-items: stretch; }
  .crm-scope .select { width: 100%; }
  .crm-scope__field { justify-content: space-between; }
  .crm-setting { grid-template-columns: 34px 1fr max-content; }
  .crm-setting__v { grid-column: 3; }
  .crm-fact-row { grid-template-columns: 1fr max-content; }
  .cv-hero__acts .btn { flex: 1; justify-content: center; }
}
