/* VANTX theming — Light by default, Dark via [data-theme="dark"] on <html>.
   Tailwind colour utilities (bg-bg, text-muted, border-border, …) consume
   the rgb-triplet vars below via the `<alpha-value>` slot defined in
   tailwind.config.js, so flipping the attribute swaps the entire palette
   without recompiling Tailwind. Hex pairs are kept too for the legacy
   style.css selectors that haven't been migrated to var() yet. */
:root {
  /* ---- Light theme (DEFAULT) ----
     Off-white surface, dark navy text. Brand neon stays vivid so the score
     chip / heatmap / live-ping all keep their punch on either background. */
  --color-bg:      250 250 252;     /* #fafafc */
  --color-surface: 255 255 255;     /* #ffffff */
  --color-border:  226 230 238;     /* #e2e6ee */
  --color-muted:   100 110 130;     /* #646e82 */
  --color-primary:   0 184 148;     /* #00b894 — slightly tamed cyan for AA contrast on white */
  --color-accent:  149  76 233;     /* #954ce9 */
  --color-danger:  225  29  72;     /* #e11d48 */
  --color-warn:    217 119   6;     /* #d97706 */

  /* Convenience hex synonyms used by CSS rules that haven't been migrated
     to var(rgb(... )) yet. They follow the same theme via [data-theme]
     overrides below. */
  --primary: #00b894;
  --accent:  #954ce9;
  --danger:  #e11d48;
  --warn:    #d97706;
  --fg:      #1a1a2e;       /* body text, light theme */
  --fg-soft: #2a2e3f;       /* slightly softer text for secondary lines */
  --bg-hex:  #fafafc;
  --surface-hex: #ffffff;
  --border-hex:  #e2e6ee;
  --muted-hex:   #646e82;

  /* Page-level layered surface (used by `body`). Light theme uses a soft
     hint of the brand neon so the page doesn't feel sterile. */
  --page-bg-base:    #fafafc;
  --page-bg-glow-1:  rgba(149, 76, 233, 0.06);   /* accent halo */
  --page-bg-glow-2:  rgba(0, 184, 148, 0.05);    /* primary halo */
  --html-bg:         #fafafc;

  /* Safe-area insets — exposed once so any element can opt in via the
     vx-safe-* utility classes below. Falls back to 0 on browsers that don't
     report them. Requires <meta viewport-fit=cover>, set in index.html. */
  --safe-top:    env(safe-area-inset-top,    0px);
  --safe-right:  env(safe-area-inset-right,  0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left:   env(safe-area-inset-left,   0px);
}

/* ---- Dark theme override ---- */
[data-theme="dark"] {
  --color-bg:      10  10  15;      /* #0a0a0f */
  --color-surface: 19  19  26;      /* #13131a */
  --color-border:  31  31  46;      /* #1f1f2e */
  --color-muted:  138 138 163;      /* #8a8aa3 */
  --color-primary:   0 255 209;     /* #00ffd1 — full neon on dark */
  --color-accent:  179  71 255;     /* #b347ff */
  --color-danger:  255  59 107;     /* #ff3b6b */
  --color-warn:    255 179  71;     /* #ffb347 */

  --primary: #00ffd1;
  --accent:  #b347ff;
  --danger:  #ff3b6b;
  --warn:    #ffb347;
  --fg:      #e6e6f0;
  --fg-soft: #c8c8d8;
  --bg-hex:      #0a0a0f;
  --surface-hex: #13131a;
  --border-hex:  #1f1f2e;
  --muted-hex:   #8a8aa3;

  --page-bg-base:    #0a0a0f;
  --page-bg-glow-1:  rgba(179, 71, 255, 0.12);
  --page-bg-glow-2:  rgba(0, 255, 209, 0.08);
  --html-bg:         #0a0a0f;
}

/* Theme-driven page background. <html> is solid so iOS rubber-band
   overscroll never reveals a white/black flash; <body> carries the
   layered radial gradient inside the viewport. */
html { background: var(--html-bg); }
body {
  background:
    radial-gradient(1200px 600px at 80% -10%, var(--page-bg-glow-1), transparent 60%),
    radial-gradient(900px 500px at -10% 110%, var(--page-bg-glow-2), transparent 60%),
    var(--page-bg-base);
  color: var(--fg);
}

/* Horizontal-overflow guard.
   `overflow-x: hidden` on <html> on iOS Safari hijacks the document scroll
   context and locks the page to a few pixels of vertical movement (the
   "scroll-lock" bug we hit). `overflow-x: clip` on <body> achieves the same
   visual result without establishing a scroll container, so iOS keeps using
   <html> as the scroller and vertical scrolling stays fluid. */
html, body { width: 100%; }
html {
  overflow-y: auto !important;         /* never let an ancestor lock vertical scroll */
  /* overscroll-behavior MUST live on the scroll container — that's <html>
     here, not <body>. Putting it on body had no effect on iOS Safari, which
     is why the rubber-band exposed the html bg under the dashboard's last
     widget on phones. */
  overscroll-behavior-y: none;
}
body {
  overflow-x: clip;                    /* RTL/wide-child guard, scroll-safe */
  overflow-y: visible;                 /* let <html> own the scroll */
  /* iOS Safari historically interprets `100vh` as the LARGE viewport
     (URL-bar collapsed), so a `min-height: 100vh` rule on body produces
     a phantom-tall body when the URL bar IS visible — visible as a big
     white gap below the last widget on an iPhone. Switching the floor to
     `100svh` (small viewport = URL bar visible) sizes body to the smaller
     of the two viewports, so when the URL bar collapses on scroll, body
     is shorter than the visible area and the html background (matching
     bg color) fills the gap seamlessly. */
  min-height: 100vh;                   /* fallback for browsers without svh */
  min-height: 100svh;                  /* iOS Safari: pin to small viewport */
  /* Notch insets on the SIDES only — header (vx-safe-top) and main
     (vx-safe-bottom) handle vertical insets. Putting top/bottom here too
     would double up against the sticky header. */
  padding-inline-start: var(--safe-left);
  padding-inline-end:   var(--safe-right);
}
/* `<main>` must never establish its own vertical scroll container — content
   should flow into the document scroll. We add explicit `overflow-y: visible`
   to defeat any future Tailwind utility that might silently introduce one. */
main#main {
  overflow-y: visible !important;
  overflow-x: clip;
  width: 100%;
  max-width: 100%;
}

/* -------- Safe-area helpers (notch / home-bar aware) --------
   `vx-safe-top`     — header pad to clear the notch (≥20px so the brand row
                       never feels welded to the camera).
   `vx-safe-bottom`  — page pad above the home bar. Floor of 32px on phones
                       so the last row is never welded to the home-indicator
                       region; tablets+ collapse to the natural inset since
                       there's no home-bar overlay risk. */
.vx-safe-top    { padding-top:    max(20px, var(--safe-top))            !important; }
/* Phone floor was 100px, which on a layout-light view (empty dashboard,
   short form) created a big empty band below the last widget. Tighter
   floor — 32px + the home-indicator inset — keeps the bottom row clear
   without inventing dead space when the page is shorter than the
   viewport. The action-row clearance we cared about (e.g. lead-modal
   primary button) is enforced by per-component padding, not this util. */
.vx-safe-bottom { padding-bottom: max(32px, calc(var(--safe-bottom) + 24px)) !important; }
.vx-safe-x {
  padding-inline-start: max(1rem, var(--safe-left));
  padding-inline-end:   max(1rem, var(--safe-right));
}
@media (min-width: 768px) {
  /* On tablets/desktops we don't need a 100px home-bar gap — collapse it. */
  .vx-safe-bottom { padding-bottom: max(2rem, var(--safe-bottom)) !important; }
}

.nav-btn {
  color: var(--muted-hex);
  transition: all 120ms ease;
  cursor: pointer;
}
/* Hover state must work in BOTH themes. Using --fg for color (which flips
   between dark navy and off-white per theme) and a semi-transparent
   primary-tinted background that reads as "hovered" on either canvas. */
.nav-btn:hover {
  color: var(--fg);
  background: rgb(var(--color-primary) / 0.08);
}
.nav-btn.active {
  color: var(--primary);
  background: rgba(0,255,209,0.08);
  box-shadow: inset 0 0 0 1px rgba(0,255,209,0.4);
}

/* -------- Mobile nav (hamburger dropdown) --------
   Below the `md` breakpoint (768px) the nav becomes a vertical floating
   panel anchored directly under the header. Tailwind's `hidden`/`md:flex`
   gates visibility; we just restyle for the open state on small viewports.
   Position is `absolute` with `top: 100%` so the panel always lands at
   the bottom edge of the (sticky) header, regardless of how tall the
   header turns out at a given breakpoint or notch. The hardcoded
   `56px` value the previous version used was off by ~16px on phones
   with the standard h-12 logo and could overlap the bottom of the
   button. */
@media (max-width: 767px) {
  #main-nav:not(.hidden) {
    display: flex;
    position: absolute;
    top: calc(100% + 0.5rem);
    inset-inline-end: 0.5rem;
    flex-direction: column;
    align-items: stretch;
    min-width: 220px;
    max-width: calc(100vw - 1rem);
    padding: 0.5rem;
    gap: 2px;
    background: var(--surface-hex);
    border: 1px solid var(--border-hex);
    border-radius: 0.875rem;
    box-shadow: 0 16px 32px rgba(0,0,0,0.55), 0 0 0 1px rgba(0,255,209,0.10);
    z-index: 45;
  }
  #main-nav .nav-btn {
    width: 100%;
    text-align: start;
    padding: 0.625rem 0.875rem;
  }
  /* Reveal the user chip inside the mobile menu so the agent name is visible
     somewhere even when the lg-only inline span is hidden. */
  #main-nav #user-chip {
    display: block !important;
    padding: 0.5rem 0.875rem;
    border-bottom: 1px solid var(--border-hex);
    margin-bottom: 0.25rem;
    max-width: none;
  }
  #main-nav #user-chip:empty { display: none !important; }

  /* Hamburger button: match the .user-menu-trigger's circular pill
     chrome so the two right-cluster icons in the mobile header read
     as a coordinated pair. The Tailwind `px-3 py-1.5 rounded-md`
     classes on the button get overridden here (CSS id-selector wins
     over Tailwind class specificity). 36×36 matches the user-menu
     pill at < 540px (28px avatar + 4px padding × 2). */
  #mobile-menu-toggle {
    width: 36px;
    height: 36px;
    padding: 0;
    border-radius: 999px;
    border: 1px solid var(--border-hex);
    background: rgba(255, 255, 255, 0.02);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
  }
  /* Center the ☰ glyph optically inside the circle. Tailwind's
     `text-base` ships a 1.5rem line-height (the inner <span>'s default),
     which pushes a hidden block of whitespace below the character and
     makes flex-centering look top-heavy. Forcing the span to fill the
     button as its own flex container — with line-height: 1 — collapses
     that whitespace and pins the glyph to the geometric centre. The
     font-size bump (18→20px) gives the icon visual parity with the
     user-menu avatar next to it. */
  #mobile-menu-toggle > span {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    line-height: 1;
    font-size: 20px;
  }
  #mobile-menu-toggle:hover,
  #mobile-menu-toggle[aria-expanded="true"] {
    border-color: var(--primary);
    background: rgba(0, 255, 209, 0.04);
  }
}

/* Auth tabs (login / register switch) */
.auth-tab {
  color: var(--muted-hex);
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all 120ms ease;
}
.auth-tab:hover { color: #fff; }
.auth-tab.active {
  color: var(--primary);
  background: rgba(0,255,209,0.08);
  border-color: rgba(0,255,209,0.30);
}

/* -------- In-button spinner --------
   Used during login / register / reset submissions. Drawn with conic-gradient
   and CSS mask so it's a single element with no markup overhead. */
.btn-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 1.5px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  flex-shrink: 0;
}

/* -------- Custom dark/neon checkbox --------
   Replaces the default native check with a small bordered box that flips
   to neon-cyan when ticked. Pure CSS, no SVG, no extra DOM. */
.checkbox-neon {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border: 1px solid var(--border-hex);
  border-radius: 4px;
  background: var(--bg-hex);
  position: relative;
  cursor: pointer;
  transition: all 120ms ease;
  flex-shrink: 0;
}
.checkbox-neon:hover { border-color: var(--muted-hex); }
.checkbox-neon:checked {
  background: var(--primary);
  border-color: var(--primary);
  box-shadow: 0 0 8px rgba(0, 255, 209, 0.35);
}
.checkbox-neon:checked::after {
  content: '';
  position: absolute;
  inset-inline-start: 4px;
  top: 1px;
  width: 5px;
  height: 9px;
  border-right: 1.5px solid var(--bg-hex);
  border-bottom: 1.5px solid var(--bg-hex);
  transform: rotate(45deg);
}

/* Heatmap chips */
.heat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid;
}

/* Priority chips — same shape as .heat but a dedicated palette so the two
   different signals don't visually collide in a row. */
.prio {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid;
}
/* Priority chips — colors use the rgb-triplet vars so the tint flips
   between themes (was hardcoded dark-theme neon, looked washed on white). */
.prio-high   { color: rgb(var(--color-danger)); border-color: rgb(var(--color-danger)); background: rgb(var(--color-danger) / 0.08); box-shadow: 0 0 8px rgb(var(--color-danger) / 0.18); }
.prio-medium { color: rgb(var(--color-warn));   border-color: rgb(var(--color-warn));   background: rgb(var(--color-warn) / 0.08); }
.prio-low    { color: var(--muted-hex);          border-color: rgb(var(--color-muted) / 0.40); background: rgb(var(--color-muted) / 0.06); }

/* -------- Interceptor modal --------
   Real-time popup when a brand-new lead arrives. The pulse animation around
   the card makes ignoring it a deliberate choice; the countdown bar gives
   the deadline a visceral, narrowing-window feel. */
.interceptor-card {
  border-color: var(--accent);
  box-shadow:
    0 0 0 1px rgba(179, 71, 255, 0.4),
    0 16px 48px rgba(0, 0, 0, 0.55),
    0 0 32px rgba(179, 71, 255, 0.20);
  animation: interceptor-pulse 1.6s ease-in-out infinite;
}
@keyframes interceptor-pulse {
  0%, 100% {
    box-shadow:
      0 0 0 1px rgba(179, 71, 255, 0.40),
      0 16px 48px rgba(0, 0, 0, 0.55),
      0 0 22px rgba(179, 71, 255, 0.20);
  }
  50% {
    box-shadow:
      0 0 0 2px rgba(179, 71, 255, 0.85),
      0 16px 48px rgba(0, 0, 0, 0.55),
      0 0 48px rgba(179, 71, 255, 0.55);
  }
}

/* Countdown bar at the bottom of the interceptor card. Fills 100% on open
   and shrinks to 0 by the timeout — duration is set inline by JS. The
   `.urgent` class is toggled by JS in the last 5 seconds: solid red +
   accelerated pulse so the deadline becomes physically uncomfortable. */
/* Card no longer has overflow:hidden, so the countdown bar carries its own
   bottom-corner rounding to stay flush with the card's rounded shape. The
   `1rem` matches `rounded-2xl` (16px) minus 1px of border. */
.countdown-bar {
  height: 4px;
  background: rgba(255, 255, 255, 0.04);
  overflow: hidden;
  border-radius: 0 0 calc(1rem - 1px) calc(1rem - 1px);
}
.countdown-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--primary), var(--accent));
  transition: width linear, background-color 220ms ease, box-shadow 220ms ease;
  transform-origin: left center;
}
[dir="rtl"] .countdown-fill {
  background: linear-gradient(270deg, var(--primary), var(--accent));
  transform-origin: right center;
}
.countdown-fill.urgent {
  background: var(--danger) !important;     /* solid red beats the gradient */
  box-shadow: 0 0 14px rgba(255, 59, 107, 0.7), 0 0 24px rgba(255, 59, 107, 0.4);
  animation: countdown-urgent 0.6s ease-in-out infinite;
}
@keyframes countdown-urgent {
  0%, 100% { opacity: 1;   }
  50%      { opacity: 0.55;}
}

/* Fade-out helper — class added to the modal overlay just before we hide it,
   so closing feels intentional instead of a binary cut. */
.fade-out {
  animation: fade-out 220ms ease-out forwards;
  pointer-events: none;
}
@keyframes fade-out {
  from { opacity: 1; }
  to   { opacity: 0; transform: scale(0.98); }
}

/* -------- Timeline (lead interactions) -----------------------------------
   Minimalist vertical line with small dots — replaces the boxed list so the
   chronology is the dominant visual signal, not the borders. */
.timeline {
  list-style: none;
  position: relative;
  margin: 0;
  padding-inline-start: 14px;
}
.timeline::before {
  content: '';
  position: absolute;
  inset-inline-start: 3px;
  top: 8px; bottom: 8px;
  width: 1px;
  background: rgba(255, 255, 255, 0.08);
}
.timeline-item {
  position: relative;
  margin: 0;
  padding: 0 0 14px 0;
}
.timeline-item:last-child { padding-bottom: 0; }
.timeline-item::before {
  content: '';
  position: absolute;
  inset-inline-start: -14px;
  top: 6px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--primary);
  box-shadow: 0 0 8px rgba(0, 255, 209, 0.45);
}
.timeline-item.is-merge::before    { background: var(--accent); box-shadow: 0 0 8px rgba(179,71,255,0.5); }
.timeline-item.is-self::before     { background: #25d366;       box-shadow: 0 0 8px rgba(37,211,102,0.5); }
.timeline-item.is-timeout::before  { background: var(--danger);  box-shadow: 0 0 8px rgba(255,59,107,0.5); }
.timeline-meta {
  color: var(--muted-hex);
  font-size: 11px;
  letter-spacing: 0.02em;
}
.timeline-body {
  font-size: 13px;
  margin-top: 2px;
  color: var(--fg);
}

.interceptor-action {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 10px;
  border: 1px solid var(--border-hex);
  background: rgba(255, 255, 255, 0.02);
  color: var(--fg);
  cursor: pointer;
  transition: all 120ms ease;
  font-size: 13px;
  text-align: start;
  width: 100%;
}
.interceptor-action:hover { border-color: var(--muted-hex); background: rgba(255, 255, 255, 0.04); }
.interceptor-action.primary { border-color: #25d366; color: #25d366; background: rgba(37, 211, 102, 0.06); }   /* WhatsApp green */
.interceptor-action.primary:hover { background: rgba(37, 211, 102, 0.14); }
/* Destructive variant — used for the "Approve & Delete" button in the
   deletion-request popup. Matches .btn-danger so the visual language
   (red border + tinted bg) reads as "this destroys data" everywhere. */
.interceptor-action.danger { border-color: var(--danger); color: var(--danger); background: rgba(255, 59, 107, 0.06); }
.interceptor-action.danger:hover { background: rgba(255, 59, 107, 0.14); }
.interceptor-action .icon { font-size: 16px; flex-shrink: 0; }
.interceptor-action .label { flex: 1; }

/* Nav badge — small red pill on the Leads button when status='New' > 0. */
.nav-badge {
  position: absolute;
  top: -4px;
  inset-inline-end: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  box-shadow: 0 0 10px rgba(255,59,107,0.5);
  animation: badgePulse 1.6s ease-in-out infinite;
}
@keyframes badgePulse {
  0%, 100% { box-shadow: 0 0 8px rgba(255,59,107,0.45); }
  50%      { box-shadow: 0 0 14px rgba(255,59,107,0.85); }
}
.heat-hot    { color: #ff5577; border-color: #ff5577; background: rgba(255,85,119,0.10); box-shadow: 0 0 14px rgba(255,85,119,0.25); }
.heat-warm   { color: #ffb347; border-color: #ffb347; background: rgba(255,179,71,0.08); }
.heat-cold   { color: #6fb5ff; border-color: #6fb5ff; background: rgba(111,181,255,0.06); }
.heat-frozen { color: #b347ff; border-color: #b347ff; background: rgba(179,71,255,0.10); box-shadow: 0 0 14px rgba(179,71,255,0.30); }

/* Heatmap legend turned into interactive FILTER BUTTONS.
   Each button reuses the matching `.heat-{tier}` palette for shape +
   colour, plus this class layers on:
     • `font: inherit` — buttons default to a smaller UA font; this
       restores the page font + 11px size from `.heat`.
     • `cursor: pointer` — make it obvious they're clickable.
     • Inactive buttons fade to `opacity: 0.5` so the currently-
       selected filter visually pops without reflowing the row.
     • Hover bumps to 0.85 for affordance.
     • `.is-active` returns to full opacity and bumps scale by 4%
       so the selected filter is unmistakable at a glance.
     • Inactive buttons drop the per-tier glow box-shadow so the
       active one's shadow reads as a deliberate selection cue. */
.heat-filter-btn {
  font: inherit;
  font-size: 11px;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 120ms ease, transform 120ms ease, box-shadow 200ms ease;
}
.heat-filter-btn:hover { opacity: 0.85; }
.heat-filter-btn.is-active {
  opacity: 1;
  transform: scale(1.04);
}
.heat-filter-btn:not(.is-active) { box-shadow: none; }

/* "All" button — there's no `heat-all` tier in the data, so it gets
   a neutral pill palette (foreground text, default border, faint
   surface fill) that reads as "no filter" rather than implying any
   particular temperature. */
.heat-all {
  color: var(--fg);
  border-color: var(--border-hex);
  background: rgba(255, 255, 255, 0.04);
}

/* Heatmap grid cells */
.heat-cell {
  aspect-ratio: 1 / 1;
  border-radius: 6px;
  border: 1px solid var(--border-hex);
  cursor: pointer;
  transition: transform 100ms ease, box-shadow 200ms ease;
}
.heat-cell:hover { transform: scale(1.12); }
.heat-cell.hot    { background: #ff3b6b; box-shadow: 0 0 8px rgba(255,59,107,0.6); }
.heat-cell.warm   { background: #ffb347; }
.heat-cell.cold   { background: #355a8a; }
.heat-cell.frozen { background: #1f1430; box-shadow: inset 0 0 6px rgba(179,71,255,0.4); }

/* Stat tiles */
.tile {
  background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0));
  border: 1px solid var(--border-hex);
  border-radius: 16px;
  padding: 18px;
  /* Hard cap so a long inner element (e.g. a token URL) can't push the tile
     past the parent grid track and trigger horizontal scroll on phones. */
  max-width: 100%;
  min-width: 0;
}
/* When a tile contains a wide pre/code block, scroll *inside* the tile rather
   than letting it widen the viewport. */
.tile pre, .tile code { max-width: 100%; }
.tile-num {
  font-size: 28px;
  font-weight: 700;
  background: linear-gradient(90deg, var(--primary), var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Table */
table.crm { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: auto; }
/* `start` flips automatically with [dir] — works in LTR and RTL.
   `word-break` + `white-space: normal` stop a long unbroken email or display
   name from forcing the whole row past viewport width on phones. */
table.crm th, table.crm td {
  padding: 10px 12px;
  text-align: start;
  border-bottom: 1px solid var(--border-hex);
  white-space: normal;
  word-break: break-word;
  overflow-wrap: anywhere;
}

/* -------- Responsive columns --------
   Tag a <th> / <td> with `col-md` to hide it under 640px (phones), and with
   `col-lg` to additionally hide it under 1024px (tablets). Keeps the most
   important columns (name, status, actions) visible at every size. */
@media (max-width: 639px) {
  .crm th.col-md, .crm td.col-md,
  .crm th.col-lg, .crm td.col-lg { display: none; }
}
@media (min-width: 640px) and (max-width: 1023px) {
  .crm th.col-lg, .crm td.col-lg { display: none; }
}

/* -------- Card-stack mode (admin Users on phones) -------------------------
   Forces a wide management table into a stack of cards by un-tabling the
   structure. Each <tr> becomes a card; each <td> becomes a label/value row
   driven by the `data-label` attribute set in renderUsers(). Long usernames
   or emails wrap inside the cell instead of stretching the page. */
@media (max-width: 639px) {
  .crm.users-stack thead { display: none; }
  .crm.users-stack,
  .crm.users-stack tbody,
  .crm.users-stack tr,
  .crm.users-stack td { display: block; width: 100%; }
  .crm.users-stack tr {
    border: 1px solid var(--border-hex);
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.02);
    margin-bottom: 12px;
    padding: 8px 12px;
  }
  .crm.users-stack td {
    border-bottom: 1px solid rgba(31, 31, 46, 0.6);
    padding: 8px 0;
    display: flex;
    align-items: center;
    gap: 12px;
    text-align: end;     /* value lines up to end; label sits at start */
  }
  .crm.users-stack td:last-child { border-bottom: 0; }
  .crm.users-stack td::before {
    content: attr(data-label);
    color: var(--muted-hex);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    /* Allow the label to shrink to ~45% of the row before truncating —
       prevents long labels (e.g. "שם פרטי ושם משפחה") from pushing the
       value off-screen. The flex pair below gives the value at least
       half the row to breathe. */
    flex: 0 1 auto;
    max-width: 50%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: start;
    margin-inline-end: auto;
  }
  /* Value side: also take only its share, ellipsis if too long. */
  .crm.users-stack td {
    overflow: hidden;
  }
  .crm.users-stack td > * {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Hide cells that carry NO content/label of their own. The actions cell
     intentionally has `data-label=""` (no header text in card-stack mode —
     the icons speak for themselves), but it's NOT empty: it holds the
     edit / reset / disable / delete buttons. The earlier version of this
     rule swept the actions cell up with the genuinely-empty cells and
     hid it on every phone, so users couldn't see the edit buttons at all
     ("אני לא רואה את כפתורי העריכה בטלפון"). The `:not(.actions-cell)`
     guard preserves the intent (hide truly empty / unlabeled cells)
     while keeping the action buttons reachable on phones. */
  .crm.users-stack td:empty,
  .crm.users-stack td[data-label=""]:not(.actions-cell) { display: none; }
  .crm.users-stack td.actions-cell {
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 6px;
  }
  /* Suppress the ::before label pseudo on the actions cell — its
     `data-label=""` would render an empty 0-width box but with
     `margin-inline-end: auto` would still affect flex distribution.
     Hiding it explicitly keeps the row's flex math clean. */
  .crm.users-stack td.actions-cell::before { display: none; }
  /* Wrapper border becomes redundant once rows are cards. */
  .users-stack-wrap { border: 0 !important; background: transparent !important; }

  /* -------- Search filter: hide non-matching rows ------------------------
     The users-list search applies Tailwind's `.hidden` (`display: none`)
     to filter rows. But the rule above sets `.crm.users-stack tr` to
     `display: block` with class+class+element specificity (0,2,1) — which
     beats `.hidden`'s 0,1,0 — so the filter silently failed on phones
     (rows toggled the class but stayed visible). This explicit rule has
     specificity 0,3,1 AND uses `!important` for belt-and-suspenders, so
     filtered rows actually disappear regardless of cascade order. */
  .crm.users-stack tr.hidden { display: none !important; }

  /* -------- Collapsed-by-default mobile rows ------------------------------
     The user wanted: phones show only the display name + actions; tap to
     expand for the rest of the metadata (id, email, role, status, last
     login). We key off the `aria-expanded` attribute on the <tr> so the
     state is also exposed to assistive tech. The display-name cell carries
     `data-primary="true"` so it stays visible regardless of state.

     Crucially, `.actions-cell` is ALSO excluded from the collapse rule —
     the edit / reset / disable / delete buttons need to be reachable
     without first expanding the card, otherwise users have no idea they
     CAN edit at all (the chevron alone isn't enough affordance — exact
     feedback was "אין אפשרות לערוך משתמשים", "no way to edit users"). */
  .crm.users-stack tr[aria-expanded="false"] td:not([data-primary="true"]):not(.actions-cell) {
    display: none;
  }
  /* Primary cell renders as its own visual row, no label prefix when
     collapsed — it IS the row. Switched from `display:flex +
     justify-content: space-between` to an explicit 2-col grid: more
     reliable across browsers/CSS-cascade combinations. The grid auto-
     flips in RTL — column 1 (1fr) goes to the start side (right), the
     auto column (chevron) goes to the end side (left). The chevron
     flips to indicate state. */
  .crm.users-stack td[data-primary="true"] {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 8px;
    font-weight: 600;
    font-size: 14px;
    color: var(--fg);
    text-align: start;
  }
  .crm.users-stack td[data-primary="true"] .user-display-name {
    text-align: start;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
  }
  .crm.users-stack td[data-primary="true"]::before {
    display: none;                     /* hide "Display Name" label — name speaks for itself */
  }
  .crm.users-stack tr[aria-expanded="true"] td[data-primary="true"] {
    border-bottom: 1px solid rgba(127, 127, 150, 0.18);
  }
  .user-display-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .user-row-chevron {
    color: var(--muted-hex);
    font-size: 14px;
    transition: transform 180ms ease;
    flex-shrink: 0;
    margin-inline-start: 8px;
  }
  .crm.users-stack tr[aria-expanded="true"] .user-row-chevron {
    transform: rotate(180deg);
  }
  .crm.users-stack tr {
    cursor: pointer;                   /* whole row is the toggle target */
    transition: background 120ms ease;
  }
  .crm.users-stack tr[aria-expanded="true"] {
    background: rgba(0, 184, 148, 0.04);
  }
  [data-theme="dark"] .crm.users-stack tr[aria-expanded="true"] {
    background: rgba(0, 255, 209, 0.04);
  }
}

/* Desktop (≥640px): chevron is decoration only — table shows everything,
   so no need to display the "tap to expand" hint. */
@media (min-width: 640px) {
  .user-row-chevron { display: none; }
}
table.crm th {
  color: var(--muted-hex);
  font-weight: 500;
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.06em;
  /* Long Hebrew column labels (e.g. "שם פרטי ושם משפחה") were pushing
     row width past the viewport. Truncate with an ellipsis once the
     auto-laid column hits its allocated space. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
table.crm tbody tr { transition: background 100ms; }
/* Theme-aware row hover — rgba(255,255,255,…) was invisible on white bg.
   Using muted/0.05 gives a subtle tint on both themes. */
table.crm tbody tr:hover { background: rgb(var(--color-muted) / 0.06); cursor: pointer; }

/* Form controls */
.field input, .field select, .field textarea {
  width: 100%;
  background: var(--bg-hex);
  border: 1px solid var(--border-hex);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--fg);
  transition: border-color 120ms;
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none;
  border-color: var(--primary);
}
.field label {
  display: block;
  font-size: 11px;
  color: var(--muted-hex);
  margin-bottom: 4px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  /* Multi-word Hebrew labels can wrap to two lines — that's fine for a
     vertically-stacked form. Soft word-break keeps long unbroken strings
     from pushing the modal wider than the viewport. */
  overflow-wrap: anywhere;
  word-break: normal;
}

/* -------- iOS Safari autozoom fix (C4) ----------------------------------
   iOS Safari forcibly zooms in when a focused input has font-size < 16px,
   regardless of `viewport-fit=cover` or `user-scalable=no` (Apple ignores
   those for accessibility). The minimum to prevent it is exactly 16px.
   Desktop keeps the dense 13px (set above); mobile gets the readable 16px.
   Covers `.vx-button` too — it receives focus on touch like a native select. */
@media (max-width: 768px) {
  .field input,
  .field select,
  .field textarea,
  .vx-button,
  input[type="text"],
  input[type="email"],
  input[type="tel"],
  input[type="password"],
  input[type="search"],
  input[type="number"],
  input[type="url"],
  input[type="date"],
  textarea,
  select {
    font-size: 16px;
  }
}

/* -------- VxSelect (custom dropdown built on a hidden <select>) --------
   The native <select> sits underneath as the source of truth for form reads;
   we render a styled button + listbox on top. The list is a fixed-position
   element appended to <body> so it escapes any modal overflow clipping. */

select.vx-native {
  /* Hide visually but keep in form.elements / accessibility tree. */
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0,0,0,0);
  border: 0;
  pointer-events: none;
  opacity: 0;
}

.vx-button {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  /* Asymmetric inline padding: a bit more breathing room on the START
     side (right in RTL, left in LTR) so the label doesn't sit jammed
     against the cell edge — user reported "טקסט עדיין נחתך קצת מצד ימין"
     i.e. the text was visually crowding the right edge in Hebrew. */
  padding-block: 8px;
  padding-inline-start: 14px;
  padding-inline-end: 10px;
  font-size: 13px;
  background: var(--bg-hex);
  border: 1px solid var(--border-hex);
  border-radius: 8px;
  color: var(--fg);
  cursor: pointer;
  text-align: start;
  transition: border-color 120ms, box-shadow 120ms;
  font-family: inherit;
  font-weight: 400;
}
.vx-button:hover  { border-color: rgb(var(--color-muted) / 0.40); }
.vx-button:focus,
.vx-button.open   {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgb(var(--color-primary) / 0.15);
}
.vx-button.placeholder .vx-lbl { color: var(--muted-hex); font-style: italic; }
.vx-button .vx-ico   { display: inline-flex; width: 16px; flex-shrink: 0; font-size: 13px; }
.vx-button .vx-ico:empty { display: none; }
.vx-button .vx-lbl   {
  flex: 1;
  /* min-width:0 is required for ellipsis to actually engage on a flex
     child — without it, the intrinsic min-content size keeps the lbl
     from shrinking, and the text gets cut hard at the container edge
     (which is what produced the right-side clipping the user reported
     on the leads-page filter buttons). */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vx-button .vx-caret {
  font-size: 11px; color: var(--muted-hex);
  transition: transform 160ms ease;
  flex-shrink: 0;
}
.vx-button.open .vx-caret { transform: rotate(180deg); }

/* List rendered into <body> with position:fixed → escapes modal scroll clip.
   z-index: 9999 keeps it above EVERY other layer (modals at 50–58, toast at 60). */
.vx-list {
  position: fixed;
  z-index: 9999;
  margin: 0;
  padding: 4px;
  list-style: none;
  /* Theme-aware surface — was hardcoded #0d0d15 (near-black) which made
     the dropdown render dark on the light theme regardless of the page bg. */
  background: var(--surface-hex);
  border: 1px solid var(--primary);
  border-radius: 10px;
  box-shadow:
    0 12px 32px rgba(15, 23, 42, 0.18),
    0 0 0 1px rgb(var(--color-primary) / 0.18);
  overflow-y: auto;
  /* Trap touch scrolling INSIDE the list so iOS rubber-banding doesn't
     bubble to the body. Without this, scrolling near the bottom of a
     long "Assigned to" list (many sales reps) on a phone occasionally
     pulled the underlying modal instead of the list, making the last
     rows feel "unreachable" — exactly what the user reported. */
  overscroll-behavior: contain;
  /* Momentum scroll on legacy iOS Safari — no-op on modern engines but
     harmless and makes long lists feel native. */
  -webkit-overflow-scrolling: touch;
  animation: vx-list-in 120ms ease-out;
}
[data-theme="dark"] .vx-list {
  /* Dark mode brings back the neon glow that reads on near-black bg. */
  box-shadow:
    0 12px 32px rgba(0, 0, 0, 0.60),
    0 0 0 1px rgb(var(--color-primary) / 0.20),
    0 0 22px rgb(var(--color-primary) / 0.18);
}
@keyframes vx-list-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.vx-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--fg);
  border-radius: 6px;
  cursor: pointer;
  user-select: none;
  transition: background 100ms;
}
.vx-option .ico { width: 16px; flex-shrink: 0; text-align: center; font-size: 13px; }
.vx-option .ico:empty { display: inline-block; }   /* keep alignment */
.vx-option .lbl {
  flex: 1;
  min-width: 0;                /* lets ellipsis kick in inside flex parent */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vx-option:hover,
.vx-option.active        { background: rgb(var(--color-primary) / 0.10); }
.vx-option.selected      { color: var(--primary); font-weight: 600; }
.vx-option.placeholder   { color: var(--muted-hex); font-style: italic; }

/* Hide the old CSS chevron (drawn by .select-wrap::after) when VxSelect
   has injected its own native-select wrapper. The button has its own caret. */
.select-wrap:has(> select.vx-native)::after { display: none; }

/* -------- Custom select chevron --------
   Strip the native arrow (positions inconsistently in RTL on macOS) and
   draw a chevron with pure CSS borders on a wrapper's ::after. No SVG
   data URIs — those broke under :focus on Chrome/macOS, tiling the icon
   across the field. The wrapper is added by Components.renderField(). */
select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
}

.select-wrap {
  position: relative;
  display: block;
}
/* `.select-wrap > select` has class+element specificity (0,1,1) so it wins
   over Tailwind's `.px-3` (0,1,0). Without this, filter dropdowns like
   `<select class="px-3 …">` keep Tailwind's narrow 12px end-padding and the
   chevron drawn at inset-inline-end:14px overlaps the option text. */
.select-wrap > select {
  width: 100%;
  padding-inline-end: 30px;
}

.select-wrap::after {
  content: '';
  position: absolute;
  inset-inline-end: 14px;
  top: 50%;
  width: 7px;
  height: 7px;
  border-right: 1.5px solid var(--muted-hex);
  border-bottom: 1.5px solid var(--muted-hex);
  transform: translateY(-75%) rotate(45deg);
  pointer-events: none;
  transition: border-color 120ms;
}
.select-wrap:focus-within::after {
  border-right-color: var(--primary);
  border-bottom-color: var(--primary);
}

.btn {
  padding: 8px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  border: 1px solid transparent;
  transition: all 120ms;
}
.btn-primary { background: rgba(0,255,209,0.15); border-color: var(--primary); color: var(--primary); }
.btn-primary:hover { background: rgba(0,255,209,0.25); }
.btn-danger { background: rgba(255,59,107,0.10); border-color: var(--danger); color: var(--danger); }
.btn-danger:hover { background: rgba(255,59,107,0.20); }
.btn-ghost { background: transparent; border-color: var(--border-hex); color: var(--fg); }
.btn-ghost:hover { border-color: var(--muted-hex); }
/* WhatsApp green — used for the per-lead "open WhatsApp" action so the
   button visually matches the destination platform (same hue as
   .interceptor-action.primary's accent). */
.btn-success { background: rgba(37,211,102,0.10); border-color: #25d366; color: #25d366; }
.btn-success:hover { background: rgba(37,211,102,0.20); }

/* Lead modal action row.
   The four buttons (Call / WhatsApp / Save / Delete-or-Request) must
   stay on a single row on phones — flex-nowrap forces it, and these
   rules make the buttons themselves narrow enough to actually fit at
   ~360px viewport width without horizontal scrolling. */
.lead-action-row .btn {
  padding: 8px 10px;
  font-size: 12.5px;
  white-space: nowrap;
  flex-shrink: 1;
  min-width: 0;
}
@media (min-width: 640px) {
  .lead-action-row .btn {
    padding: 8px 14px;
    font-size: 13px;
  }
}

/* Compact icon-style buttons used in admin tables (Users / Audit). */
.row-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-inline-start: 4px;
  font-size: 13px;
  border-radius: 6px;
  border: 1px solid var(--border-hex);
  background: transparent;
  color: var(--muted-hex);
  cursor: pointer;
  transition: all 120ms ease;
}
.row-action:hover:not(:disabled) { border-color: var(--muted-hex); color: #fff; }
.row-action.danger:hover:not(:disabled) {
  border-color: var(--danger);
  color: var(--danger);
  background: rgba(255,59,107,0.08);
}
.row-action.toggle:hover:not(:disabled),
.row-action.key:hover:not(:disabled) {
  border-color: var(--primary);
  color: var(--primary);
}
.row-action:disabled { opacity: 0.35; cursor: not-allowed; }

/* Phones get bigger row-action touch targets (≈ Apple HIG / Material 36-44px
   minimum). 28px is fine on a desktop with a mouse but too small for a
   thumb in card-stack mode where edit/reset/disable/delete sit side by
   side — small targets here were a real "can't edit users" complaint. */
@media (max-width: 639px) {
  .row-action {
    width: 36px;
    height: 36px;
    font-size: 15px;
    margin-inline-start: 0;        /* spacing handled by `.actions-cell { gap }` */
  }
}

/* -------- Leads page toolbar -----------------------------------------------
   Three-row stack on mobile, single-row inline on ≥640px. The deterministic
   structure keeps the toolbar predictable across breakpoints (no flex-wrap
   surprises). Each row is its own grid/flex group so adding filters in the
   future doesn't bleed into the actions row. */
.leads-toolbar {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.leads-filters {
  display: grid;
  grid-template-columns: 1fr 1fr;       /* 2-up on phones */
  gap: 8px;
}
/* Each filter must hold its longest label + chevron + padding without
   triggering ellipsis. "כל המשתמשים" needs ~100px text + ~50px chrome ≈
   150px. Setting min-width on the wrap (parent of the .vx-button) means
   the button — which is `width:100%` — gets a real lower bound. */
.leads-filters .select-wrap {
  min-width: 150px;
}
.leads-filter-wide {
  grid-column: 1 / -1;                  /* assignees spans full width on mobile */
}
.leads-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;       /* CSV | JSON 50/50 on mobile */
  gap: 8px;
}
.leads-action-primary {
  grid-column: 1 / -1;                  /* + New Lead full width on mobile */
}

@media (min-width: 640px) {
  /* Tablet+ layout: search stays full-width but filters and actions
     collapse onto the same row alongside it via flex. */
  .leads-toolbar {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }
  #q { flex: 1 1 220px; min-width: 220px; max-width: 360px; }
  .leads-filters {
    display: flex;
    flex-wrap: wrap;
    flex: 1 1 auto;
  }
  .leads-filter-wide {
    grid-column: auto;                  /* reset mobile span */
  }
  .leads-filters .select-wrap { flex: 0 0 auto; }
  .leads-actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
  }
  .leads-action-primary {
    grid-column: auto;                  /* reset mobile span */
  }
}

/* -------- Toasts (lightweight, stacked) ----------------------------------
   Theme-aware: every colour funnels through `--color-*` rgb-triplet vars
   (defined in :root for light, overridden under [data-theme="dark"] for
   dark) so the same rules render correctly in both modes. The previous
   build hardcoded dark-theme neon (#00ffd1, #c9fff0) for text + icon and
   a heavy black shadow — on the light background that washed the toast
   out almost completely. */
.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 500;
  background: var(--surface-hex);
  border: 1px solid var(--border-hex);
  /* Light theme: soft drop shadow only (no heavy black bloom). The dark
     override below adds a tighter, deeper shadow that reads on neon. */
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.10);
  color: var(--fg);
  transform: translateY(8px);
  opacity: 0;
  transition: transform 180ms ease, opacity 180ms ease;
  min-width: 220px;
  max-width: 360px;
  pointer-events: auto;
}
[data-theme="dark"] .toast {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.toast-in  { transform: translateY(0); opacity: 1; }
.toast-out { transform: translateY(8px); opacity: 0; }

.toast-icon {
  display: inline-flex;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 13px;
  flex-shrink: 0;
}

/* SUCCESS — primary brand colour. Border + icon use the theme primary,
   message text uses the regular fg so it stays readable on both bgs. */
.toast-success {
  border-color: rgb(var(--color-primary));
  color: var(--fg);
  box-shadow:
    0 0 0 1px rgb(var(--color-primary) / 0.15),
    0 6px 18px rgba(15, 23, 42, 0.10);
}
[data-theme="dark"] .toast-success {
  box-shadow:
    0 0 22px rgb(var(--color-primary) / 0.18),
    0 8px 24px rgba(0, 0, 0, 0.45);
}
.toast-success .toast-icon {
  background: rgb(var(--color-primary) / 0.15);
  color: rgb(var(--color-primary));
}

/* ERROR — danger colour. Same theme-aware pattern. */
.toast-error {
  border-color: rgb(var(--color-danger));
  color: var(--fg);
  box-shadow:
    0 0 0 1px rgb(var(--color-danger) / 0.15),
    0 6px 18px rgba(15, 23, 42, 0.10);
}
[data-theme="dark"] .toast-error {
  box-shadow:
    0 0 22px rgb(var(--color-danger) / 0.22),
    0 8px 24px rgba(0, 0, 0, 0.45);
}
.toast-error .toast-icon {
  background: rgb(var(--color-danger) / 0.18);
  color: rgb(var(--color-danger));
}

/* INFO — neutral. Picks up the theme border/muted colours. */
.toast-info {
  border-color: var(--border-hex);
  color: var(--fg);
}
.toast-info .toast-icon {
  background: rgb(var(--color-muted) / 0.15);
  color: var(--muted-hex);
}

/* LOADING — accent colour, same pattern. */
.toast-loading {
  border-color: rgb(var(--color-accent));
  color: var(--fg);
  box-shadow:
    0 0 0 1px rgb(var(--color-accent) / 0.15),
    0 6px 18px rgba(15, 23, 42, 0.10);
}
[data-theme="dark"] .toast-loading {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.toast-loading .toast-icon {
  background: rgb(var(--color-accent) / 0.18);
  color: rgb(var(--color-accent));
  animation: spin 1s linear infinite;
}

@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* -------- RTL adjustments --------
   Most flex/grid layout flips automatically once `dir="rtl"` is set on
   <html>. These rules cover the spots that don't, plus typography. */
[dir="rtl"] body {
  font-family: 'Heebo', 'Rubik', 'Inter', system-ui, sans-serif;
}

/* Toast lives in `bottom-6 end-6` (logical) — Tailwind's `end-*` utilities
   already flip, so no override needed. Force LTR inside code blocks so
   the URL example reads correctly even on Hebrew pages. */
[dir="rtl"] pre code { direction: ltr; text-align: left; display: block; }

/* Date inputs: keep digits LTR even on RTL pages for native pickers. */
[dir="rtl"] input[type="date"],
[dir="rtl"] input[type="email"],
[dir="rtl"] input[type="tel"] { direction: ltr; text-align: end; }

/* Field labels look better aligned to start in either direction. */
.field label { text-align: start; }

/* -------- Settings page layout ----------------------------------------------
   Branding rows: label + value side-by-side on roomy screens, but stacked on
   phones so long Hebrew/English values never get clipped or clash with the
   label. `min-width: 0` lets long words break inside flex columns. */
.settings-grid { max-width: 100%; }
.settings-dl > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  min-width: 0;
  padding: 0.25rem 0;
}
.settings-dl dt { flex-shrink: 0; }
.settings-dl dd {
  text-align: end;
  word-break: break-word;
  overflow-wrap: anywhere;
  min-width: 0;
}
@media (max-width: 539px) {
  .settings-dl > div {
    flex-direction: column;
    align-items: stretch;
    gap: 0.125rem;
  }
  .settings-dl dd { text-align: start; }
}
/* Breathing room between explanatory copy and the row of action buttons. */
.settings-actions { margin-top: 0.25rem; }
.settings-actions .btn { flex: 1 1 auto; min-width: 140px; text-align: center; }
@media (max-width: 539px) {
  .settings-actions .btn { flex-basis: 100%; }
}

/* Webhook code block: wrap long lines on phones (default `pre` keeps them on
   one line and forces horizontal scroll, which feels broken inside a
   100vw-capped page). On wider screens, fall back to scroll. */
.webhook-pre {
  white-space: pre;
  overflow-x: auto;
  max-width: 100%;
}
@media (max-width: 639px) {
  .webhook-pre {
    white-space: pre-wrap;
    word-break: break-all;
    overflow-x: hidden;
  }
}

/* -------- Idle Sales Reps list (Dashboard admin tile) ---------------------
   Compact list inside the Sales Reps card showing each idle rep + their
   exact inactivity duration. Layout: name on the start, duration on the
   end with tabular-nums so the column visually aligns. */
.idle-block {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border-hex);
}
.idle-block-title {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 10px;
  font-weight: 600;
  color: var(--muted-hex);
  margin-bottom: 6px;
}
.idle-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 160px;
  overflow-y: auto;
}
.idle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 5px 0;
  font-size: 12px;
  border-bottom: 1px solid var(--border-hex);
}
.idle-row:last-child { border-bottom: 0; }
.idle-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}
.idle-duration {
  color: var(--warn);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.idle-empty {
  font-size: 12px;
  color: var(--muted-hex);
  padding: 8px 0;
}

/* -------- Lead Score Chip (Module 1) --------------------------------------
   Compact 0–100 number badge alongside the lead name. 4 tiers, each with
   a tier-specific colour. The HOT tier carries a subtle neon glow that
   matches the brand primary; the other tiers stay flat so the heatmap chip
   in the same row remains the primary "needs attention NOW" signal — score
   is steady-state info, heatmap is the urgency cue. */
.score-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 18px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  border: 1px solid;
  margin-inline-start: 8px;
  cursor: default;
  flex-shrink: 0;
  vertical-align: middle;
  /* Tabular numerals so a "100" doesn't shift the row width vs a "47". */
  font-variant-numeric: tabular-nums;
  /* Smooth tier transitions — when an interaction lands and the score
     jumps from cool→warm, the colour fades over 280ms so the change is
     felt rather than jarring. */
  transition:
    color            280ms ease,
    border-color     280ms ease,
    background-color 280ms ease,
    box-shadow       320ms ease;
}

.score-chip-hot {
  color: var(--primary);
  border-color: var(--primary);
  background: rgba(0, 255, 209, 0.10);
  /* Subtle neon halo — present, not screaming. The primary token keeps it
     on-brand; alpha 0.30 is the threshold where it reads as "lit" without
     bleeding into adjacent cells. */
  box-shadow: 0 0 8px rgba(0, 255, 209, 0.30);
}
.score-chip-warm {
  color: #5eead4;
  border-color: #5eead4;
  background: rgba(94, 234, 212, 0.08);
}
.score-chip-cool {
  color: var(--warn);
  border-color: rgba(255, 179, 71, 0.55);
  background: rgba(255, 179, 71, 0.06);
}
.score-chip-cold {
  color: var(--muted-hex);
  border-color: #2a2a3a;
  background: rgba(255, 255, 255, 0.02);
}

/* -------- User Menu (header dropdown) -------------------------------------
   Replaces the floating user-chip + bare logout-icon pair with a structured
   profile trigger: avatar circle (initials) + display name → click to open
   a dropdown with the full profile meta and a clear icon+text logout.

   Why a dropdown: identity + sign-out belong in one mental "user" surface
   so the agent doesn't hunt for either. Avatar gradient uses brand tokens
   (primary→accent) so the chrome stays on-palette without an image. */
.user-menu {
  position: relative;
}
.user-menu-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 4px 4px;
  border-radius: 999px;
  border: 1px solid var(--border-hex);
  background: rgba(255, 255, 255, 0.02);
  color: var(--fg);
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  transition: border-color 120ms, background 120ms;
  flex-shrink: 0;
}
.user-menu-trigger:hover,
.user-menu-trigger[aria-expanded="true"] {
  border-color: var(--primary);
  background: rgba(0, 255, 209, 0.04);
}
.user-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--primary), var(--accent));
  color: var(--bg-hex);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 13px;
  text-transform: uppercase;
  flex-shrink: 0;
  box-shadow: 0 0 8px rgba(0, 255, 209, 0.20);
}
.user-menu-name {
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.user-menu-caret {
  font-size: 9px;
  color: var(--muted-hex);
  transition: transform 200ms ease;
  margin-inline-start: 2px;
}
.user-menu-trigger[aria-expanded="true"] .user-menu-caret {
  transform: rotate(180deg);
}

/* On phones the avatar is enough — drop the name to reclaim space. The
   user can still see their full name in the dropdown panel header. */
@media (max-width: 539px) {
  .user-menu-name { display: none; }
  .user-menu-trigger { padding: 4px; }
}

.user-menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  inset-inline-end: 0;
  min-width: 240px;
  background: var(--surface-hex);
  border: 1px solid var(--border-hex);
  border-radius: 12px;
  box-shadow:
    0 16px 40px rgba(0, 0, 0, 0.60),
    0 0 24px rgba(0, 255, 209, 0.12);
  z-index: 50;
  overflow: hidden;
  animation: user-menu-in 140ms ease-out;
}
@keyframes user-menu-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0);    }
}

/* On mobile we keep the panel `position: absolute` (inherited from the
   default rule above) so it always opens directly under the
   .user-menu-trigger pill. The previous `position: fixed` with a
   hardcoded `top: 56px + safe-area` was off by 8–16px depending on
   the actual header height, and could clip the top of the panel onto
   the trigger itself. Anchoring to the .user-menu container keeps
   the panel locked to the button at every breakpoint and notch. */
@media (max-width: 767px) {
  .user-menu-panel {
    min-width: 220px;
    max-width: calc(100vw - 24px);
  }
}

.user-menu-header {
  padding: 14px 16px;
  border-bottom: 1px solid var(--border-hex);
  background: rgba(0, 255, 209, 0.03);
}
.user-menu-display {
  font-weight: 600;
  font-size: 14px;
  color: var(--fg);
  word-break: break-word;
}
.user-menu-meta {
  font-size: 11px;
  color: var(--muted-hex);
  margin-top: 2px;
  word-break: break-word;
  letter-spacing: 0.02em;
}
.user-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 16px;
  background: transparent;
  border: 0;
  color: var(--fg);
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  text-align: start;
  transition: background 120ms;
}
.user-menu-item:hover { background: rgba(255, 255, 255, 0.04); }
.user-menu-item-icon {
  width: 16px;
  display: inline-flex;
  justify-content: center;
  font-size: 14px;
}
.user-menu-item-danger { color: var(--danger); }
.user-menu-item-danger:hover { background: rgba(255, 59, 107, 0.06); }

/* -------- Critical Leads list (Dashboard widget) --------------------------
   Top-N table-like list inside a tile. Click-to-act, score chip + heat chip
   + stale badge ride alongside the name for at-a-glance triage. Replaces
   the audit log widget that used to live here (now in Settings).
   The rank column uses tabular-nums so 1-9 vs 10 don't shift the row. */
.critical-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.critical-item {
  display: grid;
  /* Track sizing — four-band layout with FIXED X-positions for the
     score + heat chips per product feedback ("שתקבע את המיקום של
     המספר ושל החם … שיהיו באותם רווחים ומבחינת המיקום שלהם" —
     fix the position of the number and Hot at the same spaces and
     positions across every row).
       • Band 1 (rank)       28px      — fixed.
       • Band 2 (name)       1fr       — fills slack, ellipsis-truncates.
       • Band 3 (meta)       auto      — content-sized to the FIXED
                                         meta sub-grid (see below), so
                                         it occupies the same width on
                                         every row regardless of which
                                         tier label or score digits the
                                         row carries.
       • Band 4 (assignee)   110px     — fixed.
     Because every track except `name` is fixed-width, the `name`
     column ends at the same X-coordinate on every row, which means
     the meta cluster STARTS at the same X-coordinate on every row
     (= "באותם רווחים"). Within meta, each chip slot is also fixed
     width, so the score numeral sits at the same X and the heat
     chip starts at the same X across every row. Mobile (≤639px)
     overrides this back to a flex layout so phones can compress
     gracefully (no assignee column, no fixed slot widths). */
  grid-template-columns: 28px 1fr auto 110px;
  align-items: center;
  gap: 12px;
  padding: 10px 4px;
  border-bottom: 1px solid rgba(31, 31, 46, 0.6);
  cursor: pointer;
  transition: background 120ms ease;
}
.critical-item:last-child { border-bottom: 0; }
.critical-item:hover { background: rgba(255, 255, 255, 0.02); }
.critical-rank {
  color: var(--muted-hex);
  font-weight: 700;
  font-size: 12px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.critical-name {
  font-weight: 500;
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.critical-meta {
  /* Sub-grid with three FIXED slots so the score numeral and heat chip
     sit at the same offsets on every row — the user's pinned-position
     requirement. Slot sizes were chosen against the widest realistic
     content per locale:
       • Score 36px  — fits a centred "100" on tabular-nums.
       • Heat  72px  — fits "🌤  חמים" (Hebrew, widest tier) and
                       "👻 Ghost" (English, widest tier) with breathing
                       room; chip's inner content is centred (see below).
       • Stale 50px  — fits "STALE" / "תקוע"; left empty when the row
                       isn't stale, but the slot is reserved so the
                       cluster keeps a fixed width either way.
     Total meta cluster width: 36 + 72 + 50 + 12 (gaps) = 170px.
     `justify-self: end` anchors the cluster to the inline-end of its
     auto column (defensive — the column is its content width, but
     this stays correct even if a future change adds slack). */
  display: grid;
  grid-template-columns: 36px 72px 50px;
  align-items: center;
  gap: 6px;
  justify-self: end;
}
/* Per-slot width pins. Each chip has a default min-width / margin that
   would otherwise push the cluster around — we override here in the
   `.critical-meta` scope only so other usages (lead table, etc.) keep
   their natural sizing. */
.critical-meta > .score-chip {
  width: 36px;
  min-width: 0;
  margin-inline-start: 0;
}
.critical-meta > .heat {
  width: 72px;
  /* Centre the emoji+label pair inside the fixed-width chip so a 2-char
     "חם" and a 4-char "חמים" both look balanced rather than left-pinned
     with trailing empty space. */
  justify-content: center;
  padding-inline: 4px;
}
.critical-meta > .stale-badge {
  margin-inline-start: 0;
}
.critical-assignee {
  text-align: end;
  width: 110px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 639px) {
  /* On phones, drop the assignee column and let the name take the slack.
     Switch the meta cluster back to inline-flex so the score/heat/stale
     chips compress to their natural widths — fixed X-positions aren't
     useful on a single-tile-wide phone screen, and locking 170px of
     reserved space would steal too much room from the name. */
  .critical-item {
    grid-template-columns: 24px 1fr auto;
  }
  .critical-assignee { display: none; }
  .critical-meta {
    display: inline-flex;
    grid-template-columns: none;
    flex-wrap: nowrap;
    white-space: nowrap;
    justify-self: auto;
  }
  .critical-meta > .score-chip,
  .critical-meta > .heat {
    width: auto;
  }
}

/* -------- Bulk Action Bar (Power Pack) ------------------------------------
   Floating action bar that slides up from the bottom of the viewport when
   ≥1 leads are selected in the leads table. Uses transform-only animation
   on the compositor layer — buttery smooth at 60fps with zero layout cost.
   240ms ease-out matches premium-OS sheet-presentation feel; the offset
   curve `cubic-bezier(0.2, 0.8, 0.2, 1)` overshoots slightly into place
   for that "snappy stop" instead of a flat decel. */
.bulk-bar {
  position: fixed;
  bottom: max(20px, env(safe-area-inset-bottom));
  left: 50%;
  transform: translate(-50%, 130%);   /* hidden — sits BELOW viewport */
  z-index: 55;                          /* above modals at 50, below interceptor at 58 */
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--surface-hex);
  border: 1px solid var(--primary);
  border-radius: 14px;
  padding: 10px 14px;
  box-shadow:
    0 16px 40px rgba(0, 0, 0, 0.60),
    0 0 24px rgba(0, 255, 209, 0.18);
  /* The 240ms ease-out + custom curve is what gives this its "premium OS"
     feel — animations any longer feel sluggish, any shorter feel jumpy. */
  transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1),
              opacity   240ms ease-out;
  opacity: 0;
  pointer-events: none;
  max-width: calc(100vw - 32px);
  will-change: transform, opacity;
}
.bulk-bar[data-state="visible"] {
  transform: translate(-50%, 0);
  opacity: 1;
  pointer-events: auto;
}
.bulk-bar-cancel {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--border-hex);
  background: transparent;
  color: var(--muted-hex);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  flex-shrink: 0;
  transition: border-color 120ms, color 120ms;
}
.bulk-bar-cancel:hover {
  border-color: var(--danger);
  color: var(--danger);
}
.bulk-bar-count {
  font-weight: 600;
  font-size: 13px;
  color: var(--primary);
  white-space: nowrap;
  flex-shrink: 0;
}
.bulk-bar-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.bulk-bar-actions .btn { padding: 6px 12px; font-size: 12px; }

/* On phones the bar fills horizontally and slides up from the bottom edge,
   actions stack to full-width so each tap target is easy to hit. */
@media (max-width: 539px) {
  .bulk-bar {
    left: 12px;
    right: 12px;
    transform: translateY(130%);
    max-width: none;
  }
  .bulk-bar[data-state="visible"] {
    transform: translateY(0);
  }
  .bulk-bar-actions {
    width: 100%;
    justify-content: stretch;
  }
  .bulk-bar-actions .btn {
    flex: 1 1 auto;
    min-width: 0;
    text-align: center;
  }
}

/* Honour reduced-motion preference — slide becomes a fade. */
@media (prefers-reduced-motion: reduce) {
  .bulk-bar { transition: opacity 200ms ease-out !important; transform: translate(-50%, 0) !important; }
  .bulk-bar[data-state="hidden"] { opacity: 0 !important; pointer-events: none !important; }
  @media (max-width: 539px) {
    .bulk-bar { transform: none !important; }
  }
}

/* -------- Saved Views chips (Power Pack) ----------------------------------
   Horizontal scrollable strip above the leads filter row. Each chip is a
   one-click filter recall; a × button removes it. The dashed-border "+"
   chip at the end captures current filters into a new view. */
.saved-view-chips {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.saved-view-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--border-hex);
  background: rgba(255, 255, 255, 0.02);
  font-size: 12px;
  color: var(--fg);
  cursor: pointer;
  transition: all 120ms ease;
  font-family: inherit;
}
.saved-view-chip:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: rgba(0, 255, 209, 0.05);
}
.saved-view-chip-x {
  background: transparent;
  border: 0;
  color: var(--muted-hex);
  font-size: 14px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
  transition: color 120ms ease;
}
.saved-view-chip-x:hover { color: var(--danger); }
.saved-view-add {
  border-style: dashed;
  color: var(--muted-hex);
}
.saved-view-add:hover {
  color: var(--primary);
}

/* -------- Checkbox column in leadTable (Power Pack) -----------------------
   Width-capped so it doesn't hog space; visible at every breakpoint
   because multi-select is the primary UX win on mobile too. */
.crm th.bulk-col,
.crm td.bulk-col {
  width: 40px;
  padding-inline: 10px;
  text-align: center;
}
.crm .bulk-checkbox,
.crm #bulk-master {
  cursor: pointer;
}

/* -------- Stale Badge (Module 3 — Ghost Lead resurrection) ----------------
   Small "STALE" pill that appears beside the score chip when a lead has
   gone untouched beyond the configured threshold. Slow 2.4s breathing
   pulse — distinct from the heatmap "hot" pulse (1.6s) and the live-ping
   reconnecting flash (0.8s), so the three signals stay differentiable.

   GPU-only animation: ONLY the inner dot animates (transform + opacity).
   Border + background stay static so the row doesn't reflow or trigger
   paint on every animation frame. Idle CPU cost: 0%. */
.stale-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0 8px;
  height: 18px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  border: 1px solid var(--danger);
  color: var(--danger);
  background: rgba(255, 59, 107, 0.06);
  margin-inline-start: 6px;
  cursor: default;
  flex-shrink: 0;
  vertical-align: middle;
  /* Soft outer glow — subtle, on-brand, never screaming. */
  box-shadow: 0 0 6px rgba(255, 59, 107, 0.18);
}
.stale-dot {
  display: block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 4px rgba(255, 59, 107, 0.6);
  /* Slow breathing — 2.4s feels like a measured "warning" rather than an
     anxious alert. The keyframes only touch transform + opacity, both
     are compositor-layer properties, no layout/paint cost. */
  animation: stale-breathe 2.4s ease-in-out infinite;
  will-change: transform, opacity;
}
@keyframes stale-breathe {
  0%, 100% { opacity: 0.55; transform: scale(1);    }
  50%      { opacity: 1;    transform: scale(1.35); }
}

/* Honour reduced-motion preference — keep the colour signal, drop the
   pulsing. Visually still reads as a STALE warning without the kinesis. */
@media (prefers-reduced-motion: reduce) {
  .stale-dot { animation: none !important; opacity: 0.9; }
}

/* -------- Stale Banner (in lead detail modal) ----------------------------
   Prominent attention strip at the top of the lead modal when isStale.
   Two action buttons: "Send Standard Follow-up" (primary) and "Open
   WhatsApp" (ghost). The banner uses a single subtle danger-tinted
   surface, not the badge's pulsing dot — the modal context already
   conveys "this needs attention", so the banner stays calm and
   actionable rather than agitating. */
.stale-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  margin-bottom: 14px;
  border: 1px solid var(--danger);
  border-radius: 12px;
  background: rgba(255, 59, 107, 0.07);
  box-shadow: 0 0 12px rgba(255, 59, 107, 0.10);
}
.stale-banner-icon {
  font-size: 18px;
  color: var(--danger);
  line-height: 1;
  flex-shrink: 0;
}
.stale-banner-text {
  flex: 1 1 200px;
  font-size: 13px;
  color: #ffd2dc;
}
.stale-banner-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
@media (max-width: 539px) {
  /* Stack actions full-width on phones so the buttons stay tappable. */
  .stale-banner-actions { width: 100%; }
  .stale-banner-actions .btn { flex: 1 1 100%; }
}

/* -------- Live Ping (realtime SSE indicator) ------------------------------
   A small dot in the header that reflects the current EventSource connection
   state. Three states, each with its own colour and animation rhythm:

     offline       muted gray, no animation     — disconnected or pre-connect
     reconnecting  amber, fast pulse (0.8s)     — browser is auto-retrying
     live          neon cyan, gentle pulse (2s) — receiving events

   Animations are GPU-friendly (transform + opacity only — box-shadow stays
   static so it doesn't trigger paint on every frame). Idle CPU cost: 0%.
   The container is 14×14 to give a comfortable click target while keeping
   the visual dot at 8px. */
.live-ping {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  cursor: default;
  flex-shrink: 0;
}
.live-ping-dot {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #4a4a5e;
  /* Smooth colour & glow change between states — the eye picks up the
     transition as a "system woke up" signal. */
  transition:
    background-color 280ms ease,
    box-shadow       320ms ease;
  will-change: transform, opacity;
}

.live-ping[data-state="offline"] .live-ping-dot {
  background: #4a4a5e;
  box-shadow: none;
  opacity: 0.85;
}

.live-ping[data-state="reconnecting"] .live-ping-dot {
  background: var(--warn);
  box-shadow: 0 0 6px rgba(255, 179, 71, 0.5);
  animation: live-pulse-fast 0.8s ease-in-out infinite;
}

.live-ping[data-state="live"] .live-ping-dot {
  background: var(--primary);
  box-shadow: 0 0 8px rgba(0, 255, 209, 0.55);
  animation: live-pulse 2s ease-in-out infinite;
}

@keyframes live-pulse {
  0%, 100% { opacity: 0.7; transform: scale(1);    }
  50%      { opacity: 1;   transform: scale(1.18); }
}
@keyframes live-pulse-fast {
  0%, 100% { opacity: 0.5; transform: scale(1);   }
  50%      { opacity: 1;   transform: scale(1.2); }
}

/* Honour reduced-motion preference — keep the colour signal, drop the
   pulsing. Visually still reads as the right state without the kinesis. */
@media (prefers-reduced-motion: reduce) {
  .live-ping-dot { animation: none !important; }
}

/* The neon glow on the gate panel is symmetrical; no fix needed. */

/* ===========================================================================
   Sales Leaderboard — "Top Performers" widget
   ===========================================================================
   Composite ranking strip with three numeric metrics + live/idle pill.
   Grid layout collapses to two rows on phones; metrics stay horizontally
   readable so admins can scan rankings on mobile. */
.lb-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.lb-row {
  display: grid;
  grid-template-columns: 36px 1fr auto auto;
  align-items: center;
  gap: 14px;
  padding: 10px 4px;
  border-bottom: 1px solid rgba(127, 127, 150, 0.12);
}
.lb-row:last-child { border-bottom: 0; }
.lb-row[data-rank="1"] .lb-name { color: var(--primary); }
.lb-rank {
  font-size: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--muted-hex);
  font-weight: 700;
}
.lb-name {
  font-weight: 500;
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.lb-metrics {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  flex-shrink: 0;
}
.lb-metric {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-variant-numeric: tabular-nums;
}
.lb-metric-num {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg);
}
.lb-metric-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted-hex);
}
.lb-metric-hot .lb-metric-num { color: var(--primary); }
.lb-status-cell { flex-shrink: 0; }
.lb-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
}
.lb-status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  display: inline-block;
}
.lb-status-live {
  background: rgba(0, 184, 148, 0.10);
  color: var(--primary);
  border: 1px solid rgba(0, 184, 148, 0.30);
}
.lb-status-live .lb-status-dot {
  background: var(--primary);
  box-shadow: 0 0 6px var(--primary);
  animation: live-pulse 2s ease-in-out infinite;
}
.lb-status-idle {
  background: rgba(127, 127, 150, 0.08);
  color: var(--muted-hex);
  border: 1px solid rgba(127, 127, 150, 0.20);
}
.lb-status-idle .lb-status-dot {
  background: var(--muted-hex);
  opacity: 0.7;
}
@media (max-width: 639px) {
  /* Phones: drop status pill into a second row underneath the name to
     preserve readable metric numerals without a horizontal scroll. */
  .lb-row {
    grid-template-columns: 32px 1fr auto;
    grid-template-rows: auto auto;
    row-gap: 4px;
  }
  .lb-status-cell {
    grid-column: 2 / -1;
    grid-row: 2;
    justify-self: start;
  }
  .lb-metrics {
    grid-row: 1;
    grid-column: 3;
    gap: 10px;
  }
  /* Per product feedback: keep the unit labels visible on mobile too
     (היום / השבוע / 🔥 חם) — they carry context the bare numerals can't.
     Compress them so they still fit alongside the numerals at narrow
     widths. */
  .lb-metric-label {
    font-size: 9px;
    letter-spacing: 0.02em;
  }
  .lb-metric { gap: 3px; }
  .lb-metrics { gap: 8px; }
}
@media (prefers-reduced-motion: reduce) {
  .lb-status-live .lb-status-dot { animation: none; }
}

/* When the palette navigates a user into the Users view, briefly highlight
   the destination row so the user knows where they landed. */
.user-flash > td { background: rgba(0, 184, 148, 0.10); transition: background 1200ms ease-out; }
[data-theme="dark"] .user-flash > td { background: rgba(0, 255, 209, 0.08); }

/* ===========================================================================
   Command Palette — Cmd+K / Ctrl+K
   ===========================================================================
   Centered modal with a searchable result list. z-60 so it floats above
   every other modal (lead modal at z-50, interceptor at z-58). The card
   sits ~18vh from the top — Mac Spotlight style — so the keyboard on
   mobile doesn't push it off-screen when typing. */
/* CRITICAL: do NOT put `display: flex` on `.palette` directly.
   Tailwind's `.hidden { display: none }` and `.palette { display: flex }`
   have equal specificity (single class each), and style.css loads AFTER
   tailwind.css, so `.palette` would win the cascade — making the
   overlay permanently visible regardless of the `.hidden` class. We
   gate `display: flex` behind `:not(.hidden)` so Tailwind's hidden
   utility actually works on the palette. */
.palette {
  position: fixed;
  inset: 0;
  z-index: 60;
  align-items: flex-start;
  justify-content: center;
  padding-top: 14vh;
  padding-left: 16px;
  padding-right: 16px;
}
.palette:not(.hidden) {
  display: flex;
}
.palette[data-state="open"] { animation: palette-fade-in 140ms ease-out; }
.palette-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
[data-theme="dark"] .palette-backdrop { background: rgba(0, 0, 0, 0.70); }
.palette-card {
  position: relative;
  width: 100%;
  max-width: 580px;
  background: var(--surface-hex);
  border: 1px solid var(--border-hex);
  border-radius: 14px;
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.30),
    0 0 0 1px rgba(0, 184, 148, 0.10);
  overflow: hidden;
  animation: palette-slide-in 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
[data-theme="dark"] .palette-card {
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.60),
    0 0 0 1px rgba(0, 255, 209, 0.10);
}
.palette-input-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border-hex);
}
.palette-icon {
  font-size: 16px;
  color: var(--primary);
  font-weight: 700;
  letter-spacing: -0.5px;
}
.palette-input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--fg);
  font-size: 16px;            /* 16px keeps iOS from zooming on focus */
  font-weight: 400;
  padding: 0;
}
.palette-input::placeholder { color: var(--muted-hex); }
.palette-kbd {
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  border-radius: 5px;
  background: rgba(127, 127, 150, 0.10);
  border: 1px solid var(--border-hex);
  color: var(--muted-hex);
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.04em;
}

/* When the .palette-kbd is also rendered as a <button> (the ESC dismiss
   button at the top of the palette), it needs to feel actionable —
   clickable cursor, hover state, focus ring. The base styles above keep
   the visual character of a key cap so it still reads as a keyboard hint. */
button.palette-kbd,
.palette-close-btn {
  cursor: pointer;
  font-weight: 600;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
  -webkit-appearance: none;
  appearance: none;
}
button.palette-kbd:hover,
.palette-close-btn:hover {
  background: rgba(225, 29, 72, 0.10);   /* danger tint */
  border-color: var(--danger);
  color: var(--danger);
}
button.palette-kbd:focus-visible,
.palette-close-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
.palette-results {
  max-height: 56vh;
  overflow-y: auto;
  padding: 6px 0;
}
.palette-empty {
  padding: 28px 16px;
  text-align: center;
  color: var(--muted-hex);
  font-size: 13px;
}
.palette-group-heading {
  padding: 8px 16px 4px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-hex);
  font-weight: 600;
}
.palette-row {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 8px 16px;
  border: 0;
  background: transparent;
  cursor: pointer;
  text-align: start;
  color: var(--fg);
  transition: background 80ms ease;
}
.palette-row:focus { outline: 0; }
.palette-row-active {
  background: rgba(0, 184, 148, 0.08);
}
[data-theme="dark"] .palette-row-active { background: rgba(0, 255, 209, 0.08); }
.palette-row-icon {
  font-size: 14px;
  text-align: center;
  opacity: 0.85;
}
.palette-row-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.palette-row-title {
  font-size: 14px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.palette-row-sub {
  font-size: 11px;
  color: var(--muted-hex);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.palette-row-meta {
  font-size: 11px;
  color: var(--muted-hex);
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(127, 127, 150, 0.10);
  flex-shrink: 0;
  white-space: nowrap;
}
.palette-footer {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 16px;
  border-top: 1px solid var(--border-hex);
  font-size: 11px;
  color: var(--muted-hex);
  flex-wrap: wrap;
}
.palette-hint {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.palette-hint kbd {
  display: inline-flex;
  align-items: center;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(127, 127, 150, 0.10);
  border: 1px solid var(--border-hex);
  font-size: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fg);
}
@keyframes palette-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes palette-slide-in {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .palette[data-state="open"],
  .palette-card { animation: none; }
}
@media (max-width: 639px) {
  .palette {
    /* On phones, sit closer to the top so the keyboard doesn't shove the
       results out of view. The card itself shrinks naturally via max-width. */
    padding-top: 8vh;
  }
  .palette-footer { display: none; }   /* keyboard hints don't apply on touch */
}

