/*
 * Bedrock design tokens — structural scale (type, spacing, radius, shadow,
 * z-index). Colors are NOT defined here; they're owned entirely by the
 * palette block at the top of styles.css (light default + dark via
 * prefers-color-scheme + explicit [data-theme] override). Standalone app —
 * no external package dependency, safe to eject as-is.
 */

:root {
  color-scheme: light dark;

  --text-xs: 11px;
  --text-sm: 13px;
  --text-base: 15px;
  --text-md: 16px;
  --text-lg: 18px;
  --text-xl: 22px;
  --text-2xl: 28px;
  --text-3xl: 36px;

  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;

  --leading-tight: 1.2;
  --leading-normal: 1.5;
  --leading-relaxed: 1.7;

  /* ---- spacing (padding, gap, and margin all draw from this one scale) ---- */
  --space-0: 0;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* ---- radius ---- */
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-full: 999px;

  /* ---- shadow ---- */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
  --shadow-md: 0 6px 24px rgba(0, 0, 0, 0.18);
  --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.28);

  /* ---- z-index scale — pick from here instead of inventing a number ---- */
  --z-dropdown: 10;
  --z-sticky: 20;
  --z-overlay: 100;
  --z-modal: 200;
  --z-toast: 300;
  --z-max: 2147483000;

  /* ---- motion (2026-08-02 UI polish pass, MVP.md §10) — one shared
     vocabulary so every hover/press/enter feels like the same tool, not a
     collection of screens. --duration-fast for hover/press feedback,
     --duration-base for things entering/leaving (modals, dropdowns,
     revealed sections). prefers-reduced-motion is already handled
     globally in styles.css. ---- */
  --duration-fast: 120ms;
  --duration-base: 180ms;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.35);
    --shadow-md: 0 6px 24px rgba(0, 0, 0, 0.45);
    --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.5);
  }
}

:root[data-theme="dark"] {
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.35);
  --shadow-md: 0 6px 24px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.5);
}

:root[data-theme="light"] {
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
  --shadow-md: 0 6px 24px rgba(0, 0, 0, 0.18);
  --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.28);
}

/* ---- Bedrock identity, layered over the shared platform tokens ----
   Design brief: an insurance agency's paper-folder filing system, digitized.
   Every customer used to be a manila folder in a drawer; the signature
   device here — a colored tab down the left spine of each customer card —
   is that same folder tab, carrying real information (renewal urgency)
   instead of decoration. Cool steel-ledger paper in light mode, ink-slate
   in dark. Newsreader (serif, sparing use) for headers with some editorial
   authority; Public Sans — a government-forms face — for UI chrome, apt
   for ACORD-adjacent paperwork; Space Mono for every data field (policy
   numbers, dates, phone) so they read like stamped ledger entries, not
   generic app text.

   --color-text-muted (2026-08-02, contrast pass): most of the actual
   audience is 40-60yo agents, not designers — the original muted gray
   (#5B6660 light / #9AA6A0 dark) measured 4.58:1 against the raised
   surface, barely over the 4.5:1 AA floor for normal text and not a
   comfortable margin for smaller labels/dates. Darkened (light theme)
   and lightened (dark theme, already decent but given the same margin)
   to a real ~7:1+ (AAA-range) against every surface it's used on. */
:root {
  --font-display: 'Newsreader', ui-serif, Georgia, serif;
  --font-sans: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --font-mono: 'Space Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --color-bg: #E9ECE6;
  --color-surface: #FFFFFF;
  --color-surface-raised: #DEE3DA;
  --color-border: #C7CEC2;
  --color-text: #1C2321;
  --color-text-muted: #3A4440;
  --color-accent: #1B3A5C;
  --color-accent-contrast: #FFFFFF;
  --color-link: #1B3A5C;
  --color-ok: #3E7C4A;
  --color-warn: #A9631B;
  --color-danger: #A3372B;
  --color-stamp: #A9631B;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #14181A;
    --color-surface: #1C2224;
    --color-surface-raised: #242B2D;
    --color-border: #343C3E;
    --color-text: #E9ECE8;
    --color-text-muted: #ADB8B1;
    --color-accent: #7FA8D9;
    --color-accent-contrast: #0F1417;
    --color-link: #7FA8D9;
    --color-ok: #6FBF7E;
    --color-warn: #D9A15A;
    --color-danger: #E08579;
    --color-stamp: #D9A15A;
  }
}

:root[data-theme="dark"] {
  --color-bg: #14181A;
  --color-surface: #1C2224;
  --color-surface-raised: #242B2D;
  --color-border: #343C3E;
  --color-text: #E9ECE8;
  --color-text-muted: #ADB8B1;
  --color-accent: #7FA8D9;
  --color-accent-contrast: #0F1417;
  --color-link: #7FA8D9;
  --color-ok: #6FBF7E;
  --color-warn: #D9A15A;
  --color-danger: #E08579;
  --color-stamp: #D9A15A;
}

:root[data-theme="light"] {
  --color-bg: #E9ECE6;
  --color-surface: #FFFFFF;
  --color-surface-raised: #DEE3DA;
  --color-border: #C7CEC2;
  --color-text: #1C2321;
  --color-text-muted: #3A4440;
  --color-accent: #1B3A5C;
  --color-accent-contrast: #FFFFFF;
  --color-link: #1B3A5C;
  --color-ok: #3E7C4A;
  --color-warn: #A9631B;
  --color-danger: #A3372B;
  --color-stamp: #A9631B;
}

* {
  box-sizing: border-box;
}

html, body, #root {
  height: 100%;
  margin: 0;
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--color-link);
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
}

:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ---- UI polish pass (2026-08-02, MVP.md §10) — one shared motion
   vocabulary instead of one-off transitions per component. Every
   clickable control gets the same subtle state-change feel; a second,
   slightly slower group covers things entering the screen (cards, rows).
   Kept deliberately quiet — color/border/shadow shifts and a hairline
   press, nothing that draws attention to itself. ---- */
button,
a,
input,
select,
textarea {
  transition: background-color var(--duration-fast) ease, border-color var(--duration-fast) ease,
    color var(--duration-fast) ease, box-shadow var(--duration-fast) ease,
    opacity var(--duration-fast) ease, filter var(--duration-fast) ease,
    transform var(--duration-fast) ease;
}

button:active:not(:disabled) {
  transform: scale(0.97);
}

.customer-card,
.policy-row,
.admin-card,
.review-doc-row,
.match-option,
.upload-progress-row {
  transition: background-color var(--duration-fast) ease, border-color var(--duration-fast) ease,
    box-shadow var(--duration-fast) ease, transform var(--duration-fast) ease;
}

.app {
  display: flex;
  flex-direction: column;
  height: 100%;
  position: relative;
}

/* ---- impersonation banner (App.jsx — was in components/admin/admin.css,
   which the app bundle never loads, so this rendered fully unstyled for
   every impersonated session; moved here, the stylesheet main.jsx
   actually imports, John 2026-08-02). Exit is red (a destructive-feeling
   "leave this session" action, not a neutral one) with real gap from the
   text next to it. */
.impersonation-banner {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky, 20);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-3);
  background: var(--color-stamp);
  color: var(--color-accent-contrast);
  font-size: var(--text-sm);
  text-align: center;
}

.impersonation-banner button {
  background: var(--color-danger);
  color: var(--color-accent-contrast);
  border: none;
  border-radius: var(--radius-sm, 4px);
  padding: var(--space-1) var(--space-3);
  font-weight: var(--font-weight-medium);
  flex-shrink: 0;
}

.impersonation-banner button:hover {
  filter: brightness(1.1);
}

/* ---- desktop-agent download nudge (App.jsx, 2026-08-07) — deliberately
   calm: raised surface + a thin accent rule underneath, not a colored
   alert bar. This is an invitation to a useful feature, not a warning, so
   it shouldn't compete visually with the (much rarer, much more urgent)
   impersonation banner above it. Disappears entirely once any user in the
   company has a device, or once dismissed (2026-08-27, John: "less
   intrusive... once closed it should stay closed" — see the close button
   below and App.jsx's localStorage-backed desktopBannerDismissed). The
   header's download icon (App.jsx) stays the permanent, always-available
   way to reach /download regardless of either of those. */
.desktop-agent-banner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-8);
  background: var(--color-surface-raised);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-align: center;
}

.desktop-agent-banner a {
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}

.desktop-agent-banner-close {
  position: absolute;
  right: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  padding: var(--space-1);
  color: var(--color-text-muted);
  font-size: 14px;
  line-height: 1;
}

.desktop-agent-banner-close:hover {
  color: var(--color-text);
}

/* AccountPanel.jsx's stronger "no coverage at all" CTA — same idea, list
   context instead of a full-width bar, so it reads as a plain paragraph
   with a bit more presence than the quiet .subtitle sibling it replaces. */
.download-cta {
  font-size: var(--text-sm);
  color: var(--color-text);
  background: var(--color-surface-raised);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

/* ---- header ---- */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
}

.app-header-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  flex-shrink: 0;
}

.app-header h1 {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--font-weight-semibold);
  letter-spacing: -0.01em;
  color: var(--color-accent);
  white-space: nowrap;
  /* Without this, the h1's inherited line-height (1.5) makes its line box
     much taller than the LogoMark svg next to it — align-items: center
     on .app-header-brand centers the two boxes correctly, but the glyphs
     inside that tall, asymmetric line box don't sit centered within it,
     so the icon and the text read as misaligned even though the boxes
     technically are (John, 2026-08-02: "logo and the title don't
     align"). line-height: 1 collapses the box to the glyphs themselves. */
  line-height: 1;
}

.app-header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 1;
  min-width: 0;
}

.include-expired-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
  cursor: pointer;
}

/* ---- mobile: shrink the header chrome a bit so the brand + 5 icon
   buttons stay comfortable at narrow widths. The old text buttons here
   ("LOG OUT" wrapping mid-word, found via 390px screenshot 2026-08-02;
   still overflowing off-screen even after that fix) are gone — replaced
   with fixed-size icon buttons (.icon-btn) that don't need font-size
   shrinking tricks to fit. ---- */
@media (max-width: 480px) {
  .app-header {
    padding: var(--space-2) var(--space-3);
    gap: var(--space-2);
    /* Was nowrap-only, tuned before the SearchModeToggle pill existed
       (2026-08-07 fix, John: "header and search features are going off
       the side of the screen") — the toggle plus all 5 icon buttons
       genuinely don't fit on one line at 390px/360px no matter how tight
       the gaps get, so this now allows the whole header to wrap to a
       second line rather than spill horizontally past the viewport —
       same "stack instead of cram" mobile pattern already used
       elsewhere in this file (.results/.search-bar-wrap's own comment).
       This was also the root cause of a second, subtler bug: real
       horizontal document overflow from this row was pushing the
       browser's effective mobile layout width wider than the true
       viewport, which was ALSO throwing off the fixed-position search
       bar's own sizing even after its own unrelated min-width fix. */
    flex-wrap: wrap;
  }

  .app-header h1 {
    font-size: var(--text-lg);
  }

  .app-header-actions {
    gap: var(--space-1);
    flex-wrap: wrap;
    justify-content: flex-end;
  }
}

/* ---- header icon buttons (2026-08-03) — theme/feedback/account/logout,
   replacing small uppercase-mono text buttons that overflowed the header
   at mobile widths (John: "too small... make sure hover shows a
   tooltip"). Same square-icon-button shape as .upload-status below (kept
   in sync — both bumped from 36px to 40px together, "bigger so users can
   clearly see them"), native title attribute for the hover tooltip
   rather than a custom component, matching UploadStatus.jsx's existing
   pattern. Icon glyphs are Font Awesome (@fortawesome/react-fontawesome +
   free-solid-svg-icons). ---- */
.icon-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  color: var(--color-text);
  font-size: 18px;
  flex-shrink: 0;
}

.icon-btn:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* Log out / sign out is the one icon-btn that hints red on hover instead
   of accent-blue — a leaving action, not neutral like theme/feedback/
   account (used in both the main app header and AdminPanel's). */
.icon-btn-danger:hover {
  border-color: var(--color-danger);
  color: var(--color-danger);
}

/* ---- document upload status indicator (App.jsx header) ---- */
.upload-status {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  color: var(--color-text-muted);
}

.upload-status svg {
  width: 20px;
  height: 20px;
}

.upload-status:disabled {
  opacity: 1;
  cursor: default;
}

.upload-status-uploading,
.upload-status-processing {
  border-color: currentColor;
  cursor: pointer;
}

.upload-status-uploading:hover,
.upload-status-processing:hover {
  filter: brightness(1.15);
}

.upload-status-uploading { color: var(--color-ok); }
.upload-status-processing { color: var(--color-link); }
.upload-status-idle { color: var(--color-ok); cursor: pointer; }

.upload-status-idle:hover {
  border-color: var(--color-ok);
  background: color-mix(in srgb, var(--color-ok) 12%, var(--color-surface-raised));
}

.upload-status-attention {
  color: var(--color-accent-contrast);
  background: var(--color-danger);
  border-color: var(--color-danger);
  cursor: pointer;
}

.upload-status-attention svg circle:first-child {
  fill: var(--color-danger);
}

.upload-status-attention rect,
.upload-status-attention circle:not(:first-child) {
  fill: var(--color-accent-contrast);
}

.upload-status-attention:hover {
  filter: brightness(1.08);
}

.upload-status-spinner {
  animation: upload-status-spin 900ms linear infinite;
  /* Without these, an SVG root element's rotate() origin isn't reliably
     anchored to its own visual center across browsers (percentages can
     resolve against the nearest SVG viewport rather than the element's
     own rendered box) — the ring visibly wobbles off-center as it spins
     (2026-09-04, John: "this spinner is slightly off center"). fill-box
     makes the origin resolve against the shape's own geometry instead. */
  transform-box: fill-box;
  transform-origin: center;
}

.upload-status-spinner circle {
  stroke: currentColor;
}

@keyframes upload-status-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .upload-status-spinner { animation: none; }
}

.upload-status-count {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: var(--radius-full, 999px);
  background: var(--color-danger);
  color: var(--color-accent-contrast);
  font-size: 10px;
  font-family: var(--font-mono);
  line-height: 16px;
  text-align: center;
  border: 2px solid var(--color-surface);
  animation: badge-pop var(--duration-base) var(--ease-out);
}

@keyframes badge-pop {
  from { transform: scale(0); }
  to { transform: scale(1); }
}

/* ---- results area ---- */
.results {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-4);
  /* Reserves room for the fixed .search-bar-wrap below so result cards
     never sit hidden under it. Bumped from 108px (2026-08-27) when the
     advanced-field select + expired-policies checkbox moved down into
     the hint row (was just "Esc to clear") — that row is taller now and
     can wrap to two lines on a narrow viewport, so the reserve needs to
     cover that worst case, not just the single-line height. */
  padding-bottom: calc(var(--space-4) + 148px);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Graph search's secondary tier (2026-08-04) — customers reached only via
   a shared phone number, deliberately NOT styled like a CustomerCard (no
   folder-tab, no policy list) so it reads as "maybe, click to check" and
   never gets mistaken for a confirmed part of the main result group. */
.related-via-phone {
  border-top: 1px dashed var(--color-border);
  padding-top: var(--space-3);
  margin-top: var(--space-2);
}

.related-via-phone-label {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: var(--space-2);
}

.related-via-phone-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.related-via-phone-link {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text);
}

.related-via-phone-link:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* Vertically centered in .results' available space (2026-08-28 polish
   pass — a fixed top margin here left this stranded near the header with
   a large dead gap above the fixed-bottom search bar). Safe because
   .results-empty is always the sole child when it renders at all — both
   the "no query yet" and "no matches" cases only show this alongside a
   zero-length customers/graphGroups map(). */
.results-empty {
  color: var(--color-text-muted);
  text-align: center;
  margin-top: auto;
  margin-bottom: auto;
  font-size: var(--text-sm);
  max-width: 32ch;
  margin-left: auto;
  margin-right: auto;
}

/* Home-page empty-state stat (2026-08-03) — "# Policies over #
   Customers", shown only when no search term is present. Own font size
   (not constrained to .results-empty's 32ch/text-sm), same muted color. */
.results-stat {
  font-size: var(--text-xl);
  color: var(--color-text);
  margin-bottom: var(--space-3);
}

.results-stat strong {
  font-weight: var(--font-weight-bold);
  color: var(--color-accent);
}

/* PniGroup.jsx/PniView.jsx (graph search's default results + the PNI
   click-through pivot) deliberately have NO styling of their own
   (2026-08-05, John: "literally what we had before was perfect... let's
   change these results to look like the classic results") — both reuse
   .customer-card/.customer-card-header/.policy-list etc. directly. See
   those components for how. */

/* ---- customer card: the folder-tab signature ----
   A solid spine down the left edge stands in for a manila folder's tab.
   Its color is load-bearing, not decorative: green = nothing due soon,
   amber = a policy renews within 30 days, red = something's already
   lapsed. --tab-color is set inline per card from real policy data. */
.customer-card {
  --tab-color: var(--color-border);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 5px solid var(--tab-color);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
}

.customer-card:hover {
  box-shadow: var(--shadow-sm);
}

.customer-card-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  cursor: pointer;
}

.customer-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-3);
}

/* Left column: name + address stacked, address on the same row as the
   phone number in the right-hand .customer-card-meta column (John,
   2026-08-02 — previously its own full-width line under everything;
   before that, inside the right column, where a long address wrapped
   badly and read as clutter). min-width: 0 lets the name/address ellide
   instead of forcing the card wider. */
.customer-card-left {
  min-width: 0;
  flex: 1;
}

.customer-card-header:hover .customer-card-name {
  color: var(--color-accent);
}

.customer-card-name {
  font-weight: var(--font-weight-semibold);
  font-size: calc(var(--text-md) * var(--results-scale, 1));
  transition: color var(--duration-fast) ease;
}

/* Space Mono here (and nowhere in a policy row anymore — see below) is
   deliberate: John, 2026-08-02 — this distinctive treatment on the
   customer-level info is "what makes the profile stand out visually...
   easier to visually identify a profile vs. something else (like a
   policy)." Policies get a plain, fast-to-scan font instead — the
   CONTRAST between the two is the point, not just readability alone. */
.customer-card-meta {
  text-align: right;
  color: var(--color-text-muted);
  font-size: calc(var(--text-sm) * var(--results-scale, 1));
  font-family: var(--font-mono);
  flex-shrink: 0;
}

.customer-card-meta div {
  white-space: nowrap;
}

.customer-card-merge {
  margin-top: var(--space-2);
}

.customer-card-merge .btn-outline {
  font-family: var(--font-sans);
}

.customer-card-policy-count {
  color: var(--color-text);
  font-weight: var(--font-weight-medium);
}

.customer-card-address {
  font-family: var(--font-mono);
  font-size: calc(var(--text-sm) * var(--results-scale, 1));
  color: var(--color-text-muted);
}

/* Identities (2026-08-06) — click-to-edit placeholder/input styling shared
   by PhonePickerModal.jsx's text input and EditableAddress.jsx (a
   policy's PNI mailing address). Sized/positioned to sit inline with the
   plain text it replaces, not a full form input. */
.editable-placeholder {
  font-style: italic;
  opacity: 0.6;
}

/* A filled-in editable value (a phone number, a mailing address once
   set) previously had zero visual cue that it was clickable — read as
   plain static text, same as any other label on the card, with no way
   to tell it apart from something you can't edit (2026-08-08 polish
   pass). Dotted rather than solid underline deliberately — distinct
   from a real navigational link (solid underline, .policy-row-detail a
   above) so "click to edit" and "click to go somewhere" read as two
   different affordances, not the same one twice. */
.editable-value {
  cursor: pointer;
  border-bottom: 1px dotted var(--color-text-muted);
}

.editable-value:hover {
  border-bottom-color: var(--color-accent);
  color: var(--color-accent);
}

.editable-field-input {
  font: inherit;
  color: inherit;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-1);
  width: 12em;
  max-width: 100%;
}

/* A policy's mailing address (2026-08-06, EditableAddress.jsx) — four
   narrow fields inline instead of one wide one, matching the same
   street/city/state/zip shape DocumentEditor.jsx already edits. */
.editable-address-fields {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  align-items: center;
}

.editable-address-fields .editable-field-input {
  width: 9em;
}

.editable-address-fields .editable-field-input-state {
  width: 4em;
}

.editable-address-fields .editable-field-input-zip {
  width: 5.5em;
}

.policy-list {
  margin-top: var(--space-3);
  border-top: 1px dashed var(--color-border);
  padding-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  animation: reveal-down var(--duration-base) ease;
}

.policy-row {
  background: var(--color-surface-raised);
  border-radius: var(--radius-sm, 6px);
  padding: var(--space-2) var(--space-3);
}

.policy-row:hover {
  box-shadow: var(--shadow-sm);
}

.policy-row-archived {
  opacity: 0.6;
}

.policy-row-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

/* Real buttons, not underlined links (John, 2026-08-02: controls should
   read as controls) — border + surface fill instead of link styling,
   same hover convention as .modal-actions .secondary elsewhere. */
.btn-outline {
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 6px);
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
  /* Used inside flex ROWS (.policy-row-actions) and flex COLUMNS
     (.policy-list, for the merge button) — without this, a column
     context stretches it to the full container width by default. */
  align-self: center;
}

.btn-outline:hover:not(:disabled) {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.btn-outline:disabled {
  opacity: 0.6;
}

/* Policies switch to the plain UI sans (John, 2026-08-02: "highly easy
   to read, easy visual processing" — Space Mono's personality is right
   for the sparse customer-level info above, wrong for a denser list of
   policies someone's actually scanning). Hierarchy now comes from
   weight/size/color instead of font-family: carrier bold and a touch
   larger, policy number smaller and lighter right next to it, policy
   type centered, exp date bold and floated right.

   Grid, plain (no subgrid/display:contents — 2026-08-02: tried sharing
   the carrier column across rows via subgrid + a display:contents
   .policy-row-summary; real-world testing turned up rows silently not
   filling the card and a later padding change not visibly applying,
   most likely browser-specific subgrid/contents edge cases. Reverted to
   this: every row is its own ordinary grid, and column 1 reads
   --carrier-col-width, a plain px value CustomerCard.jsx measures across
   every carrier span in the list (scrollWidth, so it survives that
   span's own overflow:hidden) and writes onto the list container via
   ref.style.setProperty — plain CSS custom property inheritance then
   gives every row in the list the identical value. Ordinary grid/var()/
   calc(), no exotic layout features, so it behaves the same everywhere.
   Column 3 (type) is the flexible one — it absorbs whatever width is
   left so every row still fills the full card width, the same job
   carrier's old 1fr used to do; its minimum is the old fixed width so it
   never gets narrower than before. Column 2 (number) works the same way
   as column 1 — --number-col-width, measured alongside carrier width in
   CustomerCard.jsx — instead of a fixed px width, so a long policy
   number is never truncated (John, 2026-08-02). */
.policy-row-summary {
  display: grid;
  grid-template-columns: minmax(0, var(--carrier-col-width, max-content)) minmax(0, var(--number-col-width, max-content)) minmax(calc(64px * var(--results-scale, 1)), 1fr) calc(86px * var(--results-scale, 1));
  align-items: baseline;
  column-gap: var(--space-2);
  font-family: var(--font-sans);
  cursor: pointer;
  /* Baseline color for any child that isn't one of the specific
     .policy-row-carrier/-number/-type/-exp classes below (those override
     this). Without it, an unclassed child inherits whatever ambient
     color its container happens to be in. */
  color: var(--color-text);
}

/* Narrow screens: drop the policy-type column from the summary line
   entirely rather than let it and the carrier name both truncate to
   illegibility ("Fa...", "Umbr..." — found via a real 390px screenshot,
   2026-08-02). Type is still visible in the expanded detail below;
   carrier + policy number + date are the three things worth keeping on
   one line at this width. */
@media (max-width: 480px) {
  .policy-row-summary {
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--number-col-width, max-content)) calc(86px * var(--results-scale, 1));
  }

  .policy-row-type {
    display: none;
  }
}

.policy-row-summary:hover .policy-row-carrier {
  color: var(--color-accent);
}

.policy-row-carrier {
  font-weight: var(--font-weight-semibold);
  font-size: calc(var(--text-md) * var(--results-scale, 1));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--duration-fast) ease;
}

.policy-row-number {
  font-size: calc(var(--text-sm) * var(--results-scale, 1));
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Pill tag, not plain text (John, 2026-08-02) — sized to its own content
   (justify-self: center, rather than stretching + text-align: center)
   so the border hugs the word instead of spanning the whole flexible
   column. Border only, no fill — the column already sits on the row's
   own surface color, so a filled pill would just be a second background
   competing with it. */
.policy-row-type {
  justify-self: center;
  font-size: calc(var(--text-sm) * var(--results-scale, 1));
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  padding: 2px var(--space-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.policy-row-exp {
  font-weight: var(--font-weight-semibold);
  font-size: calc(var(--text-sm) * var(--results-scale, 1));
  white-space: nowrap;
  text-align: right;
  padding-right: var(--space-3);
}

/* "Exp." on its own is a label, not the fact itself — kept light/normal
   weight so the date it's attached to (still bold, on .policy-row-exp) is
   the thing the eye actually lands on (John, 2026-08-02: "preceeded by
   Exp. so the user understands", "beautiful AND easy to comprehend"). */
.policy-row-exp-label {
  font-weight: var(--font-weight-normal);
  color: var(--color-text-muted);
}

.policy-row-detail {
  margin-top: var(--space-2);
  font-family: var(--font-sans);
  font-size: calc(var(--text-sm) * var(--results-scale, 1));
  line-height: var(--leading-relaxed);
  color: var(--color-text);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-2) var(--space-4);
  animation: reveal-down var(--duration-fast) ease;
}

.policy-row-label {
  display: block;
  font-size: calc(var(--text-xs) * var(--results-scale, 1));
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

.policy-row-detail-wide {
  grid-column: 1 / -1;
}

/* Lazy-loaded coverage/party/item detail fetch (2026-08-07) — brief,
   quiet, matches .policy-row-label's muted tone rather than a spinner. */
.policy-row-detail-loading {
  font-size: calc(var(--text-sm) * var(--results-scale, 1));
  color: var(--color-text-muted);
  font-style: italic;
}

/* Coverages/parties/vehicles lists (2026-08-03) — compact rows under a
   .policy-row-label heading, same shape as the label+value convention
   above but one row per list entry instead of one value. */
.policy-row-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 2px;
}

.policy-row-list-item {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: baseline;
}

.policy-row-list-meta {
  font-size: calc(var(--text-xs) * var(--results-scale, 1));
  color: var(--color-text-muted);
}

@media (min-width: 640px) {
  .policy-row-detail {
    grid-template-columns: 1fr 1fr;
  }
}

@keyframes reveal-down {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

.policy-row-detail a,
.policy-row-detail .link-button {
  color: var(--color-link);
  font-size: calc(var(--text-sm) * var(--results-scale, 1));
  text-decoration: underline;
}

/* Button that should read as a plain inline link (PolicyRow.jsx's "View
   policy document" — opens a modal, so it can't be a real <a href>). */
.link-button {
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
}

/* ---- search-mode toggle (2026-08-04, experimental) ---- */
.search-mode-toggle {
  display: flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  overflow: hidden;
  flex-shrink: 0;
}

.search-mode-toggle button {
  background: none;
  border: none;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.search-mode-toggle button:not(:first-child) {
  border-left: 1px solid var(--color-border);
}

.search-mode-toggle button.active {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  font-weight: var(--font-weight-medium);
}

/* ---- advanced search field select (2026-08-07) — a small utility
   control, deliberately plain, sized to match the mode toggle beside it
   rather than getting its own visual treatment. ---- */
.advanced-search-select {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  background: var(--color-surface);
  color: var(--color-text-muted);
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
  flex-shrink: 0;
}

/* ---- infinite-scroll pagination (2026-08-07) ---- */
.load-more-sentinel {
  height: 1px;
}

.results-loading-more {
  text-align: center;
  padding: var(--space-3);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* ---- bottom chat input ---- */
.search-bar-wrap {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--space-3);
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
}

.search-bar {
  display: flex;
  gap: var(--space-2);
}

/* Esc-to-clear hint + the advanced-field select + expired-policies
   checkbox, all together under the input (2026-08-27, John: "Under the
   search bar it should say ESC to clear, then the dropdown, then the
   checkbox") — these are all ways of refining what the search box does,
   so they read as one row belonging to the search box rather than
   scattered separately in the header. Two groups now, not one centered
   cluster (2026-09-05, John: "the -/+ on the right under the search" while
   the other three "hug the left... same size and spacing") —
   .search-hint-group keeps the original left cluster's own size/spacing
   unchanged, this row just places it at the start and FontSizeControl at
   the end instead of centering everything as one group.
   */
.search-hint-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.search-hint-group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.search-hint {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.search-bar input {
  flex: 1;
  /* Mobile overflow fix (2026-08-07, John: "header and search features
     are going off the side of the screen") — a flex item's default
     min-width is its own intrinsic content width, not 0, so `flex: 1`
     alone doesn't let this actually shrink below that on a narrow
     screen; the fixed-width Search button + FontSizeControl (flex-
     shrink: 0, 90px together) then push the whole bar past the
     viewport. min-width: 0 is what makes flex: 1 actually mean "take
     whatever's left," down to nothing if it has to. */
  min-width: 0;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3) var(--space-4);
  color: var(--color-text);
  font-size: var(--text-md);
}

.search-bar input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: -1px;
}

.search-bar button {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  border: none;
  border-radius: var(--radius-md, 8px);
  padding: 0 var(--space-4);
  font-weight: var(--font-weight-medium);
}

.search-bar button:hover {
  filter: brightness(1.1);
}

/* ---- search-results font-size control (John, 2026-08-02) ---- */
.font-size-control {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}

.font-size-control button {
  width: 44px;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.font-size-control button:first-child {
  border-radius: var(--radius-md, 8px) 0 0 var(--radius-md, 8px);
}

.font-size-control button:last-child {
  border-radius: 0 var(--radius-md, 8px) var(--radius-md, 8px) 0;
  border-left: none;
}

.font-size-control button svg {
  width: 18px;
  height: 18px;
}

.font-size-control button:hover:not(:disabled) {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}

.font-size-control button:disabled {
  opacity: 0.4;
}

/* ---- drop overlay ---- */
.drop-overlay {
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  border: 3px dashed var(--color-stamp);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-accent);
  z-index: var(--z-overlay, 40);
  pointer-events: none;
  animation: fade-in var(--duration-fast) ease;
}

/* ---- confirm modal ---- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 20, 18, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  z-index: var(--z-modal, 50);
  animation: fade-in var(--duration-fast) ease;
}

.modal-sheet {
  background: var(--color-surface);
  border-radius: var(--radius-lg, 12px);
  border-top: 3px solid var(--color-stamp);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 520px;
  max-height: 85vh;
  overflow-y: auto;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  animation: modal-sheet-in var(--duration-base) var(--ease-out);
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes modal-sheet-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-sheet h2 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--font-weight-semibold);
}

.field-row {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-sm);
  gap: var(--space-3);
  border-bottom: 1px dashed var(--color-border);
  padding-bottom: var(--space-2);
}

.field-row span:first-child {
  color: var(--color-text-muted);
  font-family: var(--font-sans);
}

.match-option {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-2) var(--space-3);
}

.match-option:hover {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-sm);
}

.match-option button, .modal-actions button {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  border: none;
  border-radius: var(--radius-md, 8px);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
}

.match-option button:hover:not(:disabled), .modal-actions button:hover:not(:disabled) {
  filter: brightness(1.1);
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

.modal-actions .secondary {
  background: var(--color-surface-raised);
  color: var(--color-text);
  border: 1px solid var(--color-border);
}

.modal-actions .secondary:hover:not(:disabled) {
  border-color: var(--color-accent);
  color: var(--color-accent);
  filter: none;
}

/* DocumentEditor.jsx's "Discard document" was sitting as plain .secondary
   — identical weight to "Reprocess"/"Close" right next to it, so a
   genuinely destructive action (removes the document) read as no more
   consequential than a safe one (2026-08-08 polish pass). Same
   explicit-opt-in .btn-danger convention already used for "Remove" in
   .user-row — not applied broadly, only to buttons that actually are
   destructive. */
.modal-actions .btn-danger {
  color: var(--color-danger);
  border-color: var(--color-danger);
}

.modal-actions .btn-danger:hover:not(:disabled) {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: var(--color-accent-contrast);
}

.status-badge {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm, 4px);
  background: var(--color-stamp);
  color: var(--color-accent-contrast);
  align-self: flex-start;
}

/* ---- billing section (BillingSection.jsx, 2026-08-29) ---- */
.billing-issue {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-md, 8px);
  color: var(--color-danger);
  font-size: var(--text-sm);
  margin-bottom: var(--space-2);
}

.billing-issue span {
  color: var(--color-text-muted);
}

.billing-history {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
}

.billing-history-row {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr auto;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}

.billing-status {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm, 4px);
  border: 1px solid currentColor;
  justify-self: end;
}

.billing-status-ok { color: var(--color-ok); }
.billing-status-pending { color: var(--color-warn); }
.billing-status-failed { color: var(--color-danger); }

@media (max-width: 480px) {
  .billing-history-row { grid-template-columns: 1fr 1fr; row-gap: 2px; }
}

/* AccountPanel.jsx's old CSV importer already used this class for its
   helper copy, but it was never actually defined (a real gap, found
   2026-09-02 while rebuilding that flow) — every ".text-muted" element on
   the page has been rendering at full body-text contrast this whole time
   instead of the intended de-emphasized tone. */
.text-muted {
  color: var(--color-text-muted);
}

/* ---- customer import review (CustomerImportModal.jsx, 2026-09-02, John:
   "it needs to be presented and approved before it is permanently added
   ... Make this reversable") — reuses the paper-ledger folder-tab motif
   (.customer-card's colored left spine, same idea .status-badge/
   .billing-status already apply via color) for each staged row instead
   of inventing a new marker: the spine color says what will happen to
   that row (new profile / update an existing one / can't be read). */
.modal-sheet-wide {
  max-width: 720px;
}

.import-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.import-summary strong {
  color: var(--color-text);
  font-family: var(--font-mono);
}

.import-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-height: 360px;
  overflow-y: auto;
  padding-right: var(--space-1);
}

.import-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--color-border);
  background: var(--color-surface-raised);
  border-radius: 0 var(--radius-sm, 6px) var(--radius-sm, 6px) 0;
  font-size: var(--text-sm);
}

.import-row-new { border-left-color: var(--color-accent); }
.import-row-matched { border-left-color: var(--color-stamp); }
.import-row-invalid { border-left-color: var(--color-danger); opacity: 0.7; }

.import-row-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.import-row-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.import-row-status {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.import-row-status-new { color: var(--color-accent); }
.import-row-status-matched { color: var(--color-stamp); }
.import-row-status-invalid { color: var(--color-danger); }

.import-row-error {
  color: var(--color-danger);
  font-size: var(--text-xs);
}

.import-history {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.import-history-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-sm);
  padding: var(--space-2) 0;
  border-bottom: 1px dashed var(--color-border);
}

.import-history-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.import-history-meta span:last-child {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-family: var(--font-mono);
}

/* ---- mobile-first: base styles above are single-column and touch-sized
   already; layer up for wider screens ---- */
@media (min-width: 700px) {
  .results {
    max-width: 640px;
    margin: 0 auto;
    width: 100%;
  }

  .search-bar-wrap {
    max-width: 640px;
    margin: 0 auto;
    left: 0;
    right: 0;
  }
}

/* ---- auth screens: plain, quiet, no folder-tab flourish here — the
   signature belongs to the working CRM, not the login gate ---- */
.auth-screen {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}

/* A real card (2026-08-28 polish pass — this only had flex/gap before,
   so every auth screen was just form fields floating on the bare page
   background). Resting shadow, not just the hover-only lift most list-item
   cards use, since this is the page's one focal element with no sibling
   cards around it to establish grouping via border alone. */
.auth-card {
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-8);
}

/* Wider variant for the curve-pricing step (TierPicker.jsx) — first widened
   for the chart's axis labels (John, 2026-08-27: "make the parent
   container a little wider"), widened again for the two-panel tool/CTA
   layout, which needs real side-by-side room. */
.auth-card-wide {
  max-width: 680px;
}

.auth-card h1, .auth-card h2 {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  color: var(--color-accent);
  text-align: center;
}

.auth-card p.subtitle {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  text-align: center;
  margin-top: calc(-1 * var(--space-2));
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.field label {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.field-pair {
  display: flex;
  gap: var(--space-2);
}

.field-pair .field {
  flex: 1;
}

/* ---- desktop app download page (DownloadPage.jsx, 2026-08-07) ----
   Same centered, generous layout as .auth-screen but wider (this isn't a
   form, it's two side-by-side choices) and deliberately calmer — this
   page's one job is "pick your OS, get the file," nothing to second-guess.
   Cards reuse the folder-tab left-spine motif from .customer-card, here
   marking "which platform" instead of renewal urgency. */
.download-page {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--space-16) var(--space-4) var(--space-12);
  gap: var(--space-10);
}

.download-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  text-align: center;
  max-width: 480px;
}

/* Standalone centered brand lockup — LogoMark + wordmark, used above a
   focal card/hero on a page with no persistent .app-header (auth screens,
   the download page). Shared class so every no-header page anchors the
   same way; the page's own task-specific h1 goes below it. */
.brand-lockup {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  color: var(--color-accent);
  margin-bottom: var(--space-4);
}

.download-hero h1 {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  margin: 0;
}

.download-hero .subtitle {
  color: var(--color-text-muted);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  margin: 0;
}

.download-version {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  padding: var(--space-1) var(--space-3);
  margin-top: var(--space-2);
}

.download-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(220px, 260px));
  gap: var(--space-4);
  width: 100%;
  max-width: 860px;
}

@media (max-width: 860px) {
  .download-grid {
    grid-template-columns: repeat(2, minmax(220px, 280px));
    max-width: 600px;
  }
}

@media (max-width: 560px) {
  .download-grid {
    grid-template-columns: 1fr;
    max-width: 340px;
  }
}

.download-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--tab-color, var(--color-border));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5);
  text-decoration: none;
  color: inherit;
  transition: box-shadow var(--duration-fast) ease, transform var(--duration-fast) ease, border-color var(--duration-fast) ease;
}

.download-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.download-card:active {
  transform: translateY(0) scale(0.98);
}

.download-card-label {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
}

.download-card-detail {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  flex: 1;
}

.download-card-cta {
  font-family: var(--font-sans);
  font-weight: var(--font-weight-medium);
  font-size: var(--text-sm);
  color: var(--color-accent);
}

.download-card-cta::after {
  content: ' →';
}

.download-footnote {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  text-align: center;
  max-width: 420px;
  line-height: var(--leading-relaxed);
}

/* ---- affiliate program page (AffiliatesPage.jsx) ----
   Redesigned 2026-08-28 (John: "needs to look identical to the home page
   in terms of design and layout and colors") to reuse landing.html's own
   navy/cream/cyan/amber identity and header/hero/section/card rhythm,
   verbatim token-for-token — same reasoning landing.html itself uses for
   staying independent of the app's [data-theme] toggle: this is a public
   marketing page, not an app screen, so it keeps ONE fixed identity
   (dark navy default, following prefers-color-scheme when it says light)
   rather than following the signed-in app's own theme preference. Fonts
   loaded in affiliates.html's own <head> (Space Grotesk/Manrope/JetBrains
   Mono, same as landing.html's), not the app's Newsreader/Public Sans/
   Space Mono stack — this bundle renders nothing else that would need
   those. */
.aff-page {
  --aff-bg: #0B0F1E;
  --aff-surface: #131A30;
  --aff-border: #2A3557;
  --aff-text: #EDEFFC;
  --aff-text-muted: #8890B5;
  --aff-cyan: #3DDCFF;
  --aff-cyan-ink: #3DDCFF;
  --aff-amber: #FFA057;
  --aff-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.6);

  --aff-font-display: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  --aff-font-body: 'Manrope', ui-sans-serif, system-ui, sans-serif;
  --aff-font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--aff-bg);
  color: var(--aff-text);
  font-family: var(--aff-font-body);
}

@media (prefers-color-scheme: light) {
  .aff-page {
    --aff-bg: #F6F7FC;
    --aff-surface: #FFFFFF;
    --aff-border: #D8DCF0;
    --aff-text: #0B0F1E;
    --aff-text-muted: #5B6485;
    --aff-cyan-ink: #0E7490;
    --aff-amber: #B45309;
    --aff-shadow: 0 20px 50px -20px rgba(15, 23, 42, 0.18);
  }
}

.aff-page :focus-visible {
  outline: 2px solid var(--aff-cyan-ink);
  outline-offset: 3px;
}

.aff-header {
  width: 100%;
  max-width: 960px;
  padding: var(--space-4);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.aff-header-left {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  min-width: 0;
}

.aff-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  color: inherit;
}

.aff-wordmark {
  font-family: var(--aff-font-display);
  font-weight: 700;
  font-size: var(--text-lg);
  letter-spacing: -0.02em;
  color: var(--aff-text);
}

.aff-nav {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

.aff-nav a {
  font-family: var(--aff-font-body);
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--aff-text-muted);
  text-decoration: none;
  transition: color var(--duration-fast) ease;
}

.aff-nav a:hover, .aff-nav a:visited:hover { color: var(--aff-cyan-ink); }
.aff-nav a:visited { color: var(--aff-text-muted); }

/* Current-page highlight — this IS the affiliates page, so its own nav
   link reads as active rather than muted like the others. */
.aff-nav a.aff-nav-active,
.aff-nav a.aff-nav-active:visited {
  color: var(--aff-cyan-ink);
  font-weight: 600;
}

@media (max-width: 560px) {
  .aff-nav { gap: var(--space-3); }
  .aff-header-left { gap: var(--space-3); }
}

.aff-signin {
  background: transparent;
  border: 1px solid var(--aff-border);
  border-radius: var(--radius-md);
  color: var(--aff-text);
  padding: var(--space-2) var(--space-4);
  font-family: var(--aff-font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  flex-shrink: 0;
  transition: border-color var(--duration-fast) ease, transform var(--duration-fast) ease;
}

.aff-signin:hover { border-color: var(--aff-cyan-ink); }
.aff-signin:active { transform: scale(0.97); }

.aff-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-width: 640px;
  padding: var(--space-8) var(--space-4) var(--space-6);
  gap: var(--space-5);
}

.aff-hero h1 {
  font-family: var(--aff-font-display);
  font-weight: 700;
  font-size: clamp(1.9rem, 5.5vw, 2.9rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--aff-text);
  margin: 0;
  max-width: 17ch;
}

.aff-hero-sub {
  font-family: var(--aff-font-body);
  color: var(--aff-text-muted);
  font-size: var(--text-lg);
  max-width: 42ch;
  margin: 0;
}

/* The signature element (see AffiliatesPage.jsx's header comment) — same
   card/shadow/mono-digit treatment as landing.html's live pricing
   calculator, applied to this page's own live number. */
.aff-spots-widget {
  margin-top: var(--space-2);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  background: var(--aff-surface);
  border: 1px solid var(--aff-border);
  border-radius: var(--radius-lg);
  padding: var(--space-5) var(--space-8);
  box-shadow: var(--aff-shadow);
}

.aff-spots-count {
  font-family: var(--aff-font-mono);
  font-weight: 700;
  font-size: var(--text-3xl, 2.25rem);
  color: var(--aff-cyan-ink);
  line-height: 1;
}

.aff-spots-label {
  font-family: var(--aff-font-body);
  font-size: var(--text-sm);
  color: var(--aff-text-muted);
}

.aff-section {
  max-width: 960px;
  width: 100%;
  margin: 0 auto;
  padding: var(--space-8) var(--space-4);
}

.aff-section-title {
  font-family: var(--aff-font-display);
  font-weight: 700;
  font-size: clamp(1.4rem, 3.6vw, 1.9rem);
  letter-spacing: -0.01em;
  color: var(--aff-text);
  text-align: center;
  margin: 0 0 var(--space-6);
}

.aff-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-5);
}

/* Steps + perks in one row of 4 (2026-08-28, John) — explicit 4-up rather
   than auto-fit's minmax(240px,1fr): at .aff-section's 960px max width
   four cards land just under that 240px floor, which would silently wrap
   to 3+1 instead of the single row asked for. */
.aff-grid-4 {
  grid-template-columns: repeat(4, 1fr);
}

@media (max-width: 860px) {
  .aff-grid-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 480px) {
  .aff-grid-4 { grid-template-columns: 1fr; }
}

.aff-card {
  background: var(--aff-surface);
  border: 1px solid var(--aff-border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  text-align: left;
}

.aff-card h3 {
  font-family: var(--aff-font-display);
  font-weight: 600;
  font-size: var(--text-md);
  color: var(--aff-text);
  margin: 0 0 var(--space-2);
}

.aff-card p {
  font-family: var(--aff-font-body);
  color: var(--aff-text-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  margin: 0;
}

.aff-apply {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-bottom: var(--space-12);
}

.aff-apply-sub {
  font-family: var(--aff-font-body);
  color: var(--aff-text-muted);
  margin: calc(-1 * var(--space-4)) 0 var(--space-6);
}

.aff-form-card {
  background: var(--aff-surface);
  border: 1px solid var(--aff-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--aff-shadow);
  padding: var(--space-6) var(--space-5);
  max-width: 420px;
  width: 100%;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.aff-form-card h2 {
  font-family: var(--aff-font-display);
  font-weight: 700;
  font-size: var(--text-xl);
  color: var(--aff-text);
  text-align: center;
  margin: 0;
}

.aff-form-card .field label {
  font-family: var(--aff-font-body);
  color: var(--aff-text-muted);
}

.aff-form-card .field input,
.aff-form-card .field textarea {
  font-family: var(--aff-font-body);
  background: var(--aff-bg);
  border: 1px solid var(--aff-border);
  border-radius: var(--radius-md);
  color: var(--aff-text);
}

.aff-form-card .field input:focus,
.aff-form-card .field textarea:focus {
  outline: none;
  border-color: var(--aff-cyan-ink);
}

.aff-form-note {
  font-family: var(--aff-font-body);
  color: var(--aff-text-muted);
  line-height: var(--leading-relaxed);
  margin: var(--space-2) 0 0;
}

/* Screening questions (2026-08-28) — a fieldset per question so the
   Yes/No pair reads as one grouped control, not two stray radios. */
.aff-question {
  border: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.aff-question legend {
  font-family: var(--aff-font-body);
  font-size: var(--text-sm);
  color: var(--aff-text-muted);
  padding: 0;
  margin: 0 0 var(--space-1);
}

.aff-question-options {
  display: flex;
  gap: var(--space-4);
}

.aff-question-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--aff-font-body);
  font-size: var(--text-sm);
  color: var(--aff-text);
  cursor: pointer;
}

.aff-question-option input[type="radio"] {
  accent-color: var(--aff-cyan-ink);
}

.aff-cta {
  background: var(--aff-cyan);
  color: #06121A;
  border: none;
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-6);
  font-family: var(--aff-font-body);
  font-weight: 600;
  font-size: var(--text-md);
  box-shadow: 0 0 0 1px rgba(61, 220, 255, 0.4), 0 8px 32px -8px rgba(61, 220, 255, 0.55);
  cursor: pointer;
  transition: box-shadow var(--duration-fast) ease, transform var(--duration-fast) ease;
}

.aff-cta:hover:not(:disabled) { box-shadow: 0 0 0 1px rgba(61, 220, 255, 0.6), 0 10px 40px -6px rgba(61, 220, 255, 0.7); }
.aff-cta:active:not(:disabled) { transform: scale(0.97); }
.aff-cta:disabled { opacity: 0.6; cursor: default; }

@media (max-width: 480px) {
  .aff-header { padding: var(--space-3); flex-wrap: wrap; row-gap: var(--space-2); }
  .aff-header-left { flex-wrap: wrap; row-gap: var(--space-2); }
  .aff-hero { padding-top: var(--space-6); gap: var(--space-4); }
}

/* ---- company/user profile sections (AccountPanel.jsx, AdminMyProfile.jsx, 2026-08-02) ---- */
.company-profile-view {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}

.company-profile-logo {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-md, 8px);
  overflow: hidden;
  flex-shrink: 0;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
}

.company-profile-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.company-profile-logo-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  color: var(--color-text-muted);
}

.company-profile-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.company-profile-name {
  font-family: var(--font-display);
  font-weight: var(--font-weight-semibold);
  font-size: var(--text-lg);
  margin-bottom: 2px;
}

.company-profile-info .btn-link {
  align-self: flex-start;
  margin-top: var(--space-1);
}

.user-profile-view {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.user-profile-name {
  font-weight: var(--font-weight-semibold);
}

.user-profile-view .btn-link {
  align-self: flex-start;
  margin-top: var(--space-1);
}

.field input,
.field textarea {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3);
  color: var(--color-text);
  font-size: var(--text-md);
}

.field textarea {
  font-family: var(--font-sans);
  resize: vertical;
}

.field input:focus,
.field textarea:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: -1px;
}

/* ---- password-visibility toggle (PasswordInput.jsx, 2026-08-27) ---- */
.password-input {
  position: relative;
  display: flex;
}

.password-input input {
  width: 100%;
  padding-right: 2.25rem;
}

.password-input-toggle {
  position: absolute;
  right: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  padding: var(--space-1);
  color: var(--color-text-muted);
  cursor: pointer;
}

.password-input-toggle:hover {
  color: var(--color-text);
}

.btn-primary {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  border: none;
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3);
  font-size: var(--text-md);
  font-weight: var(--font-weight-medium);
}

.btn-primary:hover:not(:disabled) {
  filter: brightness(1.1);
}

.btn-primary:disabled {
  opacity: 0.6;
}

.btn-link {
  background: none;
  border: none;
  color: var(--color-link);
  font-size: var(--text-sm);
  text-decoration: underline;
  padding: 0;
  align-self: center;
}

.btn-link:hover {
  opacity: 0.7;
}

.auth-error {
  color: var(--color-danger);
  font-size: var(--text-sm);
  text-align: center;
  animation: fade-in var(--duration-base) ease;
}

/* ---- usage-based curve pricing (TierPicker.jsx, 2026-08-26) ----
   Replaces the old tier-card grid — nothing to pick anymore, just a live
   estimate against a real labeled chart. The curve is drawn from actual
   /pricing-tiers/estimate samples (never a client-side guess at the
   formula), so it can never show a different curve than what bills.
   Split into two panels (2026-08-27, John: "the user doesn't actually
   need to select anything here... the subscription button is the main
   thing") — a quiet, bordered "estimate your cost" panel that's genuinely
   optional, and a filled accent-color panel around the actual decision
   (start subscription), so the eye lands on the button, not the chart. */
.curve-pricing {
  display: flex;
  align-items: stretch;
  gap: var(--space-5);
}

@media (max-width: 640px) {
  .curve-pricing {
    flex-direction: column;
  }
}

.curve-pricing-tool {
  flex: 1 1 300px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg, 12px);
}

.curve-pricing-eyebrow {
  margin: 0;
  font-size: var(--text-xs, var(--text-sm));
  font-weight: var(--font-weight-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-align: center;
}

.curve-pricing-tool .subtitle {
  margin-top: calc(-1 * var(--space-1));
}

.curve-pricing-cta {
  flex: 1 1 240px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-5);
  background: var(--color-surface);
  border: 2px solid var(--color-accent);
  border-radius: var(--radius-lg, 12px);
}

.curve-pricing-cta h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--color-text);
}

.curve-pricing-cta p {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.curve-pricing-cta-button {
  width: 100%;
  font-size: var(--text-lg);
  padding: var(--space-4);
}

.curve-pricing-chart {
  width: 100%;
  height: auto;
  display: block;
  cursor: pointer;
  touch-action: none;
}

.curve-pricing-chart:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.curve-pricing-gridline {
  stroke: var(--color-border);
  stroke-width: 1;
  opacity: 0.6;
}

.curve-pricing-axis {
  stroke: var(--color-border);
  stroke-width: 1;
}

.curve-pricing-tick {
  font-family: var(--font-mono);
  font-size: 7px;
  fill: var(--color-text-muted);
}

.curve-pricing-axis-title {
  font-family: var(--font-sans);
  font-size: 8px;
  fill: var(--color-text-muted);
}

.curve-pricing-crosshair {
  stroke: var(--color-accent);
  stroke-width: 1;
  stroke-dasharray: 2 2;
  opacity: 0.5;
}

.curve-pricing-marker {
  fill: var(--color-accent);
  /* No transition — the marker is driven by instant local interpolation
     (see TierPicker.jsx's interpolateCents), so it needs to track the
     pointer 1:1 while dragging. A CSS transition here would make it
     visibly lag behind the slider instead. */
}

/* The count is the thing the user actually controls — it needs to read
   with the same weight as the price it produces, not sit off to the side
   as a small generic form field (John, 2026-08-27: "the user is drawn to
   the [price] but it's almost visually hard to see the number of
   policies"). Big mono number + full-width slider, both centered, placed
   above the chart so the flow reads input → curve → price. */
.curve-pricing-input-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
}

.curve-pricing-input-label {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.curve-pricing-input-big {
  width: 100%;
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  text-align: center;
  background: transparent;
  border: none;
  border-bottom: 2px solid var(--color-border);
  padding: 0 0 var(--space-1);
  -moz-appearance: textfield;
}

.curve-pricing-input-big::-webkit-outer-spin-button,
.curve-pricing-input-big::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.curve-pricing-input-big:focus {
  outline: none;
  border-bottom-color: var(--color-accent);
}

.curve-pricing-slider {
  width: 100%;
  accent-color: var(--color-accent);
}

.curve-pricing-readout {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
}

.curve-pricing-amount {
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  color: var(--color-accent);
}

.curve-pricing-unit {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.curve-pricing-avg {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-align: center;
  margin: calc(-1 * var(--space-2)) 0 0;
}

.curve-pricing-fineprint {
  font-size: var(--text-xs, var(--text-sm));
  color: var(--color-text-muted);
  text-align: center;
  margin: 0;
}

.mfa-qr {
  align-self: center;
  border-radius: var(--radius-md, 8px);
  border: 1px solid var(--color-border);
}

/* ---- feedback modal (FeedbackModal.jsx, 2026-08-02) ---- */
.mic-meter {
  height: 16px;
  border-radius: var(--radius-full, 999px);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  overflow: hidden;
}

.mic-meter-fill {
  height: 100%;
  background: var(--color-ok);
  transition: width 80ms linear;
}

.feedback-recording {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-6) 0;
}

.feedback-recording-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--color-danger);
  animation: feedback-pulse 1.2s ease-in-out infinite;
}

@keyframes feedback-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.4; transform: scale(0.8); }
}

.feedback-countdown {
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  min-width: 4ch;
}

.feedback-preview {
  width: 100%;
  max-height: 320px;
  border-radius: var(--radius-md, 8px);
  background: #000;
}

/* ---- recording control flow redesign (2026-08-27) ----
   John, live testing: the old control sat centered inside the normal
   modal, in the way, and clicking outside it destroyed the recording
   (the backdrop's click-to-close handler was still live). Now: a small
   Start/Cancel confirmation (.modal-sheet-compact) runs after the OS
   screen-picker resolves but before capture starts; once recording is
   live there's no backdrop at all, just the small draggable pill below,
   so the rest of the screen stays fully usable; its one button pauses
   (never destructively stops) into a real review window whose actions
   are deliberately split — destructive ones (Cancel, Start over) on the
   left, forward ones (Submit, Resume) on the right — so an accidental
   click lands on the wrong side of the window, not just next to the
   right button. */
.modal-sheet-compact {
  max-width: 360px;
}

.recording-pill {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: var(--z-modal, 50);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full, 999px);
  box-shadow: var(--shadow-lg);
  padding: var(--space-2) var(--space-3);
  user-select: none;
}

.recording-pill-dragging {
  opacity: 0.9;
  transform: scale(1.03);
  transition: none;
}

/* The only draggable part of the pill — deliberately separate from the
   pause button next to it, so dragging and clicking never fight over
   the same pointer-down (John: "the user should be able to move this
   around the screen wherever they want" but "it should have a control
   to stop recording"). */
.recording-pill-grip {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
  cursor: grab;
  padding: var(--space-1);
  touch-action: none;
}

.recording-pill-grip:active {
  cursor: grabbing;
}

.recording-pill-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-danger);
  animation: feedback-pulse 1.2s ease-in-out infinite;
  flex-shrink: 0;
}

.recording-pill-countdown {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  min-width: 4ch;
}

.recording-pill-pause {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  flex-shrink: 0;
  padding: 0;
}

.recording-pill-pause:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.modal-actions-split {
  justify-content: space-between;
}

.modal-actions-group {
  display: flex;
  gap: var(--space-2);
}

/* ---- toggle switch (ToggleSwitch.jsx) ---- */
.switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  flex-shrink: 0;
}

.switch-disabled {
  cursor: default;
  opacity: 0.5;
}

.switch input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.switch-track {
  width: 40px;
  height: 22px;
  background: var(--color-border);
  border-radius: var(--radius-full, 999px);
  position: relative;
  transition: background-color var(--duration-fast) ease;
}

.switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  transition: transform var(--duration-base) var(--ease-out);
}

.switch input:checked + .switch-track {
  background: var(--color-ok);
}

.switch input:checked + .switch-track .switch-thumb {
  transform: translateX(18px);
}

.switch input:focus-visible + .switch-track {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ---- account panel ---- */
.account-panel .field-row {
  padding: var(--space-2) 0;
}

.field-row:has(input[type="checkbox"]) {
  cursor: pointer;
}

.field-row:has(input[type="checkbox"]):hover span:first-child {
  color: var(--color-accent);
}

.account-section {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3);
  margin-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* The section directly under the identity summary (Company profile) — no
   rule needed there, the summary rows above already read as a distinct
   block and the section's own uppercase label carries the break (John,
   2026-08-02: "there is an HR between Company and Company Profile
   header, remove it, but keep the spacing"). Later sections (Security,
   Users, Billing, Data) keep their divider — those genuinely are a
   sequence of separate settings groups, not identity summary vs. form. */
.field-row + .account-section {
  border-top: none;
}

.account-section h3 {
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

/* A plain <form> has no layout of its own — without this, fields and the
   trailing submit button just stack edge-to-edge with zero gap (found in
   AccountPanel.jsx's Company-profile and add-user forms, the only two
   forms in the app that weren't already using .auth-card's flex+gap). */
.settings-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.user-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.user-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--text-sm);
}

/* Neutral by default (2026-08-03: every button in a .user-row — "Change
   password", "Set up" 2FA, "Turn off" MFA — was rendering in
   --color-danger just because it shared this class with the one button
   that actually IS destructive ("Remove" user), reading as alarming for
   completely benign actions). Only .btn-danger (applied explicitly to
   Remove, below) gets the red treatment now. */
.user-row button {
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 4px);
  padding: 2px var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text);
}

.user-row button:hover:not(:disabled) {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.user-row .btn-danger {
  color: var(--color-danger);
  border-color: var(--color-danger);
}

.user-row .btn-danger:hover:not(:disabled) {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: #fff;
}

.danger-zone {
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.danger-zone button {
  background: var(--color-danger);
  color: #fff;
  border: none;
  border-radius: var(--radius-md, 8px);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
}

.danger-zone button:hover:not(:disabled) {
  filter: brightness(1.1);
}

/* ---- document review modal (DocumentReview.jsx) ---- */
.review-sheet {
  max-width: 960px;
  width: 100%;
}

.review-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

.review-filename {
  display: block;
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  word-break: break-all;
}

.review-back {
  background: none;
  border: none;
  color: var(--color-link);
  font-size: var(--text-sm);
  padding: 0 0 var(--space-1);
  display: block;
}

.review-back:hover {
  opacity: 0.7;
}

.review-list-intro {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin: 0 0 var(--space-3);
}

.review-doc-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.review-doc-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3);
  text-align: left;
}

.review-doc-row:hover {
  border-color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 6%, var(--color-surface-raised));
}

.review-doc-row-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.review-doc-filename {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  word-break: break-all;
}

.review-doc-reason {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

.review-explanation {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3);
  font-size: var(--text-sm);
}

.review-explanation ul {
  margin: var(--space-1) 0 0;
  padding-left: var(--space-4);
}

.review-duplicate-link {
  display: inline-block;
  margin-top: var(--space-2);
  font-size: var(--text-sm);
}

.review-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  min-height: 0;
  flex: 1;
  overflow: hidden;
}

.review-preview {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  overflow: hidden;
  min-height: 260px;
  display: flex;
  flex: 1;
}

/* ---- duplicate-compare: existing vs. new, clearly labeled (found via
   John testing: it wasn't obvious which document was which) ---- */
.review-compare {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-height: 0;
}

.review-compare-pane {
  display: flex;
  flex-direction: column;
  min-height: 220px;
  flex: 1;
}

.review-compare-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-xs);
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm, 4px) var(--radius-sm, 4px) 0 0;
}

.review-compare-label-existing {
  background: var(--color-surface-raised);
  color: var(--color-text-muted);
}

.review-compare-label-new {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}

.review-compare-filename {
  text-transform: none;
  letter-spacing: normal;
  opacity: 0.8;
}

@media (min-width: 860px) {
  .review-compare {
    flex-direction: row;
  }
}

.review-preview iframe,
.review-preview img {
  width: 100%;
  height: 100%;
  min-height: 260px;
  border: none;
  object-fit: contain;
}

.review-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  overflow-y: auto;
  padding-right: var(--space-1);
}

.review-required-mark {
  color: var(--color-danger);
}

.review-form select,
.review-form input {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 4px);
  padding: var(--space-2);
  color: var(--color-text);
  font-size: var(--text-sm);
  width: 100%;
}

.review-row3 {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.review-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.review-list-row {
  display: grid;
  grid-template-columns: 90px 1fr 1fr auto;
  gap: var(--space-2);
  align-items: center;
  margin-top: var(--space-2);
}

.review-list-row:has(select + input:only-of-type) {
  grid-template-columns: 90px 1fr auto;
}

.review-address-row {
  grid-template-columns: 90px 1.4fr 1fr 0.6fr 0.7fr auto;
}

/* Coverages/Items rows (2026-08-03) — three plain text inputs, no label
   select, so unlike .review-list-row's default (90px first column, sized
   for a short select) every column should share space evenly. */
.review-list-row-even3 {
  grid-template-columns: 1fr 1fr 1fr auto;
}

/* Policy parties row (2026-08-03) — role select + name + full address +
   license number, always rendered (not conditional per role) so the row
   shape never changes as the agent picks a different role. */
.review-party-row {
  grid-template-columns: 130px 1.1fr 1fr 0.8fr 0.5fr 0.6fr 0.9fr auto;
}

.review-row-remove {
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 4px);
  color: var(--color-text-muted);
  width: 26px;
  height: 26px;
  font-size: var(--text-md);
  line-height: 1;
  flex-shrink: 0;
}

.review-row-remove:hover {
  border-color: var(--color-danger);
  color: var(--color-danger);
}

/* ---- mobile: the names/phones/addresses editor rows pack 3-6 columns
   into one grid line on desktop — on a phone-width modal there isn't
   room for that without truncating every field. Stack each row's
   inputs instead; the label select and remove button share a line so
   the row still reads as one unit. ---- */
@media (max-width: 600px) {
  .review-list-row,
  .review-list-row:has(select + input:only-of-type),
  .review-address-row,
  .review-list-row-even3,
  .review-party-row,
  .review-row3 {
    grid-template-columns: 1fr;
  }

  .review-list-row > select,
  .review-address-row > select,
  .review-party-row > select {
    grid-column: 1 / -2;
  }

  .review-row-remove {
    grid-row: 1;
    grid-column: -2 / -1;
    justify-self: end;
  }
}

@media (min-width: 860px) {
  .review-split {
    grid-template-columns: 1fr 1fr;
  }
}

/* ---- policy document viewer (PolicyDocumentModal.jsx) ---- */
.document-view-sheet {
  max-width: 900px;
  width: 100%;
  height: 85vh;
}

.document-view-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.document-view-filename {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  word-break: break-all;
}

.document-view-actions {
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
}

.document-view-preview {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  overflow: hidden;
  flex: 1;
  min-height: 0;
  display: flex;
}

.document-view-preview iframe,
.document-view-preview img,
.document-view-preview video {
  width: 100%;
  height: 100%;
  border: none;
  object-fit: contain;
}

/* ---- household relationship map (RelationshipMap.jsx, 2026-08-24) ----
   Coverage-stamp row is the signature element — a literal stamped/
   unstamped badge per canonical coverage category, extending the same
   "stamp" accent (--color-stamp) already used on modal-sheet's top
   border, not a new color introduced just for this screen. */
.relationship-map-sheet {
  max-width: 720px;
  width: 100%;
  max-height: 85vh;
}

.relationship-map-title {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
}

.relationship-map-title-name {
  font-family: var(--font-mono);
}

.relationship-map-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  overflow-y: auto;
}

.gap-banner-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.gap-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  background: color-mix(in srgb, var(--color-warn) 14%, var(--color-surface));
  border-left: 3px solid var(--color-warn);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text);
}

.gap-banner svg {
  color: var(--color-warn);
  flex-shrink: 0;
  margin-top: 2px;
}

.relationship-map-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.relationship-map-heading {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.household-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.household-member {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.household-member-header {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.household-member-name {
  font-family: var(--font-mono);
  font-size: var(--text-base);
  color: var(--color-text);
}

.household-member-tag {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-full);
  padding: 1px var(--space-2);
}

.household-member-contact {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.stamp-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.stamp-row-empty {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-style: italic;
}

.stamp {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.02em;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  line-height: var(--leading-tight);
}

.stamp-held {
  background: var(--color-stamp);
  color: var(--color-accent-contrast);
  border: 1px solid var(--color-stamp);
}

.stamp-other {
  background: var(--color-surface-raised);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
}

.stamp-gap {
  background: transparent;
  color: var(--color-text-muted);
  border: 1px dashed var(--color-border);
}

.other-connections-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.other-connection {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  border-bottom: 1px dashed var(--color-border);
  padding-bottom: var(--space-2);
  font-size: var(--text-sm);
}

.other-connection-name {
  font-family: var(--font-mono);
  color: var(--color-text);
}

.other-connection-detail {
  font-family: var(--font-sans);
  color: var(--color-text-muted);
}

/* ---- recent-uploads dropdown (RecentUploads.jsx) ---- */
.upload-status-anchor {
  position: relative;
}

.recent-uploads {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  width: 320px;
  max-height: 420px;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  box-shadow: var(--shadow-md);
  padding: var(--space-3);
  z-index: var(--z-dropdown, 10);
  transform-origin: top right;
  animation: dropdown-in var(--duration-base) var(--ease-out);
}

@keyframes dropdown-in {
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.recent-uploads-head {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  margin-bottom: var(--space-2);
}

.recent-uploads-empty {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin: var(--space-2) 0;
}

.recent-uploads-batch {
  padding: var(--space-2) 0;
  border-top: 1px dashed var(--color-border);
}

.recent-uploads-batch:first-of-type {
  border-top: none;
}

.recent-uploads-batch-time {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}

.recent-uploads-doc {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-sm);
  padding: 2px 0;
}

.recent-uploads-filename {
  word-break: break-all;
}

.recent-uploads-customer {
  color: var(--color-text-muted);
  white-space: nowrap;
  font-size: var(--text-xs);
}

/* ---- feedback dropdown (FeedbackMenu.jsx, 2026-08-27) — same shape as
   .recent-uploads above, deliberately: same anchored-panel treatment,
   just its own content. Its anchor wrapper reuses .upload-status-anchor
   (App.jsx) rather than a new class, since the positioning need is
   identical. ---- */
.feedback-menu {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  width: 320px;
  max-height: 420px;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  box-shadow: var(--shadow-md);
  padding: var(--space-2);
  z-index: var(--z-dropdown, 10);
  transform-origin: top right;
  animation: dropdown-in var(--duration-base) var(--ease-out);
}

/* Sits above the message list, its own full-width row — the primary
   action here (recording feedback), so it reads as a button, not a list
   item, and never gets mistaken for one of the messages below it. */
.feedback-menu-new {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3) var(--space-3);
  margin-bottom: var(--space-2);
  background: none;
  border: none;
  border-bottom: 1px solid var(--color-border);
  border-radius: 0;
  color: var(--color-accent);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
}

.feedback-menu-new:hover {
  color: var(--color-accent);
  filter: brightness(1.15);
}

.feedback-menu-head {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  padding: 0 var(--space-1);
  margin-bottom: var(--space-1);
}

.feedback-menu-empty {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  padding: var(--space-1);
}

/* One thread per feedback submission (2026-08-27 revision — a real
   two-way conversation now, not one-off items) — its own messages plus
   a reply box, so replying to a resolved issue (John: "the user might
   still respond to the Resolved message") happens right where the
   conversation already is. */
.feedback-menu-thread {
  padding: var(--space-2) var(--space-1);
  border-top: 1px dashed var(--color-border);
}

.feedback-menu-thread:first-of-type {
  border-top: none;
}

.feedback-menu-thread-head {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  word-break: break-all;
  margin-bottom: var(--space-2);
}

.feedback-menu-message {
  padding: var(--space-2);
  border-radius: var(--radius-sm, 4px);
  margin-bottom: var(--space-2);
}

.feedback-menu-message-admin {
  background: var(--color-surface-raised);
}

.feedback-menu-message-customer {
  background: none;
  border: 1px solid var(--color-border);
}

.feedback-menu-time {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}

.feedback-menu-body {
  font-size: var(--text-sm);
  margin: 0;
}

.feedback-menu-reply {
  display: flex;
  gap: var(--space-1);
}

.feedback-menu-reply input {
  flex: 1;
  min-width: 0;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm, 4px);
  padding: var(--space-1) var(--space-2);
  color: var(--color-text);
  font-size: var(--text-sm);
}

.feedback-menu-reply button {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  border: none;
  border-radius: var(--radius-sm, 4px);
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  flex-shrink: 0;
}

.feedback-menu-reply button:hover:not(:disabled) {
  filter: brightness(1.1);
}

.feedback-menu-reply button:disabled {
  opacity: 0.5;
}

/* Same visual language as .upload-status-count (unread-count badge on a
   header icon), scoped separately since it sits on a plain .icon-btn
   rather than .upload-status. */
.feedback-icon-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: var(--radius-full, 999px);
  background: var(--color-danger);
  color: var(--color-accent-contrast);
  font-size: 10px;
  font-family: var(--font-mono);
  line-height: 16px;
  text-align: center;
  border: 2px solid var(--color-surface);
  animation: badge-pop var(--duration-base) var(--ease-out);
}

/* ---- live upload-progress dropdown (UploadProgress.jsx) ---- */
.upload-progress {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  width: 300px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  box-shadow: var(--shadow-md);
  padding: var(--space-3);
  z-index: var(--z-dropdown, 10);
  transform-origin: top right;
  animation: dropdown-in var(--duration-base) var(--ease-out);
}

.upload-progress-head {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  margin-bottom: var(--space-2);
}

.upload-progress-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 260px;
  overflow-y: auto;
}

.upload-progress-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-1);
  border-radius: var(--radius-sm, 4px);
  font-size: var(--text-sm);
}

.upload-progress-row-processing,
.upload-progress-row-uploading {
  background: var(--color-surface-raised);
}

.upload-progress-row-pending {
  color: var(--color-text-muted);
}

.upload-progress-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-link);
}

.upload-progress-row-uploading .upload-progress-icon,
.upload-progress-row-uploaded .upload-progress-icon,
.upload-progress-row-done .upload-progress-icon {
  color: var(--color-ok);
}

.upload-progress-icon svg {
  width: 16px;
  height: 16px;
}

.upload-progress-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full, 999px);
}

.upload-progress-dot-warn {
  background: var(--color-warn);
}

.upload-progress-filename {
  word-break: break-all;
}

/* Parked on an AI-infra failure (2026-09-04) — same quiet, muted
   treatment as .recent-uploads-customer's trailing annotation, never a
   warning color; it resolves on its own. */
.upload-progress-queued-label {
  margin-left: auto;
  flex-shrink: 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

/* ---- Marketing Tools (2026-08-26) — header icon -> a modal-sheet with
   a left side-nav, one entry per tool. Same modal-backdrop/modal-sheet
   base every other panel here uses, just wider to make room for the nav. ---- */
.marketing-tools-panel {
  max-width: 820px;
  width: 100%;
  max-height: 85vh;
}

.marketing-tools-layout {
  display: flex;
  gap: var(--space-5);
  min-height: 360px;
  overflow: hidden;
}

.marketing-tools-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex-shrink: 0;
  width: 160px;
  border-right: 1px solid var(--color-border);
  padding-right: var(--space-4);
}

.marketing-tools-nav-item {
  text-align: left;
  background: transparent;
  border: none;
  border-radius: var(--radius-md, 8px);
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  cursor: pointer;
}

.marketing-tools-nav-item:hover { background: var(--color-surface-raised); color: var(--color-text); }

.marketing-tools-nav-item-active {
  background: var(--color-surface-raised);
  color: var(--color-text);
  font-weight: var(--font-weight-semibold);
}

.marketing-tools-content {
  flex: 1;
  overflow-y: auto;
  min-width: 0;
}

.marketing-tools-muted {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.coverage-gap-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.coverage-gap-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-3);
}

.coverage-gap-row-main {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.coverage-gap-row-name {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
}

.coverage-gap-messages {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: var(--space-1) 0 0;
  padding: 0;
  list-style: none;
}

.coverage-gap-messages li {
  background: color-mix(in srgb, var(--color-warn) 14%, var(--color-surface));
  border-left: 3px solid var(--color-warn);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text);
}

.coverage-gap-view-btn {
  flex-shrink: 0;
  background: var(--color-surface-raised);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  cursor: pointer;
}

.coverage-gap-view-btn:hover { border-color: var(--color-accent); color: var(--color-accent); }

.coverage-gap-placeholder p {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text);
  line-height: var(--leading-relaxed, 1.6);
}

@media (max-width: 640px) {
  .marketing-tools-layout { flex-direction: column; }
  .marketing-tools-nav {
    flex-direction: row;
    width: 100%;
    border-right: none;
    border-bottom: 1px solid var(--color-border);
    padding-right: 0;
    padding-bottom: var(--space-3);
  }
}

