/* ==========================================================================
   ZETIOR — CUSTOM STYLES
   ==========================================================================

   The single home for ALL custom CSS and styling overrides across the app.
   Loaded LAST (after styles.css / app.css) in every panel header, so rules
   here win the cascade without needing !important.

   Loaded by:
     - app/Views/admin/components/header.php      (Admin panel)
     - app/Views/dashboard/components/header.php  (Tenant dashboard)
     - app/Views/auth/components/header.php       (Auth screens)

   ---------------------------------------------------------------------------
   RULES OF THE ROAD
   ---------------------------------------------------------------------------
   1. Write custom CSS HERE — not inline `style=""` in views. Inline styles are
      only for values that are genuinely dynamic (computed per-row/per-record in
      PHP or Vue, e.g. a progress bar width).

   2. DO NOT rely on new Tailwind utility classes in views. The portal ships a
      PREBUILT Tailwind bundle: only classes that existed at build time are
      compiled. A brand-new arbitrary utility (e.g. `h-[25px]`) silently does
      NOTHING. Add a semantic class here instead.

   3. Put each rule in the section it belongs to, under the panel scope it
      applies to. Prefer app-wide (Section 2+) over panel-specific (Section 8)
      when the styling should be consistent everywhere.

   4. Keep selectors low-specificity and semantic (`.app-header-logo`), not
      brittle chains (`header > div > a > img`).

   ---------------------------------------------------------------------------
   TABLE OF CONTENTS
   ---------------------------------------------------------------------------
   1. Variables / tokens
   2. Base & resets
   3. Layout (header, sidebar, footer, containers)
   4. Branding (logos, marks)
   5. Components (buttons, badges, cards, modals)
   6. Forms & inputs
   7. Tables & data grids
   8. Panel-specific overrides (admin / dashboard / auth)
   9. Utilities & helpers
  10. Responsive tweaks
  11. Print
   ========================================================================== */


/* ==========================================================================
   1. VARIABLES / TOKENS
   Central knobs. Reference these instead of hardcoding repeated values.
   ========================================================================== */

:root {
  /* Brand */
  --zx-brand: #0d5c63;
  --zx-brand-dark: #0b4f51;

  /*
   * Brand ramp.
   *
   * The KT theme ships only a blue --primary and hardcodes literal
   * --color-blue-* into a few primary component variants (badges, outline
   * buttons). Without a teal ramp of our own there is nothing to point those
   * at, which is why a teal "New Tag" button used to sit beside a BLUE
   * "12 total" badge on the same row.
   *
   * --zx-brand-600 is the brand mark itself. Contrast checked against white:
   * 600 = 7.7:1 (AA + AAA body text). The dark-mode primary is deliberately
   * LIGHTER (--zx-brand-550) so white button text still clears AA (5.4:1)
   * while the fill stays 3.5:1 against the slate-950 canvas — #0d5c63 is too
   * dark to read as a filled control on a dark background.
   */
  --zx-brand-50:  #eef6f6;
  --zx-brand-100: #d5e9e9;
  --zx-brand-200: #aed6d7;
  --zx-brand-300: #7cbcbe;
  --zx-brand-400: #4a9ba0;
  --zx-brand-500: #1f7a80;
  --zx-brand-550: #14757d;  /* dark-mode primary fill */
  --zx-brand-600: #0d5c63;  /* = --zx-brand */
  --zx-brand-700: #0b4f51;  /* = --zx-brand-dark */
  --zx-brand-800: #0a4144;
  --zx-brand-900: #083336;

  /*
   * Default primary for every surface.
   *
   * The tenant dashboard emits its own `:root{--primary:…}` from the org's
   * branding_primary_color in dashboard/components/header.php — that <style>
   * block is LATER in the document than this file, so per-org white-label
   * still wins. This only supplies the default, which previously existed on
   * the dashboard alone: admin, auth and the installer inherited KT's blue,
   * so one product rendered in two different primaries.
   *
   * --ring was blue on ALL surfaces including the dashboard, which put a blue
   * focus ring on a teal button. Derived from the ramp so it tracks the brand.
   */
  --primary: var(--zx-brand-600);
  --color-primary: var(--zx-brand-600);
  --ring: var(--zx-brand-500);

  /* Branding sizes */
  --zx-header-logo-height: 25px;   /* mobile/topbar mark height */
  --zx-sidebar-logo-height: 22px;  /* sidebar mark height */

  /* Chat / inbox */
  --zx-chat-list-width: 380px;     /* conversation list pane */
  --zx-chat-header-height: 3.5rem; /* both pane headers */
  /* Chat visual refresh — brand-aligned "gradient wash" (docs/006 Tier 3.1/3.2). */
  --zx-chat-bubble-in: #ffffff;
  --zx-chat-bubble-out: #cfe7e3;             /* teal-mint (Zetior brand), not WhatsApp green */
  --zx-chat-canvas: #f5f8f8;                 /* fallback flat for the gradient below */
  --zx-chat-canvas-grad: linear-gradient(180deg, #e8f0ef 0%, #f5f8f8 40%);
  --zx-chat-bubble-radius: 16px;
  --zx-chat-bubble-notch: 5px;               /* the one squared corner on each side */
  --zx-chat-bubble-shadow: 0 1px 2px rgba(16, 40, 40, .07);
  --zx-chat-tick-read: #0ea5e9;
}
:root[data-theme="dark"], .dark {
  /* Teal-tinted dark equivalents. */
  --zx-chat-canvas-grad: linear-gradient(180deg, #0f1f1e 0%, #0b1413 40%);
  --zx-chat-bubble-in-dark: #172221;
  --zx-chat-bubble-out-dark: #12352f;

  /* Lighter fill so white button text still clears AA on a dark canvas. */
  --primary: var(--zx-brand-550);
  --color-primary: var(--zx-brand-550);
  --ring: var(--zx-brand-400);
}


/* ==========================================================================
   2. BASE & RESETS
   App-wide element defaults. Keep this section small.
   ========================================================================== */

/* (none yet) */


/* ==========================================================================
   3. LAYOUT
   Header, sidebar, footer, page containers.
   ========================================================================== */

/* (none yet) */


/* ==========================================================================
   4. BRANDING
   Logos and brand marks.
   ========================================================================== */

/*
 * Header logo mark (mobile/topbar).
 *
 * The source images are large squares (mini-logo.png is 1080x1080), so the
 * height MUST be constrained or the raw image renders at full size. Width is
 * auto so the aspect ratio is preserved and the mark never distorts.
 *
 * Lives here (not as a Tailwind class) because the portal Tailwind bundle is
 * prebuilt — `h-[25px]`/`w-auto` are not compiled into it and would no-op.
 *
 * Used by: admin + dashboard header (mobile logo).
 */
.app-header-logo {
  height: var(--zx-header-logo-height);
  width: auto;
  display: block;
}

/*
 * Sidebar logo marks (.default-logo = expanded, .small-logo = collapsed).
 *
 * Size ONLY — never set `display` here. The KT theme toggles these marks via
 * `display` on sidebar collapse (styles.css: `.small-logo{display:none}` and
 * `.kt-sidebar-collapse .kt-sidebar:not(:hover) .default-logo{display:none}`).
 * custom.css loads last, so a `display` rule here would override that and break
 * the collapse behaviour (both logos showing at once).
 */
.app-sidebar-logo {
  height: var(--zx-sidebar-logo-height);
  width: auto;
  max-width: none;
}


/* ==========================================================================
   5. COMPONENTS
   Buttons, badges, cards, modals, alerts.
   ========================================================================== */

/* --------------------------------------------------------------------------
 * Badge colour variants the views ask for but the theme never defined.
 *
 * VERIFIED IN THE BROWSER, not inferred. The theme ships only the COMPOUND
 * selector `.kt-badge-light.kt-badge-primary`, but all 71 call sites in this
 * app write the SINGLE hyphenated class `kt-badge-light-primary`. Those do not
 * match each other, so every one of those badges silently fell through to the
 * plain grey `.kt-badge` base — the "2 total" chip beside a page title has
 * never been coloured at all, on any screen.
 *
 * Defining the hyphenated forms here is what actually makes them render.
 * color-mix against var(--primary) resolves at use time, so they follow
 * whatever primary is in force — including an org's white-label colour, which
 * is emitted after this file loads.
 *
 * Do NOT "tidy" these into the compound selectors: nothing in the app uses
 * that form, and the rules go dead again.
 * -------------------------------------------------------------------------- */
.kt-badge-light-primary {
  background-color: color-mix(in srgb, var(--primary) 13%, #fff);
  color: color-mix(in srgb, var(--primary) 88%, #000);
  border-color: color-mix(in srgb, var(--primary) 22%, #fff);
}
.kt-badge-outline-primary {
  border-color: color-mix(in srgb, var(--primary) 30%, #fff);
  background-color: color-mix(in srgb, var(--primary) 7%, #fff);
  color: color-mix(in srgb, var(--primary) 90%, #000);
}

/* --------------------------------------------------------------------------
 * Shared page furniture: spinner + empty state.
 *
 * These existed as hand-copied utility strings in every screen, which is how
 * the app ended up with FOUR different loading spinners (h-10/h-8/h-12 ring
 * variants plus a ki-loading glyph) and empty states ranging from a full
 * icon+copy+CTA block down to the bare string "No data.".
 *
 * Written against semantic tokens (--muted, --border, --secondary-foreground)
 * rather than literal grays, so unlike the markup they replace they are
 * correct in dark mode without a `dark:` variant at every call site.
 *
 * Consumed by components/spinner.php and components/empty_state.php — prefer
 * those partials over these classes directly.
 * -------------------------------------------------------------------------- */
@keyframes zx-spin { to { transform: rotate(360deg); } }

.zx-spinner {
  display: inline-block;
  width: var(--zx-spinner-size, 2.5rem);
  height: var(--zx-spinner-size, 2.5rem);
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--primary) 22%, transparent);
  border-bottom-color: var(--primary);
  animation: zx-spin .7s linear infinite;
}
.zx-spinner-sm { --zx-spinner-size: 1.5rem; }
.zx-spinner-lg { --zx-spinner-size: 3rem; }

/* Respect a user who has asked the OS to stop moving things. Without this the
   ring is a continuous rotation with no pause — the exact pattern that
   triggers vestibular discomfort. */
@media (prefers-reduced-motion: reduce) {
  .zx-spinner { animation-duration: 2.4s; }
}

.zx-spinner-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .75rem;
  padding: 3rem 1rem;
  color: var(--secondary-foreground);
  font-size: var(--text-sm);
}

/* --------------------------------------------------------------------------
 * Bulk-selection bar + table row hover.
 *
 * The bar appears on 35 grids and was written every time as
 * `bg-blue-50 border-gray-100 … text-blue-700` — literal blue that ignored the
 * brand primary AND stayed light-on-light in dark mode. Row hover was
 * `hover:bg-gray-50`, 51 occurrences with the same dark-mode problem.
 *
 * Both now derive from tokens, so they follow the brand (including an org's
 * white-label colour) and invert correctly.
 * -------------------------------------------------------------------------- */
.zx-bulkbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: .5rem 1rem;
  margin-bottom: 1.25rem;
  border-radius: .5rem;
  border: 1px solid color-mix(in srgb, var(--primary) 24%, transparent);
  background: color-mix(in srgb, var(--primary) 8%, var(--card));
}
.zx-bulkbar-count {
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  color: color-mix(in srgb, var(--primary) 88%, var(--foreground));
}

.zx-row-hover { transition: background-color .12s ease-in-out; }
.zx-row-hover:hover { background-color: color-mix(in srgb, var(--muted) 55%, transparent); }

@media (prefers-reduced-motion: reduce) {
  .zx-row-hover { transition: none; }
}

.zx-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .75rem;
  padding: 3rem 1rem;
  text-align: center;
}
.zx-empty-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
  background: var(--muted);
  color: var(--muted-foreground);
  font-size: 1.5rem;
}
.zx-empty-title {
  font-size: var(--text-base);
  font-weight: var(--font-weight-semibold);
  color: var(--foreground);
}
/* Not --muted-foreground: on a white card that is slate-500 (4.6:1) which
   passes, but the old markup used text-gray-400 (2.85:1) which does not. */
.zx-empty-text {
  font-size: var(--text-sm);
  color: var(--secondary-foreground);
  max-width: 32rem;
}

/* Dark: tint toward the canvas instead of white, or the chip glows. */
.dark .kt-badge-light-primary {
  background-color: color-mix(in srgb, var(--primary) 26%, transparent);
  color: color-mix(in srgb, var(--primary) 58%, #fff);
  border-color: color-mix(in srgb, var(--primary) 38%, transparent);
}
.dark .kt-badge-outline-primary {
  border-color: color-mix(in srgb, var(--primary) 45%, transparent);
  background-color: color-mix(in srgb, var(--primary) 18%, transparent);
  color: color-mix(in srgb, var(--primary) 55%, #fff);
}

/* --------------------------------------------------------------------------
 * CHAT / INBOX (WhatsApp-web style two-pane thread)
 *
 * Role-agnostic on purpose (Section 5, not Section 8): the same markup can back
 * the tenant inbox, an admin-side conversation viewer, or any future role. Scope
 * is the `.zx-chat` root, so nothing here leaks into other pages.
 *
 * Per RULE 2 — every declaration below exists because the prebuilt Tailwind
 * bundle does NOT ship the utility. Verified live via getComputedStyle: h-14,
 * min-h-0, font-bold, whitespace-pre-wrap, break-words, rounded-2xl, shadow-sm,
 * min-w-5, max-h-32, line-clamp-2, w-60, w-44 and text-sky-500 all no-op here.
 * Do not "simplify" these back into utility classes.
 *
 * Markup contract (see app/Views/dashboard/conversations.php):
 *   .zx-chat                     root (owns page height)
 *     .zx-chat-split             flex row
 *       .zx-chat-list-pane       left  (fixed width, scrolls internally)
 *       .zx-chat-thread-pane     right (fluid)
 *         .zx-chat-thread        column: header / canvas / composer
 * -------------------------------------------------------------------------- */

/* The Vue mount root (.app-layout) is a flex child of body that never grows, so
   every page is capped at content width. Chat is the one screen that must fill
   the viewport — opt in only where a chat root is present, leaving other pages be. */
body:has(.zx-chat) .app-layout { flex: 1 1 auto; min-width: 0; }

/* Chat is an app screen, not a document: the marketing footer only steals vertical
   space from the conversation. Hidden here (not removed from the shared footer view)
   so every other page keeps it. inbox.js measures the footer for its height math and
   correctly reads 0 once this applies. */
body:has(.zx-chat) .kt-footer { display: none; }

.zx-chat-split { display: flex; min-width: 0; height: 100%; }

.zx-chat-list-pane   { display: flex; flex-direction: column; width: 100%; flex: none; min-height: 0; }
.zx-chat-thread-pane { display: none; flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; }

@media (min-width: 1024px) {
  .zx-chat-list-pane   { width: var(--zx-chat-list-width); }
  .zx-chat-thread-pane { display: flex; flex-direction: column; }
}

/* Mobile: opening a chat swaps the list pane out for the thread. */
.zx-chat-split.is-thread-open > .zx-chat-list-pane   { display: none; }
.zx-chat-split.is-thread-open > .zx-chat-thread-pane { display: flex; flex-direction: column; }
@media (min-width: 1024px) {
  .zx-chat-split.is-thread-open > .zx-chat-list-pane { display: flex; }
}

/* Column chain: every ancestor of the scroll area needs min-height:0, or flex
   items refuse to shrink below content height and the canvas grows the page
   instead of scrolling. `min-h-0` is not compiled — this is why chat wouldn't scroll. */
.zx-chat-thread { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.zx-chat-thread.is-hidden { display: none; }

.zx-chat-header { height: var(--zx-chat-header-height); flex: none; }

/* The one scroll container. */
.zx-chat-canvas {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  background-color: var(--zx-chat-canvas);   /* fallback */
  background-image: var(--zx-chat-canvas-grad);
  /* default attachment (scroll): the wash is a fixed backdrop; messages scroll over it. */
}
.dark .zx-chat-canvas { background-image: var(--zx-chat-canvas-grad); }

.zx-chat-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* ---- Conversation rows ---- */
.zx-chat-row-name-unread { font-weight: 700; }
/* Which of the org's own numbers a conversation arrived on. Only rendered when more than one
   number is connected — a conversation is unique per (contact, number), so without this the same
   person writing to two numbers produces two visually identical rows. Muted on purpose: it is
   disambiguation, not content, and must not compete with the name or preview. */
/* Business-number switcher: its own bar under the Chats header, above search. The number is the
   account context an agent is working in, so it stays visible rather than hiding in the filter
   menu. Horizontally scrollable — an org may connect many numbers and the pane is narrow. */
.zx-chat-wabar { padding: .375rem .75rem .5rem; }
.zx-chat-wabar-label {
  display: block; margin-bottom: .25rem;
  font-size: .6875rem; font-weight: 500; color: var(--color-muted-foreground, #71717a);
}
.zx-chat-wabar-row { display: flex; align-items: center; gap: .5rem; }
/* The select carries a leading WhatsApp mark, so the native control is unstyled inside a
   bordered shell rather than fought with appearance hacks. */
.zx-chat-wabar-select {
  display: flex; align-items: center; gap: .5rem; flex: 1 1 auto; min-width: 0;
  padding: .375rem .625rem; border: 1px solid var(--color-border, #e4e4e7);
  border-radius: .5rem; background: var(--color-background, #fff);
}
.zx-chat-wabar-select > i { color: #25d366; font-size: .875rem; flex: 0 0 auto; }
.zx-chat-wabar-select select {
  flex: 1 1 auto; min-width: 0; border: 0; outline: none; background: transparent;
  font-size: .8125rem; font-weight: 500; color: var(--color-mono, #18181b); cursor: pointer;
}
.zx-chat-wabar-count {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: .25rem;
  padding: .375rem .5rem; border-radius: .5rem;
  font-size: .6875rem; font-weight: 600; white-space: nowrap;
  color: #047857; background: #ecfdf5; border: 1px solid #a7f3d0;
}
.zx-chat-wabar-count:hover { background: #d1fae5; }
.zx-chat-wabar-count > i { font-size: .75rem; }

/* Queue tabs — the agent's work states (All / Mine / Unassigned / Waiting / Closed). */
.zx-chat-queue {
  display: flex; align-items: center; gap: .25rem;
  padding: 0 .75rem .5rem; overflow-x: auto;
  scrollbar-width: none; -ms-overflow-style: none;
}
.zx-chat-queue::-webkit-scrollbar { display: none; }
.zx-chat-queue-btn {
  flex: 0 0 auto; padding: .25rem .625rem; border: 1px solid transparent; border-radius: 999px;
  font-size: .75rem; font-weight: 600; line-height: 1.5; cursor: pointer;
  color: var(--color-secondary-foreground, #52525b); background: transparent;
  transition: background-color .15s, color .15s, border-color .15s;
}
.zx-chat-queue-btn:hover { background: var(--color-muted, #f4f4f5); }
.zx-chat-queue-btn.is-on {
  color: #fff; background: var(--color-primary, #0d5c63); border-color: var(--color-primary, #0d5c63);
}
/* "Waiting" is the only queue that represents a promise outstanding to a real person, so it
   stays visually distinct even when it is not the active tab. */
.zx-chat-queue-btn.is-warn { color: #b45309; }
.zx-chat-queue-btn.is-warn:hover { background: #fffbeb; }
.zx-chat-queue-btn.is-warn.is-on { color: #fff; background: #d97706; border-color: #d97706; }

/* Contact-pane sections (company / deal / tasks / custom fields). Each is omitted entirely when
   empty, so a visible heading always means there is something under it. */
.zx-contact-section {
  margin-top: .75rem; padding-top: .625rem;
  border-top: 1px solid var(--color-border, #e4e4e7);
}
.zx-contact-section-head {
  margin-bottom: .375rem;
  font-size: .625rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--color-muted-foreground, #71717a);
}
/* Lead score: the bar carries the meaning, the number is the detail. */
.zx-contact-score { display: flex; flex-direction: column; gap: .25rem; align-items: flex-end; }
.zx-contact-score-num { font-size: .75rem; font-weight: 600; color: var(--color-mono, #18181b); }
.zx-contact-score-bar {
  display: block; width: 6rem; height: .3125rem; border-radius: 999px;
  background: var(--color-muted, #f4f4f5); overflow: hidden;
}
.zx-contact-score-fill { display: block; height: 100%; border-radius: 999px; }
.zx-contact-score-fill.is-high { background: #10b981; }
.zx-contact-score-fill.is-mid  { background: #f59e0b; }
.zx-contact-score-fill.is-low  { background: #d4d4d8; }

.zx-contact-task {
  display: flex; align-items: baseline; justify-content: space-between; gap: .5rem;
  padding: .25rem 0; font-size: .75rem;
}
.zx-contact-task-title { color: var(--color-mono, #18181b); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zx-contact-task-due { flex: 0 0 auto; font-size: .6875rem; color: var(--color-muted-foreground, #71717a); }
.zx-contact-task-due.is-overdue { color: #dc2626; font-weight: 600; }

/* Action bar — act on the conversation without leaving it. Scrolls horizontally rather than
   wrapping, so the composer never gets pushed off a short screen. */
.zx-chat-actions {
  display: flex; align-items: center; gap: .375rem;
  padding: .5rem .75rem; border-top: 1px solid var(--color-border, #e4e4e7);
  overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
}
.zx-chat-actions::-webkit-scrollbar { display: none; }
.zx-chat-action {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: .4375rem;
  padding: .4375rem .75rem; border: 1px solid var(--color-border, #e4e4e7);
  border-radius: .625rem; background: var(--color-background, #fff);
  font-size: .8125rem; font-weight: 600; white-space: nowrap; cursor: pointer;
  color: var(--color-secondary-foreground, #52525b);
  box-shadow: 0 1px 2px rgb(0 0 0 / .04);
  transition: background-color .15s, border-color .15s, color .15s, box-shadow .15s, transform .1s;
}
.zx-chat-action:hover {
  color: var(--color-primary, #0d5c63);
  border-color: color-mix(in srgb, var(--color-primary, #0d5c63) 45%, var(--color-border, #e4e4e7));
  background: color-mix(in srgb, var(--color-primary, #0d5c63) 5%, #fff);
  box-shadow: 0 2px 6px rgb(0 0 0 / .07);
}
.zx-chat-action:active { transform: translateY(1px); box-shadow: 0 1px 2px rgb(0 0 0 / .06); }
.zx-chat-action > i { font-size: .875rem; opacity: .8; }
.zx-chat-action:hover > i { opacity: 1; }
.zx-chat-actionform { padding: 0 .75rem .5rem; }
.zx-chat-actionform.hidden { display: none; }
.zx-chat-actionform-row { display: flex; align-items: center; flex-wrap: wrap; gap: .375rem; }
.zx-chat-actionform-row .kt-input,
.zx-chat-actionform-row .kt-select { min-width: 8rem; flex: 1 1 8rem; }
.zx-chat-actionform-empty { font-size: .75rem; color: var(--color-muted-foreground, #71717a); }

/* Service-window state — the composer's most-consulted fact ("can I reply freely, and for how
   long?"). Rendered as bare coloured text before, which read as a stray sentence. */
.zx-chat-window {
  display: flex; align-items: center; gap: .5rem;
  margin: .5rem 1rem 0; padding: .4375rem .75rem;
  border-radius: .625rem; font-size: .75rem; font-weight: 600;
}
.zx-chat-window.hidden { display: none; }
.zx-chat-window-text { flex: 1 1 auto; min-width: 0; }
.zx-chat-window-dot {
  flex: 0 0 auto; width: .5rem; height: .5rem; border-radius: 999px; background: currentColor;
}
.zx-chat-window.is-open {
  color: #047857; background: #ecfdf5; border: 1px solid #a7f3d0;
}
/* The open window is time-limited, so the dot pulses — a static dot reads as a status light,
   not as a countdown. Respects reduced-motion. */
.zx-chat-window.is-open .zx-chat-window-dot { animation: zx-pulse 2s ease-in-out infinite; }
@keyframes zx-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) {
  .zx-chat-window.is-open .zx-chat-window-dot { animation: none; }
}
.zx-chat-window.is-closed {
  color: #b91c1c; background: #fef2f2; border: 1px solid #fecaca;
}

/* "Sending from: <number>" under the composer — the last thing an agent sees before typing, and
   the cheapest guard against answering as the wrong brand. */
.zx-chat-sendfrom {
  display: flex; align-items: center; gap: .375rem;
  padding: .25rem 1rem .5rem;
  font-size: .6875rem; color: var(--color-muted-foreground, #71717a);
}
.zx-chat-sendfrom.hidden { display: none; }
.zx-chat-sendfrom > i { color: #25d366; font-size: .75rem; }
.zx-chat-sendfrom b { color: var(--color-secondary-foreground, #52525b); font-weight: 600; }

/* ---- Thread header identity block ---------------------------------------------------------
   Name, the customer's number with a WhatsApp mark, and which of OUR numbers answers. Previously
   one grey line reading "923212591835 · via Waba Test", where nothing was legible at a glance. */
.zx-th-name {
  font-size: .9375rem; font-weight: 650; line-height: 1.3;
  color: var(--color-mono, #18181b);
}
.zx-th-sub { display: flex; align-items: center; gap: .4375rem; min-width: 0; margin-top: .0625rem; }
.zx-th-phone { font-size: .8125rem; color: var(--color-secondary-foreground, #52525b); }
.zx-th-channel {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: .25rem;
  padding: .0625rem .375rem; border-radius: 999px;
  font-size: .625rem; font-weight: 700; letter-spacing: .01em;
  color: #047857; background: #ecfdf5; border: 1px solid #a7f3d0;
}
.zx-th-channel > i { font-size: .6875rem; color: #25d366; }
.zx-th-via {
  flex: 0 0 auto; font-size: .6875rem; white-space: nowrap;
  color: var(--color-muted-foreground, #71717a);
}
.zx-th-via.hidden { display: none; }
/* The thread header holds a variable number of bands (name, sub-line, vocab chips), so it must
   GROW rather than clip — a fixed height cut the customer's name in half the moment a
   conversation carried labels. */
.zx-chat-thread .zx-chat-header {
  height: auto; min-height: var(--zx-chat-header-height);
  padding-block: .5rem;
}

/* Header tool buttons — icon + label, so "tag" and "notepad" glyphs stop being a guessing game.
   The label collapses below `sm`, where the header has no room; the title attribute covers that
   case, and the icon alone is at least a consistent target. */
.zx-th-tool {
  display: inline-flex; align-items: center; gap: .375rem;
  padding: .3125rem .5625rem; border-radius: .5rem;
  border: 1px solid var(--color-border, #e4e4e7);
  background: var(--color-background, #fff);
  color: var(--color-secondary-foreground, #52525b);
  font-size: .75rem; font-weight: 600; line-height: 1.4; white-space: nowrap; cursor: pointer;
  transition: color .15s, background-color .15s, border-color .15s;
}
.zx-th-tool > i { font-size: .875rem; opacity: .8; }
.zx-th-tool:hover {
  color: var(--color-primary, #0d5c63);
  border-color: color-mix(in srgb, var(--color-primary, #0d5c63) 45%, var(--color-border, #e4e4e7));
  background: color-mix(in srgb, var(--color-primary, #0d5c63) 5%, #fff);
}
.zx-th-tool:hover > i { opacity: 1; }
@media (max-width: 639px) {
  .zx-th-tool-label { display: none; }
  .zx-th-tool { padding: .3125rem .4375rem; }
}
.dark .zx-th-tool,
:root[data-theme="dark"] .zx-th-tool {
  background: color-mix(in srgb, #fff 6%, transparent);
  border-color: color-mix(in srgb, #fff 14%, transparent);
}
.dark .zx-th-tool:hover,
:root[data-theme="dark"] .zx-th-tool:hover {
  background: color-mix(in srgb, #fff 12%, transparent);
}

/* Thread-header CRM read-outs (owner / stage). Hidden on narrow screens: the header already
   competes for space there, and both facts remain on the list row and in the contact pane. */
.zx-th-crm { display: none; align-items: center; gap: .625rem; }
@media (min-width: 1024px) { .zx-th-crm:not(.hidden) { display: flex; } }
/* Each read-out is a bordered block so it reads as a deliberate control rather than two stray
   words floating in the header. */
.zx-th-crm-item {
  display: flex; flex-direction: column; line-height: 1.25;
  padding: .25rem .5rem; border-radius: .5rem;
  background: var(--color-muted, #f4f4f5);
  border: 1px solid var(--color-border, #e4e4e7);
}
.zx-th-crm-item.hidden { display: none; }
.zx-th-crm-label {
  font-size: .5625rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--color-muted-foreground, #71717a);
}
.zx-th-crm-value {
  font-size: .8125rem; font-weight: 600; color: var(--color-mono, #18181b);
  max-width: 10rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.zx-th-crm-chip {
  display: inline-block; padding: .0625rem .4375rem; border-radius: .3125rem;
  font-size: .75rem; font-weight: 600;
  max-width: 10rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.zx-th-crm-chip.is-open { color: #3730a3; background: #eef2ff; border: 1px solid #c7d2fe; }
.zx-th-crm-chip.is-won  { color: #065f46; background: #ecfdf5; border: 1px solid #a7f3d0; }
.zx-th-crm-chip.is-lost { color: #991b1b; background: #fef2f2; border: 1px solid #fecaca; }

/* ---- Conversation row -------------------------------------------------------------------
   Rows carry four bands (name+time, preview, meta chips, vocab chips), so they need real
   vertical rhythm and a left accent rail on the active row — a flat tint alone did not read as
   "this is the open thread" once the meta strip was added. */
.zx-chat-row {
  position: relative;
  padding: .75rem .875rem .75rem 1rem;
  border-bottom: 1px solid var(--color-border, #e4e4e7);
  transition: background-color .12s ease;
}
.zx-chat-row:hover { background: color-mix(in srgb, var(--color-primary, #0d5c63) 4%, transparent); }
.zx-chat-row.is-active { background: color-mix(in srgb, var(--color-primary, #0d5c63) 7%, transparent); }
.zx-chat-row.is-active::before {
  content: ''; position: absolute; inset-block: 0; inset-inline-start: 0;
  width: 3px; background: var(--color-primary, #0d5c63);
}

/* Row meta strip: owner · deal stage · which number.
   Chips are PILLS with real vertical padding. The first pass used 10px text at 0 vertical padding
   and a 3px radius, which read as cramped debug output rather than as designed badges. */
.zx-chat-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: .3125rem; margin-top: .375rem;
}
.zx-chat-owner {
  display: inline-flex; align-items: center; gap: .25rem;
  font-size: .6875rem; font-weight: 500; color: var(--color-muted-foreground, #71717a);
  max-width: 8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.zx-chat-owner > i { font-size: .75rem; opacity: .65; }
.zx-chat-stage {
  display: inline-block; padding: .125rem .4375rem; border-radius: 999px;
  font-size: .6875rem; font-weight: 600; line-height: 1.45; letter-spacing: .005em;
  max-width: 9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.zx-chat-stage.is-open { color: #4338ca; background: #eef2ff; border: 1px solid #c7d2fe; }
.zx-chat-stage.is-won  { color: #047857; background: #ecfdf5; border: 1px solid #a7f3d0; }
.zx-chat-stage.is-lost { color: #b91c1c; background: #fef2f2; border: 1px solid #fecaca; }

/* Which number the thread arrived on. Carries a small WhatsApp mark so it reads as a channel,
   not as a generic tag — align-self stops the row's flex column stretching it full width, which
   made it look like an input field. */
.zx-chat-via {
  display: inline-flex; align-items: center; gap: .25rem; align-self: flex-start;
  max-width: 100%; padding: .125rem .4375rem .125rem .375rem;
  border-radius: 999px; font-size: .6875rem; line-height: 1.45; font-weight: 600;
  color: var(--color-secondary-foreground, #52525b);
  background: var(--color-muted, #f4f4f5);
  border: 1px solid var(--color-border, #e4e4e7);
  overflow: hidden; white-space: nowrap;
}
.zx-chat-via::before {
  content: ''; flex: 0 0 auto; width: .4375rem; height: .4375rem; border-radius: 999px;
  background: #25d366;
}
.zx-chat-via > span { overflow: hidden; text-overflow: ellipsis; }
.zx-chat-badge { min-width: 1.25rem; }

/* ---- Bubbles (gradient-wash refresh, docs/006 Tier 3.2) ----
   Softly rounded, tail-less; one squared "notch" corner points the bubble at its side. The old
   CSS-triangle tails are dropped for a cleaner modern look — grouping still reads via spacing. */
.zx-chat-bubble {
  position: relative;
  max-width: 75%;
  border-radius: var(--zx-chat-bubble-radius);
  box-shadow: var(--zx-chat-bubble-shadow);
  color: var(--color-mono, #111827);
}
.zx-chat-bubble-in {
  background-color: var(--zx-chat-bubble-in);
  border: 1px solid rgba(13, 92, 99, .08);         /* faint teal hairline */
  border-top-left-radius: var(--zx-chat-bubble-notch);
}
.zx-chat-bubble-out {
  background-color: var(--zx-chat-bubble-out);
  border-top-right-radius: var(--zx-chat-bubble-notch);
}
.dark .zx-chat-bubble    { color: var(--color-foreground, #fafafa); }
.dark .zx-chat-bubble-in { background-color: var(--zx-chat-bubble-in-dark, #172221); border-color: rgba(255,255,255,.06); }
.dark .zx-chat-bubble-out{ background-color: var(--zx-chat-bubble-out-dark, #12352f); }
/* Only the FIRST bubble of a run gets the notch; continuation bubbles are fully rounded. */
.zx-chat-bubble-notail.zx-chat-bubble-in  { border-top-left-radius:  var(--zx-chat-bubble-radius); }
.zx-chat-bubble-notail.zx-chat-bubble-out { border-top-right-radius: var(--zx-chat-bubble-radius); }

/* Message text: preserve author line breaks; never let a long URL overflow. */
.zx-chat-body { white-space: pre-wrap; overflow-wrap: anywhere; }
/* Fallback text for a message with neither body nor media. */
.zx-chat-body-empty { font-style: italic; opacity: .7; }

/* Template marker — an agent must be able to tell a template send from free text. */
.zx-chat-tpl-chip {
  display: inline-flex; align-items: center; gap: .25rem;
  margin-bottom: .25rem; padding: .0625rem .375rem;
  border-radius: .25rem; font-size: .6875rem; font-weight: 600;
  background: rgba(0, 0, 0, .06);
  color: var(--color-secondary-foreground, #52525b);
}
.dark .zx-chat-tpl-chip { background: rgba(255, 255, 255, .1); }

/* ---- Coexistence provenance chip ----
   Marks a message Zetior did not send: typed by hand in the WhatsApp Business app
   (source=business_app) or backfilled from history (source=history). Dashed border
   distinguishes it at a glance from the solid template chip above. */
.zx-chat-src-chip {
  display: inline-flex; align-items: center; gap: .25rem;
  margin-bottom: .25rem; padding: .0625rem .375rem;
  border-radius: .25rem; font-size: .6875rem; font-weight: 600;
  border: 1px dashed rgba(0, 0, 0, .2);
  color: var(--color-secondary-foreground, #52525b);
}
.dark .zx-chat-src-chip { border-color: rgba(255, 255, 255, .28); }

/* ── Inbox conversation fidelity: quoted replies + reaction badges ───────────── */
.zx-chat-quote {
  border-left: 3px solid rgba(0, 0, 0, .2);
  padding: .125rem .5rem;
  margin-bottom: .25rem;
  font-size: .75rem;
  line-height: 1.3;
  opacity: .75;
  max-height: 2.6rem;
  overflow: hidden;
}
.dark .zx-chat-quote { border-left-color: rgba(255, 255, 255, .3); }

/* Sits on the bubble's bottom edge, the way WhatsApp renders reactions. */
.zx-chat-reaction {
  position: absolute;
  bottom: -.5rem;
  right: .5rem;
  font-size: .75rem;
  line-height: 1;
  padding: .125rem .25rem;
  border-radius: .75rem;
  background: var(--color-card, #fff);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
}
.zx-chat-bubble { position: relative; }

/* Options an interactive message offered — a record of what was sent, not live controls.
   Legacy chip style, kept for any bubble still rendering the old markup. */
.zx-chat-ix-options { display: flex; flex-wrap: wrap; gap: .25rem; margin-top: .375rem; }
.zx-chat-ix-chip {
  font-size: .6875rem; line-height: 1; padding: .25rem .5rem;
  border: 1px solid rgba(0, 0, 0, .15); border-radius: .75rem;
  color: var(--color-secondary-foreground, #52525b);
}
.dark .zx-chat-ix-chip { border-color: rgba(255, 255, 255, .25); }

/* ==========================================================================
   Organization profile (admin) — identity header, KPI tiles, tabs, timeline.
   New Tailwind utilities are NOT compiled in the prebuilt bundle, so every
   rule this page needs lives here as a semantic class.
   ========================================================================== */

/* Follows the Metronic "public-profile/profiles/company" reference: a hero
   identity block, one divided stat strip, and kt-card-table sections. Only what
   the prebuilt bundle genuinely lacks is defined here — theme classes
   (kt-card-table, kt-scrollable-x-auto, kt-badge-*, border-e-input) are used
   directly. NOTE: the theme's `kt-table-auto` is NOT in our bundle (verified:
   0 matches in styles.css), so key/value tables use .zx-kv below instead.

   ---- Identity header -------------------------------------------------------
   One block carries identity: avatar, name, sub-line and state badges. The old
   layout printed the name in the page title AND again in a separate strip below
   the actions, which read as two different things about the same org. */
.zx-org-head {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1rem;
  padding: 1.25rem 1.5rem;
}
.zx-org-head-main { display: flex; align-items: center; gap: .875rem; min-width: 0; }
.zx-org-head-text { min-width: 0; }
.zx-org-name {
  font-size: 1.25rem; font-weight: 600; line-height: 1.2; margin: 0;
  color: var(--color-mono, #0f172a);
}
.dark .zx-org-name { color: #f1f5f9; }
.zx-org-sub {
  display: flex; flex-wrap: wrap; align-items: center; gap: .375rem .625rem;
  margin-top: .3125rem; font-size: .8125rem;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-org-sub a { color: inherit; text-decoration: none; }
.zx-org-sub a:hover { color: var(--zx-brand, #0d5c63); text-decoration: underline; }
.zx-org-sub .zx-dot { opacity: .45; }

.zx-org-badge {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 48px; height: 48px; border-radius: .75rem;
  background: linear-gradient(135deg, #0f7a5a, #0d5c63);
  color: #fff; font-weight: 700; font-size: 1.125rem; letter-spacing: .02em;
}
/* A real logo: contain (never crop the mark) on a neutral plate, since most logos are
   transparent PNGs that would vanish against the brand gradient. */
.zx-org-logo {
  object-fit: contain; padding: .25rem; background: #fff;
  border: 1px solid rgba(0, 0, 0, .08);
}
.dark .zx-org-logo { background: #f8fafc; }

/* State chips use the theme's own badge classes (kt-badge kt-badge-sm
   kt-badge-outline kt-badge-success|warning|destructive|secondary) — all present
   in the bundle. Only the label prefix and the row wrapper need styling. */
.zx-chips { display: flex; flex-wrap: wrap; align-items: center; gap: .375rem; margin-top: .5rem; }
.zx-chips .kt-badge { text-transform: capitalize; }
.zx-chip-label { font-weight: 400; opacity: .65; text-transform: none; margin-inline-end: .1875rem; }

/* ---- Header actions --------------------------------------------------------
   Six equal-weight outline buttons gave no clue which was routine and which was
   destructive. One primary + an overflow menu; destructive lives in the menu. */
.zx-org-actions { display: flex; align-items: center; gap: .5rem; margin-inline-start: auto; flex-wrap: wrap; }
.zx-menu-wrap { position: relative; }
.zx-menu {
  position: absolute; inset-inline-end: 0; top: calc(100% + .375rem); z-index: 40;
  min-width: 15rem; padding: .3125rem;
  background: var(--color-background, #fff);
  border: 1px solid rgba(0, 0, 0, .1); border-radius: .625rem;
  box-shadow: 0 12px 28px rgba(0, 0, 0, .16);
}
.dark .zx-menu { background: #16181d; border-color: rgba(255, 255, 255, .14); }
.zx-menu.hidden { display: none; }
.zx-menu-item {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: .5rem .625rem; border: 0; background: none; cursor: pointer;
  font-size: .8125rem; text-align: start; border-radius: .375rem;
  color: inherit;
}
.zx-menu-item:hover { background: rgba(0, 0, 0, .05); }
.dark .zx-menu-item:hover { background: rgba(255, 255, 255, .07); }
.zx-menu-item i { opacity: .6; width: 1rem; }
.zx-menu-danger { color: #b42318; }
.zx-menu-danger:hover { background: rgba(180, 35, 24, .08); }
.dark .zx-menu-danger { color: #f4a6a0; }
.zx-menu-sep { height: 1px; margin: .3125rem .25rem; background: rgba(0, 0, 0, .08); }
.dark .zx-menu-sep { background: rgba(255, 255, 255, .12); }
.zx-menu-head {
  padding: .375rem .625rem .25rem; font-size: .6875rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--color-secondary-foreground, #52525b);
}

/* ---- Stat strip ------------------------------------------------------------
   Six separate tiles on a 4-column grid left a ragged two-orphan second row.
   The theme's company profile puts stats in ONE card as equal flex cells with
   divider rules, which stays even at any count. Wraps to a second line on
   narrow screens instead of overflowing. */
/* Grid, not flex: with flex-wrap a "divider on every cell but the first" rule still
   paints a leading rule on the first cell of each WRAPPED row, because CSS cannot
   detect wrap position. auto-fit columns wrap identically but let the divider be
   suppressed per column, which is measurable. */
.zx-stats {
  display: grid; padding: .25rem 0;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
}
.zx-stat {
  display: grid; place-content: center; gap: .3125rem;
  padding: .625rem .75rem; text-align: center; position: relative;
}
.zx-stat::before {
  content: ''; position: absolute; inset-block: .25rem; inset-inline-start: 0;
  width: 1px; background: var(--color-input, rgba(0, 0, 0, .12));
}
/* No rule at the start of any row. Six explicit breakpoints cover the auto-fit
   column counts this strip can take (1–6 tiles). */
.zx-stat:first-child::before { content: none; }
@media (min-width: 1280px) { .zx-stat:nth-child(6n + 1)::before { content: none; } }
@media (min-width: 1024px) and (max-width: 1279px) { .zx-stat:nth-child(4n + 1)::before { content: none; } }
@media (min-width: 641px)  and (max-width: 1023px) { .zx-stat:nth-child(3n + 1)::before { content: none; } }
@media (max-width: 640px)  { .zx-stat:nth-child(2n + 1)::before { content: none; } }
.zx-stat-value {
  font-size: 1.5rem; line-height: 1.1; font-weight: 600;
  color: var(--color-mono, #0f172a); font-variant-numeric: tabular-nums;
}
.dark .zx-stat-value { color: #f1f5f9; }
.zx-stat-label {
  display: flex; align-items: center; justify-content: center; gap: .3125rem;
  font-size: .8125rem; color: var(--color-secondary-foreground, #52525b);
}
.zx-stat-label i { font-size: .875rem; opacity: .6; }
.zx-stat-sub { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); opacity: .8; min-height: .875rem; }

/* Key/value detail table. The theme uses `kt-table-auto` for these, but that
   class is NOT in our prebuilt bundle (0 matches), so it would render unstyled. */
.zx-kv { width: 100%; border-collapse: collapse; }
.zx-kv td { padding: .375rem 0; font-size: .875rem; vertical-align: top; }
.zx-kv td:first-child {
  color: var(--color-secondary-foreground, #52525b);
  padding-inline-end: 1.5rem; white-space: nowrap; width: 1%;
}
.zx-kv td:last-child { color: var(--color-mono, #0f172a); }
.dark .zx-kv td:last-child { color: #f1f5f9; }

/* ---- Tabs ---- */
.zx-profile-tabs {
  display: flex; gap: .25rem; flex-wrap: wrap; margin-bottom: 1.25rem;
  border-bottom: 1px solid rgba(0, 0, 0, .1);
}
.zx-profile-tab {
  appearance: none; background: none; border: 0; cursor: pointer;
  display: inline-flex; align-items: center; gap: .4375rem;
  padding: .625rem 1rem; font-size: .875rem; font-weight: 500;
  color: var(--color-secondary-foreground, #52525b);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.zx-profile-tab i { font-size: .9375rem; opacity: .7; }
.zx-profile-tab:hover { color: var(--zx-brand, #0d5c63); }
.zx-profile-tab.is-active { color: var(--zx-brand, #0d5c63); border-bottom-color: var(--zx-brand, #0d5c63); font-weight: 600; }
.zx-profile-tab.is-active i { opacity: 1; }
.dark .zx-profile-tabs { border-bottom-color: rgba(255, 255, 255, .12); }

/* A display rule here defeats Tailwind's .hidden, so restate it explicitly. */
.zx-profile-panel { display: block; }
.zx-profile-panel.hidden { display: none; }

/* Wide tables scroll inside the panel; the page must never scroll sideways. */
.zx-profile-scroll { overflow-x: auto; max-width: 100%; }

/* Card header: title + count on the left, actions on the right. */
.zx-card-head { display: flex; align-items: center; gap: .625rem; flex-wrap: wrap; }
.zx-card-count {
  font-size: .6875rem; font-weight: 600; padding: .1875rem .4375rem; border-radius: .375rem;
  background: rgba(0, 0, 0, .06); color: var(--color-secondary-foreground, #52525b);
}
.dark .zx-card-count { background: rgba(255, 255, 255, .1); }
.zx-card-actions { display: flex; align-items: center; gap: .5rem; margin-inline-start: auto; }

/* Numeric columns line up only if they are tabular and end-aligned. */
.zx-num { text-align: end; font-variant-numeric: tabular-nums; white-space: nowrap; }
.zx-muted { color: var(--color-secondary-foreground, #52525b); }
.zx-strong { font-weight: 600; color: var(--color-mono, #0f172a); }
.dark .zx-strong { color: #f1f5f9; }
.zx-sub { display: block; font-size: .75rem; color: var(--color-secondary-foreground, #52525b); }

/* A wide table must scroll INSIDE its card, never widen the page.
   .kt-scrollable-x-auto sets overflow-x:auto but is display:grid, and a grid ITEM defaults
   to min-width:auto — so the track grew to fit the table and the whole chain inherited its
   width (measured: a 745px members table in a 390px viewport pushed the page to 786px and
   scrolled the body sideways). Pinning the grid item is what actually engages the overflow;
   min-width:0 on the wrapper alone does nothing here. */
.kt-card .kt-card-table.kt-scrollable-x-auto,
.zx-profile-panel .kt-scrollable-x-auto { min-width: 0; max-width: 100%; }
.kt-card-table.kt-scrollable-x-auto > * { min-width: 0; }

/* Row action buttons: only reveal weight on hover so tables stay calm. */
.zx-row-actions { display: inline-flex; gap: .3125rem; justify-content: flex-end; }

/* Row menus sit inside .kt-card-table.kt-scrollable-x-auto, whose overflow CLIPS an
   absolutely-positioned child (measured: 126px menu cut to 120px, hiding Delete).
   placeMenu() switches them to fixed and sets the coordinates. */
.kt-card-table .zx-menu { min-width: 13rem; }
.kt-table td .zx-menu-wrap { position: relative; }
.zx-menu-fixed { position: fixed; inset-inline-end: auto; top: 0; left: 0; }
/* Keep the trigger from stretching the row height. */
.zx-row-actions .kt-btn-icon { width: 1.75rem; height: 1.75rem; }

/* Actions column: only as wide as the trigger, so it never steals table width. */
.kt-table th:empty { width: 3rem; }

/* Two-line cells (name over email / number over display name). */
.zx-kv td:last-child, .kt-table td { vertical-align: middle; }

/* Ledger direction. Tailwind's text-success/text-destructive utilities are NOT in
   the prebuilt bundle (verified), so the colour must be declared here. */
.zx-credit { color: #1a7f43; }
.zx-debit  { color: var(--color-mono, #0f172a); }
.dark .zx-credit { color: #6ee7a0; }
.dark .zx-debit  { color: #f1f5f9; }

/* Inline proportion bar behind a numeric cell — shows the shape of a series
   without pulling in a charting library. */
.zx-bar { position: relative; display: inline-block; min-width: 3.5rem; padding: .125rem .375rem; z-index: 0; }
.zx-bar::before {
  content: ''; position: absolute; inset-block: 0; inset-inline-end: 0; z-index: -1;
  width: var(--zx-bar, 0%); min-width: .25rem; border-radius: .25rem;
  background: color-mix(in srgb, var(--zx-brand, #0d5c63) 28%, transparent);
}
.dark .zx-bar::before { background: color-mix(in srgb, #4dd4ac 26%, transparent); }

/* ---- Empty & loading states ------------------------------------------------
   "Nothing recorded." in grey gave no clue whether the panel had failed, was
   still loading, or genuinely had no data. */
.zx-empty { display: flex; flex-direction: column; align-items: center; gap: .375rem; padding: 2rem 1rem; text-align: center; }
.zx-empty i { font-size: 1.5rem; opacity: .35; }
.zx-empty-title { font-size: .875rem; font-weight: 500; }
.zx-empty-hint { font-size: .75rem; color: var(--color-secondary-foreground, #52525b); max-width: 26rem; }

.zx-skel { display: block; height: .75rem; border-radius: .25rem; background: rgba(0, 0, 0, .07); animation: zx-skel-pulse 1.4s ease-in-out infinite; }
.dark .zx-skel { background: rgba(255, 255, 255, .09); }
.zx-skel + .zx-skel { margin-top: .625rem; }
@keyframes zx-skel-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .zx-skel { animation: none; } }

/* ---- WABA quality trend ----------------------------------------------------
   One dot per captured reading, oldest to newest. Colour IS the value, so each dot
   also carries a title with the date and rating — colour alone is not accessible,
   and the gaps between readings are meaningful (the poll is daily and a webhook
   can add a row on any day). */
.zx-qtrend { padding: .75rem 0; }
.zx-qtrend + .zx-qtrend { border-top: 1px solid var(--color-input, rgba(0,0,0,.12)); }
.zx-qtrend-head { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; }
.zx-qdots { display: flex; flex-wrap: wrap; gap: .25rem; margin: .5rem 0 .25rem; }
.zx-qdot {
    width: .875rem;
    height: .875rem;
    border-radius: 3px;
    background: var(--color-input, #d4d4d8);
}
.zx-q-green  { background: #1a7f43; }
.zx-q-yellow { background: #c98a00; }
.zx-q-red    { background: #c8102e; }

/* ---- RBAC grants -----------------------------------------------------------
   Permissions grouped by module ("contacts  view export") rather than a flat run
   of module.action strings, which is unreadable at eleven grants per role.
   manage/delete are weighted — those are what matters when an access review asks
   what someone could destroy. */
.zx-grants { display: flex; flex-wrap: wrap; gap: .375rem; }
.zx-grant {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    padding: .125rem .375rem;
    border: 1px solid var(--color-input, rgba(0,0,0,.12));
    border-radius: .25rem;
    font-size: .6875rem;
    line-height: 1.4;
}
.zx-grant b { font-weight: 600; text-transform: capitalize; }
.zx-grant-a { color: var(--color-secondary-foreground, #52525b); }
.zx-grant-a + .zx-grant-a::before { content: '·'; margin: 0 .125rem; }
.zx-grant-strong { color: #c8102e; font-weight: 600; }

/* ---- Workload cell ---------------------------------------------------------
   Total and the "N open" badge must not run together: rendered inline with only
   whitespace between them, "4" + "3 open" reads as "43 open". */
.zx-workload-cell {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}

/* ---- History filter bar ----------------------------------------------------
   Sits above the timeline inside the History card. Wraps on narrow viewports so
   the selects never overflow the card edge. */
.zx-filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-bottom: 1rem;
}
/* The theme's kt-select is width:100%, so inside a flex row it stretches to fill the
   card and the two selects stack full-width. Pin an explicit width so they sit inline
   as a compact filter row instead. */
.zx-filter-bar select { flex: 0 0 auto; width: auto; min-width: 11rem; }
/* A disabled select must read as unavailable, not merely unresponsive: the action
   filter is disabled whenever the kind is wallet/payment, which carry no action. */
.zx-filter-bar select:disabled { opacity: .5; cursor: not-allowed; }

/* ---- Timeline --------------------------------------------------------------
   Grid, not flex-wrap: the old row let a long diff push the timestamp onto its
   own line, so the times no longer formed a scannable column. */
.zx-timeline { list-style: none; margin: 0; padding: 0; }
.zx-timeline-item {
  display: grid; gap: .125rem .75rem; align-items: start;
  /* minmax, NOT a fixed 5.5rem: the kind column keeps its aligned width for short
     labels ("updated"), but a long action name — `impersonation_ended` is 138px against
     an 88px column — used to overflow the pill and collide with the title behind it.
     max-content lets the outliers claim the width they need instead. */
  grid-template-columns: minmax(5.5rem, max-content) 1fr auto;
  padding: .75rem 0; border-bottom: 1px solid rgba(0, 0, 0, .07); font-size: .875rem;
}
.zx-timeline-item:first-child { padding-top: .25rem; }
.zx-timeline-item:last-child { border-bottom: 0; padding-bottom: .25rem; }
.dark .zx-timeline-item { border-bottom-color: rgba(255, 255, 255, .09); }
.zx-timeline-kind {
  grid-row: span 2; align-self: start; justify-self: start;
  font-size: .625rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  padding: .1875rem .4375rem; border-radius: .3125rem; background: #eef2f5; color: #4b5563;
}
.zx-kind-wallet  { background: #e7f6ec; color: #1a7f43; }
.zx-kind-payment { background: #eaf1fd; color: #1a56c4; }
.zx-kind-audit   { background: #f2effd; color: #5b3fd1; }
.zx-timeline-title { font-weight: 500; color: var(--color-mono, #0f172a); }
.dark .zx-timeline-title { color: #f1f5f9; }
.zx-timeline-detail {
  grid-column: 2; font-size: .8125rem; color: var(--color-secondary-foreground, #52525b);
  overflow-wrap: anywhere;
}
/* Changed fields read as "key old → new" pills instead of a comma-run. */
.zx-diff { display: inline-flex; flex-wrap: wrap; gap: .25rem .375rem; margin-top: .125rem; }
.zx-diff-k { font-size: .75rem; }
.zx-diff-k b { font-weight: 600; }
.zx-diff-v {
  font-size: .6875rem; padding: .0625rem .3125rem; border-radius: .25rem;
  background: rgba(0, 0, 0, .05); font-variant-numeric: tabular-nums;
}
.dark .zx-diff-v { background: rgba(255, 255, 255, .09); }
.zx-diff-old { text-decoration: line-through; opacity: .6; }
.zx-timeline-meta { grid-column: 3; grid-row: 1 / span 2; text-align: end; white-space: nowrap; }
.zx-timeline-at { font-size: .75rem; color: var(--color-secondary-foreground, #52525b); font-variant-numeric: tabular-nums; }
.zx-timeline-actor { display: block; font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); opacity: .85; }

.dark .zx-timeline-kind { background: #24303f; color: #cbd5e1; }
.dark .zx-kind-wallet   { background: #12331f; color: #6ee7a0; }
.dark .zx-kind-payment  { background: #16233f; color: #93b4fb; }
.dark .zx-kind-audit    { background: #241f3d; color: #bda8ff; }

/* Narrow screens: drop the fixed kind column and let rows stack. */
@media (max-width: 640px) {
  .zx-timeline-item { grid-template-columns: 1fr auto; }
  .zx-timeline-kind { grid-row: auto; grid-column: 1; justify-self: start; margin-bottom: .125rem; }
  .zx-timeline-title, .zx-timeline-detail { grid-column: 1 / -1; }
  .zx-timeline-meta { grid-column: 1 / -1; grid-row: auto; text-align: start; }
  .zx-timeline-actor { display: inline; margin-inline-start: .375rem; }
  .zx-org-actions { width: 100%; margin-inline-start: 0; }
}

/* --- Admin impersonation banner ------------------------------------------- */
/* Impersonation grants full access, so this bar must be impossible to overlook and
   must never scroll away. It sits above the fixed header, which is pushed down by
   .zx-header-offset while it is present. */
.zx-impersonation-bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  padding: .5rem 1rem; min-height: 40px;
  background: #7a2e0e; color: #fff;
  font-size: .8125rem; line-height: 1.3;
  box-shadow: 0 1px 0 rgba(0, 0, 0, .2);
}
.zx-impersonation-bar strong { font-weight: 700; }
.zx-imp-dot {
  width: .5rem; height: .5rem; border-radius: 9999px; background: #ffb4a2;
  flex: 0 0 auto; animation: zx-imp-pulse 2s ease-in-out infinite;
}
@keyframes zx-imp-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .zx-imp-dot { animation: none; } }

.zx-imp-timer { margin-inline-start: auto; font-variant-numeric: tabular-nums; opacity: .9; }
.zx-imp-exit { background: #fff; color: #7a2e0e; border: 0; font-weight: 600; }
.zx-imp-exit:hover { background: #ffe9e2; color: #5c220a; }

/* Everything else on the page is fixed to top:0, so it would sit BEHIND the banner —
   the sidebar header (logo + collapse toggle) disappears under it. Push the header, the
   sidebar and the content offset down by the banner's height. */
.zx-header-offset { top: 40px; }
body:has(.zx-impersonation-bar) .kt-sidebar { top: 40px; }
body:has(.zx-impersonation-bar) .kt-wrapper { padding-top: 40px; }

/* --- Action dialog (org profile operations) ------------------------------- */
.zx-dialog-backdrop {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center; padding: 1rem;
  background: rgba(0, 0, 0, .45);
}
/* Tailwind's .hidden loses to the display rule above — restate it. */
.zx-dialog-backdrop.hidden { display: none; }

.zx-dialog {
  width: 100%; max-width: 32rem; max-height: 90vh; overflow-y: auto;
  background: var(--color-background, #fff); color: inherit;
  border-radius: .75rem; box-shadow: 0 20px 45px rgba(0, 0, 0, .25);
}
.dark .zx-dialog { background: #16181d; }

.zx-dialog-head,
.zx-dialog-foot { display: flex; align-items: center; gap: .75rem; padding: 1rem 1.25rem; }
.zx-dialog-head { border-bottom: 1px solid rgba(0, 0, 0, .08); }
.zx-dialog-head .kt-card-title { margin: 0; }
.zx-dialog-head [data-dialog-close] { margin-inline-start: auto; }
.zx-dialog-foot { border-top: 1px solid rgba(0, 0, 0, .08); }
.dark .zx-dialog-head, .dark .zx-dialog-foot { border-color: rgba(255, 255, 255, .12); }
.zx-dialog-body { padding: 1.25rem; }

.zx-dialog-error { font-size: .8125rem; color: #b42318; margin: 0; }
.zx-dialog-error.hidden { display: none; }

.zx-field { display: block; margin-bottom: .875rem; }
.zx-field:last-child { margin-bottom: 0; }
.zx-field-label { display: block; font-size: .75rem; font-weight: 500; margin-bottom: .25rem;
  color: var(--color-secondary-foreground, #52525b); }
.zx-field-inline { display: flex; align-items: center; gap: .5rem; margin-top: .875rem; }
.zx-field-hint { display: block; margin-top: .25rem; font-size: .6875rem;
  color: var(--color-secondary-foreground, #52525b); }

/* Destructive row action — reads as dangerous without a new Tailwind utility. */
.zx-btn-danger { color: #b42318; border-color: rgba(180, 35, 24, .35); }
.zx-btn-danger:hover { background: rgba(180, 35, 24, .08); color: #911c12; }

/* ==========================================================================
   Rich message bubbles — location, contact cards, interactive rows
   WhatsApp renders each of these as its own card rather than as body text.
   ========================================================================== */

/* ---- Location ---- */
.zx-loc-card {
  display: block; width: 15rem; max-width: 100%;
  border-radius: .5rem; overflow: hidden; text-decoration: none; color: inherit;
  background: rgba(0, 0, 0, .04);
}
.zx-loc-card:hover { background: rgba(0, 0, 0, .08); }
.dark .zx-loc-card { background: rgba(255, 255, 255, .06); }
.dark .zx-loc-card:hover { background: rgba(255, 255, 255, .1); }

.zx-loc-map {
  display: block; width: 100%; height: 7rem; object-fit: cover;
  background: rgba(0, 0, 0, .06);
}
/* Shown when the tile server is unreachable (it is a third-party host and regularly is).
   Styled as a deliberate map-ish panel rather than an empty grey box: faint grid lines and a
   coloured pin read as "a place", so the card never looks like a failed image. */
.zx-loc-map-fallback {
  display: flex; align-items: center; justify-content: center;
  font-size: 1.75rem; color: #ef4444;
  background-color: #e8eef0;
  background-image:
    linear-gradient(rgba(13, 92, 99, .07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(13, 92, 99, .07) 1px, transparent 1px);
  background-size: 20px 20px, 20px 20px;
}
.dark .zx-loc-map-fallback {
  background-color: rgba(255, 255, 255, .06);
  background-image:
    linear-gradient(rgba(255, 255, 255, .06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .06) 1px, transparent 1px);
}
.zx-loc-body { display: flex; align-items: flex-start; gap: .375rem; padding: .5rem .625rem; }
.zx-loc-pin  { font-size: .875rem; line-height: 1.2; color: #ef4444; flex-shrink: 0; margin-top: .0625rem; }
.zx-loc-text { min-width: 0; }
.zx-loc-name {
  font-size: .8125rem; font-weight: 600; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.zx-loc-addr {
  font-size: .6875rem; line-height: 1.35; margin-top: .0625rem;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-loc-foot {
  padding: .4375rem; font-size: .75rem; font-weight: 500; text-align: center;
  color: var(--color-primary, #0d5c63);
  border-top: 1px solid rgba(0, 0, 0, .08);
}
.dark .zx-loc-foot { color: #5eead4; border-top-color: rgba(255, 255, 255, .12); }
.zx-loc-card:hover .zx-loc-foot { text-decoration: underline; }

/* ---- Contact cards (vCard) ---- */
.zx-vcard-wrap { display: flex; flex-direction: column; gap: .375rem; }
.zx-vcard {
  width: 15rem; max-width: 100%;
  border-radius: .5rem; background: rgba(0, 0, 0, .04); overflow: hidden;
}
.dark .zx-vcard { background: rgba(255, 255, 255, .06); }

.zx-vcard-head { display: flex; align-items: center; gap: .5rem; padding: .5rem .625rem; }
.zx-vcard-avatar {
  width: 2.25rem; height: 2.25rem; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9999px; background: var(--color-primary, #0d5c63); color: #fff;
  font-size: .75rem; font-weight: 600; letter-spacing: .02em;
}
.zx-vcard-meta  { min-width: 0; }
.zx-vcard-name  {
  font-size: .8125rem; font-weight: 600; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.zx-vcard-phone { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); }
.zx-vcard-org   { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); opacity: .8; }

.zx-vcard-foot { border-top: 1px solid rgba(0, 0, 0, .08); }
.dark .zx-vcard-foot { border-top-color: rgba(255, 255, 255, .12); }
.zx-vcard-action {
  display: block; width: 100%; padding: .4375rem;
  font-size: .75rem; font-weight: 500; text-align: center;
  color: var(--color-primary, #0d5c63); background: none; border: 0; cursor: pointer;
}
.zx-vcard-action:hover { background: rgba(0, 0, 0, .04); }
.dark .zx-vcard-action { color: #5eead4; }
.dark .zx-vcard-action:hover { background: rgba(255, 255, 255, .06); }

/* ---- Interactive action rows ----
   Divider-separated rows under the body text, the way WhatsApp renders reply buttons. */
.zx-ix-actions {
  margin-top: .375rem; border-top: 1px solid rgba(0, 0, 0, .08);
}
.dark .zx-ix-actions { border-top-color: rgba(255, 255, 255, .12); }

.zx-ix-row {
  display: flex; align-items: center; justify-content: center; gap: .375rem;
  padding: .4375rem .25rem; font-size: .75rem; font-weight: 500;
  color: var(--color-primary, #0d5c63); text-decoration: none;
  border-bottom: 1px solid rgba(0, 0, 0, .08);
}
.zx-ix-row:last-child { border-bottom: 0; }
.dark .zx-ix-row { color: #5eead4; border-bottom-color: rgba(255, 255, 255, .12); }
.zx-ix-row > i { font-size: .8125rem; }
.zx-ix-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Only a cta_url row is a real link; the rest are inert records of what was offered. */
a.zx-ix-row:hover { text-decoration: underline; }
.zx-ix-desc {
  padding: 0 .5rem .375rem; font-size: .6875rem; text-align: center;
  color: var(--color-secondary-foreground, #52525b);
}

/* ==========================================================================
   Per-message actions menu
   ========================================================================== */

/* Trigger: hidden until the bubble is hovered, but always present for keyboard users. */
.zx-msg-menu-btn {
  position: absolute; top: .125rem; inset-inline-end: .125rem;
  width: 1.25rem; height: 1.25rem; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 9999px; cursor: pointer;
  background: rgba(0, 0, 0, .06); color: var(--color-secondary-foreground, #52525b);
  opacity: 0; transition: opacity .12s ease;
}
.zx-msg-menu-btn > i { font-size: .6875rem; }
.zx-chat-bubble:hover .zx-msg-menu-btn,
.zx-msg-menu-btn:focus-visible { opacity: 1; }
.dark .zx-msg-menu-btn { background: rgba(255, 255, 255, .12); }

/* Menu itself is appended to <body> so it is never clipped by the scrolling thread. */
.zx-msg-menu {
  position: fixed; z-index: 60; min-width: 11rem;
  border-radius: .5rem; padding: .25rem;
  background: var(--color-card, #fff);
  border: 1px solid var(--color-border, rgba(0, 0, 0, .1));
  box-shadow: 0 8px 24px rgba(0, 0, 0, .16);
}
.dark .zx-msg-menu { background: var(--color-background, #18181b); }

.zx-msg-menu-emoji {
  display: flex; gap: .125rem; padding: .125rem .125rem .25rem;
  border-bottom: 1px solid var(--color-border, rgba(0, 0, 0, .1)); margin-bottom: .25rem;
}
.zx-emoji-btn {
  width: 1.75rem; height: 1.75rem; padding: 0; font-size: 1rem; line-height: 1;
  border: 0; border-radius: 9999px; background: none; cursor: pointer;
}
.zx-emoji-btn:hover { background: rgba(0, 0, 0, .06); transform: scale(1.15); }
.dark .zx-emoji-btn:hover { background: rgba(255, 255, 255, .1); }

.zx-msg-menu-item {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: .375rem .5rem; border: 0; border-radius: .375rem;
  font-size: .8125rem; text-align: start; background: none; cursor: pointer;
  color: var(--color-foreground, #18181b);
}
.zx-msg-menu-item:hover { background: rgba(0, 0, 0, .06); }
.dark .zx-msg-menu-item { color: var(--color-foreground, #fafafa); }
.dark .zx-msg-menu-item:hover { background: rgba(255, 255, 255, .1); }
.zx-msg-menu-item > i { font-size: .875rem; opacity: .75; }
.zx-msg-menu-danger { color: #dc2626; }
.dark .zx-msg-menu-danger { color: #f87171; }

/* ---- Quoted-reply bar above the composer ---- */
.zx-reply-ctx { padding: .5rem .75rem 0; }
/* The wrapper carries .hidden but its child sets display:flex, which still paints the bar.
   Hide the subtree explicitly. */
.zx-reply-ctx.hidden { display: none; }
.zx-reply-ctx-bar {
  display: flex; align-items: flex-start; gap: .5rem;
  padding: .375rem .5rem; border-radius: .375rem;
  border-inline-start: 3px solid var(--color-primary, #0d5c63);
  background: rgba(0, 0, 0, .04);
}
.dark .zx-reply-ctx-bar { background: rgba(255, 255, 255, .06); }
.zx-reply-ctx-text { min-width: 0; flex: 1; }
.zx-reply-ctx-who  {
  display: block; font-size: .6875rem; font-weight: 600;
  color: var(--color-primary, #0d5c63);
}
.dark .zx-reply-ctx-who { color: #5eead4; }
.zx-reply-ctx-body {
  display: block; font-size: .75rem; line-height: 1.35;
  color: var(--color-secondary-foreground, #52525b);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.zx-reply-ctx-close {
  flex-shrink: 0; width: 1.25rem; height: 1.25rem; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 9999px; background: none; cursor: pointer;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-reply-ctx-close:hover { background: rgba(0, 0, 0, .08); }
.dark .zx-reply-ctx-close:hover { background: rgba(255, 255, 255, .12); }

/* ==========================================================================
   Thread polish — grouping, unread divider, jump-to-latest
   ========================================================================== */

/* Grouping: consecutive messages from the same side sit tight together, with a normal gap
   between runs. The canvas sets gap:.25rem; these adjust around it. */
.zx-grp-cont { margin-top: -.125rem; }
.zx-grp-end  { margin-bottom: .375rem; }

/* The tail sits at top:0, so it belongs to the first bubble of a run; continuation bubbles
   drop it and square the corner where it would have been, so the run reads as one block. */
.zx-chat-bubble-notail::before,
.zx-chat-bubble-notail::after { display: none; }
.zx-grp-cont .zx-chat-bubble-out { border-top-right-radius: .25rem; }
.zx-grp-cont .zx-chat-bubble-in  { border-top-left-radius: .25rem; }

/* ---- Loading skeleton ---- */
.zx-skel-bubble {
  height: 2.25rem; border-radius: .75rem; max-width: 65%;
  background: linear-gradient(90deg,
    rgba(0, 0, 0, .05) 25%, rgba(0, 0, 0, .09) 37%, rgba(0, 0, 0, .05) 63%);
  background-size: 400% 100%;
  animation: zx-skel 1.4s ease-in-out infinite;
}
.dark .zx-skel-bubble {
  background: linear-gradient(90deg,
    rgba(255, 255, 255, .06) 25%, rgba(255, 255, 255, .11) 37%, rgba(255, 255, 255, .06) 63%);
  background-size: 400% 100%;
}
@keyframes zx-skel { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
/* Respect a reduced-motion preference: keep the placeholder, drop the shimmer. */
@media (prefers-reduced-motion: reduce) { .zx-skel-bubble { animation: none; } }

/* ---- Unread divider ---- */
.zx-unread-div {
  display: flex; align-items: center; gap: .625rem;
  margin: .625rem 0 .25rem;
}
.zx-unread-div::before,
.zx-unread-div::after {
  content: ''; flex: 1; height: 1px; background: var(--color-primary, #0d5c63); opacity: .35;
}
.zx-unread-label {
  font-size: .6875rem; font-weight: 600; letter-spacing: .01em;
  color: var(--color-primary, #0d5c63);
  padding: .125rem .5rem; border-radius: 9999px;
  background: rgba(13, 92, 99, .08);
}
.dark .zx-unread-label { color: #5eead4; background: rgba(94, 234, 212, .12); }
.dark .zx-unread-div::before,
.dark .zx-unread-div::after { background: #5eead4; }

/* ---- Jump to latest ---- */
.zx-jump-latest {
  position: absolute; inset-inline-end: 1.25rem; bottom: 1rem; z-index: 20;
  width: 2.25rem; height: 2.25rem; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-border, rgba(0, 0, 0, .1)); border-radius: 9999px;
  background: var(--color-card, #fff); cursor: pointer;
  color: var(--color-secondary-foreground, #52525b);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .16);
}
.zx-jump-latest:hover { background: var(--color-accent, #f4f4f5); }
.dark .zx-jump-latest { background: var(--color-background, #18181b); }
/* Same cascade trap as .zx-jump-count: this file loads after Tailwind, so `display:flex`
   would beat `.hidden` and the button would never actually hide. */
.zx-jump-latest.hidden { display: none; }
.zx-jump-count {
  position: absolute; top: -.25rem; inset-inline-end: -.25rem;
  min-width: 1.125rem; height: 1.125rem; padding: 0 .25rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9999px; background: var(--color-primary, #0d5c63); color: #fff;
  font-size: .625rem; font-weight: 700; line-height: 1;
}
/* custom.css loads AFTER the Tailwind bundle, so `display:flex` above beats `.hidden`
   at equal specificity — the badge stayed painted showing "0". Restore the intent. */
.zx-jump-count.hidden { display: none; }

/* ---- Composer "+" action menu (docs/006 #7) ----
   One labeled popover replaces the six mono action icons (and the old mobile more-sheet). Anchored
   above the "+" button; each row is icon + text label for discoverability. Works at every size. */
.zx-plus-menu {
  position: absolute; bottom: 100%; inset-inline-start: 0; margin-bottom: .5rem; z-index: 40;
  min-width: 13rem; padding: .375rem;
  background: var(--color-background, #fff);
  border: 1px solid var(--color-border, #e4e4e7); border-radius: .625rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .14);
}
.zx-plus-row {
  display: flex; align-items: center; gap: .625rem; width: 100%; text-align: start;
  padding: .5rem .625rem; border: 0; background: none; cursor: pointer;
  border-radius: .5rem; font-size: .8125rem; color: var(--color-mono, #18181b);
}
.zx-plus-row:hover { background: var(--color-accent, #f4f4f5); }
.zx-plus-row i { font-size: 1rem; color: var(--color-muted-foreground, #71717a); width: 1.25rem; text-align: center; }
.dark .zx-plus-row { color: var(--color-mono, #fafafa); }
.dark .zx-plus-row:hover { background: rgba(255, 255, 255, .06); }

/* ---- Interactive builder: two-column layout + live preview + counters (docs/006 Tier 3.3) ---- */
.zx-ix-grid { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
@media (min-width: 640px) { .zx-ix-grid { grid-template-columns: 1fr 15rem; } }
.zx-ix-preview-col { display: flex; flex-direction: column; gap: .375rem; }
.zx-ix-preview-label { font-size: .6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--color-muted-foreground, #71717a); }
/* A soft chat-canvas backdrop so the preview reads like a message in the thread. */
.zx-ix-preview { background: var(--zx-chat-canvas-grad); border: 1px solid var(--color-border, #e4e4e7); border-radius: .625rem; padding: .75rem; min-height: 8rem; }

.zx-ix-pv-bubble {
  background: var(--zx-chat-bubble-out); color: var(--color-mono, #111827);
  border-radius: var(--zx-chat-bubble-radius); border-top-right-radius: var(--zx-chat-bubble-notch);
  box-shadow: var(--zx-chat-bubble-shadow); padding: .5rem .625rem; max-width: 100%;
}
.dark .zx-ix-pv-bubble { background: var(--zx-chat-bubble-out-dark, #12352f); color: var(--color-foreground, #fafafa); }
.zx-ix-pv-header { font-weight: 700; font-size: .8125rem; margin-bottom: .1875rem; }
.zx-ix-pv-body { font-size: .8125rem; white-space: pre-wrap; overflow-wrap: anywhere; }
.zx-ix-pv-muted { color: var(--color-muted-foreground, #9ca3af); font-style: italic; }
.zx-ix-pv-footer { font-size: .6875rem; color: var(--color-muted-foreground, #6b7280); margin-top: .25rem; }
.zx-ix-pv-actions { margin-top: .5rem; display: flex; flex-direction: column; gap: .25rem; }
/* Reply/list/cta buttons render as WhatsApp does: full-width, brand-colored, above the bubble tail. */
.zx-ix-pv-btn {
  display: flex; align-items: center; justify-content: center; gap: .375rem;
  background: var(--color-background, #fff); color: var(--zx-brand, #0d5c63);
  border: 1px solid rgba(13, 92, 99, .25); border-radius: .5rem;
  padding: .375rem; font-size: .75rem; font-weight: 600;
}
.dark .zx-ix-pv-btn { background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.14); color: #7fd4cf; }
.zx-ix-pv-btn i { font-size: .875rem; }

/* Per-field n/max character counter. */
.zx-ix-counter { display: block; text-align: end; font-size: .625rem; color: var(--color-muted-foreground, #9ca3af); margin-top: -.125rem; }

/* Sidebar plan strip clips in the collapsed rail — hide it there (docs/006 Tier 3.4 / #11).
   The inbox auto-collapses the sidebar, so this is where it was most visible. */
body.kt-sidebar-collapse .zx-plan-strip { display: none; }

/* ---- Drag-and-drop upload overlay (docs/006 Tier 3.5) ---- */
.zx-drop-overlay {
  position: absolute; inset: 0; z-index: 30;
  display: flex; align-items: center; justify-content: center;
  background: rgba(13, 92, 99, .10); backdrop-filter: blur(1px);
  border: 2px dashed var(--zx-brand, #0d5c63); border-radius: .5rem;
  pointer-events: none;   /* let the drop event reach the pane underneath */
}
/* custom.css loads after the portal Tailwind bundle, so the display:flex above beats Tailwind's
   `.hidden { display:none }` (same specificity, later wins) — the overlay would show ALWAYS. Restore
   the hide explicitly. (The custom-css-beats-.hidden trap — verify with getComputedStyle, not classList.) */
.zx-drop-overlay.hidden { display: none; }
.zx-drop-inner {
  display: flex; flex-direction: column; align-items: center; gap: .5rem;
  padding: 1rem 1.5rem; border-radius: .75rem;
  background: var(--color-background, #fff); box-shadow: 0 8px 24px rgba(0,0,0,.14);
  color: var(--zx-brand, #0d5c63); font-weight: 600; font-size: .875rem;
}
.zx-drop-inner i { font-size: 1.75rem; }
.dark .zx-drop-inner { background: #172221; color: #7fd4cf; }

/* ==========================================================================
   Authentication (OTP) template modal — gap 2.2
   ========================================================================== */

.zx-auth-langs {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: .375rem;
  max-height: 9rem; overflow-y: auto;
  padding: .5rem; border: 1px solid var(--color-border, rgba(0, 0, 0, .1)); border-radius: .5rem;
}
.zx-auth-lang { display: flex; align-items: center; gap: .375rem; font-size: .8125rem; }

/* Preview area. Meta returns the rendered strings; these are shown as WhatsApp bubbles so the
   customer can judge the copy they are NOT allowed to write. */
.zx-auth-preview {
  display: flex; flex-direction: column; gap: .625rem;
  max-height: 16rem; overflow-y: auto;
  padding: .75rem; border-radius: .5rem;
  background: var(--zx-chat-canvas, #f4f4f5);
}
.dark .zx-auth-preview { background: rgba(255, 255, 255, .04); }

.zx-auth-prev-card { display: flex; flex-direction: column; gap: .25rem; }
.zx-auth-prev-lang {
  font-size: .625rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-auth-prev-bubble {
  align-self: flex-start; max-width: 20rem;
  padding: .5rem .625rem; border-radius: .5rem;
  background: var(--zx-chat-bubble-in, #fff);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12);
}
.dark .zx-auth-prev-bubble { background: var(--color-background, #18181b); }
.zx-auth-prev-body   { font-size: .8125rem; line-height: 1.4; white-space: pre-wrap; }
.zx-auth-prev-footer {
  margin-top: .25rem; font-size: .6875rem;
  color: var(--color-secondary-foreground, #52525b);
}
/* Rendered like WhatsApp's in-bubble action row, so it reads as a button not body text. */
.zx-auth-prev-btn {
  margin-top: .375rem; padding-top: .375rem;
  border-top: 1px solid rgba(0, 0, 0, .08);
  font-size: .75rem; font-weight: 500; text-align: center;
  color: var(--color-primary, #0d5c63);
}
.dark .zx-auth-prev-btn { border-top-color: rgba(255, 255, 255, .12); color: #5eead4; }

/* custom.css loads after Tailwind, so `display` here would defeat `.hidden`. */
.zx-auth-langs.hidden, .zx-auth-preview.hidden { display: none; }

/* ==========================================================================
   Carousel + limited-time offer authoring (gaps 2.3, 2.4)
   ========================================================================== */

.zx-carousel-card {
  display: flex; flex-direction: column; gap: .5rem;
  padding: .75rem; border-radius: .5rem;
  border: 1px solid var(--color-border, rgba(0, 0, 0, .1));
  background: rgba(0, 0, 0, .02);
}
.dark .zx-carousel-card { background: rgba(255, 255, 255, .03); }
.zx-carousel-card-head { display: flex; align-items: center; justify-content: space-between; }

/* Limitation notice. Deliberately prominent: an LTO template is invisible to WhatsApp
   Web/Desktop users, and a carousel's card count is frozen at approval — both are decisions
   the customer cannot undo later. */
.zx-lto-warn {
  display: flex; align-items: flex-start; gap: .5rem;
  padding: .5rem .625rem; border-radius: .375rem;
  font-size: .75rem; line-height: 1.4;
  background: rgba(234, 179, 8, .1);
  border: 1px solid rgba(234, 179, 8, .3);
  color: #854d0e;
}
.dark .zx-lto-warn { color: #fde68a; background: rgba(234, 179, 8, .08); }
.zx-lto-warn > i { flex-shrink: 0; margin-top: .0625rem; }

.zx-lto-fields { display: flex; flex-direction: column; gap: .5rem; }

/* custom.css loads after Tailwind, so `display` above would defeat `.hidden`. */
.zx-carousel-card.hidden, .zx-lto-warn.hidden, .zx-lto-fields.hidden { display: none; }

/* ==========================================================================
   Number settings modal — profile, chat setup, blocklist, Meta analytics
   (gaps 2.7, 2.8, 2.11, 2.12)
   ========================================================================== */

.zx-ns-tabs {
  display: flex; gap: .25rem; padding: 0 1.5rem;
  border-bottom: 1px solid var(--color-border, rgba(0, 0, 0, .1));
  overflow-x: auto;
}
.zx-ns-tab {
  display: inline-flex; align-items: center; gap: .375rem; white-space: nowrap;
  padding: .75rem .625rem; border: 0; background: none; cursor: pointer;
  font-size: .8125rem; font-weight: 500;
  color: var(--color-secondary-foreground, #52525b);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.zx-ns-tab:hover { color: var(--color-foreground, #18181b); }
.zx-ns-tab.is-active {
  color: var(--color-primary, #0d5c63);
  border-bottom-color: var(--color-primary, #0d5c63);
}
.dark .zx-ns-tab.is-active { color: #5eead4; border-bottom-color: #5eead4; }
.zx-ns-tab > i { font-size: .875rem; }

.zx-ns-cmd-row { display: flex; align-items: center; gap: .5rem; }
.zx-ns-cmd-row > .kt-input { min-width: 0; }

/* custom.css loads after Tailwind, so a `display` rule here would defeat `.hidden`. */
.zx-ns-panel.hidden, .zx-ns-tabs.hidden, .zx-ns-cmd-row.hidden { display: none; }

/* ==========================================================================
   Template Library picker (gap 2.10)
   ========================================================================== */

.zx-lib-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: .75rem;
  max-height: 26rem; overflow-y: auto; padding-right: .25rem;
}
.zx-lib-card {
  display: flex; flex-direction: column; gap: .375rem;
  padding: .75rem; border-radius: .5rem;
  border: 1px solid var(--color-border, rgba(0, 0, 0, .1));
  background: var(--color-card, #fff);
}
.dark .zx-lib-card { background: rgba(255, 255, 255, .03); }
.zx-lib-card-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.zx-lib-name { font-size: .8125rem; font-weight: 600; word-break: break-word; }
.zx-lib-body {
  font-size: .75rem; line-height: 1.45;
  color: var(--color-secondary-foreground, #52525b);
  /* Bodies vary wildly in length; clamp so the grid stays even. */
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.zx-lib-meta { font-size: .625rem; text-transform: uppercase; letter-spacing: .03em;
  color: var(--color-secondary-foreground, #52525b); opacity: .8; }
.zx-lib-foot { display: flex; justify-content: flex-end; margin-top: auto; padding-top: .25rem; }

.zx-lib-preview {
  padding: .75rem; border-radius: .5rem;
  background: var(--zx-chat-canvas, #f4f4f5);
}
.dark .zx-lib-preview { background: rgba(255, 255, 255, .04); }
.zx-lib-preview-body {
  max-width: 22rem; padding: .5rem .625rem; border-radius: .5rem;
  background: var(--zx-chat-bubble-in, #fff);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12);
  font-size: .8125rem; line-height: 1.45; white-space: pre-wrap;
}
.dark .zx-lib-preview-body { background: var(--color-background, #18181b); }

/* custom.css loads after Tailwind, so `display` here would defeat `.hidden`. */
.zx-lib-list.hidden, .zx-lib-card.hidden, .zx-lib-preview.hidden { display: none; }

/* ==========================================================================
   Flows form builder + responses (gap 2.1)
   ========================================================================== */

.zx-fb-screen {
  display: flex; flex-direction: column; gap: .375rem;
  padding: .75rem; border-radius: .5rem;
  border: 1px solid var(--color-border, rgba(0, 0, 0, .1));
  background: rgba(0, 0, 0, .02);
}
.dark .zx-fb-screen { background: rgba(255, 255, 255, .03); }
.zx-fb-screen-head { display: flex; align-items: center; justify-content: space-between; }

.zx-fb-comp { display: flex; align-items: center; gap: .375rem; }
.zx-fb-comp > .kt-input, .zx-fb-comp > .kt-select { min-width: 0; }

/* The generated Flow JSON. Monospace and scrollable: it is a document, not a label. */
.zx-fb-preview {
  max-height: 14rem; overflow: auto;
  padding: .625rem; border-radius: .5rem;
  background: rgba(0, 0, 0, .04);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .6875rem; line-height: 1.45; white-space: pre;
}
.dark .zx-fb-preview { background: rgba(255, 255, 255, .06); }

/* Meta's validation errors, with their line numbers. */
.zx-fb-errors {
  padding: .5rem .625rem; border-radius: .375rem;
  background: rgba(220, 38, 38, .08);
  border: 1px solid rgba(220, 38, 38, .25);
  color: #991b1b;
}
.dark .zx-fb-errors { color: #fca5a5; background: rgba(220, 38, 38, .12); }

/* ---- Responses ---- */
.zx-fb-sub {
  padding: .625rem .75rem; border-radius: .5rem;
  border: 1px solid var(--color-border, rgba(0, 0, 0, .1));
}
.zx-fb-sub-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: .375rem;
}
.zx-fb-sub-row { display: flex; gap: .75rem; font-size: .8125rem; padding: .0625rem 0; }
.zx-fb-sub-key {
  flex-shrink: 0; width: 9rem; text-transform: capitalize;
  color: var(--color-secondary-foreground, #52525b);
}

/* custom.css loads after Tailwind, so a `display` rule here would defeat `.hidden`. */
.zx-fb-preview.hidden, .zx-fb-errors.hidden,
.zx-fb-screen.hidden, .zx-fb-comp.hidden, .zx-fb-sub.hidden { display: none; }

/* ---- Message info modal ---- */
.zx-info-list { display: flex; flex-direction: column; gap: .5rem; }
.zx-info-row  { display: flex; align-items: baseline; gap: .75rem; }
.zx-info-key  {
  flex-shrink: 0; width: 7.5rem; font-size: .75rem;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-info-val  { font-size: .8125rem; word-break: break-all; }

/* Interactive builder: the per-type field groups toggle between hidden and flex. */
#ix-list-fields.flex, #ix-cta-fields.flex, #ix-button-fields.flex { display: flex; }

/* ── Template editor: repeatable button rows ─────────────────────────────────── */
.zx-tpl-btn-row {
  display: flex; align-items: center; gap: .5rem;
}
.zx-tpl-btn-row > .kt-input { min-width: 0; }

/* Prerequisite note under the coexistence connect button. Stated before the customer
   starts, because Meta only reports an unmet prerequisite mid-flow, after a dead end. */
.zx-es-prereq {
  display: block; max-width: 22rem; text-align: right;
  font-size: .6875rem; line-height: 1.35;
  color: var(--color-secondary-foreground, #52525b);
}

/* Sidebar plan strip — sits at the TOP of the nav (moved from the bottom card 2026-08-24).
   One compact row: plan name + a single status line.

   COLOURS ARE HARD-CODED ON PURPOSE. The sidebar is a dark green panel in BOTH themes, but
   var(--color-muted-foreground) / var(--color-border) resolve to their LIGHT-theme values
   here — the note rendered at 1.60:1 against the panel (WCAG AA small text needs 4.5:1),
   i.e. effectively invisible. Do not swap these back to theme tokens. */
.zx-plan-strip {
  display: flex; flex-direction: column; gap: .1875rem;
  margin: 0 .625rem .5rem; padding: .5rem .625rem;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: .5rem;
  background: rgba(0, 0, 0, .22);
  text-decoration: none; transition: background .15s ease, border-color .15s ease;
}
.zx-plan-strip:hover { background: rgba(0, 0, 0, .30); border-color: rgba(255, 255, 255, .34); }
.zx-plan-strip-caption {
  font-size: .625rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: rgba(255, 255, 255, .72);
  margin-bottom: .0625rem;
}
.zx-plan-strip-main { display: flex; align-items: center; gap: .375rem; min-width: 0; }
.zx-plan-strip-icon { font-size: .875rem; flex-shrink: 0; color: rgba(255, 255, 255, .90); }
.zx-plan-strip-name {
  font-size: .875rem; font-weight: 700; color: #fff;         /* 5.1:1 on the panel */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.zx-plan-strip-tag {
  flex-shrink: 0; padding: .0625rem .3125rem; border-radius: .25rem;
  font-size: .625rem; font-weight: 700; text-transform: uppercase; letter-spacing: .02em;
  background: rgba(255, 255, 255, .92); color: #0f7a5a;      /* inverted: reads at any size */
}
.zx-plan-strip-note {
  padding-left: 1.25rem;                    /* aligns under the name, clear of the icon */
  font-size: .75rem; line-height: 1.35; font-weight: 500;
  color: rgba(255, 255, 255, .95);          /* was muted grey at 1.60:1 — unreadable */
}
.zx-plan-strip.is-soon    .zx-plan-strip-note { color: #fde68a; font-weight: 600; }
.zx-plan-strip.is-overdue .zx-plan-strip-note { color: #fecaca; font-weight: 600; }
.zx-plan-strip.is-overdue {
  border-color: rgba(252, 165, 165, .55);
  background: rgba(239, 68, 68, .28);
}
.zx-plan-strip.is-overdue:hover { background: rgba(239, 68, 68, .36); }

/* ---- WhatsApp Setup: finish-setup guidance ------------------------------------------------
   The page reported "pending / not_verified" with four unlabelled icon buttons and no way to
   tell what was left or whose move it was. This card renders the checklist getStatus() already
   returned, and names the actor for the blocking step. */
.zx-wg { display: flex; flex-direction: column; gap: 1rem; margin-bottom: 1.25rem; }
.zx-wg-card {
  border: 1px solid color-mix(in srgb, #eab308 40%, transparent);
  border-radius: .75rem;
  background: color-mix(in srgb, #eab308 6%, transparent);
  padding: 1.125rem 1.25rem;
}
.zx-wg-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; flex-wrap: wrap; }
.zx-wg-title {
  display: flex; align-items: center; gap: .5rem;
  font-size: .9375rem; font-weight: 700; color: var(--color-foreground, #18181b);
}
.zx-wg-title > i { color: #16a34a; font-size: 1rem; }
.zx-wg-progress { font-size: .75rem; font-weight: 600; color: var(--color-secondary-foreground, #52525b); }

.zx-wg-bar {
  height: .375rem; border-radius: 9999px; margin: .75rem 0 .875rem;
  background: color-mix(in srgb, var(--color-foreground, #18181b) 10%, transparent);
  overflow: hidden;
}
.zx-wg-bar-fill { height: 100%; border-radius: 9999px; background: #eab308; transition: width .3s ease; }

.zx-wg-steps { display: flex; flex-wrap: wrap; gap: .375rem .875rem; list-style: none; padding: 0; margin: 0 0 .875rem; }
.zx-wg-step { display: inline-flex; align-items: center; gap: .3125rem; font-size: .75rem; }
.zx-wg-step > i { font-size: .8125rem; }
.zx-wg-step.is-done { color: #15803d; }
.zx-wg-step.is-done > i { color: #16a34a; }
.zx-wg-step.is-todo { color: var(--color-secondary-foreground, #52525b); }

/* The blocking step, with the actor named. */
.zx-wg-next {
  border-radius: .5rem; padding: .75rem .875rem; margin-bottom: .875rem;
  background: var(--color-card, #fff);
  border: 1px solid var(--color-border, #e4e4e7);
}
.zx-wg-who {
  display: inline-block; margin-bottom: .3125rem; padding: .125rem .4375rem;
  border-radius: .3125rem; font-size: .625rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
}
.zx-wg-next.is-you  .zx-wg-who { background: color-mix(in srgb, #eab308 22%, transparent); color: #854d0e; }
.zx-wg-next.is-meta .zx-wg-who { background: color-mix(in srgb, #3b82f6 18%, transparent); color: #1d4ed8; }
.zx-wg-next.is-us   .zx-wg-who { background: color-mix(in srgb, #16a34a 18%, transparent); color: #15803d; }
.zx-wg-what { font-size: .8125rem; line-height: 1.55; color: var(--color-secondary-foreground, #52525b); }
.zx-wg-what > b { color: var(--color-foreground, #18181b); }

.zx-wg-error {
  display: flex; align-items: flex-start; gap: .375rem;
  font-size: .75rem; line-height: 1.5; color: #b91c1c; margin-bottom: .875rem;
}
.zx-wg-actions { display: flex; flex-wrap: wrap; gap: .5rem; }

/* In-app phone verification (request_code → verify_code). Meta sends the code to the NUMBER,
   so the customer needs the handset — the copy says so before they start. */
.zx-wg-verify {
  margin-top: .875rem; padding: .875rem;
  border: 1px solid var(--color-border, #e4e4e7); border-radius: .625rem;
  background: var(--color-card, #fff);
}
.zx-wg-verify-lead { font-size: .8125rem; color: var(--color-secondary-foreground, #52525b); margin-bottom: .625rem; }
/* How a WhatsApp number was connected. Coexistence behaves differently enough (no phone
   verification, no /register, fixed 20 mps, Business app still live) that the mode has to be
   visible on the row, or the other columns read as inconsistent. */
.zx-wa-mode { display: inline-block; width: fit-content; margin-top: .1875rem; padding: .0625rem .375rem;
  border-radius: .25rem; font-size: .625rem; line-height: 1.4; font-weight: 600; letter-spacing: .01em;
  border: 1px solid transparent; }
.zx-wa-mode.is-coexistence { color: #065f46; background: #ecfdf5; border-color: #a7f3d0; }
.zx-wa-mode.is-manual      { color: #92400e; background: #fffbeb; border-color: #fde68a; }
.zx-wa-mode.is-standard    { color: #3730a3; background: #eef2ff; border-color: #c7d2fe; }

/* Meta test number (+1 555…): a code can be requested but never received, so warn before the
   customer spends attempts on a number that cannot finish verification. */
.zx-wg-verify-lead.is-warn { color: #92400e; background: #fffbeb; border: 1px solid #fde68a;
  border-radius: .5rem; padding: .5rem .625rem; }
.zx-wg-verify-row { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.zx-wg-code { margin-top: .875rem; }
.zx-wg-code-label {
  display: block; margin-bottom: .3125rem;
  font-size: .75rem; font-weight: 600; color: var(--color-foreground, #18181b);
}
.zx-wg-code-input {
  max-width: 9rem; font-size: 1rem; font-weight: 600;
  letter-spacing: .18em; text-align: center;
}
.zx-wg-code-hint { margin-top: .375rem; font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); }
.zx-wg-verify-msg { margin-top: .625rem; font-size: .75rem; line-height: 1.5; }
.zx-wg-verify-msg.is-ok    { color: #15803d; }
.zx-wg-verify-msg.is-error { color: #b91c1c; }

/* ---- System Status -----------------------------------------------------------------------
   A status page exists to answer "is it working?" in one glance. The old one was four
   identical white cards with no colour, no icons and no badges — and it relied on Tailwind
   palette classes that the prebuilt portal build only PARTLY ships: `bg-green-500` rendered
   but `bg-amber-500` came back transparent and `text-green-700` never applied. The missing
   ones were exactly the warning states, so a degraded system looked identical to a healthy
   one. State colour is therefore defined here, self-contained. */
.zx-st-loading {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  padding: 4rem 1rem; color: var(--color-secondary-foreground, #52525b); font-size: .875rem;
}
.zx-st-updated { font-size: .75rem; color: var(--color-secondary-foreground, #52525b); }
#st-refresh.is-busy { opacity: .6; pointer-events: none; }

/* Headline banner — the answer, before any detail. */
.zx-st-banner {
  display: flex; align-items: center; gap: .875rem;
  padding: 1.125rem 1.25rem; margin-bottom: 1.25rem;
  border: 1px solid transparent; border-radius: .75rem;
}
.zx-st-banner-icon { font-size: 1.5rem; flex-shrink: 0; }
.zx-st-banner-text { display: flex; flex-direction: column; gap: .125rem; min-width: 0; }
.zx-st-banner-title { font-size: 1.0625rem; font-weight: 700; }
.zx-st-banner-note  { font-size: .8125rem; opacity: .85; }

.zx-st-banner.is-ok   { background: color-mix(in srgb, #16a34a 10%, transparent); border-color: color-mix(in srgb, #16a34a 35%, transparent); }
.zx-st-banner.is-ok   .zx-st-banner-icon, .zx-st-banner.is-ok   .zx-st-banner-title { color: #15803d; }
.zx-st-banner.is-warn { background: color-mix(in srgb, #eab308 14%, transparent); border-color: color-mix(in srgb, #eab308 45%, transparent); }
.zx-st-banner.is-warn .zx-st-banner-icon, .zx-st-banner.is-warn .zx-st-banner-title { color: #a16207; }
.zx-st-banner.is-down { background: color-mix(in srgb, #ef4444 12%, transparent); border-color: color-mix(in srgb, #ef4444 40%, transparent); }
.zx-st-banner.is-down .zx-st-banner-icon, .zx-st-banner.is-down .zx-st-banner-title { color: #b91c1c; }
.zx-st-banner.is-info { background: color-mix(in srgb, #3b82f6 10%, transparent); border-color: color-mix(in srgb, #3b82f6 35%, transparent); }
.zx-st-banner.is-info .zx-st-banner-icon, .zx-st-banner.is-info .zx-st-banner-title { color: #1d4ed8; }

.zx-st-grid { display: grid; grid-template-columns: 1fr; gap: 1.25rem; margin-bottom: 1.25rem; }
@media (min-width: 1024px) { .zx-st-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.zx-st-card {
  border: 1px solid var(--color-border, #e4e4e7); border-radius: .75rem;
  background: var(--color-card, #fff); padding: 1.125rem 1.25rem; margin-bottom: 1.25rem;
}
.zx-st-grid > .zx-st-card { margin-bottom: 0; }
.zx-st-card-title {
  display: flex; align-items: center; gap: .5rem; margin-bottom: .875rem;
  font-size: .9375rem; font-weight: 600; color: var(--color-foreground, #18181b);
}
.zx-st-card-title > i { font-size: .9375rem; color: var(--color-secondary-foreground, #52525b); }
.zx-st-sub { font-size: .6875rem; font-weight: 500; color: var(--color-secondary-foreground, #52525b); }

/* Chips — one shape for every state, so colour is the only variable. */
.zx-st-chip {
  display: inline-flex; align-items: center; padding: .125rem .4375rem;
  border-radius: .3125rem; font-size: .6875rem; font-weight: 600; white-space: nowrap;
  text-transform: capitalize;
}
.zx-st-chip.is-ok    { background: color-mix(in srgb, #16a34a 15%, transparent); color: #15803d; }
.zx-st-chip.is-warn  { background: color-mix(in srgb, #eab308 22%, transparent); color: #854d0e; }
.zx-st-chip.is-down  { background: color-mix(in srgb, #ef4444 15%, transparent); color: #b91c1c; }
.zx-st-chip.is-info  { background: color-mix(in srgb, #3b82f6 15%, transparent); color: #1d4ed8; }
.zx-st-chip.is-muted { background: color-mix(in srgb, var(--color-foreground, #18181b) 8%, transparent); color: var(--color-secondary-foreground, #52525b); }

/* WhatsApp numbers — each row says what the state MEANS and what to do. */
.zx-st-num {
  display: flex; align-items: flex-start; gap: .625rem;
  padding: .75rem; border-radius: .625rem; margin-bottom: .5rem;
  border: 1px solid var(--color-border, #e4e4e7);
}
.zx-st-num:last-child { margin-bottom: 0; }
.zx-st-num.is-warn { border-color: color-mix(in srgb, #eab308 40%, transparent); background: color-mix(in srgb, #eab308 6%, transparent); }
.zx-st-num.is-down { border-color: color-mix(in srgb, #ef4444 40%, transparent); background: color-mix(in srgb, #ef4444 6%, transparent); }
.zx-st-num-icon { font-size: 1rem; flex-shrink: 0; margin-top: .0625rem; }
.zx-st-num.is-ok   .zx-st-num-icon { color: #16a34a; }
.zx-st-num.is-warn .zx-st-num-icon { color: #ca8a04; }
.zx-st-num.is-down .zx-st-num-icon { color: #dc2626; }
.zx-st-num.is-muted .zx-st-num-icon { color: var(--color-secondary-foreground, #52525b); }
.zx-st-num-text { display: flex; flex-direction: column; gap: .0625rem; min-width: 0; flex: 1; }
.zx-st-num-name { font-size: .8125rem; font-weight: 600; color: var(--color-foreground, #18181b); }
.zx-st-num-phone { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); }
.zx-st-num-note  { font-size: .6875rem; line-height: 1.4; color: var(--color-secondary-foreground, #52525b); margin-top: .125rem; }
.zx-st-num-tags { display: flex; flex-direction: column; align-items: flex-end; gap: .25rem; flex-shrink: 0; }

/* Incidents */
.zx-st-incident { border-left: 3px solid transparent; padding: .25rem 0 .75rem .75rem; margin-bottom: .75rem; }
.zx-st-incident:last-child { margin-bottom: 0; }
.zx-st-incident.is-warn { border-left-color: #eab308; }
.zx-st-incident.is-down { border-left-color: #ef4444; }
.zx-st-incident.is-info { border-left-color: #3b82f6; }
.zx-st-incident-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .375rem; margin-bottom: .3125rem; }
.zx-st-incident-title { font-size: .875rem; font-weight: 600; color: var(--color-foreground, #18181b); }
.zx-st-incident-body  { font-size: .8125rem; line-height: 1.55; color: var(--color-secondary-foreground, #52525b); margin-top: .1875rem; }
.zx-st-affects { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); }
.zx-st-time    { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); margin-top: .25rem; }

.zx-st-resolved {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .5rem 0; border-bottom: 1px solid var(--color-border, #e4e4e7);
}
.zx-st-resolved:last-child { border-bottom: 0; }
.zx-st-resolved-title { display: inline-flex; align-items: center; gap: .375rem; font-size: .8125rem; color: var(--color-foreground, #18181b); }
.zx-st-resolved-title > i { color: #16a34a; font-size: .8125rem; }

/* Empty states carry the same colour language as populated ones. */
.zx-st-empty {
  display: flex; align-items: center; gap: .5rem;
  padding: .875rem; border-radius: .5rem; font-size: .8125rem;
  background: color-mix(in srgb, var(--color-foreground, #18181b) 3%, transparent);
  color: var(--color-secondary-foreground, #52525b);
}
.zx-st-empty.is-ok { background: color-mix(in srgb, #16a34a 8%, transparent); color: #15803d; }
.zx-st-empty > i { font-size: .9375rem; }

:root:not([data-theme="light"]) .zx-st-banner.is-ok   .zx-st-banner-title,
:root[data-theme="dark"] .zx-st-banner.is-ok   .zx-st-banner-title { color: #4ade80; }
:root:not([data-theme="light"]) .zx-st-banner.is-warn .zx-st-banner-title,
:root[data-theme="dark"] .zx-st-banner.is-warn .zx-st-banner-title { color: #fcd34d; }
:root:not([data-theme="light"]) .zx-st-banner.is-down .zx-st-banner-title,
:root[data-theme="dark"] .zx-st-banner.is-down .zx-st-banner-title { color: #fca5a5; }

/* ---- Help & Documentation ----------------------------------------------------------------
   The page was 7.3 screens of unbroken prose (1,600+ words, 16 cards) with no search, no
   collapsing, and a table of contents that scrolled away after the first screen — the markup
   asked for `lg:grid-cols-[260px_1fr]` and `lg:sticky`, but the portal Tailwind is prebuilt so
   BOTH silently no-opped: one column, position:static. Layout is written here instead. */
.zx-docs-layout { display: grid; grid-template-columns: 1fr; gap: 1.25rem; align-items: start; }
@media (min-width: 1024px) {
  .zx-docs-layout { grid-template-columns: 264px minmax(0, 1fr); gap: 1.875rem; }
  .zx-docs-toc { position: sticky; top: 90px; }   /* the whole point of a TOC */
}

.zx-docs-toc-inner {
  border: 1px solid var(--color-border, #e4e4e7);
  border-radius: .75rem;
  background: var(--color-card, #fff);
  padding: .75rem;
  max-height: calc(100vh - 110px);
  overflow-y: auto;
}
.zx-docs-toc-title {
  display: block; padding: 0 .5rem .5rem;
  font-size: .6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-docs-nav { display: flex; flex-direction: column; gap: .125rem; }
.zx-docs-nav > a {
  display: flex; align-items: center; gap: .5rem;
  padding: .4375rem .5rem; border-radius: .5rem;
  font-size: .8125rem; text-decoration: none;
  color: var(--color-secondary-foreground, #52525b);
  border-left: 2px solid transparent;
}
.zx-docs-nav > a > i { font-size: .875rem; flex-shrink: 0; opacity: .8; }
.zx-docs-nav > a:hover { background: color-mix(in srgb, var(--color-foreground, #18181b) 5%, transparent); }
/* Scroll-spy: shows WHERE you are in a long document. */
.zx-docs-nav > a.is-active {
  background: color-mix(in srgb, var(--color-primary, #0d5c63) 10%, transparent);
  border-left-color: var(--color-primary, #0d5c63);
  color: var(--color-primary, #0d5c63); font-weight: 600;
}
.zx-docs-nav > a[hidden] { display: none; }
.zx-docs-noresult { padding: .5rem; font-size: .75rem; color: var(--color-secondary-foreground, #52525b); }

/* Search */
.zx-docs-search { position: relative; display: flex; align-items: center; min-width: 260px; }
.zx-docs-search > i {
  position: absolute; left: .75rem; font-size: .875rem; pointer-events: none;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-docs-search > input {
  width: 100%; padding: .5rem .75rem .5rem 2.125rem;
  border: 1px solid var(--color-border, #e4e4e7); border-radius: .5rem;
  background: var(--color-card, #fff);
  font-size: .8125rem; color: var(--color-foreground, #18181b);
}
.zx-docs-search > input:focus {
  outline: none; border-color: var(--color-primary, #0d5c63);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary, #0d5c63) 15%, transparent);
}
.zx-docs-search > input::-webkit-search-cancel-button { display: none; }
.zx-docs-search > button {
  position: absolute; right: .5rem; display: flex; align-items: center; justify-content: center;
  width: 1.25rem; height: 1.25rem; border: 0; border-radius: 9999px; cursor: pointer;
  background: color-mix(in srgb, var(--color-foreground, #18181b) 10%, transparent);
  color: var(--color-secondary-foreground, #52525b); font-size: .625rem;
}

/* Sections — collapsible, so the page opens as a scannable index not a wall of prose. */
.zx-docs-articles { display: flex; flex-direction: column; gap: .75rem; min-width: 0; }
.zx-docs-section {
  border: 1px solid var(--color-border, #e4e4e7);
  border-radius: .75rem;
  background: var(--color-card, #fff);
  scroll-margin-top: 90px;
  overflow: hidden;
}
.zx-docs-section[hidden] { display: none; }
.zx-docs-head {
  display: flex; align-items: center; gap: .625rem; width: 100%;
  padding: .875rem 1rem; border: 0; background: transparent; cursor: pointer; text-align: left;
}
.zx-docs-head:hover { background: color-mix(in srgb, var(--color-foreground, #18181b) 3%, transparent); }
.zx-docs-head-icon { font-size: 1rem; color: var(--color-primary, #0d5c63); flex-shrink: 0; }
.zx-docs-head-title {
  flex: 1; min-width: 0; font-size: .9375rem; font-weight: 600;
  color: var(--color-foreground, #18181b);
}
.zx-docs-chevron {
  font-size: .75rem; flex-shrink: 0; transition: transform .2s ease;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-docs-head[aria-expanded="false"] .zx-docs-chevron { transform: rotate(-90deg); }

.zx-docs-body {
  padding: 0 1rem 1.125rem;
  display: flex; flex-direction: column; gap: 1rem;
  font-size: .8125rem; line-height: 1.65;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-docs-body[hidden] { display: none; }
.zx-docs-body ol, .zx-docs-body ul { display: flex; flex-direction: column; gap: .5rem; padding-left: 1.25rem; }
.zx-docs-body ol { list-style: decimal; }
.zx-docs-body ul { list-style: disc; }
.zx-docs-body b, .zx-docs-body .text-mono { color: var(--color-foreground, #18181b); font-weight: 600; }
/* Search hit highlight */
.zx-docs-mark { background: #fde68a; color: #713f12; border-radius: .1875rem; padding: 0 .125rem; }

/* ---- Plan & Usage: "Usage this period" --------------------------------------------------
   Written here, not with Tailwind gap-* utilities: the portal build is prebuilt, so
   `gap-x-8 gap-y-4` resolved to `normal`. The two grid columns touched edge-to-edge, which
   ran "6 / 25,000" straight into the next metric's label and — worse — made two adjacent
   progress bars read as ONE bar spanning both. Each metric is now a bounded tile, so a bar
   can only ever belong to the metric above it. */
.zx-usage-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: .75rem;
}
@media (min-width: 640px) {
  .zx-usage-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
}

.zx-usage-item {
  display: flex; flex-direction: column; gap: .375rem;
  padding: .75rem .875rem;
  border: 1px solid var(--color-border, #e4e4e7);
  border-radius: .625rem;
  background: color-mix(in srgb, var(--color-foreground, #18181b) 2%, transparent);
}
.zx-usage-item.is-warn     { border-color: color-mix(in srgb, #eab308 45%, transparent); }
.zx-usage-item.is-critical { border-color: color-mix(in srgb, #ef4444 45%, transparent); }

.zx-usage-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.zx-usage-label {
  display: inline-flex; align-items: center; gap: .375rem; min-width: 0;
  font-size: .8125rem; color: var(--color-secondary-foreground, #52525b);
}
.zx-usage-label > i { font-size: .8125rem; flex-shrink: 0; }
.zx-usage-value {
  font-size: .8125rem; font-weight: 600; white-space: nowrap; flex-shrink: 0;
  color: var(--color-foreground, #18181b);
}

.zx-usage-track {
  width: 100%; height: .375rem; border-radius: 9999px; overflow: hidden;
  background: color-mix(in srgb, var(--color-foreground, #18181b) 10%, transparent);
}
.zx-usage-bar {
  height: 100%; border-radius: 9999px;
  background: var(--color-primary, #0d5c63);
  transition: width .3s ease;
}
.zx-usage-item.is-warn     .zx-usage-bar { background: #eab308; }
.zx-usage-item.is-critical .zx-usage-bar { background: #ef4444; }

/* The percentage and remainder, spelled out — "6 / 25,000" alone makes the reader do the
   arithmetic to answer the only question they came with: how much is left? */
.zx-usage-meta { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); }
.zx-usage-note { font-size: .6875rem; font-weight: 600; color: #dc2626; }

:root:not([data-theme="light"]) .zx-usage-item,
:root[data-theme="dark"] .zx-usage-item {
  background: color-mix(in srgb, #fff 4%, transparent);
}

/* ---- Wallet top-up modal ----------------------------------------------------------------
   The payment method used to render as a single full-width outline button that read as a
   disabled input, defaulted to unselected, and made "Request Top-up" silently do nothing until
   it was clicked. Now: one gateway is stated as fact; several become real radio cards. */

/* Single gateway — informational, not a control. */
.zx-pay-single {
  display: flex; align-items: center; gap: .625rem;
  margin-top: 1rem; padding: .75rem .875rem;
  border: 1px solid var(--color-border, #e4e4e7);
  border-radius: .625rem;
  background: color-mix(in srgb, var(--color-foreground, #18181b) 3%, transparent);
}
.zx-pay-single-icon { font-size: 1.125rem; color: var(--color-primary, #0d5c63); flex-shrink: 0; }
.zx-pay-single-text { display: flex; flex-direction: column; gap: .0625rem; min-width: 0; flex: 1; }
.zx-pay-single-label { font-size: .8125rem; font-weight: 600; color: var(--color-foreground, #18181b); }
.zx-pay-single-note  { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); }
.zx-pay-single-lock  { font-size: .875rem; color: #16a34a; flex-shrink: 0; }

/* Two or more gateways — selectable cards that look selectable. */
.zx-pay-options { display: flex; flex-direction: column; gap: .5rem; }
.zx-pay-option {
  display: flex; align-items: center; gap: .625rem; width: 100%;
  padding: .75rem .875rem; text-align: left;
  border: 1px solid var(--color-border, #e4e4e7);
  border-radius: .625rem;
  background: transparent; cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.zx-pay-option:hover { border-color: color-mix(in srgb, var(--color-primary, #0d5c63) 45%, transparent); }
.zx-pay-option.is-selected {
  border-color: var(--color-primary, #0d5c63);
  background: color-mix(in srgb, var(--color-primary, #0d5c63) 6%, transparent);
}
.zx-pay-option-icon { font-size: 1.125rem; color: var(--color-secondary-foreground, #52525b); flex-shrink: 0; }
.zx-pay-option.is-selected .zx-pay-option-icon { color: var(--color-primary, #0d5c63); }
.zx-pay-option-text { display: flex; flex-direction: column; gap: .0625rem; min-width: 0; flex: 1; }
.zx-pay-option-label { font-size: .8125rem; font-weight: 600; color: var(--color-foreground, #18181b); }
.zx-pay-option-note  { font-size: .6875rem; color: var(--color-secondary-foreground, #52525b); }
.zx-pay-option-check {
  display: flex; align-items: center; justify-content: center;
  width: 1.125rem; height: 1.125rem; flex-shrink: 0;
  border: 1px solid var(--color-border, #e4e4e7); border-radius: 9999px;
  font-size: .625rem; color: transparent;
}
.zx-pay-option.is-selected .zx-pay-option-check {
  border-color: var(--color-primary, #0d5c63);
  background: var(--color-primary, #0d5c63);
  color: #fff;
}

/* Quick-amount chips reflect the current value, so field and chips never disagree. */
.zx-topup-quick.is-selected {
  border-color: var(--color-primary, #0d5c63);
  background: color-mix(in srgb, var(--color-primary, #0d5c63) 10%, transparent);
  color: var(--color-primary, #0d5c63);
  font-weight: 600;
}

/* Inline validation — the old flow fired an alert that never rendered, so the modal simply
   did nothing. */
.zx-topup-hint { margin-top: .375rem; font-size: .75rem; color: #dc2626; }
.kt-input.zx-input-error { border-color: #dc2626; }

.zx-spin { display: inline-block; animation: zx-es-spin .8s linear infinite; }

/* Billing page: amount-due strip. The one number a customer opens Billing to find, stated
   once at the top so it cannot be confused with the (separate) wallet balance. */
.zx-billing-due {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  margin-bottom: 1.25rem; padding: .875rem 1.125rem;
  border: 1px solid color-mix(in srgb, #eab308 40%, transparent);
  border-radius: .625rem;
  background: color-mix(in srgb, #eab308 8%, transparent);
}
.zx-billing-due-main { display: flex; align-items: baseline; gap: .625rem; flex-wrap: wrap; }
.zx-billing-due-label {
  font-size: .6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: .03em;
  color: var(--color-secondary-foreground, #52525b);
}
.zx-billing-due-amount { font-size: 1.375rem; font-weight: 700; color: var(--color-foreground, #18181b); }
.zx-billing-due-meta { font-size: .75rem; color: var(--color-secondary-foreground, #52525b); }
:root:not([data-theme="light"]) .zx-billing-due,
:root[data-theme="dark"] .zx-billing-due {
  border-color: color-mix(in srgb, #eab308 35%, transparent);
  background: color-mix(in srgb, #eab308 12%, transparent);
}

/* Subscription-expired bar. Shown on the pages that stay reachable while the product is
   locked (billing, profile, help) — the rest is refused server-side. Loud on purpose: this is
   the one thing standing between the customer and a working account. */
.zx-renew-bar {
  display: flex; align-items: center; gap: .75rem;
  margin-bottom: 1.25rem; padding: .75rem 1rem;
  border: 1px solid color-mix(in srgb, #ef4444 40%, transparent);
  border-radius: .625rem;
  background: color-mix(in srgb, #ef4444 8%, transparent);
}
.zx-renew-bar-icon { font-size: 1.125rem; flex-shrink: 0; color: #dc2626; }
.zx-renew-bar-text { display: flex; flex-direction: column; gap: .125rem; min-width: 0; flex: 1; }
.zx-renew-bar-text > strong { font-size: .875rem; color: var(--color-foreground, #18181b); }
.zx-renew-bar-sub { font-size: .75rem; color: var(--color-secondary-foreground, #52525b); }
:root:not([data-theme="light"]) .zx-renew-bar,
:root[data-theme="dark"] .zx-renew-bar {
  border-color: color-mix(in srgb, #ef4444 45%, transparent);
  background: color-mix(in srgb, #ef4444 14%, transparent);
}
@media (max-width: 640px) {
  .zx-renew-bar { flex-wrap: wrap; }
}

/* Shared plan-locked panel (dashboard/components/plan_lock.php). One look for every
   feature the current plan does not include, so a lock is instantly recognisable
   wherever a customer meets one. */
.zx-plan-lock { margin-bottom: 1.25rem; }
.zx-plan-lock-badge {
  width: 3.5rem; height: 3.5rem; margin: 0 auto 1rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9999px;
  background: color-mix(in srgb, var(--color-yellow-500, #eab308) 12%, transparent);
}
.zx-plan-lock-badge > i { font-size: 1.5rem; color: var(--color-yellow-600, #ca8a04); }

/* WhatsApp number plan-cap notice, shown beside the (locked) connect controls.
   The cap used to surface only after the customer finished Meta's popup; this states the
   seat count up front. .is-warn is the softer "last seat" variant shown while they can
   still connect. */
.zx-waba-cap {
  display: flex; align-items: flex-start; gap: .4375rem;
  max-width: 24rem; text-align: left;
  margin-top: .125rem; padding: .5rem .625rem;
  border: 1px solid var(--color-yellow-300, #fde047);
  border-radius: .5rem;
  background: color-mix(in srgb, var(--color-yellow-500, #eab308) 8%, transparent);
  font-size: .6875rem; line-height: 1.4;
  color: var(--color-foreground, #18181b);
}
.zx-waba-cap > i { flex-shrink: 0; margin-top: .0625rem; color: var(--color-yellow-600, #ca8a04); }
.zx-waba-cap.is-warn {
  border-color: var(--color-border, #e4e4e7);
  background: color-mix(in srgb, var(--color-foreground, #18181b) 4%, transparent);
}
.zx-waba-cap.is-warn > i { color: var(--color-secondary-foreground, #52525b); }
.zx-waba-cap-link { font-weight: 600; text-decoration: underline; }
.dark .zx-waba-cap {
  border-color: color-mix(in srgb, var(--color-yellow-500, #eab308) 35%, transparent);
  background: color-mix(in srgb, var(--color-yellow-500, #eab308) 12%, transparent);
}
.dark .zx-waba-cap.is-warn {
  border-color: var(--color-border, #3f3f46);
  background: color-mix(in srgb, #fff 5%, transparent);
}

/* Embedded Signup status banner.
   Was a bare <span> whose text ("Opening…", "Connection cancelled.") read as debug output
   and was easy to miss entirely. Now a pill that only appears when there is something to
   say — no state class = hidden — so the connect area stays clean on load.
   NOTE: display is declared here, so `.hidden` alone would NOT hide it (see the custom.css
   vs Tailwind .hidden trap); visibility is driven by the is-* state classes instead. */
.zx-es-status {
  display: none;
  align-items: flex-start;
  gap: .4375rem;
  max-width: 24rem;
  margin-top: .125rem;
  padding: .5rem .6875rem;
  border: 1px solid transparent;
  border-radius: .4375rem;
  font-size: .75rem;
  line-height: 1.4;
  text-align: left;
}
.zx-es-status.is-info,
.zx-es-status.is-success,
.zx-es-status.is-error { display: inline-flex; }

.zx-es-status-icon { flex-shrink: 0; font-size: .875rem; line-height: 1.35; }

.zx-es-status.is-info {
  color: #1e40af; background-color: rgba(59, 130, 246, .09); border-color: rgba(59, 130, 246, .25);
}
.zx-es-status.is-success {
  color: #15803d; background-color: rgba(34, 197, 94, .1); border-color: rgba(34, 197, 94, .28);
}
.zx-es-status.is-error {
  color: #b91c1c; background-color: rgba(239, 68, 68, .09); border-color: rgba(239, 68, 68, .28);
}
/* Spin the icon while a step is in flight (opening the window, connecting). */
.zx-es-status.is-busy .zx-es-status-icon { animation: zx-es-spin .9s linear infinite; }
@keyframes zx-es-spin { to { transform: rotate(360deg); } }

.dark .zx-es-status.is-info    { color: #93c5fd; background-color: rgba(59, 130, 246, .14); border-color: rgba(59, 130, 246, .32); }
.dark .zx-es-status.is-success { color: #86efac; background-color: rgba(34, 197, 94, .14);  border-color: rgba(34, 197, 94, .32); }
.dark .zx-es-status.is-error   { color: #fca5a5; background-color: rgba(239, 68, 68, .14);  border-color: rgba(239, 68, 68, .32); }

/* ---- Media attachments (FR-WABA-008) ---- */
.zx-chat-media { margin-bottom: .25rem; }
.zx-chat-media:last-child { margin-bottom: 0; }

/* Cap BOTH axes. Height alone is not enough: a 640x360 landscape image sits well under
   18rem tall while still rendering ~512px wide, which swamps the thread. WhatsApp caps the
   long edge at roughly 20rem, so match that. */
.zx-chat-media-img {
  display: block; border-radius: .375rem;
  max-width: min(20rem, 100%); max-height: 18rem;
  width: auto; height: auto; cursor: pointer; background: rgba(0, 0, 0, .04);
}
.zx-chat-media-audio { width: 15rem; max-width: 100%; }

.zx-chat-media-doc {
  display: flex; align-items: center; gap: .5rem;
  padding: .5rem .625rem; border-radius: .375rem;
  background: rgba(0, 0, 0, .04); text-decoration: none; color: inherit;
}
.zx-chat-media-doc:hover { background: rgba(0, 0, 0, .08); }
.dark .zx-chat-media-doc { background: rgba(255, 255, 255, .06); }
.dark .zx-chat-media-doc:hover { background: rgba(255, 255, 255, .1); }

/* Placeholder for pending / failed / expired media. */
.zx-chat-media-note {
  display: flex; align-items: center; gap: .375rem;
  padding: .5rem .625rem; border-radius: .375rem;
  background: rgba(0, 0, 0, .04); font-size: .8125rem; font-style: italic;
  color: var(--color-secondary-foreground, #52525b);
}
.dark .zx-chat-media-note { background: rgba(255, 255, 255, .06); }

/* Delivery ticks */
.zx-chat-tick-read { color: var(--zx-chat-tick-read); }

/* Day separator.
   NOT sticky: the chips are flat siblings of the bubbles (no per-day wrapper), so
   `position: sticky` pins every chip at top:0 and they visibly stack on each other.
   A sticky variant needs the renderer to wrap each day's messages in its own group. */
.zx-chat-day { position: relative; z-index: 1; }

/* ---- Body row: messages + notes side panel ---- */
/* position:relative so the jump-to-latest button anchors to the message area rather than
   the whole pane, which would float it over the composer. */
.zx-chat-body-row { display: flex; flex: 1 1 auto; min-height: 0; min-width: 0; position: relative; }

/* ---- Notes & activity panel (FR-INBOX-005/006/010) ---- */
.zx-chat-notes {
  display: flex; flex-direction: column; flex: none;
  width: 20rem; min-height: 0;
  border-inline-start: 1px solid var(--color-border, #e4e4e7);
  background: var(--color-background, #fff);
}
.zx-chat-notes.is-hidden { display: none; }
/* Narrow screens: the panel takes the whole body row rather than crushing the thread. */
@media (max-width: 1279px) {
  .zx-chat-notes:not(.is-hidden) { width: 100%; }
  .zx-chat-notes:not(.is-hidden) ~ .zx-chat-canvas,
  .zx-chat-body-row:has(.zx-chat-notes:not(.is-hidden)) > .zx-chat-canvas { display: none; }
}
/* Wide screens: the contact pane is a PERMANENT third column, per the target design — CRM context
   beside the conversation instead of behind an icon. Below 1440px it reverts to the slide-over
   above, because a third fixed column there leaves the thread too narrow to read comfortably.
   `.is-hidden` still wins, so the close button works at every width. */
@media (min-width: 1440px) {
  .zx-chat-notes { width: 22rem; }
  /* :not(.is-hidden) is load-bearing — a bare `.is-pinned{display:flex}` sits AFTER the
     `.is-hidden{display:none}` rule at equal specificity and would win, leaving the close button
     dead (guide §7.1: verify the COMPUTED display, never classList). */
  .zx-chat-notes.is-pinned:not(.is-hidden) { display: flex; }
}

.zx-chat-notes-head {
  display: flex; align-items: center; justify-content: space-between;
  height: var(--zx-chat-header-height); flex: none;
  padding-inline: .75rem; border-bottom: 1px solid var(--color-border, #e4e4e7);
}

.zx-chat-notes-tabs { display: flex; flex: none; border-bottom: 1px solid var(--color-border, #e4e4e7); }
.zx-chat-tab {
  flex: 1 1 0; padding: .625rem .5rem; font-size: .8125rem; font-weight: 600;
  color: var(--color-muted-foreground, #71717a);
  background: none; border: 0; border-bottom: 2px solid transparent; cursor: pointer;
  transition: color .12s, background-color .12s, border-color .12s;
}
.zx-chat-tab:hover { color: var(--color-mono, #18181b); background: color-mix(in srgb, currentColor 5%, transparent); }
.zx-chat-tab.is-active {
  color: var(--zx-brand); border-bottom-color: var(--zx-brand);
  background: color-mix(in srgb, var(--color-primary, #0d5c63) 5%, transparent);
}

.zx-chat-notes-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: .75rem; }
.zx-chat-notes-body.is-hidden { display: none; }

/* A note is internal — deliberately styled unlike a chat bubble (amber, not green/white). */
.zx-chat-note {
  padding: .5rem .625rem; border-radius: .375rem; margin-bottom: .5rem;
  background: #fffbeb; border: 1px solid #fde68a;
}
.dark .zx-chat-note { background: rgba(120, 53, 15, .25); border-color: rgba(180, 83, 9, .4); }
.zx-chat-note-meta {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  margin-top: .25rem; font-size: .6875rem;
  color: var(--color-muted-foreground, #71717a);
}
.zx-chat-note-body { white-space: pre-wrap; overflow-wrap: anywhere; font-size: .8125rem; }
.zx-chat-mention { color: var(--zx-brand); font-weight: 600; }

.zx-chat-notes-form { flex: none; padding: .75rem; border-top: 1px solid var(--color-border, #e4e4e7); }
.zx-chat-notes-form.is-hidden { display: none; }   /* composer belongs to the Notes tab only */

/* ---- Contact / CRM detail card (docs/006 #8) ---------------------------------- */
.zx-contact-card { display: flex; flex-direction: column; gap: .5rem; font-size: .8125rem; }

/* Identity block: a tinted card rather than a hairline rule, so the person the agent is talking
   to anchors the panel instead of blending into the field list below it. */
.zx-contact-head {
  display: flex; align-items: center; gap: .75rem;
  padding: .75rem; margin-bottom: .5rem; border-radius: .75rem;
  background: color-mix(in srgb, var(--color-primary, #0d5c63) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-primary, #0d5c63) 12%, transparent);
}
.zx-contact-avatar { width: 2.75rem; height: 2.75rem; border-radius: 9999px; display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 700; font-size: 1rem; flex: none; }
.zx-contact-head-text { min-width: 0; }               /* replaces min-w-0 (may be absent from prebuilt bundle) */
.zx-contact-name { font-weight: 650; font-size: .875rem; color: var(--color-mono, #18181b); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zx-contact-phone { color: var(--color-muted-foreground, #71717a); font-size: .75rem; }

/* label / value row — zebra-free, but with a hairline so long field lists stay scannable. */
.zx-contact-row {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .375rem 0; border-bottom: 1px solid color-mix(in srgb, currentColor 7%, transparent);
}
.zx-contact-row:last-child { border-bottom: 0; }
.zx-contact-label { color: var(--color-muted-foreground, #71717a); flex: none; font-size: .75rem; }
.zx-contact-value { text-align: end; overflow-wrap: anywhere; font-weight: 500; }

/* click-to-edit affordance */
.zx-contact-editable { cursor: text; border-radius: .3125rem; padding: .0625rem .3125rem; transition: background-color .12s; }
.zx-contact-editable:hover { background: var(--color-accent, #f4f4f5); }
/* An unset field invites input instead of stacking em dashes. Dashed underline marks it as
   actionable without shouting; the row is genuinely clickable, so hiding it would remove the
   only way to fill it in. */
.zx-contact-editable.is-empty {
  color: var(--color-muted-foreground, #a1a1aa); font-style: normal; font-size: .75rem;
  border-bottom: 1px dashed var(--color-border, #d4d4d8);
}
.zx-contact-editable.is-empty:hover {
  color: var(--color-primary, #0d5c63);
  border-bottom-color: color-mix(in srgb, var(--color-primary, #0d5c63) 55%, transparent);
}
.dark .zx-contact-editable:hover { background: rgba(255,255,255,.06); }
.zx-contact-input, .zx-contact-select {
  font-size: .8125rem; padding: .1875rem .375rem; text-align: end;
  border: 1px solid var(--color-border, #e4e4e7); border-radius: .25rem;
  background: var(--color-background, #fff); color: inherit; max-width: 60%;
}
.zx-contact-select { text-align: start; }

/* blacklist toggle */
.zx-contact-toggle { display: inline-flex; align-items: center; gap: .375rem; cursor: pointer; }

/* tags */
.zx-contact-tags { display: flex; flex-wrap: wrap; gap: .25rem; justify-content: flex-end; max-width: 70%; }
.zx-contact-chip { display: inline-flex; align-items: center; gap: .1875rem; padding: .0625rem .375rem; border-radius: 9999px; font-size: .6875rem; background: var(--color-accent, #f4f4f5); }
.dark .zx-contact-chip { background: rgba(255,255,255,.08); }
.zx-contact-chip-x { border: 0; background: none; cursor: pointer; color: var(--color-muted-foreground, #71717a); line-height: 1; font-size: .875rem; }
.zx-contact-tag-input { width: 5rem; font-size: .6875rem; border: 0; background: none; color: inherit; outline: none; }

.zx-contact-full-link { margin-top: .5rem; font-size: .75rem; color: var(--zx-brand); font-weight: 600; }

/* Load-earlier control at the top of a thread (docs/006 #3). The canvas is a flex column, so
   align-self:center centers the pill. */
.zx-load-older {
  align-self: center; margin: .25rem auto .5rem; padding: .25rem .75rem;
  font-size: .75rem; font-weight: 500; border-radius: 9999px;
  background: var(--color-background, #fff); color: var(--zx-brand);
  border: 1px solid var(--color-border, #e4e4e7); cursor: pointer;
}
.zx-load-older:hover { background: var(--color-accent, #f4f4f5); }
.zx-load-older:disabled { opacity: .6; cursor: default; }
.zx-load-older-note {
  align-self: center; margin: .25rem auto .5rem; font-size: .6875rem;
  color: var(--color-muted-foreground, #71717a);
}

/* ---- Conversation vocabularies: labels / tags / categories (docs/006 #5) ---------------- */
/* Applied chips shown below the contact's phone number in the thread header. */
.zx-th-vocab { display: flex; flex-wrap: wrap; gap: .25rem; margin-top: .1875rem; }
.zx-th-vocab:empty { display: none; }

/* Colored assignment chip. Text color is set inline for contrast. */
.zx-vocab-chip { display: inline-flex; align-items: center; gap: .1875rem; padding: .0625rem .4rem; border-radius: 9999px; font-size: .6875rem; line-height: 1.4; }
.zx-vocab-chip-x { border: 0; background: none; cursor: pointer; color: inherit; opacity: .75; line-height: 1; font-size: .8125rem; }
.zx-vocab-chip-x:hover { opacity: 1; }
.zx-vocab-chip-sm { padding: 0 .3rem; font-size: .625rem; }

/* Assign panel (from the header tag button) — Labels/Tags/Categories sections. */
.zx-vocab-panel { position: absolute; end: 0; inset-inline-end: 0; top: 100%; margin-top: .25rem; z-index: 40; min-width: 14rem; max-height: 22rem; overflow-y: auto; background: var(--color-background, #fff); border: 1px solid var(--color-border, #e4e4e7); border-radius: .5rem; box-shadow: 0 8px 24px rgba(0,0,0,.14); padding: .25rem; }
.zx-vocab-sec-hdr { padding: .375rem .5rem .125rem; font-size: .625rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--color-muted-foreground, #71717a); }
.zx-vocab-sec-hdr:not(:first-child) { border-top: 1px solid var(--color-border, #e4e4e7); margin-top: .25rem; }
.zx-vocab-pick-row { display: flex; align-items: center; gap: .5rem; width: 100%; text-align: start; padding: .375rem .5rem; border: 0; background: none; cursor: pointer; border-radius: .375rem; font-size: .8125rem; }
.zx-vocab-pick-row:hover { background: var(--color-accent, #f4f4f5); }
/* The name span expands so the applied-check right-aligns (don't rely on Tailwind .grow — the
   portal bundle is prebuilt and may not ship it). */
.zx-vocab-pick-row > span:not(.zx-vocab-pick-sw) { flex: 1 1 auto; }
.zx-vocab-pick-sw { width: .875rem; height: .875rem; border-radius: 9999px; flex: none; border: 1px solid rgba(0,0,0,.1); }
.zx-vocab-pick-empty { padding: .5rem; font-size: .75rem; color: var(--color-muted-foreground, #71717a); text-align: center; }
.zx-vocab-manage { display: block; padding: .375rem .5rem; margin-top: .25rem; border-top: 1px solid var(--color-border, #e4e4e7); font-size: .75rem; color: var(--zx-brand); font-weight: 600; }

/* List-row chips + overflow. */
.zx-list-chips { display: flex; flex-wrap: wrap; gap: .1875rem; margin-top: .1875rem; }
.zx-vocab-more { font-size: .625rem; color: var(--color-muted-foreground, #71717a); align-self: center; }

/* Filter menu label group. */
.zx-filter-group-hdr { padding: .375rem .75rem .125rem; font-size: .625rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--color-muted-foreground, #71717a); border-top: 1px solid var(--color-border, #e4e4e7); margin-top: .25rem; }

/* KeenIcons sets display on i[class*="ki-"] (computed 'flex'), which BEATS Tailwind's
   `.hidden { display:none }` on the <i> check marks — so filter/panel checks showed even when their
   filter/item was not applied. Force the hide to win. (The custom-css-beats-.hidden trap — this must
   be verified with getComputedStyle, never classList.contains.) */
#inbox-filter-menu .ki-check.hidden,
#inbox-vocab-panel .ki-check.hidden { display: none !important; }

/* @mention autocomplete */
.zx-chat-mention-menu {
  position: absolute; bottom: 100%; inset-inline-start: 0; margin-bottom: .25rem;
  width: 100%; max-height: 10rem; overflow-y: auto; z-index: 30;
  background: var(--color-background, #fff);
  border: 1px solid var(--color-border, #e4e4e7); border-radius: .375rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .1);
}
.zx-chat-mention-item {
  display: block; width: 100%; text-align: start; padding: .375rem .625rem;
  font-size: .8125rem; background: none; border: 0; cursor: pointer;
}
.zx-chat-mention-item:hover, .zx-chat-mention-item.is-active { background: var(--color-accent, #f4f4f5); }

/* Activity timeline rows */
.zx-chat-activity {
  display: flex; gap: .5rem; padding: .375rem 0; font-size: .75rem;
  color: var(--color-secondary-foreground, #52525b);
  border-bottom: 1px solid var(--color-border, #e4e4e7);
}
.zx-chat-activity:last-child { border-bottom: 0; }
.zx-chat-activity-dot {
  flex: none; width: .5rem; height: .5rem; border-radius: 9999px;
  background: var(--zx-brand); margin-top: .3rem;
}

/* ---- Composer ---- */
.zx-chat-composer { flex: none; }
.zx-chat-composer textarea { border-radius: 1rem; max-height: 8rem; }

/* ---- Template picker modal (FR-INBOX-007) ---- */
.zx-chat-modal { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center; padding: 1rem; }
.zx-chat-modal.is-hidden { display: none; }
.zx-chat-modal-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, .45); }
.zx-chat-modal-card {
  position: relative; z-index: 1; display: flex; flex-direction: column;
  width: 100%; max-width: 32rem; max-height: 85vh; overflow: hidden;
}
.zx-chat-tpl-preview {
  padding: .625rem .75rem; border-radius: .375rem; font-size: .8125rem;
  white-space: pre-wrap; overflow-wrap: anywhere;
  background: var(--zx-chat-bubble-out); border: 1px solid rgba(0, 0, 0, .06);
}
.dark .zx-chat-tpl-preview { background: rgba(6, 78, 59, .5); border-color: rgba(255, 255, 255, .08); }
.zx-chat-tpl-preview.is-hidden { display: none; }
/* Filled placeholders stand out from the template's fixed text. */
.zx-chat-tpl-var { font-weight: 600; color: var(--zx-brand); }

/* ---- Inbox KT modals: sizing + elevation ----
   Sizing lives HERE, not in the markup. The four kt-modals used to carry arbitrary Tailwind
   values (max-w-[760px], top-[8%]) which are absent from the PREBUILT portal bundle, so they
   silently did nothing: every inbox modal rendered at full viewport width and full height — the
   interactive builder put six short inputs across 1500px with its counters stranded far right.
   Same trap as the docs page's lg:sticky. Never size a modal from a utility class here. */
.zx-modal {
  width: 100%;
  max-height: min(86vh, 52rem);
  margin: 7vh auto;
  border-radius: .875rem;
  box-shadow: 0 24px 48px -12px rgba(9, 9, 11, .28), 0 0 0 1px rgba(9, 9, 11, .06);
}
.zx-modal-sm { max-width: 30rem; }
.zx-modal-md { max-width: 35rem; }
.zx-modal-lg { max-width: 47.5rem; }
/* The header/footer stay put; only the body scrolls, so the primary action is never scrolled
   off the bottom of a tall form. */
.zx-modal > .kt-modal-body { overflow-y: auto; }
.dark .zx-modal { box-shadow: 0 24px 48px -12px rgba(0, 0, 0, .6), 0 0 0 1px rgba(255, 255, 255, .08); }

/* Capped field + its n/max counter as ONE unit. Without this the counter is a sibling in the
   column flex and inherits the full gap, so each input sat a full row away from its own counter
   and the form read as twice as long as it is. */
.zx-ix-field { display: flex; flex-direction: column; gap: .1875rem; }

/* ---- Our own (non-KT) modal ---- */
.zx-chat-modal-card { border-radius: .875rem; box-shadow: 0 24px 48px -12px rgba(9, 9, 11, .28), 0 0 0 1px rgba(9, 9, 11, .06); }
.dark .zx-chat-modal-card { box-shadow: 0 24px 48px -12px rgba(0, 0, 0, .6), 0 0 0 1px rgba(255, 255, 255, .08); }

/* "A filter is active" dot on the funnel button. Was -top-0.5/-end-0.5/size-2: the negative
   offsets are not in the bundle, so the dot sat inside the button instead of on its corner. */
.zx-filter-dot {
  position: absolute; top: -.125rem; inset-inline-end: -.125rem;
  width: .5rem; height: .5rem; border-radius: 9999px;
  background: var(--color-primary, #0d5c63);
}
.zx-filter-dot.hidden { display: none; }

/* `overflow-y-auto` is not in the prebuilt bundle either, so this body could not scroll: a
   template with several variables pushed its own Send button out of the card. */
.zx-chat-modal-body { overflow-y: auto; }
/* Was max-w-xs — also absent, so the "select a conversation" line ran the full pane width. */
.zx-chat-empty-hint { max-width: 20rem; }

/* Empty state for a picker with nothing to offer — a bare disabled select is a dead end that
   does not say what to do next. */
.zx-chat-empty {
  display: flex; flex-direction: column; align-items: center; gap: .375rem;
  padding: 1.25rem 1rem; text-align: center;
  border: 1px dashed var(--color-border, #e4e4e7); border-radius: .75rem;
}
.zx-chat-empty.is-hidden { display: none; }
.zx-chat-empty i { font-size: 1.25rem; color: var(--color-muted-foreground, #9ca3af); }
.zx-chat-empty-title { font-size: .8125rem; font-weight: 600; color: var(--color-mono, #18181b); }
.zx-chat-empty-note { font-size: .75rem; color: var(--color-secondary-foreground, #52525b); }
.dark .zx-chat-empty { border-color: rgba(255, 255, 255, .12); }
.dark .zx-chat-empty-title { color: #f4f4f5; }

/* ---- Dropdown panels ----
   One elevation for every inbox popover. They were each rolling their own shadow (or none), so
   the same UI read as a different depth depending on which control opened it. */
.zx-chat-menu-sm, .zx-chat-menu-md, .zx-plus-menu, .zx-chat-canned-panel,
.zx-vocab-panel, .zx-chat-mention-menu, #inbox-ai-menu {
  border-radius: .75rem;
  box-shadow: 0 12px 24px -8px rgba(9, 9, 11, .18), 0 2px 6px -2px rgba(9, 9, 11, .10),
              0 0 0 1px rgba(9, 9, 11, .05);
  overflow: hidden;
}
.dark .zx-chat-menu-sm, .dark .zx-chat-menu-md, .dark .zx-plus-menu, .dark .zx-chat-canned-panel,
.dark .zx-vocab-panel, .dark .zx-chat-mention-menu, .dark #inbox-ai-menu {
  box-shadow: 0 12px 24px -8px rgba(0, 0, 0, .55), 0 0 0 1px rgba(255, 255, 255, .08);
}
/* The AI menu hard-coded bg-white, so in dark mode it drew white-on-white text. */
#inbox-ai-menu { background: var(--color-background, #fff); }
.dark #inbox-ai-menu { background: var(--color-background, #18181b); }

/* POSITIONING LIVES HERE TOO, for the same reason as the modal widths: `bottom-full`, `top-full`,
   `w-44`, `start-3` and `overflow-y-auto` are NONE of them in the prebuilt portal bundle. The AI
   menu therefore never lifted above its button — it rendered below it, at the very bottom of the
   composer, with all but its first item cut off by the viewport. The canned-reply panel had the
   same missing anchor plus no scrolling, so a long list simply overflowed its own max-height.
   Verified absent: grep -o '\.bottom-full' across every portal stylesheet returns nothing. */
#inbox-ai-menu,
.zx-chat-canned-panel {
  position: absolute;
  bottom: 100%;
  margin-bottom: .5rem;
  overflow-y: auto;          /* wins over the shared `overflow: hidden` above — order matters */
}
#inbox-ai-menu { inset-inline-end: 0; width: 11rem; max-height: 15rem; }
.zx-chat-canned-panel { inset-inline-start: .75rem; }

/* Menus that drop DOWNWARD relied on `top-full` and only looked right by accident: with no top/
   bottom set, an absolute box falls at its static position, which happens to be under the button.
   State it, so a later markup change cannot silently move them. */
.zx-chat-menu-sm, .zx-chat-menu-md { position: absolute; top: 100%; inset-inline-end: 0; margin-top: .25rem; }
.zx-chat-menu-sm { width: 11rem; }
.zx-chat-menu-md { width: 15rem; max-height: min(28rem, 70vh); overflow-y: auto; }
.zx-chat-menu-md:not(.hidden) { display: flex; }
.zx-chat-canned-panel { width: 20rem; max-height: 16.25rem; }
.zx-chat-canned-body {
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}


/* ==========================================================================
   6. FORMS & INPUTS
   ========================================================================== */

/* (none yet) */


/* ==========================================================================
   7. TABLES & DATA GRIDS
   Shared styling for the MozzApp/KT grids used across admin + dashboard.
   ========================================================================== */

/* (none yet) */


/* ==========================================================================
   8. PANEL-SPECIFIC OVERRIDES
   Only for rules that must differ between panels. Prefer app-wide sections
   above whenever the styling should stay consistent.
   ========================================================================== */

/* ---- Admin panel ---- */
/* (none yet) */

/* ---- Tenant dashboard ---- */

/* ==========================================================================
   Sales Pipeline / Kanban board (deals.js)
   Self-contained `zx-pl-*` classes — the portal Tailwind build is prebuilt, so
   NEW utility classes silently no-op; everything the board needs lives here.
   Colours derive from the branding tokens (--primary) so white-label applies.
   ========================================================================== */

/* Disabled primary buttons in the drawer (e.g. Send template before a pick). */
#dl-drawer .kt-btn-primary:disabled,
#modal-deal .kt-btn-primary:disabled { opacity: .5; cursor: not-allowed; }

/* ---- Tag / category chips (deal drawer + cards) ---- */
.zx-pl-chips { margin-bottom: 16px; }
.zx-pl-chips.hidden { display: none; }
.zx-pl-chips-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.zx-pl-chips-label { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--kt-gray-600, #4b5563); }
.zx-pl-chips-body { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.zx-pl-chips-empty { font-size: 12px; color: var(--kt-gray-400, #b5b5c3); }
.zx-pl-chip { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600; line-height: 1; padding: 4px 8px; border-radius: 999px; color: #fff; white-space: nowrap; }
.zx-pl-chip--cat { border-radius: 6px; }   /* categories read as squarer than tags */
.zx-pl-chip-x { cursor: pointer; opacity: .8; font-size: 13px; line-height: 1; }
.zx-pl-chip-x:hover { opacity: 1; }
.zx-pl-chip-add { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; line-height: 1; padding: 5px 10px; border-radius: 7px; border: 1px solid var(--kt-gray-300, #dbdfe9); color: var(--primary, #0d5c63); cursor: pointer; background: transparent; }
.zx-pl-chip-add i { font-size: 11px; }
.zx-pl-chip-add:hover { border-color: var(--primary, #0d5c63); background: color-mix(in srgb, var(--primary, #0d5c63) 8%, #fff); }

/* small tag chips on kanban cards */
.zx-pl-card-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 7px; }
.zx-pl-card-tag { font-size: 9.5px; font-weight: 600; line-height: 1; padding: 3px 6px; border-radius: 999px; color: #fff; }

/* rotting badge on cards */
.zx-pl-rot { display: inline-block; margin-top: 6px; font-size: 9.5px; font-weight: 700; line-height: 1; padding: 3px 7px; border-radius: 999px; color: #b45309; background: #fef3c7; }

/* card select checkbox + selected state */
.zx-pl-card-check { position: absolute; top: 9px; right: 9px; width: 15px; height: 15px; cursor: pointer; opacity: 0; transition: opacity .1s ease; z-index: 2; }
.zx-pl-card:hover .zx-pl-card-check, .zx-pl-card-check:checked { opacity: 1; }
.zx-pl-card--sel { box-shadow: 0 0 0 2px var(--primary, #0d5c63); }

/* bulk action bar (fixed bottom-center) */
.zx-pl-bulkbar { position: fixed; left: 50%; transform: translateX(-50%); bottom: 20px; z-index: 88; display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: var(--kt-card-background-color, #fff); border: 1px solid var(--kt-border-color, #e5e7eb); border-radius: 12px; box-shadow: 0 10px 30px rgba(16,24,40,.18); }
.zx-pl-bulkbar.hidden { display: none; }
.zx-pl-bulk-count { font-size: 12.5px; font-weight: 700; color: var(--kt-gray-800, #1f2937); white-space: nowrap; }
.zx-pl-bulkbar .kt-select { min-width: 130px; }

/* assign popover */
.zx-pl-pop { position: absolute; z-index: 95; min-width: 200px; max-height: 260px; overflow-y: auto; background: var(--kt-card-background-color, #fff); border: 1px solid var(--kt-border-color, #e5e7eb); border-radius: 10px; box-shadow: 0 8px 24px rgba(16,24,40,.14); padding: 6px; }
.zx-pl-pop.hidden { display: none; }
.zx-pl-pop-head { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--kt-gray-400, #b5b5c3); padding: 6px 8px 4px; }
.zx-pl-pop-item { display: flex; align-items: center; gap: 7px; padding: 6px 8px; border-radius: 7px; cursor: pointer; font-size: 12.5px; }
.zx-pl-pop-item:hover { background: var(--kt-gray-100, #f4f4f6); }
.zx-pl-pop-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
.zx-pl-pop-create { display: flex; gap: 6px; padding: 6px; border-top: 1px solid var(--kt-gray-100, #f1f1f4); margin-top: 4px; }

/* custom-field section visibility guard */
#dd-cf-section.hidden { display: none; }
#zx-lr-other.hidden { display: none; }

/* ---- Lost-reason chooser dialog ---- */
.zx-pl-lr-back { position: fixed; inset: 0; z-index: 96; background: rgba(15,23,42,.42); display: flex; align-items: center; justify-content: center; }
.zx-pl-lr-box { width: 340px; max-width: 92vw; background: var(--kt-card-background-color, #fff); border-radius: 12px; box-shadow: 0 12px 32px rgba(16,24,40,.2); padding: 18px; }
.zx-pl-lr-title { font-size: 14px; font-weight: 700; color: var(--kt-gray-900, #111827); margin-bottom: 12px; }

/* .hidden must beat the display rules below (custom.css loads after Tailwind, so
   a bare `display:flex/grid` would otherwise keep these visible while loading). */
.zx-pl-stats.hidden,
.zx-pl-board.hidden { display: none; }

/* Summary strip */
.zx-pl-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px; }
.zx-pl-stat { position: relative; padding: 14px 16px; border-radius: 12px; background: var(--kt-card-background-color, #fff); border: 1px solid var(--kt-border-color, #e5e7eb); box-shadow: 0 1px 2px rgba(16,24,40,.04); overflow: hidden; }
.zx-pl-stat::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--primary, #0d5c63); opacity: .85; }
.zx-pl-stat-label { font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--kt-gray-500, #99a1b7); }
.zx-pl-stat-value { font-size: 22px; font-weight: 700; line-height: 1.15; margin-top: 4px; color: var(--kt-gray-900, #111827); }
.zx-pl-stat-sub { font-size: 11px; color: var(--kt-gray-500, #99a1b7); margin-top: 2px; }
.zx-pl-stat--won .zx-pl-stat-value { color: #16a34a; }
.zx-pl-stat--won::before { background: #16a34a; }
.zx-pl-stat--fc::before { background: #7c3aed; }

/* Board scroller */
.zx-pl-board { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 12px; align-items: flex-start; scrollbar-width: thin; }
.zx-pl-board::-webkit-scrollbar { height: 8px; }
.zx-pl-board::-webkit-scrollbar-thumb { background: var(--kt-gray-300, #dbdfe9); border-radius: 8px; }

/* Column */
.zx-pl-col { width: 300px; flex: 0 0 300px; display: flex; flex-direction: column; max-height: calc(100vh - 240px); background: var(--kt-gray-100, #f9f9f9); border: 1px solid var(--kt-border-color, #e9e9e9); border-radius: 14px; padding: 10px; transition: background .12s ease, box-shadow .12s ease; }
.zx-pl-col.zx-pl-over { background: color-mix(in srgb, var(--primary, #0d5c63) 9%, #fff); box-shadow: inset 0 0 0 2px var(--primary, #0d5c63); }
.zx-pl-col--won { background: #f0fdf4; border-color: #bbf7d0; }
.zx-pl-col--lost { background: #fef2f2; border-color: #fecaca; }

/* Column header */
.zx-pl-col-head { display: flex; align-items: center; gap: 8px; padding: 4px 6px 8px; }
.zx-pl-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; background: var(--primary, #0d5c63); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary, #0d5c63) 18%, transparent); }
.zx-pl-col--won .zx-pl-dot { background: #16a34a; box-shadow: 0 0 0 3px #bbf7d0; }
.zx-pl-col--lost .zx-pl-dot { background: #dc2626; box-shadow: 0 0 0 3px #fecaca; }
.zx-pl-col-name { font-size: 13px; font-weight: 700; color: var(--kt-gray-800, #1f2937); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.zx-pl-col-count { font-size: 11px; font-weight: 600; color: var(--kt-gray-600, #4b5563); background: var(--kt-gray-200, #f1f1f4); border-radius: 999px; padding: 1px 8px; }
.zx-pl-col-del { margin-left: auto; border: 0; background: transparent; color: var(--kt-gray-400, #b5b5c3); font-size: 18px; line-height: 1; cursor: pointer; padding: 0 2px; border-radius: 6px; }
.zx-pl-col-del:hover { color: #dc2626; background: #fee2e2; }
.zx-pl-col-total { font-size: 11px; font-weight: 600; color: var(--kt-gray-500, #99a1b7); padding: 0 6px 8px; }

/* Drop list */
.zx-pl-drop { flex: 1 1 auto; overflow-y: auto; min-height: 48px; padding: 2px; scrollbar-width: thin; }
.zx-pl-empty { border: 1px dashed var(--kt-gray-300, #dbdfe9); border-radius: 10px; color: var(--kt-gray-400, #b5b5c3); font-size: 12px; text-align: center; padding: 18px 8px; }

/* Card */
.zx-pl-card { position: relative; background: #fff; border: 1px solid var(--kt-border-color, #eaeaea); border-radius: 11px; padding: 11px 12px 11px 14px; margin-bottom: 9px; cursor: grab; box-shadow: 0 1px 2px rgba(16,24,40,.05); transition: box-shadow .12s ease, transform .12s ease, border-color .12s ease; }
.zx-pl-card:hover { box-shadow: 0 6px 16px rgba(16,24,40,.10); border-color: color-mix(in srgb, var(--primary, #0d5c63) 40%, #eaeaea); transform: translateY(-1px); }
.zx-pl-card:active { cursor: grabbing; }
.zx-pl-card.zx-pl-dragging { opacity: .45; transform: rotate(1.5deg); }
.zx-pl-card::before { content: ""; position: absolute; inset: 8px auto 8px 0; width: 3px; border-radius: 3px; background: var(--primary, #0d5c63); opacity: .55; }
.zx-pl-col--won .zx-pl-card::before { background: #16a34a; }
.zx-pl-col--lost .zx-pl-card::before { background: #dc2626; }
.zx-pl-card-title { font-size: 13px; font-weight: 600; color: var(--kt-gray-900, #111827); line-height: 1.3; margin-bottom: 6px; word-break: break-word; }
.zx-pl-card-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 7px; }
.zx-pl-value { font-size: 13px; font-weight: 700; color: var(--primary, #0d5c63); }
.zx-pl-prob { font-size: 10px; font-weight: 700; color: var(--kt-gray-600, #4b5563); background: var(--kt-gray-100, #f4f4f6); border-radius: 999px; padding: 1px 7px; }
.zx-pl-meta { display: flex; align-items: center; gap: 7px; margin-top: 8px; font-size: 11px; color: var(--kt-gray-500, #99a1b7); }
.zx-pl-contact { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.zx-pl-contact span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zx-pl-avatar { width: 20px; height: 20px; border-radius: 50%; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; color: #fff; background: var(--primary, #0d5c63); text-transform: uppercase; }
.zx-pl-owner { margin-left: auto; }
.zx-pl-due { display: inline-flex; align-items: center; gap: 4px; }
.zx-pl-due--over { color: #dc2626; font-weight: 600; }

/* ---- Deal detail / history drawer ---- */
.zx-pl-drawer-backdrop { position: fixed; inset: 0; background: rgba(15,23,42,.42); z-index: 90; animation: zxFade .15s ease; }
.zx-pl-drawer-backdrop.hidden { display: none; }
@keyframes zxFade { from { opacity: 0; } to { opacity: 1; } }
.zx-pl-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 420px; max-width: 92vw; z-index: 91; background: var(--kt-card-background-color, #fff); border-left: 1px solid var(--kt-border-color, #e5e7eb); box-shadow: -8px 0 28px rgba(16,24,40,.12); display: flex; flex-direction: column; transform: translateX(0); animation: zxSlide .2s ease; }
.zx-pl-drawer.hidden { display: none; }
@keyframes zxSlide { from { transform: translateX(24px); opacity: .4; } to { transform: translateX(0); opacity: 1; } }
.zx-pl-drawer-head { display: flex; align-items: flex-start; gap: 12px; padding: 16px 18px; border-bottom: 1px solid var(--kt-border-color, #eaeaea); }
.zx-pl-drawer-stage { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--primary, #0d5c63); }
.zx-pl-drawer-title { font-size: 17px; font-weight: 700; color: var(--kt-gray-900, #111827); line-height: 1.25; margin-top: 2px; word-break: break-word; }
.zx-pl-drawer-body { flex: 1 1 auto; overflow-y: auto; padding: 16px 18px; }

.zx-pl-dd-summary { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; margin-bottom: 16px; }
.zx-pl-dd-kv { min-width: 0; }
.zx-pl-dd-k { font-size: 10px; font-weight: 600; letter-spacing: .03em; text-transform: uppercase; color: var(--kt-gray-500, #99a1b7); }
.zx-pl-dd-v { font-size: 13px; font-weight: 600; color: var(--kt-gray-900, #111827); margin-top: 1px; word-break: break-word; }
.zx-pl-dd-v.zx-pl-value { color: var(--primary, #0d5c63); }

.zx-pl-dd-section { border-top: 1px solid var(--kt-gray-100, #f1f1f4); padding-top: 14px; margin-top: 14px; }
.zx-pl-dd-heading { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--kt-gray-600, #4b5563); margin-bottom: 10px; }
.zx-pl-heading-won { color: #16a34a; }
.zx-pl-heading-lost { color: #dc2626; }

/* Notes / tasks / activity rows */
.zx-pl-note { position: relative; background: var(--kt-gray-100, #f9f9f9); border-radius: 9px; padding: 8px 30px 8px 11px; margin-bottom: 7px; font-size: 12.5px; color: var(--kt-gray-800, #1f2937); }
.zx-pl-note-meta { font-size: 10.5px; color: var(--kt-gray-500, #99a1b7); margin-top: 3px; }
.zx-pl-note-del { position: absolute; top: 6px; right: 7px; border: 0; background: transparent; color: var(--kt-gray-400, #b5b5c3); cursor: pointer; font-size: 13px; line-height: 1; }
.zx-pl-note-del:hover { color: #dc2626; }

.zx-pl-task { display: flex; align-items: flex-start; gap: 9px; padding: 7px 2px; border-bottom: 1px solid var(--kt-gray-100, #f1f1f4); font-size: 12.5px; }
.zx-pl-task:last-child { border-bottom: 0; }
.zx-pl-task-check { margin-top: 1px; cursor: pointer; flex: 0 0 auto; width: 16px; height: 16px; border: 1.5px solid var(--kt-gray-300, #dbdfe9); border-radius: 5px; display: inline-flex; align-items: center; justify-content: center; color: transparent; }
.zx-pl-task-check:hover { border-color: var(--primary, #0d5c63); }
.zx-pl-task.done .zx-pl-task-check { background: #16a34a; border-color: #16a34a; color: #fff; }
.zx-pl-task.done .zx-pl-task-title { text-decoration: line-through; color: var(--kt-gray-400, #b5b5c3); }
.zx-pl-task-title { flex: 1 1 auto; color: var(--kt-gray-800, #1f2937); }
.zx-pl-task-due { font-size: 10.5px; color: var(--kt-gray-500, #99a1b7); }
.zx-pl-task-due.zx-pl-due--over { color: #dc2626; font-weight: 600; }

.zx-pl-act { position: relative; padding: 0 0 12px 18px; border-left: 2px solid var(--kt-gray-200, #f1f1f4); margin-left: 3px; }
.zx-pl-act:last-child { border-left-color: transparent; padding-bottom: 0; }
.zx-pl-act::before { content: ""; position: absolute; left: -5px; top: 3px; width: 8px; height: 8px; border-radius: 50%; background: var(--primary, #0d5c63); }
.zx-pl-act-txt { font-size: 12.5px; color: var(--kt-gray-800, #1f2937); }
.zx-pl-act--comm::before { background: #7c3aed; }   /* communications stand out from stage moves */
.zx-pl-act-meta { font-size: 10.5px; color: var(--kt-gray-500, #99a1b7); margin-top: 2px; }

/* Closed-deal history rows */
.zx-pl-hrow { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 2px; border-bottom: 1px solid var(--kt-gray-100, #f1f1f4); }
.zx-pl-hrow:last-child { border-bottom: 0; }
.zx-pl-hrow-title { font-size: 13px; font-weight: 600; color: var(--kt-gray-900, #111827); }
.zx-pl-hrow-sub { font-size: 10.5px; color: var(--kt-gray-500, #99a1b7); margin-top: 1px; }
.zx-pl-hrow-val { font-size: 13px; font-weight: 700; color: var(--kt-gray-700, #374151); white-space: nowrap; }
.zx-pl-hempty { font-size: 12px; color: var(--kt-gray-400, #b5b5c3); padding: 8px 2px; }

@media (max-width: 480px) { .zx-pl-drawer { width: 100vw; } }

/* ---- Drawer tabs (Details | Conversation) ---- */
.zx-pl-tabs { display: flex; gap: 4px; padding: 0 18px; border-bottom: 1px solid var(--kt-border-color, #eaeaea); }
.zx-pl-tab { position: relative; border: 0; background: transparent; padding: 10px 6px; margin-right: 12px; font-size: 13px; font-weight: 600; color: var(--kt-gray-500, #99a1b7); cursor: pointer; }
.zx-pl-tab:hover { color: var(--kt-gray-800, #1f2937); }
.zx-pl-tab-active { color: var(--primary, #0d5c63); }
.zx-pl-tab-active::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--primary, #0d5c63); border-radius: 2px; }

/* ---- Conversation tab ---- */
.zx-pl-conv-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 12px; }
.zx-pl-win { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; border-radius: 999px; padding: 3px 10px; background: var(--kt-gray-100, #f1f1f4); color: var(--kt-gray-500, #99a1b7); }
.zx-pl-win::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.zx-pl-win--open { background: #dcfce7; color: #16a34a; }
.zx-pl-win--closed { background: #f3f4f6; color: #6b7280; }

.zx-pl-feed { max-height: 40vh; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding: 4px 2px 12px; scrollbar-width: thin; }
.zx-pl-bubble { max-width: 82%; padding: 8px 11px; border-radius: 12px; font-size: 12.5px; line-height: 1.4; word-break: break-word; }
.zx-pl-bubble--in { align-self: flex-start; background: var(--kt-gray-100, #f1f1f4); color: var(--kt-gray-900, #111827); border-bottom-left-radius: 4px; }
.zx-pl-bubble--out { align-self: flex-end; background: color-mix(in srgb, var(--primary, #0d5c63) 14%, #fff); color: var(--kt-gray-900, #111827); border-bottom-right-radius: 4px; }
.zx-pl-bubble-tpl { font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--primary, #0d5c63); margin-bottom: 3px; }
.zx-pl-bubble-meta { font-size: 9.5px; color: var(--kt-gray-400, #b5b5c3); margin-top: 3px; }
.zx-pl-feed-empty { text-align: center; font-size: 12px; color: var(--kt-gray-400, #b5b5c3); padding: 24px 8px; }

.zx-pl-compose { display: flex; gap: 8px; align-items: flex-end; border-top: 1px solid var(--kt-gray-100, #f1f1f4); padding-top: 12px; }
.zx-pl-compose.hidden, .zx-pl-compose-tpl.hidden, #dd-tab-conversation.hidden, #dd-tab-details.hidden,
.zx-pl-tpl-preview.hidden, .zx-pl-conv-disabled.hidden { display: none; }
.zx-pl-compose textarea { resize: none; }
.zx-pl-compose-tpl { border-top: 1px solid var(--kt-gray-100, #f1f1f4); padding-top: 12px; }
.zx-pl-win-note { font-size: 11px; color: #b45309; background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px; padding: 6px 9px; margin-bottom: 10px; }
.zx-pl-tpl-preview { font-size: 12px; color: var(--kt-gray-700, #374151); background: var(--kt-gray-100, #f9f9f9); border-radius: 8px; padding: 8px 10px; margin-bottom: 10px; white-space: pre-wrap; }
.zx-pl-conv-disabled { font-size: 12.5px; color: var(--kt-gray-500, #99a1b7); background: var(--kt-gray-100, #f9f9f9); border: 1px dashed var(--kt-gray-300, #dbdfe9); border-radius: 10px; padding: 16px; text-align: center; }

/* ---- Auth screens ---- */
/* (none yet) */


/* ==========================================================================
   9. UTILITIES & HELPERS
   Small, reusable, single-purpose classes. Use sparingly — prefer a semantic
   class in the section above.
   ========================================================================== */

/*
 * Screen-reader-only text.
 *
 * Tailwind ships `.sr-only`, but the portal bundle is PREBUILT and nothing in
 * the app had used it, so it was never compiled in — writing `class="sr-only"`
 * in a view did nothing at all and the label rendered on screen. Defined here
 * so accessible names (icon-only buttons, loading announcements) actually work.
 */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}
/* Visible again on keyboard focus — for skip links. */
.sr-only-focusable:focus,
.sr-only-focusable:focus-visible {
  position: static;
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  clip: auto;
  white-space: normal;
}

/* Quick Send: the template-variable and OTP blocks carry `flex flex-col` for layout and
   `hidden` for state. Whichever of `.flex`/`.hidden` the prebuilt Tailwind emits last wins,
   so state is pinned here explicitly — custom.css loads after it. Verify with
   getComputedStyle().display, not classList.contains(). */
#qs-vars.hidden,
#qs-otp.hidden { display: none; }

/* Caveat line under a panel whose data is knowingly incomplete — currently the workspace
   profile's activity list, where audit rows only carry a workspace when the acting
   controller had that scope. Deliberately quiet: it qualifies the panel above without
   competing with it for attention. */
.zx-note {
  margin: -0.75rem 0 1.25rem;
  padding: 0 0.25rem;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--color-secondary-foreground, #78829d);
}


/* ==========================================================================
   9b. DEVELOPER PLATFORM
   Code blocks on the Developer Home / API Reference pages. The portal Tailwind
   is prebuilt, so `bg-mono/95`-style opacity utilities may not exist — pin the
   code-block look here instead of relying on a utility that could no-op.
   ========================================================================== */
.zx-code {
  background: #1e1e2d;
  color: #e6e6ef;
  white-space: pre;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* --- API Reference collapsibles (native <details>) ---
   The reference is a long, growing list of endpoints, so it is built from
   <details>/<summary> so it works without JS and stays compact. These rules
   give the summaries a card-like affordance + a rotating chevron. */
.zx-collapse > summary { list-style: none; cursor: pointer; user-select: none; }
.zx-collapse > summary::-webkit-details-marker { display: none; }
.zx-collapse > summary .zx-chev { transition: transform .18s ease; }
.zx-collapse[open] > summary .zx-chev { transform: rotate(90deg); }
/* Endpoint rows inside a resource: hover affordance + a left method accent. */
.zx-ep { border: 1px solid var(--color-border, #e5e7eb); border-radius: .5rem; overflow: hidden; }
.zx-ep + .zx-ep { margin-top: .5rem; }
.zx-ep > summary { padding: .625rem .875rem; display: flex; align-items: center; gap: .5rem; }
.zx-ep > summary:hover { background: var(--color-accent, #f8fafc); }
.zx-ep[open] > summary { border-bottom: 1px solid var(--color-border, #e5e7eb); }
/* Hide an endpoint when the filter marks it out (JS toggles [data-hit]). */
.zx-ep[data-hit="0"] { display: none; }


/* ==========================================================================
   10. RESPONSIVE TWEAKS
   Breakpoint-specific adjustments that don't belong inline in a section.
   ========================================================================== */

/* (none yet) */


/* ==========================================================================
   11. PRINT
   ========================================================================== */

/* (none yet) */


/* ==========================================================================
   12. DARK THEME — components added by the inbox redesign & WhatsApp grid work
   --------------------------------------------------------------------------
   Every tinted chip below shipped with a hardcoded LIGHT pastel background
   (#eef2ff, #ecfdf5, #fffbeb …), unreadable on the dark canvas.

   SELECTOR — this app switches themes with a `.dark` / `.light` CLASS on
   <html>; `data-theme` is never set. An earlier version of this block used
   `:root:not([data-theme="light"])`, which therefore matched ALWAYS — dark
   colours were being applied in light mode, and the action-bar buttons were
   rendering white-on-white borders because of it. `.dark` is the live hook;
   `[data-theme="dark"]` is kept only as a harmless belt-and-braces for any
   surface that might set the attribute.

   color-mix over the surface keeps one rule per chip rather than a second
   hand-picked palette that would drift from the first.
   ========================================================================== */

.dark .zx-chat-stage.is-open,
:root[data-theme="dark"] .zx-chat-stage.is-open,
.dark .zx-th-crm-chip.is-open,
:root[data-theme="dark"] .zx-th-crm-chip.is-open {
  color: #a5b4fc; background: color-mix(in srgb, #6366f1 18%, transparent); border-color: color-mix(in srgb, #6366f1 38%, transparent);
}
.dark .zx-chat-stage.is-won,
:root[data-theme="dark"] .zx-chat-stage.is-won,
.dark .zx-th-crm-chip.is-won,
:root[data-theme="dark"] .zx-th-crm-chip.is-won,
.dark .zx-wa-mode.is-coexistence,
:root[data-theme="dark"] .zx-wa-mode.is-coexistence {
  color: #6ee7b7; background: color-mix(in srgb, #10b981 18%, transparent); border-color: color-mix(in srgb, #10b981 38%, transparent);
}
.dark .zx-chat-stage.is-lost,
:root[data-theme="dark"] .zx-chat-stage.is-lost,
.dark .zx-th-crm-chip.is-lost,
:root[data-theme="dark"] .zx-th-crm-chip.is-lost {
  color: #fca5a5; background: color-mix(in srgb, #ef4444 18%, transparent); border-color: color-mix(in srgb, #ef4444 38%, transparent);
}
.dark .zx-wa-mode.is-manual,
:root[data-theme="dark"] .zx-wa-mode.is-manual,
.dark .zx-chat-verify-lead.is-warn,
:root[data-theme="dark"] .zx-chat-verify-lead.is-warn {
  color: #fcd34d; background: color-mix(in srgb, #f59e0b 16%, transparent); border-color: color-mix(in srgb, #f59e0b 36%, transparent);
}
.dark .zx-wa-mode.is-standard,
:root[data-theme="dark"] .zx-wa-mode.is-standard {
  color: #a5b4fc; background: color-mix(in srgb, #6366f1 16%, transparent); border-color: color-mix(in srgb, #6366f1 36%, transparent);
}

/* Surfaces that were plain white / near-white. */
.dark .zx-chat-wabar-select,
:root[data-theme="dark"] .zx-chat-wabar-select,
.dark .zx-chat-action,
:root[data-theme="dark"] .zx-chat-action {
  background: color-mix(in srgb, #fff 6%, transparent);
  border-color: color-mix(in srgb, #fff 14%, transparent);
}
.dark .zx-chat-action:hover,
:root[data-theme="dark"] .zx-chat-action:hover,
.dark .zx-chat-wabar-btn:hover,
:root[data-theme="dark"] .zx-chat-wabar-btn:hover {
  background: color-mix(in srgb, #fff 12%, transparent);
}
.dark .zx-chat-wabar-count,
:root[data-theme="dark"] .zx-chat-wabar-count {
  color: #6ee7b7; background: color-mix(in srgb, #10b981 16%, transparent); border-color: color-mix(in srgb, #10b981 34%, transparent);
}
.dark .zx-chat-wabar-count:hover,
:root[data-theme="dark"] .zx-chat-wabar-count:hover {
  background: color-mix(in srgb, #10b981 26%, transparent);
}
.dark .zx-chat-via,
:root[data-theme="dark"] .zx-chat-via {
  background: color-mix(in srgb, #fff 7%, transparent);
  border-color: color-mix(in srgb, #fff 14%, transparent);
}
.dark .zx-chat-queue-btn.is-warn:hover,
:root[data-theme="dark"] .zx-chat-queue-btn.is-warn:hover {
  background: color-mix(in srgb, #f59e0b 16%, transparent);
}
.dark .zx-contact-score-bar,
:root[data-theme="dark"] .zx-contact-score-bar {
  background: color-mix(in srgb, #fff 10%, transparent);
}
.dark .zx-contact-score-fill.is-low,
:root[data-theme="dark"] .zx-contact-score-fill.is-low {
  background: color-mix(in srgb, #fff 28%, transparent);
}

/* Service-window banner + thread-header identity + contact card — all shipped light-only. */
.dark .zx-chat-window.is-open,
:root[data-theme="dark"] .zx-chat-window.is-open,
.dark .zx-th-channel,
:root[data-theme="dark"] .zx-th-channel {
  color: #6ee7b7; background: color-mix(in srgb, #10b981 15%, transparent);
  border-color: color-mix(in srgb, #10b981 34%, transparent);
}
.dark .zx-chat-window.is-closed,
:root[data-theme="dark"] .zx-chat-window.is-closed {
  color: #fca5a5; background: color-mix(in srgb, #ef4444 15%, transparent);
  border-color: color-mix(in srgb, #ef4444 34%, transparent);
}
.dark .zx-contact-head,
:root[data-theme="dark"] .zx-contact-head {
  background: color-mix(in srgb, #fff 5%, transparent);
  border-color: color-mix(in srgb, #fff 12%, transparent);
}
.dark .zx-th-crm-item,
:root[data-theme="dark"] .zx-th-crm-item {
  background: color-mix(in srgb, #fff 6%, transparent);
  border-color: color-mix(in srgb, #fff 14%, transparent);
}
.dark .zx-contact-editable.is-empty,
:root[data-theme="dark"] .zx-contact-editable.is-empty {
  border-bottom-color: color-mix(in srgb, #fff 22%, transparent);
}
