/* Front-desk software: boring, dense, fast.
   Ocean/Meridian theme (decisions.md 2026-07-13 "Ocean theme APPROVED"; spec:
   Stats/docs/REDESIGN_MERIDIAN.md; approved look: serve-wireframes/wireframes/
   dashboards.html). Token NAMES are unchanged from the original teal theme on
   purpose — every existing var(--x) reference in this file re-skins for free.
   New tokens (added, never renamed) match the wireframe's own naming 1:1
   (serve-wireframes/wireframes/wireframe.css) so later per-area PRs that port
   Meridian CSS chunks need no renaming. */
:root {
  --bg: #f6f8fb;
  --surface: #ffffff;
  --text: #1a1f36;
  --text-2: #3c4257;
  /* Secondary-text tokens are CONTRAST-TUNED (CR-011 DS-2 / UXD-4). The old
     --muted #697386 measured 4.49 on --bg, 4.22 on --bg-inset, 4.14 on
     --accent-soft and 4.09 on --error-bg — i.e. essentially every piece of
     secondary text in the app failed WCAG AA, because almost every card,
     chip and section header sits on a tinted surface. These values clear
     4.5:1 on EVERY surface the app composes them onto (measured: --muted
     5.79 white / 5.44 bg / 5.11 inset / 5.02 accent-soft / 4.96 error-bg;
     --faint 5.16 / 4.85 / 4.55). Do not lighten either one without
     re-measuring against all five surfaces. */
  --muted: #5c6678;
  --faint: #646e7e;
  --accent: #1663e9;
  --accent-dark: #0e4fc7;
  --accent-soft: #e8effe;
  --border: #e3e8ee;
  --border-soft: #ebeff3;
  /* --border is a DECORATIVE hairline (1.23:1 — fine for a rule between
     rows, far below the 3:1 WCAG 1.4.11 needs for a control boundary).
     --border-strong is the CONTROL boundary: 3.38:1 on --surface, 3.18 on
     --bg. Every form control and every quiet button outlines in this one. */
  --border-strong: #7f8d9f;
  --hover: #f7f9fc;
  /* --ok / --warn-text darkened for AA on their own tints (were 4.29 / 4.24;
     now 5.44 on --ok-bg / 5.29 on --warn-bg). */
  --ok: #07704f;
  --ok-bg: #e8f5ee;
  --warn-text: #a34a03;
  --warn-bg: #fcf0e4;
  --error-bg: #fbe9ee;
  /* Was #df1b41 — 4.12 on --error-bg (fail) and 4.80 behind white text on a
     danger fill. #c11138 gives 5.28 on --error-bg and 6.17 with white on it. */
  --error-text: #c11138;
  /* Bridge aliases: the wireframe's canonical semantic-red names, so any
     ported Meridian CSS reaching for var(--danger) works with no edits — and
     the one spot in this file that already read var(--danger, #fallback)
     (kanban past-due card) now resolves to the real Ocean red for free. */
  --danger: var(--error-text);
  --danger-bg: var(--error-bg);
  /* Kanban column wash — completes a var(--bg-inset, rgba(0,0,0,.03))
     fallback that already existed in .board-col; same value as the
     wireframe's --col-bg. */
  --bg-inset: #eef1f6;

  /* Categorical presets (owner rule 2026-07-12) — the only colors list
     options/tags/chart series may use. The pipeline-stage system (--c-*
     below) is intentionally separate and untouched by this PR. */
  --s1: #1663e9; --s2: #7a5af8; --s3: #00a2c7; --s4: #2e9e5b;
  --s5: #d9a03c; --s6: #e4643f; --s7: #d8548c; --s8: #64748b;
  --s9: #0e8578; --s10: #12a594; --s11: #cc4b44; --s12: #a67c52;
  --s1-soft: #bcd0f8;

  /* ===== The three scales (CR-011 DS-4 / DS-5) =====
     Type: six sizes, three weights (400/600/700). A given semantic element
     renders identically on every screen. Live inventory before this landed
     was 13 sizes (10…30px) and 6 weights, with two competing label systems.
     Spacing: a 4px base. Every gap/padding/margin picks a step; nothing sits
     between two steps. */
  --fs-1: 12px;   /* meta / micro-label */
  --fs-2: 13px;   /* body-sm, dense rows, small buttons */
  --fs-3: 14px;   /* body + EVERY form control and button label */
  --fs-4: 16px;   /* section title (h2/h3) */
  --fs-5: 20px;   /* page title (h1) */
  --fs-6: 26px;   /* hero number */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;

  --radius: 6px;
  --radius-lg: 10px;
  --radius-pill: 999px;
  --gap-cards: 1rem;

  /* ===== Control geometry (CR-011 DS-1) =====
     ONE height for every control and every button, so an input, a select, a
     date field and a button sitting in the same row line up. Before this,
     selects rendered at 19px of raw OS chrome beside 35px designed inputs. */
  --control-h: 34px;
  --control-h-sm: 28px;
  /* Button standard (owner ask 2026-08-01): the ONE gap between a button's
     sprite icon and its label. Em-based so it scales with the tier's font. */
  --btn-icon-gap: 0.4em;
  /* THREE intentional field widths, replacing the seven accidental ones the
     audit measured (55 / 122 / 128 / 150 / 176 / 278 / 330px). */
  --field-w-full: 100%;
  --field-w-short: 13rem;
  --field-w-inline: 8.5rem;

  /* ===== Status-pill palette (CR-011 DS-6) =====
     Temperature and mindset get their OWN hues, deliberately NOT the
     pipeline-stage ramp (--c-* further down) — temp used to be drawn in the
     stage colours, which made an 8px dot genuinely ambiguous between "hot
     lead" and "new stage". Every -tx/-bg pair here measures ≥ 6.3:1. */
  --pill-hot: #e4643f;  --pill-hot-bg: #fdece7;  --pill-hot-tx: #9c3315;
  --pill-warm: #d9a03c; --pill-warm-bg: #fbf3e3; --pill-warm-tx: #6f4a05;
  --pill-cold: #00a2c7; --pill-cold-bg: #e2f4fa; --pill-cold-tx: #0b5f75;
  --pill-mind-cost: #a67c52;  --pill-mind-cost-bg: #f5efe8;  --pill-mind-cost-tx: #6d4c2c;
  --pill-mind-avoid: #d8548c; --pill-mind-avoid-bg: #fceef4; --pill-mind-avoid-tx: #93235a;
  --pill-mind-trust: #0e8578; --pill-mind-trust-bg: #e4f3f1; --pill-mind-trust-tx: #0a5f56;
  --pill-mind-nonstart: #64748b; --pill-mind-nonstart-bg: #eef1f5; --pill-mind-nonstart-tx: #46536a;
  --shadow: 0 2px 5px rgba(60, 66, 87, .08), 0 1px 1px rgba(0, 0, 0, .04);
  --pop-shadow: 0 0 0 1px rgba(60, 66, 87, .08), 0 15px 35px rgba(60, 66, 87, .12), 0 5px 15px rgba(0, 0, 0, .08);

  /* Typography — the whole app's fonts change from these two tokens.
     Route every font-family through var(--font-sans) / var(--font-mono). */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* App shell width — ONE token (Skyler, 2026-07-12: "the app needs more
     horizontal space"). The original 960px cap crushed the two-column
     dashboards on real monitors; 1600 gives each 50/50 column ~780px while
     staying sane on ultrawides. Deliberately narrow surfaces (login,
     settings form) keep their own smaller caps. */
  --page-max: 1600px;
  --form-measure: 34rem;   /* one width for a single-column form (UXD-62/122) */
}

* { box-sizing: border-box; }
body {
  margin: 0;
  font: var(--fs-3)/1.45 var(--font-sans);
  background: var(--bg);
  color: var(--text);
}
/* Tell the UA which colour scheme the app is authored for, so any native
   affordance we have not styled (scrollbars, the date picker's own popup,
   spin buttons) is drawn light rather than in the OS's own guess. Dark mode
   is deliberately NOT a target for this product (CR-011 DS-12): a bright
   front-desk operatory reads better in light. */
:root { color-scheme: light; }

/* ===== Accessibility utilities (deep UX audit #52, 2026-07-24) =====
   Two things live here: the screen-reader-only class used by the app's
   persistent live regions, and the skip link.

   .sr-only is the standard clip-rect technique — the element stays in the
   accessibility tree (so aria-live announcements still fire) while occupying
   no visual space. `white-space: nowrap` stops a long announcement forcing a
   scrollbar out of a 1px box. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Skip link: the first focusable element in <body>, invisible until a keyboard
   user tabs to it, then a real button pinned top-left. Keyboard-only users
   otherwise crossed ~16 rail/user-switcher stops before reaching the page. */
.skip-link {
  position: absolute;
  top: 0; left: 0;
  z-index: 200;
  transform: translateY(-150%);
  padding: 0.5rem 0.9rem;
  background: var(--surface);
  color: var(--accent-dark);
  border: 1px solid var(--accent);
  border-radius: 0 0 var(--radius) 0;
  font-size: var(--fs-3);
  font-weight: 600;
  text-decoration: none;
  box-shadow: var(--pop-shadow);
}
.skip-link:focus { transform: translateY(0); }
.skip-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The skip target is programmatically focusable only — it must never draw a
   ring just because the skip link sent focus to it. */
main.page:focus { outline: none; }

/* ===== The heading scale (CR-011 DS-4 / UXD-39) =====
   The app used to change size as you navigated: h1 was 20px on the
   dashboards and 30px on the record pages and settings; h2 was 16px vs 23px.
   The record pages come DOWN to the dashboards, not the other way, because
   a dense operational tool does not need a 30px page title. */
h1 { font-size: var(--fs-5); font-weight: 700; line-height: 1.2; margin: 0 0 var(--sp-3); }
h2 { font-size: var(--fs-4); font-weight: 700; line-height: 1.25; margin: var(--sp-5) 0 var(--sp-2); }
h3 { font-size: var(--fs-3); font-weight: 700; line-height: 1.3; margin: var(--sp-4) 0 var(--sp-2); }
/* The ONE field-label class (CR-011 UXD-57/DS-4): there were two systems for
   the same job — 11px uppercase on the record pages, 13.6px sentence case on
   settings and the edit forms. This is the record/micro form; the plain
   `label` element above is the form form. Both are on the scale. */
.field-label { display: block; font-size: var(--fs-1); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }

/* ===== Icon system: ONE central SVG sprite (app/templates/_icons.html),
   recolored via currentColor so the whole app re-themes from one place.
   Swap a <symbol> there and every icon() use updates (Skyler: themeable). ===== */
.ic { width:1.05em; height:1.05em; vertical-align:-.16em; fill:none; stroke:currentColor;
  stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; flex:none; }
.ic-fill { fill:currentColor; stroke:none; }
/* Icon sprite: taken out of flow so its inline box can't reserve a phantom
   ~22px line at the very top of the body (dead-space fix). <use> still works. */
.icon-sprite { position:absolute; width:0; height:0; overflow:hidden; }
/* Directional rotations of the one chevron symbol (UI polish 3, emoji sweep):
   the CTA-arrow "→" at the end of a link ("Open record →" etc.) and the
   back-arrow "←" ahead of a "return to X" link both reuse #i-chevron (which
   points down) rather than adding two near-duplicate symbols — same idea as
   the rail-pin toggle's own chevron rotation. */
.ic-right { transform: rotate(-90deg); }
.ic-left { transform: rotate(90deg); }
/* CR-011 UXD-32: the sort-direction chevron — ascending points up, the
   contacts list's own sort links. */
.ic-up { transform: rotate(180deg); }

/* ===== App shell: left icon rail + content column, with a phone bottom bar.
   Structure ported from the Stats prototype (Stats/web/css/app.css .rail /
   .bottom-nav) and mapped onto this app's Ocean tokens — no new tokens, no
   hardcoded colors, so it re-skins (incl. a future dark theme) for free. The
   document keeps its normal scroll; the rail is sticky-pinned.
   SalesMate-style rail (owner-approved pattern): collapsed by default (54px,
   icon-only) with hover tooltips — no more hover-to-expand. A pin toggle
   (chevron button under the logo) expands the rail to 224px with labels and
   persists the choice to localStorage; base.html reads it in <head>, before
   the stylesheet paints, so a pinned rail never flashes collapsed on load.
   (Skyler rail-nav adoption, decisions.md 2026-07-15.) ===== */
.appshell { display: flex; flex-direction: row; min-height: 100vh; }
/* Messaging fills the viewport exactly: a fixed height here lets the chat panes
   scroll internally instead of growing the page past the fold. */
.appshell:has(.msgapp) { height: 100vh; }
.appcol { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* Icon rail — 54px collapsed (icon-only, hover tooltips), 224px pinned open
   (labels shown) via the pin-toggle button; state persists in localStorage. */
.rail { position: sticky; top: 0; align-self: flex-start; z-index: 40;
  width: 54px; min-width: 54px; height: 100vh; overflow: hidden; white-space: nowrap;
  display: flex; flex-direction: column; gap: 2px; padding: 10px 9px;
  background: var(--surface); border-right: 1px solid var(--border-soft);
  transition: width .16s ease, min-width .16s ease; }
/* Pinned-open state — the same 224px width the old hover state used to
   produce, but click-driven (the pin-toggle button) and sticky across
   pageloads: html.rail-pinned is set inline in <head>, before first paint. */
html.rail-pinned .rail { width: 224px; min-width: 224px; box-shadow: 6px 0 24px rgba(26, 31, 54, .08); }
/* Hold the rail open while the switch menu is up so the popup (wider than the
   collapsed rail) is never clipped by the rail's overflow — independent of
   the pinned state; same expanded look either way. */
.rail:has(.switch-menu.open) { width: 224px; min-width: 224px; box-shadow: 6px 0 24px rgba(26, 31, 54, .08); }
.rail:has(.switch-menu.open) .logo-co,
.rail:has(.switch-menu.open) .sec,
.rail:has(.switch-menu.open) .tab span,
.rail:has(.switch-menu.open) .userchip-name { opacity: 1; }

/* Logo: accent square (always) + wordmark (revealed when pinned open). */
/* Extra bottom padding (owner ask 2026-08-01, matching the Stats app): the
   pin/collapse circle now sits BETWEEN the app mark and the first nav icon,
   so the logo block leaves room for it. */
.rail .logo { display: flex; align-items: center; gap: 10px; padding: 2px 7px 26px; text-decoration: none; }
.logo-mark { width: 20px; min-width: 20px; height: 20px; border-radius: var(--radius); background: var(--accent); }
.logo-co { font-weight: 700; font-size: 14px; color: var(--text); opacity: 0; transition: opacity .14s; }
html.rail-pinned .logo-co { opacity: 1; }

/* Rail pin control (UI polish 3, owner ask 2026-07-20: "a little arrow, a
   circle that hangs halfway off the menu" — not an in-flow row anymore).
   A small circle straddling the rail's right edge, half on/half off, near
   the logo. It's a SIBLING of .rail in the markup (base.html), not a
   descendant — .rail's overflow:hidden is load-bearing (hides collapsed-
   state label text so it can't become an invisible click target) and would
   clip anything hanging outside the rail's own box, the same problem
   .rail-tooltip below already had to dodge by staying out of that box.
   position:fixed for the same reason the tooltip uses fixed coordinates:
   the rail is ALWAYS in its "stuck" state (its natural top offset is
   already 0, so position:sticky;top:0 engages immediately and holds for
   the entire page), so a fixed viewport offset lines up with the logo
   through any scroll position, no JS math needed. left switches with
   whichever of the three states widens the rail to 224px — pinned, or the
   switch-menu forced-expand — exactly the states .rail itself keys off.
   Chevron: right (») collapsed, left («) pinned — same rotation as before. */
.rail-pin {
  /* top: between the logo mark (ends ~32px) and the first tab (~58px) —
     owner ask 2026-08-01, "move the arrow down in between those two icons"
     (like the Stats app). The .rail .logo bottom padding above reserves
     this vertical slot. */
  position: fixed; top: 34px; left: 42px; z-index: 41;
  display: flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; margin: 0; padding: 0;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--surface); box-shadow: var(--shadow);
  color: var(--muted); cursor: pointer;
  transition: left .16s ease, color .12s ease, border-color .12s ease;
}
.rail-pin:hover { color: var(--text); border-color: var(--faint); }
.rail-pin:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rail-pin .ic { width: 13px; height: 13px; min-width: 13px; transform: rotate(-90deg); transition: transform .16s ease; }
html.rail-pinned .rail-pin { left: 212px; }
html.rail-pinned .rail-pin .ic { transform: rotate(90deg); }
.rail:has(.switch-menu.open) ~ .rail-pin { left: 212px; }

/* Section labels — fixed height so icons don't jump; text shows when pinned. */
.rail .sec { height: 22px; padding: 8px 8px 2px; font-size: 10px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--faint);
  opacity: 0; transition: opacity .14s; }
html.rail-pinned .sec { opacity: 1; }

/* Tabs — icon + label rows; the label shows once the rail is pinned open. */
/* gap 2px → 6px (owner ask 2026-08-01): a little more vertical air between
   the nav icons. */
.rail .tabs { display: flex; flex-direction: column; gap: 6px; }
.rail .tab { display: flex; align-items: center; gap: 11px; width: 100%; height: 33px;
  padding: 0 8px; border: 0; border-radius: var(--radius); background: none; cursor: pointer;
  color: var(--text-2); font-size: 13px; font-weight: 500; text-align: left;
  text-decoration: none; white-space: nowrap; }
.rail .tab .ic { width: 18px; height: 18px; min-width: 18px; vertical-align: 0; color: var(--muted); }
.rail .tab span { opacity: 0; transition: opacity .14s; overflow: hidden; text-overflow: ellipsis; }
html.rail-pinned .tab span { opacity: 1; }
.rail .tab:hover { background: var(--hover); color: var(--text); }
.rail .tab.active { background: var(--accent-soft); color: var(--accent-dark); font-weight: 600; }
.rail .tab.active .ic { color: var(--accent); }
/* Unread count badge on a nav icon (Messaging) — semantic danger, not the accent. */
.nav-has-badge { position: relative; }
.nav-badge-slot { position: absolute; pointer-events: none; }
/* The badge is exempt from the collapsed-rail label fade (.rail .tab span
   opacity:0) — an unread count must show even when the rail is icons-only.
   Both the slot AND the count span inside it are `.rail .tab span`, so BOTH
   need the exemption: exempting only the slot left the number itself at
   opacity:0, so a collapsed rail showed nothing (the badge regression fixed
   2026-07-23 — the count is now visible expanded AND collapsed). */
.rail .tab .nav-badge-slot { top: 1px; left: 22px; opacity: 1; }
.rail .tab .nav-badge { opacity: 1; }
.bottom-nav .nav-badge-slot { top: 3px; left: calc(50% + 2px); }
.nav-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 8px; background: var(--error-text); color: #fff; font-size: 10px; font-weight: 700;
  line-height: 1; box-sizing: border-box; }

/* Rail foot — user switcher + sign-out, pinned to the bottom. */
.railfoot { margin-top: auto; display: flex; flex-direction: column; gap: 2px;
  padding-top: 8px; border-top: 1px solid var(--border-soft); }
.railfoot-signout { margin: 0; }
.railfoot-signout .tab { margin: 0; box-shadow: none; }
.userchip { display: flex; align-items: center; gap: 11px; padding: 3px; }
.rail .avatar-btn { width: 28px; height: 28px; min-width: 28px; font-size: 0.72rem; }
.userchip-name { opacity: 0; transition: opacity .14s; font-size: 13px; font-weight: 500;
  color: var(--text-2); overflow: hidden; text-overflow: ellipsis; }
html.rail-pinned .userchip-name { opacity: 1; }
/* Switch menu opens upward from the foot (its default drop would fall off-screen);
   caps its height so a long roster scrolls instead of clipping past the rail. */
.rail .switch-menu { top: auto; bottom: calc(100% + 6px); right: auto; left: 3px;
  max-height: 60vh; overflow-y: auto; }

/* Collapsed-rail tooltip chip (SalesMate pattern): one shared div, positioned
   by the footer script's mouseenter handler and shown only while the rail is
   collapsed (base.html gates this — see the script comment there). Kept as a
   plain sibling of .rail, not a descendant, and position:fixed, so the rail's
   own overflow:hidden (needed to keep the collapsed label text from being an
   invisible click target) never clips it. */
.rail-tooltip { position: fixed; z-index: 55; transform: translateY(-50%);
  background: #1a1f36; color: #fff; font-size: 12px; font-weight: 600;
  padding: 5px 9px; border-radius: var(--radius); white-space: nowrap; box-shadow: var(--pop-shadow);
  opacity: 0; pointer-events: none; transition: opacity .12s ease; }
.rail-tooltip.show { opacity: 1; transition-delay: .15s; }

/* Dev login-bypass warning — floating chip (top-right, fixed position) so the
   page's true vertical alignment is visible. pointer-events:none keeps it
   informational-only and never blocks clicks beneath it. */
.dev-banner { position: fixed; top: 10px; right: 14px; z-index: 45;
  pointer-events: none; }

/* Phone bottom navigation — hidden on desktop; replaces the rail ≤760px. */
.bottom-nav { display: none; }

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
  text-decoration: none;
}
.avatar-lg { width: 3.25rem; height: 3.25rem; font-size: 1.15rem; }
.avatar-sm { width: 1.5rem; height: 1.5rem; font-size: 0.65rem; }
.avatar-btn { border: 0; padding: 0; cursor: pointer; }

/* Header switch dropdown (avatar -> names -> PIN -> Enter) */
.switch-dropdown { position: relative; }
.switch-menu {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 13rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop-shadow);
  padding: 0.35rem;
  z-index: 30;
}
.switch-menu.open { display: block; }
.menu-users { list-style: none; margin: 0; padding: 0; }
.menu-users button {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  margin: 0;
  padding: 0.4rem 0.5rem;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--text);
  font-size: 0.9rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.menu-users button:hover { background: var(--hover); }
.menu-foot { border-top: 1px solid var(--border); margin-top: 0.25rem; padding: 0.4rem 0.5rem; }
.menu-foot a { color: var(--muted); font-size: 0.8rem; }
.menu-pin { padding: 0.5rem; }
.menu-pin .menu-title { margin: 0 0 0.4rem; font-size: 0.9rem; }
.menu-pin form { display: flex; align-items: center; gap: 0.4rem; }
.menu-pin input[name="pin"] { width: 5.5rem; text-align: center; letter-spacing: 0.3em; }
.menu-pin .btn-sm, .menu-pin button[type="submit"] { margin-top: 0; padding: 0.35rem 0.7rem; font-size: 0.85rem; }
.menu-error { margin: 0 0 0.4rem; color: var(--error-text); font-size: 0.85rem; }

/* ONE width philosophy for every page (CR-011 UX-F15): a page fills the shell
   column up to --page-max and is centred past it. `width: 100%` is load-bearing
   — .appcol is a flex COLUMN, and .page's cross-axis `margin: auto` stops the
   flex item stretching, so without an explicit width the page shrink-wrapped to
   its content (measured 841px inside a 1216px column at 1440 with the rail
   pinned: a ~200px dead right margin beside a cramped Appointments table, while
   the boards overflowed at the same width). Declaring the width restores the
   intent --page-max already states. */
.page { width: 100%; max-width: var(--page-max); margin: 1.5rem auto; padding: 0 1.25rem; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.25rem;
  box-shadow: var(--shadow);
}
/* ONE form measure (CR-011 UXD-62 / UXD-122). The app had four: a 352px
   card-narrow form, a 544px settings form, a 1024px settings card and a
   1216px page — so "how wide is a form here" had a different answer on every
   screen. --form-measure is now the single answer for a single-column form;
   the wide surfaces (tables, merge previews) keep .settings-card. */
.card-narrow { max-width: var(--form-measure); margin: 3rem auto; }

/* Settings (settings/multi-page, 2026-07-21: Practice / Users / AI / Lead
   Beacon, each its own /settings/* page). Every settings page is wide
   (fixes/settings-wide-download-pwa, 2026-07-22 — the owner wants one
   consistent width across all of them; wide is simply the default now, with no
   per-page modifier). Named classes only (guard test) — no inline styles. */
.settings-card { max-width: 64rem; margin: 2rem auto; }
/* The editable template library reuses .settings-card but is NOT a settings
   page (it lives at /templates); pin it to the original measure so widening
   settings leaves its look unchanged. */
.tl-page { max-width: var(--form-measure); }
/* A comfortable single-column measure for the plain top-to-bottom settings
   forms (Practice, AI): the card stays wide so its tables and merge preview
   have room, but a lone text field shouldn't stretch the full 64rem. Tables,
   .inline-form, and .ai-block content deliberately keep the full width. */
.settings-form { max-width: var(--form-measure); }
/* The top-bar tab nav shared by every /settings/* page (templates/settings/
   _nav.html) — quiet pills in the app's .chip visual language, sized to sit
   flush above .settings-card at the same width. Deliberately its own shape
   (not the icon-rail .tab, a vertical chrome element for a different job). */
.settings-tabs { display: flex; gap: 0.35rem; max-width: 64rem; margin: 2rem auto 0; flex-wrap: wrap; }
.settings-tabs + .settings-card { margin-top: 0.75rem; }
.settings-tab {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.35rem 0.75rem; border-radius: var(--radius-pill);
  font-size: 0.85rem; font-weight: 500; color: var(--muted);
  text-decoration: none; border: 1px solid transparent;
}
.settings-tab:hover { background: var(--hover); color: var(--text); }
.settings-tab.active { background: var(--accent-soft); color: var(--accent-dark);
  border-color: color-mix(in srgb, var(--accent) 28%, var(--surface)); }
.settings-tab .ic { width: 1em; height: 1em; }
/* A single checkbox + its own label, own line (Practice's "record calls"
   toggle) — the same inline-flex shape as .callhours-day / .ai-block
   .ai-check, generalized with no call-hours/AI coupling in the name. */
.field-check { display: inline-flex; align-items: center; gap: 0.4rem; margin: 0.4rem 0; font-weight: 400; }
.field-check input { margin: 0; }
/* Users page: the read-only "what each role can reach" list — stacked rows
   (role name + a couple of description lines), unlike .pulse-row/.line-row's
   single-line label/value shape. */
.role-summary-row { padding: 0.6rem 0; border-bottom: 1px solid var(--border-soft); }
.role-summary-row:last-child { border-bottom: 0; }
.role-summary-name { font-weight: 600; margin-bottom: 0.15rem; }
.merge-preview {
  margin-top: 1.5rem;
  /* Reading measure, matching .settings-form: on the now-wide settings card
     (fixes/settings-wide-download-pwa) the preview copy would otherwise run to
     ~110-char lines. Left-aligned under the form it previews. */
  max-width: 34rem;
  padding: 0.9rem 1rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.merge-preview-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.merge-preview-body { margin: 0.35rem 0 0; font-size: 0.92rem; line-height: 1.5; }
/* A labeled subsection within the settings form (e.g. "Practice identity"):
   a hairline rule + spacing so the field groups read as distinct. */
.settings-section { margin-top: 1.75rem; padding-top: 1.25rem; border-top: 1px solid var(--border); }
.settings-section h2 { margin: 0 0 0.15rem; font-size: 1rem; }

label { display: block; margin: var(--sp-3) 0 var(--sp-1); font-weight: 600; font-size: var(--fs-2); }

/* ===================================================================
   BASE FORM CONTROLS — the one shared control base (CR-011 DS-1, R4)
   ===================================================================
   Before this block the stylesheet styled `input` and gave `select`,
   `textarea`, `file`, `time` and `checkbox` essentially nothing, so every
   dropdown in the product rendered as raw OS chrome — measured at 19px tall
   in SEVEN different widths, sitting beside 35px designed inputs. That is
   the literal cause of the owner's "dropdowns are the wrong width" note.

   The contract: every control the staff can type in, pick from or toggle
   shares ONE height (--control-h), ONE font size (--fs-3), ONE border
   (--border-strong, the 3:1 boundary), ONE radius and ONE focus ring. A form
   containing an input, a select and a textarea lines up on its own.
   Add a control type here — never in a page-scoped rule. */
input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
select,
textarea,
.control {
  width: var(--field-w-full);
  box-sizing: border-box;
  padding: 0 0.6rem;
  font-family: inherit;
  font-size: var(--fs-3);
  color: var(--text);
  background-color: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}
input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
select,
.control {
  height: var(--control-h);
  line-height: normal;
}
/* A textarea is the one control that grows: same everything, height is a
   floor rather than a fixed value, and only the vertical axis resizes so a
   drag can never break the column it sits in. */
textarea {
  min-height: calc(var(--control-h) * 2.5);
  padding: 0.45rem 0.6rem;
  line-height: 1.45;
  resize: vertical;
}
/* One chevron for every dropdown, drawn from the same stroke language as the
   icon sprite (#i-chevron) — a select cannot use <use>, so the sprite's path
   is inlined as a data URI here, once. appearance:none is what retires the
   OS chrome; the right padding reserves the chevron's lane. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 1.9rem;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235c6678' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.55rem center;
  background-size: 14px 14px;
}
select:disabled { background-image: none; }
/* Native date/time inputs fall back to the UA's own (monospace) font unless
   told otherwise — the "monospace time inputs" on /settings/practice. */
input[type="date"], input[type="time"], input[type="datetime-local"], input[type="number"] {
  font-family: inherit;
  font-variant-numeric: tabular-nums;
}
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator { opacity: 0.55; cursor: pointer; }
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="time"]:hover::-webkit-calendar-picker-indicator { opacity: 0.9; }
/* File input: the raw "Choose File / No file chosen" was the most unfinished
   control in the app. The field keeps the control shell; its button wears the
   quiet-button treatment so it reads as a real control, not OS chrome. */
input[type="file"] {
  height: auto;
  min-height: var(--control-h);
  padding: 0.3rem 0.35rem;
  cursor: pointer;
}
input[type="file"]::file-selector-button {
  height: var(--control-h-sm);
  margin-right: 0.6rem;
  padding: 0 0.7rem;
  font: inherit;
  font-size: var(--fs-2);
  font-weight: 600;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
}
input[type="file"]:hover::file-selector-button { background: var(--hover); }
/* Checkbox / radio: 16px (was the UA's 13px, below the WCAG 2.5.8 target
   floor once you count the label as the hit area) and accent-coloured from
   the one token, so they never render in the OS's own blue. */
input[type="checkbox"], input[type="radio"] {
  width: 16px;
  height: 16px;
  margin: 0;
  padding: 0;
  flex: none;
  accent-color: var(--accent);
  cursor: pointer;
  vertical-align: -2px;
}
input[type="checkbox"]:disabled, input[type="radio"]:disabled { cursor: not-allowed; }

/* Focus-ring discipline (owner, 2026-07-20): a ring appears from the KEYBOARD
   only — never from a mouse hover or click. :focus-visible already gates every
   ring defined below; this one rule kills the browser-DEFAULT focus ring (e.g.
   Safari's on a <button> click — the "blue circle" hit on Insert-template) for
   any pointer focus. Keyboard focus still matches :focus-visible and keeps its
   ring; a mouse-focused input keeps its border tint below, just no outline. */
:focus:not(:focus-visible) { outline: none; }

input:focus, select:focus, textarea:focus, .control:focus { border-color: var(--accent); }
input:focus-visible, select:focus-visible, textarea:focus-visible, .control:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent);
}
/* One disabled treatment for the whole app (there were 6 :disabled rules and
   no rule about them). */
input:disabled, select:disabled, textarea:disabled, .control:disabled {
  background-color: var(--bg-inset);
  color: var(--muted);
  cursor: not-allowed;
  -webkit-text-fill-color: var(--muted);  /* Safari dims disabled text again on top of color */
}
/* Error state (CR-011 DS-15: 0 :invalid rules existed). :user-invalid fires
   only AFTER the person has interacted, so a pristine required field is never
   pre-painted red; [aria-invalid] lets a server-rendered error match it. */
input:user-invalid, select:user-invalid, textarea:user-invalid,
[aria-invalid="true"] {
  border-color: var(--error-text);
}
input:user-invalid:focus-visible, select:user-invalid:focus-visible,
textarea:user-invalid:focus-visible, [aria-invalid="true"]:focus-visible {
  outline-color: var(--error-text); border-color: var(--error-text);
}

/* The intentional widths. Three, not seven: a field fills its column, or it
   is a short field (dates, enums, codes), or it is an inline field sitting in
   a row of prose or controls. Anything else needs a reason. */
.control-full { width: var(--field-w-full); }
.control-short { width: var(--field-w-short); max-width: 100%; }
.control-inline { width: var(--field-w-inline); max-width: 100%; }
/* The dense variant, for controls that live inside a table row or card row
   rather than a form. Same everything, one step down. */
.control-sm { height: var(--control-h-sm); font-size: var(--fs-2); padding: 0 0.5rem; }
select.control-sm { padding-right: 1.6rem; background-size: 12px 12px; }
textarea.control-sm { height: auto; min-height: calc(var(--control-h-sm) * 2.5); padding: 0.35rem 0.5rem; }

/* Meridian accent-button treatment: solid accent + a matching soft accent
   shadow (dashboards.html's .btn-call/.hcall recipe), one radius scale. */
/* ===================================================================
   BUTTON HIERARCHY — exactly THREE tiers (CR-011 DS-3, R3)
   ===================================================================
   1. PRIMARY   — accent fill. `button` / `.btn`. ONE per view. It is the
                  single thing the person is most likely to click here.
   2. SECONDARY — quiet outline on --border-strong. `.btn-quiet` (and its
                  small form `.btn-sm`, the row/section-level default).
   3. TERTIARY  — text only, no box. `.btn-ghost-sm` / `button.linklike`.
   Plus TWO cross-cutting modifiers, not tiers:
     .btn-danger      destructive CONFIRM (red fill, in a modal only)
     .btn-danger-line destructive TRIGGER (red text, quiet outline, in page)
   The audit measured five renderings claiming to be "primary" and screens
   with three and five accent buttons at once. If a view has no single
   most-likely action, it has NO accent button — that is a valid answer, and
   it is why `.action-primary` on board cards is deliberately white (owner,
   2026-07-21: "the blue is too much"). Do not add a fourth tier. */
/* max-width on every tier below (owner ask, design-system foundations
   2026-07-27): a button is sized to its label, never to its container. Plain
   table cells (`.table td`) never stretched a button in practice — they're
   block boxes, and `inline-flex` already sizes to content — but a table row
   is exactly where a future flex/grid wrapper gets added around a button
   without anyone re-checking this file, so the cap is real insurance, not
   decoration. (`.action-primary` used to be the one flex:1 fill-the-row
   exception; owner ask 2026-08-01 retired that — it is content-sized like
   everything else now, with the card ⋯ anchored to the corner instead.) */
/* Button standard (owner ask 2026-08-01): ONE height for every action button —
   the SHORTER of the two the app had grown (--control-h-sm, 28px; the 34px
   --control-h stays the height of form CONTROLS: inputs/selects). And ONE
   icon-to-label gap (--btn-icon-gap, ~0.4em): every tier is inline-flex with
   the same gap, so a sprite icon never runs into its label again. */
button, .btn {
  margin-top: 1rem;
  height: var(--control-h-sm);
  max-width: 20rem;
  padding: 0 var(--sp-4);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  font-family: inherit;
  font-size: var(--fs-3);
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(22, 99, 233, 0.28);
}
button, .btn { display: inline-flex; align-items: center; justify-content: center;
  gap: var(--btn-icon-gap); }
.btn { text-decoration: none; }
/* The same icon-to-label gap on every styled tier (each re-declares display
   with higher specificity, so the gap must ride along explicitly). */
button.btn, button.btn-quiet, button.btn-sm, .btn-quiet, .btn-sm, .btn-ghost-sm,
.action-primary, .btn-danger-line { gap: var(--btn-icon-gap); }
button:hover, .btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: #fff; }
button:disabled, .btn:disabled { opacity: 0.5; cursor: not-allowed; }
button:disabled:hover, .btn:disabled:hover { background: var(--accent); border-color: var(--accent); }

/* TIER 2 — the quiet outline. This is the default for anything that is not
   THE action of the view: section Saves, Rename, Add folder, Restore, filters.
   .btn-sm is the same tier one step down, and it is by far the most-used
   class in the app (26 call sites). Making it quiet is what retires the five
   stacked accent Saves on /settings/users and the three accent buttons on
   /templates in one edit — including the `Delete…` that measured 1.09:1 by
   putting red text on the inherited accent fill. */
.btn-quiet, .btn-sm, button.btn-quiet, button.btn-sm {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--btn-icon-gap);
  margin-top: 0;
  height: var(--control-h-sm);
  padding: 0 0.8rem;
  font-family: inherit;
  font-size: var(--fs-3);
  font-weight: 600;
  line-height: 1.2;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: none;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}
.btn-sm, button.btn-sm { height: var(--control-h-sm); padding: 0 0.65rem; font-size: var(--fs-2); }
.btn-quiet:hover, .btn-sm:hover, button.btn-quiet:hover, button.btn-sm:hover {
  background: var(--hover); border-color: var(--muted); color: var(--text);
}
.btn-quiet:focus-visible, .btn-sm:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-quiet:disabled, .btn-sm:disabled { opacity: 0.5; cursor: not-allowed; background: var(--surface); }
/* The escape hatch: a small button that IS the primary of its own surface
   (the PIN confirm in the user switcher and the claim picker). Explicit, so
   an accent button is always a deliberate choice. */
.btn-sm.is-primary, button.btn-sm.is-primary {
  background: var(--accent); border-color: var(--accent); color: #fff;
  box-shadow: 0 1px 2px rgba(22, 99, 233, 0.28);
}
.btn-sm.is-primary:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: #fff; }

/* DESTRUCTIVE TRIGGER — red text on the quiet shell. Distinct from the
   primary (never accent) and from the secondary (its label is red), and it
   never competes for the eye because it carries no fill. Pairs with
   .btn-danger, the filled red CONFIRM inside the modal it opens. */
.btn-danger-line, button.btn-danger-line, .btn-sm.tx-danger, button.btn-sm.tx-danger {
  color: var(--error-text);
  background: var(--surface);
  border-color: color-mix(in srgb, var(--error-text) 38%, var(--surface));
  box-shadow: none;
}
.btn-danger-line:hover, button.btn-danger-line:hover,
.btn-sm.tx-danger:hover, button.btn-sm.tx-danger:hover {
  background: var(--error-bg); border-color: var(--error-text); color: var(--error-text);
}
/* button.linklike never reset the base button{} border/box-shadow below, so
   every "linklike" button app-wide (action-menu rows, search, Restore,
   Archive, save…) rendered as a blue-outlined pill instead of a quiet text
   action (owner ask 2026-07-20 — screenshot of the boards' ▾ menu). Reset
   both here, once, for the whole app; keep a real :focus-visible ring so
   keyboard nav doesn't lose its indicator along with the always-on border. */
button.linklike {
  margin: 0;
  padding: 0;
  border: 1px solid transparent;
  background: none;
  box-shadow: none;
  color: var(--muted);
  font-weight: 400;
  font-size: var(--fs-2);
  text-decoration: underline;
}
button.linklike:hover { color: var(--text); }
button.linklike:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* CR-011 UXD-86: a bare `.linklike` (outside `.action-list`, which already
   pads its own rows) computed to ~18px tall — under the 24px target floor.
   `padding-block` only grows the hit area; no border/background/box is added,
   so it still reads as plain text, not a button. `inline-block` is what lets
   vertical padding actually widen an <a>'s clickable box. */
button.linklike, a.linklike, details > summary.linklike {
  display: inline-block;
  padding-block: 0.3rem;
}

/* TIER 4 — the QUIET IN-CARD CONTROL reset (Log outcome v2 postmortem,
   2026-08-02). The bare `button, .btn` rule above styles THE action button:
   accent fill, 20rem cap, radius, shadow, 1rem top margin. Any quiet control
   inside a card/modal — a choice-list row, a chip, a tab, a pencil — that
   styles itself with a single class overrides only what it DECLARES; the five
   props it forgets leak through. That is exactly how the shipped Log-outcome
   list became 320px floating pills (max-width + radius + shadow + margin
   leaks) with a white-on-white hover (`button:hover{color:#fff}` at (0,1,1)
   outranks the class's own (0,1,0) rest color).

   So: every quiet in-card button class joins THIS group, which neutralizes
   the whole global button surface once. The class's own rule later in the
   file then adds back only what it wants. `button.linklike` above is the
   same fix in one-off form; this is the systemic version. A guard test
   (tests/test_choice_row_reset.py) fails if a card template grows a button
   class that is neither a styled tier nor a member of this group — the next
   card can't reintroduce the leak.

   Ordering is load-bearing: AFTER the global tiers (so it wins the cascade
   at equal specificity), BEFORE the per-class rules (~line 3400+, so their
   declared deltas win over this reset). */
.lo2-out, .lo2-chip, .ac-choice, .ac-quiet-back, .ac-quiet-next, .ac-tab,
.ac-pencil, .ac-outbtn, .claim-tile-btn, .qd-opt, .picker-row, .iconbtn,
.tpick-back, .tpick-folder, .tpick-tmpl, .composer-more-item, .load-earlier,
.ai-refresh, .ai-call-btn, .draftbtn, .ec-tb, .ec-mfpill, .ec-ccbcc,
.lv-pill, .lv-ctx-item {
  margin: 0;
  height: auto;
  min-height: 0;
  max-width: none;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font-weight: 400;
  font-size: inherit;
  line-height: inherit;
  justify-content: flex-start;
  text-align: left;
  box-shadow: none;
}
/* The hover half of the same leak lives on each member's OWN :hover rule:
   the global `button:hover` paints accent-dark + WHITE at (0,1,1), and a
   member hover at (0,2,0) beats it ONLY for the properties it declares — so
   every member hover below re-declares background AND color AND (where the
   member has a visible border) border-color. The guard test checks this. */

.flash {
  max-width: var(--page-max);
  margin: 0.75rem auto 0;
  padding: 0.5rem 1rem;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent-dark);
}
.flash-error { background: var(--error-bg); color: var(--error-text); }
.flash-ok { background: var(--ok-bg); color: var(--ok); }

.muted { color: var(--muted); }

.switch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 1rem;
}
.switch-card { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
.switch-card input[name="pin"] { text-align: center; letter-spacing: 0.3em; }
.switch-card button { margin-top: 0.25rem; width: 100%; }
.switch-card-active { border-color: var(--accent); }
.switch-name { font-weight: 600; }

/* Claim-flow v2 "who's claiming?" picker (owner spec, 2026-07-23): avatar tiles
   in the shared crmModal — pick yourself (claim straight through) or a teammate
   (reveal their inline PIN, then claim-as switches to them). Reuses the switch
   screen's avatar + centered-PIN visual language. */
.claim-picker { position: relative; }
.claim-picker-x { position: absolute; top: 0.6rem; right: 0.6rem; }
.claim-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6.75rem, 1fr));
  gap: 0.6rem;
  align-items: start;
  margin-top: 0.9rem;
}
.claim-tile { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; padding: 0.55rem 0.5rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.claim-tile-me { border-color: var(--accent); }
.claim-tile-btn { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; width: 100%; border: 0; background: none; padding: 0.2rem; cursor: pointer; box-shadow: none; }
.claim-tile-btn:hover { background: var(--hover); color: inherit; border-radius: var(--radius); }
.claim-tile-name { font-weight: 600; font-size: 0.85rem; }
.claim-pin-form { display: none; flex-direction: column; gap: 0.35rem; width: 100%; margin-top: 0.15rem; }
.claim-tile.is-open .claim-pin-form { display: flex; }
.claim-pin-form input { width: 100%; text-align: center; letter-spacing: 0.2em; }
.claim-pin-form .btn-sm { width: 100%; letter-spacing: normal; }
.claim-picker-actions { display: flex; gap: 0.6rem; justify-content: flex-end; align-items: center; margin-top: 1rem; }

/* Phone shell (≤760px, matching the Stats rail breakpoint): drop the rail and
   raise the fixed bottom bar; the workspace clears it with bottom padding. */
@media (max-width: 760px) {
  .rail { display: none; }
  /* The pin control is a sibling of .rail now (not a descendant, so it can
     hang outside .rail's own box without being clipped — see the CSS at
     .rail-pin) — which means THIS rule has to hide it explicitly too; it
     doesn't get to ride along with .rail's own display:none for free. */
  .rail-pin { display: none; }
  .bottom-nav { position: fixed; left: 0; right: 0; bottom: 0; z-index: 40; display: flex;
    background: var(--surface); border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom, 0); box-shadow: 0 -1px 6px rgba(0, 0, 0, .06); }
  .bn-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: .5rem 0 .55rem; border: 0; background: none; text-decoration: none;
    color: var(--muted); font-size: .68rem; font-weight: 600; }
  .bn-item .ic { width: 22px; height: 22px; vertical-align: 0; color: var(--muted); }
  .bn-item.active, .bn-item.active .ic { color: var(--accent); }
  .appcol { padding-bottom: calc(60px + env(safe-area-inset-bottom, 0)); }
}

@media (max-width: 600px) {
  .page { margin-top: 1rem; }
}

/* A1: chips, tables, contact/timeline layout (system-wide event colors).
   Generic chip variants ride the new semantic tokens; the per-event-type
   colors below are remapped onto the --s1..--s12 categorical presets (same
   soft-tint + mixed-text formula the approved wireframe uses). chip-ev-sms
   and chip-ev-email are ALSO the Campaigns Sent-history channel badges
   (campaigns/index.html) — left untouched here on purpose. Inside the
   timeline they're superseded by the direction system below (.tl-chip.dir-*
   always wins on specificity), so this pair's own colors never paint there. */
.chip { display: inline-block; padding: 0.05rem 0.5rem; border-radius: var(--radius); font-size: 0.75rem; background: var(--bg); color: var(--muted); border: 1px solid var(--border); text-decoration: none; }
.chip-accent { background: var(--accent-soft); color: var(--accent-dark); border-color: color-mix(in srgb, var(--accent) 28%, var(--surface)); }
.chip-ok { background: var(--ok-bg); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 28%, var(--surface)); }
.chip-danger { background: var(--error-bg); color: var(--error-text); border-color: color-mix(in srgb, var(--error-text) 28%, var(--surface)); }
.chip-ev-sms { background: color-mix(in srgb, var(--s4) 13%, var(--surface)); color: color-mix(in srgb, var(--s4) 75%, var(--text)); border-color: color-mix(in srgb, var(--s4) 30%, var(--surface)); }
.chip-ev-sms-in { background: color-mix(in srgb, var(--s10) 13%, var(--surface)); color: color-mix(in srgb, var(--s10) 75%, var(--text)); border-color: color-mix(in srgb, var(--s10) 30%, var(--surface)); }
/* The call chip reads ORANGE (its kind color, --call-hue) — same muted-tint
   formula as the other event chips. In the timeline it now paints for real:
   calls no longer stack a .dir-out/.dir-in on top (a missed call still does,
   the red dir-missed winning by specificity — the one alert exception). */
.chip-ev-call { background: color-mix(in srgb, var(--call-hue) 13%, var(--surface)); color: color-mix(in srgb, var(--call-hue) 70%, var(--text)); border-color: color-mix(in srgb, var(--call-hue) 30%, var(--surface)); }
.chip-ev-email { background: color-mix(in srgb, var(--s5) 14%, var(--surface)); color: color-mix(in srgb, var(--s5) 62%, var(--text)); border-color: color-mix(in srgb, var(--s5) 36%, var(--surface)); }
/* Inbound email chip — the --s10 inbound hue, mirroring chip-ev-sms-in, so an
   ingested patient email reads as inbound just like an inbound text. */
.chip-ev-email-in { background: color-mix(in srgb, var(--s10) 13%, var(--surface)); color: color-mix(in srgb, var(--s10) 72%, var(--text)); border-color: color-mix(in srgb, var(--s10) 30%, var(--surface)); }
/* Neutral bookkeeping (notes/stage/scheduled, owner-approved mockup
   2026-07-20): transparent/surface fill + a plain border — quiet on purpose,
   so human conversation (the colored direction chips below) visually
   dominates system bookkeeping. chip-ev-stage's old hardcoded purple
   (#eae7f3/#553a8e/#d7c5ef) is the "stragglers" leftover the PR-117 comment
   flagged — folded into this same neutral treatment now. */
.chip-ev-note, .chip-ev-stage { background: var(--surface); color: var(--muted); border-color: var(--border); }
/* Recorded-exam chip (exam-recorder integration, 2026-08-01) — its own teal
   kind identity beside notes gold / emails slate / calls orange, built from the
   same muted-tint formula so it can never drift from the family. */
.chip-ev-exam { background: color-mix(in srgb, var(--exam-hue) 13%, var(--surface)); color: color-mix(in srgb, var(--exam-hue) 70%, var(--text)); border-color: color-mix(in srgb, var(--exam-hue) 30%, var(--surface)); }

.small { font-size: 0.8rem; }
.table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.table th { text-align: left; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); border-bottom: 2px solid var(--border); padding: 0.4rem 0.75rem; }
.table td { border-bottom: 1px solid var(--border); padding: 0.45rem 0.75rem; vertical-align: top; }
.table tr:hover td { background: var(--hover); }

/* (The Appointments book's `.appt-table` fixed-layout/`<colgroup>` contract
   lived here. It is gone with the tables themselves — the book is now the
   Style-7 `.lv-` grid, where one `.lv-cA` on the panel gives every day the
   same column geometry with nothing to measure. See the `.lv-` block below.) */

.cols { display: flex; gap: var(--gap-cards); align-items: flex-start; }
.cols .side { width: 20rem; flex-shrink: 0; display: flex; flex-direction: column; gap: var(--gap-cards); }
.cols .grow { flex: 1; min-width: 0; }
@media (max-width: 900px) { .cols { flex-direction: column; } .cols .side { width: 100%; } }

.fields { margin: 0; }
.fields dt { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); margin-top: 0.6rem; }
.fields dd { margin: 0.1rem 0 0; font-size: 0.92rem; }

/* ── VIEW-THEN-EDIT RECORDS (Skyler, 2026-07-24) ────────────────────────────
   A record card shows its VALUES at rest and its CONTROLS only while editing.
   The card carries data-recmode; the pencil (.rec-toggle, delegated handler in
   base.html) flips it. Every editable field ships both layers — .rec-view and
   .rec-edit — and exactly one is displayed. Nothing here is field-specific, so
   any card that adopts .rec-card gets the behaviour for free. */
.rec-card { position: relative; }
.rec-card .rec-heading { padding-right: 2.4rem; }        /* the title never runs under the pencil */
.rec-card[data-recmode="view"] .rec-edit { display: none; }
.rec-card[data-recmode="edit"] .rec-view { display: none; }
.rec-card[data-recmode="edit"] .rec-toggle { background: var(--accent-soft); color: var(--accent-dark); }
/* A field with no value states so plainly, rather than showing an empty box or
   a stray dash — "not set" is information; a blank control is not. */
.rec-unset { color: var(--faint); font-style: italic; }
.rec-card .rec-toggle { border: 0; background: none; cursor: pointer; padding: 0; }
/* The corner toggle is a CHEVRON (collapse-then-expand, Skyler 2026-07-30):
   collapsed points right ("expand this way"), expanded rotates to the standard
   down chevron — the same rotation language the patient-list cards use. */
/* Collapsed points DOWN ("this opens downward"), expanded flips UP
   (Skyler, 2026-07-30) — the base chevron glyph already points down. */
.rec-card .rec-toggle .ic { transform: rotate(0deg); transition: transform .16s ease; }
.rec-card[data-recmode="edit"] .rec-toggle .ic { transform: rotate(180deg); }
/* Values sit a touch in from their headings so the ALL-CAPS labels read as
   the skeleton of the card at a glance (Skyler, 2026-07-30). */
.rec-card .fields dd { padding-left: 0.55rem; }
/* Expand-is-edit (Skyler, 2026-07-30): collapsed shows only fields that HAVE
   values — an unset row is noise at rest; expanding shows every field as its
   control. Rows that carry an active nudge (the missing-chart alert) opt out
   of .rec-empty in the template, so the alert never hides with its row. */
.rec-card[data-recmode="view"] .rec-empty { display: none; }
/* The chevron never overlaps content: the card reserves its corner. */
.rec-card { padding-bottom: 2.9rem; }

/* Contact panel — the same record module as the patient card (Skyler,
   2026-07-30): labelled rows with plain values; the channel ACTIONS live on
   their own icon row rather than being the values themselves. Consent stays
   silent unless a channel is blocked; preferred is marked, not reordered. */
.rec-title { display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem; }
.contact-panel { position: relative; }
/* Bottom-right, one spacing everywhere (Skyler, 2026-07-30) — the expand
   chevron sits in the same corner on every record-ish card. */
.card-edit { position: absolute; bottom: 0.7rem; right: 0.7rem; display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; border-radius: var(--radius); color: var(--muted); }
.card-edit:hover { background: var(--hover); color: var(--text); }
.card-edit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cc-blocked { font-size: 0.72rem; font-weight: 600; color: var(--error-text); }
/* The card's own title (contact-wide view): the page name moved INSIDE the
   card, glyphed (Skyler, 2026-07-30) — card-scale, not page-scale. */
.cc-name { font-size: 1.05rem; font-weight: 700; margin: 0 0 0.3rem; gap: 0.45rem; }
/* The channel-action icons (call / text / email) — quiet ghost squares on the
   same bottom line as the expand chevron. Buttons and links must render
   identically (box-shadow: none kills the global button shadow only the
   <button>s were getting). The PREFERRED channel is the highlighted one —
   accent as active-state marker, replacing the old Preferred pill. */
.cc-actions { position: absolute; left: 1.25rem; bottom: 0.7rem; display: flex; gap: 0.5rem; margin: 0; }
.cc-iconbtn { display: inline-flex; align-items: center; justify-content: center;
  margin: 0; padding: 0; width: var(--control-h); height: var(--control-h); border: 1px solid var(--border);
  border-radius: var(--radius); background: none; box-shadow: none; color: var(--muted); cursor: pointer; }
.cc-iconbtn:hover { background: var(--hover); color: var(--text); }
.cc-iconbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
a.cc-iconbtn, a.cc-iconbtn:visited { text-decoration: none; color: var(--muted); }
a.cc-iconbtn:hover { color: var(--text); }
.cc-iconbtn.is-preferred, a.cc-iconbtn.is-preferred { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-dark); }
.contact-rule { border: 0; border-top: 1px solid var(--border); margin: 1rem 0 0.7rem; }
.edit-other { display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem; }

/* The general filter-chips row and the per-user multi-select timeline filter share
   one layout. The timeline filter's checkbox is visually hidden; the chip's .on
   state (server-rendered from the saved filter) shows selection, and toggling
   auto-submits (persists per user). */
.filter-chips,
.timeline-filter { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; margin: 0.4rem 0 0.75rem; font-size: 0.8rem; }
/* Keep a short value on one line — dates in a table column, mostly (UXD-85). */
.nowrap { white-space: nowrap; }

/* The label that opens a chip row ("When", "Type") — UXD-77. Fixed width so
   two stacked filter rows line their chips up at the same x. */
.filter-lead { min-width: 3.2rem; font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.03em; color: var(--muted); }
.filter-chips + .filter-chips { margin-top: -0.45rem; }

/* ── Two DIFFERENT kinds of filter, styled to look different (owner
   walkthrough 2026-07-27: "all / auto / manual / include closed" were four
   identical chips, but they are not four of the same thing — the first
   three are a mutually-exclusive CHOICE (pick exactly one), the fourth is an
   independent ON/OFF toggle (combine with whichever choice is picked). Boards
   are the one place this pairing shows up today, hence `.board-filters` as
   the shared row wrapper; reusable wherever the same pairing appears later. */
.board-filters { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; margin: 0; }

/* The one-of-three CHOICE: one bordered pill, segments sharing a single
   hairline (a divider between them, not a gap) so the group reads as ONE
   control with a moving selection — never three separate buttons. */
.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-pill);
  overflow: hidden; background: var(--bg); }
.segmented-opt { padding: 0.2rem 0.7rem; font-size: 0.78rem; font-weight: 600; color: var(--muted);
  text-decoration: none; border-right: 1px solid var(--border); line-height: 1.6; }
.segmented-opt:last-child { border-right: 0; }
.segmented-opt:hover:not(.is-active) { background: var(--hover); color: var(--text); }
.segmented-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The active segment is the one accent fill on this row — same "active
   state" allowance as everywhere else accent blue is reserved for (no-blue-
   outline-controls rule); the two inactive segments stay neutral. */
.segmented-opt.is-active { background: var(--accent-soft); color: var(--accent-dark); }

/* The independent ON/OFF: a real switch shape (track + thumb), not another
   pill, so it reads as "combine this with whichever of the three above" —
   a different kind of decision, a different kind of control. Still a plain
   link (server round-trip, like every other board filter) wearing
   role="switch" + aria-checked so assistive tech gets the right semantics
   even though the markup is an <a>, not an <input type=checkbox>. */
.toggle-switch { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.78rem;
  font-weight: 600; color: var(--muted); text-decoration: none; cursor: pointer; user-select: none; }
.toggle-switch:hover { color: var(--text); }
.toggle-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius); }
.toggle-track { position: relative; width: 1.9rem; height: 1.05rem; border-radius: var(--radius-pill);
  background: var(--border); flex: 0 0 auto; transition: background 0.15s; }
.toggle-thumb { position: absolute; top: 1px; left: 1px; width: 0.85rem; height: 0.85rem; border-radius: 50%;
  background: var(--surface, #fff); box-shadow: var(--shadow); transition: transform 0.15s; }
.toggle-switch.is-on { color: var(--text); }
.toggle-switch.is-on .toggle-track { background: var(--accent); }
.toggle-switch.is-on .toggle-thumb { transform: translateX(0.85rem); }
@media (prefers-reduced-motion: reduce) { .toggle-track, .toggle-thumb { transition: none; } }

/* Appointments' "Book a visit" moves onto the search row (owner walkthrough
   2026-07-27, item C1) instead of its own line above it — this is the ONE
   spot in the row that pushes right, so the search field + filters stay
   left and the primary CTA lands where a page action normally does. */
.search-row-cta { margin: 0 0 0 auto; } /* zeroes the global button{margin-top:1rem} leak inside the flex row */

/* ONE rendering for a count beside a label (UXD-71) — it was a pill on the
   dashboards and plain grey text on the boards. */
.count-pill { display: inline-flex; align-items: center; justify-content: center; min-width: 1.4rem;
  padding: 0 0.4rem; border-radius: var(--radius-pill); background: var(--bg);
  border: 1px solid var(--border); font-size: 0.75rem; font-weight: 600; }

/* An empty board column states itself instead of showing ~800px of grey
   (UXD-72). Shown only while the drag container holds no card, so dragging
   the last card out reveals it and dropping one in hides it — no JS.
   The message is the drag container's next sibling, so this is a plain
   adjacent-sibling match. (It must NOT be written as
   `.col:has(.board-col:not(:has(.board-card)))` — :has() cannot be nested
   inside :has(), so that selector is invalid and the browser drops the whole
   rule silently.) */
.board-empty { display: none; padding: 0.75rem 0.25rem; }
.board-col:not(:has(.board-card)) + .board-empty { display: block; }

/* A page-level empty state (UXD-78): says what is empty, under which filter,
   and offers the action that resolves it. */
.empty-state { text-align: left; }
.empty-state .empty-title { margin: 0 0 0.35rem; font-weight: 600; }
.empty-state p + p { margin-top: 0.5rem; }

/* JOB2 2026-07-27: two of the three chips (the checkbox ones) are `<label>`
   elements — the global `label { display:block; margin: var(--sp-3) 0
   var(--sp-1); font-weight:600; }` rule (line ~512) out-specs nothing here on
   margin/font-weight (this class never set either), so those two chips carried
   extra vertical margin and a bolder weight the plain `<button>` "All" chip
   never had — the "misaligned AND bigger" the Messaging thread filters read
   as versus the plain `.chip` convention (`.filter-chips` on boards/
   appointments/calls), which has neither problem because it's built from
   `<a>`s, not `<label>`s. Resetting both here is what actually makes this the
   shared chip style, everywhere `.tf-toggle` is reused (also the contact
   timeline filter). */
.tf-toggle { display: inline-flex; align-items: center; margin: 0; font-weight: 400; padding: 0.05rem 0.55rem; border-radius: var(--radius); font-size: 0.75rem; background: var(--bg); color: var(--muted); border: 1px solid var(--border); cursor: pointer; user-select: none; }
.tf-toggle { box-shadow: none; }
.tf-toggle:hover { background: var(--bg); color: var(--muted); border-color: var(--border); }
.tf-toggle.on { background: var(--accent-soft); color: var(--accent-dark); border-color: color-mix(in srgb, var(--accent) 28%, var(--surface)); }
.tf-toggle input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
/* Timeline kind-filter pills wear their kind's OWN timeline color when active
   (owner ask 2026-08-01) — the same hue the matching entries carry (notes
   yellow, texts --s4, emails slate, calls orange, scheduled dashed accent;
   stage changes stay the neutral chip, like their entries). The generic
   accent .on above remains the fallback for non-kind uses of .tf-toggle
   (messaging thread filters). --tf-hue parameterizes one formula. */
.tf-toggle[class*="tf-kind-"].on { background: color-mix(in srgb, var(--tf-hue) 13%, var(--surface));
  color: color-mix(in srgb, var(--tf-hue) 70%, var(--text));
  border-color: color-mix(in srgb, var(--tf-hue) 30%, var(--surface)); }
.tf-kind-note { --tf-hue: var(--note-hue); }
.tf-kind-sms { --tf-hue: var(--s4); }
.tf-kind-email { --tf-hue: var(--email-hue); }
.tf-kind-call { --tf-hue: var(--call-hue); }
.tf-kind-exam { --tf-hue: var(--exam-hue); }
.tf-kind-stage { --tf-hue: var(--faint); }
.tf-toggle.tf-kind-scheduled.on { background: var(--accent-soft); color: var(--accent-dark);
  border: 1px dashed color-mix(in srgb, var(--accent) 40%, var(--surface)); }
.inline-form { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; margin: 0.5rem 0 0.75rem; }
.inline-form input, .inline-form select { width: auto; flex: 1; min-width: 8rem; }
.inline-form button { margin-top: 0; }
/* ===== ONE search-field standard (design-system foundations, 2026-07-27) ===
   Before this there were two renderings: a plain flex row with a visible gap
   between input and button (appointments, calls — read as "the button is
   floating, not part of the field") and a glued-flush version (contacts,
   boards) that got the geometry right but existed as a SEPARATE class,
   `.searchbar-inline`, so a page had to opt in by accident of which markup it
   copied. `.search-field` (partials/_search.html renders it) is now the only
   one: always the glued-flush geometry, always centred top-to-bottom (equal
   4px insets top/bottom on the absolutely-positioned button do that), on
   every page. Search is never a screen's PRIMARY action — an accent-filled
   Search made it the only blue thing on /contacts, /appointments and both
   boards while the real create action was a ghost link (CR-011 §4.1) — so the
   button stays the quiet tier. */
.search-field { position: relative; width: min(100%, 28rem); max-width: 28rem; margin-bottom: 0.75rem; }
.search-field input { width: 100%; min-width: 0; height: var(--control-h); padding-right: 5.8rem; box-sizing: border-box; }
/* Live search (owner ask 2026-08-01): every list search now filters as you
   type (the Messaging pattern), so the macro drops the Search button on live
   forms — the input takes normal right padding back. Enter still submits. */
.search-field.is-live input { padding-right: 0.8rem; }
/* One-row list header (owner ask 2026-08-01, contacts): title left, the
   search centered in the remaining space, the page's create action far
   right — one row instead of a stacked title + description + search. */
.list-head { display: flex; align-items: center; gap: 1rem; margin: 0 0 0.9rem; flex-wrap: wrap; }
.list-head h1 { margin: 0; flex: 0 0 auto; }
.list-head .search-field { margin: 0 auto; flex: 1 1 16rem; }
.list-head > .btn-quiet { margin-left: auto; flex: 0 0 auto; white-space: nowrap; }
.search-field button[type="submit"] { position: absolute; right: 4px; top: 4px; bottom: 4px;
  min-height: 0; height: auto; margin-top: 0; padding: 0 0.85rem; border-radius: calc(var(--radius) - 2px);
  background: var(--surface); color: var(--text); border-color: var(--border-strong);
  box-shadow: none; font-size: var(--fs-2); }
.search-field button[type="submit"]:hover { background: var(--hover); border-color: var(--muted); color: var(--text); }
.search-field button[type="submit"]:disabled { background: var(--bg-inset); border-color: var(--border);
  color: var(--muted); box-shadow: none; cursor: default; }
.search-field-clear { position: absolute; right: -0.25rem; top: 50%; transform: translate(100%, -50%); }
/* A search field that rides inside a flex row (`.search-row`, below, or a
   `.filter-chips` row that already carries the search form as its last
   child — boards/board.html) drops its own bottom margin and max-width cap
   so it sits flush with its neighbors instead of fighting the row for space. */
.filter-chips .search-field, .search-row .search-field { margin-bottom: 0; flex: 1 1 18rem; min-width: 12rem; max-width: 24rem; }
/* ===== Filters on the search row (design-system foundations, 2026-07-27) ===
   Every list used to stack filter chips in their own row(s) ABOVE the table,
   which cost ~2-3 rows of vertical space before any real content appeared.
   `.search-row` puts the search field and its filter-chip groups on one flex
   line — it wraps under the field on a narrow viewport (phones, the 345px
   messaging sidebar) rather than clipping, but on a normal desktop width they
   share the row search-field-then-chips, left to right. */
.search-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 0.4rem 0 0.75rem; }
.search-row .filter-chips { margin: 0; }
/* CR-011 UXD-32: was a 17px-tall link (text + a literal ↓/↕ glyph) — under the
   24px target floor. Padding lifts the hit area without changing the header
   row's rhythm (the link sits in a <th>, not a dense list). */
.table-sort { display: inline-flex; align-items: center; gap: 0.25rem; color: inherit;
  text-decoration: none; white-space: nowrap; padding: 0.35rem 0; min-height: 24px; }
.table-sort:hover, .table-sort.is-active { color: var(--accent-dark); }
.table-sort .ic { width: 12px; height: 12px; flex: none; }
.table-sort:not(.is-active) .ic { color: var(--faint); }
/* Quiet secondary/tertiary control (owner ask 2026-07-20: "get rid of the blue
   outlines on everything" — every Cancel/Close/clear/Undo button app-wide used
   this class with an always-on blue border + blue text). Transparent at rest
   (a 1px transparent border keeps the same box size so nothing shifts on
   hover/focus), normal text color, a subtle gray tint on hover — accent blue
   is reserved for the one primary action, true active/selected state, and
   prose links, none of which this class is ever used for.
   TIER 3 of the button hierarchy (CR-011 DS-3): text only, no box. The hit
   area is now a full --control-h-sm (28px) rather than the 21px the audit
   measured, which sat under the WCAG 2.5.8 24px target floor — the label
   still reads quiet, only the clickable box grew. */
.btn-ghost-sm { display: inline-flex; align-items: center; justify-content: center; gap: var(--btn-icon-gap);
  margin-top: 0; /* the base button{margin-top:1rem} leak — it sat this tier 16px below its own row (calls log) */
  min-height: var(--control-h-sm); font-size: var(--fs-2); font-weight: 600; padding: 0 0.6rem;
  border: 1px solid transparent; border-radius: var(--radius); background: none; color: var(--text-2);
  box-shadow: none; /* the base button shadow — TIER 3 is "text only, no box" */
  text-decoration: none; vertical-align: middle; }
.btn-ghost-sm:hover { background: var(--hover); color: var(--text); }
.btn-ghost-sm:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.timeline { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.timeline li { display: flex; gap: 0.7rem; padding: 0.5rem 0.2rem; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
/* Timeline row = TWO columns (owner ask 2026-08-01, was three): one narrow
   meta column stacking date / time / direction+kind chip vertically, then the
   content taking the rest. .tstamp/.ticon survive as the stacked lines inside
   .tl-meta (same classes, new geometry) so partials share one structure. */
.timeline .tl-meta { display: flex; flex-direction: column; align-items: flex-start; gap: 0.2rem;
  width: 7.5rem; flex-shrink: 0; }
.timeline .tstamp { color: var(--muted); font-size: 0.78rem; }
.timeline .tl-time { display: block; color: var(--muted); font-size: 0.78rem; }
.timeline .ticon { max-width: 100%; }

/* ===== Timeline direction system (owner-approved mockup, 2026-07-20) =====
   Three signals working together: (1) a chip's color + diagonal arrow (the
   phone-app convention, ↗ out / ↙ in) + channel glyph say who-contacted-whom
   at a glance — channel-agnostic on purpose (.tl-chip + .dir-* + any icon),
   so email (a separate in-flight PR) inherits this for free with zero new
   CSS; (2) a barely-there row wash marks where the CONTACT responded; (3)
   truthful attribution — inbound items are the contact speaking, styled with
   a mild green emphasis instead of the old (wrong) "system". Colors reuse
   the exact .chip-accent/.chip-ok/.chip-danger tokens so they harmonize with
   the rest of the app (same blue family as Messaging's outbound bubbles). */
.tl-chip { display: inline-flex; align-items: center; gap: 0.2rem; }
.tl-chip .ic { width: 0.85em; height: 0.85em; }
.tl-chip.dir-out { background: var(--accent-soft); color: var(--accent-dark); border-color: color-mix(in srgb, var(--accent) 28%, var(--surface)); }
.tl-chip.dir-in { background: var(--ok-bg); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 28%, var(--surface)); }
.tl-chip.dir-missed { background: var(--error-bg); color: var(--error-text); border-color: color-mix(in srgb, var(--error-text) 28%, var(--surface)); }
/* Inbound row wash — "where did they respond" at a glance. A missed call
   stays on the plain background: its red chip + red body text already carry
   the (opposite) signal, and a green wash there would fight it. */
.timeline li.tl-row-in { background: color-mix(in srgb, var(--ok) 6%, transparent); }
/* Truthful attribution: small + medium-weight + dark-green, distinct from
   the muted staff/system byline it replaces for inbound entries. */
.tl-author-contact { color: var(--ok); font-weight: 600; }
/* Missed-call body copy ("Patient → office · missed call") — red-tinted,
   same semantic as the dir-missed chip above. */
.tl-missed-text { color: var(--error-text); }

/* ===== Kind color language (owner ask, messaging round 2, 2026-07-21) =====
   ON TOP of the direction system above: notes get a muted YELLOW background,
   emails a muted GREY background, everywhere they render — timeline rows
   AND thread email bubbles/cards — so the KIND of an entry reads at a glance
   too, independent of direction. Dedicated seed hues (not the --s1..--s12
   categorical presets, which stay reserved for list options/tags/chart
   series, owner rule 2026-07-12) mixed at a subtle 4-5% into --surface, same
   color-mix formula .tl-row-in above already uses, with a matching (~20-24%)
   hairline border for anything individually boxed (bubbles/pills) — a plain
   timeline row instead tints its own existing bottom rule to match. Ordering
   matters here: this block must reach every .tl-row-note/.tl-row-email/
   .msg.email rule below, or a same-specificity tie elsewhere in the file
   would win by source order instead. */
:root {
  --note-hue: #c99a1e;   /* muted gold/yellow seed — notes */
  --note-bg: color-mix(in srgb, var(--note-hue) 5%, var(--surface));
  --note-border: color-mix(in srgb, var(--note-hue) 24%, var(--surface));
  --email-hue: var(--muted);  /* neutral slate seed — emails */
  --email-bg: color-mix(in srgb, var(--email-hue) 4%, var(--surface));
  --email-border: color-mix(in srgb, var(--email-hue) 20%, var(--surface));
  /* Calls get their OWN orange kind identity (owner decision, 2026-07-22):
     distinct from notes yellow / emails grey / texts blue. A dedicated orange
     seed (not an --s* categorical preset, same rule as the two hues above)
     mixed at the identical subtle 5% into --surface — a #fff7ed-family wash in
     light, and theme-aware for free (it follows --surface/--text in dark). */
  --call-hue: #e8730c;   /* muted orange seed — calls */
  --call-bg: color-mix(in srgb, var(--call-hue) 5%, var(--surface));
  --call-border: color-mix(in srgb, var(--call-hue) 24%, var(--surface));
  /* Recorded exams (exam-recorder integration, 2026-08-01) get their own teal
     seed on the identical 5%/24% formula — the visit itself is a different
     kind of event from a note about it, and the row must be findable at a
     glance in a long stream. Theme-aware for free (it follows --surface). */
  --exam-hue: #0f8f8f;   /* muted teal seed — recorded exams */
  --exam-bg: color-mix(in srgb, var(--exam-hue) 5%, var(--surface));
  --exam-border: color-mix(in srgb, var(--exam-hue) 24%, var(--surface));
}
.timeline li.tl-row-note { background: var(--note-bg); border-bottom-color: var(--note-border); }
.timeline li.tl-row-email { background: var(--email-bg); border-bottom-color: var(--email-border); }
/* Call rows read orange (the kind color) — a missed call is deliberately NOT
   given this wash (its red chip + red body text carry the alert instead; the
   template only tags non-missed calls tl-row-call). */
.timeline li.tl-row-call { background: var(--call-bg); border-bottom-color: var(--call-border); }
.timeline li.tl-row-exam { background: var(--exam-bg); border-bottom-color: var(--exam-border); }
/* A draft timeline entry (is_draft) is also individually clickable — signal
   it like any other row control. */
.timeline li[role="button"] { cursor: pointer; }
.timeline li.tl-row-email[role="button"]:hover { background: color-mix(in srgb, var(--email-hue) 8%, var(--surface)); }
/* Timeline content column (a block flex item, was a bare inline span): its
   children — the subject text, the ✨ summary, the email actions — stack
   in-flow; min-width 0 lets a long subject wrap instead of overflowing. */
.timeline .tl-content { flex: 1; min-width: 0; }
/* The email ✨ summary on the timeline: its OWN block under the subject, full
   column width — never absolutely positioned, so it can never overlap the
   subject or bleed across rows (the row grows to contain it). */
.timeline .tl-ai-summary { display: block; margin: 0.4rem 0 0.15rem; }
.timeline .tl-email-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.3rem; }
.timeline .tl-email-actions button { font-size: 0.78rem; }

/* ===== Recorded-exam card on the timeline (exam-recorder integration) =======
   The container is the SHARED .ai-box + .ai-tag (this IS AI output — the owner
   standard for "every AI output reads as one system"); everything below adds
   only this card's own spacing and its collapsed/expanded shape.

   COLLAPSED is three lines — treatment recommended · suspected barrier · where
   it ended — and expanding reveals the full field list. There is deliberately
   no transcript control anywhere in this component: the transcript is never
   staff-visible, so there is nothing here to link to. */
.timeline .tl-exam-card { display: block; margin: 0.4rem 0 0.15rem; }
.tl-exam-card .exam-brief { list-style: none; margin: 0.35rem 0 0; padding: 0; display: grid; gap: 0.15rem; font-size: 0.85rem; }
/* A quiet leading dot per line, matching the "·"-separated phrasing of the
   spec without hardcoding a bullet character in the markup. */
.tl-exam-card .exam-brief li { position: relative; padding-left: 0.85rem; }
.tl-exam-card .exam-brief li::before { content: "·"; position: absolute; left: 0.25rem; color: var(--ai-text); font-weight: 700; }
.tl-exam-card .exam-full { margin-top: 0.4rem; }
.tl-exam-card .exam-fields { margin: 0.35rem 0 0; display: grid; grid-template-columns: minmax(8rem, max-content) 1fr; gap: 0.15rem 0.75rem; font-size: 0.85rem; }
.tl-exam-card .exam-fields dt { color: var(--muted); font-weight: 600; }
.tl-exam-card .exam-fields dd { margin: 0; }
/* One column on a narrow viewport — a two-column dl with an 8rem label track
   squeezes the value column to nothing on a phone. */
@media (max-width: 40rem) {
  .tl-exam-card .exam-fields { grid-template-columns: 1fr; }
  .tl-exam-card .exam-fields dd { margin-bottom: 0.3rem; }
}
/* The quiet "Draft" pill (thread bubble + timeline row) — dashed border
   echoes .chip-scheduled's own "not real yet" language below. */
.chip-draft { background: var(--surface); color: var(--muted); border: 1px dashed var(--border); font-weight: 600; }

/* A <summary> disclosure toggle (e.g. "Exam record", "More options") is a
   secondary control, not a prose link — quiet text color, same as
   button.linklike above, not the old accent-blue. */
details > summary.linklike { cursor: pointer; color: var(--muted); font-size: 0.85rem; }
details > summary.linklike:hover { color: var(--text); }
details > summary.linklike:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A bare <a class="linklike"> (no button.linklike/.action-list ancestor to
   style it) fell through to the unstyled browser-default link blue — give it
   the same quiet treatment as its button counterpart. */
a.linklike { color: var(--muted); text-decoration: underline; }
a.linklike:hover { color: var(--text); }

/* Record-name links (UI polish 3, owner ask 2026-07-20: "after you click on
   a contact it turns purple … there shouldn't be a clicked link color …
   why are we underlining the name — looks like 1990s internet"). Shared by
   every link whose visible content IS a person/record's name — the contacts
   list, board cards, appointments, messaging thread rows + the conversation
   header, new-message search results — so :link and :visited always share
   ONE color (the surrounding text, never the browser's blue/purple
   default) and never an underline in any state. Hover stays discoverable via
   a quiet color shift (a couple of these spots already add their own
   background tint too — fine, belt and suspenders); :focus-visible gets a
   real ring, which none of these had before. Prose links inside wiki/help
   copy are NOT record-name links and keep their normal underlined
   treatment — this class is opt-in, never a blanket `a` reset. */
.rec-link, .rec-link:visited { color: inherit; text-decoration: none; }
.rec-link:hover { color: var(--accent-dark); }
.rec-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* Summary controls that already carry their own leading glyph (⋯, +, an
   icon) don't need the native ▶ disclosure triangle too — it's a duplicate
   marker (owner ask 2026-07-20: "+ Add patient" showed both, inside a blue
   outline box). Opt in per-summary; plain-text summaries above keep the
   native triangle as their only expand/collapse affordance. */
summary.no-marker { list-style: none; }
summary.no-marker::-webkit-details-marker { display: none; }

/* A2 boards (approved wireframes pipeline-*.html). Independent column
   scrolling (owner ask 2026-07-20): .appshell caps to the viewport whenever
   a .board is present — same trick as Messaging's .msgapp — so .page can
   flex-fill the remaining height below the h1 + filter row and .board can
   flex-fill THAT; each .col stretches to fill .board's height in turn. The
   <h3> is a normal sibling BEFORE .board-col (never a descendant of the
   scrolling box), so it can't scroll away without any position:sticky
   trick — .board-col is the only element that actually scrolls. */
.appshell:has(.board) { height: 100vh; }
.page:has(.board) { display: flex; flex-direction: column; width: 100%; min-width: 0; min-height: 0; flex: 1; }
/* min-width:0 (same reasoning as the min-height:0 chain below): .page is now
   a flex container, and .board's own non-wrapping row of columns gives it a
   large content-based "automatic minimum width" that can otherwise stop it
   shrinking to the page's actual width — the page would grow wider than the
   viewport instead of .board scrolling horizontally inside itself (only
   visible once columns don't all fit, i.e. narrow/mobile widths). */
/* CR-011 UX-F1 (Critical): the lane container scrolled, but INVISIBLY. macOS
   overlay scrollbars paint nothing until a scroll gesture, so at 1024 with the
   rail pinned the third Leads column (measured: right edge x=1288 against a
   1004px lane) was silently clipped with no scrollbar, no drag handle and no
   wrap — Interested, the one stage with no Autopilot, simply looked absent.
   Fixes, all of which keep the owner's fixed 21.25rem column width:
     · styling ::-webkit-scrollbar below opts this box OUT of overlay scrollbars,
       so a real, always-visible bar appears the moment content overflows;
     · scrollbar-gutter: stable reserves the track so adding/removing a column
       never shifts the cards sideways;
     · scroll-snap lands each column flush at the left edge when scrolled. */
.board { display: flex; gap: 0.75rem; align-items: stretch; overflow-x: auto; flex: 1; min-width: 0; min-height: 0; padding-bottom: 0.25rem;
  scroll-snap-type: x proximity; scrollbar-gutter: stable; overscroll-behavior-x: contain; }
.board::-webkit-scrollbar { height: 10px; }
.board::-webkit-scrollbar-track { background: var(--bg-inset, rgba(0,0,0,0.03)); border-radius: var(--radius-pill); }
.board::-webkit-scrollbar-thumb { background: var(--faint); border-radius: var(--radius-pill); }
.board::-webkit-scrollbar-thumb:hover { background: var(--muted); }
/* Columns are a FIXED width (UI polish 3, owner ask 2026-07-20: the previous
   flex-grow pass — "make cards wider so they fill most of the window" — made
   Leads' 3-column board stretch too wide on a big monitor, while Follow-up's
   4-column board, whose MINIMUM width this already was, read "pretty
   perfect"; a fixed width for every column on both boards, at every window
   size, is the fix owner asked for). flex:0 0 21.25rem pins the width
   (grow:0, shrink:0) — no min/max needed, since a fixed basis with no
   shrink can never go below or above it. Leftover space past the last
   column just stays empty; .board's own overflow-x:auto (above) still
   scrolls horizontally once columns don't all fit (narrow/mobile widths). */
.board .col { flex: 0 0 21.25rem; display: flex; flex-direction: column; min-height: 0; scroll-snap-align: start; }
/* Colored group-header pill, count INSIDE it (W-BD, ClickUp grammar, owner-
   approved 2026-08-03) — replaces the old plain <h3> + a separate
   .count-pill beside it (UXD-71's rendering stays true on the dashboards;
   the boards get their OWN grammar now, keyed to the same --c-<key> stage
   tokens `pipelines.stages._STAGE_COLOR` already assigns per stage, so a
   stage wears one hue on the board, the patient pill and the chart alike). */
.bd-head { display: flex; align-items: center; margin: 0 0.35rem 0.5rem; flex: 0 0 auto; }
.bd-gpill { display: inline-flex; align-items: center; height: 1.4rem; padding: 0 0.65rem;
  border-radius: var(--radius); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.04em;
  text-transform: uppercase; color: #fff; }
.bd-gpill-count { font-weight: 800; }
.bd-gpill-new { background: var(--c-new); }
.bd-gpill-lead { background: var(--c-lead); }
.bd-gpill-pend { background: var(--c-pend); }
.bd-gpill-obs { background: var(--c-obs); }
.bd-gpill-cxexam { background: var(--c-cxexam); }
.bd-gpill-cxcon { background: var(--c-cxcon); }
/* Soft column tint (owner: "4-5% column tints, using the EXISTING stage
   tokens") — color-mix keeps it theme-safe (mixes against the column's own
   background, not a hardcoded pastel) rather than a second hardcoded set of
   `-bg` hexes. */
.col.bd-tint-new .board-col { background: color-mix(in srgb, var(--c-new) 5%, var(--bg-inset)); }
.col.bd-tint-lead .board-col { background: color-mix(in srgb, var(--c-lead) 5%, var(--bg-inset)); }
.col.bd-tint-pend .board-col { background: color-mix(in srgb, var(--c-pend) 5%, var(--bg-inset)); }
.col.bd-tint-obs .board-col { background: color-mix(in srgb, var(--c-obs) 5%, var(--bg-inset)); }
.col.bd-tint-cxexam .board-col { background: color-mix(in srgb, var(--c-cxexam) 5%, var(--bg-inset)); }
.col.bd-tint-cxcon .board-col { background: color-mix(in srgb, var(--c-cxcon) 5%, var(--bg-inset)); }
/* Item 10: the board's optional "recently closed" section, toggled by the
   include-closed chip — a simple list, not another kanban column. */
.board-closed { margin-top: 0.75rem; padding: 0.6rem 0.75rem; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); }
.board-closed h3 { margin: 0 0 0.5rem; font-size: 0.9rem; }
.board-closed-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.board-closed-list li { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
/* Was `justify-content: safe center` above 1120px, so a board narrower than
   the viewport (Leads' 3 columns especially) floated to the middle of the
   page while the h1 + search-row above it stayed left-aligned — nothing
   above the board lined up with anything in it (owner walkthrough
   2026-07-27, item A: "the whole top part is out of alignment... I think
   it's because we have aligned left the heading... but then align center
   everything else"). ONE rule now, chosen over centring the header to match:
   left-align is what makes the first column's left edge equal the title's
   left edge on EVERY board regardless of column count (3 on Leads, 4 on
   Follow-up) — centring the header instead would need a per-board computed
   width to match, which is exactly the kind of thing that drifts out of
   sync the next time a column is added. flex's default `justify-content:
   flex-start` already does this, so the fix is deleting the override, not
   adding one. */
.board-col { flex: 1; min-height: 8rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.6rem; padding: 0.25rem; border-radius: var(--radius-lg); background: var(--bg-inset, rgba(0,0,0,0.03)); }
/* Meridian card (decisions.md 2026-07-14): same radius-lg + --shadow lift as
   the dashboard .kpi tile — border stays (matches .kpi's own border+shadow
   balance), the shadow does the lifting instead of a heavier hairline.
   flex:0 0 auto (Ocean pass 2026-07-20): never let a scrolling column
   SHRINK cards to fit — overflow should scroll, not squeeze them. */
.board-card { background: var(--card, #fff); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: 0.7rem 0.75rem; cursor: grab; flex: 0 0 auto; }
/* Urgency wash (W-BD, owner ruling 2026-08-03: №2 of the six alternatives to
   a red left/top edge bar) — reserved for a reply/emergency and a fresh
   `new`-stage lead waiting past the speed-to-lead goal. EVERY other past-due
   card carries its urgency on the chip alone (`.dchip-solid` below); no
   left-edge, top-edge, or border-color treatment survives from the old
   design anywhere on the card. */
.board-card.card-wash { background: color-mix(in srgb, var(--danger) 4%, var(--surface));
  border-color: color-mix(in srgb, var(--danger) 22%, var(--border)); }
.board-card .card-link { display: flex; flex-direction: column; gap: 0.15rem; }
.sortable-ghost { opacity: 0.4; }

/* ===================================================================
   BOARD CARD — the 5-row grammar (W-BD, Plan/ux-review-decisions-2026-
   08-03.md §H/H2). Row KINDS are fixed (owner ruling: "kinds of facts stay
   together"): 1 name + corner cluster · 2 contact · 3 work-it chips ·
   4 who-they-are chips (only row that collapses when empty) · 5 action /
   claim lock. Reused verbatim by the dashboard rollout (W-DW) via the same
   `partials/_chips.html` renderer + `app/pipelines/chips.py` derivations.
   =================================================================== */
.bc-row1 { display: flex; align-items: center; gap: 0.4rem; }
.bc-name { font-size: 0.95rem; font-weight: 700; flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The corner cluster: chart # (or nothing, pre-booking) + the Autopilot "A"
   badge (or nothing) — one badge-height row, right-aligned, never wrapping
   under the name (owner ruling: "badge-height, aligned"). NO "!" badge
   anywhere — the solid red chip (row 3) is the one past-due signal. */
.bc-corner { display: inline-flex; align-items: center; gap: 0.25rem; flex: 0 0 auto; height: 17px; }
.bc-corner .dchip { height: 17px; box-sizing: border-box; }
/* The Autopilot corner badge — a single letter, 17px, distinct from the
   system-wide wide "Auto" pill (partials/_pills.html `autopilot()`, still
   used on the patient record/contact panel): the board card's corner
   cluster supersedes it on THIS surface only (owner ruling 2026-08-03). */
.corner-auto { display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; border-radius: 5px; box-sizing: border-box;
  background: var(--accent-soft); color: var(--accent-dark); font-size: 0.62rem; font-weight: 800; }
/* Row 2 — 👤 contact (responsible party) first name · source. Same-person
   collapse (identity system, partials/_name_block.html) drops the icon+name
   when the contact IS the patient; the row itself always stays in the DOM
   (empty when neither a contact nor a source apply) — the row KIND is
   fixed, its content is not. */
.bc-row2 { display: flex; align-items: center; gap: 0.3rem; font-size: 0.78rem; color: var(--muted);
  min-height: 1rem; }
.bc-row2 .idglyph { display: inline-flex; color: var(--faint); flex: 0 0 auto; }
.bc-row2 .idglyph .ic { width: 0.85rem; height: 0.85rem; }
/* Rows 3 + 4 — the icon-property chips (variant A, owner-approved). Wraps
   at narrow column widths rather than clipping or forcing card height. */
.bc-row3, .bc-row4 { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
.bc-row2, .bc-row3 { margin-bottom: 0.4rem; }
.bc-row4 { margin-bottom: 0.4rem; }
.bc-row5 { margin-top: 0.1rem; }
/* Claim lock replaces the action row (cv-2 ruling: the board shows claims —
   nobody double-works a lead someone else is holding). */
.bc-row5 .dchip-lock { width: 100%; justify-content: center; padding-block: 0.3rem; }

/* ===================================================================
   THE CHIP RENDERER (`.dchip`, app/templates/partials/_chips.html) — one
   compact icon-property chip family, built on the app's EXISTING semantic
   and stage tokens (never a new raw color), so a fact wears the identical
   hue wherever it appears (the board card today; a dashboard row, W-DW,
   next). Default (no modifier) reads as a quiet neutral fact; variants
   carry emphasis.
   =================================================================== */
.dchip { display: inline-flex; align-items: center; gap: 0.2rem; font-size: 0.7rem; font-weight: 650;
  line-height: 1.5; padding: 0.05rem 0.4rem; border-radius: var(--radius); border: 1px solid var(--border-soft);
  background: var(--bg-inset); color: var(--text-2); white-space: nowrap; }
.dchip .ic { width: 0.7rem; height: 0.7rem; }
/* №3: the filled data chip — the ONE past-due signal (solid red fill says
   "late", the visible text is the day count alone, no "past due" words —
   the full sentence rides the title/hover). Also the "none set" coverage
   alert and the "replied" flag. */
.dchip-solid { background: var(--danger); border-color: var(--danger); color: #fff; }
/* Chart # on file — quiet mono, distinct from the amber missing-alert. */
.dchip-quiet { color: var(--muted); background: transparent; border-color: var(--border-soft);
  font-family: var(--font-mono); font-size: 0.68rem; }
.dchip-amber { color: var(--warn-text); background: var(--warn-bg); border-color: transparent; }
.dchip-hot { color: var(--pill-hot-tx); background: var(--pill-hot-bg); border-color: transparent; }
.dchip-warm { color: var(--pill-warm-tx); background: var(--pill-warm-bg); border-color: transparent; }
.dchip-cold { color: var(--pill-cold-tx); background: var(--pill-cold-bg); border-color: transparent; }
.dchip-flag { color: var(--danger); background: transparent; border-color: transparent; padding-left: 0; }
.dchip-lock { color: var(--c-longterm-tx); background: var(--c-longterm-bg); border-color: transparent; }
.dchip-live { color: var(--ok); background: var(--ok-bg); border-color: transparent; }
.dchip-offer { color: var(--text-2); background: var(--bg-inset); border-color: var(--border-soft); font-weight: 700; }
/* Proposal state — reuses proposal_pill.py's OWN color keys (draft→lead,
   sent→exam, viewed→consult, accepted→new, declined→lost, expired→dormant)
   so the board chip and the patient panel's `.pl-<key>` pill share one hue
   per state ("same fact, same chip"). */
.dchip-prop-new { color: var(--c-new-tx); background: var(--c-new-bg); border-color: transparent; }
.dchip-prop-lead { color: var(--c-lead-tx); background: var(--c-lead-bg); border-color: transparent; }
.dchip-prop-exam { color: var(--c-exam-tx); background: var(--c-exam-bg); border-color: transparent; }
.dchip-prop-consult { color: var(--c-consult-tx); background: var(--c-consult-bg); border-color: transparent; }
.dchip-prop-lost { color: var(--muted); background: var(--bg-inset); border-color: transparent; }
.dchip-prop-dormant { color: var(--c-dormant-tx); background: var(--c-dormant-bg); border-color: transparent; }
/* Appointment-type corner chip (W-DW, dashboard appointments group ONLY —
   boards never render appt_scheduled). Same stage-color custom properties
   `.dchip-prop-exam`/`-consult` already use; a distinct class name only for
   readability of the two different facts sharing one palette. */
.dchip-type-exam { color: var(--c-exam-tx); background: var(--c-exam-bg); border-color: transparent; }
.dchip-type-consult { color: var(--c-consult-tx); background: var(--c-consult-bg); border-color: transparent; }
.dchip-type-obs { color: var(--c-obs-tx); background: var(--c-obs-bg); border-color: transparent; }

/* Stage-aware Action menu (stage-transitions.md smart actions) */
.action-menu { position: relative; margin-top: 0.35rem; }
.action-menu > summary { list-style: none; cursor: pointer; display: inline-block; margin: 0; } /* margin: 0 counters the global button margin leak, all mounts */
.action-menu > summary::-webkit-details-marker { display: none; }
.action-list { position: absolute; z-index: 30; min-width: 15rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 0.35rem 0.5rem; box-shadow: var(--pop-shadow); display: flex; flex-direction: column; gap: 0.15rem; }
.action-list form { margin: 0; }
/* Plain menu rows (owner ask 2026-07-20, boards ▾ menu screenshot): reset the
   border/shadow the .linklike base fix above already strips, drop the
   forced underline, and use normal text color instead of link-blue — a
   quiet hover tint is what says "clickable" here, not an outline. Keyboard
   focus still gets a visible ring via :focus-visible. */
.action-list a, .action-list button.linklike {
  border: 1px solid transparent;
  background: none;
  box-shadow: none;
  text-decoration: none;
  color: var(--text-2);
  border-radius: var(--radius);
  font-size: 0.85rem;
}
.action-list a:hover, .action-list button.linklike:hover { background: var(--hover); color: var(--text); }
.action-list a:focus-visible, .action-list button.linklike:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* One action per full-width line — except the inline lost-reason row (select
   + ✗), which keeps its compact side-by-side layout. `.action-list > button`
   (messaging thread-row/header menu, CR-007 A4): a bare button with no form
   wrapper at all — most of its five actions are pure JS triggers (open a
   modal, navigate), not a real submit — so it needs the same full-width
   treatment the form-wrapped case already gets. */
.action-list > a, .action-list > button.linklike, .action-list form:not(.lost-inline) button.linklike { display: block; width: 100%; padding: 0.3rem 0.5rem; text-align: left; }
.action-list .lost-inline { display: flex; gap: 0.3rem; align-items: center; }
.action-list .lost-inline select { font-size: 0.8rem; max-width: 12rem; }
.action-list .lost-inline button.linklike { padding: 0.15rem 0.4rem; }
/* Early Ortho2 chart# capture (integrations.md §6): the compact optional input
   on booking actions + the deal panel's inline Chart # edit. In the primary
   action-controls row the input drops to its own quiet line UNDER the
   [primary][▾] pair, so the app-wide button standard survives. */
.chartno-input { width: auto; max-width: 11rem; font-size: 0.8rem; padding: 0.2rem 0.45rem; }
/* TC exam-record capture (integrations.md §6 "Exam records"): the collapsed
   optional block on visit-disposing outcome forms + the deal panel's
   "Exam record" editor. Two-up field grid; single column on phones. */
.exam-block { margin-top: 1rem; border-top: 1px solid var(--border); padding-top: 0.6rem; }
.exam-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 0.9rem; }
.exam-grid select { width: 100%; }
.exam-check { display: flex; gap: 0.45rem; align-items: baseline; font-weight: 400; margin: 0.6rem 0 0.1rem; }
.exam-check input { margin: 0; }
@media (max-width: 600px) { .exam-grid { grid-template-columns: 1fr; } }
/* Post-booking chart# alert badge (decisions.md 2026-07-20): a quiet amber
   warning glyph shown when a patient at/after booking still has no Ortho2
   chart#. Sized to the surrounding text and inline-flex so it never reflows a
   card/row (owner: "a little alert badge you hover over"); the hover text is a
   native title. Supersedes the old .book-form booking-input layout (the chart#
   is no longer collected inline on cards — chart# is a soft alert, not a gate). */
/* CR-011 UX-F11: the chart# warning is now focusable and carries a visible
   "No chart #" text badge, so it no longer depends on a mouse hover. Amber
   tokens only — this is a warning, never the accent. */
.chart-alert { display: inline-flex; align-items: center; gap: 0.2rem; color: var(--warn-text); vertical-align: middle;
  /* margin-left, not a template space: the macro is called flush against the
     patient name on every surface that uses it. */
  margin-left: 0.35rem;
  padding: 0 0.3rem; border: 1px solid var(--warn-text); border-radius: var(--radius); background: var(--warn-bg);
  font-size: 0.7rem; font-weight: 600; line-height: 1.6; white-space: nowrap; }
.chart-alert:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chart-alert .ic { width: 0.95em; height: 0.95em; }
.bad-phone-caution { display: inline-flex; align-items: center; gap: 0.2rem; color: var(--warn-text); font-size: 0.75rem; font-weight: 650; vertical-align: middle; white-space: nowrap; }
.bad-phone-caution .ic { width: 0.95em; height: 0.95em; }
.na-correction-link { display: inline-flex; align-items: center; gap: 0.2rem; margin-left: 0.45rem; font-weight: 650; }
.na-correction-link .ic { width: 0.9em; height: 0.9em; }
.na-sms-actions { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.45rem; }
/* App-wide action-button standard (Skyler 2026-07-04): a primary button for
   the #1 action + a square ▾ "more" button of the same height for the rest.
   Ocean pass (2026-07-20): both now stay INSIDE the card on any column width
   — the primary grows/shrinks and truncates its label (min-width:0 +
   ellipsis) while the ▾ holds a fixed size, so a long stage label can no
   longer push the ▾ past the card's edge (the reported overflow/misalign). */
.action-controls { display: flex; gap: 0.4rem; align-items: center; margin-top: 0.35rem; }
.action-controls button { margin: 0; } /* the global button{margin-top:1rem} leak */
.action-menu[open] > summary { outline: none; } /* clicked-open never rings; keyboard ring stays when closed */
.action-controls .action-menu { margin-top: 0; flex: 0 0 auto; display: flex; align-items: stretch; }
.action-controls form { margin: 0; flex: 1 1 auto; min-width: 0; display: flex; }
/* No explicit width here on purpose: flex:1 1 auto with an auto (content)
   basis is what lets this work in BOTH shapes it renders in — nested inside
   a normal <form> (the form itself flex-grows/shrinks, above) and, in the
   book-form/display:contents case, as a bare flex item of .action-controls
   directly. A width:100% here would resolve against .action-controls itself
   in the second shape and swallow the whole row, wrapping the ▾ onto its
   own line (caught live on the Interested "Scheduled exam" card). */
.action-primary {
  /* White, not accent-filled: a board of solid-blue buttons shouted
     (owner, 2026-07-21 "the blue is too much"). Quiet card primaries;
     accent stays reserved for true one-per-view primaries + active state.
     Content-sized on EVERY surface now (owner ask 2026-08-01: the board
     cards' buttons were "too wide" — same consistency as the dashboard
     ones): flex 0 1 auto sizes to the label, shrinking with ellipsis before
     ever pushing the ⋯ off the row. Height follows the one shorter button
     standard (--control-h-sm). */
  flex: 0 1 auto; min-width: 0; max-width: none; height: var(--control-h-sm); box-sizing: border-box;
  font-size: var(--fs-2); font-weight: 600; padding: 0 0.7rem;
  border: 1px solid var(--border-strong, var(--border)); border-radius: var(--radius);
  background: var(--surface, #fff); color: var(--text); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-decoration: none; display: inline-flex; align-items: center; justify-content: center;
  /* A NEUTRAL lift, not the accent one. The accent-tinted shadow is part of
     the primary tier's recipe; on a white card button it read as a faint blue
     outline, which is exactly the "no blue-outline controls" rule. */
  box-shadow: 0 1px 2px rgba(60, 66, 87, 0.10);
}
.action-primary:hover { background: var(--hover); color: var(--text); }
/* (Owner walkthrough 2026-07-27, item C4): `.action-primary` used to be
   flex:1 — right on a board CARD, where "its row" is a ~340px card and
   filling it is correct, wrong in every fixed-width actions column, where it
   stretched the button across the whole cell. Content-sizing is the base rule
   now, so the Appointments book's `.lv-acts` cell and the dashboard
   worklist's dense row both get it for free: the primary shrinks with
   ellipsis before the fixed-width ⋯ and never grows past its label. */
/* (2026-08-01: the old `.table .action-primary { flex: 0 1 auto }` override is
   gone — content-sizing is the base rule now.) */
/* The board-card ⋯ always anchors to the card's bottom-RIGHT corner (owner
   ask 2026-08-01), independent of how wide the primary beside it is. */
.board-card .action-controls .action-menu { margin-left: auto; }
/* Quiet ghost ▾ toggle (owner ask 2026-07-20): no border in the rest state —
   a transparent 1px border (not border:0) keeps the exact same box size as
   the [open] state below, so it never shifts/resizes when it activates.
   UI polish 3 (owner: the ▾ sat off-center, same screenshot family as the
   board ⋯ button): requalified the same way .card-menu-toggle below already
   is — `.action-menu > summary` above (a class + a type selector, 0,1,1)
   beat this rule's lone `.action-more` class (0,1,0), so display:inline-flex
   never won the cascade and the glyph fell back to plain inline-box
   centering instead of true flex centering. The [open] state also drops its
   accent-blue fill for the same quiet gray tint .card-menu-toggle uses. */
.action-menu > summary.action-more { width: 1.9rem; flex: 0 0 auto; padding: 0.28rem 0; border: 1px solid transparent; border-radius: var(--radius); color: var(--muted); background: none; font-size: 0.85rem; text-align: center; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
.action-more:hover { background: var(--hover); color: var(--text); }
.action-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.action-menu[open] .action-more { background: var(--border); color: var(--text); }

/* Autopilot state now renders through the ONE pill system (.pill .pill-neutral
   .pill-bare, partials/_pills.html): the accent-filled AUTO badge was the most
   saturated element on the Leads board and appeared on 11 of 14 cards, so the
   eye was drawn to the least actionable thing on the page (CR-011 UXD-68/29). */

/* Card-corner ⋯ menu toggle (board polish round 2, 2026-07-20): same quiet-
   control standard as .action-more (ghost at rest, hover tint, focus-visible
   ring) sized as a proper ~28x28 hit area instead of a thin rectangle, since
   it now stands alone in the card's top-right corner rather than riding
   beside the primary button.
   UI polish 3 (owner: the dots sat off-center, and the open state's accent
   blue was "kind of odd"): the base rule below is now qualified the same
   way .action-menu > summary is (a class + a child combinator) rather than
   a lone class, because the LONE-class version never actually won the
   cascade — `.action-menu > summary { display: inline-block }` above has
   higher specificity (a class plus a type selector beats a single class)
   and was silently overriding this rule's display:inline-flex every time,
   so align-items/justify-content were dead declarations and the "⋯" fell
   back to plain inline-box text positioning (line-height/text-align) instead
   of true flex centering — that's what actually put it off-center. The
   [open] state also drops its accent fill for a gray tint a shade stronger
   than hover, matching the quiet-control standard used everywhere else. */
.action-menu > summary.card-menu-toggle {
  /* Same fixed control height as .action-primary (stretch overshot the row;
     owner 2026-07-21: "same vertical height as the action button, same row"
     — both now on the shorter --control-h-sm standard, 2026-08-01). */
  width: var(--control-h-sm); height: var(--control-h-sm); align-self: center;
  flex-shrink: 0; box-sizing: border-box;
  border: 1px solid transparent; border-radius: var(--radius);
  background: none; color: var(--muted); font-size: 1rem; line-height: 1;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
}
.card-menu-toggle:hover { background: var(--hover); color: var(--text); }
.card-menu-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.action-menu[open] .card-menu-toggle { background: var(--border); color: var(--text); }

/* Owner walkthrough 2026-07-27 (item C1): the Coverage gaps table's "Set call
   reminder" <summary class="btn-sm no-marker"> hit the exact same cascade bug
   documented above — `.action-menu > summary` (a class + a type selector)
   outranks the lone `.btn-sm` class, so display:inline-flex from .btn-sm never
   won and the icon+label fell back to inline-block's text-only centering.
   Same fix, same shape: requalify with the child combinator so this summary
   wins back its own display/alignment. */
.action-menu > summary.btn-sm { display: inline-flex; align-items: center; justify-content: center; }

/* Unified patient card — the ONE compact patient representation (decisions.md
   2026-07-05): the contact view's patient list and the Messaging context card
   both render `prospects/_patient_card.html` with these classes. Identity row
   (name + temp dot + Autopilot pill) over a meta row (stage pill + urgency). */
.pcard { display: block; padding: 0.5rem 0.6rem; border: 1px solid var(--border); border-radius: var(--radius-lg); margin-bottom: 0.5rem; text-decoration: none; color: var(--text); background: var(--surface); }
.pcard:hover { border-color: var(--accent); }
.pcard-flat { border: 0; padding: 0; margin: 0; background: none; }
.pcard-head { display: flex; align-items: center; gap: 0.4rem; }
.pcard-name { font-weight: 700; font-size: 0.95rem; }
/* Temperature no longer renders as a bare 8px dot in the PIPELINE-STAGE colours
   — see "THE STATUS-PILL SYSTEM" and partials/_pills.html (CR-011 DS-6). */
.pcard-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 0.45rem; margin-top: 0.4rem; }
.pcard-when { font-size: 0.8rem; color: var(--muted); }
.pcard-due { font-size: 0.78rem; color: var(--muted); }
.pcard-due.overdue { color: var(--error-text); font-weight: 600; }
.pcard-pms { font-size: 0.72rem; color: var(--muted); margin-left: auto; }
.pcard-label { color: var(--muted); font-size: 0.8rem; margin-bottom: 0.5rem; }

/* Expand-in-place patient card (JOB1, 2026-07-27; layout redo 2026-07-30):
   the contact record's own patient list — Messaging's context panel never
   sets `expandable`, so it keeps the plain `.pcard` link above untouched.
   The summary is a seamless full-width stack inside the host card (no inner
   stroke — Skyler: "the card should just expand without the outline"); a
   hairline separates stacked siblings, and the footer carries the expand
   chevron bottom-right, the same corner every record card's chevron lives
   in now. */
/* Collapsed, the action row is the bottom line and the corner pair shares it;
   open, the actions hide so the item reserves the corner band instead. */
.pcard-item { position: relative; margin-bottom: 0.6rem; padding-bottom: 0.1rem; }
.pcard-item.is-open { padding-bottom: 2.2rem; }
.pcard-item + .pcard-item { border-top: 1px solid var(--border); padding-top: 0.7rem; }
.pcard-item .pcard-exp { display: block; }
/* Bottom-right corner (Skyler, 2026-07-30; funnel removed 2026-08-01 — the
   per-patient filter view is gone, so the chevron is the one control): the
   chevron expands in place. It pins to the ITEM, so when the panel is open
   it rides down to the bottom of the whole expanded card. Pulled out
   −0.55rem so the inset from the HOST card edge matches every other
   chevron's 0.7rem (host padding 1.25rem − 0.55rem). The item reserves its
   corner (padding-bottom) so content never runs beneath the button. */
.pcard-corner { position: absolute; right: -0.55rem; bottom: 0.1rem; display: flex;
  flex-direction: column; align-items: center; gap: 0.35rem; }
/* Collapsed, the chevron's bottom sits on the action row's line; open, the
   actions are hidden and it drops to the standard corner inset. */
.pcard-item.is-open .pcard-corner { bottom: -0.55rem; }
.pcard-chevron { display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h); height: var(--control-h); margin: 0; border: 0; border-radius: var(--radius);
  background: none; box-shadow: none; color: var(--muted); cursor: pointer; }
.pcard-chevron:hover { background: var(--hover); color: var(--text); }
.pcard-chevron:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Collapsed points DOWN ("this opens downward"); open flips UP. */
.pcard-chevron .ic { transform: rotate(0deg); transition: transform .16s ease; }
.pcard-chevron[aria-expanded="true"] .ic { transform: rotate(180deg); }
/* Chart # rides the flow here rather than right-aligning (the right edge
   belongs to the chevron now); Messaging's compact card keeps the
   original right-aligned chart. */
.pcard-exp .pcard-pms { margin-left: 0; }
/* The collapsed card's bottom row: primary action + ⋯ on the left, cleared
   of the corner pair (funnel + chevron ≈ 4.2rem plus the −0.55rem overhang)
   so all four sit on ONE line. */
.pcard-actions { margin-top: 0.5rem; margin-right: 2.9rem; }
.pcard-actions .action-controls { margin-top: 0; }
/* Open state (expand-is-edit): the summary sheds everything the full panel
   restates — the stage/chart/follow-up row, the head pills, and the action
   row (the panel carries its own) — leaving just the name above the record. */
.pcard-item.is-open .pcard-meta,
.pcard-item.is-open .pcard-actions,
.pcard-item.is-open .pcard-head .pill,
.pcard-item.is-open .pcard-head .pl { display: none; }
.pcard-panel-slot:not([hidden]) { margin: 0.2rem 0 0.7rem; }
/* The lazy-loaded panel renders the SAME fragment the patient page uses — a
   .card.rec-card — but inside the host card it sheds its own chrome so the
   expansion reads as one card opening, not a card inside a card. Its own
   corner chevron hides too: the item's single chevron does the collapsing,
   and the panel arrives already in its autosaving edit state. */
.pcard-panel-slot > .card { border: 0; box-shadow: none; background: none; padding: 0 0 1.9rem; }
.pcard-panel-slot .rec-toggle { display: none; }
/* "Add patient" sits UNDER the patients card — a door to a new record, not
   part of anyone's existing one. */
.addpt-under { margin: -0.4rem 0 0 0.4rem; }
.flush-top { margin-top: 0; }  /* h2 as a card's first child — replaces inline style */

/* In-window modal STANDARD (rule 2026-07-03): blurred + dimmed backdrop,
   background scroll locked (body.modal-open). Reuse .modal-overlay/.modal +
   crmModal.open/close for every popup. */
body.modal-open { overflow: hidden; }
.modal-overlay { position: fixed; inset: 0; background: rgba(15, 23, 32, 0.45); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 100; }
.modal-overlay[hidden] { display: none; }
.modal { background: var(--surface); border-radius: var(--radius-lg); padding: 1.25rem 1.4rem; width: min(30rem, 92vw); box-shadow: var(--pop-shadow); }
.modal h3 { font-size: 1.05rem; }
.modal input, .modal textarea { padding: 0.45rem 0.6rem; }
.modal textarea { resize: vertical; }

/* Shared contact picker (merge/move, 2026-07-20 — owner ask: "this would
   never work when we have 1000+ contacts/deals"): a type-ahead search modal
   with a confirm step, reused by every unbounded contact/patient picker
   instead of an all-rows <select>. Structure: the standard .modal-overlay/
   .modal shell + two panes toggled by [data-stage] on the overlay — search
   (input + live results) and confirm (target name + one plain-language line
   + the destructive submit). See app/templates/contacts/_contact_picker.html
   + GET /contacts/search (app/contacts/routes.py). */
.picker-modal .picker-pane-confirm { display: none; }
.picker-modal[data-stage="confirm"] .picker-pane-search { display: none; }
.picker-modal[data-stage="confirm"] .picker-pane-confirm { display: block; }
.picker-search { margin-bottom: 0.6rem; }
.picker-results { max-height: 46vh; overflow-y: auto; }
.picker-hint { padding: 0.6rem 0.25rem; }
/* Result row: a <button>, reset back to a plain full-width left-aligned list
   row — the base button{} solid-accent treatment doesn't belong here. */
.picker-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  margin: 0; padding: 8px 6px; border: 0; border-radius: var(--radius);
  background: none; color: var(--text); font: inherit; text-align: left;
  box-shadow: none; cursor: pointer;
}
.picker-row:hover, .picker-row.picker-row-active { background: var(--hover); color: inherit; }
.picker-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.picker-row .avatar-c { width: 30px; height: 30px; font-size: 12px; }
.picker-row-body { min-width: 0; display: flex; flex-direction: column; }
.picker-row-name { font-weight: 600; font-size: 13.5px; }
.picker-row-sub { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.picker-target-name { font-weight: 700; }
/* Destructive primary (rule 7: a costly confirm should look as serious as
   the action) — same shape/weight as the default solid button, red instead
   of accent-blue. */
.btn-danger { background: var(--error-text); border-color: var(--error-text);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--error-text) 28%, transparent); }
.btn-danger:hover { background: color-mix(in srgb, var(--error-text) 82%, black); border-color: color-mix(in srgb, var(--error-text) 82%, black); }

/* ===================== A6 role dashboards ===================== */
/* Pipeline color system (ported from the approved dashboards mockup):
   new lead → red · lead-in-progress → orange · exam → green · observation →
   purple · canceled exam → teal · canceled consult → cyan · pending → amber.
   Appointment-book type pills (Wave 2, spec §6.10): exam GREEN · OBS PURPLE ·
   start RED · consult GRAY. `start` is its own hue — a treatment start is the
   highest-stakes visit on the book and used to be coloured as a plain exam. */
:root {
  --c-new:#d64545;      --c-new-bg:#fdecea;      --c-new-tx:#a4251a;
  --c-lead:#e08a1e;     --c-lead-bg:#fdf1dc;     --c-lead-tx:#8a5a0e;
  --c-exam:#1d8a4c;     --c-exam-bg:#e7f5ec;     --c-exam-tx:#146138;
  --c-obs:#7a5bb5;      --c-obs-bg:#f0eafa;      --c-obs-tx:#553a8e;
  --c-cxexam:#0d9488;   --c-cxexam-bg:#e2f4f2;   --c-cxexam-tx:#0a6b62;
  --c-cxcon:#1f9fc9;    --c-cxcon-bg:#e3f3f9;    --c-cxcon-tx:#106688;
  --c-pend:#d9a516;     --c-pend-bg:#fdf6e0;     --c-pend-tx:#8a6510;
  --c-consult:#64748b;  --c-consult-bg:#eef1f5;  --c-consult-tx:#475569;
  --c-start:#cc4b44;    --c-start-bg:#fceceb;    --c-start-tx:#9c2f29;
  /* Long-tail campaign markers (Campaigns PR-1): muted slate tones — a calm
     "long tail / low urgency" read, distinct from the vivid stage colors. */
  --c-longterm:#5b7a9c; --c-longterm-bg:#eaf0f6; --c-longterm-tx:#3d5876;
  --c-dormant:#8a8f98;  --c-dormant-bg:#eef0f2;  --c-dormant-tx:#5b6069;
}

/* ===================================================================
   THE STATUS-PILL SYSTEM — one component, two forms (CR-011 DS-6, R2/R6/R15)
   ===================================================================
   EVERY enum the staff can see renders through this system: stage,
   appointment type, temperature, mindset, consent, delivery state, Autopilot
   on/off, claim state, review-item kind, role, channel. Never a bare
   lowercase enum, never a bare colour, never a native select standing in for
   a value.

   THE RULE: colour is never the only signal. A pill is always a mark PLUS
   the word — so it survives a colour-blind reader, a glance from across the
   room, and a screen reader. The audit found `temp` rendered as an EMPTY 8px
   dot whose meaning lived in a `title` attribute reachable only by mouse.

   TWO FORMS, one grammar:
   • .pl  — the inline LABEL form: round dot + plain text, no box. The
            Meridian grammar (REDESIGN_MERIDIAN.md "Categorical labels =
            DOTS"). Used for pipeline STAGE and appointment type, which is
            where the --c-* stage ramp belongs and the only place it belongs.
   • .pill — the enclosed BADGE form: square swatch + word on a tinted
            ground. Used for every other dimension. The square swatch and the
            enclosure are what stop a temperature reading as a stage: two
            different dimensions can never again be told apart by hue alone.
   Both share the same size, weight and dot-then-word order below. */
.pl, .pill { display:inline-flex; align-items:center; gap:5px;
  font-size:var(--fs-1); font-weight:700; line-height:1.5;
  white-space:nowrap; border:1px solid transparent; }
.pl::before, .pill::before { content:""; display:inline-block; width:8px; height:8px; flex:none;
  background:var(--pc, var(--faint)); }
/* the inline label form */
.pl { padding:1px 0; border-radius:var(--radius); background:transparent; color:var(--text-2); }
.pl::before { border-radius:50%; width:7px; height:7px; }
/* ── THE IDENTITY SYSTEM (Skyler, 2026-07-27) ────────────────────────────────
   A tooth marks a Patient, a person marks a Contact, and the glyph rides the
   SECONDARY party only — see templates/partials/_identity.html for the rule
   and the three readings it produces.

   A pill shows EITHER its status dot OR a type glyph, never both (Skyler): the
   glyph is the stronger mark, so it replaces the dot rather than crowding it.
   Written as a single-level :has() — :has() cannot be nested inside :has(),
   and an invalid selector is dropped silently, which is how a rule like this
   ends up doing nothing at all. */
.pl:has(.idglyph)::before, .pill:has(.idglyph)::before { content:none; }
.idline { display:inline-flex; align-items:center; gap:.3rem; font-size:.78rem; color:var(--muted); }
.idline-extra { color:var(--faint); }
.idglyph { display:inline-flex; align-items:center; color:var(--faint); flex:none; }
.idglyph .ic { width:13px; height:13px; }
/* Inside a coloured pill the glyph takes the pill's own ink, not the muted
   grey — otherwise it reads as a separate element sitting in the pill. */
.pl .idglyph, .pill .idglyph { color:currentColor; }
.chart-idline { font-size:.85rem; margin-top:.15rem; }

/* ── THE CALL LOG (2026-07-27) ───────────────────────────────────────────────
   A contact-led table: who rang, what happened, and the recording/transcript
   if there is one. An UNKNOWN number is shown as the number itself, in the
   monospace face, because for a spam call the digits are the whole identity —
   the one thing a staffer reads before deciding to block. */
.callog-unknown { font-family: var(--font-mono); font-size: .85rem; color: var(--text-2); }
/* Actions stay on ONE line: wrapping them doubled every row's height and made
   the log hard to scan, which is the whole job of this screen. The cell scrolls
   inside itself on a narrow viewport rather than the page scrolling sideways. */
.callog-actions { display: flex; gap: .3rem; align-items: center; flex-wrap: nowrap; }
.callog-actions .btn-ghost-sm { white-space: nowrap; }
.callog-actions form { margin: 0; }
/* the enclosed badge form */
.pill { padding:1px var(--sp-2) 1px 6px; border-radius:var(--radius-pill);
  background:var(--pill-bg, var(--bg-inset)); color:var(--pill-tx, var(--text-2));
  border-color:var(--pill-bd, var(--border)); }
.pill::before { border-radius:2px; }
/* F22: a handful of needs-attention "kind" pills read as a full staff-
   English sentence rather than a one-word badge (the SYSTEM/admin-only
   failures — see pages.REVIEW_KIND_LABELS) — too long for the ordinary
   nowrap pill, so this modifier lets just those wrap onto a second line
   instead of overflowing the na-card modal title. */
.pill.pill-wrap { white-space: normal; line-height: 1.35; }
/* Temperature — its OWN hues (see the --pill-* tokens), never the stage ramp. */
.pill-hot  { --pc:var(--pill-hot);  --pill-bg:var(--pill-hot-bg);  --pill-tx:var(--pill-hot-tx);
  --pill-bd:color-mix(in srgb, var(--pill-hot) 35%, var(--surface)); }
.pill-warm { --pc:var(--pill-warm); --pill-bg:var(--pill-warm-bg); --pill-tx:var(--pill-warm-tx);
  --pill-bd:color-mix(in srgb, var(--pill-warm) 40%, var(--surface)); }
.pill-cold { --pc:var(--pill-cold); --pill-bg:var(--pill-cold-bg); --pill-tx:var(--pill-cold-tx);
  --pill-bd:color-mix(in srgb, var(--pill-cold) 35%, var(--surface)); }
/* Mindset buckets — four categorical hues, again their own. */
.pill-mind-cost  { --pc:var(--pill-mind-cost);  --pill-bg:var(--pill-mind-cost-bg);  --pill-tx:var(--pill-mind-cost-tx);
  --pill-bd:color-mix(in srgb, var(--pill-mind-cost) 35%, var(--surface)); }
.pill-mind-avoid { --pc:var(--pill-mind-avoid); --pill-bg:var(--pill-mind-avoid-bg); --pill-tx:var(--pill-mind-avoid-tx);
  --pill-bd:color-mix(in srgb, var(--pill-mind-avoid) 35%, var(--surface)); }
.pill-mind-trust { --pc:var(--pill-mind-trust); --pill-bg:var(--pill-mind-trust-bg); --pill-tx:var(--pill-mind-trust-tx);
  --pill-bd:color-mix(in srgb, var(--pill-mind-trust) 35%, var(--surface)); }
.pill-mind-nonstart { --pc:var(--pill-mind-nonstart); --pill-bg:var(--pill-mind-nonstart-bg); --pill-tx:var(--pill-mind-nonstart-tx);
  --pill-bd:color-mix(in srgb, var(--pill-mind-nonstart) 35%, var(--surface)); }
/* Semantic states — good / attention / stopped / neutral. One meaning each. */
.pill-ok      { --pc:var(--ok); --pill-bg:var(--ok-bg); --pill-tx:var(--ok);
  --pill-bd:color-mix(in srgb, var(--ok) 30%, var(--surface)); }
.pill-warn    { --pc:var(--warn-text); --pill-bg:var(--warn-bg); --pill-tx:var(--warn-text);
  --pill-bd:color-mix(in srgb, var(--warn-text) 30%, var(--surface)); }
.pill-critical{ --pc:var(--error-text); --pill-bg:var(--error-bg); --pill-tx:var(--error-text);
  --pill-bd:color-mix(in srgb, var(--error-text) 30%, var(--surface)); }
.pill-neutral { --pc:var(--faint); --pill-bg:var(--bg-inset); --pill-tx:var(--muted); --pill-bd:var(--border); }
.pill-accent  { --pc:var(--accent); --pill-bg:var(--accent-soft); --pill-tx:var(--accent-dark);
  --pill-bd:color-mix(in srgb, var(--accent) 28%, var(--surface)); }
/* THE Autopilot indicator (owner ask 2026-08-01): one light-blue "Auto" pill,
   system-wide — every cadence_active surface renders `pill pill-auto` with the
   label exactly "Auto". Swatchless (the word is short and the tint carries the
   meaning). NOT an AI marker — purple stays reserved for AI. */
.pill-auto { --pill-bg:var(--accent-soft); --pill-tx:var(--accent-dark);
  --pill-bd:color-mix(in srgb, var(--accent) 28%, var(--surface));
  padding-left: var(--sp-2); }
.pill-auto::before { display:none; }
/* A pill with no mark — for the few enums where a colour would be noise
   (channel, role). Keeps the shape and the word; drops the swatch. */
.pill-bare::before { display:none; }
.pill-bare { padding-left: var(--sp-2); }
/* CR-011 UXD-101: Reports used four one-off `.chip-*` variants for the same
   ok/warn/neutral/accent meanings this `.pill` system already states — one
   family duplicated as two. Those pills carry a full sentence, not a short
   badge word, so `pill-wrap` lifts the shared nowrap rule for just them. */
.pill-wrap { white-space: normal; }
/* select.pillsel (the dropdown-styled-as-its-pill) retired 2026-07-30 —
   Skyler: "get rid of the color formatting on these dropdowns". The
   collapsed card shows the coloured pill; the edit control is a control. */
.pl-new{ --pc:var(--c-new); }
.pl-lead{ --pc:var(--c-lead); }
.pl-exam{ --pc:var(--c-exam); }
.pl-obs{ --pc:var(--c-obs); }
.pl-cxexam{ --pc:var(--c-cxexam); }
.pl-cxcon{ --pc:var(--c-cxcon); }
.pl-pend{ --pc:var(--c-pend); }
.pl-consult{ --pc:var(--c-consult); }
.pl-start{ --pc:var(--c-start); }
/* status pills (won/lost are STATUS, not stage) — used by the patient card */
.pl-won{ --pc:var(--c-exam); }
.pl-lost{ --pc:var(--faint); color:var(--muted); }
/* long-tail campaign markers (derived Long-term / Dormant, decisions.md 2026-07-12) */
.pl-longterm{ --pc:var(--c-longterm); }
.pl-dormant{ --pc:var(--c-dormant); }
/* CR-011 UXD-31: the contact list's patient chip is a `.pl` pill wrapped in a
   link (elsewhere `.pl` is a bare inline label — this is the one place it's
   also the click target), so it needs its own no-underline/hover/focus
   treatment; `.pl`'s color/dot are untouched. */
.chip-link, .chip-link:visited { text-decoration: none; }
.chip-link:hover { color: var(--text); }
.chip-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* Shared top band: role title + the "fastest to the phone" leaderboard. The
   old call-now hero card is gone — the every-page global call-now banner
   (base.html) now carries the oldest waiting lead + Claim & call. */
.topband { display:grid; grid-template-columns:minmax(210px,1fr) minmax(230px,1fr);
  gap:19px; align-items:stretch; margin:10px 0 22px; }
.tb-title { display:flex; flex-direction:column; justify-content:center; gap:4px;
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg); padding:12px 16px;
  box-shadow:var(--shadow); }
.tb-title h1 { margin:0; font-size:20px; line-height:1.2; }
.tb-sub { margin:0; font-size:12.5px; color:var(--muted); line-height:1.35; }
.tb-right { min-width:0; }

/* Compact leaderboard card. */
.leadmini { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg); padding:10px 12px; height:100%;
  box-shadow:var(--shadow); display:flex; flex-direction:column; justify-content:center; }
.leadmini .lm-title { font-weight:600; font-size:13px; margin-bottom:8px;
  display:flex; justify-content:space-between; align-items:center; gap:8px; }
.lm-podium { display:grid; grid-template-columns:1fr 1fr 1fr; gap:6px; }
.lm-spot { text-align:center; border-radius:var(--radius); padding:4px 2px; background:var(--bg); border:1px solid var(--border); }
/* leader highlight = accent-soft wash + accent border (Meridian: never gold). */
.lm-spot.lm1 { background:var(--accent-soft); border-color:var(--accent); }
.lm-spot.lm-me { outline:2px solid var(--accent); }
.lm-medal { font-size:.95rem; line-height:1; }
.lm-name { font-weight:600; font-size:11px; line-height:1.2; margin-top:1px; }
.lm-time { font-weight:700; font-size:12.5px; line-height:1.2; font-variant-numeric:tabular-nums; color:var(--accent-dark); }
/* Rank badges inside the dashboard leaderboard specifically get the Meridian
   accent treatment; reports.html's per-employee table keeps its own #1 style
   (that page's own Meridian pass is a separate PR — not scope-crept here). */
.leadmini .rank-chip.rank-1 { background:var(--accent); color:#fff; }

/* Worklist rows moved to the lv- list system (Style 7) on 2026-08-01 — the
   whole `.wgroup/.whead/.wrow/.w-left/.w-mid/.w-right/.wdone` card family that
   lived here is gone with it, along with the `.wclaim` two-line claim badge
   (now the one-line `.lv-claim`). What survives below is what other surfaces
   still use. See "lv- adopted by the A6 role dashboards" further down. */
.lv-row .wmenu { position: relative; z-index: 4; }
.lv-row .wmenu[open] { z-index: 50; }
.rp-link { color:inherit; text-decoration:none; }
.rp-link:hover { color:var(--accent-dark); text-decoration:underline; }
.w-note { font-size:12px; color:var(--muted); line-height:1.3; }
.w-note:empty { display:none; }

/* Secondary row action (Open/Review) — a quiet hairline pill; accent-blue is
   reserved for the row's actual primary. Kept for the Needs-Attention "Review"
   link + the admin/report "Open"/"View" links (the worklist primary is now the
   board-card .action-primary card-opener; Release/Open-record moved into the ⋯). */
/* .btn-line was a FOURTH secondary rendering (27px tall, 12px label) sitting
   beside .action-primary, .btn-ghost-sm and .btn-sm — an exact geometry
   duplicate of .btn-sm with a different name. Design-system foundations pass
   (2026-07-27): the CSS rule is gone and every `.btn-line` call site now
   reads `.btn-sm` directly, so the app has one class for this tier instead
   of two names for the same box. */
/* "Claim & call" is a real <button> in a form: reset native chrome so it sits
   flush like the board-card .action-primary it now wears. */
.btn-claim { cursor:pointer; line-height:1.2; font-family:inherit; }
/* Click-to-call dial control (the contact card's phone value): the tap-to-call
   button is a POST form to /calls/start that rings through the CRM — never the OS
   dialer. display:contents keeps the wrapping <form> layout-neutral so the button
   is the inline child exactly where the old tel: link sat. (Dashboard rows no
   longer dial directly — their primary opens the Action card.) */
.dial-form { display:contents; }
.dial-form button { font-family:inherit; line-height:1.2; cursor:pointer; }
/* G49 — presence indicator: quiet context ("<name> is working this now") — it
   is advisory, never a lock, never a call to action. */
.wpresence { font-size:11px; color:var(--muted); font-style:italic; margin-top:1px; }
.board-presence { font-size:11px; color:var(--muted); font-style:italic; }

/* SC two-column layout + TC/admin two-pane layout. */
.two-col { display:grid; grid-template-columns:1fr 1fr; gap:29px; align-items:start; }
.two-col > .col-a, .two-col > .col-b { min-width:0; }
.dcols { display:flex; gap:29px; align-items:flex-start; }
.dmain { flex:1.65; min-width:0; }
.dside { flex:1; min-width:290px; }
.dcard { margin-bottom:16px; }
h2.sec { font-size:16px; margin:6px 0 8px; }
.sec-sub { font-size:12.5px; color:var(--muted); font-weight:400; }
.ltitle { font-size:13px; font-weight:600; margin-bottom:8px; }

/* Admin KPI strip — scorecard tiles: white surface, soft shadow, uppercase
   micro-label OVER a statement-grade number (order:-1 flips the label above
   the value without touching the kv/kl/kd markup order). */
.kpi-strip { display:grid; grid-template-columns:repeat(6,1fr); gap:10px; margin-bottom:16px; }
.kpi-strip.kpi-4 { grid-template-columns:repeat(4,1fr); }
.kpi { display:flex; flex-direction:column; background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius-lg); padding:12px 14px; box-shadow:var(--shadow); }
.kpi .kv { font-size:23px; font-weight:600; letter-spacing:-.02em; line-height:1.1; font-variant-numeric:tabular-nums; }
.kpi .kl { order:-1; font-size:10.5px; font-weight:600; letter-spacing:.05em; text-transform:uppercase;
  color:var(--muted); margin-bottom:6px; }
.kpi .kd { font-size:11px; color:var(--muted); margin-top:5px; }
.kpi.accent { border-color:var(--accent); background:var(--accent-soft); }
.kpi.accent .kv { color:var(--accent-dark); }
.kpi.good .kv { color:color-mix(in srgb, var(--ok) 80%, var(--text)); }
.up { color:color-mix(in srgb, var(--ok) 80%, var(--text)); }
.down { color:color-mix(in srgb, var(--danger) 80%, var(--text)); }

/* Admin conversion funnel. */
.funnel { display:flex; flex-direction:column; gap:6px; }

/* Admin per-employee load & speed table. */
.emp-table { width:100%; border-collapse:collapse; font-size:12.5px; }
.emp-table th { text-align:left; font-size:10.5px; text-transform:uppercase; letter-spacing:.03em; color:var(--muted);
  border-bottom:2px solid var(--border); padding:5px 6px; }
.emp-table td { border-bottom:1px solid var(--border); padding:6px; vertical-align:middle; }

/* Admin system pulse. */
.pulse-row { display:flex; justify-content:space-between; align-items:center; font-size:13px; padding:7px 0; border-bottom:1px solid var(--border-soft); }
.pulse-row:last-of-type { border-bottom:0; }
.pulse-val { font-weight:700; font-variant-numeric:tabular-nums; } .pulse-warn { color:var(--c-lead-tx); }
.wall { font-size:12px; text-decoration:none; color:var(--accent-dark); font-weight:600; }

@media (max-width: 900px) {
  .topband, .topband.calm { grid-template-columns:1fr; }
  .two-col { grid-template-columns:1fr; }
  .dcols { flex-direction:column; } .dside { width:100%; min-width:0; }
  .kpi-strip, .kpi-strip.kpi-4 { grid-template-columns:repeat(2,1fr); }
}
@media (max-width: 560px) {
  /* Narrow phones: an lv- row drops its middle column (W-DW: now the chip
     cluster, not just one line of "when" text) and keeps name + action.
     `lv-c4` no longer appears on this page (its old 4th column moved into
     row 1's corner cluster) — only `lv-cH` needs the override now. */
  .lv-dash .lv-cH .lv-cols, .lv-dash .lv-cH .lv-row { grid-template-columns:minmax(0,1fr) auto; }
  .lv-dash .lv-cols > :nth-child(n+2):not(:last-child),
  .lv-dash .lv-row > :nth-child(n+2):not(:last-child) { display:none; }
}

/* ─── Utilities + classes replacing former inline styles ──────────────────────
   Skyler's centralized-CSS rule (2026-07-05): no inline style="" anywhere.
   Ad-hoc inline margins were normalized to this small spacing scale so spacing
   is consistent, not per-element-random. Prefer a semantic class; reach for a
   utility only for genuine one-offs. */
.mt-1 { margin-top: 0.4rem; }
.mt-2 { margin-top: 0.6rem; }
.mt-3 { margin-top: 1.5rem; }
.m-0 { margin: 0; }
.p-0 { padding: 0; }
.d-inline { display: inline; }
.w-full { width: 100%; }
/* A button explicitly opting into full-width (a modal's one confirm button,
   contacts/detail.html / action_card.html's restore-confirm) is exactly
   that — the global button max-width (button, .btn above) is insurance
   against an ACCIDENTAL stretch, not a ban on a deliberate one. */
button.w-full, .btn.w-full, .claim-tile-btn, .ac-call-btn { max-width: none; }
/* .flush-top (margin-top:0) is defined with the patient card above. */

/* Muted "— …" note beside a page/section heading. */
.title-sub { font-size: 0.9rem; }

/* ===== The info affordance (design-system foundations, 2026-07-27) =========
   A <details>/<summary> disclosure, not a hover-only CSS tooltip — see
   partials/_info.html for why. `<details>` gives keyboard support (Enter/
   Space toggles the native disclosure) and screen-reader semantics for free;
   the ":hover, :focus-within { open }" rule below layers in the instant
   hover-to-preview feel of a tooltip on top of that, for free mouse users. */
.info-affordance { display: inline-block; position: relative; vertical-align: middle;
  margin-left: 0.3rem; font-size: inherit; }
.info-affordance summary { display: inline-flex; align-items: center; justify-content: center;
  width: 1.1rem; height: 1.1rem; border-radius: 50%; color: var(--muted); cursor: pointer;
  list-style: none; }
.info-affordance summary::-webkit-details-marker { display: none; }
.info-affordance summary .ic { width: 0.95em; height: 0.95em; }
.info-affordance summary:hover, .info-affordance summary:focus-visible { color: var(--accent); }
.info-affordance summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 50%; }
/* Hover opens it even without a click (mouse users get the tooltip feel);
   :focus-within keeps it open while focus is inside (keyboard/click users,
   and a mouse user who clicked instead of just hovering). Native `[open]`
   still works too — all three routes land on the same visible state. */
.info-affordance-pop { display: none; }
.info-affordance:hover .info-affordance-pop,
.info-affordance:focus-within .info-affordance-pop,
.info-affordance[open] .info-affordance-pop {
  display: block; position: absolute; z-index: 40; top: calc(100% + 6px); left: 0;
  width: 16rem; max-width: 80vw;
  padding: 0.55rem 0.7rem; border-radius: var(--radius-lg); background: var(--surface);
  border: 1px solid var(--border); box-shadow: var(--pop-shadow);
  font-size: 0.8rem; font-weight: 400; color: var(--text-2); line-height: 1.4;
  white-space: normal; text-transform: none; letter-spacing: normal;
}
/* A .sec-sub used as a block paragraph under a section (adds its top gap). */
.sec-note { margin-top: 0.5rem; }
/* Colored-text helpers. */
.tx-ok { color: var(--c-exam-tx); }
.tx-danger { color: var(--error-text); }
/* Lighter note chip (dashboard group note). */
.chip-note { margin-left: 6px; font-weight: 400; }
/* "Open Needs attention →" call-to-action wall link. */
.wall-cta { display: inline-block; margin-top: 0.5rem; }
/* Compact in-popup flash — the call popup's follow-up nudge. (Was
   .flash-focus, shared with the focus-view "Viewing …" banner; that banner —
   and the per-patient filter view with it — is gone, 2026-08-01.) */
.flash-nudge { margin: 0 0 0.6rem; }
/* Modal footer buttons (was inline on every modal). UI polish 3 (owner
   screenshot, /settings/signature: the ghost "Back" link floated above the
   solid "Save signature" button instead of sitting level with it): every
   other .modal-actions row pairs two <button>s, which both eat the generic
   button{margin-top:1rem} reset (below) equally, so stretch alignment hid
   the bug — the signature page is the only spot pairing a <button> with a
   plain <a>, which never had that margin, so it stretched taller than its
   sibling and its text sat near the box's top instead of centered.
   align-items:center (any stretch was never the intent for a button row)
   plus zeroing that margin for buttons in this row (was scoped to
   #call-modal only; generalized here since it's really this row's bug,
   not that modal's) fixes it for every current and future action row. */
.modal-actions { margin-top: 0.75rem; display: flex; align-items: center; gap: 0.5rem; justify-content: flex-end; }
.modal-actions button { margin-top: 0; }
/* Side-by-side compare cards (duplicate confirmation). */
.compare-row { display: flex; gap: 1rem; flex-wrap: wrap; margin: 0.5rem 0 1rem; }
.compare-col { flex: 1; min-width: 220px; margin: 0; }
/* Scheduled-send timeline block + item + its dashed chip. */
.scheduled-sends { margin-bottom: 0.4rem; }
.sched-item { background: var(--hover); }
.chip-scheduled { background: var(--accent-soft); color: var(--accent-dark); border: 1px dashed color-mix(in srgb, var(--accent) 40%, var(--surface)); }
/* Worklist due-time, colored by the row's pill color. */
.wd-new { color: var(--c-new-tx); }
.wd-lead { color: var(--c-lead-tx); }
.wd-exam { color: var(--c-exam-tx); }
.wd-obs { color: var(--c-obs-tx); }
.wd-cxexam { color: var(--c-cxexam-tx); }
.wd-cxcon { color: var(--c-cxcon-tx); }
.wd-pend { color: var(--c-pend-tx); }
.wd-consult { color: var(--c-consult-tx); }

/* ===================== A4 Messaging center =====================
   The 3-pane chat app (approved inbox wireframe): thread list · conversation ·
   context panel. Ported onto the :root tokens + the central .ic icon system;
   neutral chat surfaces use literals in the same spirit as the rest of this
   file. No inline styles (guard test) — every visual is a named class here. */
/* Messaging inherits the shell width (--page-max) — its old 1180px bump became a
   SHRINK once the shell widened past it, so the override is gone. */
.page:has(.msgapp) { max-width: 100%; margin: 0; padding: 0; min-height: 0; flex: 1; display: flex; }
.msgapp { display: flex; align-items: stretch; overflow: hidden; background: var(--surface);
  flex: 1; min-width: 0; min-height: 0; }
.msgapp > * { min-width: 0; }

/* Round accent avatar (initials), shared by the list rows + the convo header. */
.avatar-c { width: 42px; height: 42px; border-radius: 50%; flex-shrink: 0; display: flex;
  align-items: center; justify-content: center; background: var(--accent); color: #fff;
  font-weight: 700; font-size: 15px; text-transform: uppercase; }

/* small status dots (opted-out, needs-attention) — indicators, not chips */
.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.dot-stop { background: var(--error-text); }

/* ---- LEFT: thread list ---- */
.chatlist { width: 345px; flex-shrink: 0; border-right: 1px solid var(--border); display: flex;
  flex-direction: column; min-height: 0; background: var(--surface); }
/* Filters on the search row (design-system foundations, 2026-07-27): the
   filter chips used to be a whole SEPARATE row under the search box (its own
   height, its own border-bottom). They're now inline in this same flex row,
   wrapping onto a second line only if the 345px sidebar is too narrow to fit
   everything — `height: 54px` is gone in favor of `min-height` so a wrapped
   second line can actually grow the row instead of clipping. */
.chatlist-top { padding: 8px 10px; min-height: 54px; box-sizing: border-box; border-bottom: 1px solid var(--border);
  display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 8px; }
.chatlist-search { flex: 1 1 10rem; min-width: 0; margin: 0; border-radius: 16px; padding: 6px 12px; }
/* Thread-list filter chips (messaging round 2: "Assigned to me" / "Unread") —
   same quiet .tf-toggle chip the contact timeline filter already uses
   (reused, not reinvented). Lives inline in .chatlist-top now, not its own
   bordered row. */
.chatlist-filters { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.chatlist-scroll { overflow-y: auto; flex: 1; }
.chatlist-empty { padding: 24px 16px; text-align: center; color: var(--muted); }
.chatlist-empty p { margin: 0.2rem 0; }
/* color/text-decoration come from .rec-link (added alongside this class in
   the template) rather than being repeated here — see the shared-class note
   above .rec-link. */
.chatrow { display: flex; gap: 10px; align-items: center; padding: 13px 12px; cursor: pointer;
  border-bottom: 1px solid #f0f2f3; }
.chatrow:hover { background: #f6f9f9; }
.chatrow.active { background: var(--accent-soft); }
.cr-body { flex: 1; min-width: 0; }
.cr-top { display: flex; align-items: center; gap: 7px; }
.cr-name { font-weight: 700; font-size: 14px; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; flex-shrink: 0; max-width: 52%; }
.cr-ps { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; min-width: 0; }
.cr-prev { margin-top: 3px; display: flex; align-items: center; gap: 8px; }
.cr-msg { flex: 1; font-size: 12.5px; color: var(--muted); line-height: 1.35; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.chatrow.unread .cr-msg { color: var(--text); font-weight: 500; }
.cr-you { color: var(--accent-dark); font-weight: 600; }
/* Time + unread pill, pinned top-right of the name line (owner polish
   2026-08-01: the old right-rail column — time over a ⋯ toggle — is gone;
   with the ⋯ moved into the avatar, only the timestamp needs the right edge,
   so the preview line below runs the row's FULL width). margin-left:auto is
   the pin; flex-shrink:0 keeps the time whole while .cr-ps ellipsizes. */
.cr-rail-top { display: flex; align-items: center; gap: 4px; margin-left: auto; flex-shrink: 0; }
.cr-time { font-size: 11px; color: var(--muted); white-space: nowrap; }
/* per-thread unread: count pill (beside the time) + a lifted (non-muted) preview */
.cr-unread { flex-shrink: 0; }
/* Avatar-as-menu (owner ask 2026-08-01): the row's initials circle IS the ⋯
   trigger. At rest it shows the initials; hovering or keyboard-focusing the
   circle (or having its menu open) swaps them for ⋯ — same circle, same
   size, so nothing shifts. Touch devices (no hover) simply tap the circle:
   the menu opens directly and the ⋯ shows via the [open] state. The summary
   needs its display re-declared at ≥ the `.action-menu > summary` (0,1,1)
   specificity — same requalification story as .card-menu-toggle above. */
.chatrow .action-menu { margin-top: 0; flex-shrink: 0; position: relative; }
.action-menu > summary.avatar-menu-toggle { display: flex; align-items: center;
  justify-content: center; cursor: pointer; }
.avatar-menu-toggle:focus-visible { outline: 2px solid var(--accent-dark); outline-offset: 2px; }
.avatar-menu-toggle .am-dots { display: none; font-weight: 700; }
.avatar-menu-toggle:hover .am-init,
.avatar-menu-toggle:focus-visible .am-init,
.avatar-menu[open] .avatar-menu-toggle .am-init { display: none; }
.avatar-menu-toggle:hover .am-dots,
.avatar-menu-toggle:focus-visible .am-dots,
.avatar-menu[open] .avatar-menu-toggle .am-dots { display: block; }
/* The menu anchors to the avatar (left edge of the row) instead of the old
   right rail, opening rightward into the list column. */
.chatrow .avatar-menu .action-list { left: 0; top: calc(100% + 4px); }
/* "Mark as unread" no-op notice (the contact has no inbound message yet) —
   quiet, same treatment as any other muted small aside; not an error. */
.cr-notice { padding: 0.5rem 12px 0; margin: 0; }

/* ---- MIDDLE: conversation ---- */
.thread-pane { display: flex; flex: 1; min-width: 0; min-height: 0; }
.convo { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg); }
.convo-head { display: flex; align-items: center; gap: 10px; padding: 0 14px; height: 54px; box-sizing: border-box;
  border-bottom: 1px solid var(--border); background: var(--surface); }
.convo-head .avatar-c { width: 34px; height: 34px; font-size: 13px; }
/* Header rebuild (owner ask, CR-007 A4, 2026-07-21): name + the SAME muted
   patient·stage context the thread row shows, sharing one line — mirrors
   .cr-top/.cr-name/.cr-ps. min-width:0 on the identity block is what lets
   .ch-ps ellipsis instead of overflowing the header when both are long. */
.ch-identity { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1 1 auto; }
/* The name <a> also carries .rec-link (shared record-link treatment: inherit
   color, no underline) instead of its own color/hover/underline rules here —
   it's the ONLY "open contact" control in the header (rule 1). */
.ch-name { font-weight: 700; font-size: 15px; flex-shrink: 0; max-width: 60%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.ch-ps { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; min-width: 0; }
/* The inner <a> now carries .rec-link (shared record-link treatment)
   instead of its own color/hover/underline rules. */
.ch-actions { margin-left: auto; display: flex; gap: 2px; align-items: center; }
.iconbtn { width: 32px; height: 32px; border: 0; background: none; border-radius: 8px; cursor: pointer;
  color: var(--muted); display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none; }
.iconbtn:hover { background: var(--hover); color: var(--text); }
.iconbtn:disabled { opacity: 0.4; cursor: default; }
.iconbtn:disabled:hover { background: none; color: var(--muted); }
.convo-scroll { flex: 1; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column;
  gap: 2px; }
.load-earlier { align-self: center; margin-bottom: 8px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text-2); border-radius: 14px; padding: 4px 12px;
  font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; }
.load-earlier:hover { background: var(--hover); color: var(--text); }
.load-earlier:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* day divider */
.daysep { align-self: center; font-size: 11px; color: var(--muted); background: var(--surface);
  border: 1px solid var(--border-soft); border-radius: var(--radius-lg); padding: 2px 12px; margin: 12px 0 6px; }

/* sender-aligned bubbles — the text is the hero */
.msg { max-width: 74%; padding: 8px 12px; border-radius: 16px; font-size: 14px; line-height: 1.35;
  position: relative; margin-top: 1px; overflow-wrap: anywhere; }
.msg.them { align-self: flex-end; background: #e8edec; color: #17302b; border-bottom-right-radius: 5px; }
/* Outgoing SMS bubble (owner ask, messaging round 2, 2026-07-21): light blue +
   near-black text, replacing the old solid accent-blue/white-text fill — so the
   thread reads as three quiet tints (this + the muted grey/yellow kind colors
   above) instead of one loud bubble and two quiet ones. Reuses --accent-soft/
   --accent-dark — the SAME pale-blue pair the timeline's own outbound chip
   already renders with (.tl-chip.dir-out below), so the bubble and the
   timeline's outbound entries read as the same family, not two different
   blues. Contrast: --text (near-black) on --accent-soft (near-white blue)
   passes trivially. */
.msg.me { align-self: flex-start; background: var(--accent-soft); color: var(--text); border-bottom-left-radius: 5px; }
.msg.me a { color: var(--accent-dark); text-decoration: underline; }
.msg .meta { display: block; font-size: 10.5px; margin-top: 3px; opacity: 0.75; }
.msg.me .meta { color: var(--muted); text-align: right; }
.msg.them .meta { color: var(--muted); }
.msg .tick .ic { width: 0.95em; height: 0.95em; vertical-align: -0.12em; }
.msg.failed { background: var(--error-bg); color: var(--error-text); border: 1px solid #f2c2bd; }
.msg.failed .meta { color: var(--error-text); opacity: 1; }

/* byline above a bubble when the sender changes (staff name / Auto) */
.byline { align-self: flex-start; font-size: 10.5px; color: var(--muted); margin: 5px 0 1px 4px;
  display: flex; align-items: center; gap: 4px; }
.byline.me { align-self: flex-start; margin-left: 4px; }
.bavatar-bot { display: inline-flex; width: 15px; height: 15px; border-radius: 50%; background: #7a5bb5;
  color: #fff; align-items: center; justify-content: center; }
.bavatar-bot .ic { width: 0.7rem; height: 0.7rem; }

/* failed-send: caution mark to the left of the staff-aligned outbound bubble */
.failrow { display: flex; align-items: center; gap: 7px; align-self: flex-start; max-width: 80%; margin-top: 1px; }
.failrow .msg { margin-top: 0; max-width: 100%; }
.failmark { flex-shrink: 0; color: var(--error-text); display: flex; align-items: center; }

/* empty conversation state */
.convo-empty { margin: auto; text-align: center; color: var(--muted); padding: 24px; }
.convo-empty p { margin: 0.25rem 0; }
.convo-empty .ic { width: 1.9rem; height: 1.9rem; color: var(--muted); }

/* ---- composer (consent silent unless blocked) ---- */
.composer { border-top: 1px solid var(--border); background: var(--surface); }
.composer-error { padding: 6px 14px 0; color: var(--error-text); font-size: 12px; }
.replybar { display: flex; align-items: flex-end; gap: 8px; padding: 10px 12px; }
.replyfield-wrap { flex: 1; min-width: 0; position: relative; }
.replyfield { flex: 1; display: flex; align-items: flex-end; background: var(--bg-inset); border-radius: 18px;
  padding: 2px 44px 2px 14px; position: relative; width: 100%; box-sizing: border-box; }
.replyfield-wrap .replyfield { padding-right: 44px; }
/* The reply field autogrows with content (crmAutogrow sets height from
   scrollHeight on input AND after an ✨ AI draft fills it); border-box keeps that
   measurement exact, and max-height caps it at ~8-10 lines before it scrolls. */
.replyfield textarea { flex: 1; border: 0; background: none; outline: none; padding: 9px 0; font-size: 14px;
  font-family: inherit; resize: none; line-height: 1.4; min-height: 24px; max-height: 200px;
  box-sizing: border-box; overflow-y: auto; }
.sendbtn { width: 34px; height: 34px; border-radius: 50%; border: 0; background: var(--accent); color: #fff;
  cursor: pointer; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
  position: absolute; right: 3px; bottom: 3px; }
.sendbtn .ic { transform: rotate(180deg); }
.sendbtn:hover { background: var(--accent-dark); }
.composer-blocked { display: flex; align-items: center; gap: 8px; padding: 12px 14px;
  border-top: 1px solid var(--border); background: var(--surface); color: var(--muted); font-size: 13px; }
.composer-blocked .ic { color: var(--error-text); flex-shrink: 0; }

/* ---- RIGHT: context panel ---- */
.msg-side { width: 350px; flex-shrink: 0; border-left: 1px solid var(--border); background: var(--surface);
  overflow-y: auto; padding: 12px; }
.msg-side .sblock { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 10px 11px;
  margin-bottom: 10px; font-size: 12.5px; }
.msg-side .sblock h3 { margin: 0 0 5px; font-size: 13px; }
.msg-side .ai-sblock { background: #f6f1fc; border-color: #d7c5ef; }
/* ✨ Next-move card: the suggestion body + its Generate/Regenerate control. The
   button reuses the shared .ai-refresh pill (white-on-purple), like the summary
   card's Refresh. */
.msg-side .nextmove-body { margin: 6px 0 8px; white-space: pre-wrap; line-height: 1.45; color: var(--text); }
.msg-side .nextmove form { margin: 0; }
.msg-side .s-muted { color: var(--muted); }
.msg-side .pcard { margin-bottom: 10px; }
.side-addpt { text-align: center; }

/* ---- W1: the Action workspace (docked action card) ----
   ux-review-decisions-2026-08-03 §A. `.is-docked` on `.msgapp` is the one
   layout switch: the thread list collapses to an avatar strip (the rows stay
   real links — clicking one still opens THAT thread, dropping the dock) and
   the rail widens to fit the docked card beside the patient card(s). */
.msgapp.is-docked .chatlist { width: 64px; }
.msgapp.is-docked .chatlist-top { flex-direction: column; height: auto; padding: 8px 4px; gap: 6px; }
.msgapp.is-docked .chatlist-search,
.msgapp.is-docked .chatlist-filters { display: none; }
.msgapp.is-docked .chatrow { padding: 8px 0; justify-content: center; }
.msgapp.is-docked .cr-body { display: none; }
.msgapp.is-docked .msg-side { width: 420px; }

/* The docked card itself: same `.ac-modal` innards (header/steps/foot) as the
   popup, but it carries none of `.modal`'s fixed width/shadow/max-height —
   `.ac-modal--docked` gives it its own plain-card chrome sized to the rail. */
.ac-dock { margin-bottom: 10px; }
.ac-modal--docked { background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 0.9rem 1rem; width: 100%; box-sizing: border-box; }

/* The rail's quiet invitation to dock — a chip, but interactive (a whole
   row), so it earns its own rule rather than reusing the inline `.chip`. */
.chip-action-needed { display: flex; align-items: center; gap: 6px; width: 100%;
  margin: 0 0 10px; padding: 7px 10px; border-radius: var(--radius-lg);
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, var(--border));
  background: var(--accent-soft); color: var(--accent-dark); font: inherit;
  font-size: 12.5px; font-weight: 650; cursor: pointer; text-align: left; }
.chip-action-needed:hover { background: color-mix(in srgb, var(--accent) 16%, var(--accent-soft)); }
.chip-action-needed:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chip-action-needed .ic { width: 14px; height: 14px; flex-shrink: 0; }

/* ---- ✨ AI — the ONE shared component (owner standard 2026-07-21) ----
   Every AI output ("summaries everywhere") reads as one system: a purple
   .ai-box CONTAINER + a small .ai-tag corner chip (sparkle sprite + "AI").
   Mount them together on any surface — cards, timeline entries, inline blocks.
   One purple palette, tokenised, so a re-theme is a token edit. */
:root {
  --ai-bg: #f6f1fc;      /* soft purple container tint */
  --ai-border: #d7c5ef;  /* hairline purple border */
  --ai-text: #5b3a8e;    /* purple for the tag/accents (body text stays --text) */
  --ai-tag-bg: #f0e9fa;  /* the chip fill */
}

/* THE container: purple tint + hairline border + app radius + normal padding,
   readable body text (never purple-on-purple). */
.ai-box { position: relative; background: var(--ai-bg); border: 1px solid var(--ai-border);
  border-radius: var(--radius-lg); padding: 12px 14px; color: var(--text); }

/* THE label chip — always a drawn sparkle + "AI", never an emoji glyph. */
.ai-tag { display: inline-flex; align-items: center; gap: 3px; font-size: 10.5px; font-weight: 700;
  color: var(--ai-text); background: var(--ai-tag-bg); border: 1px solid var(--ai-border);
  border-radius: 8px; padding: 0 6px; white-space: nowrap; }
.ai-tag .ic { width: 12px; height: 12px; }

/* Pinned conversion-summary card — purple container is .ai-box; this adds only
   its own spacing/layout. */
.ai-summary-card { margin-bottom: 14px; }
.ai-summary-head { display: flex; align-items: center; gap: 8px; }
.ai-summary-head strong { color: var(--ai-text); font-size: 14px; }
.ai-summary-body { margin: 8px 0 4px; white-space: pre-wrap; font-size: 13px; line-height: 1.5; color: var(--text); }
.ai-summary-foot, .ai-summary-empty { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.ai-summary-foot form, .ai-summary-empty form { margin: 0; }
.ai-summary-err { margin: 6px 0; color: var(--error-text); font-size: 12px; }
/* CR-011 UX-F10 — the Action card's summary fold. Clamped to two lines while
   folded (and the Updated/Refresh foot tucked away with it) so the accent-blue
   call button stays above the fold on a 900px laptop; the "Show more" toggle in
   action_card.html removes .is-folded to reveal the whole summary. Only the
   Action card folds — the patient chart still renders the summary in full. */
.ac-summary-fold { position: relative; }
.ac-summary-fold.is-folded .ai-summary-body { display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }
.ac-summary-fold.is-folded .ai-summary-foot { display: none; }
.ac-summary-fold .ai-summary-card { margin-bottom: 4px; }
.ac-summary-more { padding: 0; }
.ai-refresh { display: inline-flex; align-items: center; gap: 4px; background: #fff; color: var(--ai-text);
  border: 1px solid var(--ai-border); border-radius: var(--radius-pill); padding: 4px 12px; font-size: 12px; font-weight: 600;
  cursor: pointer; margin: 0; }
.ai-refresh:hover { background: var(--ai-tag-bg, #f0e9fa); color: var(--ai-text); }
.ai-refresh.stale { background: var(--ai-text); color: #fff; border-color: var(--ai-text); }
.ai-refresh[disabled] { opacity: 0.55; cursor: default; }
.ai-offhint { display: flex; align-items: center; gap: 5px; margin-bottom: 14px; color: var(--muted); }
.ai-offhint .ic { color: var(--ai-text); }

/* Composer ✨ Draft button + the drafted (purple) reply field. */
.draftbtn { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; height: 34px; padding: 0 12px;
  border: 1px solid var(--ai-border); background: #fff; color: var(--ai-text); border-radius: 18px;
  font-size: 13px; font-weight: 600; cursor: pointer; }
.draftbtn:hover { background: var(--ai-bg); color: var(--ai-text); }
.draftbtn[disabled] { opacity: 0.55; cursor: default; }
.draftbtn .ic { width: 16px; height: 16px; }
/* A drafted reply field IS an .ai-box (purple, tagged) while it's untouched AI
   output; the .ai-tag floats in the corner. Editing removes .ai-box (the JS
   `oninput`) so the chrome drops and the field returns to a normal composer. */
.replyfield .ai-tag { position: absolute; top: -8px; left: 12px; }
.replyfield:not(.ai-box) .ai-tag { display: none; }
.ai-draft-err { color: var(--error-text); font-size: 11px; align-self: center; }

/* ✨ Call summary on the timeline — purple container is .ai-box; this adds only
   the inline layout. */
.ai-call-slot { margin-top: 6px; }
.ai-call-summary { display: flex; align-items: flex-start; gap: 6px; font-size: 12.5px; }
.ai-call-summary-body { white-space: pre-wrap; line-height: 1.45; }
.ai-call-btn { display: inline-flex; align-items: center; gap: 4px; background: #fff; color: var(--ai-text);
  border: 1px solid var(--ai-border); border-radius: var(--radius-pill); padding: 3px 10px; font-size: 11.5px; font-weight: 600;
  cursor: pointer; }
.ai-call-btn:hover { background: var(--ai-bg); color: var(--ai-text); }
.ai-call-btn[disabled] { opacity: 0.55; cursor: default; }

/* AI knowledge admin page (prompt blocks · topics · assembled preview). */
.ai-block { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 8px 12px; margin: 8px 0; }
.ai-block.inactive { opacity: 0.7; }
.ai-block > summary { cursor: pointer; font-weight: 600; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* One fixed column for the roster's role pill, so every name starts at the
   same x (CR-011 UXD-130). */
.user-role-pill { min-width: 11.5rem; justify-content: center; }
.ai-block-slug { font-family: var(--font-mono); font-size: 11px; color: var(--ai-text); background: var(--ai-bg);
  border: 1px solid var(--ai-border); border-radius: var(--radius); padding: 0 6px; }
.ai-block-kw { font-weight: 400; }
.ai-block textarea, .ai-block input[type="text"], .ai-block input:not([type]) { width: 100%; }
.ai-block .ai-check { display: inline-flex; align-items: center; gap: 6px; margin: 8px 0; font-weight: 400; }

/* CR-011 UXD-131: this disclosure ("Add a user", "Add knowledge") is NOT an
   AI affordance, so it must not wear the purple AI provenance tint — that
   dilutes the one signal purple is reserved for (design rule 12). Neutral
   dashed "add" affordance instead. */
.ai-add { border-style: dashed; border-color: var(--border-strong); background: var(--surface); }
.ai-add > summary.ai-add-primary { color: var(--text-2); }
.ai-add:hover { background: var(--hover); }
.ai-preview { background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 12px; font-family: var(--font-mono); font-size: 12px; line-height: 1.5; white-space: pre-wrap;
  overflow-x: auto; max-height: 460px; overflow-y: auto; }

/* ---- New-message button + contact picker ---- */
/* CR-011 UXD-94: this used to be solid accent-filled, the same size/shape as
   the composer's .sendbtn — so "start a new thread" visually outranked
   "send," the actually-committing action, every time both were on screen.
   Quiet by default (same vocabulary as .composer-more-toggle): only tints
   accent on hover/focus, never at rest. */
.newmsg-icon { flex-shrink: 0; width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  padding: 0; margin: 0; transition: background .12s ease, color .12s ease, border-color .12s ease; }
.newmsg-icon:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.newmsg-icon:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.newmsg-results { margin-top: 0.75rem; max-height: 46vh; overflow-y: auto; }
.newmsg-hint { padding: 0.75rem 0.25rem; }
/* color/text-decoration come from .rec-link (added alongside this class in
   the template) rather than being repeated here — see the shared-class note
   above .rec-link. */
.newmsg-row { display: flex; align-items: center; gap: 10px; padding: 8px 6px; border-radius: 8px; }
.newmsg-row:hover { background: #f6f9f9; }
.newmsg-row .avatar-c { width: 30px; height: 30px; font-size: 12px; }
/* flex-shrink:0 (owner ask, CR-007 A4 polish): protects the name/number stack
   so .newmsg-ps below is what gives way first under width pressure. */
.newmsg-body { min-width: 0; flex-shrink: 0; display: flex; flex-direction: column; }
.newmsg-name { font-weight: 600; font-size: 13.5px; display: flex; align-items: center; gap: 6px; }
.newmsg-sub { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Patient/stage context (owner ask, CR-007 A4 polish): same muted derivation
   as the thread-row .cr-ps, pinned to the row's trailing edge via
   margin-left:auto. Default flex-shrink:1 + min-width:0 is deliberate — under
   a narrow modal THIS truncates before the protected .newmsg-body does. */
.newmsg-ps { margin-left: auto; padding-left: 10px; max-width: 42%; min-width: 0;
  font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; text-align: right; }

/* modal form field label (reused by the assign + picker modals) */
.fld-label { display: block; margin: 0.6rem 0 0.2rem; font-size: 0.78rem; font-weight: 600; color: var(--muted); }
/* Field grammar (CR-011 UXD-64/65). Optionality was prose — "(optional — chase
   it later if you don't have it)", "(responsible party)", "(E.164, the only
   phone)" — in a different voice per field, and one of them wrapped and shoved
   the field down the page. It is now ONE quiet marker, and the format hint
   lives under the field instead of masquerading as a value in a placeholder. */
.fld-opt { font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--faint); margin-left: 0.3rem; }
.fld-hint { margin: 0.25rem 0 0; font-size: 0.78rem; color: var(--muted); }
/* The admin-only authenticator field on the sign-in card (UXD-145): folded
   away so the field staff never see a control that does not apply to them. */
.login-totp { margin-top: 0.75rem; }
.login-totp > summary { cursor: pointer; font-size: 0.85rem; color: var(--muted); }
.login-totp > summary:hover { color: var(--text); }
.login-totp[open] > summary { margin-bottom: 0.25rem; }
/* CR-011 UXD-144: the sign-in card was top-aligned (`.card-narrow`'s own
   `margin: 3rem auto`), leaving a lot of dead space below on a normal
   viewport, with no product identity above the bare "Sign in" card. Opting
   `.page` into a centered flex layout for this one page is the same
   `:has()` pattern `.page:has(.board)` / `.page:has(.msgapp)` already use
   above, so no other page's layout is touched. */
.page:has(.login-shell) { display: flex; align-items: center; justify-content: center; min-height: calc(100vh - 3rem); }
.login-shell { width: 100%; display: flex; flex-direction: column; align-items: center; }
/* The brand mark + wordmark — reuses `.logo-mark` (the rail's own CSS-drawn
   mark, an accent-colored square token, never an image) at a larger size;
   `.logo-co` is skipped because it's opacity:0 outside the pinned rail. */
.login-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 1.5rem; }
.login-brand .logo-mark { width: 28px; height: 28px; min-width: 28px; }
.login-brand-name { font-weight: 700; font-size: 1.15rem; color: var(--text); }
.login-card { margin: 0 auto; }
/* GL-1 (owner directive 2026-08-04): "Sign in with Practice Essentials" is the
   PRIMARY, obvious sign-in action once SSO is configured — `.btn` already
   gives it the app's one-accent-primary-per-view look; `.btn-sso` only adds
   the full-width/centering a sign-in card wants. Local username/password is
   demoted to a quiet disclosure below it (`.login-local`), the break-glass
   path — same quiet-summary treatment `.login-totp` already uses. */
.btn-sso { width: 100%; justify-content: center; text-decoration: none; margin-bottom: 1.1rem; }
.login-local { margin-top: 0.25rem; text-align: left; }
.login-local > summary { cursor: pointer; font-size: 0.85rem; color: var(--muted); text-align: center; }
.login-local > summary:hover { color: var(--text); }
.login-local[open] > summary { margin-bottom: 0.75rem; }
.login-local-form .btn-quiet { width: 100%; justify-content: center; margin-top: 0.5rem; }

@media (max-width: 1000px) {
  .msg-side { display: none; }
}
@media (max-width: 720px) {
  .page:has(.msgapp) { max-width: 100%; }
  .msgapp { flex-direction: column; height: auto; }
  .chatlist { width: 100%; border-right: 0; border-bottom: 1px solid var(--border); max-height: 320px; }
  .convo { min-height: 60vh; }
}

/* ===================== A6 dashboards catch-up + Reports (2026-07-12) =====================
   The approved dashboards.html redesign (admin trim, SC queue, TC 50/50) and
   the reconciled work-gauge Reports page. Named classes only — no inline styles. */

/* mini-leaderboard timeframe links (Today · Wk · Mo) */
/* A real segmented control (CR-011 UXD-21). The leaderboard window was bold
   grey text for the current option and two 11px accent links for the others —
   three ~20px targets that did not read as a control. Segments are full-size
   targets on the shared chip language; the active one is marked with
   aria-current as well as visually, so the state is not colour-only. */
.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-pill);
  overflow: hidden; background: var(--surface); }
.segmented .seg { display: inline-flex; align-items: center; justify-content: center;
  min-height: 1.65rem; padding: 0.15rem 0.6rem; font-size: 0.75rem; font-weight: 600;
  color: var(--muted); text-decoration: none; border: 0; background: none; cursor: pointer; }
.segmented .seg + .seg { border-left: 1px solid var(--border); }
.segmented a.seg:hover { background: var(--hover); color: var(--text); }
.segmented .seg.on { background: var(--accent-soft); color: var(--accent-dark); cursor: default; }
.segmented a.seg:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.lm-tf { font-size: 11px; font-weight: 600; color: var(--muted); }

/* leaderboard rank chip (replaces medal emoji) — #1 gets a soft gold */
.rank-chip { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;
  border-radius: 50%; font-size: 11px; font-weight: 800; background: #e7ebee; color: var(--muted); }
.rank-chip.rank-1 { background: #f2d888; color: #7a5a12; }

/* (claimed-by-someone-else dimming moved to .lv-row.lv-claimed with the
   Style 7 adoption — see the lv- block near the end of this file.) */

/* after-hours lead row: a normal white row (Meridian: no wash) — the
   .pl-afterhours dot label is the whole signal; the clock starts at open. */
.pl-afterhours { --pc: var(--s8); }
.wd-calm { color: #3a5673; }
.tx-calm { color: #3a5673; }

/* TC layout redline: the card columns split 50/50 (hero band unaffected) */
.dcols-even .dmain { flex: 1; }

/* admin hot spots — live "sitting too long" lines. Urgency reads through the
   existing .pl/.sev dot-severity tag (already Meridian); a hairline-soft rule
   separates rows, no row backgrounds. */
.line-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border-soft); font-size: 13px; }
.line-row:last-child { border-bottom: 0; }
.line-row .grow { flex: 1; min-width: 0; }
.sev { min-width: 52px; text-align: center; }

/* admin "This week at a glance" — scorecard tiles that LINK into Reports
   (same kpi grammar: white card, soft shadow, label-over-number via order:-1). */
.week-glance-wrap { margin-top: 20px; }
.week-glance { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.wg-item { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 12px 14px; text-decoration: none; color: var(--text); box-shadow: var(--shadow); }
.wg-item:hover { border-color: var(--accent); background: var(--hover); }
.wg-v { font-size: 23px; font-weight: 600; letter-spacing: -.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.wg-l { order: -1; font-size: 10.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 6px; }
.wg-link { margin-top: 8px; font-size: 11px; font-weight: 600; color: var(--accent-dark); }
.wg-item:hover .wg-link { text-decoration: underline; }
@media (max-width: 600px) { .week-glance { grid-template-columns: repeat(2, 1fr); } }

/* ---- Reports (the work gauges) ---- */
.rep-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 10px; }
/* A secondary "leave this page" link out to the Stats app — not the Reports
   view's primary action (the page IS the report), so quiet like every other
   secondary control, not a blue-outlined pill. */
.stats-link { display: inline-block; font-weight: 700; font-size: 13px; color: var(--text-2); text-decoration: none;
  border: 1px solid var(--border); border-radius: 8px; padding: 7px 12px; background: var(--surface); white-space: nowrap; }
.stats-link:hover { background: var(--hover); border-color: var(--muted); }
.stats-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.stats-link-off { opacity: 0.6; border-style: dashed; cursor: default; }
/* Meridian scorecard grammar (decisions.md 2026-07-14): the tiles adopt the
   dashboard .kpi shell — white surface + radius-lg + --shadow, uppercase
   micro-label ABOVE the number via order:-1 (flex column). Replaces the
   pre-Ocean pale-teal hex left over from the teal theme. */
.rep-hero { border-color: var(--accent); background: var(--accent-soft); }
.rep-tiles { display: flex; gap: 10px; flex-wrap: wrap; margin: 10px 0 12px; }
.rep-tile { flex: 1; min-width: 150px; display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 12px 14px; box-shadow: var(--shadow); }
.rep-tile-n { font-size: 23px; font-weight: 700; color: var(--accent-dark); line-height: 1.1; }
.rep-tile-l { order: -1; font-size: 10.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }
.rep-tile-s { font-size: 11.5px; color: var(--muted); margin-top: 5px; }
/* Semantic-red warn tile — same token mix as .chip-danger, no hardcoded hex. */
.rep-tile-warn { background: var(--error-bg); border-color: color-mix(in srgb, var(--error-text) 28%, var(--surface)); }
.rep-tile-warn .rep-tile-n { color: var(--error-text); }
.rep-sub { font-size: 15px; margin: 16px 0 6px; }
.rep-chips { margin: 4px 0 10px; display: flex; gap: 8px; flex-wrap: wrap; }
.cell-r { text-align: right; }
.stats-pointer { background: #faf8fd; border-color: #ddd2ec; }
.rep-future { background: #faf8fd; border-color: #ddd2ec; }
.rep-future-cards { display: flex; gap: 12px; flex-wrap: wrap; }
.rep-future-card { flex: 1; min-width: 210px; background: var(--surface); border: 1px solid #e3daf0; border-radius: 8px; padding: 10px 12px; }
.rep-plan { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; color: #7a5bbf;
  background: #efe8fa; border-radius: 4px; padding: 1px 6px; margin-bottom: 4px; }

/* ===== Campaigns (PR-4): the seasonal / custom mass-send screen =====
   Wireframe: serve-wireframes/wireframes/campaigns.html. Everything here is
   prefixed camp- ; chips/pills/table/buttons reuse the shared classes above. */
.camp-lede { max-width: 72ch; color: var(--muted); font-size: 0.9rem; margin: 0.2rem 0 1rem; }
.camp-h2 { margin: 1.7rem 0 0.35rem; }
.camp-hist-sub, .camp-cal-sub { margin: 0 0 0.5rem; }

/* -- the year-at-a-glance cards (they ARE the campaign picker) -- */
.camp-cal { display: grid; grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr)); gap: 0.65rem; margin: 0.6rem 0 0.4rem; }
.camp-cal-card { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow); padding: 0.7rem 0.8rem; display: flex; flex-direction: column; gap: 0.4rem; }
.camp-cal-next { border-color: var(--accent); box-shadow: var(--shadow), 0 1px 6px rgba(13, 122, 114, 0.14); }
.camp-cal-custom { border-style: dashed; }
/* UXD-107: bodies run 2-6 lines, so the Compose buttons sat at five
   different heights. Pinning the last row bottom-aligns them all. */
.camp-cal-card > div:last-child { margin-top: auto; }
.camp-cal-card.camp-sel { border-color: var(--accent); box-shadow: var(--shadow), inset 0 0 0 2px rgba(13, 122, 114, 0.15); background: var(--accent-soft); }
.camp-cal-name { font-weight: 700; font-size: 0.92rem; line-height: 1.25; }
.camp-cal-when { font-size: 0.78rem; color: var(--muted); }
.camp-inwindow { color: var(--accent-dark); font-weight: 700; }
.camp-cal-chan { display: flex; gap: 0.3rem; align-items: center; flex-wrap: wrap; }
/* Channel label = dot + text (Meridian "categorical labels are dots, not
   filled pills" rule, wiki/design-principles.md) — its own scoped class
   rather than reusing .chip-ev-sms/.chip-ev-email (those stay filled pills
   for the timeline elsewhere) or .pl (that family means pipeline stage/
   status, not send channel). Same --s4/--s5 hues as chip-ev-sms/chip-ev-email
   so a channel reads as the same color everywhere in the app. */
.camp-chandot { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.78rem; font-weight: 700; color: var(--text-2); }
.camp-chandot::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--pc, var(--muted)); flex: none; }
.camp-chandot-sms { --pc: var(--s4); }
.camp-chandot-email { --pc: var(--s5); }
.camp-cal-angle { font-size: 0.8rem; line-height: 1.45; }
.camp-cal-status { font-size: 0.78rem; color: var(--muted); margin-top: auto; }
.camp-cal-card .btn-ghost-sm { display: inline-block; background: none; margin-top: 0.1rem; }

/* -- the wizard header (the back-to-the-cards affordance) + stepper -- */
.camp-wiz-head { display: flex; align-items: baseline; gap: 0.7rem; margin: 1.5rem 0 0; }
.camp-wiz-name { font-weight: 700; font-size: 0.92rem; }
.camp-stepper { list-style: none; display: flex; margin: 0.9rem 0 0; padding: 0; }
.camp-stepper li { flex: 1; position: relative; display: flex; flex-direction: column; align-items: center; gap: 0.35rem; font-size: 0.78rem; text-align: center; cursor: pointer; color: var(--muted); padding: 0 0.25rem; }
.camp-stepper li::before { content: ""; position: absolute; top: 0.95rem; left: -50%; width: 100%; height: 2px; background: var(--border); z-index: 0; }
.camp-stepper li:first-child::before { display: none; }
.camp-sdot { width: 1.9rem; height: 1.9rem; border-radius: 50%; background: var(--bg-inset); border: 1px solid var(--border); color: var(--muted); display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.85rem; position: relative; z-index: 1; }
.camp-slabel { font-weight: 600; line-height: 1.2; }
.camp-stepper li.current { color: var(--text); }
.camp-stepper li.current .camp-sdot { background: var(--accent); border-color: var(--accent); color: #fff; }
.camp-stepper li.done .camp-sdot { background: #e7f5ec; border-color: #bfe2c8; color: #146138; }
.camp-stepper li.done::before { background: #bfe2c8; }

/* -- the wizard shell -- */
.camp-wizard { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow); margin-top: 0.9rem; overflow: hidden; }
.camp-step { padding: 1rem 1rem 0.6rem; }
.camp-step[hidden] { display: none; }
.camp-step-h { font-size: 1rem; }
.camp-hint { font-size: 0.82rem; color: var(--muted); margin: 0.15rem 0 0.8rem; max-width: 78ch; }
.camp-nav { display: flex; gap: 0.5rem; align-items: center; padding: 0.7rem 1rem; border-top: 1px solid var(--border); background: var(--bg); }
.camp-nav-spacer { flex: 1; }
.camp-nav button { margin-top: 0; }
.camp-nav .camp-nav-btn { background: none; }
.camp-next { padding: 0.4rem 1rem; }

/* -- step 1: channel + preview + custom authoring -- */
.camp-chan { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--surface)); border-radius: 8px; padding: 0.6rem 0.75rem; margin-bottom: 0.8rem; }
.camp-chan-lbl { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
.camp-chan-opt { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.88rem; font-weight: 600; cursor: pointer; margin: 0; }

.camp-chan-off { opacity: 0.45; cursor: not-allowed; }
.camp-smswarn, .camp-hardconfirm { display: flex; gap: 0.55rem; align-items: flex-start; background: var(--error-bg); border: 1px solid #f2c2bd; border-left: 4px solid var(--c-new); border-radius: 8px; padding: 0.65rem 0.8rem; font-size: 0.82rem; line-height: 1.5; color: #7a1c14; }
.camp-smswarn[hidden], .camp-hardconfirm[hidden] { display: none; } /* display:flex would defeat hidden */
.camp-smswarn { margin: 0 0 0.8rem; }
.camp-smswarn .ic, .camp-hardconfirm .ic { color: var(--c-new); flex: none; margin-top: 0.15rem; }
.camp-smswarn b, .camp-hardconfirm b { color: var(--error-text); }
.camp-hardconfirm { margin: 0.8rem 0; }
.camp-hardconfirm label { display: flex; gap: 0.55rem; align-items: flex-start; cursor: pointer; margin: 0; font-weight: 400; font-size: inherit; }
.camp-hardconfirm input { margin-top: 0.2rem; }
.camp-authoring { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); padding: 0.8rem 0.9rem; max-width: 42rem; }
.camp-authoring .fld-label:first-child { margin-top: 0; }
.camp-authoring textarea { width: 100%; padding: 0.45rem 0.6rem; border: 1px solid var(--border); border-radius: 4px; font: inherit; font-size: 0.95rem; line-height: 1.5; resize: vertical; }
.camp-authoring textarea:focus { border-color: var(--accent); }
.camp-authoring textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.camp-mfpick { display: flex; flex-wrap: wrap; gap: 0.3rem; margin: 0.5rem 0 0.2rem; align-items: center; }
.camp-mfpick-lbl { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); margin-right: 0.15rem; }
.camp-mfpick button { margin: 0; box-shadow: none; background: var(--bg-inset); border: 1px solid var(--border); color: var(--text-2); font-family: var(--font-mono); font-size: 0.72rem; padding: 0.1rem 0.45rem; border-radius: 5px; cursor: pointer; font-weight: 600; }
.camp-mfpick button:hover { border-color: var(--accent); color: var(--accent-dark); background: var(--bg-inset); }
.camp-mf { font-family: var(--font-mono); font-size: 0.86em; background: var(--bg-inset); border: 1px solid var(--border); border-radius: 4px; padding: 0 0.2rem; color: var(--text-2); white-space: nowrap; }
.camp-prev-label { font-size: 0.68rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0.8rem 0 0.3rem; display: flex; align-items: center; gap: 0.35rem; }
.camp-emailcard { border: 1px solid #ecd9b7; background: #fffdf8; border-radius: var(--radius-lg); overflow: hidden; max-width: 42rem; }
.camp-esubj { background: #fdf3e4; border-bottom: 1px solid #ecd9b7; padding: 0.45rem 0.75rem; font-size: 0.82rem; }
.camp-esubj b { color: #5c4410; }
.camp-ebody { padding: 0.7rem 0.85rem; font-size: 0.86rem; line-height: 1.55; white-space: pre-wrap; }
.camp-smsbubble { background: #e3f2df; border: 1px solid #cfe6c9; border-radius: 12px; border-bottom-left-radius: 3px; padding: 0.55rem 0.75rem; font-size: 0.88rem; line-height: 1.45; max-width: 30rem; white-space: pre-wrap; }
.camp-sender-note { display: flex; align-items: center; gap: 0.35rem; margin: 0.8rem 0 0.2rem; }

/* -- step 2: segment -- */
.camp-segs { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; max-width: 42rem; }
.camp-seg { display: flex; align-items: center; gap: 0.6rem; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--border); font-size: 0.88rem; cursor: pointer; margin: 0; font-weight: 400; }
.camp-seg:last-child { border-bottom: 0; }
.camp-seg.camp-sel { background: var(--accent-soft); }

.camp-seg-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.camp-seg-name { font-weight: 600; display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
.camp-seg-sub { font-size: 0.76rem; color: var(--muted); }
.camp-seg-count { font-weight: 700; font-size: 0.88rem; white-space: nowrap; }
.camp-excl { display: flex; gap: 0.55rem; align-items: flex-start; max-width: 42rem; margin-top: 0.7rem; font-size: 0.8rem; line-height: 1.5; color: #8a6510; background: #fdf6e0; border: 1px solid #e8d9a0; border-radius: 8px; padding: 0.6rem 0.75rem; }
.camp-excl .ic { color: #8a6510; flex: none; margin-top: 0.15rem; }
.camp-recip { display: flex; align-items: baseline; gap: 0.55rem; margin: 0.9rem 0 0.3rem; }
.camp-recip-n { font-size: 1.9rem; font-weight: 800; color: var(--accent-dark); line-height: 1; }
.camp-recip-lbl { font-size: 0.82rem; color: var(--muted); }

/* -- step 3: review & compliance -- */
.camp-comply { border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--surface)); border-radius: var(--radius-lg); background: var(--hover); padding: 0.9rem; max-width: 48rem; }
.camp-comply-hd { display: flex; align-items: center; gap: 0.4rem; font-weight: 700; font-size: 0.9rem; color: var(--accent-dark); }
.camp-metric { font-size: 0.92rem; margin: 0.6rem 0 0.75rem; line-height: 1.5; }
.camp-metric-n { font-size: 1.15rem; color: var(--accent-dark); }
.camp-metric-x { color: var(--error-text); }
.camp-ctable { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; margin-bottom: 0.75rem; font-size: 0.82rem; }
.camp-ctable-num { text-align: right; width: 4rem; white-space: nowrap; font-weight: 700; }
.camp-reach { color: var(--accent-dark); }
.camp-drop { color: var(--error-text); }
.camp-guards { list-style: none; margin: 0; padding: 0; }
.camp-guards li { display: flex; gap: 0.55rem; align-items: flex-start; padding: 0.45rem 0; border-top: 1px dashed var(--border); font-size: 0.82rem; line-height: 1.5; }
.camp-guards li:first-child { border-top: 0; }
.camp-guards .ic { flex: none; margin-top: 0.15rem; color: #1d7a33; }
.camp-comply-foot { margin: 0.6rem 0 0; }

/* -- step 4: send -- */
.camp-sendbox { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); padding: 0.9rem; max-width: 42rem; }
.camp-send-big { font-size: 1rem; font-weight: 700; line-height: 1.4; }
.camp-send-big .camp-send-n { color: var(--accent-dark); }
.camp-send-btn { background: var(--c-new); margin-top: 0.9rem; padding: 0.5rem 1.1rem; font-size: 0.95rem; }
.camp-send-btn:hover { background: #b93a3a; }
.camp-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.camp-cancel { color: var(--error-text); }

@media (max-width: 760px) {
  .camp-stepper .camp-slabel { font-size: 0.68rem; }
  .camp-nav { flex-wrap: wrap; }
  .camp-recip-n { font-size: 1.5rem; }
}

/* ===== A7 click-to-call UI (PR-4): the Call popup, timeline players, and
   ring-me settings. Reuses the modal standard + :root tokens throughout. ===== */

/* Call popup (a crmModal): who/ring/recording lines + the live status view. */
.call-who { margin: 0.2rem 0 0.4rem; font-size: 1rem; font-weight: 600; }
.call-hint { margin: 0.3rem 0; font-size: 0.85rem; color: var(--c-lead-tx); background: var(--c-lead-bg);
  border: 1px solid #e6cfa0; border-radius: var(--radius); padding: 0.45rem 0.6rem; }
.call-notice { margin: 0.3rem 0; font-size: 0.82rem; color: var(--muted); }
.call-status { margin: 0.8rem 0 0.2rem; font-size: 1.15rem; font-weight: 700; color: var(--accent-dark); }
/* (the old margin-top:0 fix for this modal's own .modal-actions button now
   lives on the shared rule above — every action row had the same bug.) */
#call-outcome { margin-top: 0.7rem; border-top: 1px solid var(--border); padding-top: 0.6rem; }

/* Timeline recording player — compact enough to sit inside a timeline row. */
.call-audio { display: block; width: min(21rem, 100%); height: 2rem; margin: 0.3rem 0 0.1rem; }

/* Office hours + send windows (Settings → Practice, settings/hours redesign
   2026-08-01): compact one-line time-pair rows. A pair is two narrow time
   inputs joined by a real → glyph; the only labels are the legend row above
   the hours grid ("Open" / "Lunch") and the per-row AM/PM tags below —
   never per-field "From"/"until" words. */
.callhours-open { display: inline-flex; align-items: center; gap: 0.4rem; margin: 0;
  min-width: 8.5rem; font-weight: 400; }
.callhours-open input { margin: 0; }
.callhours-dayname { font-size: 0.95rem; }
.hrs-grid {
  display: grid;
  grid-template-columns: max-content max-content max-content;
  justify-content: start;
  column-gap: 1.4rem;
  row-gap: 0.45rem;
  align-items: center;
  margin-top: 0.6rem;
}
.hrs-col { font-size: 0.85rem; color: var(--muted); }
.hrs-pair { display: inline-flex; align-items: center; gap: 0.35rem; white-space: nowrap; }
.hrs-pair input[type="time"] { width: 7.2rem; min-width: 0;
  padding-left: 0.4rem; padding-right: 0.4rem; }
.hrs-arrow { color: var(--muted); }
.hrs-tag { font-size: 0.8rem; font-weight: 600; color: var(--muted); width: 1.7rem; }
/* Send windows: day checkbox in its own column, the AM and PM pair rows
   stacked beside it. */
.sendwin-grid {
  display: grid;
  grid-template-columns: max-content max-content;
  justify-content: start;
  column-gap: 1.4rem;
  row-gap: 0.55rem;
  margin-top: 0.6rem;
}
.sendwin-daycell { align-self: start; margin-top: 0.3rem; }
.sendwin-windows { display: flex; flex-direction: column; gap: 0.35rem; }

/* Ring-me: the /settings per-user rows (admin) … */
.ringme-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; padding: 0.35rem 0;
  border-bottom: 1px solid var(--border); }
.ringme-row:last-of-type { border-bottom: 0; }
.ringme-row button { margin-top: 0; padding: 0.3rem 0.7rem; font-size: 0.85rem; }
.ringme-name { flex: 0 0 11rem; font-weight: 600; font-size: 0.9rem; }
.ringme-toggle { display: inline-flex; align-items: center; gap: 0.35rem; margin: 0; font-weight: 400;
  font-size: 0.85rem; white-space: nowrap; }
.ringme-toggle input { margin: 0; }
.ringme-num { width: auto; flex: 1; min-width: 10rem; font-size: 0.9rem; }
.ringme-err { flex-basis: 100%; color: var(--error-text); font-size: 0.8rem; }

/* … and the avatar-menu "My ring-me" fragment (dropdown-sized). */
.ringme-menu .ringme-blurb { margin: 0 0 0.5rem; font-size: 0.78rem; color: var(--muted); line-height: 1.4; }
.ringme-form { display: flex; flex-direction: column; align-items: stretch; gap: 0.45rem; }
.ringme-line { display: flex; gap: 0.4rem; align-items: center; }
.ringme-line input { flex: 1; min-width: 0; }
.ringme-line button { margin-top: 0; padding: 0.35rem 0.7rem; font-size: 0.85rem; }
.ringme-form .linklike { align-self: flex-start; }
.ringme-saved { margin: 0 0 0.4rem; color: var(--c-exam-tx); font-size: 0.85rem; font-weight: 600; }

/* ── Tracked marketing numbers (/settings, attribution Phase 2) ── the
   campaign→number registry table + add form. Retired rows stay listed but
   greyed — they are never deleted (history keeps resolving). */
.tracknum-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; margin: 0.5rem 0 0.75rem; }
.tracknum-table th { text-align: left; font-size: 10.5px; text-transform: uppercase; letter-spacing: .03em;
  color: var(--muted); border-bottom: 1px solid var(--border); padding: 4px 6px; }
.tracknum-table td { border-bottom: 1px solid var(--border); padding: 6px; vertical-align: middle; }
.tracknum-table .inline-form { margin: 0; }
.tracknum-table button { margin-top: 0; padding: 0.25rem 0.6rem; font-size: 0.8rem; }
.tracknum-retired td { color: var(--muted); }
.tracknum-add { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.tracknum-add input { width: auto; flex: 1; min-width: 9rem; font-size: 0.9rem; }
.tracknum-add button { margin-top: 0; padding: 0.3rem 0.7rem; font-size: 0.85rem; flex: 0 0 auto; }

/* ── AI assistant (settings) ── the encrypted-key masked display + the
   secondary Test / Remove actions under the save form. */
.ai-note { margin: 0.35rem 0 0; }
.ai-key-mask { font-family: var(--font-mono); font-size: 0.85rem; background: var(--bg-inset);
  border: 1px solid var(--border); border-radius: 4px; padding: 0.1rem 0.4rem; color: var(--text-2); }
.ai-actions { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; margin-top: 0.6rem; }
.ai-actions .inline-form { margin: 0; }
.ai-actions button { margin-top: 0; padding: 0.3rem 0.7rem; font-size: 0.85rem; }

/* Minor sub-heading inside a report card (the by-campaign cut under the
   source table). */
h3.sec-minor { font-size: 13px; margin: 14px 0 4px; }

/* ============================================================================
   Autopilot — the read-only cadence + template view (/autopilot, cadence
   editor Phase 3). All read-only: no controls, just the config made legible.
   ========================================================================= */
.ap-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  flex-wrap: wrap; margin-bottom: 10px; }
.ap-ro-chip { background: var(--accent-soft); color: var(--accent-dark);
  border-color: color-mix(in srgb, var(--accent) 28%, var(--surface)); font-weight: 600; white-space: nowrap; }
/* UXD-114: this was a full accent-soft info banner restating the "View only"
   chip that sits immediately above it. The chip carries the state; this line
   carries only the part the chip does not say (what is coming next), so it is
   a quiet line rather than a second announcement. */
.ap-note { margin: 0 0 18px; }

/* Locked guardrails panel — the compliance rules the editor can never touch.
   CR-011 UXD-113: this used to wash the whole card in `--ok-bg`, a one-off
   surface style used nowhere else in the app, with its own `.chip chip-ok`
   badges. It is now a plain `.card` like every other panel; each guardrail's
   "on" state is the shared `.pl pill-ok` label (dot + plain text) instead of
   a green-filled badge, so the state lives in the mark, not the whole card. */
.ap-lock-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 8px 18px; margin-top: 10px; }
.ap-lock { display: flex; flex-direction: column; gap: 3px; }
.ap-lock p { margin: 0; }

.ap-h2 { font-size: 18px; margin: 26px 0 10px; }

/* A cadence group (one staff-facing name; New Leads holds two rails). */
.ap-group { margin-bottom: 12px; }
.ap-group-name { font-size: 15px; margin: 14px 0 6px; }
.ap-cadence { margin-bottom: 12px; }
.ap-cadence-head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; }
.ap-cadence-title { font-size: 15px; display: flex; align-items: center; gap: 8px; }
.ap-onoff { font-weight: 700; }
.ap-trigger { margin: 4px 0 0; font-size: 0.85rem; color: var(--text-2); }
.ap-rail-detail { margin: 3px 0 0; }
.ap-cadence-facts { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.ap-anchor-banner { margin: 10px 0 2px; padding: 7px 10px; font-size: 0.8rem; color: var(--warn-text);
  background: var(--warn-bg); border: 1px solid color-mix(in srgb, var(--warn-text) 24%, var(--surface));
  border-radius: var(--radius); }

/* The message ladder — one row per SENT message (wait rows folded into the day). */
.ap-ladder { list-style: none; margin: 12px 0 0; padding: 0; }
.ap-step { display: flex; gap: 12px; padding: 10px 0; border-top: 1px solid var(--border-soft); }
.ap-step:first-child { border-top: 0; }
.ap-step-when { flex: 0 0 92px; display: flex; flex-direction: column; gap: 2px; text-align: right;
  padding-top: 1px; }
.ap-day { font-size: 0.8rem; font-weight: 700; color: var(--accent-dark); }
.ap-gap { font-size: 0.72rem; }
.ap-step-body { flex: 1; min-width: 0; }
.ap-step-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ap-mode { vertical-align: baseline; }
.ap-tmpl-name { font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-2); }
.ap-subject { font-size: 0.8rem; color: var(--text-2); margin-top: 3px; }
.ap-preview { font-size: 0.82rem; color: var(--muted); margin-top: 3px; line-height: 1.4; }
.ap-preview-missing { color: var(--error-text); }

/* Channel badges — text vs email vs voice, one consistent pill. */
.ap-chan { display: inline-block; font-size: 0.66rem; font-weight: 700; letter-spacing: .02em;
  padding: 0.05rem 0.4rem; border-radius: 4px; border: 1px solid transparent; white-space: nowrap; }
.ap-chan-sms { background: var(--accent-soft); color: var(--accent-dark);
  border-color: color-mix(in srgb, var(--accent) 26%, var(--surface)); }
.ap-chan-email { background: color-mix(in srgb, var(--s2) 13%, var(--surface));
  color: color-mix(in srgb, var(--s2) 72%, var(--text)); border-color: color-mix(in srgb, var(--s2) 30%, var(--surface)); }
.ap-chan-voice { background: var(--warn-bg); color: var(--warn-text);
  border-color: color-mix(in srgb, var(--warn-text) 26%, var(--surface)); }

/* Template library. */
.ap-lib { margin-bottom: 12px; }
.ap-lib-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 10px; margin-top: 10px; }
.ap-tmpl { border: 1px solid var(--border); border-radius: var(--radius); padding: 9px 11px;
  background: var(--surface); }
.ap-tmpl-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.ap-tmpl-key { font-family: var(--font-mono); font-size: 0.8rem; font-weight: 600; color: var(--text); }
.ap-active { font-weight: 600; }
.ap-variants { cursor: default; }

/* ========================================================================
   Email composer (A5 Phase 1) — the crmModal composer, its template picker,
   schedule/remind controls, the thread email-bubble variant, and the public
   unsubscribe page. Token-based; no inline styles (guard).
   ===================================================================== */
/* Full-email reader popup (2026-07-21) — same modal shell as the composer, with
   a scrollable body; opened from a summarized email's "View full email". */
.er-modal { width: min(680px, 94vw); padding: 0; max-height: 92vh; display: flex; flex-direction: column; }
.er-head { display: flex; align-items: center; gap: 0.5rem; padding: 0.55rem 0.9rem; border-bottom: 1px solid var(--border); }
.er-head > .ic { color: var(--accent); flex-shrink: 0; }
.er-subject { font-weight: 700; font-size: 0.95rem; flex: 1; min-width: 0; overflow-wrap: anywhere; }
.er-x { margin-left: auto; }
.er-meta { display: flex; flex-wrap: wrap; gap: 0.25rem 0.9rem; padding: 0.55rem 0.9rem 0; color: var(--muted); font-size: 0.8rem; }
.er-who { font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
/* The attributed office-side staff member in a reader's meta row (call speaker
   attribution, migration 0060) — same weight as .er-who, muted like the rest of
   the meta line so it reads as context. */
.er-staff { font-weight: 600; overflow-wrap: anywhere; }
.er-body { padding: 0.85rem 0.9rem; overflow-y: auto; font-size: 0.9rem; line-height: 1.5; color: var(--text); }
.er-body.plain { white-space: pre-wrap; overflow-wrap: anywhere; }
.er-body a { color: var(--accent-dark); }
.er-body p { margin: 0 0 0.5rem; }
.er-body > :last-child { margin-bottom: 0; }
.er-foot { display: flex; justify-content: flex-end; padding: 0.6rem 0.9rem 0.85rem; border-top: 1px solid var(--border); }
.er-reply .ic { width: 0.95em; height: 0.95em; }
.ec-modal { width: min(720px, 94vw); padding: 0; max-height: 92vh; display: flex; flex-direction: column; }
.ec-head { display: flex; align-items: center; gap: 0.5rem; padding: 0.45rem 0.85rem; border-bottom: 1px solid var(--border); }
.ec-head > .ic { color: var(--accent); }
.ec-title { font-weight: 650; }
.ec-x { margin-left: 0.15rem; }
/* Insert-template moved into the header (redesign): a right-aligned control whose
   popup opens below it. margin-left:auto pushes it (and the X after it) right. */
.ec-insert { position: relative; margin-left: auto; }
.ec-tpl-btn { white-space: nowrap; }
#ec-form { display: flex; flex-direction: column; min-height: 0; }
.ec-body { padding: 0.75rem 1rem; overflow-y: auto; }
.ec-field { margin-bottom: 0.55rem; position: relative; }
.ec-label { display: block; font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--muted); margin-bottom: 0.2rem; }
.ec-control { display: flex; align-items: center; gap: 0.5rem; border: 1px solid var(--border);
  border-radius: var(--radius); padding: 0.35rem 0.55rem; background: var(--surface); min-height: 2.1rem; }
.ec-control input { border: 0; outline: 0; font: inherit; font-size: 0.85rem; width: 100%;
  background: none; color: var(--text); }
.ec-readonly { color: var(--text-2); font-size: 0.85rem; }
.ec-readonly .ic { color: var(--muted); }
.ec-chip { display: inline-flex; align-items: center; background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--surface)); color: var(--accent-dark);
  border-radius: 16px; padding: 0.1rem 0.6rem; font-size: 0.8rem; font-weight: 600; }
.ec-ccbcc { margin-left: auto; background: none; border: 0; color: var(--accent-dark);
  font-weight: 600; font-size: 0.75rem; cursor: pointer; padding: 0; }
.ec-ccbcc:hover { background: none; color: var(--accent); }
.ec-ccrow { margin-top: 0.4rem; }
.ec-ccrow[hidden] { display: none; }
.ec-tpl-menu { position: absolute; top: 100%; right: 0; left: auto; margin-top: 4px; z-index: 20; width: 22rem; max-width: 80vw; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--pop-shadow); overflow: hidden; }
.ec-tpl-menu[hidden] { display: none; }
.ec-tpl-head { padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--border-soft); font-size: 0.8rem; font-weight: 700; }
.ec-tpl-list { max-height: 15rem; overflow-y: auto; padding: 0.25rem; }
.ec-tpl-group { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted);
  font-weight: 700; padding: 0.4rem 0.4rem 0.2rem; }
.ec-tpl-item { display: block; width: 100%; text-align: left; background: var(--surface);
  border: 1px solid var(--border-soft); border-radius: 7px; padding: 0.4rem 0.5rem; margin: 0.2rem 0; cursor: pointer; }
.ec-tpl-item:hover { border-color: var(--accent); background: var(--hover); }
.ec-tpl-name { display: block; font-size: 0.82rem; font-weight: 650; color: var(--text); }
.ec-tpl-meta { display: block; font-size: 0.68rem; color: var(--muted); margin-top: 0.05rem; }
.ec-tpl-auto { color: var(--ok); font-weight: 600; }
.ec-tpl-empty { padding: 0.5rem; }
.ec-toolbar { display: flex; align-items: center; gap: 0.1rem; border: 1px solid var(--border); border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0; padding: 0.25rem 0.35rem; background: var(--hover); flex-wrap: wrap; }
.ec-tb { min-width: 1.75rem; height: 1.6rem; padding: 0 0.35rem; border: 0; background: none; color: var(--text-2);
  cursor: pointer; border-radius: 5px; font-size: 0.85rem; }
.ec-tb:hover { background: color-mix(in srgb, var(--border) 60%, var(--hover)); color: var(--text); }
.ec-tb:disabled { opacity: 0.4; cursor: default; }
.ec-tb b { font-weight: 800; }
.ec-tb i { font-style: italic; }
.ec-tb-sep { width: 1px; height: 1.1rem; background: var(--border); margin: 0 0.25rem; }
.ec-editor { border: 1px solid var(--border); border-radius: 0 0 var(--radius) var(--radius); background: var(--surface);
  padding: 0.6rem 0.7rem; min-height: 14rem; max-height: 26rem; overflow-y: auto; font-size: 0.88rem;
  line-height: 1.5; color: var(--text); }
.ec-editor:empty::before { content: attr(data-placeholder); color: var(--faint); }
.ec-editor:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: -1px; }
.ec-file { display: none; }
.ec-attach { margin-top: 0.4rem; font-size: 0.78rem; color: var(--text-2); }
.ec-attach[hidden] { display: none; }
.ec-sig { margin-top: 0.6rem; border: 1px dashed var(--border); border-radius: var(--radius);
  background: var(--hover); padding: 0.45rem 0.6rem; }
.ec-sig-h { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--faint); margin-bottom: 0.2rem; }
.ec-sig-body { font-size: 0.78rem; color: var(--text-2); line-height: 1.5; white-space: pre-line; }
.ec-foot { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; padding: 0.7rem 1rem;
  border-top: 1px solid var(--border); background: var(--col-bg);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg); position: relative; }
.ec-btn { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.8rem; font-weight: 600;
  padding: 0.3rem 0.75rem; border: 1px solid var(--accent); background: var(--accent); color: #fff;
  border-radius: var(--radius); cursor: pointer; }
.ec-btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); }
.ec-btn .ic { width: 0.95em; height: 0.95em; }
.ec-spacer { flex: 1; }
.ec-discard { color: var(--danger); }
.ec-pop { position: absolute; bottom: 3.2rem; left: 1rem; width: 17rem; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--pop-shadow);
  padding: 0.7rem 0.8rem; z-index: 6; }
.ec-pop[hidden] { display: none; }
.ec-pop-h { font-size: 0.82rem; font-weight: 700; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.3rem; }
.ec-pop-row { display: flex; gap: 0.5rem; margin-bottom: 0.4rem; }
.ec-pop-row label { flex: 1; font-size: 0.62rem; font-weight: 700; text-transform: uppercase;
  color: var(--muted); display: flex; flex-direction: column; gap: 0.15rem; }
.ec-pop-row input { border: 1px solid var(--border); border-radius: var(--radius); padding: 0.25rem 0.35rem;
  font: inherit; font-size: 0.8rem; }
.ec-pop-acts { display: flex; gap: 0.4rem; justify-content: flex-end; margin-top: 0.4rem; }
.ec-status { padding: 0 1rem; }
.ec-status:empty { display: none; }
.ec-error { margin: 0.5rem 0; padding: 0.4rem 0.6rem; border-radius: var(--radius);
  background: var(--danger-bg); color: var(--danger); font-size: 0.8rem; }
.ec-ok { margin: 0.5rem 0; padding: 0.4rem 0.6rem; border-radius: var(--radius);
  background: var(--ok-bg); color: var(--ok); font-size: 0.8rem; }

/* ---- Composer redesign (2026-07-20): From+To row, merge-field tray + colored
   in-body chips, Preview, restored-draft note, and the footer ⋯ menu. ---- */
#ec-editarea[hidden] { display: none; }
/* From + To on one row (each an .ec-field); breaks to stacked on a narrow modal. */
.ec-row2 { display: flex; gap: 0.6rem; }
.ec-half { flex: 1 1 0; min-width: 0; }
@media (max-width: 560px) { .ec-row2 { flex-direction: column; gap: 0; } }

/* Restored-draft note (accent-tinted strip above the fields). */
.ec-draftnote { display: flex; align-items: center; gap: 0.35rem; margin-bottom: 0.6rem;
  font-size: 0.78rem; color: var(--text-2); background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--surface));
  border-radius: var(--radius); padding: 0.3rem 0.55rem; }
.ec-draftnote[hidden] { display: none; }
.ec-draftnote > .ic { color: var(--accent); width: 0.9em; height: 0.9em; }
.ec-draftnote .linklike { font-size: 0.78rem; }

/* Merge-field tray — toggled row of pills below the toolbar; a click inserts a
   chip at the caret. Side borders line up with the toolbar/editor stack. */
.ec-mftray { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; padding: 0.4rem 0.5rem;
  border-left: 1px solid var(--border); border-right: 1px solid var(--border); background: var(--hover); }
.ec-mftray[hidden] { display: none; }
.ec-mftray-lbl { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--muted); margin-right: 0.15rem; }
.ec-mfpill { margin: 0; background: var(--surface); border: 1px solid var(--border); color: var(--accent-dark);
  font-family: var(--font-mono); font-size: 0.72rem; padding: 0.1rem 0.45rem; border-radius: 5px;
  cursor: pointer; font-weight: 600; }
.ec-mfpill:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-dark); }

/* In-body merge chip: an atomic, accent-tinted pill (contenteditable=false in the
   editor) that serializes back to literal {{field}} on submit. Templated body
   stays on white; the chip carries the color. */
.ec-mergechip { display: inline-block; font-family: var(--font-mono); font-size: 0.82em;
  background: var(--accent-soft); color: var(--accent-dark);
  border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--surface));
  border-radius: 5px; padding: 0 0.28rem; margin: 0 1px; white-space: nowrap; user-select: all;
  vertical-align: baseline; }

/* A toolbar button that carries a text label (Preview); pushed right with the
   merge-fields toggle. */
#ec-mf-btn { margin-left: auto; }
.ec-tb-text { width: auto; min-width: 0; display: inline-flex; align-items: center; gap: 0.2rem;
  padding: 0 0.45rem; font-size: 0.76rem; font-weight: 600; color: var(--text-2); }
.ec-tb-text .ic { width: 0.95em; height: 0.95em; }

/* Read-only merged preview (swaps in over the edit area). */
.ec-preview { padding: 0.15rem 0; }
.ec-preview[hidden] { display: none; }
.ec-prev-head { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.7rem;
  padding-bottom: 0.55rem; border-bottom: 1px solid var(--border-soft); }
.ec-prev-head > .ic { color: var(--accent); }
.ec-prev-title { font-weight: 650; font-size: 0.9rem; }
.ec-prev-edit { margin-left: auto; }
.ec-prev-subj { font-size: 0.9rem; margin-bottom: 0.7rem; }
.ec-prev-k { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); margin-right: 0.35rem; }
.ec-prev-msg, .ec-preview-body { font-size: 0.9rem; line-height: 1.55; color: var(--text); }
.ec-prev-msg p { margin: 0 0 0.5rem; }
.ec-prev-msg > :last-child { margin-bottom: 0; }
/* An unresolvable merge field in a preview — amber, so staff see what would send
   literally (a typo'd or not-yet-supported field). */
.ec-missing { font-family: var(--font-mono); font-size: 0.9em; background: var(--warn-bg);
  color: var(--warn-text); border: 1px solid color-mix(in srgb, var(--warn-text) 30%, var(--surface));
  border-radius: 4px; padding: 0 0.2rem; }

/* Footer ⋯ menu (Schedule · Remind me · Save as draft · Discard) — opens UPWARD
   from the bottom-right footer so it never overflows below the modal. */
.ec-more { margin-top: 0; }
/* Footer pair shares one literal control height (owner: same size + row as Send) */
.ec-foot .ec-btn, .ec-foot .ec-more-btn { height: 34px; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; }
.ec-foot .ec-more-btn { width: 36px; }
.ec-more-list { bottom: calc(100% + 6px); top: auto; right: 0; left: auto; min-width: 16rem; }
.ec-remind-item { display: flex; align-items: center; gap: 0.4rem; padding: 0.3rem 0.5rem;
  font-size: 0.82rem; color: var(--text-2); cursor: default; }
.ec-remind-item > span { flex: 1; }
.ec-remind-item .ic { color: var(--muted); width: 0.95em; height: 0.95em; }
.ec-remind-item select { border: 1px solid var(--border); border-radius: var(--radius); padding: 0.15rem 0.3rem;
  font: inherit; font-size: 0.78rem; background: var(--surface); color: var(--text); }
.ec-more-list button.ec-discard { color: var(--danger); }
.ec-more-list button.ec-discard:hover { color: var(--danger); background: var(--danger-bg); }

/* Email bubble variant in the message thread (✉ + subject over the body).
   Kind color language (messaging round 2): muted grey (--email-bg family),
   overriding BOTH .msg.me's solid accent-blue and .msg.them's plain grey —
   an email always reads as "email kind" regardless of direction (alignment
   still carries in/out); readable dark text either way, never white-on-tint. */
.msg.email { max-width: 84%; background: var(--email-bg); border: 1px solid var(--email-border); color: var(--text); }
.msg.email a { color: var(--accent-dark); }
.msg.email .meta { color: var(--muted); }
.msg.email .email-subj { display: flex; align-items: center; gap: 0.3rem; font-weight: 700;
  margin-bottom: 0.25rem; padding-right: 1.8rem; }
.msg.email .email-subj .ic { width: 0.95em; height: 0.95em; }
/* height/max-width/padding: the base button{} rules leak a FIXED control
   height + a 20rem width cap onto this linklike button — a subject long
   enough to wrap kept its fixed height and its second line painted OVER the
   body's first line (owner bug, 2026-08-01). Let it size to its content. */
.msg.email .email-subject-link { color: var(--text); font: inherit; font-weight: 700;
  text-align: left; min-width: 0; overflow-wrap: anywhere;
  height: auto; max-width: 100%; padding: 0; white-space: normal; }
.msg.email .email-subject-link:hover { color: var(--accent-dark); text-decoration: underline; }
.msg.email .email-body { font-size: 0.85rem; }
.msg.email .email-body p { margin: 0 0 0.4rem; }
.msg.email .email-body > :last-child { margin-bottom: 0; }
/* Ingested email bodies are plain text (auto-escaped, not HTML): keep their line
   breaks + long-word wrapping without letting a run of text overflow the bubble. */
.msg.email .email-body.plain { white-space: pre-wrap; overflow-wrap: anywhere; }
/* Delivery truth wins over kind color: a failed/blocked send (.msg.failed,
   defined earlier) must still read red, never muted-grey — the extra class
   here (3 selectors) outranks the 2-class .msg.email tie above regardless of
   source order. */
.msg.email.failed { background: var(--error-bg); border-color: #f2c2bd; color: var(--error-text); }
.msg.email.failed .meta { color: var(--error-text); }
/* An unsent composer draft (messaging round 2, "drafts visible"): the same
   muted-grey email card, PLUS a dashed border (the same "not real yet"
   language .chip-scheduled uses) and click affordances — the whole bubble is
   a button that resumes the composer (see _stream_items.html). */
.msg.email.draft-msg { border-style: dashed; font: inherit; text-align: left; cursor: pointer; width: auto; }
.msg.email.draft-msg:hover { background: color-mix(in srgb, var(--email-hue) 8%, var(--surface)); }
.msg.email.draft-msg .email-body { opacity: 0.85; }
/* Long UNsummarized email bubble: clamp the inline body to 8 RENDERED lines
   (owner ask 2026-08-01 — blank lines count, since pre-wrap renders them)
   with a quiet "Show full email" expander (crmToggleEmail flips .clamped +
   the label). line-clamp's own trailing ellipsis signals there is more —
   replacing the old fixed max-height + gradient fade. The server decides
   WHICH emails clamp (web.py _email_is_long: >8 lines or >600 chars, the
   char cap as backstop for a wall of unwrapped text); this rule decides
   WHERE the cut lands. A summarized email skips all of this — its subject
   opens the full-email reader. */
.msg.email .email-body.clamped { display: -webkit-box; -webkit-line-clamp: 8; line-clamp: 8;
  -webkit-box-orient: vertical; overflow: hidden; }
/* The subject is the full-email reader affordance; inbound mail keeps only a
   compact reply icon in the top-right, so actions never consume another row. */
.email-reply { position: absolute; top: 5px; right: 7px; width: 26px; height: 26px; }
.email-reply .ic { width: 0.95em; height: 0.95em; }
.email-expand { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.78rem; }
.msg.email .email-expand { margin-top: 0.35rem; }

/* Public unsubscribe confirmation page (standalone, no app nav). */
.public-shell { min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 1.5rem; background: var(--bg); }
.public-card { width: min(30rem, 94vw); }
.public-h { font-size: 1.2rem; margin: 0 0 0.5rem; }

/* ===== Action toasts (owner ask 2026-07-20): a quiet, auto-dismissing
   notification that says what just happened, with an optional Undo — the old
   global flash/error now render as these (base.html), and the board drag adds
   stage-move toasts live. ONE fixed top-center stack (matches where the old
   flash sat, for continuity). Front-desk-quiet: surface bg + hairline border +
   soft shadow + a thin accent edge — NOT an accent-filled banner. All tokens,
   so it re-skins (incl. a future dark theme) for free. See #toast-stack +
   window.crmToast in base.html. ===== */
.toast-stack {
  position: fixed; top: 0.9rem; left: 50%; transform: translateX(-50%); z-index: 200;
  display: flex; flex-direction: column; gap: 0.5rem;
  width: min(30rem, 94vw);
  pointer-events: none; /* the gaps between toasts never eat clicks */
}
.toast {
  pointer-events: auto; position: relative;
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.6rem 0.75rem 0.7rem;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: var(--radius); box-shadow: var(--pop-shadow);
  overflow: hidden; animation: toast-in 160ms ease-out;
}
.toast-out { opacity: 0; transform: translateY(-6px);
  transition: opacity 220ms ease, transform 220ms ease; }
.toast-body { flex: 1 1 auto; min-width: 0; font-size: 13.5px; line-height: 1.35; }
.toast-error { border-left-color: var(--error-text); }
.toast-error .toast-body { color: var(--error-text); }
/* The two action buttons reset the app's solid-accent button{} base back to a
   quiet control (same discipline as .linklike / .toast-x below). */
.toast-undo {
  flex: none; margin: 0; padding: 0.25rem 0.6rem;
  background: var(--accent-soft); color: var(--accent-dark);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--surface));
  border-radius: var(--radius); font-size: 12.5px; font-weight: 600;
  box-shadow: none; cursor: pointer;
}
.toast-undo:hover { background: color-mix(in srgb, var(--accent) 18%, var(--surface)); }
.toast-undo:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.toast-undo[disabled] { opacity: 0.5; cursor: default; }
.toast-x {
  flex: none; margin: 0; padding: 2px; width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; color: var(--muted); border: 0; border-radius: var(--radius);
  box-shadow: none; cursor: pointer;
}
.toast-x:hover { background: var(--hover); color: var(--text); }
.toast-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.toast-x .ic { width: 14px; height: 14px; }
.toast-bar {
  position: absolute; left: 0; bottom: 0; height: 2px; width: 100%;
  transform-origin: left center;
  background: color-mix(in srgb, var(--accent) 40%, var(--border));
  animation: toast-bar linear forwards;
}
.toast-error .toast-bar { background: color-mix(in srgb, var(--error-text) 40%, var(--border)); }
@keyframes toast-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }
@keyframes toast-bar { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .toast-bar { display: none; }
  .toast-out { transition: none; }
}

/* ===== Global ⌘K search palette (owner ask 2026-07-20: "system-wide search …
   command K … search by phone number by name or by chart ID … quick find
   patients … a quick button depending on … what stage they're in"). crmModal
   standard (blur + scroll-lock via .modal-overlay). All tokens; the palette
   NAVIGATES — every quick action is a link. See app/templates/search/. ===== */
/* The rail-bottom affordance is a plain .tab (mirrors the sign-out button), so it
   only needs the base button{} margin/shadow neutralized to sit flush like the
   others. It lives inside .railfoot (already pushed to the bottom), above the
   user chip. */
.railfoot-search { margin: 0; box-shadow: none; }
.cmdk-kbd {
  font: 11px var(--font-mono); background: var(--bg-inset); border: 1px solid var(--border);
  border-radius: 4px; padding: 0 4px; color: var(--muted); line-height: 1.5;
}
/* A command palette sits near the top, not dead-center. */
.cmdk-overlay { align-items: flex-start; }
.cmdk-modal { width: min(40rem, 94vw); margin-top: 11vh; padding: 0; overflow: hidden; }
.cmdk-inputrow {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.8rem 1rem; border-bottom: 1px solid var(--border);
}
.cmdk-inputicon { flex: none; width: 18px; height: 18px; color: var(--muted); }
.cmdk-inputrow .cmdk-input {
  flex: 1 1 auto; width: 100%; margin: 0; padding: 0;
  border: 0; background: none; color: var(--text); font-size: 16px;
}
.cmdk-inputrow .cmdk-input:focus { outline: none; border: 0; }
.cmdk-inputrow .cmdk-input::placeholder { color: var(--faint); }
.cmdk-results { max-height: 52vh; overflow-y: auto; padding: 0.35rem; }
.cmdk-empty { padding: 1rem 0.75rem; }
.cmdk-list { list-style: none; margin: 0; padding: 0; }
.cmdk-row {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.5rem 0.6rem; border-radius: var(--radius); cursor: pointer;
}
.cmdk-row.cmdk-active { background: var(--accent-soft); }
.cmdk-avatar {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11.5px; font-weight: 700; background: var(--bg-inset); color: var(--text-2);
}
.cmdk-avatar-contact { background: var(--border-soft); color: var(--muted); }
.cmdk-rowbody { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.cmdk-name { font-size: 13.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmdk-context { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmdk-go {
  flex: none; font-size: 12px; font-weight: 600; color: var(--accent-dark);
  background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--surface));
  border-radius: var(--radius); padding: 0.15rem 0.5rem; white-space: nowrap;
}
.cmdk-row.cmdk-active .cmdk-go { background: var(--surface); }
.cmdk-go2 {
  flex: none; font-size: 12px; color: var(--muted); text-decoration: none;
  padding: 0.15rem 0.45rem; border-radius: var(--radius); border: 1px solid var(--border); white-space: nowrap;
}
.cmdk-go2:hover { color: var(--text); background: var(--hover); }
.cmdk-foot { display: flex; gap: 1rem; padding: 0.5rem 0.85rem; border-top: 1px solid var(--border); }
.cmdk-hint { font-size: 11.5px; color: var(--faint); display: inline-flex; align-items: center; gap: 4px; }
@media (max-width: 760px) { .cmdk-foot { display: none; } }

/* ===== The Action card — the context-aware work-a-patient popup =============
   A crmModal (blurred backdrop + scroll-lock) lazy-loaded into #action-card-slot.
   Reuses the shared .modal shell; adds a taller/scrollable body and the grouped
   INFO · ✨summary · script · outcomes layout. Tokenised — a re-theme is a token
   edit; zero inline styles (guard). */

/* The opener — a quiet ghost icon-button on the board card, appointment row, and
   worklist. Modelled on .action-more/.card-menu-toggle so the base solid button{}
   never bleeds through. */
.ac-open { width: 1.9rem; height: 1.9rem; min-width: 1.9rem; padding: 0; margin: 0;
  border: 1px solid transparent; border-radius: var(--radius); color: var(--muted);
  background: none; box-shadow: none; display: inline-flex; align-items: center;
  justify-content: center; cursor: pointer; flex: 0 0 auto; }
.ac-open:hover { background: var(--hover); color: var(--accent); border-color: var(--border); }
.ac-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-open .ic { width: 16px; height: 16px; }
.appt-actions { display: flex; align-items: center; gap: 0.4rem; }

/* The card shell: wider than the default modal, and its own scroll so a tall
   card (summary + scripts + every outcome) never overflows the viewport. */
.modal.ac-modal { width: min(38rem, 94vw); max-height: 88vh; overflow-y: auto;
  padding: 1.1rem 1.25rem; }
/* 1. HEADER — name · stage chip · attempt · ✕, one line that wraps gracefully. */
.ac-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; }
.ac-identity { min-width: 0; }
.ac-head h3.ac-title { margin: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; }
.ac-name { font-size: 1.05rem; }
.ac-phone { color: var(--text-2); font-size: 0.88rem; font-weight: 500; }
.ac-patient-line { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.3rem; }
.ac-chip-attempt { font-size: 11px; font-weight: 700; color: var(--accent-dark);
  background: var(--accent-soft); border-radius: 8px; padding: 1px 7px; }
.ac-x { flex: 0 0 auto; }

/* 2. CONTEXT — responsible party · phone, one muted line. Owner addition
   (ux-review-decisions-2026-08-03 §F): also carries source/offer, SMS
   opt-out, claim/lock state, and (Pending only) Temp + Mindset — a row of
   quiet chips, never a call to action. */
.ac-context { margin-top: 0.25rem; display: flex; flex-wrap: wrap;
  align-items: center; gap: 0.25rem 0.7rem; font-size: 0.82rem; color: var(--text-2); }
.ac-context .chip { font-size: 0.78rem; }
.ac-ctx-item { display: inline-flex; align-items: center; gap: 0.3rem; }
.ac-ctx-item .ic { width: 13px; height: 13px; }
.ac-ctx-warn { color: var(--warn-text); font-weight: 600; }

/* Section rhythm + the small labelled group headers. */
.ac-section { margin-top: 0.9rem; }
.ac-group-label { display: flex; align-items: center; gap: 0.3rem; font-size: 11px;
  font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; color: var(--muted);
  margin-bottom: 0.4rem; }
.ac-group-label .ic { width: 13px; height: 13px; }

/* 4. PRIMARY ACTION — the ONE accent Call button (exactly one blue per state). */
.ac-call-btn { width: 100%; display: inline-flex; align-items: center; justify-content: center;
  gap: 0.35rem; height: 38px; box-sizing: border-box; font-size: 0.92rem; font-weight: 650;
  padding: 0 0.9rem; border: 1px solid var(--accent); border-radius: var(--radius);
  background: var(--accent); color: #fff; cursor: pointer; white-space: nowrap;
  box-shadow: 0 1px 2px rgba(22, 99, 233, 0.28); }
.ac-call-btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); }
.ac-call-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-call-btn:disabled { opacity: 0.6; cursor: default; }
.ac-call-btn .ic { width: 16px; height: 16px; }
.ac-call p { margin: 0.35rem 0 0; }
.ac-live { color: var(--accent-dark); font-weight: 600; }

/* ── The per-action WIZARD (Wave 2, FR-08) ──────────────────────────────────
   Design contract, from the owner: usable, intuitive, uncluttered. Concretely:
     · exactly ONE accent-blue primary per step (.ac-primary-btn / .ac-call-btn)
       — every other control is quiet or ghost;
     · only the current step is in the DOM's flow, so the next click is obvious
       without reading;
     · choices are generous tap targets with a one-line hint, never a dense list;
     · all colour/spacing from :root tokens — no literals, no inline styles. */

/* G49 — presence: quiet advisory line, no icon, no accent — context, not a
   call to action ("<name> is working this now"). */
.ac-presence { font-size: 0.8rem; color: var(--muted); font-style: italic;
  margin: 0.3rem 0 0; }

/* The action's name + the step rail. Reads as "you are doing X, at step N". */
.ac-wizhead { display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; gap: 0.5rem 0.9rem; margin-top: 0.8rem; padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--border-soft); }
.ac-wiz-action { display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.95rem; font-weight: 700; color: var(--text); }
.ac-wiz-action .ic { width: 15px; height: 15px; color: var(--accent); }
.ac-steprail { display: flex; align-items: center; flex-wrap: wrap; gap: 0.75rem;
  list-style: none; margin: 0; padding: 0; }
.ac-steprail-item { display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: 11.5px; font-weight: 600; color: var(--faint); }
.ac-stepnum { display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; border-radius: 50%; font-size: 10px; font-weight: 700;
  background: var(--bg-inset); color: var(--muted); }
.ac-steprail-item.is-done { color: var(--muted); }
.ac-steprail-item.is-done .ac-stepnum { background: var(--ok-bg); color: var(--ok); }
.ac-steprail-item.is-current { color: var(--accent-dark); }
.ac-steprail-item.is-current .ac-stepnum { background: var(--accent); color: #fff; }

/* One step. Hidden steps are truly out of the flow — nothing from a later step
   is visible earlier, which is the entire point of the rebuild. */
.ac-step { margin-top: 0.9rem; }
.ac-step[hidden] { display: none; }
.ac-step-lede { margin: 0 0 0.6rem; font-size: 0.92rem; font-weight: 650; color: var(--text); }

/* The ONE accent primary of a step. Same silhouette as .ac-call-btn but
   auto-width, so a step never shows two blue buttons. */
.ac-primary-btn { display: inline-flex; align-items: center; justify-content: center;
  gap: 0.35rem; height: 34px; box-sizing: border-box; font-size: 0.87rem; font-weight: 650;
  padding: 0 1rem; border: 1px solid var(--accent); border-radius: var(--radius);
  background: var(--accent); color: #fff; cursor: pointer; white-space: nowrap;
  box-shadow: 0 1px 2px rgba(22, 99, 233, 0.28); }
.ac-primary-btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); }
.ac-primary-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-primary-btn.w-full { width: 100%; }

/* Quiet step navigation — never blue, never competing with the primary. */
.ac-quiet-back, .ac-quiet-next { display: inline-flex; align-items: center; gap: 0.25rem;
  background: none; border: 1px solid transparent; border-radius: var(--radius);
  padding: 0.25rem 0.55rem; font-size: 0.82rem; font-weight: 600; color: var(--muted);
  cursor: pointer; text-decoration: none; }
.ac-quiet-back:hover, .ac-quiet-next:hover { background: var(--hover); color: var(--text); border-color: transparent; }
.ac-quiet-back:focus-visible, .ac-quiet-next:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-quiet-back .ic, .ac-quiet-next .ic { width: 12px; height: 12px; }
.ac-call .ac-quiet-next { margin-top: 0.55rem; }

/* The step-2 outcome choices: roomy rows, a label over its one-line hint. Not
   a dense option list — each is a decision, so each gets space. */
.ac-choices { display: grid; gap: 0.45rem; margin-bottom: 0.8rem; }
.ac-choice { display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem;
  width: 100%; text-align: left; padding: 0.6rem 0.75rem; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: none; }
.ac-choice:hover { background: var(--hover); color: var(--text); border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); }
.ac-choice:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-choice-label { font-size: 0.88rem; font-weight: 650; color: var(--text); }
.ac-choice-hint { font-size: 0.76rem; color: var(--muted); font-weight: 400; }

/* Step-3 panels: only the chosen outcome's fields exist. */
.ac-outpanel[hidden], .ac-conpanel[hidden] { display: none; }
.ac-form { display: flex; flex-direction: column; gap: 0.6rem; margin: 0; }
.ac-form textarea, .ac-form input, .ac-form select { width: 100%; box-sizing: border-box; }
.ac-form p { margin: 0; }
.ac-formfoot { display: flex; align-items: center; justify-content: space-between;
  gap: 0.6rem; margin-top: 0.2rem; }
/* W5 reopen redesign: LOST's card stacks two named paths — the second is
   visually separated the same way the old accidental/not-accidental split
   was, just under a clearer name. */
.ac-reopen-path + .ac-reopen-path { margin-top: 0.9rem; padding-top: 0.8rem; border-top: 1px solid var(--border-soft); }
.ac-reopen-path-title { margin: 0 0 0.15rem; font-size: 0.85rem; font-weight: 650; color: var(--text); }
.ac-reopen-fallback { margin-top: 0.5rem; }
.ac-reopen-fallback > summary { cursor: pointer; }

/* ── Log outcome v2 — the one-screen modal (owner-approved 2026-08-01) ────────
   review/log-outcome-wireframe.html is the spec. Everything here is built from
   the existing :root tokens: the green chips reuse --ok/--ok-bg (the app's one
   "this is settled" colour), the selected row reuses --accent-soft, and the
   type pill is the shared .pl-* family — no new palette. */
.lo2-head { align-items: flex-start; }
.lo2-title { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.lo2-contact { display: flex; align-items: center; gap: 0.35rem; margin-top: 0.2rem;
  font-size: 0.85rem; color: var(--muted); }
.lo2-contact .ic { width: 13px; height: 13px; }
.lo2-visit { margin: 0 0 0.6rem; font-size: 0.88rem; font-weight: 650; color: var(--text); }

/* The outcome list: bare rows, no dots, no explanations (owner). Picking one
   hides the rest and turns the survivor into a "change" affordance. */
.lo2-outs { border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; margin-bottom: 0.85rem; }
.lo2-out { display: block; width: 100%; text-align: left; padding: 0.42rem 0.8rem;
  border: 0; border-bottom: 1px solid var(--border-soft); background: var(--surface);
  font: inherit; font-size: 0.88rem; color: var(--text); cursor: pointer; }
.lo2-out:last-child { border-bottom: 0; }
.lo2-out:hover { background: var(--hover); color: var(--text); border-color: var(--border-soft); }
.lo2-out:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.lo2-out[hidden] { display: none; }
.lo2-out.is-sel { background: var(--accent-soft); color: var(--accent-dark); font-weight: 650; }
.lo2-out.is-sel::after { content: "change"; float: right; font-weight: 400;
  font-size: 0.78rem; color: var(--accent-dark); }

.lo2-detail[hidden] { display: none; }
.lo2-detail { border-top: 1px solid var(--border-soft); padding-top: 0.85rem; }

/* Green "already on file" chips — never re-asked; an editable one is a button
   that swaps itself for the field so a wrong value can still be corrected. */
.lo2-known { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.7rem; }
.lo2-chip { display: inline-flex; align-items: center; gap: 0.3rem; height: 1.4rem;
  padding: 0 0.55rem; border: 0; border-radius: 999px; background: var(--ok-bg);
  color: var(--ok); font: inherit; font-size: 0.75rem; font-weight: 650; }
.lo2-chip .ic { width: 11px; height: 11px; }
.lo2-chip[hidden] { display: none; }
.lo2-chip.is-editable { cursor: pointer; }
.lo2-chip.is-editable:hover { background: color-mix(in srgb, var(--ok) 16%, var(--surface)); color: var(--ok); }
.lo2-chip.is-editable:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.lo2-correct[hidden] { display: none; }

/* Fields stack in one column at one width (wireframe): no side-by-side grid,
   because a two-column form is what made the old block feel like a spreadsheet. */
.lo2-fields { display: flex; flex-direction: column; gap: 0.2rem; }
.lo2-fields .fld-label { max-width: 18.5rem; }
.lo2-fields .lo2-note { max-width: 100%; }
.lo2-narrow input { max-width: 9rem; }
.lo2-agerow { display: flex; align-items: flex-end; gap: 1.4rem; }
.lo2-check, .lo2-alt { display: inline-flex; align-items: center; gap: 0.4rem;
  margin: 0; font-weight: 400; /* the bare label{} rule leaks 600 + margins */
  font-size: 0.85rem; color: var(--text-2); }
.lo2-check { padding-bottom: 0.45rem; }
.lo2-check input, .lo2-alt input { width: auto; accent-color: var(--accent); }
.lo2-alt { margin: 0.1rem 0 0.3rem; color: var(--muted); }
.req { color: var(--danger); }

/* cv-6: the optional barrier[] checkbox group (Pending / Scheduled-start
   Log-outcome rows) — the label's .lo2-check row style, wrapped to fit
   however many are ticked instead of forcing one-per-line. */
.lo2-barrier-opts { display: flex; flex-wrap: wrap; gap: 0.15rem 1rem; margin-top: 0.3rem; }
.lo2-barrier-opts .lo2-check { padding-bottom: 0.3rem; }

/* The quiet hairline between "what happened in the room" and "what happens
   next" — a break, deliberately not a heading. */
.lo2-break { border-top: 1px solid var(--border-soft); margin: 0.7rem 0 0.5rem; }

/* The shared appointment date + time control (partials/_appt_datetime.html). */
.apt-dt-label { display: block; margin-bottom: 0.2rem; }
.apt-dt-pair { display: flex; gap: 0.4rem; align-items: center; }
.apt-dt-pair input[type="date"] { max-width: 10.5rem; }
.apt-dt-pair select { max-width: 8rem; }

/* The follow-up affordance (owner convention): the date READS as a sentence,
   with a small pencil to change it — never an always-open editable field. */
.ac-followup { display: flex; flex-direction: column; gap: 0.35rem;
  padding: 0.55rem 0.7rem; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--hover); }
.ac-followup-read { display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: 0.85rem; color: var(--text-2); }
.ac-followup-read .ic { width: 13px; height: 13px; color: var(--muted); }
.ac-followup-read[hidden] { display: none; }
.ac-followup-field[hidden] { display: none; }
.ac-pencil { display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; margin-left: 0.15rem; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: var(--radius);
  color: var(--muted); box-shadow: none; }
.ac-pencil:hover { background: var(--surface); border-color: var(--border); color: var(--accent); }
.ac-pencil:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-pencil .ic { width: 13px; height: 13px; }
/* Where Autopilot legitimately owns the schedule the card SAYS so instead of
   demanding a date (spec §6.1's one sanctioned exception). */
.ac-autopilot-note { display: flex; align-items: center; gap: 0.4rem; margin: 0;
  padding: 0.5rem 0.7rem; font-size: 0.82rem; color: var(--accent-dark);
  background: var(--accent-soft); border-radius: var(--radius); }
.ac-autopilot-note .ic { width: 14px; height: 14px; }

/* The header's ⋯ (this stage's other actions) sits beside the ✕. */
.ac-head-controls { display: flex; align-items: center; gap: 0.25rem; flex: 0 0 auto; }
.ac-overflow .action-list button.linklike { display: flex; align-items: center; gap: 0.4rem; }

/* 5. SCRIPTS — two quiet tabs (Call script / Voicemail) over one content panel. */
.ac-tabs { display: flex; gap: 0.35rem; margin-bottom: 0.5rem; }
.ac-tab { background: var(--surface); border: 1px solid transparent; border-radius: var(--radius);
  padding: 0.3rem 0.6rem; font-size: 12.5px; font-weight: 600; color: var(--muted); cursor: pointer; }
.ac-tab:hover { color: var(--text); background: var(--hover); border-color: var(--border); }
.ac-tab.is-active { color: var(--accent-dark); background: var(--accent-soft); border-color: var(--accent); }
.ac-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-tabpanel[hidden] { display: none; }
.ac-script-body { font-size: 13px; line-height: 1.5; white-space: pre-wrap; color: var(--text); }
.ac-script-note { font-size: 12.5px; line-height: 1.45; color: var(--muted); }
.ac-script-body + .ac-script-note { margin-top: 0.35rem; }
.ac-script-pending { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 12.5px; color: var(--muted); }
.ac-script-pending .ic { width: 13px; height: 13px; }

/* 6. OUTCOMES — one row of quiet buttons; Connected… reveals the panel below. */
.ac-disp-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; }
.ac-disp-row form { margin: 0; }
.ac-outbtn { height: 30px; box-sizing: border-box; font-size: 0.8rem; font-weight: 600;
  padding: 0 0.65rem; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text-2); cursor: pointer; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 0.3rem; }
.ac-outbtn:hover { background: var(--hover); color: var(--text); }
.ac-outbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ac-outbtn .ic { width: 13px; height: 13px; }
.ac-connected-btn[aria-expanded="true"] { background: var(--hover); color: var(--text); }
.ac-connected-panel { display: flex; flex-direction: column; gap: 0.45rem; margin-top: 0.6rem;
  padding: 0.6rem 0 0; border-top: 1px solid var(--border-soft); }
.ac-connected-panel[hidden] { display: none; }
.ac-connected-panel > form { margin: 0; }
.ac-book summary { cursor: pointer; }
.ac-book-form { display: flex; flex-direction: column; gap: 0.3rem; margin: 0.4rem 0 0;
  padding: 0.5rem 0.6rem; border: 1px solid var(--border); border-radius: var(--radius); }
.ac-book-form .action-primary { margin-top: 0.3rem; }
.ac-book-form p { margin: 0; }
.ac-book-grid { display: grid; grid-template-columns: minmax(8rem, 0.8fr) minmax(12rem, 1.2fr);
  gap: 0.6rem; align-items: end; }
.ac-book-grid label { display: flex; flex-direction: column; gap: 0.25rem; }
.ac-book-grid select, .ac-book-grid input { width: 100%; min-width: 0; box-sizing: border-box; }
@media (max-width: 560px) { .ac-book-grid { grid-template-columns: 1fr; } }

/* Inline appointment controls shared by board menus and the Action card. */
.action-book > summary { cursor: pointer; list-style: none; }
.action-book > summary::-webkit-details-marker { display: none; }
.action-book-form { display: flex; flex-direction: column; gap: 0.45rem; min-width: 15rem;
  margin-top: 0.4rem; padding: 0.55rem; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface); }
.action-book-form label { display: flex; flex-direction: column; gap: 0.25rem;
  color: var(--muted); font-size: 0.78rem; }
.action-book-form input { width: 100%; min-width: 0; box-sizing: border-box; }

/* Move controls (reused deal_actions macro) — stacked, not in a dropdown here. */
.ac-move-row form { margin: 0; }
.ac-modal .lost-inline { display: flex; align-items: center; gap: 0.3rem; }
.ac-modal .lost-inline select { max-width: 14rem; }

/* 7. FOOTER — quiet channel pivots + the full record. */
.ac-foot { margin-top: 1rem; padding-top: 0.6rem; border-top: 1px solid var(--border-soft);
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
.ac-foot-channels { display: flex; align-items: center; flex-wrap: wrap; gap: 0.75rem; }
.ac-compose-off { color: var(--muted); opacity: 0.7; cursor: not-allowed; text-decoration: none; }
.ac-foot-record { margin-left: auto; }

/* The per-click ring chooser (office vs the admin's own phone), stacked above
   the card — office is the primary/default. */
.ac-chooser-overlay { z-index: 110; }
.modal.ac-chooser { width: min(22rem, 92vw); }
.ac-chooser-opts { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.4rem; }
.ac-chooser-opts .ac-call-btn, .ac-chooser-opts .action-primary { width: 100%; }
.ac-chooser-num { font-variant-numeric: tabular-nums; opacity: 0.85; font-weight: 600; }

/* ── Beacon station tokens (settings/beacon): visible + copyable ───────── */
.token-cell { display: inline-flex; align-items: center; gap: 0.4rem; }
/* Single readable line — the Copy button is the interaction, so the display
   truncates instead of wrapping (a 43-char token inside a table cell). */
.token-cell code { font-size: 0.78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 13rem; }

/* ── ✨ AI email draft (branch ai/draft-and-call-summaries) ───────────────────
   The email composer's ✨ Draft reuses the SMS composer's .draftbtn affordance in
   the footer. Clicking fills the editor with an editable AI draft (never sent);
   while it's untouched the editor tints purple with a labelled hint, and both
   drop on the first edit — the same "then it's yours" treatment as
   .replyfield.ai-box. All colors come from the shared --ai-* tokens. */
.ec-foot .draftbtn { box-sizing: border-box; }
.ec-editor.ec-ai { background: var(--ai-bg); border-color: var(--ai-border); }
.ec-ai-hint { margin-top: 0.4rem; display: flex; align-items: center; gap: 6px;
  font-size: 0.78rem; color: var(--ai-text); }
.ec-ai-err { margin-top: 0.4rem; }
/* The hidden hand-off slot the ✨ Draft response lands in (the script moves it
   into the editor); never shown. */
.ec-ai-payload { display: none; }

/* ============================================================================
   SMS-composer parity (task #19): composer tools (template picker + emoji
   palette), the grey->accent send-button state, and the /templates library
   tree. New classes, token-based; no inline styles (CI guard). Emoji glyphs
   appear only inside message content / the palette cells — never as UI chrome.
   ========================================================================== */

/* Send button: grey (idle) until the message has content, then accent — the
   PRIMARY action, so accent blue is correct (no-blue-outline standard). Toggled
   by base.html crmInitComposers via data-send-btn/.is-ready. Overrides the
   always-accent base rule above. */
.sendbtn { background: var(--bg-inset); color: var(--muted); transition: background .12s ease, color .12s ease; }
.sendbtn:hover { background: var(--border); }
.sendbtn.is-ready { background: var(--accent); color: #fff; }
.sendbtn.is-ready:hover { background: var(--accent-dark); }

/* One compact plus menu owns the SMS composer's secondary actions. */
.composer-more { position: relative; flex: none; }
.composer-more > summary { list-style: none; }
.composer-more > summary::-webkit-details-marker { display: none; }
.composer-more-toggle { width: 34px; height: 34px; border: 1px solid var(--border);
  border-radius: 50%; background: var(--surface); color: var(--muted); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; }
.composer-more-toggle:hover, .composer-more[open] .composer-more-toggle {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
}
.composer-more-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.composer-more-toggle .ic { width: 18px; height: 18px; }
.composer-more-list { position: absolute; bottom: calc(100% + 8px); left: 0; z-index: 42;
  width: 210px; padding: 6px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--pop-shadow); }
.composer-more-item { display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border: 0; border-radius: var(--radius); background: none;
  color: var(--text); font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
.composer-more-item:hover, .composer-more-item[aria-expanded="true"] {
  background: var(--bg-inset); color: var(--text);
}
.composer-more-item.composer-tool { width: 100%; height: auto; border-radius: var(--radius);
  justify-content: flex-start; }
.composer-more-item .ic { width: 16px; height: 16px; color: var(--muted); flex: none; }
.composer-more-host { display: block; position: relative; width: 100%; }

/* Legacy individual composer-tool classes remain shared by other composer
   mounts; the Messaging reply bar itself now uses .composer-more above. */
.composer-tools { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.composer-tool { width: 32px; height: 32px; border: 0; border-radius: 50%; background: none;
  color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
.composer-tool:hover { background: var(--bg-inset); color: var(--text); }
.composer-tool[aria-expanded="true"] { background: var(--accent-soft); color: var(--accent); }
.composer-tool .ic { width: 18px; height: 18px; }
.tpick, .emoji-picker { position: relative; display: inline-flex; }

/* Shared popover shell — both composer menus open UPWARD (the bar sits at the
   bottom of the pane). */
.tpick-menu, .emoji-menu { position: absolute; bottom: calc(100% + 8px); left: 0; z-index: 40;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--pop-shadow); padding: 6px; }
/* A class `display` would otherwise beat the [hidden] attribute's display:none —
   keep the menus hidden until their button opens them. */
.tpick-menu[hidden], .emoji-menu[hidden] { display: none; }
/* The email composer's picker sits in the modal HEADER, so its menu opens
   DOWNWARD instead of up (SMS composer is at the bottom of the pane). */
.tpick-down .tpick-menu { top: calc(100% + 4px); bottom: auto; }
.composer-more-list .tpick-menu, .composer-more-list .emoji-menu {
  left: calc(100% + 6px); bottom: 0;
}

/* Template picker: drill-down folders -> templates. */
.tpick-menu { width: 300px; max-height: 340px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.tpick-back, .tpick-folder, .tpick-tmpl { display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border: 0; border-radius: var(--radius); background: none; color: var(--text);
  font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
/* Explicit `color` here (not just `background`) because the generic
   `button:hover` rule (styles.css above) sets `color: #fff` and has EQUAL
   selector specificity to this one — being earlier in the file, it would
   otherwise lose the background fight but WIN the color one (the cascade
   picks per-property, not per-rule), leaving white text on this light hover
   background. That's the "hover makes it unreadable" bug — every row here
   inherited it. */
.tpick-back:hover, .tpick-folder:hover, .tpick-tmpl:hover { background: var(--bg-inset); color: var(--text); }
.tpick-back { color: var(--muted); font-weight: 500; }
.tpick-backarrow { font-size: 16px; line-height: 1; }
.tpick-folder-name { flex: 1; }
.tpick-chev { color: var(--muted); }
.tpick-tmpl-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tpick-tmpl-tag { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
  border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 1px 6px; flex-shrink: 0; }
.tpick-auto-h { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
  padding: 8px 10px 3px; margin-top: 2px; border-top: 1px solid var(--border-soft); }
.tpick-empty { padding: 10px; color: var(--muted); font-size: 12px; }
.tpick-menu .ic { width: 16px; height: 16px; flex-shrink: 0; }

/* Emoji palette — a compact grid of characters inserted into the message. */
.emoji-menu { width: 244px; display: grid; grid-template-columns: repeat(6, 1fr); gap: 2px; }
.emoji-cell { border: 0; background: none; border-radius: var(--radius); font-size: 20px; line-height: 1;
  padding: 5px 0; cursor: pointer; }
.emoji-cell:hover { background: var(--bg-inset); }

/* Scheduled-text confirmation note + the chosen-MMS-media chip. */
.composer-note { padding: 6px 14px 0; color: var(--muted); font-size: 12px; display: flex; align-items: center; gap: 6px; }
.composer-note .ic { width: 14px; height: 14px; }
.sms-attach { display: inline-flex; align-items: center; gap: 6px; align-self: center; padding: 3px 8px;
  background: var(--bg-inset); border-radius: var(--radius-pill); font-size: 12px; color: var(--text); max-width: 180px; }
.sms-attach[hidden] { display: none; }  /* class display would beat the [hidden] attribute otherwise */
.sms-attach .ic { width: 13px; height: 13px; color: var(--muted); flex: none; }
.sms-attach-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sms-attach-x { border: 0; background: none; padding: 0; cursor: pointer; color: var(--muted); display: inline-flex; flex: none; }
.sms-attach-x .ic { width: 12px; height: 12px; }

/* Schedule popover — opens upward from the composer tool row. */
.sms-sched { position: relative; display: inline-flex; }
.composer-more-list .sms-sched { display: block; width: 100%; }
.sms-sched-pop { position: absolute; bottom: calc(100% + 8px); left: 0; z-index: 44; width: 300px;
  max-width: calc(100vw - 2rem);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--pop-shadow); padding: 12px; box-sizing: border-box; }
.sms-sched-pop[hidden] { display: none; }
.sms-sched-h { font-weight: 600; display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.sms-sched-h .ic { width: 15px; height: 15px; color: var(--muted); }
.sms-sched-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); gap: 8px; }
.sms-sched-row label { display: flex; flex-direction: column; gap: 3px; font-size: 12px;
  color: var(--muted); min-width: 0; }
.sms-sched-row input { width: 100%; min-width: 0; }
/* JOB2 2026-07-27: was `justify-content: center` with two look-alike quiet
   buttons — neither the standard modal footer's alignment (`.modal-actions`:
   commit bottom-right, quiet Cancel to its left) nor its button tiers (the
   template now gives Schedule `.btn-sm.is-primary`, the small-primary escape
   hatch, so it reads as the actual commit rather than a second Cancel). */
.sms-sched-acts { display: flex; justify-content: flex-end; align-items: center; gap: 8px; margin-top: 10px; }
.composer-more-list .sms-sched-pop { left: calc(100% + 6px); bottom: 0; }

/* ── /templates management page — the editable library tree ── */
.tl-newfolder { flex-wrap: wrap; align-items: flex-end; gap: 8px; margin-bottom: 14px; }
.tl-tree { list-style: none; margin: 0; padding: 0; }
.tl-tree .tl-tree { margin-left: 18px; border-left: 2px solid var(--border-soft); padding-left: 10px; }
.tl-folder { margin: 6px 0; }
.tl-folder-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.tl-folder-name { font-weight: 600; display: inline-flex; align-items: center; gap: 6px; }
.tl-folder-name .ic { width: 15px; height: 15px; color: var(--muted); }
/* CR-011 UXD-121: Rename reveals on demand (same disclosure pattern as
   .composer-more) instead of an always-open, fixed-width input sitting in
   the folder row and crowding Delete off onto its own wrapped line. */
.tl-rename-toggle { position: relative; }
.tl-rename-toggle > summary { list-style: none; cursor: pointer; }
.tl-rename-toggle > summary::-webkit-details-marker { display: none; }
.tl-rename-form { position: absolute; top: calc(100% + 6px); left: 0; z-index: 5;
  display: flex; align-items: center; gap: 6px; padding: 8px; white-space: nowrap;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--pop-shadow); }
.tl-rename-form input { width: 14rem; }
.tl-templates { list-style: none; margin: 4px 0 4px 22px; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.tl-tmpl { display: flex; align-items: center; gap: 8px; padding: 4px 0; position: relative; }
.tl-tmpl-name { flex: 1; font-size: 13px; }
/* Hover peek: the row's exact stored wording, shown while the pointer rests on
   the template's name (or on the peek itself, so long copy can be scrolled).
   Merge slugs render as read-only pills (.tl-mf) — never resolved here. */
.tl-peek { position: absolute; z-index: 120; left: 0; right: auto; top: calc(100% + 4px);
  width: 34rem; max-width: 92vw; max-height: 22rem; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--pop-shadow); padding: 0.7rem 0.85rem;
  font-size: 0.85rem; line-height: 1.55; color: var(--text);
  visibility: hidden; opacity: 0; transition: opacity 0.12s ease 0.25s, visibility 0s linear 0.37s; }
.tl-tmpl-link:hover ~ .tl-peek, .tl-peek:hover { visibility: visible; opacity: 1; }
.tl-peek-subj { font-weight: 600; margin-bottom: 0.45rem; }
.tl-peek-body { white-space: pre-wrap; }
.tl-mf { display: inline; background: var(--surface); border: 1px solid var(--border);
  color: var(--accent-dark); font-family: var(--font-mono); font-size: 0.78em;
  padding: 0 0.3rem; border-radius: 5px; white-space: nowrap; }
.tl-ch { font-size: 10px; text-transform: uppercase; letter-spacing: .03em; }
.tl-ch-sms { background: var(--accent-soft); color: var(--accent); }
.tl-ch-email { background: var(--bg-inset); color: var(--muted); }
.tl-move select { max-width: 12rem; }
.tl-unfiled-h { margin: 16px 0 6px; font-size: 13px; color: var(--muted); }
.tl-auto-group { margin: 10px 0; }
.tl-auto-track { font-weight: 600; display: inline-flex; align-items: center; gap: 6px; margin-bottom: 2px; }
.tl-auto-track .ic { width: 15px; height: 15px; color: var(--muted); }
.tl-tmpl-ro { color: var(--muted); }
.tl-ch-voice { background: var(--warn-bg); color: var(--warn-text); }
/* A template name is a link into the wording editor — quiet (no underline/visited
   color, per the interaction standard); the accent shows on hover/focus only. */
.tl-tmpl-link { color: var(--text); text-decoration: none; cursor: pointer; }
.tl-tmpl-link:hover { color: var(--accent); }
.tl-tmpl-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ===== Template wording editor (/templates/{id}) + the Practice-settings "Phone
   greetings" section (2026-07-22). tpe-* is the shared editor; vp-* wraps the two
   voice prompts on Practice settings. Zero inline styles — everything lives here,
   built on the same tokens as the composer. ===== */
.ic-back { transform: rotate(90deg); }  /* chevron points down → left, a back arrow */
.tpe-page { max-width: 46rem; }
.tpe-back { display: inline-flex; align-items: center; gap: 0.2rem; margin-bottom: 0.6rem; }
.tpe-back .ic { width: 0.9em; height: 0.9em; }
.tpe-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.tpe-title { margin: 0; font-size: 1.3rem; }
.tpe-key { margin-top: 0.15rem; }
.tpe-key code { font-family: var(--font-mono); font-size: 0.85em; background: var(--hover);
  border: 1px solid var(--border-soft); border-radius: 4px; padding: 0 0.25rem; }
.tpe-caution { display: flex; gap: 0.45rem; align-items: flex-start; margin: 0.9rem 0;
  padding: 0.55rem 0.7rem; border: 1px solid color-mix(in srgb, var(--warn-text) 28%, var(--surface));
  background: var(--warn-bg); border-radius: var(--radius); font-size: 0.85rem; line-height: 1.45; }
.tpe-caution .ic { color: var(--warn-text); flex: none; margin-top: 0.1rem; }
.tpe-form { max-width: 40rem; margin-top: 1rem; }
.tpe-label { display: block; font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--muted); margin: 0.9rem 0 0.25rem; }
.tpe-toolbar { display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; margin-bottom: 0.35rem; }
.tpe-tb { display: inline-flex; align-items: center; gap: 0.25rem; width: auto; min-width: 0;
  padding: 0.25rem 0.5rem; font-size: 0.76rem; font-weight: 600; color: var(--text-2);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; }
.tpe-tb:hover { border-color: var(--accent); background: var(--hover); }
.tpe-tb .ic { width: 0.95em; height: 0.95em; }
.tpe-mftray { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; padding: 0.4rem 0.5rem;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--hover); margin-bottom: 0.4rem; }
.tpe-mftray[hidden] { display: none; }
.tpe-mftray-lbl { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--muted); }
.tpe-mfpill { margin: 0; width: auto; background: var(--surface); border: 1px solid var(--border);
  color: var(--accent-dark); font-family: var(--font-mono); font-size: 0.78rem; padding: 0.1rem 0.4rem;
  border-radius: 5px; cursor: pointer; }
.tpe-mfpill:hover { border-color: var(--accent); background: var(--accent-soft); }
.tpe-body { width: 100%; font: inherit; font-size: 0.9rem; line-height: 1.55; color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 0.55rem 0.65rem; min-height: 6rem; }
.tpe-body:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
.tpe-voicehint { margin-top: 0.3rem; }
.tpe-voicehint-warn { color: var(--warn-text); }
.tpe-actions { margin-top: 1rem; }
.tpe-readonly { margin-top: 1rem; }
.tpe-ro-field { margin: 0.5rem 0; }
.tpe-ro-k { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); margin-right: 0.4rem; }
.tpe-ro-body { white-space: pre-wrap; font: inherit; font-size: 0.9rem; line-height: 1.55; color: var(--text);
  background: var(--hover); border: 1px solid var(--border-soft); border-radius: var(--radius);
  padding: 0.55rem 0.65rem; margin: 0.2rem 0 0; }
.tpe-preview { margin-top: 1.25rem; border-top: 1px solid var(--border-soft); padding-top: 0.8rem; }
.tpe-prev-head { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.55rem; }
.tpe-prev-head > .ic { color: var(--accent); }
.tpe-prev-title { font-weight: 650; font-size: 0.9rem; }
.tpe-prev-body { white-space: pre-wrap; font-size: 0.9rem; line-height: 1.55; color: var(--text); }
.tpe-prev-subj { font-size: 0.9rem; margin-bottom: 0.5rem; }
.tpe-prev-k { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); margin-right: 0.35rem; }
/* Phone-greetings section (Practice settings) — reuses the tpe-* editor bits. */
.vp-prompt { margin: 0.9rem 0 1.4rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border-soft); }
.vp-prompt:last-of-type { border-bottom: 0; padding-bottom: 0; }
.vp-preview { margin-top: 0.8rem; }
.vp-lenhint[hidden] { display: none; }

/* ── Call/voicemail media UX (calls/media-ux) ───────────────────────────────
   The ✨ call summary shows first on the timeline; "View transcript" opens the
   full transcript (PHI) in the shared reader modal (reuses .modal-overlay/.modal
   + the .er-* reader chrome). New here: the quiet affordance, the speaker turns,
   and the compact voicemail player in the Needs-attention list. All tokens. */
/* Quiet "view in reader" affordance, same idiom/size as the email .email-viewfull
   (button.linklike base supplies the muted color + focus ring). */
.view-transcript { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.78rem; }
.view-transcript .ic { width: 0.95em; height: 0.95em; }
/* Speaker turns in the transcript reader (.er-body supplies padding + scroll).
   A bold neutral speaker prefix keeps turns scannable without a new color. */
.ct-body .ct-turn { margin: 0 0 0.5rem; }
.ct-body .ct-turn:last-child { margin-bottom: 0; }
.ct-speaker { font-weight: 700; color: var(--text); }
/* The reader title's ✨ tag sits inline with the "Call transcript" label. */
.er-subject .ai-tag { margin-left: 0.35rem; vertical-align: middle; }
/* Compact voicemail player inside the Needs-attention Details cell (narrower
   than the timeline .call-audio it extends). */
.na-vm-audio { width: min(16rem, 100%); margin: 0.35rem 0 0.1rem; }

/* ── Timeline single attribution (owner de-redundancy, 2026-07-23) ──
   ONE leading actor byline per row (replacing the old patient-name prefix AND
   the duplicate trailing "— Name"). Bold neutral like the prefix it replaces;
   an inbound entry keeps the mild-green .tl-author-contact treatment defined
   above, so "the contact is speaking" still reads at a glance. */
.tl-author { font-weight: 600; }
/* Per-entry patient attribution (owner ask 2026-08-01): the timeline is always
   the whole family stream, so a patient-scoped entry names its patient with a
   quiet muted label at the start of the row — attribution without a filter. */
.tl-patient { color: var(--muted); font-weight: 600; font-size: 0.85rem; margin-right: 0.15rem; }

/* ── Inline "View transcript" in the shared call-summary box (2026-07-23) ── */
.ai-call-summary-body { flex: 1; min-width: 0; }
.ai-call-vt { margin-left: auto; align-self: flex-start; white-space: nowrap; }

/* ── Calls in the messaging conversation stream (calls-in-messaging, 2026-07-23)
   A call is an EVENT, not a chat bubble: a centered card in the message column
   (align-self:center, like .daysep) carrying its outcome line + time, the ✨
   summary (shared .ai-call-summary partial), and the recording player when saved.
   Orange = the call kind color (--call-hue), matching the
   contact timeline's own call rows so the two surfaces read as one family; a
   MISSED inbound reads red (the alert), like the timeline's missed chip. */
.callmsg { align-self: center; width: min(96%, 42rem); margin: 8px 0 2px;
  background: var(--call-bg); border: 1px solid var(--call-border); border-radius: 12px;
  padding: 8px 12px; font-size: 13px; box-sizing: border-box; }
.callmsg.missed { background: var(--error-bg);
  border-color: color-mix(in srgb, var(--error-text) 24%, var(--surface)); }
.callmsg-line { display: flex; align-items: center; gap: 6px; color: var(--text); }
.callmsg.missed .callmsg-line { color: var(--error-text); }
.callmsg-ic { display: inline-flex; align-items: center; flex: none;
  color: color-mix(in srgb, var(--call-hue) 70%, var(--text)); }
.callmsg.missed .callmsg-ic { color: var(--error-text); }
.callmsg-ic .ic { width: 0.95em; height: 0.95em; }
.callmsg-body { flex: 1; min-width: 0; }
/* Speaker attribution on a call card (migration 0060): the office-side staff
   member's first name, quiet by design — it is audit context, not the headline.
   Rendered only when the attribution is KNOWN, so there is no "unknown" state to
   style. */
.callmsg-who { color: var(--muted); font-size: 11px; font-weight: 600;
  white-space: nowrap; flex: none; }
.callmsg.missed .callmsg-who { color: var(--error-text); }
.callmsg-time { color: var(--muted); font-size: 10.5px; white-space: nowrap; }
.callmsg-audio { margin: 6px 0 2px; }
/* In Messaging, the AI pill + transcript affordance form a compact header.
   The summary itself gets the entire second row and full card width. */
.callmsg .ai-call-summary { display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 6px 10px; margin-top: 6px; }
.callmsg .ai-call-summary > .ai-tag { grid-column: 1; grid-row: 1; justify-self: start; }
.callmsg .ai-call-vt { grid-column: 2; grid-row: 1; margin-left: 0; align-self: center; }
.callmsg .ai-call-summary-body { grid-column: 1 / -1; grid-row: 2; width: 100%; }

/* ── Schedule-text popup date/time inputs (owner polish, 2026-07-23) ──
   The Schedule-text popup's native <input type=date/time> fields, brought to
   the app input standard within the compact popover — the global `input`
   rule is sized for full-width forms, so these get tighter padding + a smaller
   font (mirrors the email composer's own .ec-pop-row inputs, so the two
   schedule popups read the same). Focus states inherit the global input:focus /
   input:focus-visible; the width:100% from .sms-sched-row input above stays. */
.sms-sched-row input { padding: 0.3rem 0.4rem; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface); color: var(--text);
  font: inherit; font-size: 0.78rem; min-height: 1.9rem; box-sizing: border-box; }
.sms-sched-row input::-webkit-calendar-picker-indicator { opacity: 0.55; cursor: pointer; }
.sms-sched-row input:hover::-webkit-calendar-picker-indicator { opacity: 0.85; }

/* ===== Global call-now banner (base.html + GET /callnow/banner) =============
   A slim, single-line, every-page banner naming the oldest UNCLAIMED fresh lead
   with a live count-up and a Claim & call. Quiet by design: token colors, one
   line, no motion beyond the ticking clock. Aligned to the page content column
   (same max-width + gutter as .page). Replaces the old dashboard hero card. */
.callnow-banner { display: flex; justify-content: center; align-items: stretch; flex-wrap: wrap; gap: 0.6rem;
  width: calc(100% - 2.5rem); max-width: calc(var(--page-max) - 2.5rem);
  margin: 0.75rem auto 0; box-sizing: border-box; font-size: 13px; }
.callnow-banner[hidden] { display: none; }
/* Messaging (JOB2 2026-07-27): `.page:has(.msgapp)` is the one full-bleed
   shell — no shell margin/gutter for the banner to align to (the comment
   above says "aligned to the page content column", but there IS no column
   here). Narrow + centered + gutter-margined above an edge-to-edge white
   panel left bare grey (the plain --bg) on both sides and above — the
   "unexplained grey space at the top" Skyler flagged. Full-width and
   flush, with its own background + divider, reads as the chat shell's own
   top bar instead of a stray floating card. */
.appcol:has(.msgapp) .callnow-banner:not([hidden]) {
  width: 100%; max-width: none; margin: 0; padding: 0.5rem 1.25rem;
  justify-content: flex-start; background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.callnow-card { display: flex; align-items: center; gap: 0.55rem; flex: 1 1 24rem;
  max-width: 31rem; min-width: min(100%, 20rem); padding: 0.45rem 0.7rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); box-sizing: border-box; }
.callnow-card--orange { border-color: var(--warn-text); }
.callnow-card--red { border-color: var(--danger); }
/* The escalation dot — colored green → amber → red by the SERVER thresholds the
   Beacon shares (helper.service.beacon_thresholds), so both dots always agree. */
.callnow-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; background: var(--muted); }
.callnow-dot--green { background: var(--ok); }
.callnow-dot--orange { background: var(--warn-text); }
.callnow-dot--red { background: var(--danger); }
.callnow-msg { color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.callnow-name { font-weight: 700; }
.callnow-clock { font-variant-numeric: tabular-nums; font-weight: 700; }
.callnow-more { color: var(--muted); font-size: 12px; flex: 0 0 auto; }
.callnow-grow { flex: 1 1 auto; }
/* Owner walkthrough 2026-07-27 (item A): the button sat low in the card. The
   global `button { margin-top: 1rem }` reset (the same leak `.action-controls
   button` and `.action-menu > summary` elsewhere in this file counter) was
   never overridden here, so this button's flex box carried an extra 1rem on
   top only — `.callnow-card`'s align-items:center then centered THAT taller
   box, not the button's own visible face, reading as pushed toward the floor
   of the card. */
.callnow-claim { flex: 0 0 auto; margin: 0; background: var(--accent); color: #fff; border: 1px solid var(--accent);
  border-radius: var(--radius); font-weight: 650; font-size: 12.5px; padding: 0.35rem 0.9rem;
  cursor: pointer; white-space: nowrap; box-shadow: 0 1px 2px rgba(22, 99, 233, 0.28); }
.callnow-claim:hover { background: var(--accent-dark); }
.callnow-claim:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.callnow-x { flex: 0 0 auto; width: 1.6rem; height: 1.6rem; display: inline-flex; align-items: center;
  justify-content: center; border: 1px solid transparent; border-radius: var(--radius);
  background: none; color: var(--muted); cursor: pointer; padding: 0; }
.callnow-x:hover { background: var(--hover); color: var(--text); }
.callnow-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.callnow-x .ic { width: 14px; height: 14px; }

/* ── Templates v2 (2026-07-23): friendly Auto names, single Auto folder, the
   manual-template manager (create form + merge-field drag-drop), and the
   composer "Manage templates…" entry point. All tokens, no inline styles. ── */

/* The single "Auto" folder in the composer picker — its Autopilot tag sits
   inline before the chevron (it drills into pipeline subfolders). */
.tpick-folder-auto .tpick-tmpl-tag { margin-left: auto; }

/* Merge-field drop target: a dashed accent ring while a pill is dragged over
   the message textarea (wording editor + New-template form share it). */
.mf-drop { outline: 2px dashed var(--accent); outline-offset: -2px; }

/* Composer "Manage templates…" affordance (admin-write/staff-read). Email
   composer: a ghost link in the header; SMS: a tool-row icon. */
.ec-manage-tpl { display: inline-flex; align-items: center; gap: 0.25rem; white-space: nowrap; }
.ec-manage-tpl .ic { width: 0.95em; height: 0.95em; }
.sms-manage-tpl { text-decoration: none; }

/* New-template form (admin) on the /templates library — the create half of the
   manager; reuses the wording editor's tray + preview (tpe-* / mf-tray). */
.tl-newtmpl-wrap { margin: 0.4rem 0 0.9rem; }
/* /templates' one true primary (CR-011 UXD-119). The page used to render
   THREE accent-filled buttons — Add folder, Rename and Delete… — while its
   actual create action was a 21px ghost. The folder controls are now the
   quiet tier (.btn-sm) and this disclosure carries the accent. */
.tl-newtmpl-summary { display: inline-flex; align-items: center; justify-content: center; gap: var(--btn-icon-gap);
  cursor: pointer; height: var(--control-h-sm); padding: 0 var(--sp-4);
  font-size: var(--fs-3); font-weight: 600; color: #fff;
  background: var(--accent); border: 1px solid var(--accent); border-radius: var(--radius);
  box-shadow: 0 1px 2px rgba(22, 99, 233, 0.28);
  list-style: none; width: max-content; }
.tl-newtmpl-summary::-webkit-details-marker { display: none; }
.tl-newtmpl-summary:hover { border-color: var(--accent-dark); background: var(--accent-dark); }
.tl-newtmpl-summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tl-newtmpl-summary .ic { width: 0.95em; height: 0.95em; }
.tl-newtmpl[open], .tl-newtmpl { margin-top: 0.7rem; max-width: 40rem; }
.tl-nt-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.tl-nt-field { display: flex; flex-direction: column; gap: 0.2rem; flex: 1 1 10rem; }
.tl-nt-field label { font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--muted); }
.tl-nt-field[hidden] { display: none; }
.tl-nt-lbl { display: block; font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--muted); margin: 0.8rem 0 0.3rem; }
.tl-nt-tray { margin-bottom: 0.4rem; }
.tl-nt-body { width: 100%; font: inherit; font-size: 0.9rem; line-height: 1.55; color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 0.55rem 0.65rem; min-height: 5rem; }
.tl-nt-body:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
.tl-nt-actions { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.8rem; }
.tl-nt-preview:not(:empty) { margin-top: 0.9rem; padding-top: 0.7rem; border-top: 1px solid var(--border-soft); }

/* Archive (soft-delete) controls on the library — a quiet per-row "Archive"
   secondary action + the muted Archived section listing restorable templates. */
.tl-archive { margin: 0; }
.tl-archive-btn { font-size: 0.78rem; }
.tl-archive-btn:hover { color: var(--danger, #c0392b); }
.tl-archived-h { margin-top: 1.4rem; }
.tl-archived .tl-tmpl-name { color: var(--muted); }

/* ===================================================================
   Reduced motion (deep UX audit #52, 2026-07-24)
   -------------------------------------------------------------------
   LAST in the file on purpose: it must out-cascade every rule above it.

   What motion this app actually has — the block is written against this
   inventory, not as a reflex:
     · the icon rail sliding 54px → 224px on pin (width + min-width, .16s)
     · the rail-pin chevron rotating -90° → 90°, and the pin's `left` sliding
       with the rail
     · label/tooltip/logo opacity fades (.12–.14s) in the rail and user chip
     · the toast stack: a translateY(-8px) slide-in, a translateY exit, and a
       6-second scaleX countdown bar that is continuous motion by definition
     · the Messaging send button's colour transition
   Everything above is decoration — nothing in this app conveys meaning
   THROUGH movement — so collapsing the durations to a hair above zero keeps
   every end state (rail expanded, chevron rotated, label visible) and drops
   only the travel. Durations are near-zero rather than 0 so JS that waits on
   `transitionend`/`animationend` (the toast dismissal does) still gets its
   event and never leaks an element.

   Transform-driven travel is additionally cancelled outright, because a
   near-instant slide is still a flicker; the countdown bar, which is nothing
   but motion, is removed rather than frozen mid-scale.

   The pre-existing toast-only block further up stays — it says the same thing
   for .toast/.toast-bar/.toast-out and is harmless overlap. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  /* No travel: land on the end state immediately rather than sliding to it. */
  .toast { animation: none !important; transform: none !important; }
  .toast-out { transition: none !important; transform: none !important; }
  /* A 6s scaling progress bar is motion with no still frame worth keeping. */
  .toast-bar { display: none !important; }
  /* The rail's width change is the app's largest movement; snap it. */
  .rail, .rail-pin { transition: none !important; }
}

/* ── The blocked-numbers page (task #53) ──────────────────────────────────
   A short admin form, held to the shared form measure so the inputs don't
   stretch to a full-width card and read as bigger than the decision is. */
.blocked-add { max-width: var(--form-measure); }
.blocked-add input[type="text"] { width: 100%; }
.blocked-add .btn { margin-top: 14px; }
/* The blocklist link rides the outcome filter row, pushed to its far end so it
   reads as a destination, not another filter chip. */
.callog-blocklink { margin-left: auto; }

/* ── Sibling travel groups (0077) ─────────────────────────────────────────
   The suggestion sits inside the Patients card, so it is a quiet inset panel
   rather than another bordered card competing with the patient cards it is
   about. */
.sib-suggest { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 24%, var(--surface));
  margin: 0 0 10px; padding: 12px 14px; box-shadow: none; }
.sib-suggest-line { margin: 0 0 4px; }
.sib-suggest .btn { margin-top: 10px; }
/* The "travels with …" line belongs to the card above it, so it tucks under
   with no gap and indents to sit inside the card's own text column. */
.sib-line { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap; margin: -4px 0 10px; padding: 0 12px; }

/* ── Record-card edit controls: sized to what they hold (Skyler, 2026-07-30;
   supersedes the one-width rule of 2026-07-27). The uniform 11rem read fine
   until the fields multiplied — a six-character chart number in an 11rem box
   is mostly box, and the expanded card looked wider than its data. A select
   auto-sizes to its longest option, a date input to its format, and the chart
   number gets exactly its six characters plus breathing room. max-width keeps
   everything inside the rail. Scoped to .rec-card so no other screen moves. */
.rec-card .rec-edit select,
.rec-card .rec-edit input[type="date"] { flex: none; width: auto; max-width: 100%; }
/* .inline-form's own `input { flex: 1 }` would stretch these across the dd —
   the flex reset has to ride the same selector as the width for it to hold. */
.rec-card .rec-edit .inline-form input,
.rec-card .rec-edit.inline-form input { flex: none; }
/* input.chartno-input (not bare .chartno-input): the global field rule's
   `input:not(...)x3` weighs 0,3,1 — this must tie it to win by order. */
.rec-card .rec-edit input.chartno-input { flex: none; width: 11ch; min-width: 11ch; max-width: 100%; }

/* ── Contact record: three columns (Skyler, 2026-07-27) ───────────────────
   left = the records (contact + patients) · centre = the timeline, which is
   what you came to read · right = ✨ AI. The right rail is deliberately the
   SAME width as the left, so the timeline sits visually centred rather than
   shoved off to one side by a lopsided third column. Mirrors Messaging, where
   AI has always been on the right.

   Below ~1200px the third column has nowhere honest to go, so the rail drops
   under the timeline rather than squeezing all three into a strip. */
/* Width comes from .side — the two rails are the same by construction, not
   by two numbers that have to be kept in step. */
@media (max-width: 1200px) {
  .cols:has(.side-ai) { flex-wrap: wrap; }
  .cols .side-ai { width: 100%; }
}

/* ── Contact card parity with the patient card (2026-07-27, tightened
   2026-07-30) ── the same `.rec-card` + `dl.fields` the patient panel uses,
   so the two halves of a family read identically. What's left here is only
   what is genuinely contact-specific: the plain channel value and the inline
   edit form's text fields (full-bleed — names, emails and notes are long;
   the select stays content-sized via the shared record-card rule). */
.cc-val-plain { color: var(--text); }
.cc-editform input, .cc-editform textarea { width: 100%; }

/* A chart number is six characters and always upper-case (Skyler, 2026-07-27).
   The transform is cosmetic — what makes it TRUE is clean_chart_no() upper-casing
   on the way in, so a value typed lower-case at one screen and upper at another
   can never become two patients to the exam-note webhook that matches on it. */
.chart-upper { text-transform: uppercase; }

/* ── Quick-pick date shortcuts (Skyler, 2026-07-31) ─────────────────────────
   The ⚡ beside every date input opens this popover: relative shortcuts
   (3 days … 6 months) that set the field and fire `change`, so autosaving
   forms save as if typed. Quiet ghost affordance per the button standards. */
.qd-btn { display: inline-flex; align-items: center; justify-content: center; margin: 0 0 0 0.3rem;
  padding: 0; width: var(--control-h); height: var(--control-h); border: 1px solid var(--border);
  border-radius: var(--radius); background: none; box-shadow: none; color: var(--muted);
  cursor: pointer; vertical-align: middle; flex: none; }
.qd-btn:hover { background: var(--hover); color: var(--text); }
.qd-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qd-pop { position: absolute; z-index: 120; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--pop-shadow); padding: 0.45rem;
  display: flex; gap: 0.6rem; align-items: flex-start; }
.qd-col { display: flex; flex-direction: column; gap: 0.1rem; min-width: 6.4rem; }
.qd-colh { font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); padding: 0.15rem 0.55rem 0.25rem; }
.qd-opt { display: flex; justify-content: space-between; align-items: baseline; gap: 0.7rem;
  margin: 0; padding: 0.35rem 0.55rem; border: 0; border-radius: var(--radius); background: none;
  box-shadow: none; font-size: 0.85rem; color: var(--text); cursor: pointer; text-align: left; }
.qd-opt:hover, .qd-opt:focus-visible { background: var(--hover); color: var(--text); outline: none; }
.qd-date { font-size: 0.72rem; color: var(--muted); }

/* ── Cadence editor Phase 4 (/autopilot/<track>) ─────────────────────────────
   One page per cadence: the √-compressed timeline "cheater" strip (the review
   center's tlx visual language, rebuilt on the app's tokens), the step cards
   with an editable gap + on/off switch + up/down reorder, and the shared
   locked-guardrails panel. Dots position from the --apx-f custom property
   (set at runtime from data-f — no inline styles in templates). */
.apx-track-toggle { flex: none; }

/* The on/off switch — a quiet toggle (accent only when ON, per the button
   standards: blue marks primary/active state). Used for the track switch and
   each message's switch. */
.apx-switch { display: inline-flex; align-items: center; gap: 0.45rem; cursor: pointer; }
.apx-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.apx-switch-track { display: inline-flex; align-items: center; width: 2.1rem; height: 1.15rem;
  padding: 0.1rem; border-radius: 999px; background: var(--border-strong); flex: none;
  transition: background 0.15s ease; }
.apx-switch-knob { width: 0.95rem; height: 0.95rem; border-radius: 50%; background: var(--surface);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); transition: transform 0.15s ease; }
.apx-switch input:checked + .apx-switch-track { background: var(--accent); }
.apx-switch input:checked + .apx-switch-track .apx-switch-knob { transform: translateX(0.95rem); }
.apx-switch input:focus-visible + .apx-switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
.apx-switch-label { font-size: 0.8rem; font-weight: 600; color: var(--text-2); }

/* Timeline strip */
.apx-tl-card { margin-bottom: 10px; }
.apx-tl-sum { margin: 0 0 4px; font-size: 0.85rem; }
.apx-tl { position: relative; height: 88px; margin: 12px 2px 2px; }
.apx-tl-rail { position: absolute; left: 24px; right: 24px; top: 52px; height: 2px; background: var(--border); }
.apx-tl-ends { position: absolute; left: 24px; right: 24px; top: 4px; display: flex;
  justify-content: space-between; font-size: 0.72rem; color: var(--muted); font-weight: 600; }
.apx-tl-dot { position: absolute; top: 52px; width: 13px; height: 13px; border-radius: 50%;
  transform: translate(-50%, -50%); border: 2px solid var(--surface);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.16); text-decoration: none;
  left: calc(24px + (100% - 48px) * var(--apx-f, 0)); }
.apx-tl-dot:hover { box-shadow: 0 0 0 2px var(--accent); }
.apx-tl-dot > i { position: absolute; left: 50%; transform: translateX(-50%); font-style: normal;
  font-size: 0.66rem; font-weight: 700; color: var(--text-2); white-space: nowrap; }
.apx-tl-dot.up > i { bottom: 15px; }
.apx-tl-dot.down > i { top: 15px; }
.apx-tl-sms { background: var(--accent); }
.apx-tl-email { background: var(--s2); }
.apx-tl-dot.apx-tl-off { background: var(--border-strong); }
.apx-tl-dot.apx-tl-off > i { color: var(--muted); text-decoration: line-through; }
.apx-tl-legend { display: flex; gap: 14px; margin-top: 4px; font-size: 0.75rem; color: var(--muted); }
.apx-tl-legend .li { display: flex; gap: 6px; align-items: center; }
.apx-tl-k { width: 11px; height: 11px; border-radius: 50%; flex: none;
  border: 2px solid var(--surface); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.14); }
.apx-tl-k.apx-tl-off { background: var(--border-strong); }

/* Step cards */
.apx-steps { list-style: none; margin: 14px 0 20px; padding: 0; display: flex;
  flex-direction: column; gap: 10px; }
.apx-step { display: flex; gap: 10px; align-items: stretch; scroll-margin-top: 70px; }
.apx-step-rail { flex: 0 0 2.2rem; display: flex; flex-direction: column; align-items: center;
  gap: 6px; padding-top: 10px; }
.apx-step-n { font-size: 0.75rem; font-weight: 700; color: var(--muted); }
.apx-move { display: flex; flex-direction: column; gap: 2px; }
.apx-move-btn { padding: 0.1rem 0.3rem; }
.apx-step-body { flex: 1; min-width: 0; }
.apx-step-off .apx-step-body { opacity: 0.65; }
.apx-step-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.apx-step-title { font-weight: 700; font-size: 0.92rem; }
.apx-step-key { font-family: var(--font-mono); font-size: 0.72rem; color: var(--muted); }
.apx-step-day { font-weight: 600; }
.apx-step-onoff { margin-left: auto; flex: none; }
.apx-off-note { margin: 6px 0 0; }
.apx-gap-form { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0 2px;
  padding: 7px 10px; border: 1px solid var(--border-soft); border-radius: var(--radius);
  background: var(--hover); font-size: 0.85rem; }
.apx-gap-sentence { display: inline-flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; }
/* Beats the global full-width control rule: the gap number is a small inline
   field inside a sentence, not a form column. */
.apx-gap-form input.apx-gap-input[type="number"] { width: 4.6rem; text-align: center; }
.apx-gap-save { flex: none; }
.apx-preview { margin-top: 10px; border-top: 1px dashed var(--border-soft); padding-top: 8px; }
.apx-edit-copy { display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; }

/* ===== Templates split-pane (owner ask 2026-08-01): the /templates library on
   the left in its own scroll container, the wording editor stationary on the
   right. Under --tls-collapse the two stack (left first). tls-* is layout only —
   the editor's internals stay tpe-*, the library rows tl-*. ===== */
.tls-page { max-width: 100rem; margin: 0 auto; }
.tls-split { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 4fr);
  gap: 1rem; align-items: start; }
/* Left: the library scrolls inside its card so the editor never drifts away. */
.tls-left { margin: 2rem 0 0; max-width: none; max-height: calc(100vh - 7.5rem);
  overflow-y: auto; overscroll-behavior: contain; }
/* Right: stationary — sticks below the top bar while the left side scrolls. */
.tls-right { position: sticky; top: 1rem; min-width: 0; }
.tls-editor-card { margin: 2rem 0 0; max-width: none;
  max-height: calc(100vh - 7.5rem); overflow-y: auto; }
.tls-empty { display: flex; flex-direction: column; align-items: center; gap: 0.4rem;
  padding: 3.5rem 1rem; text-align: center; }
.tls-empty .ic { width: 26px; height: 26px; color: var(--faint); }
@media (max-width: 1100px) {
  .tls-split { grid-template-columns: 1fr; }
  .tls-left { max-height: none; overflow-y: visible; }
  .tls-right { position: static; }
  .tls-editor-card { max-height: none; }
}

/* In-pane saved/restored flash line (the pane re-render can't use the cookie
   toast — it never navigates). */
.tpe-flash { display: inline-flex; align-items: center; gap: 0.35rem; margin: 0 0 0.6rem;
  padding: 0.35rem 0.6rem; border-radius: var(--radius); background: var(--ok-bg);
  color: var(--ok); font-size: 0.85rem; }
.tpe-flash .ic { width: 14px; height: 14px; }

/* Variant surfacing (owner ask 2026-08-01): chips on an Automated row open that
   variant's editor; the editor header names the variant + offers a switcher. */
.tl-vars { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.tl-var { position: relative; display: inline-flex; }
.tl-var-chip, .tpe-var-chip { display: inline-block; font-size: 11px; line-height: 1.5;
  padding: 0 0.45rem; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--muted); text-decoration: none; white-space: nowrap; }
a.tl-var-chip:hover, a.tpe-var-chip:hover { color: var(--accent); border-color: var(--accent); }
.tl-var-chip:focus-visible, .tpe-var-chip:focus-visible { outline: 2px solid var(--accent);
  outline-offset: 2px; }
/* Each variant chip carries its own peek (same delayed reveal as the row's). */
.tl-var-chip:hover ~ .tl-peek, .tl-var .tl-peek:hover { visibility: visible; opacity: 1; }
.tpe-variants { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 0.5rem 0 0; }
.tpe-var-current { background: var(--accent-soft); color: var(--accent-dark);
  border-color: var(--accent-soft); font-weight: 600; }
.tpe-variant-tag { font-size: 0.85rem; font-weight: 500; color: var(--muted); }

/* Edit history (0083): a quiet collapsed section under the preview. */
.tpe-history { margin-top: 1rem; border-top: 1px dashed var(--border-soft); padding-top: 0.7rem; }
.tpe-history-summary { list-style: none; cursor: pointer; display: inline-flex;
  align-items: center; gap: 0.35rem; font-weight: 600; font-size: 0.9rem; }
.tpe-history-summary::-webkit-details-marker { display: none; }
.tpe-history-summary .ic { width: 15px; height: 15px; color: var(--muted); }
.tpe-revlist { list-style: none; margin: 0.6rem 0 0; padding: 0; display: flex;
  flex-direction: column; gap: 0.55rem; }
.tpe-rev { border: 1px solid var(--border-soft); border-radius: var(--radius);
  padding: 0.5rem 0.65rem; }
.tpe-rev-meta { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.tpe-rev-n { font-weight: 600; font-size: 0.82rem; }
.tpe-rev-restore { margin-left: auto; }
.tpe-rev-preview { margin-top: 0.3rem; font-size: 0.82rem; color: var(--muted);
  white-space: pre-wrap; overflow-wrap: anywhere; }

/* ── lv- list-view system (Style 7, owner-approved 2026-08-01) ── */
/* Mechanically ported from the "7 · Hybrid" candidate in
   serve-wireframes/wireframes/list-styles.html (ClickUp's colored group pill
   + per-group column headers, over the Ledger candidate's 37px aligned rows
   and hairline dividers instead of per-row card shadows). No page adopts it
   yet — this PR only lands the shared component family so page PRs can rely
   on it.

   Column layout: .lv-cols and .lv-row ship WITHOUT grid-template-columns —
   put one of the modifier classes below on the WRAPPING .lv-panel (mirrors
   the mockup: `<div class="lv-panel lv-c5">`), which sets the same grid on
   both its .lv-cols header and every .lv-row inside it:
     .lv-c4  4 cols  — e.g. a queue with no auto-pilot column
     .lv-c5  5 cols  — the standard 5-column dashboard queue panel
     .lv-c6  6/7 cols — the Calls table variant
     .lv-cA  6 cols  — the Appointments book variant
     .lv-cC  7 cols  — the Contacts list variant (Contact/Mobile/Patients/
             Prefers/Created/Last contacted/actions). Every column is sized to
             its own content (Mobile ≈ a formatted number, Created/Last
             contacted ≈ a short date, Prefers ≈ its longest word, actions ≈
             the ⋯ button) — Patients is the ONE flexible column, absorbing
             the row's slack because it carries the stage chips and benefits
             most from the room.
     .lv-cH  3 cols, 40px rows — the half-page dashboard split variant
   Per-section columns rule: each panel shows only the columns its stage
   actually knows (e.g. New Leads has no Autopilot column — nobody has
   assessed a lead that hasn't been talked to yet) — see
   wiki/design-principles.md "List views (Style 7)".

   Token mapping (mockup hex -> app token), chosen for existing semantic fit
   over raw hex distance where a stage/status token already means the same
   thing elsewhere in the app:
     .lv-g-red    #e5566d -> var(--c-new)    (already the app-wide "new" stage red)
     .lv-g-amber  #eab308 -> var(--c-pend)   (already the app-wide "pending" stage amber)
     .lv-g-purple #7c5cff -> var(--lv-purple) (minted: no existing token is a vivid
                                                violet — var(--c-obs) is the closest
                                                but reads as a muted mauve, not this
                                                group pill's saturated punch)
     .lv-g-blue   var(--accent) -> var(--accent) (mockup already referenced this token)
     .lv-g-slate  ad hoc #8a94a6/#5b6b82 -> var(--c-longterm) (app's existing muted
                                                slate-blue "long-term" stage token)
     .lv-g-green  (not in mockup, required by this family) -> var(--ok)
     .lv-st-pink   #fde7ef/#d81b60 -> var(--pill-mind-avoid-bg)/-tx (near-identical bg)
     .lv-st-amber  #fdf3d7/#a07800 -> var(--pill-warm-bg)/-tx (near-identical bg)
     .lv-st-purple #ece7fd/#6a4fe0 -> var(--c-obs-bg)/-tx (near-identical bg)
     .lv-st-blue   accent-soft/accent -> var(--accent-soft)/var(--accent) (already identical)
     .lv-st-red    red-soft/red -> var(--error-bg)/var(--error-text) (already near-identical)
   Row hairline/hover reuse existing --border-soft / --hover (both already
   near-identical to the mockup's --hair / row-hover values). The mockup's
   .btn is intentionally NOT ported — pages use the app's existing button
   classes (.btn / .btn-sm / .btn-quiet etc., already 28px). */

:root {
  --lv-purple: #7c5cff;
}

/* The group pill owns the space ABOVE it (mockup `.s7 .gpill`), so a page can
   emit `pill, panel, pill, panel…` as a flat sequence with no wrapper per
   group and no `:not(:last-child)` gymnastics — the pill's own top margin is
   the gap between groups, and the first pill on a page cancels it. */
.lv-gpill { display: inline-flex; align-items: center; height: 22px; padding: 0 10px;
  border-radius: 5px; font-size: 10.5px; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: #fff; margin: 20px 0 0; }
.lv-gpill:first-child { margin-top: 0; }
.lv-g-red { background: var(--c-new); }
.lv-g-amber { background: var(--c-pend); }
.lv-g-purple { background: var(--lv-purple); }
.lv-g-blue { background: var(--accent); }
.lv-g-slate { background: var(--c-longterm); }
.lv-g-green { background: var(--ok); }
/* Two more stage colors the role dashboards need (2026-08-01): Observation and
   Canceled Consult already own app-wide tokens, and reusing .lv-g-purple for
   OBS would collide with Needs attention on the very same TC screen. */
.lv-g-obs { background: var(--c-obs); }
.lv-g-cxcon { background: var(--c-cxcon); }

.lv-panel { background: var(--surface); border: 1px solid var(--border);
  border-radius: 0 8px 8px 8px; padding: 4px 14px 6px; }

.lv-cols { display: grid; gap: 10px; padding: 8px 2px 6px; font-size: 10.5px;
  letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
  font-weight: 700; border-bottom: 1px solid var(--border); }

.lv-row { display: grid; gap: 10px; align-items: center; height: 37px;
  border-bottom: 1px solid var(--border-soft); padding: 0 2px; font-size: 13.5px; }
.lv-row:last-child { border-bottom: none; }
.lv-row:hover { background: var(--hover); }
.lv-row > * { min-width: 0; }
.lv-row.lv-urgent { background: var(--error-bg); }
/* Soft row wash (W-DW, owner ruling 2026-08-03 — same №2 treatment the board
   cards use, `.card-wash` above): a replied/emergency patient, or a fresh
   lead past the speed-to-lead goal. Deliberately the SAME 4% mix formula,
   against `.lv-panel`'s own `--surface` background — lighter than
   `.lv-urgent`'s stronger `--error-bg` (reserved for a re-engaged
   Needs-attention ITEM's own top-of-queue priority, untouched by this
   rollout). No left/top edge anywhere, on either row kind. */
.lv-row.lv-wash { background: color-mix(in srgb, var(--danger) 4%, var(--surface)); }
/* Needs-attention queue (2026-08-01): a review item's resolution occasionally
   carries more than a button — the orphaned-text match/create controls, a
   voicemail's player. Those rows opt into `.lv-row-flex` (height:auto, the
   same min-height keeps the common case pixel-identical to every other
   37px row) instead of forcing rare rich content to overflow a fixed box. */
.lv-row.lv-row-flex { height: auto; min-height: 37px; padding-block: 6px; }

.lv-c4 .lv-cols, .lv-c4 .lv-row { grid-template-columns: 1fr 92px 110px 128px; }
.lv-c5 .lv-cols, .lv-c5 .lv-row { grid-template-columns: 1fr 92px 64px 110px 128px; }
.lv-c6 .lv-cols, .lv-c6 .lv-row { grid-template-columns: 74px 52px 60px 1fr 150px 108px 210px; }
.lv-cA .lv-cols, .lv-cA .lv-row { grid-template-columns: 76px 1fr 92px 110px 110px 150px; }
.lv-cC .lv-cols, .lv-cC .lv-row { grid-template-columns: 136px 100px minmax(0, 1fr) 64px 96px 104px 40px; }
.lv-cH .lv-cols, .lv-cH .lv-row { grid-template-columns: 1fr 72px 108px; }
.lv-cH .lv-row { height: 40px; }

.lv-who { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lv-who .lv-sub { font-weight: 400; color: var(--muted); font-size: 12px; margin-left: 6px; }
.lv-meta { color: var(--muted); font-size: 12.5px; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }

.lv-stage { display: inline-flex; align-items: center; height: 20px; padding: 0 9px;
  border-radius: 5px; font-size: 10px; font-weight: 700; letter-spacing: .05em; }
/* F22: the same long-sentence kind labels as .pill-wrap above, inside the
   needs-attention queue's fixed-height row instead of a modal title — pairs
   with the row opting into .lv-row-flex (height:auto) so the row grows to
   fit instead of the pill overflowing a 37px box. */
.lv-stage.lv-stage-wrap { white-space: normal; height: auto; min-height: 20px;
  line-height: 1.3; text-align: left; }
.lv-st-pink { background: var(--pill-mind-avoid-bg); color: var(--pill-mind-avoid-tx); }
.lv-st-amber { background: var(--pill-warm-bg); color: var(--pill-warm-tx); }
.lv-st-purple { background: var(--c-obs-bg); color: var(--c-obs-tx); }
.lv-st-blue { background: var(--accent-soft); color: var(--accent); }
.lv-st-red { background: var(--error-bg); color: var(--error-text); }

.lv-acts { display: flex; gap: 6px; align-items: center; white-space: nowrap; }
/* The shared `action_controls` macro (primary + ⋯) drops straight into an
   .lv-acts cell. Its 0.35rem top margin exists to separate it from the
   name/badge row stacked ABOVE it on a board card; in a 37px grid row there
   is nothing above it, and the margin only pushes the buttons off centre. */
.lv-acts .action-controls { margin-top: 0; }
/* Contacts list (lv-cC): the row's own ⋯ is a bare `.action-menu`, not the
   shared `action_controls` macro above, so it needs the same margin-top
   cancellation directly — without it the details element's default
   0.35rem top margin pushed the ⋯ visibly below the row's vertical center. */
.lv-acts .action-menu { margin-top: 0; }
/* …and its ⋯ menu opens LEFTWARD. `.action-list` is left-anchored by default,
   which is right on a board card (mid-page, 340px of room to its right) and
   wrong in a list whose actions column ends at the page's right edge: a 15rem
   menu hung off the left of that ⋯ runs straight off the viewport. */
.lv-acts .action-list { right: 0; }
/* Two 12px lines inside a 37px row: the sub-line rides BESIDE the name on
   this system (the mockup's `.sub`), so it must never introduce a second
   line of its own — .chart-alert is a flex badge and would. */
.lv-who .chart-alert { margin-left: 6px; vertical-align: middle; }

.lv-split { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; align-items: start; }

/* ── lv- adopted by the A6 role dashboards (2026-08-01) ──
   The section wrapper (group pill + its panel), plus the pieces a real page
   needs that a static mockup did not: a header note, an "open the full list"
   link, an empty state, and the recently-done strip. */
.lv-sec { margin-bottom: 22px; }
.lv-sechead { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* The pill names the section — it never wraps to two lines; the note beside it
   is what gives way when the column is narrow. */
.lv-sechead .lv-gpill { flex: none; white-space: nowrap; }
.lv-secnote { font-size: 11.5px; color: var(--muted); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.lv-secall { margin-left: auto; font-size: 12px; text-decoration: none;
  color: var(--accent-dark); font-weight: 600; white-space: nowrap; }
.lv-donelabel { font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; padding: 8px 2px 4px;
  border-top: 1px solid var(--border); }

/* The dashboards' rows carry the app's REAL action controls (a 32px
   .action-primary beside the 32px board-card ⋯ menu), which need more room
   than the mockup's demo buttons — so the actions column stays wide. W-DW
   (2026-08-03) widened the MIDDLE column too: it used to hold one line of
   "when" text (84px was plenty); it now holds the chip cluster that
   replaced it (follow-up clock · calls · replied · temp · proposal), which
   wraps across more than one line at the low end. `lv-c4` no longer
   appears on this page (its old 4th "Auto" column moved into row 1's
   corner cluster), so only `lv-cH`'s grid needs updating here. */
.lv-dash .lv-cH .lv-cols, .lv-dash .lv-cH .lv-row {
  grid-template-columns: minmax(0, 1fr) minmax(150px, 300px) 176px; }
/* Row 1 (name + corner cluster) and row 2 (contact) now stack — patient-
   first (owner ruling 2026-08-03), matching the board card's own row 1/
   row 2 rotated horizontal — so the fixed single-line height goes auto,
   with a floor that keeps a bare row (no chips, no row 2) close to its old
   40px. */
.lv-dash .lv-cH .lv-row { height: auto; min-height: 46px; padding-block: 6px; }
/* The empty state is one sentence, not a grid — it must out-specify both. */
.lv-dash .lv-panel .lv-row.lv-empty { grid-template-columns: minmax(0, 1fr); }

/* `.lv-who` stacks its two rows (name+corners, then contact) instead of the
   old single inline baseline — the identity block a board card's row 1/2
   already reads. */
.lv-dash .lv-who { display: flex; flex-direction: column; align-items: flex-start;
  gap: 1px; min-width: 0; }
.lv-dash .wr-row1 { display: flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; }
.lv-dash .wr-row1 .lv-name { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* Row 2 — 👤 contact · source (H4: the icon precedes the name on cards AND
   dashboard rows). Mirrors the board card's own `.bc-row2` values. */
.lv-dash .wr-row2 { display: flex; align-items: center; gap: 0.3rem; font-size: 0.78rem;
  color: var(--muted); min-width: 0; max-width: 100%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.lv-dash .wr-row2 .idglyph { display: inline-flex; color: var(--faint); flex: 0 0 auto; }
.lv-dash .wr-row2 .idglyph .ic { width: 0.75rem; height: 0.75rem; }
/* The middle column: was one line of "when" text, is now the chip cluster —
   wraps instead of clipping (the row's own auto height, above, makes room). */
.lv-dash .lv-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  white-space: normal; overflow: visible; text-overflow: clip; }

/* A claimed lead reads quieter, and its holder badge is one line like the rest. */
.lv-row.lv-claimed { opacity: 0.62; }
.lv-row.lv-claimed .lv-who { color: var(--muted); }
.lv-claim { font-size: 12px; font-weight: 600; color: var(--muted); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
/* Urgent (a re-engaged patient): the row wears the red wash from .lv-urgent and
   its name goes red — never a whole tinted card (owner spec). */
.lv-who-urgent, .lv-who-urgent .rp-link, .lv-who-urgent .w-clamp { color: var(--danger); }
.lv-meta.lv-past { color: var(--danger); font-weight: 600; }
.lv-meta.lv-calm { color: var(--accent-dark); }
/* Presence is advisory, never a call to action — same quiet italic it had. */
.lv-sub.lv-presence { font-style: italic; }
.lv-acts { justify-content: flex-end; }
.lv-acts .wform { display: contents; }
.lv-acts .action-controls { margin-top: 0; }
/* On a one-line row the Needs-attention sentence truncates (via .lv-who's own
   ellipsis) instead of wrapping to three lines; the full text still rides the
   .na-peek hover. The peek is anchored on the ROW, not on .lv-who — .lv-who
   clips its overflow, and an absolutely positioned box escapes that clip only
   when its containing block sits outside it. */
.lv-row { position: relative; }
.lv-row .na-clamp-wrap { display: inline; position: static; }
.lv-row .w-clamp { display: inline; }
.lv-row .na-peek { top: calc(100% + 2px); }

/* ── lv- page chrome: header + column filters + row context menu (Calls
   redesign, owner 2026-08-01) ──
   Line 1 is a three-track grid so the search sits OPTICALLY CENTRED on the
   page (not merely after the heading) whatever the heading's width; line 2 is
   the filter row every filterable column heading opens into. */
.lv-pagehead { display: grid; grid-template-columns: 1fr minmax(0, 26rem) 1fr;
  align-items: center; gap: 14px; margin-bottom: 0.6rem; }
.lv-pagehead h1 { margin: 0; }
.lv-pagehead-end { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }

.lv-filterbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
  margin-bottom: 0.9rem; min-height: 28px; }
.lv-fgroup { display: flex; align-items: center; gap: 6px; }
.lv-flead { font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted); }
/* Dismiss/close affordances inside the filter row — quiet, never a bordered
   button (no-blue-outline-controls rule). */
.lv-fclose, .lv-fclose:visited, .lv-fpill a, .lv-fpill a:visited {
  display: inline-flex; align-items: center; color: inherit; text-decoration: none; opacity: .65; }
.lv-fclose:hover, .lv-fpill a:hover { opacity: 1; }
.lv-fpill { display: inline-flex; align-items: center; gap: 5px; height: 24px;
  padding: 0 7px 0 10px; border-radius: 999px; background: var(--accent-soft);
  color: var(--accent); font-size: 12px; font-weight: 600; }

/* A filterable column heading. Reads as a heading until you touch it. */
.lv-colf, .lv-colf:visited { color: inherit; text-decoration: none; cursor: pointer; }
.lv-colf:hover, .lv-colf.is-open { color: var(--accent); }

.lv-plink, .lv-plink:visited { color: var(--accent); text-decoration: none; font-weight: 600; }
.lv-plink:hover { color: var(--accent-dark); }

/* Media pills — clickable, and only rendered when the artifact exists. */
.lv-pill { display: inline-flex; align-items: center; height: 20px; padding: 0 9px;
  border: 0; border-radius: 999px; background: var(--accent-soft); color: var(--accent);
  font-family: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer; }
.lv-pill:hover { background: var(--accent); color: #fff; }

.lv-dir { display: inline-flex; align-items: center; gap: 4px; }

/* The row's right-click menu (admins only — the markup is only rendered for
   them). Positioned at the pointer by base.html's crmCtxMenu. */
.lv-ctx { position: fixed; z-index: 1200; min-width: 11rem; padding: 4px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: var(--shadow); }
.lv-ctx[hidden] { display: none; }
.lv-ctx form { margin: 0; }
.lv-ctx-item { display: block; width: 100%; text-align: left; padding: 7px 10px;
  border: 0; border-radius: 6px; background: none; font-family: inherit; font-size: 13px;
  color: var(--error-text); cursor: pointer; }
.lv-ctx-item:hover { background: var(--error-bg); color: var(--error-text); }
.tpe-rev-empty { margin: 0.6rem 0 0; }

/* ── needs-attention views (2026-08-01) ── */
/* The queue page's view pills (All · Admin · Treatment Coordinator ·
   Scheduling Coordinator) — the shared segmented control + a quiet count. */
.na-views { margin: 0.35rem 0 0.9rem; }
.na-view-count { font-weight: 400; color: var(--muted); margin-left: 0.25rem; }
.segmented .seg.on .na-view-count { color: inherit; opacity: 0.75; }
/* Re-engaged patients pin to the top and read urgent: red edge + red text
   tokens only — never a whole tinted card (owner spec). */
.table tr.na-urgent td:first-child { box-shadow: inset 3px 0 0 var(--danger); }
.na-urgent-text { color: var(--danger); font-weight: 600; }
/* (the dashboard's own urgent row is now .lv-row.lv-urgent + .lv-who-urgent.) */
/* Needs-attention item text: clamped to 3 lines on the queue page; the full
   text rides a hover peek (the .tl-peek pattern — pure CSS). A dashboard
   lv-row narrows this to one line — see .lv-row .w-clamp above. */
.na-clamp-wrap { position: relative; display: block; }
.w-clamp { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; font-weight: 500; }
.na-peek { position: absolute; z-index: 120; left: 0; top: calc(100% + 4px);
  width: 26rem; max-width: 92vw; max-height: 18rem; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--pop-shadow); padding: 0.6rem 0.75rem;
  font-size: 0.85rem; font-weight: 400; line-height: 1.5; color: var(--text);
  visibility: hidden; opacity: 0; transition: opacity 0.12s ease 0.25s, visibility 0s linear 0.37s; }
.na-clamp-wrap:hover .na-peek, .na-peek:hover { visibility: visible; opacity: 1; }
/* The item resolution popup (partials/na_card.html). */
.na-card { max-width: 34rem; }

/* ===== Help — the in-app wiki (F23 / W8 phase 1, 2026-08-03) ===============
   /help renders wiki/*.md server-side (app/help/markdown.py). Layout mirrors
   the Templates split-pane (.tls-*): a left nav card, a stationary content
   card on the right. .wiki-body is the ONE scoped class family for whatever
   the renderer emits — headings already inherit the app's global h1/h2/h3
   scale (line 186), so only what that scale doesn't cover needs rules here. */
.help-page { max-width: 80rem; margin: 0 auto; }
.help-split { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 7fr);
  gap: 1rem; align-items: start; margin: 2rem 0 0; }
.help-nav { position: sticky; top: 1rem; max-height: calc(100vh - 7.5rem);
  overflow-y: auto; padding: 0.75rem; }
.help-nav-link { display: block; padding: 0.4rem 0.6rem; border-radius: var(--radius);
  color: var(--text-2); text-decoration: none; font-size: 0.88rem; font-weight: 500; }
.help-nav-link:hover { background: var(--hover); color: var(--text); }
.help-nav-link.active { background: var(--accent-soft); color: var(--accent-dark); font-weight: 600; }
.help-nav-list { list-style: none; margin: 0.25rem 0 0; padding: 0; }
.help-content { min-width: 0; }
@media (max-width: 900px) {
  .help-split { grid-template-columns: 1fr; }
  .help-nav { position: static; max-height: none; }
}

/* The rendered markdown body. */
.wiki-body p { margin: 0 0 0.9rem; line-height: 1.55; }
.wiki-body p:last-child { margin-bottom: 0; }
.wiki-body h1, .wiki-body h2, .wiki-body h3 { scroll-margin-top: 1rem; }
.wiki-body ul.wiki-list, .wiki-body ol.wiki-list { margin: 0 0 0.9rem; padding-left: 1.4rem; }
.wiki-body li { margin: 0.2rem 0; line-height: 1.5; }
.wiki-body li > ul.wiki-list, .wiki-body li > ol.wiki-list { margin: 0.2rem 0 0 0; }
.wiki-body blockquote.wiki-blockquote { margin: 0 0 0.9rem; padding: 0.1rem 1rem;
  border-left: 3px solid var(--border-strong); color: var(--text-2); }
.wiki-body blockquote.wiki-blockquote p { margin: 0.5rem 0; }
.wiki-body code { padding: 0.1rem 0.35rem; border-radius: 4px; background: var(--bg-inset);
  font-family: var(--font-mono); font-size: 0.87em; }
.wiki-body pre.wiki-pre { margin: 0 0 0.9rem; padding: 0.75rem 0.9rem; border-radius: var(--radius);
  background: var(--bg-inset); overflow-x: auto; }
.wiki-body pre.wiki-pre code { padding: 0; background: none; font-size: 0.85rem; line-height: 1.5; }
.wiki-body hr.wiki-hr { margin: 1.5rem 0; border: 0; border-top: 1px solid var(--border-soft); }
.wiki-body a { color: var(--accent); text-decoration: underline;
  text-decoration-color: var(--border-strong); }
.wiki-body a:hover { color: var(--accent-dark); text-decoration-color: currentColor; }
.wiki-table-wrap { margin: 0 0 0.9rem; overflow-x: auto; }
.wiki-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.wiki-table th, .wiki-table td { padding: 0.45rem 0.65rem; border: 1px solid var(--border-soft);
  text-align: left; vertical-align: top; }
.wiki-table th { background: var(--bg-inset); font-weight: 600; }
.na-card-text { font-size: 0.92rem; line-height: 1.5; }
