/* ============================================================
   REDLINE — Design system & shared components
   ------------------------------------------------------------
   This is the ONE stylesheet used by every page. It is split into
   sections:
     1. Design tokens (colours, spacing, type) for dark + light
     2. Base/reset
     3. Layout shell (sidebar, topbar, page container)
     4. Components (cards, badges, buttons, tables, forms, etc.)
     5. Page-specific tweaks that are still generic enough to share
     6. AI assistant chat panel
     7. Responsive rules

   Dark mode is the default look. Light mode is switched on by
   adding data-theme="light" to the <html> element (see chrome.js).
   We use CSS custom properties ("tokens") so every component just
   references a variable like var(--text) instead of a raw colour —
   that's what makes theme-switching a one-line change instead of a
   find-and-replace across every file.
   ============================================================ */

/* ---------- 1. DESIGN TOKENS ---------- */

:root {
  /* Surfaces — dark charcoal, never pure black, so cards can still
     read as "raised" against the page background */
  --bg: #0b0d10;
  --bg-elevated: #14171c;
  --bg-card: #15181d;
  --bg-hover: #1d2129;
  --bg-inset: #0f1114;

  /* Borders */
  --border: #262b33;
  --border-soft: #1c2029;

  /* Text */
  --text: #f4f5f6;
  --text-muted: #9aa1ac;
  /* Phase 25d-2 — was #656c78 (3.05:1-3.68:1 against this theme's own
     surfaces, real, measured — see the WCAG 2.2 4.5:1 body-text
     minimum; .text-faint is used at 11-13px in ~200 real places across
     this codebase, e.g. dates, meta rows, sub-labels — never large
     enough to qualify for the lower 3:1 large-text threshold). Lightened
     just enough to clear 4.5:1 against every surface token below
     (worst case, --bg-hover: 4.57:1), while staying visibly a step
     dimmer than --text-muted. */
  --text-faint: #838c9b;
  --text-on-accent: #0b0d10;

  /* Signature accent — used SPARINGLY, only for genuine urgency */
  --red: #ff3b3b;
  --red-strong: #ff1f3d;
  --red-dim: rgba(255, 59, 59, 0.14);
  --red-border: rgba(255, 59, 59, 0.35);

  /* Secondary accents */
  --amber: #f5a623;
  --amber-dim: rgba(245, 166, 35, 0.14);
  --amber-border: rgba(245, 166, 35, 0.35);

  --green: #2fd672;
  --green-dim: rgba(47, 214, 114, 0.14);
  --green-border: rgba(47, 214, 114, 0.35);

  --blue: #5b9dff;
  --blue-dim: rgba(91, 157, 255, 0.14);
  --blue-border: rgba(91, 157, 255, 0.35);

  /* Calendar CATEGORY colours (Phase 6d) — deliberately separate from
     red/amber/green above, which mean genuine URGENCY everywhere else
     in this app (a zone, a status). These three exist purely so six
     different kinds of date are easy to tell apart at a glance on the
     calendar grid — they never signal "this is at risk." */
  --violet: #a78bfa;
  --violet-dim: rgba(167, 139, 250, 0.14);
  --teal: #2dd4bf;
  --teal-dim: rgba(45, 212, 191, 0.14);
  --orange: #ff8a3d;
  --orange-dim: rgba(255, 138, 61, 0.14);
  --rose: #f472b6;
  --rose-dim: rgba(244, 114, 182, 0.14);

  /* Type */
  --font-display: "Space Grotesk", "Inter", sans-serif;
  --font-body: "Inter", sans-serif;

  /* Spacing scale (4px base) */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-7: 32px;
  --sp-8: 40px;
  --sp-9: 56px;
  --sp-10: 72px;

  /* Radii */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* Shadows — dark mode uses soft glow-free elevation */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.55);

  --sidebar-w: 232px;
  --sidebar-w-collapsed: 76px;
  --topbar-h: 64px;

  --transition-fast: 120ms ease;
  --transition-base: 200ms ease;

  /* Base body text size — Comfortable (the default) is the exact
     15px every page was already designed against; Compact below
     scales it down a notch, not a redesign. */
  --base-font-size: 15px;
}

/* Density (Phase 9c) — Comfortable is the existing, unchanged default
   above; Compact is an opt-in for a power user who'd rather see more
   on screen at once, toggled in Settings and stored the same way the
   theme is (see chrome.js's applyStoredDensity()). A root class, not
   a per-page override, so every page (present and future) gets it for
   free just by using the existing --sp-* variables like it always
   has — nothing about how a page is BUILT needs to know density
   exists at all. */
:root[data-density="compact"] {
  --sp-1: 3px;
  --sp-2: 6px;
  --sp-3: 9px;
  --sp-4: 12px;
  --sp-5: 15px;
  --sp-6: 18px;
  --sp-7: 24px;
  --sp-8: 30px;
  --sp-9: 42px;
  --sp-10: 54px;
  --base-font-size: 13.5px;
}

/* Text size (Phase 25d-1) — see chrome.js's own applyStoredTextSize()
   top comment for why this is `zoom` rather than another round of
   --sp-* and --base-font-size scaling: almost all of this app's real text
   is set with hardcoded, absolute pixel `font-size` values in inline
   styles, which a token-based scale (density's own approach, just
   above) would never touch. `zoom` scales the entire rendered page —
   layout, spacing, and every hardcoded pixel size alike — which
   genuinely enlarges everything rather than a token-referencing
   fraction of it. Independent of density: a "Compact + Large text"
   combination is a real, valid, expected choice for someone who wants
   more on screen at once AND bigger text — the two multiply naturally
   since one scales the tokens density itself defines and the other
   scales the whole rendered result on top. 1.15 is a real, visible
   step up (comparable to a browser's own first zoom-in notch) without
   the wrapping/overflow issues a much larger jump risks.

   One real, confirmed-by-testing difference from native browser
   Ctrl-+ zoom, though: `vh` units are resolved against the TRUE,
   unzoomed viewport (so `100vh` still means "exactly the real screen
   height" in layout terms) but the box that height produces then
   gets PAINTED at that-layout-size × zoom, same as everything else —
   so anything sized with an exact `height: 100vh` paints visibly
   TALLER than the actual viewport under zoom, unlike native zoom
   (which redefines what "the viewport" means for the whole page, so
   100vh and the real screen never disagree). The .sidebar rule right
   below is the one place in this app that hits this — see its own
   comment. */
:root[data-text-size="large"] {
  zoom: 1.15;
}

/* Phase 25d-1 — compensates the .sidebar-specific fallout of the
   `zoom` mismatch described just above. .sidebar (further down this
   file) is the ONE full-height element in this app sized with an
   exact `height: 100vh` (not min-height — see its own comment on why:
   position:sticky plus a footer deliberately pinned to the bottom of
   the screen). Under zoom that box paints 1.15x taller than the real
   viewport, pushing the footer (confirmed live: sign-out/account
   link) below the fold with no scrollbar to recover it on any page
   whose own content isn't independently tall enough to scroll the
   page down first — a genuine reachability regression, not a cosmetic
   one. Since zoom paints at (layout height × zoom), shrinking the
   LAYOUT height by the same factor lands the PAINTED result back at
   exactly 100vh. `.app-shell`'s own `min-height: 100vh` doesn't need
   this — a min-height painting taller than the viewport is just an
   ordinary, harmless, scrollable page, not a stuck full-height box. */
:root[data-text-size="large"] .sidebar {
  height: calc(100vh / 1.15);
}

/* Light theme overrides. Kept genuinely usable (not just inverted)
   because some end users are older, non-technical brokers who need
   real contrast, not a washed-out grey-on-grey theme. */
:root[data-theme="light"] {
  --bg: #f3f3f1;
  --bg-elevated: #ffffff;
  --bg-card: #ffffff;
  --bg-hover: #ececea;
  --bg-inset: #eaeae7;

  --border: #dcdcd8;
  --border-soft: #e6e6e3;

  --text: #1a1c1f;
  --text-muted: #52565d;
  /* Phase 25d-2 — was #83878e (2.99:1-3.61:1, measured, against this
     theme's own card/inset surfaces — same real ~200-usage, always-
     small-text issue as the dark theme's own --text-faint just above).
     Darkened to clear 4.5:1 against every surface (worst case,
     --bg-inset: 4.51:1), while staying visibly lighter than
     --text-muted. */
  --text-faint: #63666c;
  --text-on-accent: #ffffff;

  --red: #d81f2f;
  --red-strong: #b8101f;
  --red-dim: rgba(216, 31, 47, 0.08);
  --red-border: rgba(216, 31, 47, 0.3);

  --amber: #a35d00;
  --amber-dim: rgba(163, 93, 0, 0.1);
  --amber-border: rgba(163, 93, 0, 0.3);

  --green: #157a3f;
  --green-dim: rgba(21, 122, 63, 0.1);
  --green-border: rgba(21, 122, 63, 0.3);

  --blue: #1d5fd1;
  --blue-dim: rgba(29, 95, 209, 0.1);
  --blue-border: rgba(29, 95, 209, 0.3);

  --violet: #6d4fd6;
  --violet-dim: rgba(109, 79, 214, 0.1);
  /* Phase 25d-2 — teal/orange were #0f8a7c / #c25a12 (4.25:1 / 4.41:1
     against this theme's white card surface, measured — both real,
     narrow AA misses when used as actual text, e.g. a calendar-entry
     label, not just as a small category dot). Nudged darker just
     enough to clear 4.5:1 (4.58:1 / 4.57:1) without changing which
     hue is which. */
  --teal: #0e8477;
  --teal-dim: rgba(15, 138, 124, 0.1);
  --orange: #be5812;
  --orange-dim: rgba(194, 90, 18, 0.1);
  --rose: #c2298a;
  --rose-dim: rgba(194, 41, 138, 0.1);

  --shadow-sm: 0 1px 2px rgba(20, 20, 15, 0.06);
  --shadow-md: 0 8px 24px rgba(20, 20, 15, 0.08);
  --shadow-lg: 0 16px 48px rgba(20, 20, 15, 0.12);
}

/* ---------- 2. BASE / RESET ---------- */

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

html {
  color-scheme: dark;
}
html[data-theme="light"] {
  color-scheme: light;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--base-font-size);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  transition: background var(--transition-base), color var(--transition-base);
}

h1, h2, h3, h4, h5 {
  font-family: var(--font-display);
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.01em;
}

p {
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font-family: var(--font-body);
  cursor: pointer;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

img, svg {
  display: block;
  max-width: 100%;
}

input, select, textarea {
  font-family: var(--font-body);
  color: var(--text);
}

::selection {
  background: var(--red);
  color: #fff;
}

/* Focus states — always visible, never removed, for keyboard users */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.numeric {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
}

/* Small helper used throughout for muted secondary text */
.text-muted { color: var(--text-muted); }
.text-faint { color: var(--text-faint); }

/* ---------- 3. APP SHELL LAYOUT ---------- */

.app-shell {
  display: flex;
  min-height: 100vh;
}

/* Sidebar — persistent left nav, injected by chrome.js on every
   authenticated page */
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--bg-elevated);
  border-right: 1px solid var(--border-soft);
  display: flex;
  flex-direction: column;
  padding: var(--sp-5) var(--sp-3);
  position: sticky;
  top: 0;
  height: 100vh;
  transition: width var(--transition-base);
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0 var(--sp-2);
  margin-bottom: var(--sp-7);
}

.sidebar-logo svg { width: 28px; height: 28px; flex-shrink: 0; }

.sidebar-logo .wordmark {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  flex: 1;
  /* The nav list can be taller than a shorter screen (13 links +
     dividers). Without this, overflow would push the footer/logout
     button below the sidebar's fixed 100vh height — and because the
     sidebar is position:sticky, that overflow is permanently stuck
     off-screen, unreachable by scrolling the page. Scrolling just the
     nav list internally keeps the footer always visible instead. */
  overflow-y: auto;
  min-height: 0;
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border-radius: var(--radius-md);
  color: var(--text-muted);
  font-size: 14px;
  font-weight: 500;
  transition: background var(--transition-fast), color var(--transition-fast);
  white-space: nowrap;
  position: relative;
}

.sidebar-link svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

.sidebar-link:hover {
  background: var(--bg-hover);
  color: var(--text);
}

.sidebar-link.active {
  background: var(--red-dim);
  color: var(--text);
}

.sidebar-link.active::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 60%;
  background: var(--red);
  border-radius: var(--radius-pill);
}

.sidebar-link .badge-count {
  margin-left: auto;
  background: var(--red);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
}

.sidebar-footer {
  border-top: 1px solid var(--border-soft);
  padding-top: var(--sp-4);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: linear-gradient(135deg, #ff3b3b, #a4192b);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  flex-shrink: 0;
}

.sidebar-footer .who { display: flex; flex-direction: column; min-width: 0; }
.sidebar-footer .who .name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-footer .who .role { font-size: 12px; color: var(--text-faint); }

/* Main column: topbar + page content */
.main-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.topbar {
  height: var(--topbar-h);
  border-bottom: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--sp-7);
  position: sticky;
  top: 0;
  background: rgba(11, 13, 16, 0.85);
  backdrop-filter: blur(8px);
  z-index: 20;
}

:root[data-theme="light"] .topbar {
  background: rgba(243, 243, 241, 0.85);
}

.topbar-title {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  color: var(--text-muted);
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.page {
  padding: var(--sp-7);
  max-width: 1320px;
  width: 100%;
  margin: 0 auto;
}

/* ---------- 4. COMPONENTS ---------- */

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  transition: all var(--transition-fast);
  white-space: nowrap;
}

.btn svg { width: 16px; height: 16px; }

.btn-primary {
  background: var(--red);
  color: #fff;
}
.btn-primary:hover { background: var(--red-strong); transform: translateY(-1px); }

.btn-secondary {
  background: var(--bg-hover);
  color: var(--text);
  border-color: var(--border);
}
.btn-secondary:hover { background: var(--border); }

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

.btn-success {
  background: var(--green);
  color: #06301a;
}
.btn-success:hover { filter: brightness(1.08); }

.btn-sm { padding: 6px 12px; font-size: 13px; border-radius: var(--radius-sm); }
.btn-block { width: 100%; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; }

/* Cards */
.card {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-6);
}

.card-tight { padding: var(--sp-4); }

/* Stat cards on dashboard */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-4);
  margin-bottom: var(--sp-7);
}

/* Some pages (Revenue, Claims, Pipeline) show 3 stat cards instead of
   4 — a class, not an inline style, so the responsive rules below can
   still override it at narrow widths (inline styles always win over
   a stylesheet, even inside a media query). */
.stat-grid-3 { grid-template-columns: repeat(3, 1fr); }
.stat-grid-2 { grid-template-columns: repeat(2, 1fr); }

.stat-card {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  position: relative;
  overflow: hidden;
}

.stat-card .stat-label {
  font-size: 13px;
  color: var(--text-muted);
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.stat-card .stat-value {
  font-family: var(--font-display);
  font-size: 38px;
  font-weight: 700;
  line-height: 1;
}

.stat-card .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.dot-red { background: var(--red); box-shadow: 0 0 8px var(--red); }
.dot-amber { background: var(--amber); }
.dot-green { background: var(--green); }
.dot-muted { background: var(--text-faint); }

/* Phase 16c — the trust dashboard's own status dots (Settings ->
   Security): reuses the .dot-red/.dot-amber/.dot-green colour classes
   above, but with its own sizing since those are otherwise scoped to
   .stat-card .dot. */
.trust-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 7px;
  vertical-align: middle;
}

.stat-card.urgent {
  border-color: var(--red-border);
}
.stat-card.urgent::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, var(--red-dim), transparent 60%);
  pointer-events: none;
}

/* Badges: countdown + status */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 700;
  font-family: var(--font-display);
  border: 1px solid transparent;
  white-space: nowrap;
}

.badge-red { background: var(--red-dim); color: var(--red); border-color: var(--red-border); }
.badge-amber { background: var(--amber-dim); color: var(--amber); border-color: var(--amber-border); }
.badge-green { background: var(--green-dim); color: var(--green); border-color: var(--green-border); }
.badge-blue { background: var(--blue-dim); color: var(--blue); border-color: var(--blue-border); }
.badge-muted { background: var(--bg-hover); color: var(--text-muted); border-color: var(--border); }

.badge-red.pulse::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--red);
  animation: pulse-dot 1.6s infinite;
}

/* Phase 16a — a loud, impossible-to-miss warning banner. Used right
   now for "database encryption is OFF" on Settings → Security, but
   generic enough (just "banner-danger", not "encryption-banner") for
   any future must-not-miss security warning. */
.banner-danger {
  background: var(--red-dim);
  border: 1px solid var(--red-border);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  color: var(--text);
}
.banner-danger strong { color: var(--red); font-size: 14px; }
.banner-danger p { font-size: 13px; color: var(--text-muted); }

@keyframes pulse-dot {
  0% { box-shadow: 0 0 0 0 var(--red-border); }
  70% { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Status pill used for plain-English state, distinct from urgency badges */
.status-pill {
  font-size: 13px;
  color: var(--text-muted);
}

/* Tables */
.table-wrap {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

table {
  width: 100%;
  border-collapse: collapse;
}

thead th {
  text-align: left;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  font-weight: 600;
  padding: var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--border-soft);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

thead th:hover { color: var(--text-muted); }

thead th .sort-arrow { opacity: 0.4; margin-left: 4px; font-size: 10px; }
thead th.sorted .sort-arrow { opacity: 1; color: var(--red); }

tbody tr {
  border-bottom: 1px solid var(--border-soft);
  transition: background var(--transition-fast);
}
tbody tr:last-child { border-bottom: none; }

tbody tr.clickable { cursor: pointer; }
tbody tr.clickable:hover { background: var(--bg-hover); }

/* Generic clickable-row affordance for non-<tr> rows (claim-row,
   kanban-card, etc). */
.clickable { cursor: pointer; transition: background var(--transition-fast); }
.clickable:hover { background: var(--bg-hover); }

td {
  padding: var(--sp-4) var(--sp-5);
  font-size: 14px;
  vertical-align: middle;
}

td.cell-primary { font-weight: 600; }

/* Forms */

/* A server-reported form error (wrong login, etc). Red is earned
   here — a failed login is a genuine, if minor, moment of friction,
   same red used for renewal urgency elsewhere in the app. */
.form-error {
  background: var(--red-dim);
  border: 1px solid var(--red-border);
  color: var(--red);
  font-size: 13px;
  font-weight: 500;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-md);
  margin-bottom: var(--sp-5);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-5);
}

.field label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

.field input,
.field select,
.field textarea {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 11px 14px;
  font-size: 14px;
  color: var(--text);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

/* Phase 25d-3 — a real gap found live: this rule's own `outline: none`
   is MORE specific than the generic `input:focus-visible` ring
   further up this file, so it always wins — meaning every form field
   built with `.field` (the large majority of every form in this app)
   relied on a 1px border-colour swap alone to show focus, easy to
   miss for exactly the audience this product already designs around
   (see chrome.js's own applyStoredTextSize() comment on presbyopia).
   Keeping the border/background change (it's a real, correct signal)
   but adding a genuine soft ring alongside it via box-shadow, using
   this same red the border already switches to, so it doesn't
   introduce a second, competing focus colour. */
.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--red);
  background: var(--bg-elevated);
  outline: none;
  box-shadow: 0 0 0 3px var(--red-dim);
}

.field textarea { resize: vertical; min-height: 100px; font-family: var(--font-body); }

.field-row { display: flex; align-items: center; gap: var(--sp-2); }
/* Phase 25d-2 — was 16x16 (below the WCAG 2.2 24x24 minimum target size). */
.field-row input[type="checkbox"] { width: 24px; height: 24px; accent-color: var(--red); }
.field-row label { font-size: 13px; color: var(--text-muted); font-weight: 500; }

.search-input {
  position: relative;
  min-width: 260px;
}
.search-input svg {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  color: var(--text-faint);
}
.search-input input {
  width: 100%;
  padding-left: 38px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding-top: 10px;
  padding-bottom: 10px;
  padding-right: 14px;
  color: var(--text);
  font-size: 14px;
}
/* Phase 25d-3 — same fix, same reason as .field input:focus above. */
.search-input input:focus { border-color: var(--red); outline: none; box-shadow: 0 0 0 3px var(--red-dim); }

/* Theme toggle switch */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--bg-hover);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 4px;
  cursor: pointer;
  position: relative;
  width: 56px;
  height: 30px;
}
.theme-toggle .knob {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--red);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  transition: transform var(--transition-base);
  transform: translateX(0);
}
:root[data-theme="light"] .theme-toggle .knob { transform: translateX(26px); }
.theme-toggle .knob svg { width: 13px; height: 13px; }

/* Progress bar — used on the renewal runway rows */
.progress-track {
  width: 100%;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--bg-inset);
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--green), var(--amber), var(--red));
  transition: width 400ms ease;
  position: relative;
}
.progress-fill.animated::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.25), transparent);
  animation: shimmer 2.2s infinite;
}
@keyframes shimmer {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

/* Renewal runway list (dashboard) */
.runway-row {
  display: grid;
  grid-template-columns: 90px 1.4fr 1fr 1.6fr auto;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-soft);
}
.runway-row:last-child { border-bottom: none; }

.runway-client .name { font-weight: 600; font-size: 14px; }
.runway-client .meta { font-size: 12px; color: var(--text-faint); margin-top: 2px; }

.runway-progress { display: flex; flex-direction: column; gap: 6px; }
.runway-progress .days-label { font-size: 12px; color: var(--text-faint); }

/* Timeline / pizza-tracker (client detail page) */
.timeline {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  position: relative;
  padding: var(--sp-4) 0;
}

.timeline-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  flex: 1;
  position: relative;
  z-index: 1;
}

.timeline-step .node {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--bg-inset);
  border: 2px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-faint);
  margin-bottom: var(--sp-2);
  transition: all var(--transition-base);
}
.timeline-step .node svg { width: 14px; height: 14px; }

.timeline-step.done .node {
  background: var(--green);
  border-color: var(--green);
  color: #06301a;
}
.timeline-step.current .node {
  /* --red-dim is a translucent rgba() tint (14% opacity), not a solid
     colour — on its own that lets the connecting line drawn behind
     this node (z-index: 0, see .timeline-line below) show straight
     through the circle, right across the step number, even though
     stacking order already puts the node on top. The done/pending
     nodes never had this problem because --green and --bg-inset are
     both fully opaque. Layering the same tint over a solid card-
     coloured backing keeps the exact intended dim-red look while
     actually blocking whatever's behind it. */
  background: linear-gradient(var(--red-dim), var(--red-dim)), var(--bg-card);
  border-color: var(--red);
  color: var(--red);
  animation: pulse-ring 1.8s infinite;
}
@keyframes pulse-ring {
  0% { box-shadow: 0 0 0 0 var(--red-border); }
  70% { box-shadow: 0 0 0 8px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.timeline-step .label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  max-width: 100px;
}
.timeline-step.done .label,
.timeline-step.current .label { color: var(--text); }

/* top = .timeline's own padding-top (var(--sp-4), the space above
   where a .timeline-step actually starts) + half the node's own 32px
   height, landing this line exactly on every node's vertical centre.
   Was a hardcoded 32px before — correct by coincidence at default
   density (16px padding + 16px), but silently wrong under
   :root[data-density="compact"], where --sp-4 drops to 12px and this
   never followed it down. calc() ties it to the same variable the
   padding actually uses instead of a second, easily-stale copy of it. */
.timeline-line {
  position: absolute;
  top: calc(var(--sp-4) + 16px);
  left: 5%;
  right: 5%;
  height: 2px;
  background: var(--border);
  z-index: 0;
}
.timeline-line-fill {
  position: absolute;
  top: calc(var(--sp-4) + 16px);
  left: 5%;
  height: 2px;
  background: var(--green);
  z-index: 0;
  transition: width 500ms ease;
}

/* Approvals queue cards */
.approval-item {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  background: var(--bg-card);
  margin-bottom: var(--sp-4);
  overflow: hidden;
}

.approval-head {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-5);
  cursor: pointer;
}

.approval-head .icon-wrap {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--bg-inset);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-muted);
}
.approval-head .icon-wrap svg { width: 18px; height: 18px; }

.approval-head .info { flex: 1; min-width: 0; }
.approval-head .info .title { font-weight: 600; font-size: 14px; }
.approval-head .info .sub { font-size: 12px; color: var(--text-faint); margin-top: 2px; }

.approval-head .chevron {
  width: 18px;
  height: 18px;
  color: var(--text-faint);
  transition: transform var(--transition-base);
  flex-shrink: 0;
}
.approval-item.open .chevron { transform: rotate(180deg); }

.approval-body {
  display: none;
  padding: 0 var(--sp-5) var(--sp-5);
  border-top: 1px solid var(--border-soft);
}
.approval-item.open .approval-body { display: block; padding-top: var(--sp-5); }

.draft-box {
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: var(--sp-5);
  font-size: 14px;
  line-height: 1.7;
  white-space: pre-wrap;
  margin-bottom: var(--sp-4);
}
.draft-box[contenteditable="true"] {
  border-color: var(--blue-border);
  background: var(--bg-elevated);
}

.approval-actions {
  display: flex;
  gap: var(--sp-3);
  justify-content: flex-end;
}

.approval-item.sent .approval-head { opacity: 0.6; }
.sent-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--green);
  font-weight: 600;
}

/* Activity feed */
.feed-item {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: 13px;
}
.feed-item:last-child { border-bottom: none; }
.feed-item .feed-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-faint);
  margin-top: 6px;
  flex-shrink: 0;
}
.feed-item .feed-time { color: var(--text-faint); font-size: 12px; white-space: nowrap; }

/* Fact-find card (client detail) */
.fact-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-4);
}
.fact-item { display: flex; flex-direction: column; gap: 4px; }
.fact-item .fact-label { font-size: 12px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.03em; }
.fact-item .fact-value { font-size: 15px; font-weight: 600; }
.fact-item .fact-value[contenteditable="true"] {
  border-bottom: 1px dashed var(--border);
  padding-bottom: 2px;
}
/* Phase 25d-3 — same fix, same reason as .field input:focus above; a
   border-bottom colour swap alone is an especially subtle change for
   a single-line inline-edit field. */
.fact-item .fact-value[contenteditable="true"]:focus {
  outline: none;
  border-bottom-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-dim);
}

/* Empty / helper states */
.empty-state {
  text-align: center;
  padding: var(--sp-9) var(--sp-6);
  color: var(--text-faint);
}

/* Section headers reused across pages */
.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-4);
}
.section-head h2 { font-size: 17px; }

/* Login page */
.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  position: relative;
  overflow: hidden;
}
.login-wrap::before {
  content: "";
  position: absolute;
  width: 900px;
  height: 900px;
  background: radial-gradient(circle, var(--red-dim) 0%, transparent 70%);
  top: -300px;
  right: -300px;
  pointer-events: none;
}
.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-8) var(--sp-7);
  box-shadow: var(--shadow-lg);
  position: relative;
  z-index: 1;
}
.login-logo {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-7);
}
.login-logo svg { width: 36px; height: 36px; }
.login-logo .wordmark { font-family: var(--font-display); font-size: 22px; font-weight: 700; }
/* Language selector (Phase 10a) — sits between the logo and the
   title, its own small row, same .filter-chip look every other pill
   choice in this app already uses. */
.login-lang-row { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-6); }
.login-lang-row .filter-chip { padding: 5px 12px; font-size: 12px; }
.login-title { font-size: 20px; margin-bottom: var(--sp-1); }
.login-sub { color: var(--text-muted); font-size: 14px; margin-bottom: var(--sp-6); }
.login-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-6);
}
.login-foot a { font-size: 13px; color: var(--text-muted); }
.login-foot a:hover { color: var(--red); }

/* Phase 16b — "Continue with Google." Google's own branding
   guidelines call for a plain white/light button with their real
   multi-colour "G" mark and readable dark text — never re-themed to
   match a host app's own colours (a dark-red Redline-styled Google
   button would misrepresent whose sign-in flow this actually is) —
   so this is deliberately the one button on this page that does NOT
   follow Redline's own dark-first, red-accent button styling. */
.login-divider {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-5) 0;
  color: var(--text-faint);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.login-divider::before, .login-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-soft);
}
.google-signin-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 10px 16px;
  background: #fff;
  color: #3c4043;
  border: 1px solid #dadce0;
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: box-shadow 0.15s ease, background 0.15s ease;
}
.google-signin-btn:hover { background: #f8f9fa; box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
.login-tagline {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--sp-7);
  text-align: center;
  font-size: 12px;
  color: var(--text-faint);
  letter-spacing: 0.03em;
}

/* Toggles for settings integrations, list items */
.list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--border-soft);
}
.list-item:last-child { border-bottom: none; }

.pill-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: var(--bg-hover);
  color: var(--text-faint);
  border: 1px solid var(--border);
  /* Without these, a .list-item row's flex layout squeezes this pill
     narrower whenever its sibling text is long enough to need the
     room (a two-line description vs. a one-line one) — the pill has
     no fixed width of its own, so it was competing for space instead
     of just keeping its natural size, which is what made "COMING
     SOON" wrap to two lines on some Integrations rows and not others,
     depending only on how long that row's description happened to be. */
  flex-shrink: 0;
  white-space: nowrap;
}

/* The form-template mapper's raw PDF field name (public/js/formTemplates.js)
   — a real internal identifier baked into the uploaded PDF file itself
   (e.g. "vehicle_1_year"), never something a client sees. Previously
   plain unstyled monospace text sitting in its own full table column,
   reading as leftover debug output rather than an intentional part of
   the page. A small muted reference chip signals "this is what the
   PDF calls it, for your own double-checking" without competing for
   attention with the two things actually worth acting on: the plain-
   English label and the data it maps to. */
.pdf-field-chip {
  display: inline-block;
  font-family: monospace;
  font-size: 11px;
  color: var(--text-faint);
  background: var(--bg-inset);
  padding: 3px 7px;
  border-radius: var(--radius-sm);
}

/* ---------- 5. LOADING SKELETONS ----------
   Every page now waits on a real network request before it has
   anything to show — see public/js/api.js. Rather than a blank flash
   (content pops in) or a spinner (feels broken if the request is
   fast), each page's markup starts as a few skeleton blocks — plain
   rectangles the right rough SIZE and SHAPE for what's about to load
   there — with a slow shimmer so it reads as "loading", not "empty".
   Drop a few .skeleton-line / .skeleton-block elements into any
   container while data is in flight, then replace that container's
   innerHTML with the real render once the fetch resolves — the same
   pattern every page already uses for its normal render functions,
   just with one extra render pass before the real one. */

.skeleton-line, .skeleton-block {
  background: linear-gradient(90deg, var(--bg-inset) 25%, var(--bg-hover) 50%, var(--bg-inset) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.6s ease-in-out infinite;
  border-radius: var(--radius-sm);
}

.skeleton-line {
  height: 14px;
  margin-bottom: var(--sp-3);
}
.skeleton-line.short { width: 40%; }
.skeleton-line.medium { width: 65%; }
.skeleton-line.wide { width: 100%; }

.skeleton-block {
  width: 100%;
  border-radius: var(--radius-md);
}

@keyframes skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ---------- 6. AI ASSISTANT CHAT ---------- */

.ai-launcher {
  position: fixed;
  bottom: var(--sp-6);
  right: var(--sp-6);
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--red);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-lg);
  z-index: 50;
  border: none;
  transition: transform var(--transition-base);
}
.ai-launcher:hover { transform: scale(1.06); }
.ai-launcher svg { width: 24px; height: 24px; }

.ai-panel {
  position: fixed;
  bottom: calc(var(--sp-6) + 72px);
  right: var(--sp-6);
  width: 380px;
  max-height: 560px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  z-index: 50;
  overflow: hidden;
  opacity: 0;
  transform: translateY(16px) scale(0.98);
  pointer-events: none;
  transition: all var(--transition-base);
}
.ai-panel.open {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

.ai-panel-head {
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.ai-panel-head .title { display: flex; align-items: center; gap: var(--sp-2); font-weight: 600; font-size: 14px; }
.ai-panel-head .title .rdot { width: 7px; height: 7px; border-radius: 50%; background: var(--red); box-shadow: 0 0 6px var(--red); }
.ai-panel-close { background: none; border: none; color: var(--text-faint); }
.ai-panel-close svg { width: 18px; height: 18px; }

/* The required "AI can be wrong" notice (Phase 4e) — always visible,
   never dismissible, sits between the header and the conversation. */
.ai-notice {
  padding: 8px var(--sp-5);
  font-size: 11.5px;
  color: var(--text-faint);
  background: var(--bg-inset);
  border-bottom: 1px solid var(--border-soft);
}

/* Phase 25b-4 — first-visit pointers. Blue, not red: never urgent,
   just a one-time "here's the one thing that matters" nudge — see
   this phase's own standing rule that red is reserved for genuinely
   urgent things. Deliberately plain: no arrow, no animation, no
   backdrop — a single sentence and a dismiss button, gone forever
   once clicked. */
.first-visit-pointer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 10px var(--sp-4);
  margin: var(--sp-3) 0;
  font-size: 12.5px;
  color: var(--text);
  background: var(--blue-dim);
  border: 1px solid var(--blue-border);
  border-radius: var(--radius-md);
}
.first-visit-pointer button { flex-shrink: 0; }

.ai-panel-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--sp-4) var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.ai-msg {
  max-width: 88%;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  font-size: 13.5px;
  line-height: 1.5;
}
.ai-msg.bot {
  background: var(--bg-hover);
  align-self: flex-start;
  border-bottom-left-radius: 2px;
}
.ai-msg.user {
  background: var(--red);
  color: #fff;
  align-self: flex-end;
  border-bottom-right-radius: 2px;
}

.ai-suggestions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: 0 var(--sp-5) var(--sp-3);
}
.ai-suggestion-btn {
  text-align: left;
  background: var(--bg-hover);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: 12.5px;
  color: var(--text-muted);
  transition: all var(--transition-fast);
}
.ai-suggestion-btn:hover { border-color: var(--red-border); color: var(--text); }

/* A drafting action's result link (Phase 4e) — e.g. "View X chase in
   Approvals →", shown under the bot's reply when it just queued
   something real. A plain link, deliberately: it's server-controlled
   data (never parsed out of the AI's own free-text answer), styled
   to sit quietly under the message bubble rather than look like part
   of the AI's own words. */
.ai-action-link {
  align-self: flex-start;
  margin-top: -6px;
  font-size: 12px;
  color: var(--red);
  font-weight: 600;
}
.ai-action-link:hover { text-decoration: underline; }

.ai-panel-input {
  border-top: 1px solid var(--border-soft);
  padding: var(--sp-3);
  display: flex;
  gap: var(--sp-2);
}
.ai-panel-input input {
  flex: 1;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  font-size: 13.5px;
  color: var(--text);
}
/* Phase 25d-3 — same fix, same reason as .field input:focus above. */
.ai-panel-input input:focus { outline: none; border-color: var(--red); box-shadow: 0 0 0 3px var(--red-dim); }
.ai-panel-input button {
  background: var(--red);
  color: #fff;
  border: none;
  border-radius: var(--radius-md);
  width: 38px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ai-panel-input button svg { width: 16px; height: 16px; }

/* Client detail page header */
.client-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--sp-6);
  margin-bottom: var(--sp-6);
}
.client-header .title-row { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: 6px; }
.client-header h1 { font-size: 26px; }
.client-header .biz-type { color: var(--text-muted); font-size: 14px; margin-bottom: var(--sp-4); }
.client-header .premium-block { text-align: right; flex-shrink: 0; }
.client-header .premium-block .label { font-size: 12px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.03em; }
.client-header .premium-block .value { font-family: var(--font-display); font-size: 30px; font-weight: 700; }

/* ---------- policy switcher (Phase 11a) ----------
   Compact cards for a client holding more than one policy — type,
   carrier, premium, renewal countdown, status ribbon, at a glance.
   Same "one glance rule" every other card in this app follows: never
   more than these five facts on the card face itself. */
.policy-switch-row {
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.policy-switch-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 190px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  cursor: pointer;
  text-align: left;
  transition: all var(--transition-fast);
  font-family: inherit;
}
.policy-switch-card:hover { border-color: var(--border); }
.policy-switch-card.active { border-color: var(--red); box-shadow: 0 0 0 1px var(--red); }
.policy-switch-card .psc-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.policy-switch-card .psc-type { font-weight: 600; font-size: 13.5px; }
.policy-switch-card .psc-insurer { font-size: 12.5px; color: var(--text-muted); }
.policy-switch-card .psc-bottom { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); font-size: 12.5px; margin-top: 2px; }
.policy-switch-add {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 120px;
  padding: var(--sp-3) var(--sp-4);
  background: transparent;
  border: 1px dashed var(--border-soft);
  border-radius: var(--radius-md);
  color: var(--text-faint);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: all var(--transition-fast);
}
.policy-switch-add:hover { border-color: var(--border); color: var(--text); }

.contact-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-4);
}
.contact-grid .fact-label { font-size: 12px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 4px; }
.contact-grid .fact-value { font-size: 14px; font-weight: 600; }

/* The client header's own two lower rows: recent-activity info, then
   (visually separated — different question entirely) the per-client
   correspondence settings. Previously all four pieces (contact tag,
   last-touched, language, register) sat in one flex-wrap row with no
   grouping, so on a narrower render it wrapped into what just looked
   like a pile of unrelated chips. */
.client-activity-row {
  margin-top: var(--sp-3);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
}
.client-correspondence-row {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-3);
}
.client-correspondence-heading {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}
.client-correspondence-heading .text-faint { font-size: 11.5px; font-weight: 400; }
.client-correspondence-controls {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  flex-wrap: wrap;
}

/* ---------- "last touched" strip (Phase 9c) ----------
   Deliberately quiet — this is a continuity aid, not an alert, so it
   never uses red/amber. A subtle single line; click it for the last
   3 activity items in a small popover. */
.last-touched-strip { position: relative; display: inline-block; }
.last-touched-toggle {
  display: flex;
  align-items: center;
  gap: 7px;
  background: none;
  border: none;
  padding: 0;
  color: var(--text-faint);
  font-size: 12.5px;
  cursor: pointer;
  text-align: left;
}
.last-touched-toggle:hover { color: var(--text-muted); }
.last-touched-toggle strong { color: var(--text-muted); font-weight: 600; }
.last-touched-popover {
  display: none;
  position: absolute;
  z-index: 60;
  top: calc(100% + 8px);
  left: 0;
  flex-direction: column;
  gap: 10px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-3) var(--sp-4);
  width: 280px;
}
.last-touched-popover.open { display: flex; }
.last-touched-popover-item { font-size: 12.5px; }
.last-touched-popover-item:not(:last-child) { padding-bottom: 8px; border-bottom: 1px solid var(--border-soft); }

.doc-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: 13.5px;
}
.doc-row:last-child { border-bottom: none; }
.doc-row .doc-icon {
  width: 32px; height: 32px;
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-muted);
  flex-shrink: 0;
}
.doc-row .doc-icon svg { width: 15px; height: 15px; }
.doc-row .doc-name { flex: 1; font-weight: 500; }
.doc-row .doc-meta { color: var(--text-faint); font-size: 12px; }

.notes-box {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-muted);
  margin-top: var(--sp-2);
}

/* ============================================================
   PHASE 2 COMPONENTS
   ============================================================ */

/* Sidebar section break between nav groups */
.sidebar-divider {
  height: 1px;
  background: var(--border-soft);
  margin: var(--sp-3) var(--sp-2);
}

/* Generic icon-only button (topbar bell, etc). Same footprint as
   the theme toggle so the topbar's right-hand cluster lines up. */
.icon-btn {
  position: relative;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  background: var(--bg-hover);
  border: 1px solid var(--border);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition-fast);
}
.icon-btn:hover { color: var(--text); border-color: var(--border-soft); }
.icon-btn svg { width: 18px; height: 18px; }

/* Notifications bell + dropdown */
.notif-wrap { position: relative; }
.notif-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--red);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--bg-elevated);
}
.notif-dropdown {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: 360px;
  max-height: 420px;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: all var(--transition-fast);
  z-index: 40;
}
.notif-dropdown.open { opacity: 1; transform: translateY(0); pointer-events: auto; }

/* ---------- "explain this screen" help panel (Phase 9c) ----------
   Same wrap/dropdown shape as the notifications bell right next to
   it — a familiar interaction reused, not a new one invented. */
.help-wrap { position: relative; }
#help-btn { font-weight: 700; font-size: 15px; }
.help-dropdown {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: 320px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-4);
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: all var(--transition-fast);
  z-index: 40;
}
.help-dropdown.open { opacity: 1; transform: translateY(0); pointer-events: auto; }
.help-dropdown-head { font-weight: 700; font-size: 13.5px; margin-bottom: 8px; }
.help-dropdown p { font-size: 13px; color: var(--text-muted); line-height: 1.5; margin-bottom: 10px; }
.help-dropdown p:last-child { margin-bottom: 0; }
.help-dropdown .help-tip { color: var(--text-faint); font-size: 12.5px; }
.help-dropdown .help-tip strong { color: var(--text-muted); }
.notif-dropdown-head {
  padding: var(--sp-4) var(--sp-4) var(--sp-2);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}
.notif-item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--border-soft);
  transition: background var(--transition-fast);
}
.notif-item:hover { background: var(--bg-hover); }
.notif-dot { width: 7px; height: 7px; border-radius: 50%; margin-top: 6px; flex-shrink: 0; }
.notif-dot-client { background: var(--red); }
.notif-dot-quote { background: var(--blue); }
.notif-dot-claim { background: var(--amber); }
.notif-dot-task { background: var(--red); }
.notif-dot-pipeline { background: var(--green); }
.notif-text { flex: 1; font-size: 13px; line-height: 1.4; }
.notif-time { font-size: 11px; color: var(--text-faint); white-space: nowrap; }

/* Dashboard filter chips above the renewal runway */
.filter-row { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-4); flex-wrap: wrap; }
.filter-chip {
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 600;
  transition: all var(--transition-fast);
}
.filter-chip:hover { border-color: var(--border-soft); color: var(--text); }
.filter-chip.active { background: var(--red); border-color: var(--red); color: #fff; }

/* Dashboard widget row: Revenue / Tasks / Claims / Pipeline, each a
   clickable card through to its own page. */
.widget-grid {
  display: grid;
  /* 7 widgets since Phase 8c's "Policies protected" card joined the
     row (6 since Phase 8a's Certificates card) — a clean 3-per-row
     wrap (2 full rows of 3, one trailing card) rather than cramming
     every card into one ever-narrower row (see the marketing site's
     own identical "9 items, 3x3" reasoning from Phase 7b for why a
     wrapped grid beats a single row). A lone trailing card in the
     last row is a normal, common dashboard shape — not worth forcing
     an artificial 4th card just to fill it. */
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-4);
  margin-bottom: var(--sp-7);
}
.widget-card {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  transition: all var(--transition-fast);
}
a.widget-card { cursor: pointer; }
a.widget-card:hover { border-color: var(--border); background: var(--bg-hover); transform: translateY(-2px); }
.widget-card .widget-head { display: flex; align-items: center; justify-content: space-between; }
.widget-card .widget-title { font-size: 13px; font-weight: 600; color: var(--text-muted); display: flex; align-items: center; gap: 8px; }
.widget-card .widget-title svg { width: 16px; height: 16px; }
.widget-card .widget-value { font-family: var(--font-display); font-size: 26px; font-weight: 700; }
.widget-card .widget-sub { font-size: 12px; color: var(--text-faint); }
.widget-card .widget-list { display: flex; flex-direction: column; gap: 6px; margin-top: 2px; }
.widget-card .widget-list-row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  font-size: 12.5px;
  color: var(--text-muted);
  min-width: 0;
}
.widget-card .widget-list-row span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.widget-card .widget-list-row span:last-child { flex-shrink: 0; }

/* ---------- Dashboard Customise mode (Phase 15a) ----------
   See public/js/dashboard.js and public/js/widgets/registry.js. */
.dash-customize-bar { display: flex; align-items: center; gap: var(--sp-2); }
.dash-customize-bar svg { width: 15px; height: 15px; }

/* Every widget renders for real inside its own frame — this list is
   deliberately ONE plain column regardless of a widget's normal size
   (stat/medium/large), so dragging is always a simple up/down
   reorder, never a cross-grid puzzle. See registry.js's own comment
   on why "no free-form grid" outranks flexibility here. */
.widget-customize-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.widget-customize-frame {
  display: flex;
  flex-direction: column;
  border: 1px dashed var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-3);
  background: var(--bg-card);
  cursor: grab;
  position: relative; /* Phase 17b — .wcf-resize handles are absolute, so the frame must be the positioning context */
  overflow: hidden; /* Phase 17b — keep frames inside their grid cells so resize handles don't leak across cells */
}
.widget-customize-frame.dragging { opacity: 0.4; }
.widget-customize-frame .wcf-toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 1px dashed var(--border-soft);
}
.widget-customize-frame .wcf-handle { display: flex; color: var(--text-faint); flex-shrink: 0; }
.widget-customize-frame .wcf-handle svg { width: 16px; height: 16px; }
.widget-customize-frame .wcf-title { flex: 1; display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; color: var(--text-muted); }
.widget-customize-frame .wcf-title svg { width: 15px; height: 15px; }
.widget-customize-frame .wcf-remove { background: none; border: none; color: var(--text-faint); cursor: pointer; display: flex; padding: 2px; flex-shrink: 0; }
.widget-customize-frame .wcf-remove:hover { color: var(--red); }
.widget-customize-frame .wcf-remove svg { width: 16px; height: 16px; }
/* The real widget content, mounted inside — a link/card widget's own
   hover lift (a.widget-card:hover, a.stat-card:hover) would fight
   with dragging the FRAME around it, so switch it off in here. */
.widget-customize-frame .wcf-body { pointer-events: none; }

.widget-tray-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.widget-tray-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
}
.widget-tray-item-body { flex: 1; min-width: 0; }
.widget-tray-item-title { font-size: 13px; font-weight: 600; }
.widget-tray-item-desc { font-size: 12px; color: var(--text-faint); }

/* Static, schematic thumbnails — NOT a live render of real data, just
   enough of a shape (plus the widget's own real icon, or a stat's own
   dot colour) to recognise it by, per the phase brief's own "static
   SVG per widget, not live renders" instruction. */
.widget-thumb {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  background: var(--bg-hover);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 8px;
}
.widget-thumb svg { width: 16px; height: 16px; color: var(--text-faint); }
.widget-thumb-stat { width: 56px; height: 40px; }
.widget-thumb-medium { width: 72px; height: 48px; }
.widget-thumb-large { width: 96px; height: 40px; flex-direction: row; align-items: center; }
.widget-thumb-dot { width: 10px; height: 10px; border-radius: 50%; }
.widget-thumb-bar { display: block; height: 4px; border-radius: 2px; background: var(--border-soft); width: 100%; }
.widget-thumb-bar.short { width: 60%; }

/* Lightweight bar chart for the Revenue page — plain divs, no
   chart library, height driven by an inline style percentage. */
.bar-chart { display: flex; align-items: flex-end; gap: var(--sp-2); height: 220px; padding-top: var(--sp-4); }
.bar-chart-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; justify-content: flex-end; }
.bar-chart-bar { width: 100%; border-radius: 4px 4px 0 0; background: var(--red); opacity: 0.8; transition: opacity var(--transition-fast); min-height: 3px; cursor: default; }
.bar-chart-col:hover .bar-chart-bar { opacity: 1; }
.bar-chart-label { font-size: 10.5px; color: var(--text-faint); }
.bar-chart-value { font-size: 10.5px; color: var(--text-muted); font-family: var(--font-display); }

/* Tasks */
.task-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border-soft);
}
.task-row:last-child { border-bottom: none; }
.task-checkbox {
  /* Phase 25d-2 — was 20x20 (below the WCAG 2.2 24x24 minimum target
     size for a standalone clickable control; this <span> IS the whole
     hit target, no surrounding padding). */
  width: 24px;
  height: 24px;
  border-radius: 6px;
  border: 1.5px solid var(--border);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: transparent;
  background: var(--bg-inset);
}
.task-checkbox.checked { background: var(--green); border-color: var(--green); color: #06301a; }
.task-checkbox svg { width: 12px; height: 12px; }
.task-row .task-body { flex: 1; min-width: 0; }
.task-row .task-title { font-size: 14px; }
.task-row.done .task-title { text-decoration: line-through; color: var(--text-faint); }
.task-row .task-client { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
.task-suggested-badge {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  background: var(--blue-dim);
  color: var(--blue);
  border: 1px solid var(--blue-border);
  white-space: nowrap;
}
.quick-add-row { display: flex; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.quick-add-row input { flex: 1; }

/* Claims */
.claim-row {
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-soft);
  display: grid;
  grid-template-columns: 1.5fr 1fr 130px 130px auto;
  align-items: center;
  gap: var(--sp-4);
}
.claim-row:last-child { border-bottom: none; }
.claim-row .claim-client { font-weight: 600; font-size: 14px; }
.claim-row .claim-type { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
.claim-timeline-item { display: flex; gap: var(--sp-3); padding: var(--sp-3) 0; border-bottom: 1px solid var(--border-soft); font-size: 13px; }
.claim-timeline-item:last-child { border-bottom: none; }
.claim-timeline-item .feed-dot { margin-top: 6px; }

/* Pipeline kanban board */
.kanban-board {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-4);
  align-items: start;
}
.kanban-column {
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-3);
  min-height: 200px;
  transition: background var(--transition-fast);
}
.kanban-column.drag-over { background: var(--red-dim); }
.kanban-column-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--sp-2) var(--sp-2) var(--sp-3);
  font-size: 13px;
  font-weight: 700;
}
.kanban-column-head .count { color: var(--text-faint); font-weight: 500; }
.kanban-card {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  margin-bottom: var(--sp-3);
  cursor: grab;
  transition: box-shadow var(--transition-fast), border-color var(--transition-fast);
}
.kanban-card:active { cursor: grabbing; }
.kanban-card:hover { border-color: var(--border); box-shadow: var(--shadow-sm); }
.kanban-card.dragging { opacity: 0.4; }
.kanban-card.stale { border-color: var(--amber-border); }
.kanban-card .kc-name { font-weight: 600; font-size: 13.5px; margin-bottom: 4px; }
.kanban-card .kc-meta { font-size: 11.5px; color: var(--text-faint); margin-bottom: 8px; }
.kanban-card .kc-value { font-size: 12.5px; color: var(--text-muted); display: flex; justify-content: space-between; }
.kanban-card .kc-days { font-size: 11px; margin-top: 8px; }
.kanban-card .kc-actions { margin-top: 10px; }

/* Quote comparison table (client detail) */
.quote-table-wrap { overflow-x: auto; }
.quote-table { width: 100%; border-collapse: collapse; }
.quote-table th, .quote-table td { padding: var(--sp-3) var(--sp-4); text-align: left; font-size: 13.5px; border-bottom: 1px solid var(--border-soft); vertical-align: top; }
.quote-table th { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-faint); }
.quote-table td.quote-insurer { font-weight: 700; }
.quote-table tr.recommended { background: var(--green-dim); }
.quote-change { display: inline-flex; align-items: center; gap: 4px; font-weight: 700; font-size: 12.5px; }
.quote-change.up { color: var(--red); }
.quote-change.down { color: var(--green); }
.quote-change.flat { color: var(--text-faint); }
.quote-cover-list { margin: 0; padding-left: 0; list-style: none; font-size: 12.5px; color: var(--text-muted); display: flex; flex-direction: column; gap: 3px; }
.recommend-btn { white-space: nowrap; }

/* Missing-document indicator on the Documents page */
.doc-missing-tag {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--red);
}

/* ============================================================
   PHASE 3 COMPONENTS
   ============================================================ */

/* Global search (topbar) */
.topbar-title { flex-shrink: 0; }
.topbar-actions { flex-shrink: 0; }
.topbar-search {
  flex: 1;
  max-width: 480px;
  margin: 0 var(--sp-6);
  position: relative;
}
.topbar-search .search-input { min-width: 0; width: 100%; }
.search-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  max-height: 420px;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: all var(--transition-fast);
  z-index: 40;
}
.search-dropdown.open { opacity: 1; transform: translateY(0); pointer-events: auto; }
.search-group-label {
  padding: var(--sp-3) var(--sp-4) var(--sp-1);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}
.search-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  font-size: 13.5px;
}
.search-result:hover, .search-result.highlighted { background: var(--bg-hover); }
.search-result-label { font-weight: 600; }
.search-result-sub { color: var(--text-faint); font-size: 12px; white-space: nowrap; }
.search-empty { padding: var(--sp-4); font-size: 13px; color: var(--text-faint); }

/* Team workload widget (Team page + dashboard for Owners/Managers) */
.workload-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border-soft);
}
.workload-row:last-child { border-bottom: none; }
.workload-row .workload-name { flex-shrink: 0; width: 160px; font-size: 13.5px; font-weight: 600; }
.workload-row .workload-track {
  flex: 1;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--bg-inset);
  overflow: hidden;
}
.workload-row .workload-fill { height: 100%; border-radius: var(--radius-pill); background: var(--green); }
.workload-row.overloaded .workload-fill { background: var(--red); }
.workload-row .workload-count { flex-shrink: 0; width: 90px; text-align: right; font-size: 12.5px; color: var(--text-muted); }

/* Role badges (Team + Settings) */
.role-badge {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
}
.role-badge-owner { background: var(--red-dim); color: var(--red); border-color: var(--red-border); }
.role-badge-manager { background: var(--blue-dim); color: var(--blue); border-color: var(--blue-border); }
.role-badge-exec { background: var(--bg-hover); color: var(--text-muted); border-color: var(--border); }

/* Template studio */
.template-page-layout {
  display: grid;
  grid-template-columns: 290px 1fr;
  gap: var(--sp-6);
  align-items: start;
}

/* This used to be a flex ROW (the default) with no wrap and no fixed
   widths on its children — with a real paragraph-length description
   next to a short name inside a narrow column, that squeezed BOTH
   into two awkward, word-by-word-wrapping side-by-side slivers
   instead of the plain stacked "heading, then caption" look the
   name/description styling was clearly meant to read as. Explicit
   flex-direction: column is the actual fix; align-items/justify-
   content below are for a COLUMN's axes now, not a row's. */
.template-list-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: var(--sp-4);
  border-radius: var(--radius-md);
  cursor: pointer;
  border: 1px solid transparent;
}
.template-list-item:hover { background: var(--bg-hover); }
.template-list-item.active { background: var(--red-dim); border-color: var(--red-border); }
.template-list-item .tli-name { font-weight: 600; font-size: 14px; }
/* One line, truncated with an ellipsis rather than wrapping — every
   row now reads as a uniform-height, scannable list instead of a
   stack of variously-sized paragraphs. Nothing is lost: the full
   description re-appears in the editor panel the moment a template
   is selected. */
.template-list-item .tli-desc {
  font-size: 12px;
  color: var(--text-faint);
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.template-list-section-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  padding: var(--sp-4) var(--sp-4) 6px;
}
.template-list-section-label:not(:first-child) {
  margin-top: var(--sp-2);
  border-top: 1px solid var(--border-soft);
}

.template-editor-layout {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-6);
  align-items: start;
}
.chip-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.placeholder-chip {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  background: var(--blue-dim);
  color: var(--blue);
  border: 1px solid var(--blue-border);
}
.placeholder-chip:hover { background: var(--blue-border); }
.template-editor-box {
  width: 100%;
  min-height: 280px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  font-size: 14px;
  line-height: 1.7;
  color: var(--text);
  white-space: pre-wrap;
}
/* Phase 25d-3 — same fix, same reason as .field input:focus above. */
.template-editor-box:focus { outline: none; border-color: var(--red); background: var(--bg-elevated); box-shadow: 0 0 0 3px var(--red-dim); }
.template-preview-box {
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: var(--sp-5);
  font-size: 14px;
  line-height: 1.7;
  white-space: pre-wrap;
  min-height: 280px;
}
.template-edited-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--amber-dim);
  color: var(--amber);
  border: 1px solid var(--amber-border);
}

/* Reports */
.report-card { margin-bottom: var(--sp-6); }
.report-card .report-figures {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-5);
  margin-bottom: var(--sp-4);
}
.report-figure { display: flex; flex-direction: column; gap: 4px; }
.report-figure .rf-label { font-size: 12px; color: var(--text-faint); }
.report-figure .rf-value { font-family: var(--font-display); font-size: 24px; font-weight: 700; }
/* One headline number leads each card (Phase 9c, one-glance rule) —
   bigger than the ordinary figures it sits above, so the eye lands on
   it first before any "Show details" is ever opened. */
.report-figure-lead { display: inline-flex; margin-bottom: 4px; }
.report-figure-lead .rf-label { font-size: 13px; }
.report-figure-lead .rf-value { font-size: 34px; }
.report-breakdown-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: 13px;
}
.report-breakdown-row:last-child { border-bottom: none; }
.report-breakdown-row .rb-bar-wrap { flex: 1; margin: 0 var(--sp-4); height: 6px; background: var(--bg-inset); border-radius: var(--radius-pill); overflow: hidden; }
.report-breakdown-row .rb-bar { height: 100%; background: var(--red); border-radius: var(--radius-pill); }

/* Onboarding checklist card */
.onboarding-card {
  margin-bottom: var(--sp-7);
}
/* The close button used to be position:absolute, floating over the
   whole card — which put it at the mercy of whatever this card's own
   scrollbar happened to be doing (this card can genuinely scroll; see
   .onboarding-steps below), and a real scrollbar sitting right where
   the button was drawn made it effectively unreachable. Making it a
   normal flex child of a real header row instead removes the problem
   at the root: it's simply never inside anything that scrolls, so
   there's no track for it to ever end up behind, regardless of how
   long the step list below ever gets. */
.onboarding-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
}
.onboarding-close {
  flex-shrink: 0;
  display: flex;
  background: none;
  border: none;
  color: var(--text-faint);
  cursor: pointer;
  padding: var(--sp-1);
  margin: calc(var(--sp-1) * -1);
}
/* This icon SVG has no width/height of its own (see REDLINE_ICONS.close,
   chrome.js) and never had a sizing rule here either — an unstyled inline
   SVG's browser-default size is roughly 300x150px, not "however big the
   button around it looks." Combined with flex-shrink: 0 above, that
   oversized, un-shrinkable button was being pushed out past the visible
   edge of the card instead of ever showing as a small X — the actual
   root cause, not just the scrollbar this was mistaken for. Matches
   .icon-btn svg's own 18px elsewhere in this file. */
.onboarding-close svg {
  width: 18px;
  height: 18px;
}
.onboarding-steps {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin-top: var(--sp-4);
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.onboarding-step {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  font-size: 14px;
}
.onboarding-step .ob-tick {
  width: 20px; height: 20px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  color: transparent;
}
.onboarding-step.done .ob-tick { background: var(--green); border-color: var(--green); color: #06301a; }
.onboarding-step.done .ob-tick svg { width: 12px; height: 12px; }
.onboarding-step.done .ob-label { color: var(--text-faint); text-decoration: line-through; }
.onboarding-step a.ob-label { color: var(--text); font-weight: 500; }
.onboarding-step a.ob-label:hover { color: var(--red); }

/* Empty states get an action button, never just text */
.empty-state .btn { margin-top: var(--sp-3); }

/* Outcome close-out (client detail) */
.outcome-form { display: flex; flex-direction: column; gap: var(--sp-4); }
.outcome-choice-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.outcome-choice {
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--bg-inset);
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 600;
}
.outcome-choice.selected.outcome-renewed { background: var(--green-dim); border-color: var(--green-border); color: var(--green); }
.outcome-choice.selected.outcome-lost,
.outcome-choice.selected.outcome-lapsed,
.outcome-choice.selected.outcome-not_renewing { background: var(--red-dim); border-color: var(--red-border); color: var(--red); }

.outcome-banner {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border-radius: var(--radius-md);
  margin-bottom: var(--sp-6);
  font-size: 13.5px;
}
.outcome-banner.renewed { background: var(--green-dim); border: 1px solid var(--green-border); color: var(--green); }
.outcome-banner.lost, .outcome-banner.lapsed, .outcome-banner.not_renewing { background: var(--red-dim); border: 1px solid var(--red-border); color: var(--red); }

.confirmed-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--green);
}
.changed-field-note {
  font-size: 11.5px;
  color: var(--amber);
  margin-top: 4px;
}

/* ============================================================
   CLIENT-FACING confirm.html — deliberately its own visual world.
   Light by default (clients are the general public, not brokers who
   chose dark mode), branded with the brokerage's own name rather
   than a heavy Redline identity, mobile-first because this link
   gets opened on a phone.
   ============================================================ */

.confirm-page {
  min-height: 100vh;
  background: #f5f5f3;
  color: #1a1c1f;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--sp-6) var(--sp-4) var(--sp-9);
}
:root[data-theme="dark"] .confirm-page,
.confirm-page[data-client-theme="dark"] {
  background: #0f1114;
  color: #f4f5f6;
}
.confirm-wrap { width: 100%; max-width: 560px; }
.confirm-brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-7);
  padding-top: var(--sp-4);
}
.confirm-brand .cb-badge {
  width: 44px; height: 44px;
  border-radius: var(--radius-md);
  background: var(--red);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 16px;
  flex-shrink: 0;
}
.confirm-brand .cb-name { font-family: var(--font-display); font-weight: 700; font-size: 18px; }
.confirm-intro { margin-bottom: var(--sp-7); }
.confirm-intro h1 { font-size: 22px; margin-bottom: var(--sp-2); }
.confirm-intro p { font-size: 14px; color: #55595f; line-height: 1.6; }
:root[data-theme="dark"] .confirm-intro p, .confirm-page[data-client-theme="dark"] .confirm-intro p { color: #9aa1ac; }

.confirm-section {
  background: #ffffff;
  border: 1px solid #e2e2df;
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  margin-bottom: var(--sp-4);
}
:root[data-theme="dark"] .confirm-section, .confirm-page[data-client-theme="dark"] .confirm-section {
  background: #15181d;
  border-color: #262b33;
}
.confirm-section .cs-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-3); }
.confirm-section .cs-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: #8a8f96; font-weight: 700; }
.confirm-section .cs-value { font-size: 17px; font-weight: 600; font-family: var(--font-display); }
.confirm-section .cs-value[contenteditable="true"] { border-bottom: 1px dashed var(--red); padding-bottom: 2px; outline: none; }
/* Phase 25d-3 — a real, genuine gap found live: this element had no
   `:focus` rule at all, so its always-on dashed underline looked
   IDENTICAL focused or not — a keyboard user editing their own
   details on this public, client-facing confirmation page (see this
   file's own top comment on why confirm.html is deliberately its own
   visual world) had no way to tell which field was actually active. */
.confirm-section .cs-value[contenteditable="true"]:focus {
  border-bottom-style: solid;
  box-shadow: 0 0 0 3px var(--red-dim);
}
.confirm-section-actions { display: flex; gap: var(--sp-2); }
.confirm-btn-correct, .confirm-btn-edit {
  padding: 8px 14px;
  border-radius: var(--radius-md);
  font-size: 12.5px;
  font-weight: 600;
  border: 1px solid #dcdcd8;
  background: #f5f5f3;
  color: #52565d;
}
:root[data-theme="dark"] .confirm-btn-correct, :root[data-theme="dark"] .confirm-btn-edit,
.confirm-page[data-client-theme="dark"] .confirm-btn-correct, .confirm-page[data-client-theme="dark"] .confirm-btn-edit {
  background: var(--bg-hover); border-color: var(--border); color: var(--text-muted);
}
.confirm-btn-correct.selected { background: var(--green); border-color: var(--green); color: #06301a; }
.confirm-btn-edit.selected { background: var(--blue); border-color: var(--blue); color: #fff; }

.confirm-submit-row { margin-top: var(--sp-7); }
.confirm-submit-row .btn { width: 100%; padding: 16px; font-size: 15px; }
.confirm-footnote { text-align: center; font-size: 12px; color: #8a8f96; margin-top: var(--sp-5); }

.confirm-success {
  text-align: center;
  padding: var(--sp-9) var(--sp-5);
}
.confirm-success .cs-icon {
  width: 64px; height: 64px;
  border-radius: 50%;
  background: var(--green-dim);
  color: var(--green);
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto var(--sp-5);
}
.confirm-success .cs-icon svg { width: 30px; height: 30px; }
.confirm-success h1 { font-size: 20px; margin-bottom: var(--sp-2); }
.confirm-success p { color: #55595f; font-size: 14px; }
:root[data-theme="dark"] .confirm-success p, .confirm-page[data-client-theme="dark"] .confirm-success p { color: #9aa1ac; }

/* ---------- 7. RESPONSIVE ---------- */

@media (max-width: 1180px) {
  .stat-grid, .stat-grid-3 { grid-template-columns: repeat(2, 1fr); }
  .fact-grid { grid-template-columns: repeat(2, 1fr); }
  .contact-grid { grid-template-columns: repeat(2, 1fr); }
  .widget-grid { grid-template-columns: repeat(2, 1fr); }
  .kanban-board { grid-template-columns: repeat(2, 1fr); }
  .topbar-search { max-width: 280px; margin: 0 var(--sp-4); }
  .template-editor-layout { grid-template-columns: minmax(0, 1fr); }
  .report-card .report-figures { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 900px) {
  /* minmax(0, 1fr), not just 1fr — a bare 1fr track's minimum size is
     its content's min-content width, not zero, so a single wide word
     or a run of chip buttons can still force the whole grid wider
     than the viewport even after it's "gone single column". */
  .detail-two-col { grid-template-columns: minmax(0, 1fr) !important; }
  .template-page-layout { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 960px) {
  .sidebar { width: var(--sidebar-w-collapsed); padding: var(--sp-4) var(--sp-2); }
  .sidebar-logo .wordmark, .sidebar-link span, .sidebar-footer .who { display: none; }
  .sidebar-link { justify-content: center; }
  .sidebar-link.active::before { display: none; }
  .runway-row { grid-template-columns: 70px 1fr auto; }
  .runway-row .runway-progress, .runway-row .status-pill { display: none; }
  .page { padding: var(--sp-5); }
  .ai-panel { width: calc(100vw - 32px); right: 16px; }
  .topbar-title { display: none; }
  .topbar-search { margin: 0 var(--sp-4) 0 0; max-width: none; }
  .workload-row .workload-name { width: 110px; }
}

@media (max-width: 640px) {
  .stat-grid, .stat-grid-3, .stat-grid-2 { grid-template-columns: 1fr; }
  .fact-grid { grid-template-columns: 1fr; }
  .widget-grid { grid-template-columns: 1fr; }
  .kanban-board { grid-template-columns: 1fr; }
  .claim-row { grid-template-columns: 1fr; row-gap: var(--sp-2); }
  .notif-dropdown, .help-dropdown { width: calc(100vw - 32px); right: -8px; }
  .topbar { padding: 0 var(--sp-4); }

  /* Below phone width there isn't room for badge + name + button on
     one line, so the action button drops to its own row instead of
     getting clipped. */
  .runway-row {
    grid-template-columns: 70px 1fr;
    row-gap: var(--sp-3);
  }
  .runway-row .btn { grid-column: 1 / -1; }

  /* Data tables (Clients, Compliance) have more columns than a phone
     screen can show at a readable size. Rather than clip columns
     off invisibly, let the table scroll horizontally within its
     card so every column stays reachable. */
  .table-wrap { overflow-x: auto; }
  .table-wrap table { min-width: 640px; }

  /* The 12-month revenue chart has more columns than a phone screen
     can fit at a readable width — same fix as the data tables above:
     scroll horizontally inside the card instead of forcing the
     whole page wider. */
  .bar-chart { overflow-x: auto; padding-bottom: var(--sp-2); }
  .bar-chart-col { min-width: 52px; flex: 0 0 auto; }

  /* The search box no longer forces the "Clients" header row wider
     than the screen — it wraps to its own line instead. */
  .section-head { flex-wrap: wrap; row-gap: var(--sp-3); }
  .search-input { min-width: 0; width: 100%; }

  /* Not enough width for a global search box alongside the icon
     cluster on a phone — the search bar drops, title stays hidden
     (icon-only sidebar already identifies the page). */
  .topbar-search { display: none; }

  .report-card .report-figures { grid-template-columns: 1fr; }
  .outcome-choice-row { flex-direction: column; align-items: stretch; }

  /* Quick-add rows (Tasks, Documents) pack an input + several selects
     into one flex line — selects don't shrink below their option
     text's width, so on a phone they wrap to their own full-width
     rows instead of forcing the page wider. */
  .quick-add-row { flex-wrap: wrap; }
  .quick-add-row input, .quick-add-row select, .quick-add-row button { flex: 1 1 100%; min-width: 0; }

  .hub-tabs { flex-wrap: wrap; }
  .register-form-grid { grid-template-columns: 1fr; }
  .doc-grid { grid-template-columns: 1fr; }

  /* A 7-column grid has nowhere to go on a phone — the month grid
     becomes a single scrollable column of days instead (still a real
     calendar, just stacked), and the week grid does the same. */
  .cal-month-grid, .cal-week-grid { grid-template-columns: 1fr; }
  .cal-month-grid .cal-weekday-head { display: none; }
  .cal-day-cell { min-height: 0; flex-direction: row; align-items: center; flex-wrap: wrap; }
  .cal-day-events { flex-direction: row; flex-wrap: wrap; }
}

/* ============================================================
   PHASE 6a — THE COMPLIANCE HUB
   ============================================================ */

/* ---------- the four sub-section tabs ----------
   Same visual language as .filter-chip (runway filters) — a familiar
   pattern reused rather than a brand new tab component. */
.hub-tabs {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-6);
  border-bottom: 1px solid var(--border-soft);
  padding-bottom: var(--sp-4);
}
.hub-tab {
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-soft);
  background: var(--bg-card);
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 600;
  transition: all var(--transition-fast);
}
.hub-tab:hover { border-color: var(--border); color: var(--text); }
.hub-tab.active { background: var(--red); border-color: var(--red); color: #fff; }
.hub-panel { display: none; }
.hub-panel.active { display: block; }

/* ---------- document library ---------- */
.doc-upload-form {
  display: grid;
  grid-template-columns: 2fr 1.3fr 1fr 1.4fr auto;
  gap: var(--sp-3);
  align-items: end;
  margin-bottom: var(--sp-6);
}
.doc-upload-form label { display: block; font-size: 11.5px; color: var(--text-faint); margin-bottom: 4px; }

.ask-docs-row { display: flex; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.ask-docs-row input { flex: 1; }

.doc-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-4);
}
.doc-card {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-4);
  cursor: pointer;
  transition: all var(--transition-fast);
}
.doc-card:hover { border-color: var(--border); background: var(--bg-hover); }
.doc-card .doc-title { font-weight: 600; font-size: 14px; margin-bottom: 6px; }
.doc-card .doc-meta { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.doc-card .doc-snippet { font-size: 12.5px; color: var(--text-muted); line-height: 1.5; }
.doc-card .doc-snippet mark {
  background: var(--red-dim);
  color: var(--red);
  border-radius: 3px;
  padding: 0 2px;
  font-weight: 600;
}
.doc-not-searchable { font-size: 11.5px; color: var(--text-faint); font-style: italic; margin-top: 4px; }

.doc-detail-text {
  white-space: pre-wrap;
  font-size: 13px;
  line-height: 1.7;
  color: var(--text-muted);
  background: var(--bg-inset);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  max-height: 420px;
  overflow-y: auto;
  margin-top: var(--sp-4);
}

/* ---------- registers ---------- */
.register-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--sp-3);
  align-items: end;
  margin-bottom: var(--sp-6);
}
.register-form-grid label { display: block; font-size: 11.5px; color: var(--text-faint); margin-bottom: 4px; }
.register-form-grid .btn { grid-column: -2 / -1; }

.overdue-flag { color: var(--red); font-weight: 600; font-size: 11.5px; }

/* ---------- CPD progress ---------- */
.cpd-row {
  display: grid;
  grid-template-columns: 160px 1fr 140px;
  align-items: center;
  gap: var(--sp-3);
  padding: 10px 0;
  border-bottom: 1px solid var(--border-soft);
}
.cpd-row:last-child { border-bottom: none; }
.cpd-name { font-weight: 600; font-size: 13.5px; }
.cpd-track { height: 8px; border-radius: var(--radius-pill); background: var(--bg-inset); overflow: hidden; }
.cpd-fill { height: 100%; border-radius: var(--radius-pill); background: var(--green); }
.cpd-row.behind .cpd-fill { background: var(--amber); }
.cpd-count { font-size: 12px; color: var(--text-muted); text-align: right; }

/* ---------- file checks ---------- */
.checklist-item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: 12px 0;
  border-bottom: 1px solid var(--border-soft);
}
.checklist-item:last-child { border-bottom: none; }
/* Phase 25d-2 — was 18x18 (below the WCAG 2.2 24x24 minimum target size). */
.checklist-item input[type="checkbox"] { width: 24px; height: 24px; margin-top: 0; accent-color: var(--red); }
.checklist-item .checklist-label { font-weight: 600; font-size: 13.5px; }
.checklist-item .checklist-note { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
.checklist-item .checklist-source { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-faint); }

.score-ring {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
}
.score-ring.good { color: var(--green); }
.score-ring.warn { color: var(--amber); }
.score-ring.bad { color: var(--red); }

/* ============================================================
   PHASE 6b — MID-TERM ADJUSTMENTS + THE COMMUNICATIONS LOG
   ============================================================ */

/* ---------- "log a call" — has to feel instant, not like a form ---------- */
.comm-quick-log {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.comm-quick-log select { flex: 0 0 auto; width: auto; }
.comm-quick-log input[type="text"] { flex: 1; }

.comm-followup-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-faint);
  cursor: pointer;
  margin-bottom: var(--sp-2);
}
.comm-followup-toggle input { width: auto; margin: 0; }
.comm-followup-input { margin-bottom: var(--sp-3); }

/* ---------- the quick "Log call" popup from global search ---------- */
.search-log-btn {
  flex-shrink: 0;
  background: none;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  color: var(--text-faint);
  padding: 4px 8px;
  font-size: 11px;
  font-weight: 600;
  margin-left: var(--sp-2);
}
.search-log-btn:hover { border-color: var(--red-border); color: var(--red); }

.search-log-popup {
  position: absolute;
  z-index: 50;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-3);
  width: 280px;
  display: flex;
  gap: 8px;
}
.search-log-popup input {
  flex: 1;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  font-size: 13px;
  color: var(--text);
}
.search-log-popup button {
  background: var(--red);
  color: #fff;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0 12px;
  font-size: 12.5px;
  font-weight: 600;
  flex-shrink: 0;
}

/* ============================================================
   PHASE 6c — COMMISSION RECONCILIATION + THE INSURER DIRECTORY
   ============================================================ */

/* ---------- the insurer/month picker above the reconciliation ---------- */
.recon-picker-row {
  display: flex;
  gap: var(--sp-4);
}
.recon-picker-row > div { flex: 1; max-width: 280px; }
.recon-picker-row label { display: block; font-size: 11.5px; color: var(--text-faint); margin-bottom: 4px; }

/* ---------- the column-mapping preview table's own per-column select ---------- */
.mapping-select {
  width: 100%;
  font-size: 11.5px;
  padding: 4px 6px;
}

/* ---------- the manual quick-entry grid for a PDF statement ---------- */
.manual-grid-row {
  display: grid;
  grid-template-columns: 1.2fr 1.2fr 1fr auto;
  gap: var(--sp-3);
  align-items: center;
  padding: 6px 0;
}
.manual-grid-header {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-soft);
}

/* ============================================================
   PHASE 6d — THE CALENDAR, MY DAY, AND FINISHING POLISH
   ============================================================ */

/* ---------- calendar toolbar ---------- */
.cal-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-4);
  flex-wrap: wrap;
  gap: var(--sp-3);
}
.cal-nav { display: flex; align-items: center; gap: var(--sp-2); }
.cal-range-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 16px;
  margin-left: var(--sp-2);
}
.cal-view-toggle { display: flex; gap: var(--sp-2); }

/* ---------- legend / type filter ---------- */
.cal-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: var(--sp-5);
}
.cal-legend-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-soft);
  background: var(--bg-card);
  color: var(--text-faint);
  font-size: 12px;
  font-weight: 600;
  transition: all var(--transition-fast);
}
.cal-legend-chip.active { color: var(--text); border-color: var(--border); }
.cal-legend-chip:not(.active) { opacity: 0.5; }

.cal-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.cal-dot-blue, .cal-chip-blue { background: var(--blue); }
.cal-dot-violet, .cal-chip-violet { background: var(--violet); }
.cal-dot-teal, .cal-chip-teal { background: var(--teal); }
.cal-dot-orange, .cal-chip-orange { background: var(--orange); }
.cal-dot-rose, .cal-chip-rose { background: var(--rose); }
.cal-dot-muted, .cal-chip-muted { background: var(--text-faint); }
/* Phase 20c — read-only Google Calendar context, deliberately its own
   colour (unused elsewhere in this KIND palette — see config.js's own
   comment on REDLINE_CALENDAR_TYPES.google). */
.cal-dot-green, .cal-chip-green { background: var(--green); }

/* ---------- month grid ---------- */
.cal-month-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
  background: var(--border-soft);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.cal-weekday-head {
  background: var(--bg-inset);
  padding: 8px 0;
  text-align: center;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  font-weight: 600;
}
.cal-day-cell {
  background: var(--bg-card);
  min-height: 108px;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cal-day-cell.other-month { background: var(--bg-inset); }
.cal-day-cell.other-month .cal-day-num { color: var(--text-faint); }
.cal-day-num {
  align-self: flex-start;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 600;
  font-family: var(--font-display);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
}
.cal-day-num:hover { background: var(--bg-hover); color: var(--text); }
.cal-day-cell.today .cal-day-num { background: var(--red); color: #fff; }
.cal-day-events { display: flex; flex-direction: column; gap: 3px; }

/* Event "chips" — same swatch-plus-label idea across the month grid,
   week grid, and the day popover, just sized down for a busy cell. */
.cal-chip {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-left: 3px solid var(--text-faint);
  border-radius: 4px;
  padding: 2px 6px;
  font-size: 11px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-chip:hover { border-color: var(--border); background: var(--bg-hover); }
.cal-chip.done { opacity: 0.5; text-decoration: line-through; }
.cal-chip-blue { border-left-color: var(--blue); }
.cal-chip-violet { border-left-color: var(--violet); }
.cal-chip-teal { border-left-color: var(--teal); }
.cal-chip-orange { border-left-color: var(--orange); }
.cal-chip-rose { border-left-color: var(--rose); }
.cal-chip-muted { border-left-color: var(--text-faint); }

.cal-more-btn {
  background: none;
  border: none;
  color: var(--text-faint);
  font-size: 11px;
  font-weight: 600;
  text-align: left;
  padding: 2px 6px;
}
.cal-more-btn:hover { color: var(--text); }

.cal-day-popover {
  position: absolute;
  z-index: 60;
  flex-direction: column;
  gap: 4px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-3);
  width: 240px;
}

/* ---------- week grid ---------- */
.cal-week-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--sp-3);
}
.cal-week-col {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-3);
  min-height: 300px;
}
.cal-week-col.today { border-color: var(--red-border); }
.cal-week-day-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  background: none;
  border: none;
  width: 100%;
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border-soft);
  color: var(--text-faint);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
}
.cal-week-day-num {
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--text);
  text-transform: none;
  letter-spacing: normal;
}
.cal-week-col.today .cal-week-day-num { color: var(--red); }
.cal-week-events { display: flex; flex-direction: column; gap: 4px; }

/* ---------- My Day (dashboard toggle) ---------- */
.dash-view-toggle {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-6);
}
.myday-item {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border-soft);
}
.myday-item:last-child { border-bottom: none; }
.myday-tier-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.myday-tier-1 .myday-tier-dot { background: var(--red); }
.myday-tier-2 .myday-tier-dot { background: var(--red); }
.myday-tier-3 .myday-tier-dot { background: var(--amber); }
.myday-tier-4 .myday-tier-dot { background: var(--blue); }
.myday-tier-5 .myday-tier-dot { background: var(--text-faint); }
.myday-body { flex: 1; min-width: 0; }
.myday-title { font-weight: 600; font-size: 14px; }
.myday-subtitle { font-size: 12.5px; color: var(--text-faint); margin-top: 2px; }

/* ---------- keyboard shortcuts overlay ---------- */
.shortcuts-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(0, 0, 0, 0.55);
  display: none;
  align-items: center;
  justify-content: center;
}
.shortcuts-overlay.open { display: flex; }
.shortcuts-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-6);
  width: 360px;
  max-width: 90vw;
}
.shortcuts-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: 13.5px;
}
.shortcuts-row:last-child { border-bottom: none; }
.shortcuts-keys kbd {
  display: inline-block;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 2px 7px;
  font-family: var(--font-display);
  font-size: 11.5px;
  margin-left: 4px;
}

/* ---------- field-test note button (Phase 13b) ----------
   A RESEARCH tool, not product chrome — deliberately styled to look
   like one (a dashed border, not a solid filled button) rather than
   blending in with the real UI. Bottom-LEFT, so it never collides
   with the AI assistant launcher (.ai-launcher, bottom-right). Only
   ever in the DOM at all when field-test mode is on — see
   chrome.js's renderFieldNoteButton(). */
.field-note-btn {
  position: fixed;
  bottom: var(--sp-6);
  left: var(--sp-6);
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 16px;
  border-radius: 999px;
  border: 1px dashed var(--amber);
  background: var(--bg-elevated);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-lg);
  transition: transform var(--transition-base);
}
.field-note-btn:hover { transform: scale(1.03); }

/* ---------- first-run tour (Phase 13b) ----------
   A "spotlight" effect done with plain CSS, no clip-path/mask: a huge
   (9999px) box-shadow spread on .tour-highlight dims the WHOLE page
   except a soft-edged rectangle right around whatever it's
   positioned over — the highlighted element itself sits underneath,
   genuinely untouched, still fully interactive if a step ever needed
   that (this phase's four don't, but the mechanism doesn't rule it
   out). .tour-overlay itself has NO backdrop of its own and ignores
   clicks (pointer-events: none) — only the highlight ring and the
   card need to intercept anything. */
.tour-overlay {
  position: fixed;
  inset: 0;
  z-index: 250;
  pointer-events: none;
}
.tour-highlight {
  position: fixed;
  border-radius: var(--radius-md);
  box-shadow: 0 0 0 4px var(--red), 0 0 0 9999px rgba(0, 0, 0, 0.6);
  transition: top 0.2s ease, left 0.2s ease, width 0.2s ease, height 0.2s ease;
}
.tour-card {
  position: fixed;
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-5);
  width: 300px;
  max-width: calc(100vw - 32px);
  pointer-events: auto;
}

/* ---------- certificate issue wizard (Phase 8a) ----------
   A bigger, SCROLLABLE sibling of .shortcuts-overlay above (that one
   is deliberately small and fixed-height — a cheat sheet, never a
   real form) — same fixed, centred, dimmed-backdrop shape, just tall
   enough to hold a real multi-section form and free to grow past the
   viewport height without breaking. */
.cert-wizard-overlay {
  position: fixed;
  inset: 0;
  z-index: 210;
  background: rgba(0, 0, 0, 0.55);
  display: none;
  align-items: flex-start;
  justify-content: center;
  overflow-y: auto;
  padding: var(--sp-7) var(--sp-4);
}
.cert-wizard-overlay.open { display: flex; }
.cert-wizard-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-6);
  width: 640px;
  max-width: 100%;
}
.cert-wizard-section { margin-bottom: var(--sp-5); }
.cert-wizard-section label {
  display: block;
  font-size: 11.5px;
  color: var(--text-faint);
  margin-bottom: 4px;
}
.cert-policy-block {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-3);
}
.cert-policy-block-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-2);
}
.cert-limit-row {
  display: grid;
  grid-template-columns: 1fr 140px;
  gap: var(--sp-2);
  margin-bottom: 6px;
}
.cert-limit-row input { font-size: 13px; }
.cert-holder-row { display: flex; gap: var(--sp-3); align-items: flex-end; margin-bottom: var(--sp-3); }
.cert-holder-row > div { flex: 1; }
.cert-routine-note {
  font-size: 12.5px;
  color: var(--text-faint);
  background: var(--bg-inset);
  border-radius: var(--radius-md);
  padding: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.cert-routine-note.non-routine { color: var(--amber); }

/* ---------- certificate queue rows (morning queue + expiry radar) ---------- */
.cert-queue-item { align-items: flex-start; }
.cert-queue-item .list-item-actions { display: flex; gap: var(--sp-2); align-items: center; }

/* ---------- client data sheet + schedule tables (Phase 8b) ----------
   Native <details>/<summary> for the collapsible groups — genuinely
   accessible, keyboard-operable, and needs zero JavaScript to expand/
   collapse, matching this app's "vanilla JS only where JS actually
   earns its keep" instinct.

   .disclosure-group (Phase 9c) is the exact same component, generalised
   to a plain, sitewide name — the progressive-disclosure audit reuses
   it anywhere a page needs to hide detail behind a "show more," not
   just the client data sheet it was first built for (commissions,
   reports, compliance). Same rules, two selectors, so there's still
   only one place to change how a disclosure group looks. */
.data-sheet-group, .disclosure-group {
  border-bottom: 1px solid var(--border-soft);
  padding: var(--sp-3) 0;
}
.data-sheet-group:last-child, .disclosure-group:last-child { border-bottom: none; }
.data-sheet-group summary, .disclosure-group summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 13.5px;
  padding: 6px 0;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
}
.data-sheet-group summary::-webkit-details-marker, .disclosure-group summary::-webkit-details-marker { display: none; }
.data-sheet-group summary::before, .disclosure-group summary::before {
  content: "";
  width: 0; height: 0;
  border-left: 5px solid var(--text-faint);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform 0.15s ease;
}
.data-sheet-group[open] summary::before, .disclosure-group[open] summary::before { transform: rotate(90deg); }
.data-sheet-group .fact-grid { margin-top: var(--sp-3); }
.disclosure-group > *:not(summary) { margin-top: var(--sp-4); }

.schedule-table {
  width: 100%;
  margin-top: var(--sp-3);
  font-size: 13px;
  border-collapse: collapse;
}
.schedule-table th {
  text-align: left;
  font-size: 11px;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 4px 8px 4px 0;
  font-weight: 500;
}
.schedule-table td { padding: 4px 8px 4px 0; vertical-align: middle; }
.schedule-cell {
  display: block;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  min-height: 20px;
}
/* Phase 25d-3 — same fix, same reason as .field input:focus above. */
.schedule-cell:focus { background: var(--bg-inset); outline: none; box-shadow: 0 0 0 3px var(--red-dim); }
.schedule-add-row input {
  font-size: 12.5px;
  padding: 5px 7px;
  width: 100%;
}
.schedule-delete-btn { color: var(--text-faint); font-size: 15px; line-height: 1; padding: 2px 8px; }
.schedule-delete-btn:hover { color: var(--red); }

/* ---------- document workbench (Phase 8b) ---------- */
.workbench-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-soft);
}
.workbench-toolbar select {
  font-size: 12.5px;
  padding: 6px 10px;
}
.workbench-breadcrumb {
  padding: var(--sp-3) var(--sp-5);
  font-size: 12.5px;
  color: var(--text-faint);
  border-bottom: 1px solid var(--border-soft);
}
.workbench-breadcrumb a { color: var(--text-faint); }
.workbench-breadcrumb a:hover { color: var(--text); }
#workbench-list { padding: 0 var(--sp-5); }
#workbench-list .list-item:last-child { border-bottom: none; }

.merge-drag-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  margin-bottom: 6px;
  background: var(--bg-card);
  cursor: grab;
  font-size: 13px;
}
.merge-drag-item svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-faint); }
.merge-drag-item.dragging { opacity: 0.4; }

.bulk-import-dropzone {
  border: 2px dashed var(--border);
  border-radius: var(--radius-lg);
  padding: var(--sp-6);
  text-align: center;
  font-size: 13px;
  color: var(--text-faint);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
  justify-content: center;
}
.bulk-import-dropzone.dragover {
  border-color: var(--red);
  background: var(--red-dim);
}

/* ---------- Setup Centre (Phase 20a's wizard, restructured Phase 24a) ----------
   A vertical nav list, one section open at a time on the right — same
   290px/1fr two-pane shape .template-page-layout already established
   for the Template Studio, same list-item interaction language as
   .template-list-item, just with a real completion tick per row
   instead of a plain bullet. Collapses to a horizontal, wrapping strip
   on mobile (.template-page-layout's own 900px breakpoint already
   stacks the two panes; this just keeps the nav itself usable at that
   width rather than nine full-height rows stacked above the content). */
.gs-steps {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.gs-step {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: 13px;
  color: var(--text-faint);
  width: 100%;
  text-align: left;
  padding: var(--sp-3);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  background: none;
  cursor: pointer;
}
.gs-step:hover { background: var(--bg-hover); }
.gs-step .gs-step-num {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  flex-shrink: 0;
}
.gs-step.active { color: var(--text); font-weight: 600; background: var(--red-dim); border-color: var(--red-border); }
.gs-step.active .gs-step-num { border-color: var(--red); color: var(--red); }
.gs-step.done .gs-step-num { border-color: var(--green); color: var(--green); background: var(--green-dim); }

@media (max-width: 900px) {
  .gs-steps { flex-direction: row; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-5); }
  .gs-step { width: auto; padding: var(--sp-2) var(--sp-3); }
}

/* A review-grid cell still missing a required value — the same red
   vocabulary as every other "needs attention" signal in this app
   (badge-red, the runway's red zone), never a silent drop. */
.gs-cell-missing {
  border-color: var(--red-border) !important;
  background: var(--red-dim) !important;
}
.gs-review-table input {
  width: 100%;
  min-width: 90px;
  font-size: 12.5px;
  padding: 4px 6px;
}

/* ---------- shared modal + toast (Phase 12a) ----------
   .modal-overlay/.modal-card is a small, GENERIC sibling of
   .shortcuts-overlay above — same fixed, centred, dimmed-backdrop
   shape, sized for a short message or a couple of fields rather than
   a real multi-section form (that's .cert-wizard-overlay's job). Built
   by utils.js's showModal(), not hand-written per page — see that
   function's own top comment for why. */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 220;
  background: rgba(0, 0, 0, 0.55);
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
}
.modal-overlay.open { display: flex; }
.modal-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-6);
  width: 420px;
  max-width: 100%;
}
.modal-close-btn {
  background: none;
  border: none;
  color: var(--text-faint);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
}
.modal-close-btn:hover { color: var(--text); background: var(--bg-inset); }

/* A readonly link field + "Copy" button — the certificate portal
   link's own modal body (client.js), but generic enough for any
   future "here's a link, copy it" case. */
.modal-link-row {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.modal-link-row input {
  flex: 1;
  font-family: var(--font-mono, monospace);
  font-size: 12.5px;
}

/* ---------- one-click copy buttons (Phase 13a) ----------
   Built by utils.js's copyFieldHtml() — see that function's own top
   comment. .copy-field is the wrapper around a value plus its button;
   the hover-reveal is scoped to devices that genuinely HAVE hover (a
   real mouse) so it isn't invisible on a touchscreen, which never
   fires :hover reliably — there, the button just stays visible. */
.copy-field {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.copy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Phase 25d-2 — was 20x20 (below the WCAG 2.2 24x24 minimum target
     size; this <button> has no padding of its own, so its box IS the
     hit target). */
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}
.copy-btn svg { width: 13px; height: 13px; }
.copy-btn:hover { color: var(--red); background: var(--bg-inset); }
@media (hover: hover) and (pointer: fine) {
  .copy-btn {
    opacity: 0;
    transition: opacity 0.12s ease;
  }
  .copy-field:hover .copy-btn,
  .copy-btn:focus-visible {
    opacity: 1;
  }
}
/* A copy button that IS the primary action where it lives (the
   certificate portal link modal), not a hover-reveal accent next to
   other text — always visible, on every device. */
.copy-btn.always-visible { opacity: 1; }

/* ---------- toast (Phase 12a) ----------
   A small, self-dismissing confirmation — built by utils.js's own
   showToast(), never hand-placed in a page's own HTML (there's
   nothing to toggle: JS creates and removes the element itself). */
.toast {
  position: fixed;
  bottom: var(--sp-6);
  left: 50%;
  transform: translateX(-50%) translateY(12px);
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft);
  box-shadow: var(--shadow-lg);
  border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-5);
  font-size: 13px;
  color: var(--text);
  z-index: 300;
  max-width: min(90vw, 460px);
  opacity: 0;
  transition: opacity 0.25s ease, transform 0.25s ease;
  pointer-events: none;
}
.toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Phase 25d-5 — an undo toast (utils.js's own showToast() with
   actionLabel/onAction) needs a real, clickable button inside — the
   plain .toast above is deliberately pointer-events:none (fire-and-
   forget, never meant to be interacted with), so this variant turns
   pointer events back on for itself specifically rather than for
   every toast. */
.toast-with-action {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}
.toast-action-btn {
  flex-shrink: 0;
  background: none;
  border: none;
  color: var(--blue);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-sm);
}
.toast-action-btn:hover { background: var(--bg-hover); }

/* ---------- 17b. DASHBOARD GRID ENGINE ----------
   Phase 17b's 12-column, fixed-row grid replaces Phase 15a's stacked
   .stat-grid/.widget-grid layout on the Command Centre. Every widget
   instance is positioned explicitly by gridColumn/gridRow (see
   public/js/dashboard.js's renderGrid()), and its own width class
   (w-sm/w-md/w-lg, derived from colSpan) drives what it chooses to
   render inside. */

/* The shared 12-column grid container. 40px per cell, 12px gap — chosen
   so the curated default's spans land on clean, readable heights for
   every widget's actual content (stat card + toolbar, medium summary,
   full runway) without content spilling into the next row. */
.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: 40px;
  gap: 12px;
}

/* Below tablet width the grid becomes a single column in the
   user's row-then-column order — spans ignored, order preserved.
   Customise mode on mobile allows reorder + remove only (no spans)
   with an honest note (see dashboard.js's renderCustomizeGridMobile). */
.dashboard-grid-mobile {
  grid-template-columns: 1fr;
  grid-auto-rows: auto;
}

/* Each widget instance's cell. The cell is the positioned grid item;
   the real card (stat-card, widget-card, or a large widget's own
   .card) sits inside it and should fill the cell vertically.
   flex-direction: column matters even when a widget has only ONE
   child (the common case) because a few large widgets — the runway,
   activity, my day — build their own markup as SEVERAL sibling
   elements straight into this cell (a section-head, then their own
   list) rather than one wrapping .card. Without an explicit column
   direction here, flex's default is row, and those widgets' sections
   silently laid out side by side instead of stacked — a real bug
   found by actually rendering the dashboard and looking at it, not
   by reading the code. */
.dashboard-widget-cell {
  display: flex;
  flex-direction: column;
  min-height: 0; /* allow the cell to shrink inside the grid track */
}

/* Make any card-style child fill its cell, but via flex-grow rather
   than a flat height:100% — several widgets (runway, activity, my
   day) put a fixed-height header ABOVE this card/list as a sibling,
   so the card only gets the space left over after that header, not
   the whole cell. flex:1 + min-height:0 + overflow-y:auto together
   mean: take the remaining room, and if the real content is still
   taller than that, scroll internally instead of spilling into the
   grid row below — the fix for the onboarding checklist, the team
   workload list, and any other widget whose real content (a longer
   client book, a bigger team) is bigger than its box ever assumed.
   Customise mode's own preview (.wcf-body, inside .widget-customize-
   frame) gets the exact same treatment — it's a genuinely separate
   piece of markup from .dashboard-widget-cell (see widgetFrameHtml()
   in dashboard.js), and without this rule ALSO covering it, a
   widget's preview while customising never got any of this fill/
   scroll/padding behaviour at all, silently falling back to each
   card's own base CSS regardless of its actual current span. */
.dashboard-widget-cell > a,
.dashboard-widget-cell > .card,
.widget-customize-frame .wcf-body > a,
.widget-customize-frame .wcf-body > .card {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
}
/* Override for the one card with its own internal close button
   (position:absolute, relative to THIS card) — see the comment next
   to .onboarding-steps in the "Onboarding checklist card" section.
   Same (0,2,0) specificity as the rule above; this only wins the tie
   because it's declared after it. .onboarding-steps itself carries
   the flex-grow/scroll instead, so the header stays anchored above
   whatever a real scrollbar does to the steps list underneath it. */
.dashboard-widget-cell > .onboarding-card,
.widget-customize-frame .wcf-body > .onboarding-card {
  overflow: visible;
}
.widget-customize-frame .wcf-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* The runway's own client list and the team workload's own row list
   aren't wrapped in a .card themselves (see above) — same "grow to
   fill what's left, then scroll" treatment, scoped so it never
   touches a .table-wrap or list used anywhere else in the app. */
.dashboard-widget-cell .table-wrap,
.dashboard-widget-cell .workload-rows,
.widget-customize-frame .table-wrap,
.widget-customize-frame .workload-rows {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* Size-class-driven layout tweaks. These classes are applied by
   gridSizeClass() based on the instance's current colSpan (w-sm ≤3,
   w-md 4-6, w-lg ≥7). Widgets use them inside their own render()
   to decide what to show; the few things that genuinely need a CSS
   rule (padding at narrow spans to avoid double-padding) live here.
   renderCustomizeGrid()/renderCustomizeGridMobile() (dashboard.js) put
   this same class on .wcf-body, so a widget's Customise-mode preview
   uses the SAME padding its real, live position on the dashboard
   would — before this, every widget previewed at the base (widest)
   padding regardless of its actual span, which is what made a small
   stat card's own label/value/sub read as cramped against its own
   border while customising: it was rendering with 20px of padding
   reserved for a wide card, inside a box only ever sized for a narrow
   one. */
.dashboard-widget-cell.w-sm .stat-card,
.dashboard-widget-cell.w-sm .widget-card,
.wcf-body.w-sm .stat-card,
.wcf-body.w-sm .widget-card {
  padding: var(--sp-4);
  border-radius: var(--radius-md);
}
.dashboard-widget-cell.w-md .stat-card,
.dashboard-widget-cell.w-md .widget-card,
.wcf-body.w-md .stat-card,
.wcf-body.w-md .widget-card {
  padding: var(--sp-5);
}
.dashboard-widget-cell.w-lg .stat-card,
.dashboard-widget-cell.w-lg .widget-card,
.wcf-body.w-lg .stat-card,
.wcf-body.w-lg .widget-card {
  padding: var(--sp-5) var(--sp-6);
}

/* When the runway isn't full-width, the compact shape it used to only
   get on phones now also applies to a resized desktop runway — same
   rule, triggered by a class driven by colSpan rather than only the
   viewport's width. */
.dashboard-widget-cell.runway-compact .runway-row {
  grid-template-columns: 90px 1fr auto;
}
.dashboard-widget-cell.runway-compact .runway-progress,
.dashboard-widget-cell.runway-compact .status-pill {
  display: none;
}

/* Mobile override: every widget spans the full single column,
   overriding the inline gridColumn the desktop layout set. */
@media (max-width: 960px) {
  .dashboard-grid .dashboard-widget-cell {
    grid-column: 1 / -1 !important;
    grid-row: auto !important;
  }
}

/* ---------- Customise mode overlay grid ----------
   The same 12-column grid the normal view uses, minus the widget
   content, so the user sees a faint cutting-mat structure while they
   drag/resize. The grid lines are the container's own background
   painted through the gaps. */
.dashboard-customize-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: 40px;
  gap: 12px;
  position: relative;
  background-color: var(--bg-inset);
  padding: 12px;
  border-radius: var(--radius-md);
  min-height: 300px;
}

/* ---------- Drag ghost ----------
   A hollow, snapped version of the widget's current frame while the
   user drags. Lives inside the same grid as the customize frames, so
   gridColumn/gridRow place it exactly. */
.dashboard-grid-ghost {
  border: 2px dashed var(--blue);
  background: var(--blue-dim);
  border-radius: var(--radius-lg);
  pointer-events: none;
  z-index: 10;
  opacity: 0.75;
  transition: none;
}

/* ---------- Customise frame selection ----------
   A selected widget shows a clear outline so it's obvious which one
   arrow keys will move (see wireFrameInteractions in dashboard.js). */
.widget-customize-frame.wcf-selected {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

/* ---------- Resize handles ----------
   Hit areas at the bottom, right, and corner of a frame. They're
   aria-hidden because they're a pointer affordance, not a real
   interactive element in the accessibility tree — keyboard users
   resize with Shift+arrows on the frame itself. */
.wcf-resize {
  position: absolute;
  background: transparent;
  z-index: 2;
}
.wcf-resize-r {
  right: 0; top: 0; bottom: 0;
  width: 12px;
  cursor: col-resize;
}
.wcf-resize-b {
  bottom: 0; left: 0; right: 0;
  height: 12px;
  cursor: row-resize;
}
.wcf-resize-corner {
  right: 0; bottom: 0;
  width: 16px; height: 16px;
  cursor: nwse-resize;
  background: linear-gradient(135deg, transparent 50%, var(--text-faint) 50%);
  border-radius: 0 0 var(--radius-sm) 0;
}

/* ---------- Stat card breakdown (Phase 17b) ----------
   Extra rows shown only when the widget is wide enough to earn them
   (w-md or wider, see registry.js's renderStatWidget). */
.stat-breakdown {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border-soft);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.stat-breakdown-row {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--text-muted);
}
.stat-breakdown-row span:last-child {
  font-weight: 600;
  color: var(--text);
}

/* ============================================================
   PHASE 17b VISUAL QA FIXES — per-widget responsive tweaks
   keyed on the size class (w-sm / w-md / w-lg) that renderGrid()
   applies to every .dashboard-widget-cell. These rules prevent
   clipping, truncation, and layout breakage at every span band
   (stat: 2-4 cols, medium: 3-6 cols, large: 6-12 cols) on both
   1280px and 1920px viewports.
   ============================================================ */

/* ---------- Stat cards (w-sm = 2-3 cols, w-md = 4 cols) ---------- */
.dashboard-widget-cell.w-sm .stat-value {
  font-size: clamp(26px, 5vw, 38px);
}
.dashboard-widget-cell .stat-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dashboard-widget-cell .stat-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- Medium widget cards (w-sm = 3 cols, w-md = 4-6 cols) ---------- */
.dashboard-widget-cell.w-sm .widget-value {
  font-size: clamp(18px, 3.5vw, 26px);
}
.dashboard-widget-cell .widget-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dashboard-widget-cell .widget-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dashboard-widget-cell .widget-sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- Runway: long client names in compact mode ---------- */
.dashboard-widget-cell.runway-compact .runway-client {
  min-width: 0;
  overflow: hidden;
}
.dashboard-widget-cell.runway-compact .runway-client .name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dashboard-widget-cell.runway-compact .runway-client .meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Full-width runway: also protect long names from overlapping columns */
.dashboard-widget-cell:not(.runway-compact) .runway-client {
  min-width: 0;
  overflow: hidden;
}
.dashboard-widget-cell:not(.runway-compact) .runway-client .name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dashboard-widget-cell:not(.runway-compact) .status-pill {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- Activity feed: clamp long text to 2 lines ---------- */
.dashboard-widget-cell .feed-item > div:first-child {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- Team workload: long names should ellipsis ---------- */
.dashboard-widget-cell .workload-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- My Day widget: long titles ellipsis ---------- */
.dashboard-widget-cell .myday-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- Touch-friendly resize handles ----------
   Mouse users get the small 12px/16px indicators. Touch devices
   (tablets, phones with styli) get a 44px hit target so a finger
   can actually grab them without zooming in. */
@media (pointer: coarse) {
  .wcf-resize-r {
    width: 44px;
  }
  .wcf-resize-b {
    height: 44px;
  }
  .wcf-resize-corner {
    width: 44px;
    height: 44px;
  }
}

/* ---------- PRINT ----------
   Brokers still print things for meetings — reports, quote
   comparisons, file checks. The rule is simple: nobody wants to burn
   ink on the sidebar, the search bar, the AI launcher, or a button
   that does nothing on paper, and dark-mode colours belong on a
   screen, never printed on white paper. */
@media print {
  .sidebar, .topbar, .ai-launcher, .ai-panel,
  .filter-row, .filter-chip, .hub-tabs, .cal-toolbar, .cal-legend,
  #add-event-panel, #cal-day-popover, .shortcuts-overlay, .cert-wizard-overlay,
  .modal-overlay, .toast, .tour-overlay, .field-note-btn,
  .btn, button, input, select, .quick-add-row, .doc-upload-form,
  .register-form-grid, .theme-toggle, .search-log-btn {
    display: none !important;
  }

  /* Force light, printer-friendly colours regardless of the theme the
     visitor happened to be using on screen — see this section's top
     comment. */
  :root, :root[data-theme="light"], :root[data-theme="dark"] {
    --bg: #ffffff; --bg-elevated: #ffffff; --bg-card: #ffffff; --bg-inset: #f4f4f2;
    --text: #101214; --text-muted: #3d4147; --text-faint: #6b6f75;
    --border: #d3d3ce; --border-soft: #e3e3df;
  }

  body, .app-shell, .main-col, .page { background: #fff !important; color: #101214 !important; display: block !important; }
  .page { max-width: 100% !important; padding: 0 !important; margin: 0 !important; }

  .card, .report-card, table, .table-wrap {
    box-shadow: none !important;
    border: 1px solid #d3d3ce !important;
    break-inside: avoid;
  }
  a { color: #101214 !important; text-decoration: none !important; }
  .badge, .role-badge, .pill-tag { border: 1px solid #999 !important; background: none !important; color: #101214 !important; }

  /* A table normally scrolls horizontally on screen (see .table-wrap)
     — on paper there's no scrolling, so let it expand to fit instead
     of clipping columns off the edge of the page. */
  .table-wrap { overflow-x: visible !important; }

  /* Phase 25d-4 — a real, confirmed print quirk found via live
     testing: a browser's own print rendering can still evaluate
     :hover against wherever the mouse last happened to be resting
     (e.g. the client list row under the cursor right before Ctrl-P),
     which without this would print that one row as a dark stripe —
     exactly the "dark background burning ink" this whole print block
     exists to prevent. Every :hover background in this app is a pure
     on-screen affordance (a row highlight, a card lift) that means
     nothing on paper — a click can't land on printed paper — so
     they're all switched off here at once rather than chased down
     one selector at a time. */
  *:hover { background: none !important; }
}
