/* ============================================================================
   The Captain — polish layer
   ============================================================================

   The portal's design system: one set of tokens, one set of shared components,
   and an explicit block that hands that look to the selectors already in
   styles.css so no surface is left behind.

   HOW THIS FILE LOADS
   server/assets.py injects the <link> at render time, immediately after the
   styles.css link. That position is deliberate and load-bearing:

     styles.css          base + ~90 page sections
     polish.css          THIS FILE — wins the base cascade on source order
     page <style> block  a page's own deliberate design still wins
     JS-injected <style> runtime blocks (common.js et al) win over everything

   So: to restyle something shared, write it here. To keep a page's own look
   (my-bonus is intentionally always-dark, clock-station is intentionally
   self-contained), do nothing — the ordering already protects it.

   RULES FOR THIS FILE
   - No `!important`. Ever. A ratchet test fails the build if one appears.
     Where a page-local block shadows a shared class, the one sanctioned
     escalation is `:root .pcu-badge` (0,2,0). Needing more than that means the
     page block should be edited instead — fix the page.
   - Never hardcode a neutral, a status colour, a radius or a duration. Use the
     tokens below, so light/dark and any future retune stay in one place.
   - Brand stays brand: --navy, --red, --gold, --cream and the logo are not
     up for redesign here.
   - The phone tail at the BOTTOM of this file must stay last. Because this file
     loads after styles.css, its desktop rules out-order the MOBILE APP SHELL
     block at styles.css:12778, so every phone override has to be re-asserted
     down there.
   ============================================================================ */


/* ============================================================================
   1. TOKENS
   ============================================================================ */

:root {
  /* ---- Status scale ------------------------------------------------------
     Soft tint + saturated DARK ink. Never a saturated fill with white text:
     filled chips read as buttons, crush at small sizes, and are the thing the
     reference material calls out explicitly as the wrong answer.

     Every ink clears 5.3:1 on its own tint, on --card and on --bg, so a badge
     is legible wherever it lands. Verified numerically, not by eye. */
  --ok-bg:      #e8f6ee;  --ok-ink:      #0f6b39;  --ok-bd:      #bfe5d0;
  --warn-bg:    #fdf3e0;  --warn-ink:    #8a5a00;  --warn-bd:    #f0dcaf;
  --bad-bg:     #fcebee;  --bad-ink:     #a80d2a;  --bad-bd:     #f3c9d1;
  --info-bg:    #e9f1fc;  --info-ink:    #14508f;  --info-bd:    #c6dbf5;
  --neutral-bg: #eef1f6;  --neutral-ink: #47566a;  --neutral-bd: #dce3ec;

  /* The 2026 layer's status aliases (styles.css:7724) re-pointed at the new
     inks, so roughly 40 existing consumers inherit the contrast fix for free.
     --bad darkens slightly from brand red; brand red itself is untouched and
     every navy/red/gold FILL stays exactly as it is. */
  --ok:   var(--ok-ink);
  --warn: var(--warn-ink);
  --bad:  var(--bad-ink);

  /* ---- Corrections to styles.css ----------------------------------------
     --slate is referenced 19 times in styles.css (326, 373, 417, 1265,
     7123-7253) and defined nowhere, so every one of those labels inherits its
     parent's colour and reads at full ink weight instead of muted. */
  --slate: var(--ink-soft);

  /* --tint-* and --raise are defined ONLY under [data-theme="dark"]
     (styles.css:3879-3885). In light mode roughly 40 selectors resolve them to
     nothing and the background falls to transparent. These are the light-mode
     halves that were missing. */
  --tint-red:   var(--bad-bg);      --tint-red-bd:   var(--bad-bd);
  --tint-amber: var(--warn-bg);     --tint-amber-bd: var(--warn-bd);
  --tint-green: var(--ok-bg);       --tint-green-bd: var(--ok-bd);
  --tint-blue:  var(--info-bg);     --tint-blue-bd:  var(--info-bd);
  --raise:      var(--surface-2);   /* a surface that lifts above --card */

  /* ---- Elevation ---------------------------------------------------------
     Four rungs. --e2 and --e3 are byte-identical to the existing --shadow and
     --shadow-lift, so the 282 rules already using those move with the scale
     rather than drifting away from it. */
  --e1: 0 1px 2px rgba(16, 24, 40, .05);
  --e2: 0 1px 2px rgba(16, 24, 40, .04), 0 4px 14px rgba(16, 24, 40, .06);
  --e3: 0 2px 6px rgba(16, 24, 40, .07), 0 12px 32px rgba(16, 24, 40, .12);
  --e4: 0 8px 24px rgba(16, 24, 40, .10), 0 28px 64px rgba(16, 24, 40, .18);
  --ring: 0 0 0 3px rgba(75, 146, 219, .18);

  /* ---- Radius ------------------------------------------------------------
     --radius-sm/md/lg (8/12/16) already exist at styles.css:7714. --radius-card
     is the single knob for the whole card family. */
  --radius-card: 16px;
  --radius-xl:   20px;
  --radius-pill: 999px;

  /* ---- Motion ------------------------------------------------------------
     Short, eased, consistent. --dur-sheet matches the bottom-sheet transition
     already at styles.css:12861 so phone and desktop share one timing. The
     prefers-reduced-motion block at styles.css:7546 neutralises all of it. */
  --dur-press: 110ms;
  --dur-state: 170ms;
  --dur-enter: 240ms;
  --dur-sheet: 340ms;
  --ease-out:    cubic-bezier(.2, .8, .3, 1);     /* decelerate: enters, hovers */
  --ease-in-out: cubic-bezier(.4, 0, .2, 1);      /* symmetric: toggles */
  --ease-spring: cubic-bezier(.2, .9, .3, 1.2);   /* = the existing pc-pop */

  /* ---- Type scale --------------------------------------------------------
     Semantic names, shorthand values, so one variable sets weight/size/leading.
     Display type (Trade Gothic Cond) stays for stat numbers and page headings;
     everything functional is --font-ui. */
  --t-eyebrow: 700 11px/1.2 var(--font-ui);
  --t-label:   600 12.5px/1.35 var(--font-ui);
  --t-body-sm: 500 13.5px/1.5 var(--font-ui);
  --t-body:    500 14.5px/1.55 var(--font-ui);
  --t-title:   700 18px/1.3 var(--font-ui);
  --t-h2:      700 22px/1.25 "Trade Gothic Cond", var(--font-ui);
  --t-stat:    700 clamp(26px, 3vw, 34px)/1.05 "Trade Gothic Cond", var(--font-ui);
  --t-code:    500 12.5px/1.45 var(--mono-code);

  /* Uppercase micro-eyebrows are intentional portal-wide; this is their
     tracking, kept in one place so they stay consistent. */
  --track-eyebrow: .09em;
}

:root[data-theme="dark"] {
  /* Alpha layers rather than flat hexes, so a tint composites correctly over
     BOTH --card (#0f1d33) and --bg (#07111f). Every ink clears 6.5:1 on both. */
  --ok-bg:      rgba(52, 199, 123, .18);  --ok-ink:      #6ee7a0;
  --ok-bd:      rgba(52, 199, 123, .38);
  --warn-bg:    rgba(255, 182, 18, .18);  --warn-ink:    #ffcb5c;
  --warn-bd:    rgba(255, 182, 18, .36);
  --bad-bg:     rgba(232, 59, 84, .20);   --bad-ink:     #ff9aac;
  --bad-bd:     rgba(232, 59, 84, .40);
  --info-bg:    rgba(75, 146, 219, .20);  --info-ink:    #8fc0f5;
  --info-bd:    rgba(75, 146, 219, .40);
  --neutral-bg: rgba(255, 255, 255, .07); --neutral-ink: #a7b8ce;
  --neutral-bd: rgba(255, 255, 255, .16);

  /* styles.css:3879 already defines --raise and --tint-* for dark. Re-stating
     them here keeps the two themes reading from one place; the values are the
     same intent, expressed through the status scale above. */
  --tint-red:   var(--bad-bg);      --tint-red-bd:   var(--bad-bd);
  --tint-amber: var(--warn-bg);     --tint-amber-bd: var(--warn-bd);
  --tint-green: var(--ok-bg);       --tint-green-bd: var(--ok-bd);
  --tint-blue:  var(--info-bg);     --tint-blue-bd:  var(--info-bd);

  --e1: 0 1px 2px rgba(0, 0, 0, .35);
  --e2: 0 1px 2px rgba(0, 0, 0, .40), 0 6px 18px rgba(0, 0, 0, .35);
  --e3: 0 2px 8px rgba(0, 0, 0, .50), 0 16px 40px rgba(0, 0, 0, .50);
  --e4: 0 10px 30px rgba(0, 0, 0, .55), 0 32px 72px rgba(0, 0, 0, .60);
  --ring: 0 0 0 3px rgba(75, 146, 219, .30);
}


/* ============================================================================
   2. SHARED COMPONENTS

   Each block does two things: define a `pc-` class that new work can use, and
   re-point the selectors already in styles.css onto the same look, so no
   surface is left behind. The re-point lists are the reason this file is long;
   they are also the reason the portal stops looking like ninety pages.
   ============================================================================ */


/* ---- 2.1 Badges, pills and chips -------------------------------------------
   Soft tint, saturated dark ink, hairline border. The border matters: a tint
   is only 1.02:1 against the page canvas, so without it a badge sitting
   outside a white card would have no edge at all.

   Filled chips (white text on a saturated block) are deliberately gone. They
   read as buttons, they crush at 10px, and they were the source of every
   failing contrast ratio on the portal: "in review" was gold on amber at
   1.57:1, and heck_yes/heck_no/offer were white on mid-tone fills.           */

.pcu-badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--neutral-bd);
  background: var(--neutral-bg);
  color: var(--neutral-ink);
  font: var(--t-eyebrow);
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}
.pcu-badge--ok   { background: var(--ok-bg);   color: var(--ok-ink);   border-color: var(--ok-bd); }
.pcu-badge--warn { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-bd); }
.pcu-badge--bad  { background: var(--bad-bg);  color: var(--bad-ink);  border-color: var(--bad-bd); }
.pcu-badge--info { background: var(--info-bg); color: var(--info-ink); border-color: var(--info-bd); }
/* Sentence case for badges carrying a real phrase rather than a status word. */
.pcu-badge--text { text-transform: none; letter-spacing: 0; font: var(--t-label); }

/* A leading dot, for status that reads faster as a colour than as a word. */
.pcu-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }

/* -- re-point: the existing badge vocabulary -- */
.state-badge, .badge, .rpt-pill, .pc-ntag {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--neutral-bd);
  background: var(--neutral-bg);
  color: var(--neutral-ink);
  border-radius: var(--radius-pill);
  padding: 3px 9px;
  font: var(--t-eyebrow);
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.state-badge--done, .badge--active, .badge--heck_yes, .badge--yes,
.rpt-pill--ok, .pc-ntag--resolved {
  background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-bd);
}
.state-badge--in_review, .badge--offer, .rpt-pill--warn {
  background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-bd);
}
.state-badge--changes_requested, .badge--heck_no, .badge--no, .rpt-pill--bad {
  background: var(--bad-bg); color: var(--bad-ink); border-color: var(--bad-bd);
}
.badge--draft, .badge--archived, .state-badge--draft, .state-badge--todo,
.rpt-pill--flat {
  background: var(--neutral-bg); color: var(--neutral-ink); border-color: var(--neutral-bd);
}

/* -- re-point: delta chips (up / down / flat) --
   An arrow is added by ::before so the meaning survives for anyone who cannot
   separate the green from the red. */
.delta { font: var(--t-label); padding: 3px 9px; border-radius: var(--radius-pill);
  border: 1px solid var(--neutral-bd); background: var(--neutral-bg);
  color: var(--neutral-ink); display: inline-flex; align-items: baseline;
  gap: 5px; white-space: nowrap; }
/* The comparison never wraps mid-phrase; on a narrow card it drops out rather
   than turning the chip into two ragged lines. */
.delta__cmp { white-space: nowrap; }
@media (max-width: 420px) { .delta__cmp { display: none; } }
.delta--up   { background: var(--ok-bg);  color: var(--ok-ink);  border-color: var(--ok-bd); }
.delta--down { background: var(--bad-bg); color: var(--bad-ink); border-color: var(--bad-bd); }
.delta--flat { background: var(--neutral-bg); color: var(--neutral-ink); border-color: var(--neutral-bd); }
/* No arrow is added here: the markup already ships one (▲ / ▼ / ■), so a
   ::before produced "↑ ▲ 87.9%" on every stat card. The direction glyph and the
   tint together carry the meaning for anyone who cannot separate the hues. */

/* -- re-point: filter pills (.pill) --
   Active state keeps the navy fill: this is a control, not a status, and the
   filled state is what makes "which filter am I on" readable at a glance. */
.pill {
  font: var(--t-label); text-transform: none; letter-spacing: 0;
  padding: 7px 13px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--surface);
  color: var(--muted);
  transition: background var(--dur-state) var(--ease-out),
              color var(--dur-state) var(--ease-out),
              border-color var(--dur-state) var(--ease-out);
}
.pill:hover { color: var(--text); border-color: var(--muted); }
.pill--active { background: var(--brand); color: var(--cream); border-color: var(--brand); }


/* ---- 2.2 Surfaces ----------------------------------------------------------
   One card recipe: hairline, 16px radius, and elevation that is felt rather
   than seen. The reference dashboards all sit a white card on a soft neutral
   field and let the border do the work; a heavy shadow is what made the old
   cards read as floating rather than placed.                                */

.pcu-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--e1);
  padding: 20px 22px;
}
.pcu-card--flush { padding: 0; overflow: hidden; }
.pcu-card--pad-lg { padding: 26px 28px; }
.pcu-card--interactive {
  cursor: pointer;
  transition: box-shadow var(--dur-state) var(--ease-out),
              transform var(--dur-state) var(--ease-out),
              border-color var(--dur-state) var(--ease-out);
}
@media (hover: hover) {
  .pcu-card--interactive:hover {
    box-shadow: var(--e2); transform: translateY(-1px); border-color: var(--muted);
  }
}
.pcu-card__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
}
.pcu-card__title { font: var(--t-title); color: var(--text); margin: 0; }
.pcu-card__sub { font: var(--t-body-sm); color: var(--muted); margin: 2px 0 0; }

/* -- re-point: the card-shaped surfaces already in the portal -- */
.metric-card, .rpt-stat, .eosr-card, .rpt-pnl, .role-card, .stat-card,
.pnl-chart, .pnl-unpublished, .eos-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--e1);
  background: var(--surface);
}
/* The signature red edge stays, but as a placed accent rather than a slab:
   3px, inset from the corners, with rounded ends. */
.metric-card::before {
  width: 3px; inset: 14px auto 14px 0; border-radius: var(--radius-pill);
}
/* "Data source not connected yet" hardcoded a cream fill and a warm dashed
   border, both of which went white-on-white in dark mode. */
.metric-card.metric-card--pending {
  background: var(--warn-bg); border: 1px dashed var(--warn-bd); box-shadow: none;
}
.metric-card.metric-card--pending .metric-card__label { color: var(--warn-ink); }


/* ---- 2.3 Tables ------------------------------------------------------------
   Look only, never layout: these selectors already carry their own grid or
   column rules and this must not fight them. A light uppercase header, roomy
   rows, hairline separators, a hover tint, and tabular figures so columns of
   numbers line up.                                                          */

.pcu-table { width: 100%; border-collapse: collapse; font: var(--t-body-sm); }
.pcu-table thead th,
.admin-table thead th, .perm-table thead th, .qc-rep-table thead th,
.wk-table thead th, .sched-roster__table thead th, .team-360table thead th,
.svc-table thead th, .sd-htable thead th, .pnl-table thead th,
.md-table thead th, .proc__table thead th, .ed-difftable thead th {
  font: var(--t-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--muted);
  text-align: left;
  padding: 10px 12px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.pcu-table tbody td,
.admin-table tbody td, .perm-table tbody td, .qc-rep-table tbody td,
.wk-table tbody td, .sched-roster__table tbody td, .team-360table tbody td,
.svc-table tbody td, .sd-htable tbody td, .pnl-table tbody td,
.md-table tbody td, .proc__table tbody td, .ed-difftable tbody td {
  padding: 11px 12px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: middle;
}
@media (hover: hover) {
  .pcu-table tbody tr:hover,
  .admin-table tbody tr:hover, .perm-table tbody tr:hover,
  .qc-rep-table tbody tr:hover, .svc-table tbody tr:hover,
  .pnl-table tbody tr:hover, .wk-table tbody tr:hover {
    background: var(--zebra);
  }
}
/* Numbers align on the decimal even when the row heights differ. */
.pcu-table td[data-num], .pcu-table .num,
.pnl-table td, .wk-table td.num, .svc-table td.num {
  font-variant-numeric: tabular-nums;
}


/* ---- 2.4 List rows ---------------------------------------------------------
   The portal is mostly lists. One rhythm: a title, a quiet second line, a
   hairline between, a hover tint, and a chevron that can actually be seen.  */

.pcu-listrow {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  transition: background var(--dur-state) var(--ease-out);
}
.pcu-listrow:last-child { border-bottom: 0; }
@media (hover: hover) { .pcu-listrow:hover { background: var(--zebra); } }
.pcu-listrow__main { min-width: 0; flex: 1; }
.pcu-listrow__title { font: 600 14.5px/1.35 var(--font-ui); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pcu-listrow__sub { font: var(--t-body-sm); color: var(--muted); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pcu-listrow__chev { flex: none; color: var(--muted); font-size: 18px; line-height: 1; }

/* -- re-point: chevrons --
   .pc-go was --line (#e5e9f0 on white = 1.22:1) and --line-card in dark
   (1.33:1). It is the affordance that says "this row opens", and it was
   effectively invisible in both themes. */
.pc-go, .cust-locrow__chev, .rpt-row__chev {
  color: var(--muted);
  transition: color var(--dur-state) var(--ease-out),
              transform var(--dur-state) var(--ease-out);
}
@media (hover: hover) {
  a:hover > .pc-go, .pc-cust:hover .pc-go, .cust-locrow:hover .cust-locrow__chev {
    color: var(--text); transform: translateX(2px);
  }
}


/* ---- 2.5 Empty states ------------------------------------------------------
   There were twenty-eight of these, each invented on its own page. One
   recipe: centred, quiet, and never a dead end.                            */

.pcu-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; text-align: center;
  padding: 40px 24px;
  color: var(--muted);
}
.pcu-empty__icon { font-size: 26px; opacity: .5; margin-bottom: 2px; }
.pcu-empty__title { font: var(--t-title); color: var(--text); }
.pcu-empty__body { font: var(--t-body-sm); color: var(--muted); max-width: 46ch; }

.eos-empty, .inv-empty, .cust-empty, .cust-results__empty, .binder__empty,
.board__empty, .fleet-empty, .admin-empty, .chart-empty, .chat-empty,
.hist-tbl__empty, .alys-empty, .nt-empty, .fr-empty, .wk-empty,
.qc-empty, .todo-empty, .sched-panel__empty {
  color: var(--muted);
  text-align: center;
  padding: 36px 24px;
  font: var(--t-body-sm);
}
.inv-empty__title, .alys-empty__title, .qc-empty-title, .pcu-empty__title {
  font: var(--t-title); color: var(--text);
}


/* ---- 2.6 Avatars -----------------------------------------------------------
   Eight identical implementations of "navy circle, cream initials" existed.  */

.pcu-avatar, .user-chip__avatar, .admin-avatar, .team-row__avatar,
.team-head__avatar, .eos-node__avatar, .pcard__avatar, .snap__avatar,
.pcm-sheet__avatar {
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--brand);
  color: var(--cream);
  font: 700 12px/1 var(--font-ui);
  letter-spacing: .02em;
  text-transform: uppercase;
  flex: none;
  overflow: hidden;
}
.pcu-avatar { width: 32px; height: 32px; }
.pcu-avatar--sm { width: 24px; height: 24px; font-size: 10px; }
.pcu-avatar--lg { width: 44px; height: 44px; font-size: 15px; }


/* ---- 2.7 Buttons -----------------------------------------------------------
   Two workhorse families exist (.btn and .todo-btn) and they had drifted
   apart. They get one treatment here rather than one more family.

   Primary is the brand navy fill. Secondary is a surface with a hairline.
   The press state is the detail that makes a button feel native: a real,
   fast, 1px settle rather than a colour change.                            */

.pcu-btn, .btn, .todo-btn, .eos-btn, .field-btn, .sections-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  font: 600 13.5px/1 var(--font-ui);
  letter-spacing: 0; text-transform: none;
  padding: 10px 16px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-state) var(--ease-out),
              border-color var(--dur-state) var(--ease-out),
              box-shadow var(--dur-state) var(--ease-out),
              color var(--dur-state) var(--ease-out),
              transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) {
  .pcu-btn:hover, .btn:hover, .todo-btn:hover, .eos-btn:hover, .field-btn:hover {
    background: var(--surface-2); border-color: var(--muted);
  }
}
.pcu-btn:active, .btn:active, .todo-btn:active, .eos-btn:active, .field-btn:active {
  transform: translateY(1px);
}

.pcu-btn--primary, .btn--primary, .btn--active, .todo-btn--primary, .eos-btn--primary {
  background: var(--brand); border-color: var(--brand); color: var(--cream);
}
@media (hover: hover) {
  .pcu-btn--primary:hover, .btn--primary:hover, .todo-btn--primary:hover,
  .eos-btn--primary:hover {
    background: var(--navy-deep); border-color: var(--navy-deep); color: var(--cream);
  }
}
.pcu-btn--ghost, .btn--ghost {
  background: transparent; border-color: var(--border); color: var(--text);
}
/* Danger reads as danger without shouting: tinted at rest, filled on intent. */
.pcu-btn--danger, .btn--danger {
  background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad-ink);
}
@media (hover: hover) {
  .pcu-btn--danger:hover, .btn--danger:hover {
    background: var(--red); border-color: var(--red); color: #fff;
  }
}
.pcu-btn--sm, .btn--sm, .btn--mini { padding: 7px 12px; font-size: 12.5px;
  border-radius: var(--radius-sm); }
.pcu-btn--icon { padding: 9px; width: 36px; height: 36px; }

/* .pc-callback shipped white text on a mid-blue fill (3.26:1, an AA failure on
   every page's topbar). It becomes a tinted action like the rest. */
.pc-callback {
  background: var(--info-bg); border: 1px solid var(--info-bd); color: var(--info-ink);
  border-radius: var(--radius-md);
}
@media (hover: hover) {
  .pc-callback:hover { background: var(--info-bd); color: var(--info-ink); }
}

button[disabled], .btn[disabled], .btn[aria-disabled="true"],
.pcu-btn[disabled], input[disabled], select[disabled], textarea[disabled] {
  opacity: .55; cursor: not-allowed;
}


/* ---- 2.8 Fields ------------------------------------------------------------
   styles.css:7799 sets a good base, but on an ELEMENT selector, so every
   class-based field on every page out-specifies it and keeps its own look.
   These are the class-based ones, brought onto the same base.              */

.pcu-input, .form-input, .field input, .field select, .field textarea,
.rpt-select, .crm-search__input, .hiring-bar select {
  font: var(--t-body); font-size: 14px;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  transition: border-color var(--dur-state) var(--ease-out),
              box-shadow var(--dur-state) var(--ease-out);
}
/* Padding only on the new class. Several existing fields carry a leading icon
   and encode the room for it in their own padding-left; setting a uniform
   padding here slid the text under the icon on the Customers search. */
.pcu-input { padding: 9px 12px; }
.pcu-input:focus, .form-input:focus, .field input:focus, .field select:focus,
.field textarea:focus, .rpt-select:focus, .crm-search__input:focus {
  outline: none; border-color: var(--blue); box-shadow: var(--ring);
}
.pcu-field__label, .form-field__label, .field__label {
  font: var(--t-label); color: var(--muted); display: block; margin-bottom: 6px;
}
.pcu-field__hint, .form-field__hint, .field-hint {
  font: var(--t-body-sm); color: var(--muted); margin-top: 5px;
}


/* ---- 2.9 Segmented controls ------------------------------------------------
   Eleven separate implementations of the same control. One look: a quiet
   track, and a raised surface "thumb" marking the active option. The thumb
   carries weight and full ink as well as elevation, so the active state is
   still obvious without a saturated fill.                                  */

.pcu-segmented, .scope-toggle, .workscope, .sched__viewtoggle, .rock-scope,
.rpt-groupswitch, .dept-switch, .l10-seg, .tech-seg, .pnl-viewtoggle,
.history-toggle {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  /* A segmented control with more options than fit must SCROLL, not spill.
     .dept-switch already did this with overflow-x on a block flex container;
     inline-flex shrink-wraps to its content, so without these the department
     switcher ran 290px off the right edge of a phone. */
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}
.pcu-segmented::-webkit-scrollbar, .dept-switch::-webkit-scrollbar,
.scope-toggle::-webkit-scrollbar, .l10-seg::-webkit-scrollbar { display: none; }
.pcu-segmented__btn, .scope-btn, .workscope__btn, .sched__viewbtn,
.rock-scope__btn, .dept-switch__pill, .tech-seg__btn, .pnl-viewtoggle__btn {
  flex: none;
}
.pcu-segmented__btn, .scope-btn, .workscope__btn, .sched__viewbtn,
.rock-scope__btn, .rpt-groupswitch button, .dept-switch__pill, .l10-seg button,
.tech-seg__btn, .pnl-viewtoggle__btn, .history-toggle button {
  border: 0; background: transparent;
  color: var(--muted);
  font: 600 13px/1 var(--font-ui);
  text-transform: none; letter-spacing: 0;
  padding: 7px 13px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--dur-state) var(--ease-out),
              color var(--dur-state) var(--ease-out),
              box-shadow var(--dur-state) var(--ease-out);
}
@media (hover: hover) {
  .pcu-segmented__btn:hover, .scope-btn:hover, .workscope__btn:hover,
  .sched__viewbtn:hover, .rock-scope__btn:hover, .tech-seg__btn:hover,
  .pnl-viewtoggle__btn:hover { color: var(--text); }
}
.pcu-segmented__btn--on, .scope-btn--active, .scope-btn.is-active,
.workscope__btn--active, .sched__viewbtn--active, .rock-scope__btn--active,
.dept-switch__pill--active, .tech-seg__btn--active, .pnl-viewtoggle__btn--active,
.l10-seg button[aria-selected="true"] {
  background: var(--surface); color: var(--text); box-shadow: var(--e1);
}


/* ---- 2.10 Tabs -------------------------------------------------------------
   Underline tabs stay underline: they mark a section of a page, not a choice
   between values, and turning them into pills would lose that distinction. */

.pcu-tabs, .hub-tabs, .team-tabs, .tabs {
  display: flex; align-items: center; gap: 2px;
  border-bottom: 1px solid var(--border);
}
.pcu-tab, .hub-tab, .team-tab, .tab {
  position: relative;
  border: 0; background: transparent;
  color: var(--muted);
  font: 600 14px/1 var(--font-ui);
  text-transform: none; letter-spacing: 0;
  padding: 11px 14px;
  cursor: pointer;
  transition: color var(--dur-state) var(--ease-out);
}
@media (hover: hover) { .pcu-tab:hover, .hub-tab:hover, .team-tab:hover, .tab:hover { color: var(--text); } }
.pcu-tab--active, .hub-tab--active, .team-tab--active, .tab--active { color: var(--text); }
.pcu-tab--active::after, .hub-tab--active::after, .team-tab--active::after,
.tab--active::after {
  content: ""; position: absolute; left: 10px; right: 10px; bottom: -1px;
  height: 2px; border-radius: 2px 2px 0 0; background: var(--red);
}


/* ---- 2.11 Stat tiles -------------------------------------------------------
   The reference dashboards lead with a row of these, and they are restrained:
   a small muted label, a number sized to be read rather than admired, and a
   delta that says whether the number is good news.

   The portal's stat number was 62px (78px on a hero card), which is a poster,
   not a KPI. Dropping it to the type scale is the single biggest perceptual
   change in this pass; --t-stat is the one knob if it should go back.      */

.pcu-stat { display: flex; flex-direction: column; gap: 6px; }
.pcu-stat__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.pcu-stat__chip {
  width: 34px; height: 34px; border-radius: var(--radius-md);
  display: grid; place-items: center; font-size: 16px;
  background: var(--surface-2); color: var(--muted); flex: none;
}
.pcu-stat__label {
  font: var(--t-eyebrow); letter-spacing: var(--track-eyebrow);
  text-transform: uppercase; color: var(--muted); margin: 0;
}
.pcu-stat__num {
  font: var(--t-stat); color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.pcu-stat__sub { font: var(--t-body-sm); color: var(--muted); }

.metric-card__value, .rpt-stat__num, .stat-card__value {
  font: var(--t-stat); color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
  margin: 4px 0 2px;
}
.metric-card--hero .metric-card__value { font: var(--t-stat); font-size: clamp(32px, 3.6vw, 42px); }
.metric-card__label, .rpt-stat__label, .stat-card__label {
  font: var(--t-eyebrow); letter-spacing: var(--track-eyebrow);
  text-transform: uppercase; color: var(--muted);
}
.metric-card__icon {
  width: 34px; height: 34px; border-radius: var(--radius-md);
  background: var(--surface-2); color: var(--muted); font-size: 16px;
}
.metric-card { padding: 20px 22px; }
.metric-card__head { margin-bottom: 8px; }


/* ---- 2.12 Modals, sheets and toasts ---------------------------------------- */

.pcu-modal, .modal, .eos-modal__card, .confirm-modal, .wo-modal__card, .rs-modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--e4);
}
.modal-backdrop, .pcu-modal__scrim {
  background: rgba(4, 16, 32, .45);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.modal__title, .pcu-modal__title { font: var(--t-title); color: var(--text); }

.pc-toast, .toast, .eos-toast, .hi-toast {
  border-radius: var(--radius-md);
  border: 1px solid var(--neutral-bd);
  background: var(--neutral-bg);
  color: var(--neutral-ink);
  font: var(--t-body-sm);
  box-shadow: var(--e3);
  padding: 11px 15px;
}
.pc-toast--ok, .toast--ok { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-bd); }
.pc-toast--warn { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-bd); }
.pc-toast--err, .toast--err, .eos-toast--bad, .hi-toast--bad {
  background: var(--bad-bg); color: var(--bad-ink); border-color: var(--bad-bd);
}


/* ---- 2.13 Focus, selection and scrollbars ----------------------------------
   One visible focus ring on everything reachable by keyboard. :focus-visible
   only, so a mouse click never paints a ring and an autofocused composer stays
   clean.                                                                    */

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
::selection { background: color-mix(in srgb, var(--blue) 28%, transparent); color: var(--text); }

* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border); border-radius: var(--radius-pill);
  border: 2px solid transparent; background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: content-box; }


/* ---- 2.14 Stage-coloured chrome --------------------------------------------
   The work-order board, the schedule and the repairs board all colour
   themselves from a per-stage `--stage` custom property that the office sets
   in Admin. Any rule that puts white text on that colour is a coin flip: the
   shipped palette included #f5b912 (1.78:1) and #66bb2a (2.41:1) for the count
   chips on the main ops board.

   So the stage colour becomes a TINT and the text stays the page's own ink.
   That holds for any hue anybody picks in future, which a hand-tuned pair
   never could.                                                             */

.jbm__count {
  background: color-mix(in srgb, var(--stage, var(--brand)) 16%, var(--surface));
  color: var(--text);
  border: 1px solid color-mix(in srgb, var(--stage, var(--brand)) 40%, transparent);
  font-variant-numeric: tabular-nums;
  padding: 1px 8px;
}
.jbm__group--empty .jbm__count {
  background: var(--neutral-bg); color: var(--neutral-ink); border-color: var(--neutral-bd);
}


/* ---- 2.15 Quiet text that got too quiet ------------------------------------
   Two shared labels sat under AA once their own opacity was taken into
   account: the topbar eyebrow (--ink-soft at .8 = 3.55:1) and the nav group
   caret (3.66:1). Both are small, both are functional, and neither needs to be
   faint to read as secondary.                                              */

.topbar__sub { opacity: 1; color: var(--muted); }
.nav__caret { color: var(--muted); opacity: 1; }


/* ---- 2.16 "Done" states that stayed readable -------------------------------
   Finished work-order cards receded by dropping the whole card to opacity .72,
   which is a blunt instrument: it dims the title, the customer and the type
   together and pushed all three under AA (3.07:1 for the type label).

   Recession is a job for the SURFACE, not for the text. The card sits back on
   the secondary surface and loses its shadow; every word in it stays legible. */

.wof-card--done, .wob-card--done {
  opacity: 1;
  background: var(--surface-2);
  box-shadow: none;
}
.wof-card--done .wof-card__cust, .wob-card--done .wob-card__meta span,
.wof-card--done .wob-card__meta span {
  color: var(--muted); opacity: 1;
}


/* ---- 2.17 Solid accent chips -----------------------------------------------
   Grades, ranks and risk positions are deliberately colour-coded, and a
   saturated block is the right answer for them: the colour IS the signal. What
   was wrong was the text on top of it.

   Two rules, applied here to every one of them:

   1. White text needs a hue dark enough to carry it. #4B92DB (grade B) was
      3.26:1, bronze 3.24:1, the second-hottest risk 3.82:1. Each moves to a
      deeper shade of the SAME hue, so the colour coding survives.
   2. A chip with a FIXED light background must pin navy text, never
      var(--ink). --ink flips to near-white in dark mode, which turned the gold
      and silver rank chips into 1.31:1 in the dark theme: white on gold. This
      was live and invisible.                                                */

.qc-grade--b { background: #2e76b8; color: #fff; }          /* was 3.26:1 */
.qc-rank--3  { background: #9a5c22; color: #fff; }          /* bronze, was 3.24:1 */
.qc-risk__rank--2 { background: #b33f17; color: #fff; }     /* was 3.82:1 */

/* Fixed light chips: navy text in BOTH themes. Scoped to the elements that
   actually CARRY the light fill. The button itself is --surface with --text and
   must be left alone; pinning navy on it made its label 1.06:1 in dark mode. */
.qc-grade--c, .qc-grade--na, .qc-rank--1, .qc-rank--2, .qc-risk__rank--3 {
  color: var(--navy);
}
.qc-grade--na { background: #b9c0c8; }
/* The "#" prefix inside a rank chip was dimmed by opacity, which on gold and
   silver put it at 1.31:1. It is part of the label, not decoration. */
.qc-rank__hash { color: inherit; opacity: 1; }

/* The EOS gold accent is 1.76:1 against white, so nothing gold may carry white
   text. Navy on gold is 9.11:1. Only the gold pieces, not their container. */
.hi-fab__plus, .hi-fab .eos-label { color: var(--navy); }

/* Brand red as TEXT is 2.81:1 on the dark canvas. --bad-ink knows both themes. */
.qc-risk__reasons, .qc-bad, .qc-day__count, .qc-mini__note.qc-bad {
  color: var(--bad-ink);
}

/* A link with no styling falls back to the browser default #0000ee, which is
   2.01:1 on the dark canvas. Bare links inside content get the brand blue. */
.content a:not([class]) { color: var(--info-ink); }

/* Trend context used a light-mode green that fell to 3.30:1 on the dark
   canvas. The status tokens already know both themes. */
.qc-trend__ctx.qc-up, .qc-mini__note.qc-up { color: var(--ok-ink); }
.qc-trend__ctx.qc-down, .qc-mini__note.qc-down, .qc-mini__note.qc-bad { color: var(--bad-ink); }

/* Inside a tinted delta chip the comparison text was dimmed to .8 and landed
   just under AA. It is the chip's own ink, at the chip's own strength; the
   weight difference already says which part is the number. */
.delta__cmp { opacity: 1; color: inherit; font-weight: 400; }

/* A search icon at opacity .5 lands at 3.19:1 and reads as a smudge. */
.crm-search__icon { opacity: .75; color: var(--muted); }

/* Customer QC badges are white on a solid fill; two of the four hues were too
   light to carry it (#2e8b57 = 4.25:1, #b8860b = 3.25:1). Same hues, deepened. */
.cust-qc-badge--ok { background: #1f7a45; }
.cust-qc-badge--minor { background: #8a5a00; }


/* ---- 2.18 Brand colour used as TEXT ----------------------------------------
   Brand red and brand gold are FILL colours. As text they behave very
   differently in the two themes:

     --red  #C60C30 on the dark canvas  = 3.15:1   (2.81:1 on a card)
     --gold #FFB612 on white            = 1.76:1

   styles.css says `color: var(--red)` in 164 places, so this is not a handful
   of mistakes, it is a missing token. --red-ink is brand red in light and a
   lightened brand red in dark (7.07:1 on a card, still unmistakably the Pool
   Captains red). Fills keep using --red and are untouched.                  */

:root { --red-ink: var(--red); --gold-ink: #8a5a00; }
:root[data-theme="dark"] { --red-ink: #f2707f; --gold-ink: var(--warn-ink); }
.tool-card__cta,
.binder__eyebrow,
.veh__driver,
.veh-popup__driver,
.qc-risk__reasons,
.qc-day__count,
.rpt-row__err,
.hm-block__eyebrow,
.eos-row__accent-bad,
.cust-alert,
.pc-alert {
  color: var(--red-ink);
}

/* The gold EOS pill: white on gold is 1.76:1. Navy on gold is 9.11:1, and the
   pill keeps being gold, which is the whole point of the accent. */
.eos-label { color: var(--navy); }
.eos-label--outline { color: var(--gold-ink); }
/* This one is gold TEXT on the page background, not a gold pill. */
.hl-kind--employee { background: var(--warn-bg); color: var(--warn-ink); }
.hl-kind--customer { background: var(--info-bg); color: var(--info-ink); }


/* ---- 2.19 Small functional glyphs -------------------------------------------
   Drag grips, sort arrows, disclosure carets, reorder buttons and lock toggles
   were all drawn in a light grey between 2.0:1 and 3.3:1. They are controls,
   not decoration: if you cannot see the grip you cannot reorder the row.    */

.eos-row__grip, .item-row__grip, .adsort__arrow, .model-picker__chev,
.sc2__lockbtn, .vto-sec__hint, .eos-btn--icon, .qc-day__chev {
  color: var(--muted);
  opacity: 1;
}
.vto-rock__num { color: var(--info-ink); }


/* ============================================================================
   3. THE STATUS PILL CONSOLIDATION

   Forty separate families had each invented their own status chip: .qt-pill,
   .svc-pill, .sd-chip, .rs-badge, .team-pill, .jb-chip, .pchip, .pbadge,
   .stoff, .cal__chip, .crm-badge, .qc-daygrade, .wod-part__state and the rest.
   Every one hand-picked a tint and an ink, and almost none had a dark-mode
   counterpart, which produced two failure modes:

     rgba tint + dark ink   the tint composites over the dark canvas and goes
                            dark, the ink stays dark: "in progress" was 2.51:1,
                            "viewed" 2.32:1, "sent" 2.84:1
     opaque tint + dark ink  the chip stays a light slab on a dark page

   Mapping all of them onto the five status tokens fixes both at once, in both
   themes, and means the next chip somebody adds inherits it rather than
   guessing a third shade of green.

   Grouped by MEANING, not by page. A chip that says "done" should look the
   same on the schedule, the work-order board and the quote list.
   ============================================================================ */

/* -- positive: done, approved, secured, delivered, on, go -- */
.qt-pill--approved, .svc-pill--done, .sd-chip--done, .sstop-pill--done,
.rs-badge--done, .jb-chip--ok, .pchip--ok, .pbadge--ok, .team-pill--excused,
.team-pill--delivered, .crm-log__state--confirmed, .crm-pill--secured_pool,
.crm-badge--call, .wod-part__state--go, .qc-daygrade--a, .qc-stop--ok .qc-stop__badge,
.admin-badge--on, .dept-tag--service, .eos-chip--ok, .proc__pill--done,
.pnl-chip--exact, .rpt-badge--done, .stage-prog__step--done, .mc-chip--positive,
.wk__group--ops {
  background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-bd);
}

/* -- caution: pending, expired, waiting, in review, partial, sorted -- */
.qt-pill--expired, .rs-badge--prog, .jb-chip--amber, .pchip--miss, .pbadge--miss,
.pbadge--exp, .team-pill--strike-verbal, .crm-pill--needs_follow_up,
.wod-part__state--wait, .qc-daygrade--c, .qc-stop--minor .qc-stop__badge,
.qc-rep-tag--warn, .ih-chip--report, .wob-card__pill--amber, .stoff--holiday,
.cal__chip--holiday, .rpt-badge--sorted, .view-badge--peek .view-badge__status,
.qt-pill--sent {
  background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-bd);
}

/* -- negative: declined, skipped, unplanned, needs attention, void -- */
.qt-pill--declined, .svc-pill--skip, .sd-chip--skip, .sstop-pill--skip,
.rs-badge--attn, .jb-chip--warn, .team-pill--unexcused, .team-pill--strike-final,
.qc-daygrade--d, .qc-daygrade--f, .qc-stop--red .qc-stop__badge, .qc-chip--red,
.qc-gtag--on, .ih-chip--declined, .dept-tag--repair, .fr-badge--bug,
.stoff--unplanned, .cal__chip--unplanned, .eos-chip--bad, .mc-chip--negative {
  background: var(--bad-bg); color: var(--bad-ink); border-color: var(--bad-bd);
}

/* -- informational: sent, viewed, scheduled, in progress, day, note -- */
.qt-pill--viewed, .svc-pill--prog, .sd-chip--prog, .rs-badge--repair,
.rs-badge--diag, .team-pill--open, .crm-badge--text, .crm-badge--email,
.crm-badge--in_person, .wod-part__state--ok, .qc-daygrade--b, .pbadge--day,
.ih-chip--email, .ih-chip--diag, .ih-chip--quote, .ih-chip--repair,
.fr-badge--feature, .rpt-badge--diag, .rpt-badge--notify, .pnl-chip--modelled,
.eos-chip--imported, .wk__group--service, .cal__chip--extra, .cal__chip--late,
.stoff--late, .stoff--partial, .cal__chip--partial {
  background: var(--info-bg); color: var(--info-ink); border-color: var(--info-bd);
}

/* -- neutral: draft, off, archived, closed, dismissed, not applicable -- */
.qt-pill--draft, .qt-pill--void, .svc-pill--plan, .sd-chip--plan,
.sstop-pill--wip, .rs-badge--note, .jb-chip--money, .pchip--2x, .pbadge--freq,
.team-pill--closed, .crm-log__state--dismissed, .crm-badge--note,
.qc-daygrade--na, .qc-gtag--off, .ih-chip--feedback, .dept-tag--excluded,
.dept-tag--none, .admin-badge--off, .fr-badge--status, .eos-chip--muted,
.pnl-chip--withheld, .proc__pill--role, .stoff--off, .cal__chip--off,
.step-card__badge--upcoming, .todo-tag--private {
  background: var(--neutral-bg); color: var(--neutral-ink); border-color: var(--neutral-bd);
}

/* Amber-ish middles that read as caution rather than information. */
.stoff--timeoff, .cal__chip--timeoff, .team-pill--strike-written,
.pnl-chip--heuristic, .wk__group--alys {
  background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-bd);
}

/* styles.css carries hand-written dark overrides for a few of these families at
   (0,2,0), which would out-specify the block above in dark mode and put the old
   colours back. Re-asserted at the same weight so the tokens win in both
   themes and there is one source of truth. */
:root[data-theme="dark"] .qc-chip--red,
:root[data-theme="dark"] .qc-daygrade--d, :root[data-theme="dark"] .qc-daygrade--f,
:root[data-theme="dark"] .qc-stop--red .qc-stop__badge {
  background: var(--bad-bg); color: var(--bad-ink);
}
:root[data-theme="dark"] .qc-daygrade--b {
  background: var(--info-bg); color: var(--info-ink);
}
:root[data-theme="dark"] .qc-daygrade--c,
:root[data-theme="dark"] .qc-stop--minor .qc-stop__badge {
  background: var(--warn-bg); color: var(--warn-ink);
}
:root[data-theme="dark"] .qc-stop--ok .qc-stop__badge {
  background: var(--ok-bg); color: var(--ok-ink);
}
:root[data-theme="dark"] .qc-daygrade--na, :root[data-theme="dark"] .qc-chip,
:root[data-theme="dark"] .qc-stop__badge {
  background: var(--neutral-bg); color: var(--neutral-ink);
}
:root[data-theme="dark"] .stoff--holiday, :root[data-theme="dark"] .cal__chip--holiday {
  color: var(--warn-ink);
}
:root[data-theme="dark"] .stoff--partial, :root[data-theme="dark"] .cal__chip--partial,
:root[data-theme="dark"] .stoff--late, :root[data-theme="dark"] .cal__chip--late {
  color: var(--info-ink);
}
:root[data-theme="dark"] .stoff--timeoff, :root[data-theme="dark"] .cal__chip--timeoff {
  color: var(--warn-ink);
}

/* Solid sequence markers on the tech's day: white on --blue is 3.26:1 and white
   on the success green 2.31:1. Same signal, carried as a tint. */
.sd-card__seq { background: var(--neutral-bg); color: var(--text);
  border: 1px solid var(--neutral-bd); }
.sd-card--next .sd-card__seq, .sd-card--open .sd-card__seq {
  background: var(--info-bg); color: var(--info-ink); border-color: var(--info-bd); }
.sd-card--completed .sd-card__seq {
  background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-bd); }
.sd-chip--home { background: var(--warn-bg); color: var(--warn-ink); }
.sd-chip--alys, .sd-chip--alys-com { background: var(--info-bg); color: var(--info-ink); }

/* "Today" markers used --blue as text (3.26:1 on white). */
.rs-head--today .rs-head__dow, .rs-head--today .rs-head__num,
.svc-head--today .svc-head__dow, .svc-head--today .svc-head__num,
.sd-card__uplabel, .sd-navbtn {
  color: var(--info-ink);
}


/* ============================================================================
   4. TWO ROOT CAUSES

   Sweeping every page turned up dozens of near-misses, and almost all of them
   traced back to the same two mistakes rather than to dozens of bad colours.
   ============================================================================ */

/* ---- 4.1 --bg-subtle was never defined -------------------------------------
   Used as `var(--bg-subtle, #f8fafc)` on the checklist editor rows, so it
   always fell through to a hardcoded near-white. That is a light slab sitting
   on the dark canvas with dark-mode muted text on top of it (2.32:1). Defining
   the token fixes every use at once and every future one. */
:root { --bg-subtle: var(--surface-2); }

/* ---- 4.2 opacity stacked on already-muted text -----------------------------
   The single most common defect in the portal. --muted is chosen to sit just
   above the AA floor, so ANY opacity on top of it drops below: a container at
   .55 took its 4.9:1 metadata to 2.16:1, and `--muted at .5` gave 2.07:1.

   Opacity is for whole surfaces receding, not for text. Where a row or card
   should look finished or empty, it recedes on the surface and keeps its ink. */

.rsx-stop--done, .wob-card--done {
  opacity: 1;
  background: var(--surface-2);
}
.rsx-stop--done .rsx-stop__name, .rsx-stop--done .rsx-stop__dur,
.rsx-stop--done .rsx-stop__meta { color: var(--muted); opacity: 1; }
.rsx-stop--done .rsx-stop__name { color: var(--text); }

.sc2__cell.is-empty .sc2__val { color: var(--muted); opacity: 1; }
.checklist-quote__by { color: var(--muted); opacity: 1; }
.wsx__hint, .sd-card__addr, .sd-card__last, .rsx-note, .mc-row__sub,
.mc-hour__label, .mc-row__time, .qt-pill--void {
  color: var(--muted); opacity: 1;
}

/* Reorder and move controls: 1.51:1 and 2.17:1 respectively. A control you
   cannot see is a control you do not have. */
.rs-stop__mvbtn, .ss-mini, .eos-btn.eos-btn--icon { color: var(--muted); opacity: 1; }

/* Navy as text on a dark surface is 1.05:1. The scorecard's "This Week" header
   pinned --brand, which is the navy FILL colour. */
.sc2 thead th.is-current, .sc2__week-lbl { color: var(--text); }

/* Live Customer Pulse defines its own palette; two entries sat under AA on
   their own surfaces (4.43:1 and 3.77:1). Same hues, one step deeper. */
:root { --mc-muted: #56657b; --mc-neu: #566275; }

/* Deep-dimmed "done" chips on the tech's day were still white-on-light-green
   because the colour arrives as an inline style from the work-order type map. */
.sd-chip.sd-chip--plan {
  background: var(--neutral-bg); color: var(--neutral-ink);
  border: 1px solid var(--neutral-bd);
}


/* ---- 4.3 The last of the shared near-misses ---------------------------------
   Each of these was found by rendering the page and measuring, not by reading
   the stylesheet. They are the tail of the same two root causes.           */

/* Reorder arrows inherit a lighter colour from their button chrome, so the
   token has to land on the glyph's own element. */
.ss-mini, .rs-stop__mvbtn, .adsort__arrow { color: var(--muted); }
.ss-item__req span, .ss-item__photo span, .ss-item__rm { color: var(--text); }
#ss-add, #ss-save, #tc-apply { color: var(--info-ink); background: var(--info-bg);
  border-color: var(--info-bd); }

/* Workspace switcher icons are emoji with an explicit near-black colour, which
   knocks out their glyph on the dark canvas. Emoji carry their own colour. */
.wsx__icon { color: var(--text); }

/* Weekly schedule: --blue as text on a blue-tinted cell, and brand red on the
   dark canvas. */
.wk__group--service { color: var(--info-ink); }
.wk__group--ops { color: var(--ok-ink); }
.wk__group--alys { color: var(--warn-ink); }
.wk__dayhead--today .wk__dayname, .wk__cell--clickable { color: var(--text); }
.wk__cell--clickable .wk__cellnote, .wk__cell--unplanned { color: var(--bad-ink); }

/* CRM source tag pinned --blue-mid, which is a navy relative: 1.45:1 in dark. */
.crm-tag { color: var(--info-ink); background: var(--info-bg); border-color: var(--info-bd); }

/* Live pulse: the tab strip kept a cream fill on the dark canvas. */
.mc-viewtab, #view-tab-feed { background: var(--surface-2); color: var(--text); }

/* Small ghost buttons on the uniforms and bonus pages sat between 2.27:1 and
   3.78:1 because they inherited a lightened chrome colour. */
.bp-btn--sm, .uni-linkbtn { color: var(--muted); }
.uni-linkbtn { color: var(--bad-ink); }


/* ---- 4.4 Containers that receded by dimming their own text -----------------
   The same mistake as 4.2, in the places that hold whole rows and cards. A
   button at 72%, a row at 55%, a card at 70%: each looked correctly secondary
   and each took its label under AA. Every one of these recedes on the surface
   instead, which is both more legible and closer to how the reference UIs mark
   an inactive or finished item.                                             */

.wsx__btn { opacity: 1; background: var(--surface-2); }
.wsx__btn--active { background: var(--surface); }
.wsx__hint { color: var(--muted); }

.team-row--inactive, .fr-card--done, .fr-card--dismissed, .cust-tl--past,
.route-chat--preview {
  opacity: 1; background: var(--surface-2);
}
.team-row--inactive .team-row__name, .fr-card--done .fr-card__title { color: var(--muted); }

/* Drag feedback is the one place opacity is right: the element is being moved,
   not read. Left alone: .navord--drag, .item-row--drag, .wof-card--dragging. */

.qc-empty-sub, .board__empty, .eos-node__holder--open, .qc-daygrade__wd,
.capture-row__src, .cust-spot__icon, .inv-search__icon, .model-picker__chev,
.nav__caret {
  opacity: 1; color: var(--muted);
}
.capture-foot__dot { opacity: 1; background: var(--border); }

/* styles.css:4821 overrides the weekly-schedule group label in dark at (0,2,0),
   putting --blue back as text at 4.41:1. Matched weight, theme-aware token. */
:root[data-theme="dark"] .wk__group--service { color: var(--info-ink); }
:root[data-theme="dark"] .wk__group--repair { color: var(--info-ink); }
:root[data-theme="dark"] .wk__group--mgr { color: var(--info-ink); }

/* Live Customer Pulse scopes its palette to body.mc, which out-specifies a
   :root declaration. Two of its inks were just under AA on their own surfaces. */
body.mc { --mc-muted: #56657b; --mc-neu: #566275; }
:root[data-theme="dark"] body.mc { --mc-muted: #9db0cc; --mc-neu: #9fb4cf; }

/* A completed stop should sit back, not become unreadable: .7 opacity took its
   address line to 3.96:1. */
.sd-card--completed { opacity: 1; background: var(--surface-2); }
.sd-card--completed .sd-card__addr, .sd-card--completed .sd-card__last { color: var(--muted); }

/* Disabled controls are exempt from the contrast requirement, but .3 is harsher
   than it needs to be to read as unavailable. */
.rs-stop__mvbtn:disabled, .ss-mini:disabled, .bp-btn:disabled { opacity: .5; }

/* The "you" marker on the Admin roster: --blue-mid on a blue tint is 3.15:1 in
   light and 4.17:1 in dark, and it is 9px, which is the smallest text on the
   page. It gets the info token, which is chosen for exactly this job. */
.admin-you { color: var(--info-ink); background: var(--info-bg); }


/* ---- 4.5 The tech's day ----------------------------------------------------
   tech-day.css is loaded after polish.css by the three field pages, so these
   need to out-specify it rather than rely on order. Same two root causes: a
   completed stop dimmed itself to 72%, and the status chips hand-picked their
   colours.                                                                  */

.md-card.md-card--done { opacity: 1; background: var(--surface-2); }
.md-card.md-card--done .md-card__addr, .md-card.md-card--done .md-card__est,
.md-card.md-card--done .md-progress { color: var(--muted); }
.md-card.md-card--done .md-card__time { color: var(--ok-ink); }
.md-status.md-status--completed { background: var(--ok-bg); color: var(--ok-ink); }
.md-status.md-status--skipped { background: var(--bad-bg); color: var(--bad-ink); }
.md-status.md-status--in_progress { background: var(--info-bg); color: var(--info-ink); }
.md-chip { background: var(--neutral-bg); color: var(--neutral-ink);
  border: 1px solid var(--neutral-bd); }
.md-chip--warn { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-bd); }
.sch-kind--service { background: var(--info-bg); color: var(--info-ink); }
.sch-kind--repair { background: var(--warn-bg); color: var(--warn-ink); }




/* ---- 5.1 tech-day.css loads AFTER this file --------------------------------
   The three field pages link static/css/tech-day.css after polish.css, so the
   .md-* family out-orders everything above. This is the one sanctioned
   escalation from the header: :root raises these to (0,2,0) so the tokens win,
   without an !important and without editing a file the field pages depend on.

   Measured before: the timeline node was white on a success green at 2.31:1,
   the running timer 3.45:1, the instruction note 1.69:1 in dark mode.       */

:root .md-card__time { color: var(--info-ink); }
:root .md-card--done .md-card__time { color: var(--ok-ink); }
:root .md-node { background: var(--info-bg); color: var(--info-ink);
  border: 1px solid var(--info-bd); }
:root .md-card--done .md-node { background: var(--ok-bg); color: var(--ok-ink);
  border-color: var(--ok-bd); }
:root .md-card--active .md-node { background: var(--warn-bg); color: var(--warn-ink);
  border-color: var(--warn-bd); }
:root .md-timer { background: var(--ok-bg); color: var(--ok-ink); }
:root .md-timer--warn { background: var(--warn-bg); color: var(--warn-ink); }
:root .md-instr { color: var(--warn-ink); background: var(--warn-bg);
  border: 1px solid var(--warn-bd); border-radius: var(--radius-sm);
  padding: 6px 9px; }
:root .md-chip { background: var(--neutral-bg); color: var(--neutral-ink); }
:root .md-chip--warn { background: var(--warn-bg); color: var(--warn-ink); }
:root .md-status--completed { background: var(--ok-bg); color: var(--ok-ink); }
:root .md-card--done { opacity: 1; background: var(--surface-2); }
:root .md-card--done .md-card__addr, :root .md-card--done .md-card__est,
:root .md-card--done .md-progress { color: var(--muted); }

/* Two more surfaces that used a brand colour where a theme-aware ink belongs. */
.chat-welcome__eyebrow { color: var(--red-ink); }
/* The map container's own fill shows whenever tiles have not arrived (a slow
   connection, or a truck out of signal). A light slab is the worst thing to
   flash on a dark page; the secondary surface is what an empty panel looks
   like everywhere else in the portal. */
#map, .fleet-map, .route-map { background: var(--surface-2); }

/* ---- 4.6 Brand colour as text,
everywhere it happens -----------------------
   styles.css sets `color: var(--red)` in 164 places and `color: var(--gold)` in
   more. Where the rule also paints a background it is a fill and is correct.
   Where it does not,
it is TEXT,
and brand red measures 2.81:1 on a card in
   dark mode while brand gold is 1.76:1 on white.

   These are every such rule,
retargeted onto the theme-aware inks. In light
   mode --red-ink IS brand red,
so nothing changes; in dark mode they become
   legible. Generated by scanning styles.css rather than found one page at a
   time,
so the tail does not come back.                                     */

.sidebar__tag,
.connect-link,
.metric-card--error .metric-card__value,
.login__tag,
.veh__driver,
.veh-popup__driver,
.fleet-tab.is-active,
.safety-row.score--bad,
.safety-row__score.score--bad,
.conflict-item__note,
.capday__now--over,
.capdays__clear:hover,
.move-card__notes,
.timeline__quarter,
.binder__eyebrow,
.binder__rolecard-label,
.binder-cover__eyebrow,
.binder-crumb__link:hover,
.page-list__link:hover,
.outline-step__no,
.outline-sub__link:hover,
.outline-add:hover,
.procedure-card--open:hover,
.procedure-card--open:hover .procedure-card__go,
.proc__p a.rt-link,
.proc__callout-body a.rt-link,
.proc__quote a.rt-link,
.proc__video-icon,
.proc__doc-open,
.ed-doc-link,
.ask__sub,
.captain-head__name,
.file-chip__remove:hover,
.chat-welcome__eyebrow,
.msg__who,
.msg__text--error p,
.captain-doc__eyebrow,
.home-hero__eyebrow,
.home-card__eyebrow,
.home-card__go,
.tool-card__cta,
.tool-quote__head,
.quote-line--total span:last-child,
.user-menu__item--danger,
.qc-h2--alert,
.qc-attn-item--severe .qc-attn-probs li,
.qc-attn-risk__lead,
.qc-trend__ctx.qc-down,
.qc-day__count,
.qc-mini--bad .qc-mini__num,
.qc-mini__note.qc-down,
.qc-mini__note.qc-bad,
.qc-recur li strong,
.qc-persist li strong,
.qc-risk__reasons,
.qc-missed-drop__x,
.qc-risk__item--critical,
.qc-risk__issue-date,
.qc-risk__lead--risk,
.cust-locrow__chev,
.cust-stat--bad .cust-stat__num,
.cust-evt--invoice.cust-evt--due .cust-evt__body,
.cust-line--due span,
.cust-tl-today,
.cust-tl__prob--severe,
.cust-qc__cons,
.qc-stop--red,
.qc-stop__cons,
.ed-item-del:hover,
.ptr__spinner,
.todos__error,
.todo-tag--mail,
.icon-btn--danger:hover,
.admin-email-input--error,
.form-error,
.cal__cell--today,
.cal__cell--today .cal__num,
.sched-row--unplanned,
.sched-row__hours--unplanned,
.fr-error,
.inv-stat--warn,
.team__error,
.team-row__flag--strike,
.team-link--danger,
.eos-form__error,
.eos-state__error,
.cap-planner--short,
.cap-planner--short .cap-planner__verdict,
.rebal-warn,
.move-card__checks .mc-warn,
.hfield .hreq,
.rpt-loc__clear:hover,
.rpt-error,
.crm-stat--due,
.crm-inbox__dismiss,
.crm-row__follow--due,
.npf-req,
.hist-edit__status--err,
.qc-stat--band-over,
.bw-home:hover,
.bw-hero__eyebrow,
.bw-meter__count b,
.bw-sub:hover .bw-sub__go,
.bw-add:hover,
.proc__link:hover,
.ed-text a,
.ed-float__item--danger,
.rpt-detail__del,
.rpt-quote__rm:hover,
.pr-lost__date,
.pr-kpi b.alert,
.wod-part__out,
.wod-quote__warn,
.jb-rail--attn .pr-seclbl,
.jb-rail--attn .jb-card,
.pcappr__err,
.jba__card--attn,
.ih-issue--declined,
.ih-issue--sev-severe,
.ih-btn--danger,
.pcappr__ctxphoto--off,
.pcappr__scenmiss,
.pcappr__rep--off .pcappr__rep__note,
.poo-sum__late,
.poo__eta--late,
.rsx-cap--over .rsx-cap__lbl,
.rs-cell__meta--over,
.topbar-more__item--danger,
.sstop-dl__sendmsg.is-err,
.sd-readings-save__msg--err,
.hm-stat--warn .hm-stat__value,
.alys-cov__sub,
.alys-row--late,
.alys-row--late .alys-row__seen,
.alys-row--never .alys-row__seen,
.alys-row__day--none,
.alys-chk__clearbtn:hover,
.alysb__card.alys-row--late,
.alys-fact--warn .alys-fact__v,
.alys-lab__missing,
.pcm-row--danger {
  color: var(--red-ink);
}
.move-card--warn,
.changes-drawer__summary strong,
.qc-warn,
.cust-evt--text,
.cust-evt--text.cust-evt--inbound,
.qc-stop--minor,
.hub-tab--active.hub-tab--gold,
.todo-section__title--gold,
.todo-sub__title--gold,
.todo-section--big3 .todo-section__title,
.sched-row--holiday,
.cap-planner--unknown,
.crm-stat--warn,
.qc-stat--band-around,
.bw-cover__eyebrow,
.l10__eyebrow,
.l10 .eos-chart__g0,
.l10 .eos-chart__g1,
.l10 input[type="checkbox"],
.wod-panel--approve,
.wod-sched--need .wod-sched__date,
.jba__title,
.jba__card--quote,
.jba__amt,
.vto-uniques li::marker,
.alys-row--soon,
.alysb__card.alys-row--soon,
.dept-src--manual,
.dept-star.is-primary,
.wsx__btn--personal.wsx__btn--active .wsx__label::after,
.l10-btn--issue,
.l10-picks__eyebrow,
.l10 .l10-dot.is-current,
.l10-segue__name,
.l10__ring.is-paused::after,
.l10 .l10-hero__owner-name,
.l10 .l10-num.is-ok .l10-num__goalbig .l10-num__value,
.l10 .l10-num.is-bad .l10-num__goalbig .l10-num__value,
.l10 .l10-num__goalbig .l10-num__value,
.l10 .l10-num__goal-val,
.l10 .l10-meas__owner,
.l10-focus__count,
.l10-choose__eyebrow,
.l10 .l10-carry__date {
  color: var(--gold-ink);
}

/* ---- 4.7 Navy as text ------------------------------------------------------
   The same shape as 4.6 and the one that bites hardest: --navy IS the ink in
   light mode,
so `color: var(--navy)` looks correct and reads perfectly until
   the theme flips,
at which point it is navy on a near-black canvas. The
   invoice amount on /send-invoice measured 1.06:1,
which is invisible.

   --text is navy in light and the light ink in dark,
so in light mode not one
   pixel changes. Rules that also paint their own background are fills and are
   left alone.                                                               */

.cap__input:focus,
.cap__toggle:hover,
.capwk__num:focus,
.capday__in:focus,
.capdays__byday:hover,
.capdays__weekly:hover,
.chat-drawer__act:hover,
.view-badge__status,
.chat-history__search input:focus,
.msg__copy:hover,
.model-picker__check,
.msg__status--owner,
.cust-spot__bar:focus-within,
.cust-card--feature .cust-card__h,
.ptr--ready .ptr__spinner,
.addrow__input:focus,
.addrow__opts:hover,
.big3-row__box,
.check-row__box,
.todo-row__box,
.todo-tag--due,
.capture-row__move:hover,
.form-input:focus,
.admin-email-input:focus,
.sched__navbtn:hover,
.sched__today:hover,
.cal__cell:hover,
.sched-member__edit:hover,
.sched-print-head__month,
.inv-search__input:focus,
.inv-chip:hover,
input.inv-qty:focus,
.inv-row__edit:hover,
.inv-locbtn:hover,
.inv-form__hint--tracked,
.team-tab--active,
.team-check__box,
.rule-fact__val,
.route-popup__cust,
.confirm-modal__cb input,
.cap__weekin:focus,
.rebal-row__days,
.pa-day input,
.role-card--new,
.hsec__title,
.rv-card h3,
.rv-disc__k,
.jd h2,
.resume__title,
.rz-spin,
.other-notes > span,
.stage-prog__step--current,
.rpt-groupswitch__btn:hover,
.rpt-archive__search:focus,
.crm-search__input:focus,
.crm-chip:hover,
.crm-row:hover,
.qc-rep-table tbody tr:first-child .qc-rep-rank,
.qc-rep-score,
.eos-chart__g0,
.eos-chart__g1,
.wof-followup,
.rs-day__btn:hover,
.rpt-mail__photo-cb,
.rpt-quote__sumrow--total,
.inv-q__amount,
.inv-q__copy:hover,
.qt-approve__amt,
.wod-billing__row--total .wod-billing__what,
.wod-billing__row--total .wod-billing__amt,
.rpt-pick__search:focus,
.jbm__amt,
.pcappr__rep input[type="checkbox"],
.pcappr__bdline--total,
.rs-toggle input,
.rs-filter-opt input,
.techpick__pill:hover,
.techpick__row input,
.rs-cell__stats:hover,
.rs-modal__stop input,
.vto-sec__pencil:hover,
.sstop-bodyhdr,
.pr-route--native,
.pr-route__day input,
a.svc-wochip:hover,
.sc2 thead th.is-current,
.wsx__btn--business.wsx__btn--active .wsx__label::after,
.l10-dot.is-current {
  color: var(--text);
}

/* ---- 4.8 The interactive blue as text --------------------------------------
   --blue is #4B92DB,
which is 3.26:1 on white. It is a good FILL colour and a
   good focus ring,
and it is used as text in 104 rules where it is simply too
   light to read: section headings,
"today" markers,
link-style buttons.

   --info-ink is the same blue family chosen to clear AA in both themes. This is
   a visible change in light mode,
unlike 4.6 and 4.7,
and it is the point: that
   text was never dark enough.                                               */

.field input:focus,
.binder-search__input:focus,
.chat-suggest__chip:hover,
.field select:focus,
.field textarea:focus,
.admin-role-select:focus,
.calc-pool-input:focus,
.calc-growthmix__input:focus,
.chem-type__btn:hover,
.cust-history-more__sum,
.cust-evt--call,
.cust-evt--email.cust-evt--outbound,
.ed-align__btn:hover,
.ed-rt:focus,
.sched-row--working,
.inv-stat--slow,
.team-row:hover,
.team-pick input,
.eos-field input:focus,
.eos-field select:focus,
.eos-field textarea:focus,
.eos-spinner,
.rpt-kind:hover,
.rpt-kind__radio,
.rpt-loc__input:focus,
.rpt-textarea:focus,
.rpt-photo-add:hover,
.rpt-minput:focus,
.crm-stat--money,
.npf-in:focus,
.npf-choice:hover,
.qc-stat--rep,
.ed-embed__empty:hover,
.ed-embed__act:hover,
.ed-quiet:focus,
.ed-quiet:focus-visible,
.ed-float__search:focus,
.ed-float__search:focus-visible,
.ed-float__chip:hover,
input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]):not([type="range"]):focus,
select:focus,
textarea:focus,
.wo-status-line a,
.wo-part-stock__link,
.rs-head--today .rs-head__dow,
.rpt-detail__back,
.rpt-mail__redraft,
.rpt-quote__add,
.pr-kv__sel:focus,
.pr-tab--on,
.vto-values li::marker,
.vto-goals li::marker,
.vto-issues li::marker,
.vto-rock__num,
.svc-head--today .svc-head__dow,
.svc-head--today .svc-head__num,
.svc-stop:hover,
.svc-daytab:hover,
.svc-daytab--today:not(.is-on) .svc-daytab__dow,
.svc-sec__h,
.sstop-sec__h,
.sstop-dl__btn.is-open,
.sd-card:hover,
.sd-sheet__sub,
.sd-sectitle,
.sd-hcap--read,
.svc-pstop:hover,
.svc-cellact:hover:not(:disabled),
.svc-edit,
.svc-pstop__info:hover,
.sd-card--in_progress,
.sd-card__uplabel,
.rd-col__head h2,
.rd-iconbtn:hover,
.rd-sec,
.hm-day-card__cta,
.hm-tile:hover,
.alys-day--today,
.alys-move__day--today,
.alys-chk__add:hover,
.alysb__col--today,
.alys-prof__chk:hover,
a.alys-pool:hover,
a.alys-pool:hover .alys-pool__go,
.rce__area a,
.rce-view__content a,
.pc-npanel__ft-link {
  color: var(--info-ink);
}


/* ---- 4.9 Runtime-injected CSS ----------------------------------------------
   Eight scripts build a <style> at load time and append it to <head>, so they
   land after every stylesheet and win on source order. Specificity still beats
   order, so :root raises these one step and the tokens apply without editing a
   script another session may be holding.                                    */

:root .prof-m-bad { color: var(--red-ink); }
:root .prof-m-good { color: var(--ok-ink); }
:root .prof-m-mid { color: var(--warn-ink); }
:root .prof-flag { background: var(--bad-bg); color: var(--bad-ink); }
:root .prof-flag--quiet { background: var(--neutral-bg); color: var(--neutral-ink); }

/* ---- 4.10 The Alys board ---------------------------------------------------
   A serviced pool recedes at 72% opacity, taking its address to 2.85:1.      */
.alysb__card--done { opacity: 1; background: var(--surface-2); }
.alysb__card--done .alysb__addr, .alysb__card--done .alysb__meta,
.alysb__card--done .alysb__moved { color: var(--muted); }
.alys-pool__tick { color: var(--ok-ink); }
.alys-row--never .alysb__addr, .alys-row--never .alysb__meta { color: var(--text); }


/* ---- 4.11 Components have the last word ------------------------------------
   The mechanical retargets above scan for rules that set a colour and no
   background, but a FILL can spread its parts across two rules: .btn carries
   the ink and .btn--primary carries the navy. That is how the "Apply new
   routes" button ended up navy on navy. Button selectors are excluded from
   those scans, and the deliberate treatments are restated here so the
   component sections always win over a generated list.                      */

.pcu-btn--primary, .btn--primary, .btn--active, .todo-btn--primary,
.eos-btn--primary, .uni-btn, .en-btn {
  background: var(--brand); border-color: var(--brand); color: var(--cream);
}
.pcu-btn--danger, .btn--danger {
  background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad-ink);
}
.btn--ghost, .uni-btn--ghost, .en-btn--ghost {
  background: transparent; color: var(--text);
}

/* Two remaining fills that could not carry white: the chemistry page's add
   buttons and the hiring board's column counts. */
.rd-addbtn { background: var(--brand); color: var(--cream); border-color: var(--brand); }
.board__col-head .n {
  background: var(--neutral-bg); color: var(--neutral-ink); border: 1px solid var(--neutral-bd);
}

/* Tab counters on Report to Office: --ink-soft on a flat grey is 4.15:1. */
.rpt-tab__count {
  background: var(--neutral-bg); color: var(--neutral-ink);
  border: 1px solid var(--neutral-bd);
}
.rpt-tab--active .rpt-tab__count {
  background: var(--info-bg); color: var(--info-ink); border-color: var(--info-bd);
}


/* ---- 4.12 The time tracker's two lens colours ------------------------------
   time.css treats "ON the business" (brand blue) and "IN the business" (clay)
   as load-bearing: the same two colours everywhere. They work as fills and as
   tints; as INK on their own tints they measured 2.77:1 and 3.15:1.

   The lens identity is kept and only the ink deepens, so a blue chip is still
   blue and a clay chip still clay. --on-soft and --in-soft derive from --blue
   and the clay directly, so the tints are unchanged.                        */

/* time.css is linked after polish.css by /time and /settings, so these take the
   :root escalation rather than relying on source order. */
:root .tt { --on: #1a5ea6; --in: #96501c; }
:root[data-theme="dark"] .tt { --on: var(--info-ink); --in: #e0a06a; }
:root .tt-mini.v1, :root .tt-vchip.v1 { background: var(--info-bg); color: var(--info-ink); }
:root .tt-mini.v0, :root .tt-vchip.v0 { background: var(--warn-bg); color: var(--warn-ink); }
:root .tt-mini.v2, :root .tt-vchip.v2 { background: var(--bad-bg); color: var(--bad-ink); }
:root .tt-mini.on, :root .tt-toggle button.sel-on, :root .tt-dim button.on {
  background: var(--info-bg); color: var(--info-ink);
}
:root .tt-mini.in, :root .tt-toggle button.sel-in, :root .tt-dim button.in {
  background: var(--warn-bg); color: var(--warn-ink);
}
:root #tt-enable, :root #tt-start {
  background: var(--brand); color: var(--cream); border-color: var(--brand);
}
:root .tt-day-total, :root .nt-locked { color: var(--muted); opacity: 1; }


/* ---- 4.13 Feedback log and reviews ----------------------------------------- */

.cb-cust { color: var(--info-ink); }
.cb-link { color: var(--info-ink); }
.cb-link--danger { color: var(--bad-ink); }
.cb-tag--callback { border-color: var(--info-bd); color: var(--info-ink); background: var(--info-bg); }
.cb-tag--team { border-color: var(--info-bd); color: var(--info-ink); background: var(--info-bg); }
.cb-tag--positive { border-color: var(--ok-bd); color: var(--ok-ink); background: var(--ok-bg); }
.cb-tag--constructive { border-color: var(--warn-bd); color: var(--warn-ink); background: var(--warn-bg); }

.rv-avatar { background: var(--brand); color: var(--cream); }
/* A gold star at 2.03:1 is a shape you infer rather than see. Deepened to the
   amber the rest of the portal uses for "caution/rating", which still reads as
   gold and can actually be counted. The unfilled star stays a hairline: that
   one is meant to be absent. */
.rv-stars { color: var(--warn-ink); }
.rv-stars .rv-star--off { color: var(--border); }

/* A locked notification row is unavailable, not unreadable: 72% opacity took
   both its label and the reason it is locked to 3.07:1. Same treatment as
   everywhere else. */
.nt-catrow--locked { opacity: 1; background: var(--surface-2); }
.nt-catrow--locked .nt-locked, .nt-catrow--locked .tt-day-total { color: var(--muted); }

/* The value toggle on the time tracker keeps its three tiers and takes their
   inks from the scale: #7a6000 on its own tint measured 1.59:1 in dark. */
:root .tt-vtoggle button.sel-v.v0 { background: var(--warn-bg); color: var(--warn-ink); }
:root .tt-vtoggle button.sel-v.v1 { background: var(--info-bg); color: var(--info-ink); }
:root .tt-vtoggle button.sel-v.v2 { background: var(--bad-bg); color: var(--bad-ink); }

/* The Alys list row (the phone layout of the same board) had its own copy of
   the fade-when-done pattern: .72 opacity took the address to 2.96:1. */
.alys-row--done { opacity: 1; background: var(--surface-2); }
.alys-row--done .alys-row__addr, .alys-row--done .alys-row__meta { color: var(--muted); }
.alys-row__tick { color: var(--ok-ink); }
.alys-row__day--none { color: var(--bad-ink); }


/* ============================================================================
   5. THE KPI ROW

   Every reference dashboard opens the same way: a band of tiles, each with a
   small icon, a quiet label, one number, and a line of context telling you
   whether the number is good. The portal had the number and the label; the
   icon and the context were missing, so a screen full of figures gave no help
   reading them, and each page invented its own tile.

   One component, used wherever a page leads with figures. Icons are lifted
   from the rendered nav (see navIcon in home.js) so there is no second icon
   set to keep in step.
   ============================================================================ */

.pcu-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}
.pcu-kpi {
  display: flex; flex-direction: column; gap: 7px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--e1);
  padding: 15px 17px;
  min-width: 0;
  color: inherit; text-decoration: none;
}
.pcu-kpi__head { display: flex; align-items: center; gap: 9px; min-width: 0; }
.pcu-kpi__chip {
  flex: none; width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--muted);
  font-size: 15px; line-height: 1;
}
.pcu-kpi__chip svg, .pcu-kpi__chip .nav__ico { width: 17px; height: 17px; }
.pcu-kpi__label {
  font: var(--t-eyebrow); letter-spacing: var(--track-eyebrow);
  text-transform: uppercase; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pcu-kpi__value {
  font: var(--t-stat); color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.pcu-kpi__foot {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  font: var(--t-body-sm); color: var(--muted); min-height: 20px;
}
/* A tile that opens something says so on hover, and only on a pointer device. */
.pcu-kpi--link { cursor: pointer;
  transition: box-shadow var(--dur-state) var(--ease-out),
              transform var(--dur-state) var(--ease-out),
              border-color var(--dur-state) var(--ease-out); }
@media (hover: hover) {
  .pcu-kpi--link:hover {
    box-shadow: var(--e2); transform: translateY(-1px); border-color: var(--muted);
  }
  .pcu-kpi--link:hover .pcu-kpi__chip { background: var(--info-bg); color: var(--info-ink); }
}
/* The accent edge is the portal's signature; kept, but placed rather than slabbed. */
.pcu-kpi--accent { position: relative; overflow: hidden; }
.pcu-kpi--accent::before {
  content: ""; position: absolute; inset: 14px auto 14px 0; width: 3px;
  border-radius: var(--radius-pill); background: var(--acc, var(--red));
}
.pcu-kpi--accent { padding-left: 21px; }


/* ---- 5.1 Every stat tile in the portal, on one recipe ----------------------
   Six families already had the KPI shape - an icon, a label, a figure, a line
   of context - and each drew it differently: .hm-stat on Home, .metric-card on
   Data, .rpt-stat on Reports, .adstat in Admin, .pnl-kpi on the P&L, .eos-stat
   on the EOS pages. Four different paddings, four different label sizes, three
   different ways of placing the icon.

   Re-pointed rather than rewritten: every one of them already emits the right
   parts, so this is a stylesheet change and no page's JS moves. That is also
   why it is safe - there is no markup to get wrong.                        */

.hm-stats-grid, .adstats, .metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 12px;
}
.hm-stat, .adstat, .rpt-stat, .pnl-kpi, .eos-stat {
  display: flex; flex-direction: column; gap: 7px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--e1);
  padding: 15px 17px;
  min-width: 0;
}
.hm-stat__top { display: flex; align-items: center; gap: 9px; margin-bottom: 0; min-width: 0; }
/* The icon becomes a chip, the way it reads in the reference material: a small
   tinted square rather than a loose glyph sitting next to the words. */
.hm-stat__ic, .metric-card__icon {
  flex: none; width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--muted);
  font-size: 15px; line-height: 1;
}
.hm-stat__ic svg, .hm-stat__ic .nav__ico { width: 17px; height: 17px; }
:root[data-theme="dark"] .hm-stat__ic { color: var(--muted); }

.hm-stat__label, .adstat__label, .rpt-stat__label, .pnl-kpi__label, .eos-stat__label,
.metric-card__label {
  font: var(--t-eyebrow); letter-spacing: var(--track-eyebrow);
  text-transform: uppercase; color: var(--muted); margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hm-stat__value, .adstat__value, .rpt-stat__num, .pnl-kpi__value, .eos-stat__num,
.metric-card__value {
  font: var(--t-stat); color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
  margin: 0;
}
.hm-stat__sub, .adstat__note, .rpt-stat__sub, .metric-card__source,
.metric-card__context {
  font: var(--t-body-sm); color: var(--muted); margin: 0;
}
.hm-stat__foot, .pnl-kpi__deltas {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  min-height: 20px; margin: 0;
}
@media (hover: hover) {
  a.hm-stat:hover, a.metric-card:hover {
    box-shadow: var(--e2); transform: translateY(-1px); border-color: var(--muted);
  }
  a.hm-stat:hover .hm-stat__ic { background: var(--info-bg); color: var(--info-ink); }
}


/* ============================================================================
   6. PHONE TAIL

   THIS BLOCK MUST STAY LAST IN THIS FILE.

   polish.css loads after styles.css, so every rule above out-orders the MOBILE
   APP SHELL block at styles.css:12778 even inside a media query (media queries
   add no specificity). Anything the phone needs to win has to be re-asserted
   down here, after the desktop rules it is overriding.
   ============================================================================ */

@media (max-width: 768px) {

  /* ---- Touch targets -------------------------------------------------------
     Thirteen controls in the shared chrome measured between 14px and 40px tall:
     the nav rows, the theme toggle, the back button, the bell, the overflow
     menu, the topbar actions. 44px is the floor a thumb can hit reliably, and
     it is padding rather than font size so nothing reflows.                  */
  .nav__item, .nav__label, .todo-btn, .btn, .pcu-btn,
  #theme-toggle, #fr-open, #pc-bell, #topbar-more-btn, .pc-back, .pc-callback {
    min-height: 44px;
  }
  .nav__item, .nav__label { display: flex; align-items: center; }
  #theme-toggle, #fr-open, .pc-back, #pc-bell, #topbar-more-btn {
    display: inline-flex; align-items: center; justify-content: center;
  }
  .sidebar__website { display: inline-flex; align-items: center; min-height: 44px; }
  /* Anything genuinely icon-only stays square rather than growing wide. */
  .pc-back, #pc-bell, #topbar-more-btn { min-width: 44px; }

  /* ---- The floating tab bar must not sit on the content --------------------
     .pcm-bar is fixed at the bottom with a blur, and the only clearance is
     padding on .content. Anything a page renders outside .content, or inside
     its own scroll container, ended up underneath it: the Home stat comparison
     line and the Quality Control trend labels were both fully covered.       */
  body.pcm-on .content, body.has-tabbar .content {
    padding-bottom: calc(104px + env(safe-area-inset-bottom));
  }
  body.pcm-on .hm-wrap, body.pcm-on .qc-trends {
    scroll-padding-bottom: calc(104px + env(safe-area-inset-bottom));
  }
  /* A page whose own element is fixed to the bottom needs to clear the bar too. */
  body.pcm-on .sd-sticky, body.pcm-on .md-sticky {
    bottom: calc(78px + env(safe-area-inset-bottom));
  }

  /* ---- Comfortable rhythm on a small screen ------------------------------ */
  .content { padding: 18px 16px 40px; }
  .pcu-card, .metric-card { padding: 16px 16px; border-radius: var(--radius-lg); }
  .topbar { padding: 10px 16px; }
  .pcu-table thead th, .pcu-table tbody td { padding: 10px 10px; }

  /* Never let a control's label shrink below what a phone can render. */
  .pcu-badge, .state-badge, .badge, .pill, .delta { font-size: 11px; }

  /* ---- Long text has to fold, not push ------------------------------------
     A flex child only shrinks below its content when it is allowed to, so a
     long customer name pushed a CRM row eight pixels past the right edge. The
     name and its sub-line get the full ellipsis treatment.                  */
  .crm-row__main, .crm-row__nameline, .crm-row__sub {
    min-width: 0; max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* The rich-editor demo panel keeps a desktop minimum width, so on a phone the
     whole two-column layout hangs off the side. */
  .rcedemo, .rcedemo__panel, #editor-host, #editor-host .rce {
    min-width: 0; max-width: 100%;
  }
  .rcedemo__json { overflow-x: auto; }

  /* A button whose label is a whole sentence ("Waive this week's payouts...")
     cannot sit on one line at 390px: it ran 390 pixels past the edge. Let it
     wrap onto its own full-width row like the rest of the bar. */
  .bp-waivebar .bp-btn { max-width: 100%; white-space: normal; text-align: left; }
  .bp-waivebar .bp-btn--sm { flex-basis: 100%; }
}

/* ---- Found once real production data filled these pages -------------------
   Three surfaces only render with data the empty sandbox never had: a startable
   stop on the tech's day, an enrolled tech on the bonus plan, a checked-in crew
   member. All three were hand-coloured.                                     */
:root .md-btn--start { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-bd); }
:root .bp-tech__name { color: var(--text); }
:root .bp-tech__stat-label { color: var(--muted); }
:root .bp-chip--weekly { color: var(--info-ink); }

/* The value of the To Invoice column, shown only to people the server decided
   may see money (see the board endpoint). Reads as a figure, not a status. */
.jbm__money {
  margin-left: auto;
  font: 700 13px/1 var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--ok-ink);
  background: var(--ok-bg);
  border: 1px solid var(--ok-bd);
  border-radius: var(--radius-pill);
  padding: 3px 10px;
  white-space: nowrap;
}

/* An empty column on the work-order jump strip was dimmed to 48% opacity,
   which took its own label to 3.07:1. "Nothing here" still has to be readable:
   it recedes on the surface and keeps its words, like everywhere else. */
.jbp__chip--empty { opacity: 1; background: var(--surface-2); color: var(--muted); }
.jbp__chip--empty .jbp__lbl, .jbp__chip--empty .jbp__n { color: var(--muted); }

/* The unread badge is white on the dark theme's alert red at 4.05:1, and it is
   9.5px, the smallest text in the chrome. Brand red carries white at 6:1. */
.pc-bell-count, #pc-bell-count { background: var(--red); color: #fff; }
