:root {
  /* ===== Brand — taken from the AB Horizon Group logo. Fixed across both
     themes (buttons, badges and brand accents use these verbatim). ===== */
  --navy: #1a2a68;
  --navy-light: #263a86;
  --horizon: #4f7a36;       /* logo green — the accent color */
  --horizon-dark: #436b2e;  /* darker accent for hover/pressed (was inline in .mail-compose-btn:hover / .app-update-banner button) */
  --horizon-soft: #dde8d2;  /* fixed light-green pastel — active-nav / chip fill in BOTH themes (see .stage-pill comment) */
  --danger: #c0453a;
  --danger-strong: #b93636; /* solid-fill destructive hover (was inline #b93636 / #b93636) */
  --success: #4f7a36;
  --star: #b8860b;          /* "starred" gold — mail flag / favourite (dark value in the [data-theme] block) */

  /* ===== Neutrals — cooled from the original warm beige toward a true
     premium-SaaS grey. Every surface/border/fill in the app routes through
     these, so this re-point reaches every module. ===== */
  --surface: #ffffff;       /* card / panel background */
  --surface-2: #f4f6f8;     /* subtle fill: hover, table headers, tracks — also the page bg */
  --surface-3: #e9edf2;     /* one step darker fill (selected rows) — was the undefined `--cream-dark` fallback #f1efe8 */
  --cream: var(--surface-2);/* legacy alias — kept so every existing `var(--cream)` rule still resolves */
  --cream-dark: var(--surface-3);
  --slate: #4b5568;         /* body-secondary text */
  --slate-light: #8992a3;   /* muted / meta text */
  --border: #e4e7ec;        /* was #e3e2dc (warm) — now a cool hairline */
  --border-strong: #d4d8df; /* dividers / hover borders that need a touch more contrast */

  /* ===== Theme-adaptive semantic tokens (dark values in the block below) ===== */
  --bg: var(--surface-2);
  --text: var(--navy);      /* NOTE: light value stays exactly --navy — several
                               dark-mode fixes below pin colours to --navy on the
                               understanding that it equals --text in light mode. */
  --unread-bg: #f5f8fd;
  --unread-bg-hover: #eaf1fc;

  /* ===== Spacing scale (4px base) ===== */
  --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;

  /* ===== Radius scale — base bumped 8 → 10 for a softer, modern corner ===== */
  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 14px;        /* matches the existing mobile "native card" value */
  --radius-pill: 999px;

  /* ===== Type scale ===== */
  --fs-11: 11px; --fs-12: 12px; --fs-13: 13px; --fs-14: 14px;
  --fs-15: 15px; --fs-17: 17px; --fs-20: 20px; --fs-24: 24px;
  --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700;
  --lh-tight: 1.3; --lh-normal: 1.5;

  /* ===== Elevation — theme-aware (dark overrides below) ===== */
  --shadow-xs: 0 1px 2px rgba(22, 35, 61, 0.06);
  --shadow-sm: 0 1px 2px rgba(22, 35, 61, 0.06), 0 2px 8px rgba(22, 35, 61, 0.07);
  --shadow-md: 0 4px 14px rgba(22, 35, 61, 0.10);
  --shadow-lg: 0 12px 32px rgba(22, 35, 61, 0.16);
  --shadow-card: var(--shadow-sm);

  /* ===== Focus ring ===== */
  --focus-ring: 0 0 0 3px rgba(79, 122, 54, 0.30);

  /* ===== Status tone pairs — ONE set of semantic chip colours, replacing the
     ~6 near-duplicate colour maps that were scattered through app.js. Light
     values stay close to what was already on screen; the dark values (below)
     are the real fix for "unreadable badges in dark mode". ===== */
  --tone-neutral-bg: #edeef1;  --tone-neutral-fg: #3d4453;
  --tone-info-bg:    #dbe8fb;  --tone-info-fg:    #1c5cab;
  --tone-success-bg: #e0f2e0;  --tone-success-fg: #2f7d32;
  --tone-warning-bg: #fdf0d5;  --tone-warning-fg: #a3690c;
  --tone-danger-bg:  #f6dcda;  --tone-danger-fg:  #b0392f;
  --tone-accent-bg:  #e6ecff;  --tone-accent-fg:  #3b4fc4;

  /* Chart palette — ordinal blue ramp + fixed won/lost status colors.
     Validated with the dataviz skill's validate_palette.js. Unchanged. */
  --chart-sourcing: #6da7ec;
  --chart-followup: #2a78d6;
  --chart-agreement: #1c5cab;
  --chart-golive: #104281;
  --status-good: #0ca30c;
  --status-critical: #d03b3b;
}

/* Dark mode - toggled via [data-theme="dark"] on <html> (see app.js
   initTheme/toggleTheme), persisted in localStorage. Only the neutral
   surface/text/fill tokens + the tone/elevation scales are repointed; brand
   colors (--navy, --horizon, --horizon-soft) stay fixed across themes so
   buttons and active-nav chips keep their normal contrast in both. */
[data-theme="dark"] {
  --surface: #181d29;   /* de-purpled from #171f36 — neutral charcoal */
  --surface-2: #1f2530;
  --surface-3: #28303c;
  --cream: var(--surface-2);
  --cream-dark: var(--surface-3);
  --text: #e8eaf0;
  --slate: #b7bed4;
  --slate-light: #838cab;
  --border: #2b313d;    /* was #2d3654 (blue) */
  --border-strong: #3a404e;
  --bg: #0c0f16;
  --unread-bg: #1a2233;
  --unread-bg-hover: #212c40;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.40);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.40), 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
  --focus-ring: 0 0 0 3px rgba(143, 209, 158, 0.35);

  --tone-neutral-bg: #2b313d;  --tone-neutral-fg: #c5cbd6;
  --tone-info-bg:    #17324f;  --tone-info-fg:    #9cc6f5;
  --tone-success-bg: #1c3a24;  --tone-success-fg: #7fd08a;
  --tone-warning-bg: #3d2f16;  --tone-warning-fg: #e6b667;
  --tone-danger-bg:  #3f2320;  --tone-danger-fg:  #f0a79f;
  --tone-accent-bg:  #23294a;  --tone-accent-fg:  #aab6f0;

  --star: #e3b341;   /* brighter gold — #b8860b is too dim on a dark surface */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  /* Disables the double-tap-to-zoom gesture (while leaving normal
     scrolling/panning untouched) - part of the same "no unwanted zoom"
     approach as the viewport meta tag (user-scalable=no, see index.html)
     and app.js's touch/gesture event guards; this one's the CSS-only layer
     of it, honored by every modern mobile browser. */
  touch-action: manipulation;
}

/* The #app-screen shell (below) already guarantees there is nothing for
   html/body to scroll - but on iOS Safari a touch swipe can still trigger a
   page-level elastic "rubber-band" bounce with ZERO real overflow present,
   and WebKit implements that bounce as a transform on the whole rendered
   page - which visibly drags position:fixed/sticky elements (the topbar,
   the WhatsApp/Mail/Chat full-bleed panels) along with it for the split
   second of the bounce, reading exactly like "the header is scrolling
   away" even though nothing on the page is actually out of place. This is
   the standards-based fix: tell the browser there is no document-level
   scroll to hand off to at all, so it never starts that bounce. Support:
   Safari 16+, all current Chrome/Firefox/Android WebViews; harmless no-op
   on anything older (falls back to the pre-existing #app-screen fixed
   shell, which is why the app has never actually needed body scroll). */
html, body { overscroll-behavior: none; }

.hidden { display: none !important; }

/* Respected once, globally, rather than per-animation - anyone with this
   OS-level preference set gets every transition/animation in the app
   (view-enter, the mobile "More" sheet, etc.) collapsed to instant. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---------- Login ---------- */
#login-screen, #reset-password-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg, var(--navy) 0%, var(--navy) 45%, var(--bg) 45%);
}

/* Pre-boot splash (index.html #boot-splash). Visibility is driven entirely
   by the inline <head> rule on html[data-auth="initializing"]; this only
   provides the look: a plain white full-screen with the centred logo and a
   small spinner below it - deliberately NOT the login screen's navy
   gradient. Hard-coded white (not a theme token) so it's identical in light
   and dark mode. */
#boot-splash {
  display: none; /* shown only by the inline html[data-auth="initializing"] rule */
  min-height: 100vh;
  align-items: center;
  justify-content: center;
  background: #ffffff;
}
.boot-splash-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
}
/* Override .brand-mark's login-screen bottom margin - the flex gap handles
   the spacing down to the loader. */
#boot-splash .brand-mark {
  margin: 0;
}

/* Subtle loading indicator, centred directly below the logo. Navy (a brand
   colour, pinned across light/dark - see the --navy note near the top of
   this file) over a faint navy track, on the white splash. It disappears
   with the whole splash the instant boot() clears data-auth. */
.boot-splash-loader {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2.5px solid rgba(26, 42, 104, 0.16); /* --navy (#1a2a68) at low opacity */
  border-top-color: var(--navy);
  animation: boot-splash-spin 0.7s linear infinite;
}
@keyframes boot-splash-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .boot-splash-loader { animation-duration: 1.6s; }
}

.login-card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 48px 40px;
  width: 360px;
  max-width: 100%;
  box-shadow: var(--shadow-lg);
  text-align: center;
}

/* Width is pinned alongside height (matching the default logo's own
   1117x359 aspect ratio at each size, so the current logo renders exactly
   as before) rather than left auto - a custom-uploaded logo with a
   different aspect ratio then gets letterboxed via object-fit: contain
   inside this exact same box instead of rendering at a different width and
   shifting the login card's centering or the header's nav/user-chip
   layout. See Admin Settings' Logo Management for the upload path. */
.brand-mark {
  height: 64px;
  width: 199px;
  object-fit: contain;
  margin: 0 auto 20px;
  display: block;
}
.brand-mark.small { height: 32px; width: 100px; margin: 0; }

.login-card h1 { font-size: 20px; margin: 0 0 4px; }
.subtitle { color: var(--slate-light); font-size: 13px; margin: 0 0 28px; }

#login-form { text-align: left; }
label { display: block; font-size: 12px; color: var(--slate); margin: 14px 0 4px; font-weight: 600; }
input, select, textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--horizon);
  box-shadow: var(--focus-ring);
}

button {
  cursor: pointer;
  font-family: inherit;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform 0.05s ease;
}
button:disabled { cursor: default; opacity: 0.55; }
/* Only for keyboard/programmatic focus, not every mouse click - matches
   modern browser default behavior for :focus-visible, applied explicitly
   here so it's consistent across every button variant below rather than
   depending on each browser's own default outline (which several of these
   buttons already override to `border: none`). */
button:focus-visible { outline: 2px solid var(--horizon); outline-offset: 2px; }

.primary-btn, #login-form button[type="submit"], #reset-password-form button[type="submit"] {
  background: var(--navy);
  color: white;
  border: none;
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
  margin-top: 18px;
  width: 100%;
  box-shadow: 0 1px 2px rgba(22, 35, 61, 0.12);
}
.primary-btn:hover:not(:disabled), #login-form button[type="submit"]:hover, #reset-password-form button[type="submit"]:hover { background: var(--navy-light); box-shadow: 0 2px 8px rgba(22, 35, 61, 0.2); }
.primary-btn:active:not(:disabled) { transform: translateY(1px); box-shadow: 0 1px 2px rgba(22, 35, 61, 0.12); }

.secondary-btn {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
}
.secondary-btn:hover:not(:disabled) { border-color: var(--navy); background: var(--cream); }
.secondary-btn:active:not(:disabled) { transform: translateY(1px); }

/* Destructive actions (Remove/Delete/Reset-to-default) - outlined red
   rather than solid, so it doesn't visually compete with .primary-btn for
   attention (a destructive action shouldn't be the most prominent button on
   screen), but fills solid on hover as a deliberate "you're about to commit
   to this" cue before the click actually lands. */
.danger-btn {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger);
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
}
.danger-btn:hover:not(:disabled) { background: var(--danger); color: white; }
.danger-btn:active:not(:disabled) { transform: translateY(1px); }

/* Small tertiary action (Test Connection, etc.) - visually quieter than
   .secondary-btn's full border, for an action that's frequent/low-stakes
   and shouldn't carry the same visual weight as Save. */
.ghost-btn {
  background: transparent;
  color: var(--slate);
  border: 1px solid transparent;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
}
.ghost-btn:hover:not(:disabled) { background: var(--cream); color: var(--text); }
.ghost-btn:active:not(:disabled) { transform: translateY(1px); }

.error-text { color: var(--danger); font-size: 13px; margin-top: 10px; min-height: 16px; }

/* ============================================================================
   Design system — shared utilities & components
   Consolidates patterns that were previously re-implemented ad-hoc (inline
   styles in app.js, near-duplicate per-module classes). Introduced by the
   design-system pass; module sections further down are migrated onto these.
   ========================================================================== */

.text-muted { color: var(--slate-light); }

/* Standalone "nothing here yet" / "Loading…" line for an empty list or panel
   body. Replaces the ~57 inline `style="color:#8992a3"` <p> tags in app.js.
   Use .empty-state--inline inside a table cell or a tight inline slot. */
.empty-state {
  margin: 0;
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--slate-light);
  font-size: var(--fs-13);
}
.empty-state--inline { padding: var(--space-2) 0; text-align: left; }

/* Card surface — opt-in base for panel / tile surfaces. Existing per-module
   card classes (.stat-tile, .settings-card, .chart-card, …) are migrated onto
   this in Stages 2–3. */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}
.card--interactive {
  transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
  cursor: pointer;
}
.card--interactive:hover { box-shadow: var(--shadow-md); border-color: var(--border-strong); }

/* One unambiguous headline verdict at the top of a results panel (e.g.
   Bulk Merchant Import) - the surrounding stat grid requires reading
   several numbers to know whether an operation actually worked.
   Migrated onto the theme-aware --tone-* pairs so it stays readable in dark
   mode (the old fixed light pastels did not). */
.import-result-banner { padding: 12px 16px; border-radius: var(--radius); font-size: 14px; font-weight: 700; }
.import-result-banner.success { background: var(--tone-success-bg); color: var(--tone-success-fg); }
.import-result-banner.warning { background: var(--tone-warning-bg); color: var(--tone-warning-fg); }
.import-result-banner.danger { background: var(--tone-danger-bg); color: var(--tone-danger-fg); }
.import-result-banner.info { background: var(--horizon-soft); color: var(--navy); }

/* Shown when a list endpoint's server-side cap (GET /merchants's
   DEFAULT_LIST_LIMIT) actually truncated the result - lets Search/Filters
   double as the "see more" mechanism instead of the Kanban silently
   under-representing a large stage with no indication anything was cut. */
/* color: var(--navy), not var(--text) - same fixed-light-background/
   theme-adaptive-text mismatch as .stage-pill below (see its own comment) -
   this fixed light background never changes with theme, so the text on it
   can't either. */
.capped-banner {
  padding: 10px 14px;
  border-radius: var(--radius);
  font-size: 13px;
  background: var(--horizon-soft);
  color: var(--navy);
  margin-bottom: 12px;
}

.pipeline-load-more-row { display: flex; justify-content: center; margin-top: 14px; }

/* ---------- App shell ----------
   Global fixed-header layout: #app-screen is a full-viewport-height flex
   column; the topbar takes its natural height and never scrolls; <main> is
   the ONLY scroll container. Same at every breakpoint (the mobile block
   below no longer needs to position:fixed the topbar or pad <main> to clear
   it). The page body itself never scrolls. */
#app-screen {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh; /* tracks mobile browser chrome show/hide */
  overflow: hidden;
}
.topbar {
  flex: 0 0 auto;      /* natural height, never shrink, never scroll away */
  position: sticky;    /* redundant given the flex column, but a cheap guard */
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 12px 24px;
  background: var(--surface);
  border-bottom: 3px solid var(--horizon);
  color: var(--text);
}
main {
  flex: 1 1 auto;
  min-height: 0;       /* let the flex child actually shrink so it can scroll */
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* Stops a swipe that runs past main's own top/bottom from chaining into
     a document-level bounce once it hits that boundary - see the html/body
     overscroll-behavior rule above for the full explanation. */
  overscroll-behavior-y: contain;
}
.topbar nav { display: flex; gap: 4px; flex: 1; }
.nav-btn {
  background: transparent;
  border: none;
  color: var(--slate);
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  font-size: 14px;
}
.nav-btn:hover { color: var(--text); }
.nav-btn.active { background: var(--horizon-soft); color: var(--navy); font-weight: 700; }

/* Bottom tab bar + its "More" sheet are mobile-only (native-app-style
   primary navigation - see the 640px block for the real layout).
   display:none outside that breakpoint rather than just visually hiding,
   so they never affect desktop layout/tab order at all. */
.mobile-tab-bar, .mobile-more-sheet { display: none; }

.user-chip { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--text); }
.role-badge {
  background: var(--horizon);
  color: white;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
}
#logout-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  font-size: 12px;
}

.notif-bell-wrap { position: relative; }
.notif-bell-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text);
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  line-height: 1;
}
/* color: var(--navy) on hover, not the inherited var(--text) - confirmed
   live as a real dark-mode bug: the hover background is the same fixed
   light --horizon-soft pastel as .stage-pill etc. (see its comment), but
   without an override here the icon kept var(--text)'s dark-mode LIGHT
   value, making it nearly invisible against the still-light hover
   background - the reported "white dot swallows the icon" bug. */
.notif-bell-btn:hover { background: var(--horizon-soft); color: var(--navy); }
.theme-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text);
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  line-height: 1;
  cursor: pointer;
}
.theme-toggle-btn:hover { background: var(--horizon-soft); color: var(--navy); }
.notif-badge {
  position: absolute;
  top: 0;
  right: 0;
  background: var(--danger);
  color: white;
  font-size: 10px;
  font-weight: 700;
  min-width: 15px;
  height: 15px;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 3px;
}
.notif-panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  width: 340px;
  max-width: calc(100vw - 24px);
  max-height: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  z-index: 50;
  display: flex;
  flex-direction: column;
}
.notif-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
  font-size: 13px;
  color: var(--text);
}
.notif-list { overflow-y: auto; }
.notif-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  padding: 10px 14px;
  cursor: pointer;
  font-size: 12px;
}
.notif-item:hover { background: var(--cream); }
.notif-item.unread { background: var(--horizon-soft); }
/* Fixed dark colors, not the plain var(--text)/var(--slate)/
   var(--slate-light) below - same fixed-bg/adaptive-text mismatch as
   .notif-bell-btn:hover above (see its comment): an unread row's
   background is the fixed light --horizon-soft above, so its text needs
   fixed dark colors too. Each value here is that token's own light-mode
   value (#4b5568 = --slate, #8992a3 = --slate-light), so light mode is
   unchanged. */
.notif-item.unread .notif-item-title { color: var(--navy); }
.notif-item.unread .notif-item-body { color: #4b5568; }
.notif-item.unread .notif-item-time { color: #8992a3; }
.notif-item-title { font-weight: 700; color: var(--text); font-size: 13px; margin-bottom: 2px; }
.notif-item-body { color: var(--slate); margin-bottom: 4px; }
.notif-item-time { color: var(--slate-light); font-size: 11px; }
.notif-empty { padding: 24px 14px; text-align: center; color: var(--slate-light); font-size: 13px; }
.link-btn {
  background: transparent;
  border: none;
  color: var(--horizon);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  padding: 0;
}
.link-btn:hover { text-decoration: underline; }

main { padding: var(--space-6) var(--space-8); }

.view-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-5);
}
.view-header h2 { margin: 0; font-size: var(--fs-20); letter-spacing: -0.01em; }
.view-actions { display: flex; gap: var(--space-2); }
.view-actions input { width: 240px; }

.view-actions .primary-btn { width: auto; margin: 0; }

/* My Leads no longer keeps its action buttons in .view-header (they moved
   into .filter-toolbar alongside Search/Filters - see index.html) so its
   header is just a title now; a tighter gap above the toolbar reads better
   than the 20px meant for a title+buttons row. */
#view-pipeline .view-header { margin-bottom: 12px; }

/* Mail keeps its own tighter title-to-toolbar gap too - the connected-email
   label already sits on the header's right side via .view-header's own
   space-between, so there's no reason for the 20px meant for other pages'
   plain title-only headers. */
#view-mail .view-header { margin-bottom: 10px; }

/* ---------- Shared collapsible filter toolbar ---------- */
/* Used by Leads, My Team/Employees, and anywhere else a search bar is
   paired with a handful of secondary filters: the search stays permanently
   visible, the rest collapse behind a "Filters" toggle so the page opens on
   the actual content, not a wall of controls - the count badge means a
   collapsed panel doesn't hide the fact that filters are still narrowing
   the view. One shared component so every list page in the app behaves and
   looks the same way, rather than each inventing its own filter row. */
.filter-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 12px; }
.filter-search { flex: 1; min-width: 200px; }
.filter-toggle {
  display: flex;
  align-items: center;
  gap: 7px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}
.filter-toggle:hover { border-color: var(--horizon); }
/* color: var(--navy) - same fixed-bg/adaptive-text mismatch as
   .notif-bell-btn:hover above (see its comment). */
.filter-toggle.active { border-color: var(--horizon); background: var(--horizon-soft); color: var(--navy); }

/* Icon + label pairing for a compact toolbar button (Filters/Import/New
   Merchant on the Leads toolbar, same pattern as .mail-icon-btn) - the icon
   stays put and only the label hides at narrow widths, so the button
   shrinks to a small square tap target instead of wrapping onto its own
   line. */
.icon-label-btn { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.icon-label-btn svg { flex-shrink: 0; }
/* Leads toolbar buttons live inside .filter-toolbar now (not .view-actions),
   so they need the same width:auto override .view-actions .primary-btn used
   to provide. */
.filter-toolbar .primary-btn, .filter-toolbar .secondary-btn { width: auto; margin: 0; }
.filter-count {
  background: var(--horizon);
  color: white;
  font-size: 11px;
  font-weight: 700;
  border-radius: var(--radius);
  min-width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
}

.filter-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
}
.filter-group { display: flex; flex-direction: column; gap: 5px; min-width: 160px; }
.filter-group label { margin: 0; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--slate-light); }
.filter-group select, .filter-group input[type="text"] { width: 100%; }
.filter-group-dates { min-width: 220px; }
/* Full-width break inside the same flex-wrap .filter-panel, grouping the
   Global Search & Advanced Filters upgrade's expanded field list
   (Classification/Location/People/Dates/Availability) into labeled
   sections instead of one undifferentiated wall of controls. */
.filter-group-heading {
  flex-basis: 100%;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--horizon);
  border-top: 1px solid var(--border);
  padding-top: 12px;
  margin-top: 2px;
}
.filter-panel > .filter-group-heading:first-child { border-top: none; padding-top: 0; margin-top: 0; }
.filter-date-inputs { display: flex; align-items: center; gap: 6px; }
.filter-date-inputs input[type="date"] { flex: 1; min-width: 0; }
.filter-date-sep { color: var(--slate-light); font-size: 12px; }
.filter-clear { margin-left: auto; align-self: center; }
/* A checkbox filter (e.g. "Include Archived") sits at the same baseline as
   the select groups beside it rather than the label-above/control-below
   layout those use - centering by itself over a fixed min-height keeps
   both patterns aligned along one row. */
.filter-checkbox-group { display: flex; align-items: center; min-height: 40px; }
.filter-checkbox-label { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: normal; color: var(--slate); margin: 0; white-space: nowrap; }
.filter-checkbox-label input[type="checkbox"] { width: auto; }

/* Multiple-Manager Assignment: the "Reports To" multi-select (Add Employee
   modal, Employee Profile's role/manager control) - a scrollable checkbox
   list rather than a native <select multiple>, which is unfamiliar/fiddly
   to operate (ctrl/cmd-click to multi-pick) compared to plain checkboxes. */
.manager-checkbox-list { display: flex; flex-direction: column; gap: 2px; max-height: 160px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 10px; margin-top: 4px; }

/* My Team hierarchy panel - "Reports To" (Manager/Team Leader/HOD) and
   "Team Members" (fellow Sales/Operations) sections, each a responsive grid
   of clickable person-cards (see hierarchyCardsHtml) opening that person's
   full Employee Profile (routes/users.js's canView allows this - see its
   comment). auto-fill/minmax means the grid re-flows from several columns
   on desktop down to a single column on mobile with no explicit breakpoint
   needed. */
.team-hierarchy-panel { display: flex; flex-direction: column; gap: 16px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 18px; margin-bottom: 16px; }
.hierarchy-section-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--slate-light); margin-bottom: 8px; }
.hierarchy-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
.hierarchy-card {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  transition: border-color 0.15s, background 0.15s;
}
/* Name text color pinned to var(--navy) on hover - same fixed-bg/adaptive-
   text mismatch as .notif-bell-btn:hover above (see its comment): the
   name has no background of its own, so it sits directly on this card's
   hover background. */
.hierarchy-card.clickable:hover { border-color: var(--navy-light); background: var(--horizon-soft); }
.hierarchy-card.clickable:hover .hierarchy-card-name { color: var(--navy); }
.hierarchy-card-avatar {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  color: var(--text);
  font-weight: 700;
  font-size: 12px;
}
.hierarchy-card-info { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.hierarchy-card-name { font-weight: 700; font-size: 13px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hierarchy-card-info .role-badge { align-self: flex-start; }

/* Reports' filter panel doesn't collapse (few enough controls that hiding
   them by default would cost more clicks than it saves) but shares the
   same card/label language - and separates "narrow the data" (the filter
   groups) from "do something with it" (Apply/Download/Export) into its own
   right-aligned cluster instead of one long row of mixed-purpose buttons. */
.report-actions { display: flex; gap: 8px; align-items: center; margin-left: auto; }
.report-actions .primary-btn, .report-actions button { width: auto; margin: 0; }

/* ---------- Card surfaces — unified resting treatment ----------
   Design-system pass: every panel/tile surface in the app shares one look —
   1px hairline + a barely-there resting shadow + the --radius-lg corner — so
   the whole CRM reads as one system on desktop, matching the "native card"
   elevation the mobile breakpoint already applies. Per-component padding,
   layout and hover/active states stay in each section's own rules below. */
.stat-tile,
.chart-card,
.activity-list,
.settings-section,
.settings-card,
.table-scroll,
.filter-panel,
.team-hierarchy-panel,
.kanban-col,
.calls-log-list {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}

/* ---------- Dashboard ---------- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--space-3); }
.stat-tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-4) var(--space-5);
  text-align: center;
}
.stat-tile.danger { border-color: var(--danger); }
.stat-tile .stat-value { font-size: var(--fs-24); font-weight: var(--fw-bold); color: var(--text); letter-spacing: -0.01em; }
.stat-tile.danger .stat-value { color: var(--danger); }
.stat-tile .stat-label { font-size: var(--fs-12); color: var(--slate-light); margin-top: var(--space-1); }
.dashboard-section-title { margin: var(--space-6) 0 var(--space-3); font-size: var(--fs-15); }

/* ---------- Settings accordion (Settings > every section below Company
   Profile) ---------- */
/* Every one of the 9 Settings sections used to render open, all at once,
   top to bottom - the single biggest source of the "too long/cluttered"
   complaint this replaces. Each section now renders (and, per
   app.js's settingsSectionLoaders, fetches its data) only the first time
   it's actually opened, and independently keeps whatever open/closed state
   the admin left it in across re-visits to the Settings tab - see
   wireSettingsAccordion/loadSettings in app.js. */
.settings-accordion { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.settings-section { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); overflow: hidden; }
.settings-section-header {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 18px;
  background: none;
  border: none;
  text-align: left;
  font-family: inherit;
  margin: 0;
}
.settings-section-header:hover { background: var(--cream); }
.settings-section-heading { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.settings-section-title { font-size: 15px; font-weight: 700; color: var(--text); }
.settings-section-subtitle { font-size: 12px; color: var(--slate-light); font-weight: 400; }
.settings-chevron { flex-shrink: 0; color: var(--slate-light); transition: transform 0.18s ease; }
.settings-section.open .settings-chevron { transform: rotate(180deg); }
.settings-section.open > .settings-section-header { border-bottom: 1px solid var(--border); background: var(--cream); }
.settings-section-body { display: none; padding: 20px 18px; }
.settings-section.open > .settings-section-body { display: block; }
@media (max-width: 640px) {
  .settings-section-header { padding: 14px; }
  .settings-section-body { padding: 16px 14px; }
}

/* Per-provider card inside API & Integration Management (and reused by
   Logo Management's own preview box) - a level of visual separation above
   a plain bordered box, since these sit one after another inside an
   already-bordered accordion panel and need to read as distinct cards, not
   blend into the panel's own edge. */
.settings-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 16px 18px;
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}
.settings-card:hover { box-shadow: 0 2px 10px rgba(22, 35, 61, 0.06); border-color: var(--slate-light); }
[data-theme="dark"] .settings-card:hover { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25); }

.clickable { cursor: pointer; }
.stat-tile.clickable { transition: box-shadow 0.15s, transform 0.15s; }
.stat-tile.clickable:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.bar-row.clickable:hover .bar-row-label { color: var(--text); font-weight: 600; }
.activity-row.clickable:hover { background: var(--cream); }
.legend-item.clickable:hover { text-decoration: underline; }

.section-link {
  font-size: 12px;
  font-weight: 600;
  color: var(--slate);
  margin-left: 10px;
  vertical-align: middle;
}
.section-link:hover { color: var(--text); text-decoration: underline; }

.charts-row { display: grid; grid-template-columns: 1.4fr 1fr; gap: 16px; align-items: start; }
.chart-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px;
}
.chart-card h4 { margin: 0 0 14px; font-size: 13px; color: var(--slate); text-transform: uppercase; letter-spacing: 0.04em; }
.chart-card h4.clickable:hover { color: var(--text); text-decoration: underline; }

.bar-row { display: grid; grid-template-columns: 110px 1fr 34px; align-items: center; gap: 10px; margin-bottom: 10px; }
.bar-row:last-child { margin-bottom: 0; }
.bar-row-label { font-size: 12px; color: var(--slate); text-align: right; }
.bar-track { background: var(--cream); border-radius: 7px; height: 14px; overflow: hidden; }
.bar-fill {
  height: 100%;
  border-radius: 0 4px 4px 0;
  transition: filter 0.15s;
  cursor: default;
}
.bar-fill:hover { filter: brightness(1.12); }
.bar-row-value { font-size: 12px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }

.stacked-bar { display: flex; gap: 2px; height: 18px; border-radius: 5px; overflow: hidden; background: var(--cream); }
.stacked-seg { height: 100%; transition: filter 0.15s; cursor: default; }
.stacked-seg:hover { filter: brightness(1.12); }
.stacked-seg:first-child { border-radius: 4px 0 0 4px; }
.stacked-seg:last-child { border-radius: 0 4px 4px 0; }
.chart-legend { display: flex; gap: 16px; margin-top: 12px; flex-wrap: wrap; }
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--slate); }
.legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }

/* ---------- Recent activity ---------- */
.activity-list { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.activity-row { display: flex; align-items: flex-start; gap: 10px; padding: 11px 16px; border-bottom: 1px solid var(--border); }
.activity-row:last-child { border-bottom: none; }
.activity-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 5px; flex-shrink: 0; }
.activity-main { flex: 1; min-width: 0; }
.activity-title { font-size: 13px; font-weight: 700; color: var(--text); }
.activity-meta { font-size: 12px; color: var(--slate-light); margin-top: 2px; }
.activity-meta strong { color: var(--slate); font-weight: 600; }
.activity-time { font-size: 11px; color: var(--slate-light); white-space: nowrap; margin-top: 2px; }

@media (max-width: 900px) {
  .charts-row { grid-template-columns: 1fr; }
}

/* ---------- Drill-down tables ---------- */
.table-scroll {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.data-table {
  width: 100%;
  min-width: 600px;
  border-collapse: collapse;
  font-size: var(--fs-13);
}
.data-table th {
  text-align: left;
  font-size: var(--fs-11);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--slate-light);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-strong);
  background: var(--surface-2);
}
.data-table td { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); color: var(--text); }
.data-table tr:last-child td { border-bottom: none; }
.data-table tr.data-row:hover { background: var(--cream); }
.data-table tr.overdue-row td { color: var(--danger); }

/* Mobile stand-in for a .data-table (Merchant Details modal's Contacts,
   Submissions, and Activity Log sections only - see the @media(max-width:
   640px) toggle below). Hidden on desktop; a .data-table's fixed min-width
   forcing horizontal scroll inside the fixed-position merchant modal was
   the real cause of that page "shifting left/right" on mobile, not
   something a hidden scrollbar should paper over - this replaces the
   layout itself with a stacked card list that has no minimum width at
   all, so there is nothing left to overflow. */
.mobile-card-list { display: none; flex-direction: column; gap: 10px; }
.mobile-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
}
.mobile-card-title { font-weight: 700; color: var(--text); font-size: 14px; }
.mobile-card-row { font-size: 13px; color: var(--slate); margin-top: 4px; line-height: 1.4; }
.mobile-card-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.mobile-card-actions button { width: auto; margin: 0; font-size: 12.5px; padding: 6px 12px; }

/* Merchant/Lead modal's Contacts section - was taking up far more vertical
   space than a "who to call" list needs: an always-fully-expanded 11-field
   Add Contact form, and table rows that stacked role/department onto their
   own separate lines. Scoped to this section only (.contacts-table etc.),
   not the shared .data-table/.mobile-card rules other pages still use at
   their original size. */
.contacts-section-heading { margin: 20px 0 8px; }
.contacts-table-scroll, .contacts-card-list { margin-bottom: 8px; }
.contacts-table td, .contacts-table th { padding: 7px 12px; font-size: 12.5px; }
.contact-name { font-weight: 600; }
.contact-type-pill { margin-left: 6px; font-size: 10.5px; padding: 2px 8px; }
.contact-subtext { color: var(--slate-light); font-size: 12px; }
.contact-row-actions { white-space: nowrap; }
.contact-row-actions button { width: auto; margin: 0 6px 0 0; padding: 4px 10px; font-size: 12px; }
.contact-row-actions button:last-child { margin-right: 0; }
.contact-mobile-card { padding: 10px 12px; }
.contacts-add-btn { width: auto; padding: 6px 14px; font-size: 13px; }
.contact-form-wrap { margin-top: 10px; padding: 12px 14px; background: var(--cream); border-radius: var(--radius); }
.contact-form-wrap .form-grid { gap: 4px 14px; }
.contact-form-wrap .form-grid > div { margin-bottom: 8px; }
/* The <details> disclosure holding the rarely-used fields (Department,
   Contact Role, additional numbers/emails, LinkedIn, Notes) - collapsed by
   default, opened automatically when editing a contact that already has
   one of those fields filled in (see contactFormFieldsHtml). */
.contact-form-more summary {
  cursor: pointer; font-size: 12.5px; color: var(--slate-light); font-weight: 600;
  padding: 4px 0; list-style: none;
}
.contact-form-more summary::-webkit-details-marker { display: none; }
.contact-form-more summary::before { content: '▸ '; }
.contact-form-more[open] summary::before { content: '▾ '; }
.contact-form-more .form-grid { margin-top: 8px; }

/* Merchant/Lead modal's Notes section - a running log of dated, authored
   notes (see renderNotesSectionHtml/wireNotesSection in app.js), styled to
   match the Contacts section's own compact conventions just above. */
.merchant-notes-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.merchant-note-row { background: var(--cream); border-radius: var(--radius); padding: 10px 12px; }
.merchant-note-text { font-size: 13px; color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere; }
.merchant-note-meta { font-size: 11.5px; color: var(--slate-light); margin-top: 4px; }
.merchant-note-form { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.merchant-note-form textarea { width: 100%; resize: vertical; }

/* ---------- Kanban ---------- */
.kanban {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}
.kanban-col {
  background: var(--surface);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  min-height: 200px;
}
.kanban-col-header {
  padding: 12px 14px;
  border-bottom: 2px solid var(--horizon);
  font-weight: 700;
  font-size: 13px;
  display: flex;
  justify-content: space-between;
}
.kanban-col-header .count {
  background: var(--cream);
  border-radius: var(--radius-pill);
  padding: 1px 8px;
  font-size: 12px;
  color: var(--slate);
}
.kanban-col.lost .kanban-col-header { border-color: var(--danger); }
.kanban-col.won .kanban-col-header { border-color: var(--success); }

.merchant-card {
  margin: 10px;
  padding: 12px;
  background: var(--cream);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: box-shadow 0.15s;
}
.merchant-card:hover { box-shadow: var(--shadow-md); }
.merchant-card { position: relative; }
.merchant-card .bulk-select-checkbox { display: none; position: absolute; left: 10px; top: 12px; width: 16px; height: 16px; cursor: pointer; }
.kanban.bulk-select-mode .merchant-card { padding-left: 34px; }
.kanban.bulk-select-mode .merchant-card .bulk-select-checkbox { display: block; }
.merchant-card.bulk-selected { border-color: var(--navy-light); background: var(--cream-dark, #f1efe8); }
.merchant-card .m-name-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 6px; margin-bottom: 2px; }
.merchant-card .m-name { font-weight: 700; font-size: 14px; }
.merchant-card .m-call-btn { flex-shrink: 0; }
.merchant-card .m-meta { font-size: 12px; color: var(--slate-light); }

.bulk-email-bar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 14px;
  margin-bottom: 12px;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 13px;
}
#bulk-email-count { font-weight: 700; }
.bulk-email-chip-list { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; max-height: 120px; overflow-y: auto; }
.bulk-email-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--cream); border: 1px solid var(--border); border-radius: 999px;
  padding: 4px 10px; font-size: 12px;
}
.bulk-email-chip button { border: none; background: none; cursor: pointer; color: var(--slate-light); font-size: 14px; line-height: 1; padding: 0; }
.bulk-email-placeholder-row { display: flex; flex-wrap: wrap; gap: 12px; margin: 4px 0 8px; }
.bulk-email-result-row { display: flex; justify-content: space-between; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.bulk-email-result-row:last-child { border-bottom: none; }
.bulk-email-result-status.sent { color: var(--success); font-weight: 700; }
.bulk-email-result-status.failed { color: var(--danger); font-weight: 700; }
/* Why a card matched an active global search term (routes/merchants.js's
   matched_on) - only shown when the match wasn't on the merchant name
   itself, which is already obvious from the card's own title. */
.m-match-reason { color: var(--horizon); font-weight: 600; }
.priority-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-left: 4px;
  vertical-align: middle;
}

.recently-viewed-strip {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-bottom: 4px;
  margin-bottom: 16px;
}
.recently-viewed-card {
  flex: 0 0 auto;
  min-width: 160px;
  padding: 10px 12px;
  background: var(--cream);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: box-shadow 0.15s;
}
.recently-viewed-card:hover { box-shadow: var(--shadow-md); }
.recently-viewed-card .m-name { font-weight: 700; font-size: 13px; margin-bottom: 2px; }
.recently-viewed-card .m-meta { font-size: 11px; color: var(--slate-light); }

/* ---------- Mail ---------- */
/* Shared 24x24-viewBox outline icons (see mailIcon() in app.js) - sized per
   usage via the width/height passed to mailIcon(), this just keeps every
   one vertically centered against its sibling text/buttons. */
.mail-icon { display: inline-block; vertical-align: middle; flex-shrink: 0; }

.mail-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.mail-search-wrap { position: relative; flex: 1; min-width: 200px; display: flex; align-items: center; }
.mail-search-wrap .mail-search-icon { position: absolute; left: 10px; color: var(--slate-light); pointer-events: none; }
.mail-search-wrap input[type="search"] { width: 100%; padding-left: 34px; }
.mail-read-filter { display: flex; gap: 4px; flex-shrink: 0; }
.mail-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--slate);
  cursor: pointer;
}
.mail-filter-chip:hover { background: var(--cream); }
.mail-filter-chip.active { background: var(--horizon-soft); border-color: var(--horizon-soft); color: var(--navy); }
/* Compose/Disconnect - icon + label, the label collapses to icon-only on
   mobile (see the 640px block) to keep the toolbar to two compact rows. */
.mail-icon-btn { display: inline-flex; align-items: center; gap: 6px; }
.mail-connected-label { font-size: 12px; font-weight: 400; color: var(--slate-light); margin-left: 10px; overflow-wrap: anywhere; }

/* Refresh (see refreshMailMessages/pollMailIfOpen in app.js) - a plain
   round icon button, same family as the notification bell in the topbar,
   so it reads as a small utility action rather than competing with the
   accent-colored Compose button for attention. Sized up slightly (34px vs
   the 28px .mail-icon-btn-sm default) since it sits in the main toolbar
   next to real touch targets, not nested inside a message card. */
#mail-refresh-btn { width: 34px; height: 34px; flex-shrink: 0; }
#mail-refresh-btn:disabled { cursor: default; opacity: 0.6; }
.mail-refresh-icon { transition: transform 0.15s ease; }
#mail-refresh-btn:hover:not(:disabled) .mail-refresh-icon { transform: rotate(45deg); }
@keyframes mail-refresh-spin { to { transform: rotate(360deg); } }
#mail-refresh-btn.mail-refreshing .mail-refresh-icon { animation: mail-refresh-spin 0.8s linear infinite; }
.mail-refresh-error { width: 100%; margin: -6px 0 8px; }

/* Desktop Compose - deliberately its own pill-shaped, accent-colored
   button rather than the generic navy .primary-btn every other toolbar
   action uses, so it reads as THE one primary "create" action in Mail (the
   same visual role Gmail's own Compose button plays) instead of blending
   in as just another button. Uses --horizon (the brand accent green) since
   that's already the established color for this exact action on mobile
   (.mail-compose-fab below) and for other "positive/create" actions
   elsewhere (ai-action-confirm-btn, starred) - keeps desktop and mobile
   Compose visually the same color family, just a different shape. */
.mail-compose-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--horizon);
  color: white;
  border: none;
  padding: 11px 22px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 700;
  box-shadow: 0 2px 10px rgba(79, 122, 54, 0.35);
  transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}
.mail-compose-btn:hover { background: var(--horizon-dark); box-shadow: 0 4px 14px rgba(79, 122, 54, 0.45); transform: translateY(-1px); }
.mail-compose-btn:active { transform: translateY(0); box-shadow: 0 2px 8px rgba(79, 122, 54, 0.35); }

/* Gmail-style mobile toolbar pieces (hamburger, the drawer's own Disconnect
   entry) - hidden by default since desktop already has a persistent folder
   column (no hamburger needed) and its Disconnect button stays in the
   toolbar. The 640px block below turns each one on. */
.mail-mobile-menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  background: none;
  border: none;
  border-radius: 50%;
  color: var(--text);
  cursor: pointer;
}
.mail-mobile-menu-btn:hover { background: var(--cream); }
.mail-folder-divider { display: none; }
/* Doubled-up selector (not just .mail-folder-disconnect) so this "off by
   default" rule outweighs .mail-folder-item's own display:flex below on
   specificity rather than depending on which happens to come later in the
   file - this button also carries the .mail-folder-item class for its
   layout/hover styling. */
.mail-folder-item.mail-folder-disconnect {
  display: none;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  font-family: inherit;
  color: var(--danger);
}
.mail-folder-item.mail-folder-disconnect:hover { background: var(--cream); }
.mail-layout {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 14px;
  height: calc(100vh - 190px);
  min-height: 420px;
}
.mail-folders, .mail-messages, .mail-detail {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow-y: auto;
  overscroll-behavior-y: contain; /* see the html/body overscroll-behavior rule above */
}
.mail-folders { padding: 8px; }
/* Gmail-style two-step nav on desktop too, not just mobile: Folders stays
   its own column, but the list and the detail pane share the SAME second
   column/row (rather than each getting their own fixed-width column) so
   that whichever one is display:none simply vacates the space instead of
   leaving a separate empty column beside the other. setMailMobilePane sets
   display:none/'' on these two to keep them mutually exclusive at every
   width - list-only, then Folders+full-width-preview, never all three at
   once. Reset back to grid-column/row:auto under the mobile single-column
   breakpoint below, since a stray explicit column-2 placement inside a
   1-column grid would otherwise create an unwanted implicit second track. */
.mail-messages, .mail-detail { grid-column: 2; grid-row: 1; }
/* Grid-hidden outside the mobile drawer breakpoint - `display: none` pulls
   it out of .mail-layout's grid flow entirely (a bare, unstyled child div
   would otherwise consume an implicit 4th grid row) rather than just being
   invisible in place. Mobile's @media block turns it into the fixed-
   position full-screen overlay it actually is. */
.mail-drawer-backdrop { display: none; }
.mail-folder-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  cursor: pointer;
  color: var(--slate);
}
.mail-folder-item .mail-folder-icon { color: var(--slate-light); }
.mail-folder-item:hover .mail-folder-icon { color: var(--text); }
.mail-folder-item:hover { background: var(--cream); }
/* Icon uses var(--navy), not var(--text) like the :hover icon above -
   confirmed live as a real dark-mode bug: this row's own text right
   below already correctly uses var(--navy) (fixed) against this same
   fixed light --horizon-soft background (same reasoning as .stage-pill
   elsewhere), but the icon had its own separate, more specific rule
   still pointing at var(--text) (theme-adaptive), which went light-on-
   light-green and nearly disappeared in dark mode - exactly the
   "folder icons blending into the background" bug reported live. */
.mail-folder-item.active .mail-folder-icon { color: var(--navy); }
.mail-folder-item.active { background: var(--horizon-soft); color: var(--navy); font-weight: 700; }

/* position:relative + overflow:hidden turns the row into a clipping window
   for the swipe backgrounds/content below - .mail-row-content is the part
   that actually slides (and carries the row's real background/padding/
   border, moved here from .mail-message-row itself) so the archive/delete
   backgrounds underneath can only ever be seen through the gap a swipe
   opens up, never spill outside the row's own bounds. */
.mail-message-row { position: relative; overflow: hidden; border-bottom: 1px solid var(--border); cursor: pointer; }
.mail-message-row.focused { outline: 2px solid var(--navy-light); outline-offset: -2px; }
/* Vertical padding kept as tight as still reads as "padding" rather than
   "the text is touching the row edge" - horizontal stays untouched (14px),
   only the vertical dimension shrinks, per repeated feedback that rows
   were still too tall. Explicit line-height on the two text rows below
   (rather than leaving it to the font stack's own "normal", which varies
   enough between -apple-system/Segoe UI/Roboto to add a few unpredictable
   px) locks in the same tight spacing everywhere this renders. */
.mail-row-content { position: relative; z-index: 1; background: var(--surface); padding: 5px 14px; }
.mail-message-row:hover .mail-row-content { background: var(--cream); }
.mail-message-row.unread .mail-row-content { background: var(--unread-bg); }
.mail-message-row.unread .mail-row-sender { font-weight: 700; }
.mail-row-top { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; line-height: 1.2; color: var(--slate-light); }
.mail-row-sender { color: var(--text); overflow-wrap: anywhere; }
/* De-emphasized so it reads as secondary to the sender name, not competing
   with it - same treatment as .mail-meta-from-email in the reading view,
   just scoped separately so the two can be tuned independently. */
.mail-row-sender-email { font-weight: 400; color: var(--slate-light); }
/* "To:" lead-in shown in Sent/Drafts/Outbox rows, where the row surfaces the
   recipient instead of the sender. */
.mail-row-lead-prefix { font-weight: 400; color: var(--slate-light); }
.mail-message-row.unread .mail-row-lead-prefix { font-weight: 400; }
.mail-thread-badge { display: inline-block; background: var(--horizon-soft); color: var(--navy); border-radius: var(--radius); padding: 1px 7px; font-size: 11px; font-weight: 700; }
/* Groups date + quick actions as one right-hand unit (rather than two
   separate flex siblings of .mail-row-top) - desktop keeps them side by
   side with actions overlaying the date on hover (position:relative here
   is that overlay's containing block, moved off .mail-row-top since it's
   no longer needed there); mobile stacks them instead (see the 640px
   block) so the timestamp sits top-right with the icons directly under it. */
.mail-row-side { position: relative; display: flex; align-items: center; flex-shrink: 0; }
.mail-row-date { flex-shrink: 0; white-space: nowrap; }
.mail-row-subject { font-size: 13px; line-height: 1.2; margin-top: 0; color: var(--slate); overflow-wrap: anywhere; }

/* Swipe-to-archive/delete (touch only, see wireMailRowSwipeActions) - full-
   bleed color revealed proportionally as .mail-row-content is dragged
   aside, one on each side since the direction isn't known until the user
   starts dragging. */
.mail-row-swipe-bg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  font-weight: 700;
  font-size: 13px;
  color: white;
  opacity: 0;
}
.mail-row-swipe-bg-archive { background: var(--horizon); justify-content: flex-start; padding-left: 20px; }
.mail-row-swipe-bg-delete { background: var(--danger); justify-content: flex-end; padding-right: 20px; }

/* Quick-action icons (star/archive/delete) overlay the date on hover
   instead of taking their own permanent row - keeps the list compact while
   still reachable with one click, Gmail-style. Positioned absolutely within
   .mail-row-side (not a flex sibling) so they cost zero layout space when
   hidden - an opacity-only toggle would still reserve their box's height on
   every row even when invisible. */
.mail-row-actions {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--cream);
  padding-left: 6px;
  opacity: 0;
  pointer-events: none;
}
.mail-message-row:hover .mail-row-date,
.mail-message-row:focus-within .mail-row-date { visibility: hidden; }
.mail-message-row:hover .mail-row-actions,
.mail-message-row:focus-within .mail-row-actions { opacity: 1; pointer-events: auto; }
.mail-row-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-radius: 4px;
  width: 24px;
  height: 24px;
  line-height: 1;
  cursor: pointer;
  color: var(--slate);
}
.mail-row-action:hover { background: var(--horizon-soft); color: var(--navy); }
.mail-row-action.starred { color: var(--star); }

.mail-load-more {
  display: block;
  width: 100%;
  background: var(--surface);
  border: none;
  border-top: 1px solid var(--border);
  padding: 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
}
.mail-load-more:hover { background: var(--cream); }
.mail-load-more:disabled { color: var(--slate-light); cursor: default; }

.mail-empty-hint { color: var(--slate-light); font-size: 13px; }

.mail-detail { padding: 20px; }

/* Thread header - just the subject + how many messages, shared across every
   card below rather than repeated per-card. Each card gets its own
   sender/date/recipients (see .mail-thread-card below), which is what the
   old single-message .mail-detail-header used to carry. */
.mail-thread-header { display: flex; align-items: baseline; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; }
.mail-thread-count-label { font-size: 12px; color: var(--slate-light); }
.mail-thread-header .mail-detail-subject { margin: 0; }
.mail-detail-subject {
  margin: 0 0 14px;
  font-size: 19px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.mail-detail-meta { display: flex; gap: 12px; align-items: flex-start; }
/* color: var(--navy), not var(--text) - same fixed-light-background/
   theme-adaptive-text mismatch as .stage-pill below (see its own comment) -
   confirmed live: sender initials were unreadable in dark mode. */
.mail-avatar {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--horizon-soft);
  color: var(--navy);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
}
/* min-width: 0 is load-bearing here - without it a flex child won't shrink
   below its content's natural width, and a long name/email just pushes the
   date off the edge instead of wrapping. */
.mail-meta-main { flex: 1; min-width: 0; }
/* Sender + "Latest" badge only now - the date used to share this row
   (space-between-ed to the far edge), but now sits in the header's own
   outer row instead (see .mail-thread-card-header/.mail-meta-date below),
   pinned top-right regardless of how long the sender name/email get. */
.mail-meta-top { display: flex; align-items: baseline; gap: 10px 16px; flex-wrap: wrap; }
.mail-meta-sender { font-size: 14px; font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.mail-meta-email { font-weight: 400; color: var(--slate-light); }
/* The sender's own address, not a recipient's - same visual treatment as
   .mail-meta-email but kept as its own class so the mobile rule below that
   hides recipient addresses (to keep To/Cc/Bcc compact) doesn't also hide
   who the message is actually from. */
.mail-meta-from-email { font-weight: 400; color: var(--slate-light); overflow-wrap: anywhere; }
.mail-meta-date { font-size: 12px; color: var(--slate-light); white-space: nowrap; flex-shrink: 0; }
/* Gmail-style compact reply-icon row, top-right of each message header
   next to the date (see mailThreadCardHtml in app.js for the full
   reasoning - a second, additional access point alongside the existing
   bottom .mail-detail-actions buttons and .mail-quick-actions row, neither
   of which moved). Borderless/small, distinct from the bordered 34px
   .mail-quick-action-btn used in that other row, matching the reference
   screenshot's minimal icon-only treatment. */
.mail-meta-date-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.mail-quick-reply-icons { display: flex; align-items: center; gap: 2px; }
.mail-icon-btn-sm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  background: none;
  border: none;
  border-radius: 50%;
  color: var(--slate);
  cursor: pointer;
}
.mail-icon-btn-sm:hover { background: var(--cream); color: var(--text); }
.mail-more-menu-wrap { position: relative; }
.mail-more-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 20;
  min-width: 170px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 6px;
  display: flex;
  flex-direction: column;
}
.mail-more-menu.hidden { display: none; }
.mail-more-menu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--slate);
  text-align: left;
  cursor: pointer;
}
.mail-more-menu button:hover { background: var(--cream); color: var(--text); }
.mail-more-menu-divider { border: none; border-top: 1px solid var(--border); margin: 6px 2px; }
.mail-toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--navy);
  color: white;
  padding: 10px 18px;
  border-radius: var(--radius);
  font-size: 13px;
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.25);
  z-index: 1000;
}

/* Auto-update banner (see checkForAppUpdate in app.js) - persistent (no
   auto-dismiss timer, unlike .mail-toast above) since the whole point is
   that this tab needs a reload before its own fixes actually take effect;
   letting it silently disappear would defeat that. z-index above even the
   call widgets (1001) - a stale tab notice belongs above everything,
   including mid-call, since it explains why something else might already
   be broken. Bottom-center, same corner-free placement as .mail-toast, so
   it never collides with either calling widget's own bottom-corner spot. */
.app-update-banner {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1002;
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: calc(100vw - 32px);
  background: var(--navy);
  color: white;
  padding: 12px 16px 12px 18px;
  border-radius: var(--radius);
  font-size: 13px;
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.3);
}
.app-update-banner button {
  flex-shrink: 0;
  background: var(--horizon);
  color: white;
  border: none;
  padding: 7px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.app-update-banner button:hover { background: var(--horizon-dark); }
@media (max-width: 640px) {
  .app-update-banner { flex-direction: column; align-items: stretch; text-align: center; bottom: calc(56px + 16px + env(safe-area-inset-bottom, 0)); }
  .app-update-banner button { width: 100%; }
}
.mail-meta-recipients { margin-top: 6px; display: flex; flex-direction: column; gap: 3px; }
.mail-meta-row { display: flex; gap: 8px; font-size: 13px; }
.mail-meta-label { flex-shrink: 0; min-width: 30px; color: var(--slate-light); font-weight: 600; }
.mail-meta-value { color: var(--slate); overflow-wrap: anywhere; min-width: 0; }
/* Long To/Cc/Bcc lists collapse behind "+N more" instead of running on
   indefinitely - .mail-meta-rest starts hidden and the button toggles it,
   Gmail-style, so a 20-recipient Bcc doesn't dominate the reading pane. */
.mail-meta-rest.hidden { display: none; }
.mail-meta-more-btn {
  background: none;
  border: none;
  color: var(--horizon);
  font-weight: 700;
  font-size: 12px;
  cursor: pointer;
  padding: 0;
  margin-left: 4px;
}
.mail-meta-more-btn:hover { text-decoration: underline; }

/* Conversation view accordion - every message in the thread is its own
   card, oldest to newest top to bottom (natural reading order), the newest
   (and any unread) auto-expanded, everything else a one-line collapsed
   summary. Click any card - collapsed or expanded - to toggle it in place;
   nothing here ever replaces the whole pane or navigates away, which is
   what made the old "separate clickable summary list" version confusing to
   follow. */
.mail-thread-accordion { display: flex; flex-direction: column; gap: 10px; }
.mail-thread-card { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); overflow: hidden; }

.mail-thread-card.collapsed {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
}
.mail-thread-card.collapsed:hover { background: var(--cream); border-color: var(--navy-light); }
/* Unread messages inside a thread need to stand out exactly like an unread
   row in the main list does (bold sender + a tinted background), so a
   reply you haven't read yet is never mistaken for one you have. */
.mail-thread-card.collapsed.unread { background: var(--unread-bg); }
.mail-thread-card.collapsed.unread:hover { background: var(--unread-bg-hover); }
.mail-thread-card.collapsed.unread .mail-thread-card-sender { font-weight: 700; color: var(--text); }
.mail-avatar-sm { width: 28px; height: 28px; font-size: 11px; }
.mail-thread-card-sender { flex-shrink: 0; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; color: var(--slate); }
.mail-thread-card-snippet { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; color: var(--slate-light); }
.mail-thread-card-date { flex-shrink: 0; font-size: 12px; color: var(--slate-light); white-space: nowrap; }

.mail-thread-card.expanded { padding: 16px; }
.mail-thread-card.expanded.unread { border-left: 3px solid var(--navy-light); }
.mail-thread-card-header { display: flex; gap: 12px; align-items: flex-start; cursor: pointer; padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--border); }
.mail-thread-card.expanded .mail-body { padding-left: 52px; } /* aligns with the header's sender text, past the avatar */
.mail-thread-card.expanded .mail-attachments:not(:empty) { margin-left: 52px; }
.mail-thread-card.expanded .mail-quick-actions { margin-left: 52px; margin-bottom: 16px; }
.mail-thread-card.expanded .mail-detail-actions { margin-left: 52px; margin-top: 16px; }
/* Compact icon row directly under the header (Star/Mark Unread/Archive/
   Delete/Label) - separate from .mail-detail-actions (Reply/Reply
   All/Forward), which stays at the bottom after the message body since
   deciding how to respond is naturally the last step of reading, not the
   first. */
.mail-quick-actions { display: flex; gap: 6px; }
.mail-quick-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  width: 34px;
  height: 34px;
  color: var(--slate);
  cursor: pointer;
}
.mail-quick-action-btn:hover { background: var(--cream); color: var(--text); }
.mail-quick-action-btn.starred { color: var(--star); border-color: var(--star); }
/* Every thread's newest message gets this badge regardless of read state,
   so "which one is latest" never depends on spotting the last card in the
   stack - it's labeled outright. */
.mail-thread-latest-badge {
  display: inline-block;
  background: var(--horizon);
  color: white;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: var(--radius);
  padding: 2px 8px;
}

/* Real email HTML bodies often carry fixed-width tables/images from the
   original sender - contain that overflow to the body itself so it can't
   force the whole page to scroll sideways on a narrow screen.

   background/color use theme tokens (var(--surface)/var(--text)) so the
   preview itself genuinely switches with the CRM's theme, not just its
   chrome. sanitizeMessageHtml (backend/lib/zohoMail.js) deliberately
   keeps a sender's own inline style/color/bgcolor (that's how real email
   formatting works) - left alone, that authored HTML would still fight
   this container's background in dark mode (e.g. a sender's own
   background:#fff div staying a jarring white island, or color:#333
   chosen for a white page going unreadable on our now-dark surface). The
   applyMailBodyThemeFallbacks() pass in app.js (run right after this
   HTML is inserted, on every .mail-body) closes that gap per element:
   anything that sets ONLY color or ONLY background gets the matching
   .mail-content-fallback-* class below to fill in the missing half from
   our theme; anything setting BOTH (a colored banner/button the sender
   already built its own contrast for) is left completely untouched, so
   original formatting survives. */
.mail-body { font-size: 14px; line-height: 1.6; word-wrap: break-word; max-width: 100%; overflow-x: auto; background: var(--surface); color: var(--text); padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius); }
.mail-body a { color: var(--horizon); }
/* --horizon itself (#4f7a36) only clears ~3.2:1 against the dark-mode
   --surface background - readable-ish but under the 4.5:1 body-text
   threshold. A brighter tint of the same green clears ~9:1 instead. */
[data-theme="dark"] .mail-body a { color: #8fd19e; }
/* Both fallbacks are dark-mode-only - light mode never had a contrast
   problem to begin with (senders already write for a white page), so
   leaving these unscoped would only cost light mode its original color
   choices (e.g. a sender's own blue link) for no reason. Sits on our
   theme background (had no background of its own), so its text follows
   our theme's light text in dark mode. */
[data-theme="dark"] .mail-content-fallback-theme-text { color: var(--text) !important; }
/* Kept its own (near-certainly light, per real-world email HTML) inline
   background but had no text color of its own - fixed dark text in dark
   mode, not a theme token, since that background itself never changes
   with theme. */
[data-theme="dark"] .mail-content-fallback-dark-text { color: #1a1a1a !important; }
[data-theme="dark"] .mail-body img { opacity: 0.92; }
.mail-body img { max-width: 100%; height: auto; }
.mail-body table { max-width: 100%; }
.mail-detail-actions { display: flex; gap: 8px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border); flex-wrap: wrap; }
/* color: var(--text) is required, not cosmetic - a <button> is a form
   control, so unlike a div/span it does NOT inherit the page's text
   color by default; left unset, every browser falls back to its own
   native button text color (effectively fixed black) regardless of
   theme. That's the confirmed live bug: Reply/Reply All/Forward (and
   their icons, which use stroke="currentColor" - see mailIcon() in
   app.js) stayed black in both Light and Dark Mode until this was set
   explicitly. */
.mail-detail-actions button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 6px 14px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 13px;
  color: var(--text);
}
.mail-detail-actions button:hover { background: var(--cream); color: var(--text); }
.mail-detail-actions button:focus-visible { outline: 2px solid var(--horizon); outline-offset: 1px; }
.mail-detail-actions button:disabled { color: var(--slate-light); border-color: var(--border); cursor: default; opacity: 0.7; }
.mail-detail-actions button.starred { color: var(--star); border-color: var(--star); }

/* ---------- Mail Phase 2: attachments, labels, rich composer ---------- */
.mail-attachments { margin-top: 16px; }
.mail-attachment-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--slate-light); margin-bottom: 6px; }
.mail-attachment-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 6px 6px 6px 12px;
  margin: 0 8px 8px 0;
  font-size: 13px;
  cursor: pointer;
}
.mail-attachment-chip:hover { border-color: var(--navy-light); }
.mail-attachment-name { color: var(--text); max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-attachment-size { color: var(--slate-light); font-size: 12px; }
.mail-attachment-download {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 50%;
  width: 26px;
  height: 26px;
  cursor: pointer;
  color: var(--slate);
}
.mail-attachment-download:hover { background: var(--horizon-soft); color: var(--navy); }
.mail-attachment-icon { display: inline-flex; color: var(--slate-light); }

/* Lightweight overlay reused for both the image lightbox (dark, photo-
   viewing convention) and the label picker (light override below) - avoids
   a full #modal for either, since neither has other page state to
   coordinate with. */
.mail-lightbox-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 15, 30, 0.85);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
  padding: 20px;
}
.mail-lightbox-box { max-width: min(90vw, 800px); max-height: 90vh; display: flex; flex-direction: column; gap: 12px; }
.mail-lightbox-box img { max-width: 100%; max-height: 75vh; object-fit: contain; border-radius: var(--radius-sm); }
.mail-lightbox-actions { display: flex; align-items: center; gap: 10px; }
.mail-label-picker-box {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 20px;
  width: 380px;
  color: var(--slate);
}
.mail-label-picker-box h4 { margin: 0 0 10px; color: var(--text); }
.mail-label-picker-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; max-height: 240px; overflow-y: auto; }
.mail-label-picker-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.mail-label-picker-row button { font-size: 12px; padding: 4px 10px; }

/* .mail-label-dot and .mail-label-create-form are shared with the
   per-message label picker above (openMailLabelPicker in app.js) - the
   toolbar's own Labels/Filters dropdown chrome they used to share
   (.mail-labels-dropdown/.mail-labels-panel/.mail-label-list/.mail-label-chip)
   was removed along with those two toolbar buttons. */
.mail-label-dot { flex-shrink: 0; width: 10px; height: 10px; border-radius: 50%; }
.mail-label-create-form { display: flex; gap: 6px; }
.mail-label-create-form input { flex: 1; font-size: 13px; }
.mail-label-create-form button { flex-shrink: 0; font-size: 12px; padding: 6px 10px; }

/* Recipient chip fields (To/Cc/Bcc) - a div styled like the plain text
   input it replaced, holding rendered chips plus a borderless inner input
   for typing the next address. The inner input needs to override the
   global `input { width:100%; border; padding }` rule (line ~68) since it
   sits inside its own already-bordered wrapper, not standing alone. */
.recipient-field {
  position: relative; /* anchors .recipient-suggestions below - see its own comment */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-height: 40px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 5px 8px;
  cursor: text;
}
.recipient-field:focus-within { border-color: var(--horizon); }
.recipient-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.recipient-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 3px 4px 3px 10px;
  font-size: 13px;
  color: var(--text);
}
.recipient-chip-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }
.recipient-chip-remove {
  background: transparent;
  border: none;
  border-radius: 50%;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  line-height: 1;
  font-size: 13px;
  color: var(--slate);
}
.recipient-chip-remove:hover { background: var(--surface); color: var(--text); }
.recipient-field .recipient-typing {
  flex: 1 1 120px;
  min-width: 120px;
  width: auto;
  border: none;
  padding: 4px 2px;
  font-size: 14px;
  background: transparent;
}
.recipient-field .recipient-typing:focus { outline: none; border: none; }
.recipient-chip-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--navy);
  color: white;
  font-size: 9px;
  font-weight: 700;
  flex-shrink: 0;
}

/* Gmail-style recipient autocomplete dropdown (see wireRecipientSuggestions
   in app.js) - floats below the WHOLE chip field (anchored off
   .recipient-field's own position:relative above), not just the caret, so
   it never shifts as chips wrap across lines. z-index only needs to clear
   this field's own siblings within the compose form, not the app-wide
   overlay stack (.mail-more-menu's own 20 is the closest precedent here -
   same "one dropdown above ordinary page content" tier, not a widget-level
   concern like the call/AI widgets' 1000+). */
.recipient-suggestions {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  max-height: 220px;
  overflow-y: auto;
}
.recipient-suggestions.hidden { display: none; }
.recipient-suggestion {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  padding: 8px 10px;
  cursor: pointer;
  text-align: left;
  font-size: 13px;
  color: var(--text);
}
.recipient-suggestion:last-child { border-bottom: none; }
/* .active (not just :hover) is what keyboard Up/Down highlights - a plain
   :hover-only treatment would leave arrow-key navigation with no visible
   indication of which entry Enter is about to pick. */
.recipient-suggestion:hover, .recipient-suggestion.active { background: var(--cream); }
.recipient-suggestion-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Gmail-style compose recipient block (see index.html's #compose-form) -
   avatar + To row with an inline Cc/Bcc toggle, replacing the old stacked
   <label>To</label><div class="recipient-field"> pairs. .recipient-field
   itself is unchanged (still used as-is elsewhere in this block); only its
   chrome is overridden here to read as an underlined text row instead of a
   boxed input, matching the reference screenshot. */
/* "Which message am I replying to" - Reply/Reply All only, otherwise only
   implied by the inline composer's position in the thread (see
   openComposeModal in app.js, which sets the text and toggles .hidden). */
.compose-reply-context {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--cream);
  border-radius: var(--radius-sm);
  padding: 7px 10px;
  margin-bottom: 10px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--slate);
  overflow-wrap: anywhere;
}
.compose-reply-context.hidden { display: none; }

/* Read-only "who this sends as" row - reuses .compose-field-row so it
   wraps on mobile exactly the same proven way the other recipient rows
   already do (see the mobile overflow-fix comment further down). */
.compose-from-row { color: var(--slate-light); }
.compose-from-value { font-size: 13px; color: var(--slate); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.compose-recipient-block { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 4px; }
.compose-avatar {
  display: none; /* shown only when docked inline - see .mail-compose-docked-inline below */
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--horizon);
  color: white;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  margin-top: 2px;
}
.compose-recipient-fields { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.compose-field-row { display: flex; align-items: center; gap: 8px; }
.compose-field-row.hidden { display: none; }
.compose-field-label { flex-shrink: 0; font-size: 13px; color: var(--slate-light); width: 30px; }
.compose-field-row .recipient-field {
  flex: 1;
  min-width: 0;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  min-height: 32px;
  padding: 2px 0;
}
.compose-field-row .recipient-field:focus-within { border-color: var(--horizon); }
.compose-ccbcc-toggle { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.compose-ccbcc-toggle button {
  background: none;
  border: none;
  color: var(--slate-light);
  font-size: 13px;
  cursor: pointer;
  padding: 2px 0;
}
.compose-ccbcc-toggle button:hover { color: var(--text); text-decoration: underline; }
.compose-ccbcc-toggle button.hidden { display: none; }

.compose-subject-row { display: flex; flex-direction: column; gap: 2px; margin: 8px 0; }
.compose-subject-row.hidden { display: none; }
.compose-subject-row label { font-size: 12px; color: var(--slate-light); margin: 0; }
.compose-subject-row input {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 4px 0;
}
.compose-subject-row input:focus { outline: none; border-color: var(--horizon); }

.compose-template-row { display: flex; flex-direction: column; gap: 2px; margin: 8px 0; }
.compose-template-row.hidden { display: none; }
.compose-template-row label { font-size: 12px; color: var(--slate-light); margin: 0; }
.compose-template-row select {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 4px 0;
  background: transparent;
  color: var(--text);
}
.compose-template-row select:focus { outline: none; border-color: var(--horizon); }
/* The closed <select> sits on the page's own background, so
   background: transparent above looks right - but the native options
   popup is a separate OS-rendered surface that ignores that transparency
   and defaults to a plain white listbox regardless of theme. color: var(--text)
   still cascades into each <option> though, so dark mode was rendering
   near-white option text on that default white popup - invisible. Setting
   both properties explicitly on <option> pins the popup to the same theme
   surface as the rest of the app instead of the browser's default. */
.compose-template-row select option {
  background: var(--surface);
  color: var(--text);
}

/* Gmail-style "expand full conversation" panel inside Reply/Reply All's own
   compose window (toggled by #compose-thread-toggle-btn, populated by
   renderComposeThreadPreview in app.js) - sits between the recipient/
   subject fields and the toolbar+editable body, read scrollable
   vertically only (overflow-x: hidden, on top of every child already
   wrapping/constraining its own width - see .mail-body and
   .compose-thread-msg-summary below) so a long conversation or a wide
   HTML email inside it can never push the compose window itself into
   horizontal scroll, on mobile or otherwise. A capped max-height keeps it
   "compact" per its own design goal - long, not tall, is fine since it
   scrolls in place rather than pushing the Send button an arbitrary
   distance down the page. */
.compose-thread-preview {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--cream);
  margin: 8px 0;
  max-height: 260px;
  overflow-y: auto;
  overflow-x: hidden;
}
.compose-thread-preview-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--slate-light);
  padding: 8px 10px 4px;
}
.compose-thread-msg + .compose-thread-msg { border-top: 1px solid var(--border); }
.compose-thread-msg-summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  cursor: pointer;
  min-width: 0;
}
.compose-thread-msg-summary:hover { background: var(--surface); }
.compose-thread-msg-sender { flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; font-weight: 600; color: var(--text); }
.compose-thread-msg-snippet { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: var(--slate-light); }
.compose-thread-msg-date { flex-shrink: 0; font-size: 11.5px; color: var(--slate-light); white-space: nowrap; }
.compose-thread-msg.expanded .compose-thread-msg-summary { border-bottom: 1px solid var(--border); }
.compose-thread-msg-body { padding: 4px 10px 12px 40px; font-size: 13px; }

/* Rich text composer - flat, borderless circular icon buttons (restyled to
   match Gmail's own compose toolbar look, see the reference screenshot)
   rather than the boxed 6px-radius buttons this used to be; the
   contenteditable div below is styled to match the textarea it replaced
   (same border/radius/padding/min-height) so nothing about the compose
   modal's overall shape changed, just what's inside it. */
.compose-toolbar { display: flex; align-items: center; gap: 2px; margin: 4px 0 6px; flex-wrap: wrap; }
.compose-toolbar button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 50%;
  width: 34px;
  height: 34px;
  font-size: 13px;
  cursor: pointer;
  color: var(--slate);
}
.compose-toolbar button:hover { background: var(--cream); color: var(--text); }
.compose-draft-status { font-size: 12px; color: var(--slate-light); margin-left: 6px; }
/* Discard (trash) - inline-docked mode only, see the .mail-compose-window.
   mail-compose-docked-inline rule below; floating mode already has the
   header's Close (X) button for this, so showing both would be redundant
   there. Pinned to the toolbar's far right, matching where Gmail's own
   discard icon sits. */
.compose-discard-btn { display: none; margin-left: auto; }
.mail-compose-window.mail-compose-docked-inline .compose-discard-btn { display: inline-flex; }
.compose-body-editable {
  min-height: 200px;
  max-height: 400px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  font-size: 14px;
  line-height: 1.5;
  /* A long pasted URL/token with no natural break point would otherwise
     force this contenteditable area (and everything around it) wider than
     the compose box - same reasoning as .mail-body's identical rule for
     READING a message; this is the write-side equivalent. */
  overflow-wrap: anywhere;
}
.compose-body-editable:focus { outline: none; border-color: var(--navy-light); }
.compose-body-editable ul, .compose-body-editable ol { padding-left: 24px; margin: 8px 0; }

/* Email Template rich body editor (openMailTemplateEditor) - reuses
   .compose-toolbar / .compose-body-editable, plus these extras. */
.mt-toolbar { border: 1px solid var(--border); border-bottom: none; border-radius: var(--radius-sm) var(--radius-sm) 0 0; padding: 3px 4px; background: var(--cream-light, #fafaf7); }
.mt-toolbar button { width: auto; min-width: 30px; height: 30px; border-radius: var(--radius-sm); padding: 0 6px; font-size: 12px; }
.mt-toolbar button.active { background: var(--navy-light); color: #fff; }
.mt-tb-sep { width: 1px; align-self: stretch; margin: 4px 3px; background: var(--border); }
.mt-tb-field { display: inline-flex; align-items: center; gap: 3px; height: 30px; padding: 0 4px; font-size: 12px; color: var(--slate); }
.mt-tb-field input[type="color"] { width: 22px; height: 20px; padding: 0; border: 1px solid var(--border); border-radius: 3px; background: none; cursor: pointer; }
.mt-tb-size { height: 28px; border: 1px solid var(--border); border-radius: 4px; font-size: 12px; }
.mt-body-editable { border-radius: 0 0 var(--radius-sm) var(--radius-sm); min-height: 260px; }
.mt-body-editable table, .compose-body-editable table { border-collapse: collapse; }
.mt-body-editable table td, .mt-body-editable table th { border: 1px solid var(--border); padding: 4px 8px; }
.mt-body-editable img, .compose-body-editable img { max-width: 100%; height: auto; }
.mt-body-readonly { background: var(--cream); }
.mt-body-html {
  width: 100%;
  min-height: 260px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.5;
  border: 1px solid var(--border);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  padding: 10px 12px;
  resize: vertical;
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}
.mt-body-html:focus { outline: none; border-color: var(--navy-light); }

/* Gmail-style quoted history (see app.js's buildQuoteHtml) - reuses Gmail's
   own gmail_quote/gmail_attr class names, both here (composing) and in
   .mail-body (reading a message someone else sent from real Gmail, which
   already carries these exact classes) so both surfaces look identical. */
.gmail_attr { color: var(--slate-light); font-size: 13px; margin: 0 0 4px; }
.gmail_quote_blockquote {
  margin: 0 0 0 8px;
  padding-left: 12px;
  border-left: 2px solid var(--border);
  color: inherit;
}

.compose-attachments-list { display: flex; flex-wrap: wrap; gap: 0; margin-top: 8px; }
.compose-attachment-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 6px 6px 6px 12px;
  margin: 0 8px 8px 0;
  font-size: 13px;
}
.compose-attachment-chip.uploading { opacity: 0.7; }
.compose-attachment-chip.error { border-color: var(--danger); }
.compose-attachment-chip.error .mail-attachment-size { color: var(--danger); }
/* A pre-attached file that came from the selected template. */
.compose-attachment-chip.template { background: var(--horizon-soft); border-color: var(--horizon); }
.compose-attachment-chip.template .mail-attachment-size { color: var(--horizon); }

/* Template editor: Pre-Attached Files list */
.mt-attach-list { display: flex; flex-wrap: wrap; gap: 0; margin-top: 4px; }
.mt-attach-chip { margin: 0 8px 8px 0; }
.mt-attach-open {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--horizon); text-decoration: underline; font: inherit;
  max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mt-attach-open:hover { color: var(--text); }
.compose-attachment-remove {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 50%;
  width: 20px;
  height: 20px;
  line-height: 1;
  cursor: pointer;
  color: var(--slate);
  font-size: 13px;
}
.compose-attachment-remove:hover { background: var(--cream); color: var(--text); }

/* Back-to-list header, above the message preview - shown at every width now
   that list/detail are mutually exclusive on desktop too (see .mail-layout,
   .mail-messages/.mail-detail below): without it there'd be no way back to
   the list once a message is open on desktop. Only shown inside
   .mail-detail (see markup, MAIL_BACK_TO_LIST) - the list pane itself never
   needs a "back", so this rule doesn't need pane-specific gating. */
.mail-pane-header { display: flex; padding: 2px 4px 10px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
.mail-back-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  color: var(--horizon);
  font-weight: 700;
  font-size: 14px;
  padding: 10px 2px;
  min-height: 40px;
  cursor: pointer;
}
.mail-back-btn:hover { text-decoration: underline; }

/* Was: "tablet/shrunk-window range keeps all three panes side by side,
   just narrower" - removed after user feedback that showing folders +
   list + reading pane simultaneously anywhere below desktop width reads
   as three separate sections, not a modern single-flow inbox, regardless
   of whether there's technically enough horizontal room for it. The
   single-pane block below (folders drawer, list<->detail toggle) now
   starts at the same 900px this used to hand off from, so there's no
   more in-between width that still shows all three at once - only true
   desktop width does. This value is duplicated in
   MAIL_SINGLE_PANE_QUERY (app.js) - keep both in sync if it ever changes. */
@media (max-width: 900px) {
  /* Gmail-style single-row header: the "Mail" title AND the connected-email
     address both go away completely on mobile (not shown anywhere in this
     view - it isn't needed on every screen) - a hamburger opens the folders
     drawer where the title used to be, Search grows to fill the rest of the
     row. Compose moves to a floating action button and Disconnect into the
     drawer itself (see .mail-folder-disconnect), so nothing else competes
     with Search for room on this row. */
  #view-mail .view-header { display: none; }

  .mail-toolbar { gap: 6px; margin-bottom: 6px; }
  .mail-mobile-menu-btn { display: flex; order: 0; }
  /* flex-basis 0 (not auto) - a bare <input type="search">'s intrinsic
     content width (~200px) would otherwise count toward this row's
     line-fitting math even though flex-grow later shrinks it back down. */
  .mail-search-wrap { order: 1; flex: 1 1 0; min-width: 0; }
  #mail-compose-btn, #mail-disconnect-btn { display: none; }
  /* Compose already moves to a floating action button on mobile (above) and
     Disconnect into the drawer - Refresh isn't given an equivalent, it just
     stays right here in the toolbar (explicit order so it lands after
     Search, not sandwiched between the hamburger and the growing search
     box by flex's default same-order/source-order tiebreak). */
  #mail-refresh-btn { order: 2; }
  /* Compact filter pills - much smaller than the desktop chips (which the
     base .mail-filter-chip rule still serves): tighter padding, smaller
     type, a fixed ~24px tap height instead of letting font metrics decide
     it. flex-start (not center) + nowrap + scroll-if-needed means a 4th
     chip or a long device font never clips the row's start - it just
     becomes horizontally scrollable instead of wrapping or overflowing. */
  .mail-read-filter {
    order: 4;
    flex: 1 1 100%;
    justify-content: flex-start;
    gap: 5px;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .mail-read-filter::-webkit-scrollbar { display: none; }
  .mail-filter-chip {
    padding: 3px 9px;
    font-size: 10.5px;
    min-height: 22px;
    border-radius: 11px;
    white-space: nowrap;
    flex-shrink: 0;
  }

  .mail-folder-divider { display: block; margin: 8px 0; border: none; border-top: 1px solid var(--border); }
  .mail-folder-item.mail-folder-disconnect { display: flex; }

  /* Gmail-style: opens straight to the active folder's message list (no
     separate folder-picking pane to drill through first), reading a
     message stacks the detail pane on top of it, "back" retraces that one
     step. Folders themselves live in the slide-out drawer below rather
     than being a third stacked pane. Panes get more vertical room now
     that the header above them is two short rows instead of six. */
  /* #mail-content becomes the real height-owner for the whole mobile Mail
     screen - previously .mail-messages/.mail-detail each guessed their own
     height as "80vh - 56px", a fixed percentage that (a) used `vh`, which
     mobile browsers define as the LARGEST possible viewport (as if the
     address bar were permanently hidden) rather than what's actually on
     screen right now - so whenever the address bar was showing, the pane
     was laid out taller than the real visible area, cutting content off at
     the bottom / leaving a blank gap depending on how much chrome was
     showing at that moment - and (b) used the same 80vh figure whether the
     toolbar above it was visible (list view) or hidden (detail view, see
     the toolbar-hiding rule below), so reading a message never actually
     reclaimed the space the toolbar had been using - a real source of the
     "blank space underneath the preview" report. Flex here instead:
     .mail-toolbar (a flex item, natural height, 0 when hidden in detail
     view) and .mail-layout (flex:1, takes whatever's left) split
     #mail-content's real height between them with zero guessing about the
     toolbar's own size. #mail-content's own height is set directly as an
     inline style by syncMailContentHeight() in app.js, not by a CSS calc()
     here - a hand-typed constant for "everything above this element"
     (topbar height + main's own top padding + ...) is exactly the kind of
     guess that already drifted out of sync once while writing this fix
     (main's mobile bottom padding turned out to be tab-bar-height + an
     extra 20px gap, not just the tab bar alone); getBoundingClientRect().top
     measures that real, current total directly, however it's actually
     composed, and stays correct even if any of those ancestor values ever
     change independently. */
  #mail-content {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  /* Negative side margins pull the whole layout out to the viewport edges,
     past <main>'s own 16px mobile padding (see the main rule above) - a
     native inbox reads as one continuous full-width screen, not a panel
     floating with margins on both sides. The toolbar above it deliberately
     keeps main's normal padding (unchanged) - only the scrollable
     list/detail content bleeds edge to edge. */
  .mail-layout { grid-template-columns: 1fr; height: auto; flex: 1 1 auto; min-height: 0; position: relative; margin: 0 -16px; }
  .mail-messages, .mail-detail { grid-column: auto; grid-row: auto; }
  /* No more card treatment here (no border-radius/shadow) - per user
     feedback the Inbox should feel like one continuous screen, not a
     floating panel, so this white area simply runs the full viewport
     width edge to edge instead of reading as a boxed-off "card". Height is
     100% of whatever .mail-layout was actually given above (real flex
     math, not a vh guess) - only this pane's own content scrolls
     (overflow-y:auto, set on the shared .mail-folders/.mail-messages/
     .mail-detail rule up top), the page itself never needs to. */
  .mail-messages, .mail-detail {
    display: none;
    height: 100%;
    min-height: 0;
    border: none;
  }
  .mail-messages { padding-top: 6px; }
  .mail-layout[data-mobile-pane="list"] .mail-messages { display: block; animation: mail-pane-in-left 0.22s ease; }
  .mail-layout[data-mobile-pane="detail"] .mail-detail { display: block; animation: mail-pane-in-right 0.22s ease; }
  @keyframes mail-pane-in-right {
    from { opacity: 0; transform: translateX(28px); }
    to { opacity: 1; transform: translateX(0); }
  }
  @keyframes mail-pane-in-left {
    from { opacity: 0; transform: translateX(-28px); }
    to { opacity: 1; transform: translateX(0); }
  }

  /* Reading a message replaces the whole top bar with just the detail
     pane's own back button (.mail-pane-header inside .mail-detail, "<
     Inbox") - the search box/filter chips/hamburger/Compose FAB are
     list-only controls. Leaving them visible while reading a message is
     what "feels like three separate sections" traced back to on this
     exact flow: the toolbar read as a persistent extra region regardless
     of which pane was actually showing, instead of the clean Gmail-style
     "Inbox -> message -> back" a single back arrow gives. #mail-content
     carries its own copy of the pane attribute (see setMailMobilePane)
     specifically so this rule can reach .mail-toolbar, which is
     .mail-layout's sibling, not its descendant - a selector scoped to
     .mail-layout's own attribute has no way to reach it. */
  #mail-content[data-mobile-pane="detail"] .mail-toolbar { display: none; }

  /* Same full-bleed treatment as Mail's message list just above - one
     continuous screen instead of a boxed-off card, for a consistent look
     across every list in the CRM rather than Mail being the only one. */
  .calls-log-list { margin: 0 -16px; border: none; border-radius: 0; }

  /* Folders drawer - slides in from the left over whichever pane is
     showing (list or detail) rather than replacing it, opened via the
     hamburger in the toolbar (#mail-mobile-menu-btn), an edge-swipe from
     anywhere in the mail view (wireMailDrawerSwipe), or closed by tapping
     the backdrop/picking a folder/Escape. min/max-height reset to plain
     100% - the 72vh/80vh above was sized for a pane sharing vertical space
     in the stack, not an overlay meant to span edge to edge.
     top: var(--topbar-height) (set by a ResizeObserver in app.js, see the
     .topbar wiring near MOBILE_MORE_VIEWS) starts both the drawer and its
     backdrop below the logo header instead of at the true viewport top -
     a fixed z-index alone (still set below, as a rounding-error safety
     net) only kept the LOGO visible over the drawer, it didn't stop the
     drawer's own top folder items from rendering underneath the header
     and being clipped there. The 56px fallback is a reasonable estimate
     for before the observer's first measurement ever lands. */
  .mail-folders {
    position: fixed;
    top: var(--topbar-height, 56px);
    bottom: 0;
    left: 0;
    width: min(82vw, 300px);
    z-index: 85;
    display: block;
    min-height: 0;
    max-height: none;
    border-radius: 0 var(--radius) var(--radius) 0;
    box-shadow: 2px 0 24px rgba(15, 23, 42, 0.25);
    transform: translateX(-100%);
    transition: transform 0.25s ease;
  }
  .mail-folders.mail-drawer-open { transform: translateX(0); }
  .mail-drawer-backdrop {
    display: block;
    position: fixed;
    top: var(--topbar-height, 56px);
    right: 0;
    bottom: 0;
    left: 0;
    background: rgba(15, 23, 42, 0.4);
    z-index: 81;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
  }
  .mail-drawer-backdrop.open { opacity: 1; pointer-events: auto; }

  /* .mail-pane-header itself is now set at every width in the base rule
     above - only this mobile-specific tighter spacing override remains
     here. .mail-detail's own padding shrank below (20px -> 10px) to reclaim
     reading width - this negative-margin bleed has to shrink by the same
     amount to keep pulling the pane-header exactly flush with the pane's
     new edges.

     The back button row itself is shrunk hard (was 10px/40px-min-height/
     10px-margin-bottom + a divider - the single biggest fixed-height
     contributor above the actual message on a phone) and the divider
     dropped entirely - the subject line right below it already reads as
     its own block without a rule under a header this short. Every px
     reclaimed here is a px the email body starts higher on screen. */
  .mail-detail { padding: 10px 12px; }
  .mail-detail .mail-pane-header { margin: -10px -12px 2px; padding: 4px 12px; border-bottom: none; }
  .mail-detail .mail-back-btn { min-height: 28px; padding: 2px; font-size: 13px; }

  .mail-folder-item { min-height: 44px; display: flex; align-items: center; font-size: 14px; }
  /* .mail-row-content (the child) already carries its own 5px/14px padding
     at every breakpoint (see the base rule) - .mail-message-row used to
     ALSO pad itself here, stacking both layers into dead space per row
     edge. One layer only. 16px horizontal - not 12px - now that
     .mail-layout bleeds to the viewport edge: this is the only inset text
     has from the true screen edge, so it needs to read like a native app's
     row padding, not a leftover card-interior padding sized for a pane
     that had its own margin outside it. Vertical padding matches the base
     rule (not overridden wider here) - only the vertical dimension keeps
     shrinking per repeated "still too tall" feedback, width is untouched. */
  .mail-row-content { padding: 5px 16px; }
  .mail-row-subject { font-size: 14px; }
  /* No hover on touch, so the desktop hover-to-reveal treatment doesn't
     apply - actions drop out of the absolute overlay and become an
     always-visible, always-tappable part of the row instead, with larger
     touch targets than the desktop hover icons need. Timestamp top-right,
     icons stacked directly under it (.mail-row-side below) rather than
     all three of sender/date/icons crowding one line - matches the same
     "date top-right, actions below" layout already used in the reading
     header, for a consistent look across the whole Mail module. */
  .mail-row-top { flex-wrap: nowrap; align-items: flex-start; }
  /* Sender truncates instead of pushing the row's total content past the
     available width and forcing the date/icons column to shrink or wrap. */
  .mail-row-sender { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* This always-visible action column is the single biggest driver of
     mobile row height (it stacks taller than the sender+subject text ever
     does), so it's the highest-leverage place to trim for "more emails per
     screen" - tighter gap and smaller buttons than the original 4px/32px
     (now down to 1px/24px, still a comfortable tap target given the row
     itself is the larger primary target), without abandoning the
     stacked-column layout above (still needed to avoid crowding sender/
     date/icons onto one line on a narrow screen). */
  .mail-row-side { flex-direction: column; align-items: flex-end; gap: 1px; }
  .mail-row-date { flex-shrink: 0; }
  .mail-row-actions { position: static; opacity: 1; pointer-events: auto; background: transparent; padding-left: 0; transform: none; flex-shrink: 0; }
  .mail-message-row:hover .mail-row-date { visibility: visible; }
  .mail-row-action { width: 24px; height: 24px; font-size: 14px; }

  /* Subject stays reasonably prominent (it's the one thing worth being
     bold/larger) but everything else in the reading view below tightens up
     - meta text, avatar, action buttons - freeing up width and height for
     the message body itself, the actual point of the screen.

     The margin-bottom a prior pass put on .mail-detail-subject itself was
     dead weight in practice - .mail-thread-header .mail-detail-subject
     { margin: 0 } (base rule, two classes) always outranks a single-class
     override on specificity, so the real gap under the subject was coming
     entirely from .mail-thread-header's own margin-bottom (14px, untouched
     until now). Trimmed at the source instead. */
  .mail-thread-header { margin-bottom: 6px; gap: 6px; }
  .mail-detail-subject { font-size: 16px; line-height: 1.3; }
  .mail-avatar { width: 32px; height: 32px; font-size: 12px; }
  /* Baseline-aligned sender works fine once there's room to sit on one
     line; below that it wraps (mail-meta-top already allows it) and
     baseline alignment would misalign the two lines - flex-start instead. */
  .mail-meta-top { align-items: flex-start; }
  .mail-meta-sender { font-size: 13px; }
  .mail-meta-date { font-size: 11px; }
  /* Real overflow found by live-testing at 375px (the assumption that
     .mail-meta-main would just shrink to fit, same as it always did for
     the bare date, turned out wrong - four icons plus the date add up to
     more fixed width than a narrow header has room for, squeezing
     meta-main to 0 and overflowing the sender name straight out of it).
     Fix #1: .mail-thread-card-header wraps on mobile, and this block
     takes flex-basis:100% to always drop to its own full-width line
     (right-aligned) below the avatar/sender row instead of fighting them
     for space on one line - same wrap-to-own-line pattern already
     verified for the compose window's Cc/Bcc toggle earlier the same day.
     Fix #2, found re-testing at an even narrower 320px (iPhone SE class,
     the same real width that reopened the recipient-chip overflow earlier
     the same day): even alone on its own full-width line, four 26px icons
     plus the date text still don't fit a header that narrow - flex-wrap
     here too lets the icon group drop to a SECOND line below the date
     (both right-aligned) when needed, rather than assuming one dedicated
     line is automatically narrow-enough. Each individual line's content
     (date alone, or the icon group alone) fits comfortably at any real
     phone width - nothing here depends on a specific breakpoint's exact
     arithmetic being right, matching the actually-durable fix already
     used for the recipient chip. */
  .mail-thread-card-header { flex-wrap: wrap; }
  .mail-meta-date-actions { flex-basis: 100%; flex-wrap: wrap; justify-content: flex-end; gap: 6px; margin-top: 4px; }
  .mail-icon-btn-sm { width: 26px; height: 26px; }
  .mail-meta-recipients { margin-top: 2px; gap: 2px; }
  /* Collapses to one truncating line (names only, no raw <email> addresses)
     instead of wrapping every recipient's full name+email across several
     lines - closer to the reference's compact single-line "To: X & N
     others" and a lot less vertical space for what's rarely the reason
     you opened the email. */
  .mail-meta-row { font-size: 12px; gap: 6px; flex-wrap: nowrap; }
  .mail-meta-label { min-width: 24px; }
  .mail-meta-value { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .mail-meta-email { display: none; }
  /* Star/Mark-unread/Archive/Delete/Label - the exact same five actions
     already live in the header's own "More" menu (mail-quick-reply-icons'
     kebab, see mailThreadCardHtml in app.js), so this was a second full
     32px-tall button row doing nothing a tap into More doesn't already
     cover. Dropped on mobile only (desktop keeps the dedicated row) - one
     more real professional-mobile-mail-app trait: a single compact header
     toolbar (Reply/Reply All/Forward/More) instead of stacked duplicate
     button rows competing with the message for screen space. */
  .mail-quick-actions { display: none; }
  .mail-body { font-size: 13.5px; line-height: 1.5; padding: 12px; }
  .mail-attachments { margin-top: 10px; }
  .mail-attachment-chip { padding: 5px 5px 5px 10px; font-size: 12px; }
  .mail-attachment-download { width: 24px; height: 24px; }

  /* Reply/Reply All/Forward as full-label buttons below the body - already
     duplicated by the compact icon row at the top of the header (present
     on every card, above the fold, no scrolling needed to reach it), so
     this bottom copy is dropped on mobile rather than shrunk further. The
     header row alone satisfies "Reply/Reply All/Forward/More stay easily
     accessible" without a second dedicated action bar. */
  .mail-detail-actions { display: none; }

  /* Conversation view on a narrow screen: the 52px body/actions indent that
     aligns things with the avatar on desktop just eats width here, and the
     collapsed row's sender name doesn't need a fixed max-width once the
     snippet is the one giving way first. */
  .mail-thread-card {
    border: none;
    border-radius: 14px;
    box-shadow: 0 1px 2px rgba(22, 35, 61, 0.06), 0 2px 8px rgba(22, 35, 61, 0.07);
    transition: transform 0.12s ease, box-shadow 0.12s ease;
  }
  .mail-thread-card.collapsed:active { transform: scale(0.98); box-shadow: 0 1px 2px rgba(22, 35, 61, 0.08); }
  .mail-thread-card.expanded { padding: 10px 12px 12px; }
  .mail-thread-card-header { padding-bottom: 8px; margin-bottom: 8px; gap: 8px; }
  .mail-thread-card.expanded .mail-body,
  .mail-thread-card.expanded .mail-attachments:not(:empty),
  .mail-thread-card.expanded .mail-quick-actions,
  .mail-thread-card.expanded .mail-detail-actions { margin-left: 0; }
  .mail-thread-card-sender { max-width: 40vw; }
  .mail-thread-card-snippet { display: none; } /* sender + date is enough context to tap into on a phone-width row */

  /* Larger touch targets for the composer toolbar and attachment controls -
     no hover on touch, so these need to be comfortably tappable at their
     resting size rather than relying on a hover state to feel deliberate. */
  .compose-toolbar button { width: 38px; height: 38px; font-size: 15px; }
  .mail-attachment-download { width: 32px; height: 32px; }
}

/* ---------- Tasks ---------- */
.task-list { display: flex; flex-direction: column; gap: var(--space-2); }
.task-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
  padding: var(--space-3) var(--space-4);
}
.task-row.overdue { border-left: 4px solid var(--danger); }
/* Title + status badge on one line (badge trailing, never squeezed since
   the title is the one that wraps/shrinks), assignee+due date below - the
   status used to be buried mid-sentence in the meta line as plain text;
   pulling it out into its own .stage-pill here is what actually makes it
   readable at a glance instead of something you have to go looking for. */
.task-row-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.task-row-top .t-title { flex: 1; min-width: 0; }
.task-row-top .stage-pill { flex-shrink: 0; margin-bottom: 0; }
.task-row .t-title { font-weight: var(--fw-semibold); font-size: var(--fs-14); }
.task-row .t-meta { font-size: var(--fs-12); color: var(--slate-light); margin-top: 2px; }
.task-row button {
  background: var(--success);
  color: white;
  border: none;
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  font-size: 12px;
}

/* ---------- Employees / My Team ---------- */
.employee-list { display: flex; flex-direction: column; gap: var(--space-2); }
.employee-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
  padding: var(--space-3) var(--space-4);
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.employee-row.clickable:hover { border-color: var(--border-strong); background: var(--surface); box-shadow: var(--shadow-md); }
.employee-row button {
  background: transparent;
  border: 1px solid var(--border);
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  font-size: 12px;
}

/* Decorative initials circle - a fixed visual anchor at the start of every
   row so the eye has a consistent left edge to scan down, instead of names
   of varying length setting where each row "starts". */
/* color: var(--navy), not var(--text) - same fixed-light-background/
   theme-adaptive-text mismatch as .stage-pill below (see its own comment). */
.employee-avatar {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--horizon-soft);
  color: var(--navy);
  font-weight: 700;
  font-size: 14px;
}

.employee-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.employee-name-line { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.employee-name { font-weight: 700; font-size: 15px; color: var(--text); }

/* Meta/stat lines share one compact, muted style with a middle-dot
   separator between siblings (::not(:last-child)::after) rather than
   baking the separator into the text itself, so empty/variable-length
   fields never leave a dangling "· ·". */
.employee-meta-line, .employee-stat-line { display: flex; flex-wrap: wrap; align-items: center; font-size: 12px; color: var(--slate-light); }
.employee-stat-line { color: var(--slate); }
.employee-stat-line strong { color: var(--text); font-weight: 700; }
.employee-meta-line span:not(:last-child)::after, .employee-stat-line span:not(:last-child)::after {
  content: '·';
  margin: 0 6px;
  color: var(--border);
}
.employee-stat-overdue { color: var(--danger); font-weight: 600; }

/* Active/Inactive pulled out of the text into its own corner pill, colored
   semantically (status-good/critical), so it reads at a glance instead of
   being buried at the end of a dense meta line. */
.status-pill {
  flex-shrink: 0;
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.status-pill.active { background: var(--tone-success-bg); color: var(--tone-success-fg); }
.status-pill.inactive { background: var(--tone-danger-bg); color: var(--tone-danger-fg); }

/* ---------- Modal ---------- */
.modal {
  position: fixed; inset: 0;
  background: rgba(22,35,61,0.5);
  display: flex; align-items: center; justify-content: center;
  z-index: 100;
}
.modal-box {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 28px;
  width: 420px;
  max-height: 88vh;
  overflow-y: auto;
  /* Explicit, not just the absence of a rule - guarantees vertical-only
     scrolling inside the box itself regardless of content, on top of (not
     instead of) actually fixing what could be wide enough to need it (see
     the mobile-card-list rules above). */
  overflow-x: hidden;
  position: relative;
}

/* Locks the page behind any open modal from scrolling/panning at all -
   toggled by the MutationObserver in app.js watching each .modal's own
   class attribute, so it applies uniformly to every modal in the app
   without touching any individual open/close call site. This is the
   standard iOS body-scroll-lock technique (overflow:hidden alone is not
   sufficient on iOS Safari - it still allows rubber-band panning of a
   position:static body). Root cause this fixes: with the Merchant Details
   modal open on the Leads/Pipeline view, the Kanban board sitting behind
   it is a legitimately horizontally-scrollable region (see the .kanban
   mobile carousel below) - without this lock, a touch gesture on the
   modal could still be captured by that background scroll, which is what
   was producing the reported "shifts left/right while scrolling" even
   after the modal's own content-width overflow was already fixed. */
/* With the fixed-header shell it's <main> that scrolls, not <body>, so the
   lock freezes <main> (and the JS in initModalScrollLock preserves/restores
   its scrollTop). position:fixed on <body> is neither needed nor safe here
   any more. */
body.modal-open #app-screen > main {
  overflow: hidden;
}
.modal-box.wide { width: 640px; }
.modal-close {
  position: absolute; top: 14px; right: 16px;
  background: none; border: none; font-size: 20px; color: var(--slate-light);
}
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.form-grid .full { grid-column: 1 / -1; }

/* Stage-change validation (see app.js's highlightMissingFields): red label
   + red input/select/textarea border on a field the destination stage
   requires but is still missing/wrong, plus the same reason text the top
   summary message already shows, repeated right under the field so it's
   obvious without scrolling back up. Auto-removed (see
   handleStageFieldLiveCheck) the moment the field's value satisfies the
   requirement - no need to resubmit just to clear it. */
.form-grid .field-invalid label { color: var(--danger); }
.form-grid .field-invalid input,
.form-grid .field-invalid select,
.form-grid .field-invalid textarea {
  border-color: var(--danger);
}
.field-error-msg { color: var(--danger); font-size: 12px; margin: 4px 0 0; }

/* color is deliberately var(--navy), NOT var(--text) - confirmed live as a
   real dark-mode bug: the default background here is --horizon-soft, a
   fixed light pastel that (by design, same as every other horizon-soft
   chip in this app - nav-btn.active, mail-folder-item.active, etc.) never
   changes with theme, but --text DOES change to a light color in dark
   mode. A caller that doesn't override color with its own inline style
   (many don't - e.g. a merchant's plain stage badge) inherited that
   dark-mode light text straight onto the still-light pastel background,
   making it unreadable. var(--navy) matches --text's own LIGHT-mode value
   exactly (--text: var(--navy) in :root), so this changes nothing in light
   mode and fixes dark mode. Callers that DO set their own inline color
   (status pills using var(--status-good)/var(--danger), which are already
   theme-fixed saturated colors) are unaffected either way. */
.stage-pill {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 700;
  background: var(--horizon-soft);
  color: var(--navy);
  margin-bottom: 12px;
}

/* ---------- Badge system ----------
   .stage-pill (above) is this app's original status-chip primitive; .badge is
   its design-system name and shares the same shape. The .badge--<tone>
   modifiers below are theme-aware (via the --tone-* pairs) and are what let
   app.js's ~6 old inline colour maps (TASK_STATUS_STYLES, APPROVAL_STATUS_
   STYLES, CALL_OUTCOME_COLORS, contact-type / source badges, …) collapse into
   one system that also reads correctly in dark mode. Defined here, right after
   .stage-pill, so a `class="stage-pill badge--success"` correctly overrides
   .stage-pill's own horizon-soft default on source order. */
.badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-11);
  font-weight: var(--fw-bold);
  line-height: 1.5;
  white-space: nowrap;
}
.badge--neutral, .stage-pill.badge--neutral { background: var(--tone-neutral-bg); color: var(--tone-neutral-fg); }
.badge--info,    .stage-pill.badge--info    { background: var(--tone-info-bg);    color: var(--tone-info-fg); }
.badge--success, .stage-pill.badge--success { background: var(--tone-success-bg); color: var(--tone-success-fg); }
.badge--warning, .stage-pill.badge--warning { background: var(--tone-warning-bg); color: var(--tone-warning-fg); }
.badge--danger,  .stage-pill.badge--danger  { background: var(--tone-danger-bg);  color: var(--tone-danger-fg); }
.badge--accent,  .stage-pill.badge--accent  { background: var(--tone-accent-bg);  color: var(--tone-accent-fg); }

/* Employee-list / hierarchy role tags (My Team) — a quieter alternative to
   .role-badge's brand green, but still a solid, high-contrast pill that reads
   clearly against a row in BOTH themes. The plain .badge--neutral chip (a
   subdued dark-on-dark tint) is right for status labels but disappears here,
   so this pairing gets a fixed mid-grey fill instead, theme-independent like
   .stage-pill's own default. */
.role-badge.badge--neutral {
  background: #64707f;
  color: #ffffff;
}

.stage-actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 16px 0; }
/* color: var(--text) required - same native-<button>-doesn't-inherit-
   text-color bug as .mail-detail-actions button above (see its
   comment): left unset, these stage-transition buttons render in the
   browser's own fixed black regardless of theme. .current below already
   sets its own color, so it's unaffected. */
.stage-actions button {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  color: var(--text);
}
.stage-actions button:hover { background: var(--cream); }
.stage-actions button:focus-visible { outline: 2px solid var(--horizon); outline-offset: 1px; }
.stage-actions button:disabled { color: var(--slate-light); cursor: default; opacity: 0.7; }
.stage-actions button.current { background: var(--navy); color: white; border-color: var(--navy); }

/* ---------- Task Stats period filter (Completed Tasks tab) ---------- */
.period-filter-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
/* Reports' report-type tabs reuse .period-filter-row/.period-btn (same
   pill-tab language as Completed Tasks' period selector) but need more
   breathing room before the filter panel that follows immediately, where
   Completed Tasks' period row is followed by a plain stat grid instead. */
.report-tabs { margin-bottom: 18px; }
.period-btn {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  color: var(--slate);
}
.period-btn:hover { border-color: var(--horizon); color: var(--text); }
.period-btn.active { background: var(--navy); color: white; border-color: var(--navy); font-weight: 600; }
.period-custom-row { display: flex; align-items: center; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.period-custom-row input[type="date"] { width: auto; }
.period-custom-row span { color: var(--slate-light); font-size: 13px; }
.period-custom-row button { background: var(--navy); color: white; border: none; padding: 8px 16px; border-radius: var(--radius-sm); font-size: 13px; }

.stat-tile.active-bucket { border: 2px solid var(--navy); }

/* Below 900px, five stacked full-width columns means scrolling past every
   card in every stage just to see what's next - a horizontal, swipeable
   strip (one column mostly in view, the next peeking at the edge) matches
   how a Kanban board is actually used on a phone. */
@media (max-width: 900px) {
  .kanban {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 86%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: 6px;
    -webkit-overflow-scrolling: touch;
  }
  .kanban-col { scroll-snap-align: start; }
}

@media (max-width: 640px) {
  /* iOS Safari (and some Android browsers) auto-zoom the whole page when
     focusing a form field with a font-size under 16px - overriding the
     base 14px (see the top-of-file input/select/textarea rule) here,
     mobile-only, is the standard fix for that, working together with the
     viewport meta tag's maximum-scale/user-scalable=no (see index.html)
     rather than relying on either alone since browser support for each is
     inconsistent on its own. Desktop keeps the smaller 14px - this isn't a
     visual density change there, only a mobile zoom-prevention fix. */
  input, select, textarea { font-size: 16px; }
  /* Higher-specificity than the rule above (class + element), so it needs
     its own explicit mobile override too - the create-label input in
     Mail's label picker is reachable on mobile same as everything else. */
  .mail-label-create-form input { font-size: 16px; }
  .ai-assistant-input-row input { font-size: 16px; }
  .recipient-field .recipient-typing { font-size: 16px; }
  /* Real fix for a genuine horizontal-overflow bug in the compact Gmail-
     style compose recipient row (.compose-field-row: avatar + To/Cc/Bcc
     label + .recipient-field + the Cc/Bcc toggle, all on one line) -
     every value below was arrived at empirically, by forcing the live page
     to a real mobile width (the project's own established technique - see
     the mail-nav saga elsewhere in this file) and re-measuring actual
     scrollWidth vs. clientWidth after each change, not hand-calculated -
     this specific row went through three such rounds before every
     descendant (compose window, body-wrap, the field itself) measured
     zero overflow. Root causes, all three fixed:
     1) .recipient-typing's base min-width:120px (line ~1163 above, sized
        for the OLD full-width boxed recipient field this row didn't exist
        to compete with) doesn't shrink for this new compact row - even
        with flex-wrap on .recipient-field itself, a wrapped item still
        can't shrink below its OWN min-width, so the typing input alone
        forced #compose-to-field wider than the row had room for.
     2) The Cc/Bcc toggle ("Cc"/"Bcc" text links) and the field competing
        for space on the SAME line as the label left too little room even
        after (1) - on a genuinely narrow phone, label + field + toggle
        together don't fit on one line at any usable field width.
        flex-basis:100% on BOTH the toggle and the field forces each onto
        its own line (label alone on line 1, field full-width on line 2,
        toggle full-width/right-aligned on line 3) - measured as the
        difference between a residual ~30px overflow and zero.
     3) Even at the field's new full row-width, a real recipient name
        ("Shakti Ghodadra") still overflowed slightly at the chip text's
        base 260px cap (150px was still too generous, measured) - 60px is
        the tightest cap that measured zero overflow while still showing a
        recognizable truncated prefix ("Shakti…"). Aggressive, but this is
        the actual available space on a narrow phone once the avatar/
        label/remove-button/padding chrome around the text is accounted
        for - verified, not guessed. */
  .compose-field-row { flex-wrap: wrap; row-gap: 4px; }
  .recipient-field .recipient-typing { min-width: 40px; }
  .compose-ccbcc-toggle { flex-basis: 100%; justify-content: flex-end; }
  .compose-field-row .recipient-field { flex-basis: 100%; }
  /* A fixed px cap here (an earlier version of this fix used 60px) only
     ever matches the ONE width it was measured against - re-checking at a
     narrower 320px-wide phone (iPhone SE class, still real) reopened the
     exact same overflow, since the chip's own avatar/remove-button/padding
     chrome (~58px, roughly HALF a narrow chip's total width) eats a
     proportionally bigger share of an even-smaller available space than
     it did at 375px. Real fix: let the text shrink to WHATEVER room is
     actually left (min-width:0 + flex:1, the same pattern already used
     everywhere else in this file to make a flex child truly shrinkable)
     instead of targeting one specific breakpoint's arithmetic - correct
     at any width by construction, not just the ones actually tested. */
  .recipient-chip { min-width: 0; max-width: 100%; }
  .recipient-chip-text { flex: 1 1 auto; min-width: 0; max-width: none; }
  .recipient-chips { min-width: 0; }
  /* Same reasoning for a long attachment filename inside the compact
     compose box specifically (the base 220px cap is sized for the wider
     desktop floating window). */
  .mail-attachment-name { max-width: 140px; }
  /* contenteditable, not a real <input>/<textarea> - not covered by the
     rule above at all, but iOS Safari applies the same focus-zoom to any
     editable element, so it needs the identical fix. */
  .compose-body-editable { font-size: 16px; }

  #login-screen, #reset-password-screen { padding: 16px; }
  .login-card { padding: 32px 24px; }

  /* Primary navigation moves to the bottom tab bar below - the topbar's
     old job was just "hold the nav row" plus identity/notifications, and
     with the nav row gone it collapses back to one short, uncluttered
     row instead of the two-row wrap-and-scroll it used to need.
     The topbar no longer needs position:fixed here (or anywhere) - the
     global #app-screen flex-column shell keeps it pinned at the top of the
     viewport at every breakpoint while <main> alone scrolls, so nothing
     ever scrolls "underneath" it and there's no flow gap for <main> to
     pad. Historic note: this used to be position:fixed to stop the page
     scrolling away from behind the Mail folders drawer; the fixed-shell
     layout makes that impossible by construction. z-index kept so the
     topbar still stacks above the drawer/backdrop. */
  .topbar { z-index: 86; padding: 10px 16px; gap: 10px; }
  .topbar nav { display: none; }
  .user-chip { margin-left: auto; }
  /* Logout now lives in the More sheet (.mobile-more-logout-btn) - no need
     to repeat it in the topbar too on mobile. */
  #logout-btn { display: none; }

  /* ---------- Bottom tab bar (native-app primary nav) ---------- */
  .mobile-tab-bar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 80;
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: 0 -2px 16px rgba(22, 35, 61, 0.08);
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .mobile-tab-btn {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    background: none;
    border: none;
    padding: 8px 2px 7px;
    min-height: 56px;
    color: var(--slate-light);
    font-size: 10px;
    font-weight: 600;
    white-space: nowrap;
    transition: color 0.15s ease;
  }
  .mobile-tab-btn svg { transition: transform 0.15s ease; }
  .mobile-tab-btn.active { color: var(--horizon); }
  .mobile-tab-btn.active svg { transform: translateY(-1px) scale(1.08); }
  .mobile-tab-btn:active svg { transform: scale(0.9); }

  /* ---------- "More" sheet ---------- */
  .mobile-more-sheet { display: block; position: fixed; inset: 0; z-index: 95; }
  .mobile-more-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    opacity: 0;
    animation: mobile-sheet-fade 0.2s ease forwards;
  }
  .mobile-more-panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--surface);
    border-radius: 18px 18px 0 0;
    padding: 8px 8px calc(12px + env(safe-area-inset-bottom, 0));
    box-shadow: 0 -8px 32px rgba(22, 35, 61, 0.25);
    transform: translateY(100%);
    animation: mobile-sheet-up 0.22s cubic-bezier(0.32, 0.72, 0, 1) forwards;
  }
  .mobile-more-handle { width: 36px; height: 4px; border-radius: 3px; background: var(--border); margin: 6px auto 10px; }
  .mobile-more-item {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    background: none;
    border: none;
    text-align: left;
    padding: 13px 14px;
    border-radius: var(--radius);
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
    min-height: 48px;
  }
  .mobile-more-item:active { background: var(--cream); }
  .mobile-more-item svg { flex-shrink: 0; color: var(--slate); }
  .mobile-more-divider { border: none; border-top: 1px solid var(--border); margin: 6px 8px; }
  .mobile-more-logout { color: var(--danger); }
  .mobile-more-logout svg { color: var(--danger); }

  @keyframes mobile-sheet-fade { to { opacity: 1; } }
  @keyframes mobile-sheet-up { to { transform: translateY(0); } }

  /* The bell sits mid-chip, not at a screen edge - a fixed-width dropdown
     anchored to it (like on desktop) can overflow either side depending on
     how much room the username/role badge take up. Anchoring to the
     viewport itself instead of the bell guarantees it always fits. */
  .notif-panel {
    position: fixed;
    top: 64px;
    left: 12px;
    right: 12px;
    width: auto;
    max-width: none;
  }

  /* Comfortable tap target for every checkbox added since the original
     mobile pass (task template/workflow-gate toggles, the stage-visibility
     grid, employee archive filter) - default checkboxes are too small to
     reliably tap on a phone. */
  input[type="checkbox"] { min-width: 18px; min-height: 18px; }

  /* Bottom padding clears the fixed bottom tab bar (56px + its own
     safe-area inset) so the last row of any page is never hidden behind it.
     No top padding to "clear the topbar" any more - the #app-screen flex
     shell already lays <main> out directly below the (non-fixed) topbar. */
  main { padding: 16px 16px calc(56px + 20px + env(safe-area-inset-bottom, 0)); }

  .view-header {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  .view-actions { flex-direction: column; }
  .view-actions input,
  .view-actions .primary-btn { width: 100%; }

  .filter-toolbar { flex-direction: column; align-items: stretch; }
  .filter-toggle { justify-content: center; width: 100%; min-height: 40px; }
  .filter-panel { flex-direction: column; align-items: stretch; gap: 14px; }
  .filter-group, .filter-group-dates { min-width: 0; width: 100%; }
  .filter-clear { margin-left: 0; align-self: flex-start; min-height: 40px; }
  .filter-checkbox-group { min-height: 0; }

  /* My Leads toolbar: the title stays its own short row, and search +
     Filters + Import + New Merchant all share ONE row - the three buttons
     shrink to icon-only (label hidden, icon + count badge stay) so all
     four controls fit without wrapping, instead of the ~5-row stack this
     used to be. That frees up real space for the Kanban board underneath,
     which is the actual point of this page on a phone. */
  #view-pipeline .view-header { margin-bottom: 10px; }
  #view-pipeline .filter-toolbar { flex-direction: row; flex-wrap: nowrap; align-items: center; gap: 6px; }
  #view-pipeline .filter-search { flex: 1 1 auto; min-width: 0; }
  #view-pipeline .filter-toggle,
  #view-pipeline #import-merchant-btn,
  #view-pipeline #new-merchant-btn {
    width: auto;
    flex: 0 0 auto;
    padding: 9px 10px;
    min-height: 40px;
    justify-content: center;
  }
  #view-pipeline .btn-label { display: none; }
  .report-actions { flex-direction: column; align-items: stretch; margin-left: 0; width: 100%; }
  .report-actions .primary-btn, .report-actions button { width: 100% !important; }

  .task-row, .employee-row {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }

  /* Same "native card" elevation as the phase-1 pass (stat tiles, Kanban,
     merchant cards) - flat border swapped for a soft shadow, rounded
     corners, tap-press feedback. The overdue accent stays a left border
     specifically (not swallowed by border:none) since it's the only
     visual signal a task is late - everything else about the border goes,
     but that one pixel of information doesn't. */
  .task-row {
    border: none;
    border-radius: 14px;
    padding: 14px 16px;
    box-shadow: 0 1px 2px rgba(22, 35, 61, 0.06), 0 2px 8px rgba(22, 35, 61, 0.07);
    transition: transform 0.12s ease, box-shadow 0.12s ease;
  }
  .task-row.overdue { border-left: 4px solid var(--danger); padding-left: 12px; }
  .task-row.clickable:active { transform: scale(0.98); box-shadow: 0 1px 2px rgba(22, 35, 61, 0.08); }
  .task-row-top .t-title { font-size: 15px; }

  .modal { padding: 16px; }
  .modal-box, .modal-box.wide {
    width: 100%;
    max-width: 100%;
    padding: 24px 20px;
  }
  .form-grid { grid-template-columns: 1fr; }

  /* Merchant Details modal only (see the .mobile-card-list rule above for
     why) - every other .table-scroll in the app (Reports, Employee
     Activity, Mail attachments, etc.) is untouched and keeps its existing,
     already-working horizontal-scroll-within-its-own-box mobile behavior. */
  #merchant-modal .table-scroll { display: none; }
  #merchant-modal .mobile-card-list { display: flex; }

  .bar-row { grid-template-columns: 72px 1fr 30px; gap: 6px; }
  .activity-row { flex-wrap: wrap; }
  .activity-time { margin-left: 18px; }

  .chart-card { padding: 14px 16px; }
  .charts-row { gap: 12px; }

  .stat-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .stat-tile { padding: 14px; }
  .stat-tile .stat-value { font-size: 24px; }

  .data-table { min-width: 480px; font-size: 12px; }
  .data-table th, .data-table td { padding: 8px 10px; }

  .dashboard-section-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 0; }
  .section-link { margin-left: 0; }

  /* ---------- "Native card" elevation pass ---------- */
  /* A flat 1px border reads as "bordered desktop widget"; a soft shadow
     with no border reads as a native card sitting above the page - this
     is one of the cheapest, highest-leverage changes for the "premium app"
     feel the redesign is after. Applied to every card-like surface at
     once rather than one at a time so the whole app reads consistently,
     not just whichever screen got touched most recently. */
  .stat-tile, .chart-card, .activity-list, .kanban-col, .merchant-card,
  .table-scroll, .filter-panel, .compose-attachment-chip {
    border: none;
    box-shadow: 0 1px 2px rgba(22, 35, 61, 0.06), 0 2px 8px rgba(22, 35, 61, 0.07);
  }
  .stat-tile, .chart-card, .kanban-col, .merchant-card { border-radius: 14px; }
  .kanban-col-header { border-radius: 14px 14px 0 0; }

  /* Touch has no hover state - :active (the moment of contact) is what
     hover would have been on desktop, giving every tappable card the same
     "this responded to me" feedback a native app control has. */
  .stat-tile.clickable, .merchant-card, .activity-row.clickable, .chart-card h4.clickable {
    transition: transform 0.12s ease, box-shadow 0.12s ease;
  }
  .stat-tile.clickable:active, .merchant-card:active {
    transform: scale(0.97);
    box-shadow: 0 1px 2px rgba(22, 35, 61, 0.08);
  }

  /* Page title reads as a native app's large-title header - bolder and
     tighter than the shared desktop size, consistent across every view
     rather than each page picking its own. */
  .view-header h2 { font-size: 20px; letter-spacing: -0.01em; }

  /* Every view fades/slides in on entry instead of popping in instantly -
     small, but it's the difference a native app's own screen transitions
     make versus a plain document swap. prefers-reduced-motion is
     respected globally near the top of this file. */
  .view:not(.hidden) { animation: view-enter 0.22s ease; }
  @keyframes view-enter {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
  }

  /* Comfortable tap targets (~40px) for the controls people actually tap
     most on a phone - stage moves, nav, row-level actions. */
  .stage-actions button,
  .nav-btn,
  .task-row button,
  .employee-row button {
    min-height: 40px;
    padding-top: 10px;
    padding-bottom: 10px;
  }

  /* Task review (Approve/Reject/Send Back) and merchant approval
     (Approve/Reject) button rows are sized with an inline width:auto for
     their compact desktop look - stack them full-width on mobile instead so
     each is a full-width tap target rather than a cramped inline row. */
  #review-actions .primary-btn,
  #review-actions button,
  #approval-actions .primary-btn,
  #approval-actions button,
  #task-actions .primary-btn,
  #mark-signed-btn {
    width: 100% !important;
    min-height: 40px;
  }
  #review-actions button,
  #approval-actions button {
    margin-left: 0 !important;
    margin-top: 8px;
  }
  #review-actions button:first-child,
  #approval-actions button:first-child {
    margin-top: 0;
  }
}

/* On a real phone width (as opposed to a shrunk desktop window at exactly
   640px), the topbar's first row - logo, bell, name, role badge, logout -
   gets tight enough that a longer name (e.g. "Shubham") can push the logout
   button into a second, awkward-looking line. Dropping the name and
   keeping just the role badge (still identifies who's logged in well
   enough) reliably buys back the room. */
@media (max-width: 480px) {
  #current-user-name { display: none; }
  .user-chip { gap: 6px; }
}

/* Below this, even the shortened label column crowds long stage names
   ("Go Live / Closed Won") - stack label+value above the bar instead. */
@media (max-width: 420px) {
  .bar-row {
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto;
    row-gap: 4px;
  }
  .bar-row-label { grid-column: 1; grid-row: 1; text-align: left; }
  .bar-row-value { grid-column: 2; grid-row: 1; text-align: right; }
  .bar-track { grid-column: 1 / -1; grid-row: 2; }
}

/* ---------- Calling (Telnyx) - softphone widget ---------- */
/* The floating widget opens directly for dialing/incoming/active calls (see
   calls.js) and auto-closes when a call ends - there's no persistent
   launcher button. The Calls module's own Dial Pad tab (one nav tap away)
   is the entry point for starting a call. */

/* Gmail-style mobile Compose FAB - hidden on desktop (which keeps its
   existing Compose button in the mail toolbar) and hidden above the mobile
   breakpoint's own visibility rule too by default; the 640px block turns it
   on and positions it to clear the bottom tab bar. */
.mail-compose-fab {
  display: none;
  position: fixed;
  right: 24px;
  bottom: 92px;
  z-index: 89;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--horizon);
  color: white;
  border: none;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
  transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}
/* Darken the same green rather than switching to navy on hover/press - a
   full color swap read as jarring, and this now matches the desktop
   Compose pill's own hover shade (.mail-compose-btn) for one consistent
   "Compose" identity across both layouts. */
.mail-compose-fab:hover { background: var(--horizon-dark); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); transform: translateY(-1px); }
.mail-compose-fab:active { transform: translateY(0) scale(0.94); box-shadow: 0 3px 12px rgba(0, 0, 0, 0.25); }

/* z-index: 1001, not the 90 this used to be - every generic overlay in the
   app (.modal is 100, .mail-lightbox-overlay is 200, even the transient
   toasts hit 1000) sat ABOVE this widget, so opening literally anything -
   a merchant record, an edit modal, an image preview - while a call was
   active fully buried the widget behind that overlay's own backdrop: still
   in the DOM and still "on", just visually gone and unclickable. An active
   call has to stay reachable no matter what else is open, the same way a
   real phone/OS call screen floats above every other app - so this is
   deliberately the single highest z-index in the stylesheet. */
.call-widget {
  position: fixed;
  right: 24px;
  bottom: 88px;
  z-index: 1001;
  width: 300px;
  max-width: calc(100vw - 32px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.2);
  overflow: hidden;
}
.call-widget-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--navy);
  color: white;
  font-weight: 700;
  font-size: 13px;
}
.call-widget-close {
  background: none;
  border: none;
  color: white;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
}
.call-widget-body { padding: 14px; }

/* Minimized - header-only bar, same convention as the Mail compose
   window's own .compose-minimized (see its CSS comment): the body just
   goes display:none, nothing in it is unmounted or reset, so an in-
   progress call (mute state, live timer, held state) is exactly as it was
   the instant it's restored. Status text stays live in the header the
   whole time (renderActiveCallUI/flashCallEnd update #call-widget-status
   regardless of this class), so there's still a real, at-a-glance answer
   to "what's happening with my call" even collapsed - not just a mystery
   bar. cursor:pointer signals the header itself is clickable to restore
   (see the click listener in calls.js), matching Compose's own header-
   click-to-restore behavior. */
.call-widget.call-widget-minimized { width: auto; min-width: 220px; }
.call-widget.call-widget-minimized .call-widget-body { display: none; }
.call-widget.call-widget-minimized .call-widget-header { cursor: pointer; }

/* AI Assistant - same fixed-widget convention as .call-widget above, offset
   further right so an active call and an open conversation never overlap. */
.ai-assistant-launcher {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 91;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--horizon);
  color: white;
  border: none;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.ai-assistant-launcher:hover { background: var(--horizon-dark); }
.ai-assistant-launcher.hidden { display: none; }

.ai-assistant-widget {
  position: fixed;
  right: 24px;
  bottom: 88px;
  z-index: 91;
  width: 380px;
  max-width: calc(100vw - 32px);
  height: 520px;
  max-height: calc(100vh - 120px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.2);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ai-assistant-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--navy);
  color: white;
  font-weight: 700;
  font-size: 13px;
  flex-shrink: 0;
}
.ai-assistant-header-actions { display: flex; gap: 4px; }
.ai-assistant-icon-btn {
  background: none;
  border: none;
  color: white;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 7px;
  border-radius: 4px;
}
.ai-assistant-icon-btn:hover { background: rgba(255, 255, 255, 0.15); }

.ai-assistant-messages {
  flex: 1;
  overflow-y: auto;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  font-size: 13.5px;
}
.ai-msg { max-width: 88%; line-height: 1.45; white-space: pre-wrap; word-wrap: break-word; }
/* color: var(--navy), not var(--text) - same fixed-light-background/
   theme-adaptive-text mismatch as .stage-pill below (see its own comment) -
   confirmed live: the employee's own chat bubbles were unreadable in dark
   mode. */
.ai-msg-user {
  align-self: flex-end;
  background: var(--horizon-soft);
  color: var(--navy);
  padding: 8px 12px;
  border-radius: 12px 12px 2px 12px;
}
.ai-msg-assistant {
  align-self: flex-start;
  background: var(--cream);
  color: var(--text);
  padding: 8px 12px;
  border-radius: 12px 12px 12px 2px;
  border: 1px solid var(--border);
}
.ai-msg-error { color: var(--danger); }
.ai-msg-empty-hint { color: var(--slate-light); text-align: center; margin-top: 30px; font-size: 13px; }

.ai-action-card {
  align-self: flex-start;
  max-width: 92%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  font-size: 13px;
}
.ai-action-card-title { font-weight: 700; color: var(--text); margin-bottom: 4px; }
.ai-action-card-body { color: var(--slate); margin-bottom: 8px; white-space: pre-wrap; }
.ai-action-card-actions { display: flex; gap: 8px; }
.ai-action-card-actions button {
  font-size: 12.5px;
  padding: 5px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  cursor: pointer;
  background: var(--surface);
}
.ai-action-confirm-btn { background: var(--horizon) !important; color: white !important; border-color: var(--horizon) !important; }
.ai-action-cancel-btn { color: var(--slate); }
.ai-action-card-status { font-size: 12px; color: var(--slate-light); font-style: italic; }

.ai-assistant-input-row {
  display: flex;
  gap: 8px;
  padding: 10px;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}
.ai-assistant-input-row input {
  flex: 1;
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 8px 14px;
  font-size: 13.5px;
  outline: none;
}
.ai-assistant-input-row input:focus { border-color: var(--horizon); }
.ai-assistant-input-row button {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: var(--horizon);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}
.ai-assistant-input-row button:disabled { background: var(--slate-light); cursor: not-allowed; }

.ai-typing-dots { display: inline-flex; gap: 3px; align-items: center; padding: 2px 0; }
.ai-typing-dots span {
  width: 6px; height: 6px; border-radius: 50%; background: var(--slate-light);
  animation: ai-typing-bounce 1.2s infinite ease-in-out;
}
.ai-typing-dots span:nth-child(2) { animation-delay: 0.15s; }
.ai-typing-dots span:nth-child(3) { animation-delay: 0.3s; }
@keyframes ai-typing-bounce { 0%, 60%, 100% { opacity: 0.35; } 30% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .ai-typing-dots span { animation: none; }
}

@media (max-width: 720px) {
  .ai-assistant-widget {
    right: 16px;
    bottom: calc(56px + 16px + env(safe-area-inset-bottom, 0));
    width: calc(100vw - 32px);
    height: calc(100vh - 180px);
  }
  .ai-assistant-launcher {
    right: 16px;
    /* Stacked above the bottom tab bar's own FAB row (.mail-compose-fab /
       .call-widget, both anchored at 56px tab bar + 16px gap) rather than
       sharing that exact spot - the two were previously positioned
       identically and the AI launcher silently covered Compose Mail on
       every mobile screen. +52px button height +12px gap clears it. */
    bottom: calc(56px + 16px + 52px + 12px + env(safe-area-inset-bottom, 0));
  }
}

/* ---------- Gmail-style floating Compose window ---------- */
/* Anchored bottom-RIGHT - matches real Gmail's own compose/reply pop-out
   position exactly (per explicit user request; this used to sit bottom-
   LEFT specifically to avoid the AI Assistant launcher/.call-widget, which
   also live bottom-right - now that Compose claims that same corner, the
   AI launcher is hidden instead for as long as any compose window state is
   open, see updateAiLauncherVisibility() in app.js, the same mechanism the
   AI widget's own panel already uses to hide the launcher while IT'S open).
   Flush to the viewport's bottom edge (bottom:0, only rounded on top) for
   the same reason a real Gmail compose window sits flush against the
   browser chrome - it reads as "docked", not "floating in space". z-index
   92 (above the AI widget's 91) means Compose always wins any leftover
   overlap regardless.
   Three states via a class on this same element: default (normal) is the
   non-blocking floating card; .compose-minimized collapses the body,
   leaving just the header (draft stays fully intact underneath - the form
   is hidden via CSS, never unmounted, so nothing is lost); .compose-
   maximized grows to a large, backdrop-covered focused editor for
   "pop out to full screen". Normal/minimized deliberately have NO backdrop
   at all (not even a transparent blocking one) - the inbox and whatever
   thread is open behind them must stay fully clickable, per the explicit
   "read the original email while composing a reply" requirement. */
.mail-compose-window {
  position: fixed;
  right: 24px;
  bottom: 0;
  z-index: 92;
  width: 500px;
  max-width: calc(100vw - 32px);
  height: 540px;
  max-height: calc(100vh - 48px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 10px 10px 0 0;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.22);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.mail-compose-window.hidden { display: none; }

.mail-compose-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--navy);
  color: white;
  font-weight: 700;
  font-size: 13px;
  flex-shrink: 0;
  cursor: pointer;
  user-select: none;
}
.mail-compose-header-actions { display: flex; gap: 2px; }
.mail-compose-icon-btn {
  background: none;
  border: none;
  color: white;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 7px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mail-compose-icon-btn:hover { background: rgba(255, 255, 255, 0.15); }
/* #compose-thread-toggle-btn's pressed/open state - the icon itself never
   changes (unlike the dock-toggle button's swap-the-SVG approach), just a
   persistent highlight so it's clear the panel is currently showing. */
.mail-compose-icon-btn.active { background: rgba(255, 255, 255, 0.25); }
/* color: var(--navy), not inherited - same fixed-bg/adaptive-text mismatch
   as .notif-bell-btn:hover above: docked-inline recolors this icon to
   var(--slate-light) (see the docked-inline .mail-compose-icon-btn rule
   below), which turns light in dark mode and nearly disappears against
   this fixed light --horizon-soft active background otherwise. This is
   #compose-thread-toggle-btn's pressed state when the compose window is
   docked inline (its normal position on mobile). */
.mail-compose-window.mail-compose-docked-inline .mail-compose-icon-btn.active { background: var(--horizon-soft); color: var(--navy); }

.mail-compose-body-wrap {
  flex: 1;
  overflow-y: auto;
  /* Explicit, not left to default - setting only overflow-y otherwise
     computes overflow-x to 'auto' too (the CSS spec's own rule for mixed
     overflow axes), which is exactly what silently turned the recipient-
     row bug below into a horizontal scrollbar INSIDE this box instead of
     a visibly broken layout. The real fix is making every row inside
     actually fit (see .compose-field-row and the mobile block below) -
     this is a backstop for anything future/unforeseen, not the fix itself. */
  overflow-x: hidden;
  padding: 14px;
  min-height: 0;
}
.mail-compose-body-wrap #compose-form { display: flex; flex-direction: column; height: 100%; }
/* Less tall than the base rule's 200px/400px - this floating window is
   itself only ~540px, most of which To/Cc/Bcc/Subject/toolbar/Send already
   claim, so the base min-height would force scrolling on the window
   itself before the body field even filled up. */
.mail-compose-window .compose-body-editable { min-height: 110px; max-height: none; flex: 1; }
/* Rounded pill (matches the reference screenshot's Send button shape) and
   auto width instead of the generic .primary-btn's full-width block - this
   is the one button in the form that shouldn't stretch edge to edge. */
.mail-compose-send-btn { width: auto; border-radius: var(--radius-pill); padding: 10px 24px; margin-top: 12px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
.mail-compose-send-btn:disabled { opacity: 0.75; cursor: default; }
/* Sending state (see setComposeSendingUi/the compose-form submit handler) -
   the spinner is always in the DOM (avoids layout jump from inserting/
   removing a node) and just stays 0-width/hidden until .sending is set. */
.compose-send-spinner { display: none; }
.mail-compose-send-btn.sending .compose-send-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid rgba(255, 255, 255, 0.4);
  border-top-color: white;
  border-radius: 50%;
  animation: mail-refresh-spin 0.7s linear infinite;
}

/* Save as Draft / Discard / Cancel confirmation (see closeComposeWindow) */
.compose-close-confirm-actions { display: flex; flex-direction: column; gap: 8px; }
.compose-close-confirm-actions .primary-btn,
.compose-close-confirm-actions .secondary-btn { width: 100%; margin: 0; }
.compose-close-discard-btn { color: var(--danger); border-color: var(--danger); }
.compose-close-discard-btn:hover { background: var(--danger); color: white; }

/* Brief "Email sent" confirmation (see the compose-form submit handler) -
   same bottom-right corner Compose itself anchors to, so it reads as
   "that's what just closed" rather than appearing somewhere unrelated.
   Fades/slides in, holds, then the JS removes it after ~3s - no follow-up
   click needed to dismiss it. */
.mail-sent-toast {
  position: fixed;
  right: 24px;
  bottom: 24px;
  background: var(--navy);
  color: white;
  padding: 12px 20px;
  border-radius: var(--radius);
  font-size: 13.5px;
  font-weight: 600;
  box-shadow: 0 6px 20px rgba(22, 35, 61, 0.35);
  z-index: 93;
  display: flex;
  align-items: center;
  gap: 8px;
  animation: mail-sent-toast-in 0.2s ease;
}
.mail-sent-toast.hidden { display: none; }
@keyframes mail-sent-toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
@media (max-width: 640px) {
  .mail-sent-toast { left: 16px; right: 16px; bottom: calc(72px + env(safe-area-inset-bottom, 0)); }
}

/* Minimized - header-only bar, draft untouched underneath (the form is
   display:none, never removed from the DOM/re-rendered). Click the header
   itself (not just the button) to restore, same as a real Gmail minimized
   compose bar. */
.mail-compose-window.compose-minimized {
  width: 280px;
  height: auto;
}
.mail-compose-window.compose-minimized .mail-compose-body-wrap { display: none; }

/* Maximized ("full screen", compose-maximize-btn) - large centered focused
   editor, the one state that DOES get a backdrop (see .mail-compose-
   backdrop) since it's meant to be the deliberately focused mode, unlike
   normal/minimized which must stay non-blocking. Not to be confused with
   the separate inline/floating "Pop-out"/"Pop-in" dock toggle just below -
   that one only exists for Reply/Reply All and moves the window between
   living under a message card vs. this same floating corner card; this
   maximize state is orthogonal and available in both. */
.mail-compose-window.compose-maximized {
  left: 6vw;
  right: 6vw;
  bottom: 6vh;
  top: 6vh;
  width: auto;
  height: auto;
  max-width: none;
  max-height: none;
  border-radius: var(--radius);
  border-bottom: 1px solid var(--border);
}
.mail-compose-backdrop {
  position: fixed;
  inset: 0;
  z-index: 91;
  background: rgba(22, 35, 61, 0.5);
  display: none;
}
.mail-compose-backdrop.visible { display: block; }

/* Gmail-style inline reply, docked directly under the message card being
   replied to (see applyComposeDockMode/ensureInlineComposeSlot in app.js) -
   the SAME #mail-compose-window/#compose-form used for New/Forward and the
   floating Reply, just reparented and restyled to sit in normal document
   flow instead of position:fixed floating in the corner. Everything not
   overridden here (header, toolbar, attachments, minimize collapsing the
   body) is inherited unchanged from the base .mail-compose-window rules
   above. */
.mail-compose-window.mail-compose-docked-inline {
  position: static;
  left: auto; right: auto; top: auto; bottom: auto;
  width: 100%;
  max-width: 100%;
  height: auto;
  max-height: none;
  border-radius: var(--radius);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08);
}
/* Only the body area scrolls internally, capped well short of the full
   viewport - a reply to a long-running thread shouldn't force the compose
   box to grow past what's comfortably visible alongside the messages
   above it. */
.mail-compose-window.mail-compose-docked-inline .mail-compose-body-wrap { max-height: 50vh; }
/* Minimizing an inline reply collapses it to just its header, same as
   floating, but must stay the slot's full width rather than snapping to
   the floating window's fixed 280px minimized width. */
.mail-compose-window.mail-compose-docked-inline.compose-minimized { width: 100%; height: auto; }
.mail-inline-compose-slot { display: block; }
/* Matches the reference Gmail inline-reply screenshot: no dark title bar at
   all when docked inline - just the recipient avatar (see .compose-avatar
   above) and the Pop-out icon, right-aligned in what's left of the header
   once its title/minimize/maximize/close are hidden. Floating mode is
   completely untouched (still the normal navy bar with all four controls -
   that's Gmail's own floating "New Message" window chrome, which already
   looks right as-is). */
.mail-compose-window.mail-compose-docked-inline .mail-compose-header {
  background: transparent;
  color: var(--slate);
  padding: 6px 6px 0;
  justify-content: flex-end;
}
.mail-compose-window.mail-compose-docked-inline #compose-modal-title,
.mail-compose-window.mail-compose-docked-inline #compose-minimize-btn,
.mail-compose-window.mail-compose-docked-inline #compose-maximize-btn,
.mail-compose-window.mail-compose-docked-inline #compose-close-btn {
  display: none;
}
.mail-compose-window.mail-compose-docked-inline .mail-compose-icon-btn { color: var(--slate-light); }
.mail-compose-window.mail-compose-docked-inline .mail-compose-icon-btn:hover { background: var(--cream); color: var(--text); }
.mail-compose-window.mail-compose-docked-inline .compose-avatar { display: flex; }

/* Inline reply/forward, spaced out - this used to reuse the floating
   window's cramped chrome verbatim (8px gaps, a 30px label, 13px type)
   even though docked-inline has the thread pane's full width to work
   with, not a fixed 500px floating card. Only label width/font, row
   height/padding, block gaps and the editor's starting height change
   here - none of the values the mobile overflow-fix comment above
   documents tuning (recipient-typing min-width, the Cc/Bcc toggle, chip
   text max-width) are touched, so that fix is unaffected on narrow
   screens; a wider label/taller row just sits on its own wrapped line
   there exactly as before. */
.mail-compose-window.mail-compose-docked-inline .mail-compose-body-wrap { padding: 18px 20px; }
.mail-compose-window.mail-compose-docked-inline .compose-avatar { width: 38px; height: 38px; font-size: 14px; }
.mail-compose-window.mail-compose-docked-inline .compose-recipient-block { gap: 14px; margin-bottom: 6px; }
.mail-compose-window.mail-compose-docked-inline .compose-recipient-fields { gap: 8px; }
.mail-compose-window.mail-compose-docked-inline .compose-field-label { width: 42px; font-size: 13.5px; }
.mail-compose-window.mail-compose-docked-inline .compose-field-row .recipient-field { min-height: 40px; padding: 7px 0; }
.mail-compose-window.mail-compose-docked-inline .recipient-field .recipient-typing { font-size: 14.5px; }
.mail-compose-window.mail-compose-docked-inline .compose-subject-row,
.mail-compose-window.mail-compose-docked-inline .compose-template-row { margin: 12px 0; }
.mail-compose-window.mail-compose-docked-inline .compose-subject-row input,
.mail-compose-window.mail-compose-docked-inline .compose-template-row select { font-size: 14.5px; padding: 6px 0; }
.mail-compose-window.mail-compose-docked-inline .compose-body-editable { min-height: 180px; }
.mail-compose-window.mail-compose-docked-inline .compose-reply-context { font-size: 13px; padding: 8px 12px; }

@media (max-width: 640px) {
  /* No room for a small floating card on a phone - normal/maximized both
     take over the full screen (the standard mobile "compose sheet"
     pattern, same as Gmail's own mobile app), so the pop-out button is
     mostly a no-op on mobile and that's fine/expected. Minimized stays a
     small bar, but inset from the right edge to clear the AI Assistant
     launcher's own spot (52px circle + 16px gap + 16px margin = 84px)
     rather than reaching edge-to-edge, so the two never overlap. */
  .mail-compose-window {
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;
    width: 100%;
    height: 100%;
    max-width: 100%;
    max-height: 100%;
    border-radius: 0;
    border: none;
    z-index: 95;
  }
  .mail-compose-window.compose-minimized {
    left: 16px;
    right: 84px;
    top: auto;
    bottom: calc(56px + 16px + env(safe-area-inset-bottom, 0));
    width: auto;
    height: auto;
    border-radius: var(--radius);
    border: 1px solid var(--border);
  }
  .mail-compose-window.compose-maximized { left: 0; right: 0; bottom: 0; top: 0; border-radius: 0; }
  .mail-compose-backdrop.visible { display: none; } /* full-screen already covers everything - a backdrop under it is invisible anyway */
  /* Same "sender + date is enough on a phone-width row" tradeoff as the
     main thread accordion's own collapsed cards (.mail-thread-card-snippet
     above) - keeps each collapsed row to one line instead of wrapping. */
  .compose-thread-msg-snippet { display: none; }
  .compose-thread-msg-sender { max-width: 45vw; }
  .compose-thread-preview { max-height: 45vh; }
  .compose-thread-msg-body { padding-left: 10px; } /* the desktop 40px avatar-aligned indent eats too much width on a phone */
}

.call-active-panel, .call-incoming-panel { text-align: center; }
.call-active-merchant { font-weight: 700; color: var(--text); font-size: 15px; }
.call-active-number { color: var(--slate); font-size: 13px; margin-top: 2px; }
.call-active-state { color: var(--slate-light); font-size: 13px; margin-top: 8px; font-variant-numeric: tabular-nums; }
.call-incoming-actions {
  display: flex;
  gap: 8px;
  margin-top: 14px;
}
.call-incoming-actions button { flex: 1; width: auto; margin: 0; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.call-hangup-btn { color: var(--danger) !important; border-color: var(--danger) !important; }
.call-hangup-btn:hover { background: var(--danger) !important; color: white !important; }
#call-view-merchant-btn { margin-top: 10px; }

/* In-call control grid (Mute/Hold/Speaker/Keypad/Transfer) - icon-over-
   label 5-up grid, closer to how a native phone app's in-call screen lays
   these out than a row of text buttons would be. */
.call-active-controls-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
  margin-top: 16px;
}
.call-control-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--slate);
  cursor: pointer;
}
/* color: var(--navy) - same fixed-bg/adaptive-text mismatch as
   .notif-bell-btn:hover above (see its comment); .active below already
   sets its own explicit color so it's unaffected. */
.call-control-btn:hover { background: var(--horizon-soft); color: var(--navy); }
.call-control-btn.active { background: var(--navy); border-color: var(--navy); color: white; }

.call-hangup-btn-full {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: 14px;
  padding: 12px;
  border: none;
  border-radius: var(--radius);
  background: var(--danger);
  color: white;
  font-weight: 700;
  cursor: pointer;
}
.call-hangup-btn-full:hover { background: var(--danger-strong); }

.call-dtmf-panel, .call-transfer-panel {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  text-align: left;
}
.call-dtmf-panel { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.call-dtmf-btn {
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  cursor: pointer;
}
.call-dtmf-btn:hover { background: var(--horizon-soft); color: var(--navy); }

.call-transfer-input { width: 100%; }
.call-transfer-agents-label { font-size: 11px; font-weight: 700; color: var(--slate-light); text-transform: uppercase; letter-spacing: 0.03em; margin: 10px 0 4px; }
.call-transfer-agent-btn {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  margin-bottom: 6px;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
}
.call-transfer-agent-btn:hover { background: var(--cream); }

/* ---------- Calls module (#view-calls) ---------- */
/* Tight top nav - the module has no page title/header any more (see
   index.html), so this is the very first thing on the screen and is
   deliberately compact: a single-line search/dial field plus a scrollable
   row of 4 tabs, not the airier 2-row toolbar other modules use. */
.calls-toolbar { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.calls-toolbar .mail-search-wrap { min-width: 0; }
.calls-toolbar .mail-read-filter {
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  gap: 6px;
}
.calls-toolbar .mail-read-filter::-webkit-scrollbar { display: none; }
.calls-toolbar .mail-filter-chip { flex-shrink: 0; padding: 5px 12px; font-size: 12px; }

.calls-log-list {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.call-row-highlight { background: var(--horizon-soft) !important; transition: background 0.3s ease; }
/* Fixed dark colors for the row's own text while highlighted - same
   fixed-bg/adaptive-text mismatch as .notif-bell-btn:hover above (see its
   comment): this row's sender/date/subject/parties text has no
   background of its own, so it sits directly on the fixed light
   highlight background above. Each value is that class's own light-mode
   color (#4b5568 = --slate, #8992a3 = --slate-light), so light mode is
   unchanged. */
.call-row-highlight .mail-row-sender { color: var(--navy); }
.call-row-highlight .mail-row-top { color: #8992a3; }
.call-row-highlight .mail-row-subject,
.call-row-highlight .call-log-parties { color: #4b5568; }
.call-row-highlight .call-log-secondary { color: #8992a3; }
.call-hangup-cause { color: var(--danger); font-style: italic; }

.call-status-badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 700;
  background: var(--cream);
  color: var(--slate);
}
.call-status-incoming { background: var(--tone-success-bg); color: var(--tone-success-fg); }
.call-status-outgoing { background: var(--horizon-soft); color: var(--navy); }
.call-status-missed { background: var(--tone-danger-bg); color: var(--tone-danger-fg); }

/* Bold red caller name/number, same as a native Phone app's missed-call
   treatment - the badge alone doesn't "stand out immediately" the way the
   row's own primary text does. */
.call-row-missed .mail-row-sender { color: var(--danger); font-weight: 700; }

/* Contacts tab rows - same .mail-message-row shell every other Calls/Mail
   row uses (border, hover, tap-feedback all come free), just laid out as a
   horizontal flex row (avatar + name/number + call button) instead of the
   stacked block the base row assumes. */
.call-contact-row { display: flex; align-items: center; gap: 10px; }
.call-contact-row .mail-row-content { flex: 1; min-width: 0; padding-left: 0; }
/* color: var(--navy), not var(--text) - same fixed-light-background/
   theme-adaptive-text mismatch as .stage-pill below (see its own comment). */
.call-contact-avatar {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  margin-left: 12px;
  border-radius: 50%;
  background: var(--horizon-soft);
  color: var(--navy);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
}
.call-contact-call-btn {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  margin-right: 12px;
  border-radius: 50%;
  border: none;
  background: var(--status-good);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.call-contact-call-btn:hover { background: #2f9e2f; }

/* Call log rows only (not Contacts, which keeps its plain letter avatar) -
   same circle metrics as call-contact-avatar, colored per outcome using
   the exact same 3 color pairs call-status-badge already established, so
   the row's avatar and its own status badge always agree at a glance. */
.call-log-avatar {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  margin-left: 12px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.call-log-avatar-incoming { background: var(--tone-success-bg); color: var(--tone-success-fg); }
.call-log-avatar-outgoing { background: var(--horizon-soft); color: var(--navy); }
.call-log-avatar-missed { background: var(--tone-danger-bg); color: var(--tone-danger-fg); }

/* Explicit "who called, who received" line - one step quieter than the
   status-badge line so it doesn't compete with it, but a full weight above
   call-log-secondary's failure-reason/rung-agent detail, since naming both
   parties is core information for every row, not an occasional footnote. */
.call-log-parties { font-size: 12.5px; color: var(--slate); margin-top: 2px; overflow-wrap: anywhere; }
.call-parties-arrow { color: var(--slate-light); margin: 0 2px; }

/* Secondary meta line (rung-but-didn't-answer agents, why it failed) -
   deliberately quieter than call-log-parties above so a routine call stays
   short while a missed call with a full rung-agent list still has
   somewhere to go without crowding the rest of the row. */
.call-log-secondary { font-size: 12px; color: var(--slate-light); margin-top: 2px; overflow-wrap: anywhere; }

/* Paired with call-contact-call-btn (same size, same circle) but outlined
   rather than filled - a secondary action (listen) next to the primary one
   (call), not a plain text link buried in the meta line. */
.call-recording-btn {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--horizon);
  display: flex;
  align-items: center;
  justify-content: center;
}
.call-recording-btn:hover { background: var(--horizon-soft); }

/* ---------- Dial Pad tab (inline - see calls.js's dialPadHtml) ---------- */
/* The default tab, not a modal/sheet - kept compact and centered rather than
   stretching to fill the available width, so it reads as one deliberately-
   sized native control instead of an oversized one on a wide viewport. */
.dialpad-panel { display: flex; flex-direction: column; align-items: center; padding-top: 4px; }

/* This is the same input callsToolbarHtml() renders in place of the search
   box while the Dial Pad tab is active (see .call-dial-input-wrap there) -
   compact single-line height matching the search bar it replaces, not the
   oversized standalone number display earlier drafts of this module used. */
#call-dial-input {
  width: 100%;
  font-size: 17px;
  font-weight: 500;
  text-align: center;
  letter-spacing: 0.02em;
}
.mail-search-wrap.call-dial-input-wrap input { padding-left: 12px; }

.call-dial-search-results { width: 100%; }
.call-dial-search-results:not(:empty) {
  max-height: 140px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin: 6px 0;
}
.call-dial-result {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: var(--surface);
  border: none;
  border-bottom: 1px solid var(--border);
  padding: 8px 10px;
  cursor: pointer;
  text-align: left;
}
.call-dial-result:last-child { border-bottom: none; }
.call-dial-result:hover { background: var(--cream); }
.call-dial-result-name { font-size: 13px; font-weight: 600; color: var(--text); }
.call-dial-result-number { font-size: 12px; color: var(--slate-light); flex-shrink: 0; }

/* Fixed button size instead of stretching to fill the row (the old
   aspect-ratio/1fr grid) - a compact, centered keypad reads as one
   deliberately-sized native control on any screen width, rather than
   ballooning into oversized circles on a wide viewport. */
.call-keypad {
  display: grid;
  grid-template-columns: repeat(3, 54px);
  justify-content: center;
  gap: 10px 26px;
  margin-top: 14px;
}
.call-keypad-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  width: 54px;
  height: 54px;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: 50%;
  cursor: pointer;
  /* Holding the 0 key for + (see calls.js) shouldn't trigger iOS's native
     text-selection/callout bubble or Android's press-and-hold context menu
     - neither is meaningful on a button with no text to select anyway, but
     without this they can visually interrupt the hold. */
  -webkit-touch-callout: none;
  user-select: none;
}
.call-keypad-btn:hover { background: var(--horizon-soft); }
.call-keypad-btn:active { transform: scale(0.96); }
/* Same treatment as :hover - visual confirmation that a long-press has
   registered, held for as long as the press itself (see calls.js), not
   just a momentary flash like :active gives on a normal tap. */
.call-keypad-btn.long-press-active { background: var(--horizon-soft); }
/* var(--navy) on hover/long-press, not the default var(--text)/
   var(--slate-light) below - same fixed-bg/adaptive-text mismatch as
   .notif-bell-btn:hover above (see its comment): this fixed light
   --horizon-soft background pairs with the default (theme-adaptive)
   digit/letters colors otherwise, going nearly invisible in dark mode. */
.call-keypad-btn:hover .digit, .call-keypad-btn.long-press-active .digit,
.call-keypad-btn:hover .letters, .call-keypad-btn.long-press-active .letters { color: var(--navy); }
.call-keypad-btn .digit { font-size: 18px; font-weight: 700; color: var(--text); line-height: 1; }
.call-keypad-btn .letters { font-size: 8px; font-weight: 600; color: var(--slate-light); letter-spacing: 0.06em; }

.call-dial-actions {
  display: grid;
  grid-template-columns: 54px 54px 54px;
  justify-content: center;
  gap: 26px;
  align-items: center;
  margin-top: 14px;
}
.call-dial-call-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: var(--status-good);
  border: none;
  color: white;
  cursor: pointer;
}
.call-dial-call-btn:hover { background: #2f9e2f; }
.call-dial-backspace-btn {
  justify-self: center;
  background: none;
  border: none;
  font-size: 20px;
  color: var(--slate);
  cursor: pointer;
  padding: 8px;
}
.call-dial-backspace-btn:hover { color: var(--text); }

/* Merchant modal phone fields - a small call icon sits beside the input
   rather than replacing it, so the field stays directly editable. */
.phone-field-row { display: flex; align-items: center; gap: 6px; }
.phone-field-row input { flex: 1; min-width: 0; }
.call-field-btn {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--horizon);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.call-field-btn:hover { background: var(--horizon-soft); }
/* WhatsApp button - brand green, sits next to the Call button wherever a
   Lead/contact number is shown. Opens the CRM's own WhatsApp module (internal
   navigation), not an external link. */
.call-field-btn--wa { color: #25d366; }
.call-field-btn--wa:hover { background: rgba(37, 211, 102, 0.14); color: #1da851; }

@media (max-width: 640px) {
  /* Floats above the bottom tab bar (56px tall + its safe-area inset)
     instead of the screen edge, so it doesn't overlap it. */
  .call-widget { right: 16px; bottom: calc(56px + 16px + env(safe-area-inset-bottom, 0)); width: calc(100vw - 32px); }
  .mail-compose-fab { display: flex; right: 16px; bottom: calc(56px + 16px + env(safe-area-inset-bottom, 0)); }

  /* No page title above the toolbar on mobile either (see index.html) -
     .calls-toolbar's own compact base styling already carries the module,
     nothing extra needed at this breakpoint beyond a touch more chip
     padding for comfortable tap targets. */
  .calls-toolbar .mail-filter-chip { padding: 6px 12px; }

  /* Slightly denser than the base (already-compact) sizing, to keep the
     keypad and its digits/labels comfortably clear of the bottom tab bar
     on shorter phone screens without any scrolling. */
  .call-keypad { grid-template-columns: repeat(3, 50px); gap: 8px 22px; }
  .call-keypad-btn { width: 50px; height: 50px; }
  .call-dial-actions { grid-template-columns: 50px 50px 50px; gap: 22px; }
  .call-dial-call-btn { width: 50px; height: 50px; }
}

/* ---------- Internal Chat (frontend/js/chat.js) ----------
   Two-pane layout (list + thread), simpler than Mail's three-pane folders/
   list/detail split since chat has no folder concept - a combined
   Chats/Directory tabbed list plays that role instead. Same breakpoint
   (900px) and data-mobile-pane + inline-style belt-and-suspenders approach
   as .mail-layout/setMailMobilePane, reused deliberately rather than
   reinvented - see setMailMobilePane's own three-bugfix history. */
.chat-layout {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 14px;
  height: calc(100vh - 190px);
  min-height: 420px;
}
.chat-list-pane, .chat-thread-pane {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* #chat-thread-content is the innerHTML target inside .chat-thread-pane
   (see chat.js's renderChatThread) - without its own flex rules here,
   .chat-messages{flex:1} below has nothing to size against, so the
   composer only stayed pinned to the bottom by accident of content length
   rather than reliably, WhatsApp-style, on every screen size. */
#chat-thread-content { display: flex; flex-direction: column; height: 100%; }
.chat-list-search-row { padding: 10px; border-bottom: 1px solid var(--border); }
.chat-list-tabs { display: flex; border-bottom: 1px solid var(--border); }
.chat-list-tab {
  flex: 1; padding: 10px; background: none; border: none; cursor: pointer;
  font-size: 13px; font-weight: 600; color: var(--slate-light);
  border-bottom: 2px solid transparent;
}
.chat-list-tab.active { color: var(--navy-light); border-bottom-color: var(--navy-light); }
.chat-list-body { flex: 1; overflow-y: auto; overscroll-behavior-y: contain; }
.chat-empty-hint { color: var(--slate-light); font-size: 13px; padding: 16px; text-align: center; }

.chat-list-item {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  cursor: pointer; border-bottom: 1px solid var(--border);
}
.chat-list-item:hover { background: var(--cream); }
.chat-list-item.active { background: var(--unread-bg); }
.chat-list-item-main { flex: 1; min-width: 0; }
.chat-list-item-name { font-weight: 700; font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-list-item-sub { font-size: 12px; color: var(--slate-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-list-item-presence { font-size: 11px; color: var(--slate-light); }
.chat-unread-badge {
  background: var(--horizon); color: #fff; border-radius: 999px;
  font-size: 11px; font-weight: 700; min-width: 18px; height: 18px;
  display: flex; align-items: center; justify-content: center; padding: 0 5px;
}

.chat-avatar {
  position: relative; width: 38px; height: 38px; border-radius: 50%;
  background: var(--navy-light); color: #fff; display: flex; align-items: center;
  justify-content: center; font-size: 13px; font-weight: 700; flex-shrink: 0;
}
.chat-presence-dot {
  position: absolute; right: -1px; bottom: -1px; width: 11px; height: 11px;
  border-radius: 50%; border: 2px solid var(--surface);
}
.chat-presence-dot.online { background: #2eb872; }
.chat-presence-dot.away { background: #e0a72a; }
.chat-presence-dot.offline { background: #9aa1ac; }

.chat-thread-header {
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}
.chat-back-btn { display: none; background: none; border: none; font-size: 24px; cursor: pointer; color: var(--text); padding: 0 4px; }
.chat-thread-header-info { flex: 1; min-width: 0; }
.chat-thread-name { font-weight: 700; font-size: 14px; }
.chat-thread-status { font-size: 12px; color: var(--slate-light); }
.chat-icon-btn { background: none; border: 1px solid var(--border); border-radius: 50%; width: 34px; height: 34px; cursor: pointer; font-size: 15px; }
.chat-icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }

.chat-messages { flex: 1; overflow-y: auto; overscroll-behavior-y: contain; padding: 14px; display: flex; flex-direction: column; gap: 4px; }
.chat-message-row { display: flex; align-items: flex-end; gap: 6px; max-width: 78%; }
.chat-message-row.mine { align-self: flex-end; flex-direction: row-reverse; }
.chat-message-row.theirs { align-self: flex-start; }
.chat-bubble { background: var(--cream); border-radius: 12px; padding: 8px 12px; overflow-wrap: anywhere; }
.chat-message-row.mine .chat-bubble { background: var(--horizon-soft); }
.chat-bubble-text { font-size: 14px; line-height: 1.4; }
.chat-bubble-meta { font-size: 10.5px; color: var(--slate-light); margin-top: 3px; text-align: right; display: flex; gap: 4px; justify-content: flex-end; }
.chat-deleted-text { color: var(--slate-light); }
.chat-edited-tag { font-style: italic; }
.chat-read-tick.read { color: var(--navy-light); }
.chat-reply-preview {
  border-left: 2px solid var(--navy-light); padding-left: 6px; margin-bottom: 4px;
  font-size: 11.5px; color: var(--slate-light);
}
.chat-attachment-image { max-width: 220px; max-height: 220px; border-radius: var(--radius); display: block; margin-bottom: 4px; }
.chat-attachment-file { display: inline-block; font-size: 13px; margin-bottom: 4px; color: var(--navy-light); }
.chat-message-actions { display: flex; gap: 2px; opacity: 0; transition: opacity 0.1s; }
.chat-message-row:hover .chat-message-actions { opacity: 1; }
.chat-message-actions button { background: none; border: none; cursor: pointer; font-size: 13px; padding: 2px 4px; color: var(--slate-light); }
.chat-system-row { text-align: center; font-size: 12px; color: var(--slate-light); margin: 6px 0; }

.chat-typing-indicator { padding: 0 14px 4px; font-size: 12px; color: var(--slate-light); font-style: italic; }

.chat-composer { border-top: 1px solid var(--border); padding: 10px; }
.chat-pending-files { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.chat-pending-files.hidden { display: none; }
.chat-pending-file-chip {
  background: var(--cream); border: 1px solid var(--border); border-radius: 999px;
  padding: 3px 10px; font-size: 12px; display: inline-flex; align-items: center; gap: 6px;
}
.chat-pending-file-chip button { border: none; background: none; cursor: pointer; color: var(--slate-light); }
.chat-composer-row { display: flex; align-items: flex-end; gap: 8px; }
.chat-input {
  flex: 1; min-height: 36px; max-height: 120px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 18px; padding: 8px 14px;
  font-size: 14px; line-height: 1.4;
}
.chat-input:empty:before { content: attr(data-placeholder); color: var(--slate-light); }
.chat-input:focus { outline: none; border-color: var(--navy-light); }
.chat-send-btn {
  background: var(--navy-light); color: #fff; border: none; border-radius: 18px;
  padding: 8px 18px; font-size: 13px; font-weight: 700; cursor: pointer; flex-shrink: 0;
}
.chat-send-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.chat-reply-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 14px; background: var(--cream); font-size: 12px; border-top: 1px solid var(--border);
}
.chat-reply-bar.hidden { display: none; }
.chat-reply-bar button { border: none; background: none; cursor: pointer; font-size: 16px; color: var(--slate-light); }

@media (max-width: 900px) {
  .chat-layout { grid-template-columns: 1fr; height: calc(100vh - 140px); gap: 0; border: none; }
  .chat-list-pane, .chat-thread-pane { border-radius: 0; grid-column: 1; grid-row: 1; }
  .chat-back-btn { display: block; }
  .chat-message-row { max-width: 88%; }
}

@media (max-width: 640px) {
  .chat-input { font-size: 16px; } /* matches this app's own established iOS-zoom-prevention override for every other text input */
}

/* ---------- Internal Chat Phase 2 (groups, reactions, search) ---------- */
/* +Group used to be position:absolute over the tabs row, which overlapped
   the Directory tab's own label at every viewport width - confirmed live.
   It's a genuine third item in the same flex row now (flex-shrink:0 so a
   narrow list pane squeezes the two tabs, never this button's label). */
.chat-new-group-btn {
  flex-shrink: 0; align-self: center; margin-right: 8px; background: none; border: 1px solid var(--border);
  border-radius: 12px; padding: 3px 10px; font-size: 11.5px; cursor: pointer; color: var(--navy-light); white-space: nowrap;
}
.chat-bubble-sender { font-size: 11.5px; font-weight: 700; color: var(--navy-light); margin-bottom: 2px; }

.chat-reaction-trigger-wrap { position: relative; }
.chat-reaction-picker {
  position: absolute; bottom: 100%; left: 0; margin-bottom: 4px; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 4px 6px; display: flex; gap: 2px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15); z-index: 5;
}
.chat-reaction-picker.hidden { display: none; }
.chat-reaction-picker button { border: none; background: none; cursor: pointer; font-size: 16px; padding: 2px 4px; border-radius: 50%; }
.chat-reaction-picker button:hover { background: var(--cream); }
.chat-reactions-row { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.chat-reaction-pill {
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 1px 7px; font-size: 12px; cursor: pointer;
}
.chat-reaction-pill.mine { background: var(--horizon-soft); border-color: var(--navy-light); }

.chat-group-member-list { display: flex; flex-direction: column; gap: 4px; max-height: 200px; overflow-y: auto; }
.chat-group-member-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 8px; border-bottom: 1px solid var(--border); font-size: 13px;
}
.chat-group-member-row:last-child { border-bottom: none; }

/* Internal Calling - WhatsApp-style: a slim bar pinned at the top of the
   screen while a call is minimized/ongoing (default/collapsed state), which
   expands into a genuine full-screen call overlay on tap. Both states live
   in the same fixed #internal-call-widget container (see index.html),
   toggled by the .expanded class - internal-calls.js only ever swaps their
   innerHTML, never re-creates the elements, so listeners bound once (like
   the bar's own tap-to-expand handler) survive every re-render. */
.internal-call-widget { position: fixed; inset: 0 0 auto 0; z-index: 1001; pointer-events: none; }
.internal-call-widget.hidden { display: none; }
.internal-call-widget > * { pointer-events: auto; }

.internal-call-bar {
  position: fixed;
  top: var(--topbar-height, 56px);
  left: 0;
  right: 0;
  z-index: 1001;
  background: var(--navy);
  color: white;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}
.internal-call-widget.expanded .internal-call-bar { display: none; }
.internal-call-bar-inner { display: flex; align-items: center; gap: 10px; padding: 8px 16px; }
.internal-call-bar-title { font-weight: 700; font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.internal-call-bar-sub { margin-left: auto; font-size: 12.5px; opacity: 0.85; flex-shrink: 0; }

.internal-call-overlay {
  position: fixed;
  inset: 0;
  z-index: 1002;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 24px 20px calc(28px + env(safe-area-inset-bottom, 0));
  background: linear-gradient(180deg, var(--navy) 0%, #101a44 100%);
  color: white;
  text-align: center;
}
.internal-call-widget.expanded .internal-call-overlay { display: flex; }
.internal-call-minimize-btn {
  position: absolute;
  top: calc(16px + env(safe-area-inset-top, 0));
  left: 16px;
  width: 38px; height: 38px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.12);
  color: white;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.internal-call-minimize-btn:hover { background: rgba(255, 255, 255, 0.2); }
.internal-call-minimize-icon { transform: rotate(-90deg); }

.internal-call-avatar-circle {
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
  color: white;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; flex-shrink: 0;
}
.internal-call-avatar-wrap { margin-bottom: 6px; }
.internal-call-name { font-weight: 700; font-size: 20px; margin-bottom: 2px; }
.internal-call-status { font-size: 14px; opacity: 0.85; margin-bottom: 18px; }
.internal-call-error { color: #ff8f8f; }
/* Set on the active-screen status/timer line when any peer's underlying
   ICE state degrades (see pc.oniceconnectionstatechange in internal-
   calls.js) - a temporary network hiccup during backgrounding/minimizing
   shows as "Reconnecting..." instead of a timer that's silently frozen or
   controls that look live but aren't actually connected to anything. */
.internal-call-status.reconnecting { color: #ff8f8f; font-weight: 700; }

/* Group calling (Phase 3.1) - a small grid of avatar/name/status tiles
   instead of one big centered avatar once more than one other person is
   really on the call; see internal-calls.js's internalCallParticipantsAreaHtml. */
.internal-call-roster-grid {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 16px 20px;
  max-width: 420px; margin-bottom: 12px;
}
.internal-call-roster-tile { display: flex; flex-direction: column; align-items: center; gap: 6px; width: 76px; }
.internal-call-roster-name { font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.internal-call-roster-status { font-size: 11px; opacity: 0.75; }

.internal-call-bottom-controls {
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: 12px;
  width: 100%;
  max-width: 420px;
  margin-top: auto;
}
/* Reuses the External Calling softphone's own .call-control-btn (icon-over-
   label tile) and .call-hangup-btn-full (full-width red pill) - see their
   definitions above - so the two calling surfaces in this app read as one
   consistent product, restyled here into an even horizontal row (Mute,
   Speaker, Add Participant, End) instead of the softphone's own grid/
   standalone layouts. Dark overlay background needs its own light-on-dark
   variant of these classes rather than the softphone's light-surface ones. */
.internal-call-bottom-controls .call-control-btn {
  flex: 1; background: rgba(255, 255, 255, 0.1); border: none; color: white;
}
.internal-call-bottom-controls .call-control-btn:hover { background: rgba(255, 255, 255, 0.18); color: white; }
.internal-call-bottom-controls .call-control-btn.active { background: white; color: var(--navy); }
.internal-call-bottom-controls .call-hangup-btn-full { flex: 1.3; width: auto; margin-top: 0; }

.internal-call-incoming-actions { max-width: 320px; }
.internal-call-btn {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  border: none; border-radius: var(--radius); padding: 14px 10px;
  font-size: 13px; font-weight: 700; cursor: pointer; color: white;
}
.internal-call-btn-accept { background: var(--success); }
.internal-call-btn-end, .internal-call-btn-reject { background: var(--danger); }

.internal-call-add-participant-panel {
  width: 100%; max-width: 340px; max-height: 220px; overflow-y: auto;
  background: rgba(255, 255, 255, 0.08); border-radius: 12px; margin-bottom: 12px;
}
.internal-call-add-participant-panel.hidden { display: none; }
.internal-call-add-panel-list { display: flex; flex-direction: column; }
.internal-call-add-panel-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: none; border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  color: white; padding: 10px 14px; font-size: 13.5px; cursor: pointer; text-align: left;
}
.internal-call-add-panel-row:hover { background: rgba(255, 255, 255, 0.08); }
.internal-call-add-panel-row:last-child { border-bottom: none; }
.internal-call-add-panel-empty { padding: 14px; font-size: 13px; opacity: 0.75; }

/* The bar sits just below the topbar and needs every view's own content to
   make room for it so it never covers a page's own header (e.g. Chat's
   thread header) - the +60px below is the bar's own rendered height (44px)
   plus a little breathing room, added on top of `main`'s EXISTING
   padding-top in each context (24px flat on desktop - see the plain `main`
   rule near the top of this file; the topbar-height-based mobile formula -
   see the mobile main rule) rather than recomputing padding-top from
   scratch, so this only ever adds space and never fights either base rule. */
body.internal-call-bar-active main { padding-top: calc(24px + 60px); }

@media (max-width: 640px) {
  .internal-call-bar-title { max-width: 45vw; }
  /* mobile <main> now has a flat 16px top padding (the flex shell handles
     clearing the topbar), so this just adds the ~60px call-bar height. */
  body.internal-call-bar-active main { padding-top: calc(16px + 60px); }
}

/* Merchant record -> Internal Chat integration (Phase 4) - the small
   back-link shown in a merchant-linked group's thread header. */
.chat-merchant-link {
  display: inline-flex; align-items: center; gap: 3px; margin-top: 2px;
  background: none; border: none; padding: 0; font-size: 11.5px; color: var(--horizon);
  cursor: pointer; text-decoration: underline;
}

/* NOTE: the WhatsApp shared-inbox styles live in their own file now,
   frontend/css/whatsapp.css (loaded after this one) — the module no longer
   reuses the .chat-* shell. Only the merchant-modal WhatsApp panel below is
   still styled here, since app.js's loadMerchantWaPanel() renders it. */

/* WhatsApp panel inside the Merchant Details modal */
.wa-merchant-panel:empty { display: none; }
.wa-panel-list {
  display: flex; flex-direction: column; gap: var(--space-1);
  border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3);
  background: var(--surface-2);
}
.wa-panel-msg { display: flex; gap: var(--space-2); font-size: var(--fs-13); align-items: baseline; }
.wa-panel-msg--outbound .wa-panel-who { color: var(--horizon); }
.wa-panel-who { font-weight: var(--fw-semibold); flex-shrink: 0; }
.wa-panel-text { flex: 1; min-width: 0; overflow-wrap: anywhere; color: var(--text); }
.wa-panel-time { flex-shrink: 0; color: var(--slate-light); font-size: var(--fs-11); }
