/* Inbox 3-pane layout — feature-scoped, imported only by InboxPage. Uses the
   global CSS variables so it stays consistent with the rest of the app. */

.inbox {
  display: grid;
  grid-template-columns: 320px 1fr 300px;
  /* Constrain the single row to the container height (minmax(0,...) lets the
     columns shrink below their content) so each pane scrolls internally
     instead of stretching the grid to content height and scrolling the window. */
  grid-template-rows: minmax(0, 1fr);
  gap: 0;
  height: calc(100dvh - var(--header-height) - (2 * var(--content-padding)));
  min-height: 480px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

/* --- Left: conversation list --- */
.conv-list {
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--color-border);
  min-width: 0;
  min-height: 0;
}

/* --- Filter bar -----------------------------------------------------------
   Two compact rows (search, then status + a Filters disclosure) instead of the
   four stacked rows this replaced, which cost ~230px of a 320px-wide column.
   A third row appears only when filters are actually applied. */

.conv-filterbar {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  border-bottom: 1px solid var(--color-border);
}

.conv-search-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.conv-search-icon {
  position: absolute;
  left: 11px;
  color: var(--color-text-subtle);
  pointer-events: none;
}

.conv-search.form-control {
  /* Slightly tighter than a form input: this is a toolbar control, but still
     16px text so mobile Safari does not zoom on focus. */
  min-height: 40px;
  padding: 8px 34px;
  width: 100%;
}

/* The native search clear button would sit under ours. */
.conv-search::-webkit-search-cancel-button { display: none; }

.conv-search-clear {
  position: absolute;
  right: 4px;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  border-radius: var(--radius-full);
  color: var(--color-text-muted);
}

.conv-search-clear:hover {
  background: var(--color-surface-hover);
  color: var(--color-text);
}

.conv-controls {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

/* Segmented Open / Resolved */
.conv-segmented {
  display: inline-flex;
  flex: 1;
  min-width: 0;
  padding: 3px;
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.conv-seg {
  flex: 1;
  min-width: 0;
  min-height: 34px;
  padding: 6px 8px;
  border: none;
  background: none;
  border-radius: 6px;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
  transition: background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.conv-seg:hover { color: var(--color-text); }

.conv-seg.is-active {
  background: var(--color-surface);
  color: var(--color-primary-active);
  box-shadow: var(--shadow-xs);
}

/* Filters disclosure */
.conv-filter-menu { position: relative; flex-shrink: 0; }

.conv-filter-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 8px 12px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-muted);
}

.conv-filter-btn:hover {
  background: var(--color-surface-hover);
  color: var(--color-text);
}

.conv-filter-btn.has-active {
  border-color: var(--color-primary);
  background: var(--color-primary-soft);
  color: var(--color-primary-active);
}

.conv-filter-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}

.conv-filter-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: 260px;
  max-width: 80vw;
  padding: var(--spacing-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  z-index: var(--z-sticky);
}

.conv-pop-check { min-height: 36px; padding: 0 0 var(--spacing-sm); }
.conv-pop-group { margin-bottom: var(--spacing-sm); }
.conv-pop-group .form-control { min-height: 38px; padding: 8px 12px; }

.conv-pop-footer {
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-sm);
  padding-top: var(--spacing-sm);
  border-top: 1px solid var(--color-border);
}

/* Applied filters: wrapped, never clipped, each individually removable. */
.conv-active-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.conv-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 28px;
  padding: 4px 8px 4px 10px;
  border: 1px solid var(--color-primary-soft-border);
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
  color: var(--color-info-text);
  font-size: var(--font-size-xs);
  font-weight: 600;
  max-width: 100%;
  overflow-wrap: anywhere;
}

.conv-chip:hover { background: #dbeafe; }

.conv-chip-clear {
  min-height: 28px;
  padding: 4px 8px;
  border: none;
  background: none;
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.conv-chip-clear:hover { color: var(--color-text); }

.conv-list-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--spacing-xs);
}

.conv-row {
  display: flex;
  gap: var(--spacing-sm);
  width: 100%;
  padding: 10px 12px;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: left;
}

.conv-row:hover {
  background: var(--color-bg);
}

.conv-row.is-active {
  background: var(--color-primary-soft);
  box-shadow: inset 3px 0 0 var(--color-primary);
}

/* Unread should be legible from the name alone, not only from the badge. */
.conv-row.is-unread .conv-name { font-weight: 700; }
.conv-row.is-unread .conv-preview { color: var(--color-text); font-weight: 500; }

.conv-avatar,
.contact-avatar-lg {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: #fff;
  font-weight: 700;
}

/* Must come after .conv-avatar: same specificity, so source order decides.
   All six clear 4.5:1 against the white initial. */
.conv-avatar-t0 { background: #2563eb; }
.conv-avatar-t1 { background: #7c3aed; }
.conv-avatar-t2 { background: #0f766e; }
.conv-avatar-t3 { background: #b45309; }
.conv-avatar-t4 { background: #be123c; }
.conv-avatar-t5 { background: #4338ca; }

.conv-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.conv-top,
.conv-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
}

.conv-name {
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.conv-time {
  font-size: 11px;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.conv-preview {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.conv-badge {
  flex-shrink: 0;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.conv-load-more {
  display: block;
  margin: var(--spacing-sm) auto;
}

.conv-list-footer {
  padding: 8px var(--spacing-md);
  border-top: 1px solid var(--color-border);
  font-size: 11px;
  color: var(--color-text-muted);
}

/* --- Center: thread --- */
.thread {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.thread-empty {
  align-items: center;
  justify-content: center;
}

.thread-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-sm) var(--spacing-md);
  border-bottom: 1px solid var(--color-border);
}

.thread-name {
  font-weight: 700;
}

.thread-sub {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* Green here is genuine WhatsApp state (the free-reply window is open), which
   is exactly what the palette reserves green for. */
.window-countdown {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-whatsapp-text);
  background: var(--color-whatsapp-soft);
  border: 1px solid #a7f3d0;
  padding: 5px 10px;
  border-radius: var(--radius-full);
  white-space: nowrap;
}

/* Under an hour the same control turns amber — an approaching deadline the
   user may want to act on, not an error. */
.window-countdown.is-urgent {
  color: var(--color-warning-text);
  background: var(--color-warning-soft);
  border-color: #fcd34d;
}

.window-closed-tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-warning-text);
  background: var(--color-warning-soft);
  border: 1px solid #fcd34d;
  padding: 5px 10px;
  border-radius: var(--radius-full);
  white-space: nowrap;
}

.thread-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--spacing-md);
  background: #f7f9fa;
}

.thread-load-older {
  display: block;
  margin: 0 auto var(--spacing-md);
}

.msg-day-group {
  margin-bottom: var(--spacing-sm);
}

.msg-day-label {
  text-align: center;
  margin: var(--spacing-sm) 0;
  font-size: 11px;
  color: var(--color-text-muted);
}

.msg-row {
  display: flex;
  margin-bottom: 6px;
}

.msg-in {
  justify-content: flex-start;
}

.msg-out {
  justify-content: flex-end;
}

.msg-bubble {
  max-width: 68%;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  word-wrap: break-word;
}

.msg-out .msg-bubble {
  background: #d9fdd3;
}

.msg-bubble.is-failed {
  background: #fee2e2;
}

/* Phase 8.2 — inbound button tap / list pick.
   Static badge, never clickable: it reports state, it is not an action. Paired
   with a visible word rather than colour alone, and neutral (not the blue of
   the outbound Template tag) so it reads as subordinate to the message text.
   nowrap keeps the label whole — it is fixed copy, so it can never overflow. */
.msg-reply-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text-muted);
  background: var(--gray-100);
  border-radius: var(--radius-full);
  padding: 1px 8px;
  margin-bottom: 4px;
  white-space: nowrap;
}

.msg-text {
  margin: 0;
  white-space: pre-wrap;
  font-size: var(--font-size-md);
  color: var(--color-text);
}

.msg-caption {
  margin-top: 4px;
}

.msg-link {
  color: var(--color-info);
  text-decoration: underline;
  word-break: break-all;
}

.msg-out .msg-link {
  color: var(--color-primary-active);
}

/* Link preview card (OpenGraph) */
.link-preview {
  display: block;
  max-width: 320px;
  margin-bottom: 6px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: rgba(0, 0, 0, 0.05);
  border: 1px solid var(--color-border);
  text-decoration: none;
  color: inherit;
}

.link-preview-img {
  display: block;
  width: 100%;
  max-height: 180px;
  object-fit: cover;
}

.link-preview-body {
  padding: 6px 10px;
}

.link-preview-domain {
  font-size: 10px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.link-preview-title {
  font-weight: 600;
  font-size: var(--font-size-sm);
  color: var(--color-text);
  margin-top: 1px;
}

.link-preview-desc {
  font-size: 12px;
  color: var(--color-text-muted);
  margin-top: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.msg-meta {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  margin-top: 2px;
}

.msg-time {
  font-size: 10px;
  color: var(--color-text-muted);
}

.msg-tick {
  font-size: 11px;
  color: var(--color-text-muted);
}

.tick-read {
  color: #34b7f1;
}

.tick-failed {
  color: var(--color-danger);
}

.tick-retrying {
  color: var(--color-text-muted);
}

/* Failed send: the reason, then the recovery. An error without a way out is
   the anti-pattern — every failure here either offers Try again or explains in
   words why trying again cannot help. */
.msg-error {
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  font-size: 11px;
  color: var(--color-danger);
}

.msg-error-text {
  margin: 0;
}

/* Sits inside a bubble, so it stays small — but not below the 24px pointer
   target minimum, and its label is a word rather than the icon alone. */
.msg-retry {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 24px;
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 600;
  font-family: inherit;
  color: var(--color-danger-text, var(--color-danger));
  background: var(--color-danger-soft);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}

.msg-retry:hover:not(:disabled) {
  border-color: var(--color-danger);
}

.msg-retry:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring-danger, var(--focus-ring));
}

.msg-retry:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Why a retry will not help, or how many have already been made. Muted so it
   reads as a footnote to the error rather than a second error. */
.msg-error-hint {
  margin: 0;
  color: var(--color-text-muted);
}

.msg-media {
  display: block;
  margin-bottom: 4px;
}

.msg-media-image {
  max-width: 260px;
  max-height: 260px;
  border-radius: var(--radius-sm);
}

.msg-media-video {
  max-width: 260px;
  border-radius: var(--radius-sm);
}

.msg-media-doc {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--color-primary);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.msg-media-pending,
.msg-media-error {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  font-style: italic;
}

/* --- Composer --- */
.composer {
  position: relative;
  border-top: 1px solid var(--color-border);
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-surface);
}

/* Inline "/shortcut" canned-reply autocomplete — floats above the composer so
   it doesn't shift the chat layout. Reuses the .canned-item rows from team.css. */
.composer-autocomplete {
  position: absolute;
  left: var(--spacing-md);
  right: var(--spacing-md);
  bottom: calc(100% - var(--spacing-sm));
  max-height: 260px;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  z-index: 20;
  padding: 4px;
}

.composer-autocomplete-hint {
  font-size: 11px;
  color: var(--color-text-muted);
  padding: 6px 8px;
}

.composer-autocomplete .canned-item.is-active {
  background: var(--color-bg);
}

.composer-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.composer-input {
  flex: 1;
}

/* Icon-only composer controls. Full 44px hit box even though the glyph is
   20px — these sit close together above the keyboard on a phone. */
.composer-attach {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  height: var(--tap-target);
  flex-shrink: 0;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  cursor: pointer;
  padding: 0;
}

.composer-attach:hover {
  background: var(--color-surface-hover);
  color: var(--color-primary);
}

/* Labeled template trigger in the open composer — discoverable (not a bare icon). */
.composer-tpl-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--tap-target);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-primary);
  font-size: var(--font-size-sm);
  font-weight: 600;
  cursor: pointer;
  padding: 8px 14px;
  border-radius: var(--radius-full);
  white-space: nowrap;
  flex-shrink: 0;
}
.composer-tpl-btn:hover {
  background: var(--color-primary-soft);
}

.composer-file-input {
  display: none;
}

.composer-preview-name {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.composer-preview {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  padding: 6px 10px;
  margin-bottom: var(--spacing-sm);
  background: var(--color-bg);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
}

.composer-preview-remove {
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--color-text-muted);
}

.composer-error {
  margin-bottom: var(--spacing-sm);
  color: var(--color-danger);
  font-size: var(--font-size-sm);
}

.composer-closed {
  border-top: 1px solid var(--color-border);
  padding: var(--spacing-md);
  background: #fffbeb;
}

/* The 24h-window notice explains a WhatsApp rule the owner did not choose, so
   it reads as guidance rather than an error they caused. */
.composer-closed-text {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  margin: 0;
  color: var(--color-warning-text);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-base);
}

/* --- Right: contact panel --- */
.contact-panel {
  border-left: 1px solid var(--color-border);
  padding: var(--spacing-lg);
  min-height: 0;
  overflow-y: auto;
  text-align: center;
}

.contact-panel-empty {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

.contact-avatar-lg {
  width: 72px;
  height: 72px;
  font-size: 28px;
  margin: 0 auto var(--spacing-sm);
}

.contact-name {
  font-weight: 700;
  font-size: var(--font-size-lg);
}

.contact-phone {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin-bottom: var(--spacing-md);
}

.contact-section {
  text-align: left;
  border-top: 1px solid var(--color-border);
  padding-top: var(--spacing-md);
  margin-top: var(--spacing-md);
}

.contact-section-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-sm);
}

.contact-details {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  margin: 0;
  font-size: var(--font-size-sm);
}

.contact-details dt {
  color: var(--color-text-muted);
}

.contact-details dd {
  margin: 0;
  text-align: right;
  word-break: break-all;
}

.contact-placeholder-text {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  font-style: italic;
  margin: 0;
}

/* Narrow screens: collapse the contact panel; stack list over thread. */
@media (max-width: 1024px) {
  .inbox {
    grid-template-columns: 280px 1fr;
  }

  .contact-panel {
    display: none;
  }
}

@media (max-width: 640px) {
  .inbox {
    grid-template-columns: 1fr;
  }
}

/* =========================================================================
   Contact panel — collapsible sections
   -------------------------------------------------------------------------
   The panel is a dense ~320px column that now carries counts, campaigns,
   broadcasts, notes and a history rail, so everything below the header
   collapses. Density is bought with rhythm, not by shrinking text: the type
   floor stays at --font-size-xs (12px) and every header is a full 44px target,
   per the audience note in variables.css.
   ========================================================================= */

.panel-section {
  text-align: left;
  border-top: 1px solid var(--color-border);
}

/* The heading exists for document structure (h3 inside the panel's own
   hierarchy); all of its styling lives on the button it wraps. */
.panel-section-heading {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
}

.panel-section-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  width: 100%;
  /* Full tap target without padding the collapsed panel out of usefulness. */
  min-height: var(--tap-target);
  padding: var(--spacing-sm) var(--spacing-2xs);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-standard);
}

.panel-section-head:hover {
  background: var(--color-surface-hover);
}

/* Never remove: keyboard users navigate this panel section by section. */
.panel-section-head:focus-visible {
  outline: var(--focus-outline);
  outline-offset: 2px;
}

.panel-section-chevron {
  flex: none;
  color: var(--color-text-subtle);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.panel-section-chevron.is-open {
  transform: rotate(90deg);
}

.panel-section-title {
  /* min-width:0 lets the title shrink instead of shoving the count pill out of
     the row — the count is the part that must never wrap. */
  flex: 1;
  min-width: 0;
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

/* A state badge, not a chip: it reports a count and is never itself clickable.
   Hidden from assistive tech — PanelSection renders the same figure as a full
   phrase beside it. */
.panel-section-count {
  flex: none;
  min-width: 20px;
  padding: 0 var(--spacing-xs);
  border-radius: var(--radius-full);
  background: var(--color-surface-hover);
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  font-variant-numeric: tabular-nums;
  line-height: 20px;
  text-align: center;
  white-space: nowrap;
}

.panel-section-body {
  padding: 0 var(--spacing-2xs) var(--spacing-md);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-base);
}

/* A nested contact-section would draw a second divider immediately under the
   header it already sits beneath. */
.panel-section-body .contact-section {
  border-top: 0;
  padding-top: 0;
  margin-top: 0;
}

/* Figures in the Overview/Messages tables are read as a column, so they need
   tabular figures — Inter has them, and proportional digits make 11 vs 8 look
   like different indents. */
.panel-section-body .contact-details dd {
  font-variant-numeric: tabular-nums;
}

/* --- List rows (campaigns, broadcasts) --- */
.panel-list,
.panel-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
}

.panel-list-item {
  padding: var(--spacing-sm) 0;
  border-bottom: 1px solid var(--color-border);
}

.panel-list-item:last-child {
  border-bottom: 0;
}

.panel-list-main {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-sm);
}

/* Wraps rather than truncates. A campaign name is the one thing that
   identifies the row, and an ellipsis with the full value only on hover is
   unreachable by keyboard and touch — so the name gets the extra line. */
.panel-list-title {
  min-width: 0;
  font-weight: 600;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.panel-list-meta {
  margin-top: var(--spacing-2xs);
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  overflow-wrap: anywhere;
}

/* --- History rail --- */
.panel-timeline-item {
  position: relative;
  display: flex;
  gap: var(--spacing-sm);
  padding: 0 0 var(--spacing-md);
}

/* The connecting line, drawn behind the dots and stopped at the last item so
   the rail reads as a finished sequence rather than trailing off. */
.panel-timeline-item::before {
  content: '';
  position: absolute;
  top: var(--spacing-sm);
  bottom: 0;
  left: 3px;
  width: 2px;
  background: var(--color-border);
}

.panel-timeline-item:last-child {
  padding-bottom: 0;
}

.panel-timeline-item:last-child::before {
  display: none;
}

.panel-timeline-dot {
  position: relative;
  flex: none;
  width: 8px;
  height: 8px;
  margin-top: var(--spacing-sm);
  border-radius: var(--radius-full);
  background: var(--color-primary);
}

.panel-timeline-body {
  min-width: 0;
}

/* --- Notes --- */
.contact-note-form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-md);
}

.contact-note-form textarea {
  width: 100%;
  min-height: 64px;
  resize: vertical;
}

.contact-note-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.contact-note {
  padding: var(--spacing-sm) 0;
  border-bottom: 1px solid var(--color-border);
}

.contact-note:last-child {
  border-bottom: 0;
}

.contact-note-body {
  margin: 0 0 var(--spacing-xs);
  color: var(--color-text);
  /* Agents paste order ids and links in here; let them reflow rather than
     stretch the panel into a horizontal scroll. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.contact-note-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
}

.contact-note-author {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Text button, but still a real pointer target (WCAG 2.2 target size). */
.panel-text-button {
  flex: none;
  min-height: 24px;
  padding: var(--spacing-2xs) var(--spacing-xs);
  margin-right: calc(var(--spacing-xs) * -1);
  background: none;
  border: 0;
  border-radius: var(--radius-xs);
  color: var(--color-danger);
  font-size: var(--font-size-xs);
  cursor: pointer;
}

.panel-text-button:hover {
  background: var(--color-danger-soft);
}

.panel-text-button:focus-visible {
  outline: var(--focus-outline);
  outline-offset: 1px;
}

/* Motion here is orientation (the chevron points where the section went), so
   it is the first thing to drop when the user asks for less of it. */
@media (prefers-reduced-motion: reduce) {
  .panel-section-head,
  .panel-section-chevron {
    transition: none;
  }
}
/* Guided setup — its own full-screen chrome, outside MainLayout.
   Imported only by OnboardingPage. */

.ob-shell {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
}

/* ---- Top bar ------------------------------------------------------- */

.ob-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}

.ob-brand {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  font-weight: 700;
  color: var(--color-text);
}

/* The mark itself is <BrandMark/> — see components/layout/BrandMark.jsx.
   This header used to draw its own blue tile with a generic chat glyph, which
   meant the product had two different logos depending on which screen you
   landed on first. */

.ob-top-right {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
}

.ob-step-count {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
}

/* ---- Step rail ----------------------------------------------------- */

.ob-rail {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  padding: 0 var(--spacing-lg) var(--spacing-sm);
}

.ob-rail-track {
  height: 4px;
  border-radius: 999px;
  background: var(--color-border);
  overflow: hidden;
  margin-bottom: var(--spacing-sm);
}

.ob-rail-fill {
  display: block;
  height: 100%;
  background: var(--color-primary);
  /* Width, not transform: the bar is a measure of progress and reads more
     honestly growing from its own start than scaling from the middle. */
  transition: width 240ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .ob-rail-fill {
    transition: none;
  }
}

.ob-rail-steps {
  display: flex;
  gap: var(--spacing-xs);
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-x: auto;
}

.ob-rail-step button {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  padding: var(--spacing-xs) var(--spacing-sm);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text-subtle);
  font: inherit;
  font-size: var(--font-size-sm);
  white-space: nowrap;
  cursor: default;
}

.ob-rail-step button:not(:disabled) {
  cursor: pointer;
}

.ob-rail-step button:not(:disabled):hover {
  background: var(--color-surface-hover);
}

.ob-rail-num {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--color-border-strong);
  font-size: 11px;
  font-weight: 700;
}

.ob-rail-step.is-current button {
  color: var(--color-text);
  font-weight: 600;
}

.ob-rail-step.is-current .ob-rail-num {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-on-primary);
}

.ob-rail-step.is-done .ob-rail-num {
  border-color: var(--color-success);
  background: var(--color-success);
  color: #fff;
}

/* ---- Body ---------------------------------------------------------- */

.ob-main {
  flex: 1;
  display: flex;
  justify-content: center;
  padding: var(--spacing-xl) var(--spacing-lg);
}

.ob-card {
  width: 100%;
  max-width: 640px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--spacing-xl);
  align-self: flex-start;
}

/* Step one carries the connect form and needs the room. */
.ob-card-wide {
  max-width: 860px;
}

.ob-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-text-subtle);
  margin-bottom: var(--spacing-xs);
}

.ob-heading {
  font-size: var(--font-size-2xl);
  line-height: 1.25;
  margin-bottom: var(--spacing-sm);
  /* Balance only, no width cap: a cap short enough to force a break on the
     longest heading also breaks the ones that fit. Where a heading does wrap,
     balance keeps the last line from holding a single word. */
  text-wrap: balance;
}

.ob-lede {
  color: var(--color-text-muted);
  line-height: 1.7;
  max-width: 60ch;
  margin-bottom: var(--spacing-lg);
}

.ob-number {
  color: var(--color-text);
  white-space: nowrap;
}

.ob-instructions {
  margin: 0 0 var(--spacing-lg);
  padding-left: var(--spacing-lg);
  color: var(--color-text-muted);
  line-height: 2;
  max-width: 58ch;
}

.ob-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
}

.ob-note {
  margin-top: var(--spacing-md);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  line-height: 1.6;
  max-width: 60ch;
}

.ob-done,
.ob-warning,
.ob-check {
  display: flex;
  gap: var(--spacing-sm);
  align-items: flex-start;
  padding: var(--spacing-md);
  border-radius: var(--radius-md);
  line-height: 1.6;
}

.ob-done {
  border: 1px solid var(--color-success);
  background: var(--color-success-soft);
  color: var(--color-success-text);
}

.ob-warning,
.ob-check.is-warn {
  border: 1px solid var(--color-warning);
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
}

.ob-check {
  margin-top: var(--spacing-lg);
}

.ob-check.is-ok {
  border: 1px solid var(--color-success);
  background: var(--color-success-soft);
  color: var(--color-success-text);
}

.ob-done svg,
.ob-warning svg,
.ob-check svg {
  flex-shrink: 0;
  margin-top: 2px;
}

.ob-connect-panel {
  margin-top: var(--spacing-lg);
}

/* The connect panels are cards in their own right on the settings screen.
   Inside the wizard they are already inside one, so drop the second frame. */
.ob-connect-panel > .card {
  border: 0;
  box-shadow: none;
  padding: 0;
  background: transparent;
}

/* ---- Closing screen ------------------------------------------------ */

.ob-seal {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  margin-bottom: var(--spacing-md);
}

.ob-seal.is-ready {
  background: var(--color-success-soft);
  color: var(--color-success);
}

.ob-seal.is-waiting {
  background: var(--color-warning-soft);
  color: var(--color-warning);
}

.ob-recap {
  list-style: none;
  margin: 0 0 var(--spacing-lg);
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.ob-recap li {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  border-bottom: 1px solid var(--color-border);
  color: var(--color-text-muted);
}

.ob-recap li:last-child {
  border-bottom: 0;
}

.ob-recap li.is-done {
  color: var(--color-text);
}

.ob-recap-mark {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--color-border);
  color: var(--color-text-subtle);
}

.ob-recap li.is-done .ob-recap-mark {
  background: var(--color-success);
  color: #fff;
}

.ob-recap-title {
  flex: 1;
  overflow-wrap: anywhere;
}

.ob-recap-tag {
  margin-left: var(--spacing-xs);
  font-size: 12px;
  color: var(--color-text-subtle);
}

.ob-recap-link {
  border: 0;
  background: none;
  padding: var(--spacing-xs);
  color: var(--color-primary);
  font: inherit;
  font-size: var(--font-size-sm);
  font-weight: 600;
  cursor: pointer;
}

.ob-recap-link:hover {
  text-decoration: underline;
}

/* ---- Footer -------------------------------------------------------- */

.ob-foot {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
}

.ob-foot-right {
  display: flex;
  gap: var(--spacing-sm);
}

@media (max-width: 640px) {
  .ob-top,
  .ob-rail,
  .ob-foot {
    padding-left: var(--spacing-md);
    padding-right: var(--spacing-md);
  }

  .ob-main {
    padding: var(--spacing-lg) var(--spacing-md);
  }

  .ob-card {
    padding: var(--spacing-lg);
  }

  .ob-brand-name {
    display: none;
  }

  /* Stacked, full-width and thumb-reachable rather than two cramped buttons. */
  .ob-actions > * {
    width: 100%;
  }
}
/* WhatsApp Connection feature — feature-scoped styles (imported only here). */

.wa-stack {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-lg);
}

.wa-method-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--spacing-md);
}

/* The method chooser is a real radio group: the input is visually hidden but
   still focusable, and the card is the label. Never `display: none` on the
   input — that removes it from the tab order and from every screen reader. */
.wa-method-fieldset {
  border: 0;
  margin: 0;
  padding: 0;
}

.wa-method-legend {
  padding: 0;
  margin-bottom: var(--spacing-sm);
  font-weight: 600;
  color: var(--color-text);
}

.wa-method-card {
  position: relative;
  display: flex;
  gap: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  background: var(--color-surface);
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 120ms ease, background-color 120ms ease;
}

.wa-method-card:hover:not(.is-disabled) {
  border-color: var(--color-border-strong);
  background: var(--color-surface-hover);
}

/* Same recipe as the house .sr-only: hidden from sight, still focusable and
   still announced. Not opacity/visibility/display, all of which remove it from
   the tab order or the accessibility tree. */
.wa-method-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Focus lands on the hidden input, so the ring has to be drawn on the card. */
.wa-method-input:focus-visible + .wa-method-body {
  outline: 2px solid var(--color-primary);
  outline-offset: 6px;
  border-radius: var(--radius-sm);
}

.wa-method-card.is-selected {
  border-color: var(--color-primary);
  background: var(--color-primary-soft);
  box-shadow: var(--shadow-sm);
}

.wa-method-card.is-disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.wa-method-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-xs);
}

.wa-method-title {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  font-weight: 600;
  color: var(--color-text);
}

.wa-method-desc {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  line-height: 1.6;
}

/* The tick is decoration on top of the selected state, never the only signal
   of it — the border, the fill and the radio itself all say the same thing. */
.wa-method-tick {
  position: absolute;
  top: var(--spacing-sm);
  right: var(--spacing-sm);
  display: none;
  width: 22px;
  height: 22px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-on-primary);
}

.wa-method-card.is-selected .wa-method-tick {
  display: flex;
}

/* ---- Embedded Signup panel (Path B) -------------------------------- */

.wa-es-heading {
  font-size: var(--font-size-lg);
  margin-bottom: var(--spacing-xs);
}

.wa-es-lede {
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-md);
  max-width: 62ch;
  line-height: 1.7;
}

.wa-es-points {
  list-style: none;
  margin: 0 0 var(--spacing-lg);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  max-width: 68ch;
}

.wa-es-points li {
  display: flex;
  gap: var(--spacing-sm);
  align-items: flex-start;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  line-height: 1.6;
}

.wa-es-points svg {
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--color-primary);
}

.wa-es-cta {
  min-width: 240px;
}

.wa-es-note {
  margin-top: var(--spacing-sm);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  max-width: 62ch;
}

/* A connection that succeeded with a caveat. Warning, not error: the number
   IS connected, and colouring it red would send them to support over
   something they can finish themselves. */
.wa-notice {
  display: flex;
  gap: var(--spacing-sm);
  align-items: flex-start;
  padding: var(--spacing-md);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
  line-height: 1.6;
}

.wa-notice svg {
  flex-shrink: 0;
  margin-top: 2px;
}

.wa-steps {
  margin: 0 0 var(--spacing-md);
  padding-left: var(--spacing-lg);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  line-height: 1.7;
}

.wa-copy-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.wa-code {
  flex: 1;
  display: block;
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-sm);
  word-break: break-all;
}

/* Auto-pause banner. Sits above the card header because the tenant's sending
   has actually stopped — this is not a hint, it is the reason nothing is going
   out, and burying it in a detail row would let them miss it entirely. */
.connection-paused {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--spacing-md);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-danger-soft, rgba(220, 38, 38, 0.08));
  color: var(--color-text);
  font-size: var(--font-size-sm);
  line-height: 1.5;
}

.connection-paused strong {
  color: var(--color-danger);
}

/* Meta refusing every send — same red as the pause above it, but with the way
   out beside the text: this one is repaired in Meta, not here. */
.connection-blocked {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  padding: var(--spacing-md);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-danger-soft, rgba(220, 38, 38, 0.08));
  color: var(--color-text);
  font-size: var(--font-size-sm);
  line-height: 1.5;
}

.connection-blocked > div { flex: 1; min-width: 260px; }

/* The title carries the alarm colour; a menu path quoted mid-sentence does
   not — red there reads as another thing being wrong. */
.connection-blocked > div > strong { display: block; color: var(--color-danger); }

.connection-blocked span { display: block; margin-top: 2px; max-width: 78ch; }

.connection-blocked .btn { flex-shrink: 0; }

/* Missing webhook fields. Warning-coloured rather than error: the connection
   works, but specific events will never arrive — a silent gap, not a failure. */
.wa-missing-fields {
  margin: var(--spacing-md) 0;
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-warning, #d97706);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  line-height: 1.5;
}

.wa-missing-fields ul {
  margin: var(--spacing-sm) 0;
  padding-left: var(--spacing-md);
}

.wa-missing-fields code {
  font-family: var(--font-mono, monospace);
}

/* The way out of this screen and into Meta's, at the head of the steps that
   tell them what to do once they are there. Primary-soft rather than a plain
   row: it is the one action in a card that is otherwise all reading. */
.wa-meta-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  margin-bottom: var(--spacing-lg);
  padding: var(--spacing-md);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  border-radius: var(--radius-md);
}

.wa-meta-link > div { flex: 1; min-width: 220px; }

/* The heading only. An inline <strong> naming a menu path inside the sentence
   must not become its own line — it reads as a broken layout. */
.wa-meta-link > div > strong:first-child { display: block; font-weight: 700; }

.wa-meta-link p {
  margin-top: 2px;
  font-size: var(--font-size-sm);
  line-height: 1.55;
  color: var(--color-text-muted);
  max-width: 62ch;
}

.wa-meta-link .btn { flex-shrink: 0; }

/* The second-best link, and it reads as second-best on purpose: one primary
   action per block, or neither gets pressed. */
.wa-meta-fallback {
  margin-top: var(--spacing-sm);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.wa-meta-fallback a { color: var(--color-primary); text-decoration: underline; }

.wa-fields-ok {
  margin: var(--spacing-md) 0;
  font-size: var(--font-size-sm);
  color: var(--color-success);
}

/* ==========================================================================
   Coexistence — the Business app and this inbox on one number
   ========================================================================== */

.coex-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4, 16px);
  /* `.card` carries no padding in this system — each row supplies its own, so
     the dividers can span the full width. This card has no rows to hang it on,
     so it takes the same 16/24 inset directly and stays in rhythm with the
     Connection card above it. */
  padding: 16px 24px;
}

.coex-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3, 12px);
  flex-wrap: wrap;
}

.coex-heading {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
}

/* Status, not brand: this says "your phone is still live", which is a state.
   Kept to one line with a shrinkable label so a badge never wraps. */
.coex-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 1 auto;
  min-width: 0;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 600;
  color: var(--color-success-text);
  background: var(--color-success-soft);
  border-radius: var(--radius-full);
  padding: 2px 10px;
}

.coex-lede {
  margin: 0;
  max-width: 65ch;
  font-size: 13px;
  line-height: 1.6;
  color: var(--color-text-muted, var(--gray-600));
}

.coex-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-3, 12px);
}

.coex-row {
  display: flex;
  gap: var(--space-3, 12px);
  padding: var(--space-3, 12px);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  background: var(--color-surface, #fff);
}

.coex-row-icon {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--color-text-muted, var(--gray-600));
}

.coex-row-body { min-width: 0; flex: 1 1 auto; }

.coex-row-title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
}

.coex-row-status {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--color-text-muted, var(--gray-600));
}

/* Tone carries the icon colour only. The status word beside it always says the
   same thing in text, so nothing here depends on seeing a colour. */
.coex-row.is-done .coex-row-icon { color: var(--color-success); }
.coex-row.is-working .coex-row-icon { color: var(--color-primary, #2563eb); }
.coex-row.is-problem .coex-row-icon { color: var(--color-danger); }
/* Stalled is not failed: nobody refused us, the import simply went quiet, and
   it is usually recoverable by asking again. Warning rather than danger. */
.coex-row.is-stalled .coex-row-icon { color: var(--color-warning); }

/* Why it stopped, and the one button that does something about it. Kept inside
   the row so the reason can never be read against the wrong sync. */
.coex-row-problem {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.coex-row-reason {
  margin: 0;
  max-width: 60ch;
  font-size: 12px;
  line-height: 1.6;
  color: var(--color-text-muted, var(--gray-600));
}

.coex-row-refusal {
  margin: 0;
  max-width: 60ch;
  font-size: 12px;
  line-height: 1.6;
  color: var(--color-danger-text, var(--color-danger));
}

.coex-progress {
  margin-top: 8px;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--gray-200);
  overflow: hidden;
}

.coex-progress-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--color-primary, #2563eb);
  /* Width only, and only while a real import is moving. */
  transition: width 400ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .coex-progress-fill { transition: none; }
}

.coex-progress-note {
  margin: 6px 0 0;
  max-width: 60ch;
  font-size: 12px;
  line-height: 1.5;
  color: var(--color-text-muted, var(--gray-600));
}

.coex-alert {
  display: flex;
  gap: var(--space-3, 12px);
  padding: var(--space-3, 12px);
  border: 1px solid var(--color-danger-soft);
  border-left: 3px solid var(--color-danger);
  border-radius: var(--radius-md, 8px);
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
}

.coex-alert > .icon { flex: 0 0 auto; color: var(--color-danger); }
.coex-alert-title { margin: 0; font-size: 13px; font-weight: 600; }
.coex-alert-body { margin: 4px 0 0; max-width: 60ch; font-size: 12px; line-height: 1.6; }

/* --- Pre-connect disclosure -------------------------------------------- */

.coex-disclose {
  margin-top: var(--space-4, 16px);
  padding: var(--space-3, 12px);
  border: 1px solid var(--color-primary-soft-border);
  border-radius: var(--radius-md, 8px);
  background: var(--color-primary-soft);
}

.coex-disclose-lede {
  display: flex;
  gap: 8px;
  margin: 0;
  max-width: 62ch;
  font-size: 13px;
  line-height: 1.6;
  color: var(--color-info-text);
}

.coex-disclose-lede > .icon { flex: 0 0 auto; margin-top: 2px; }

.coex-disclose-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  /* 44px is the touch minimum; the padding is what gets it there rather than a
     height that would leave the label floating. */
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-info-text);
  text-align: left;
}

.coex-disclose-toggle:hover { text-decoration: underline; }

.coex-disclose-toggle .icon { transition: transform 150ms ease; }
.coex-disclose-toggle .icon.is-open { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
  .coex-disclose-toggle .icon { transition: none; }
}

.coex-disclose-list {
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.coex-disclose-list li {
  display: flex;
  gap: 8px;
  max-width: 62ch;
  font-size: 12px;
  line-height: 1.6;
  color: var(--color-info-text);
}

.coex-disclose-list li > .icon { flex: 0 0 auto; margin-top: 3px; }

/* --- Which Embedded Signup flow to run ---------------------------------- */

.wa-mode {
  margin: 0 0 var(--space-4, 16px);
  padding: 0;
  border: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.wa-mode-legend {
  padding: 0;
  margin-bottom: 8px;
  font-size: 13px;
  font-weight: 600;
}

.wa-mode-option {
  display: flex;
  gap: 10px;
  /* 12px + the 20px control keeps the whole row past the 44px touch minimum,
     and the label IS the target — not just the radio dot. */
  padding: 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 8px);
  background: var(--color-surface, #fff);
  cursor: pointer;
}

.wa-mode-option:hover { border-color: var(--color-border-strong); }

/* Selection is carried by the border AND the radio itself, never by colour
   alone — the fill is a tint, not the signal. */
.wa-mode-option.is-selected {
  border-color: var(--color-primary, #2563eb);
  background: var(--color-primary-soft);
}

.wa-mode-option input { margin-top: 3px; flex: 0 0 auto; }

.wa-mode-option:focus-within {
  outline: 2px solid var(--color-primary, #2563eb);
  outline-offset: 2px;
}

.wa-mode-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.wa-mode-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
}

.wa-mode-note {
  max-width: 60ch;
  font-size: 12px;
  line-height: 1.6;
  color: var(--color-text-muted, var(--gray-600));
}

.wa-mode-disclose {
  margin: -4px 0 4px 30px;
  padding: 10px 12px;
  border-radius: var(--radius-md, 8px);
  background: var(--color-primary-soft);
}
/* =========================================================================
   Design tokens — single source of truth for the whole SPA.

   Brand: the Wazigo navy shell, a deep "trust blue" for everything the user
   can press, and the logo's forest green for brand expression only.

   THREE colours, three jobs — keep them apart:
     --color-primary        blue   = interactive. Buttons, links, active nav,
                                     focus. If you can click it, it is blue.
     --color-whatsapp       mint   = WhatsApp STATE. Connected, delivered,
                                     24h window open. Never decoration.
     --color-brand-green    forest = WHO WE ARE. The mark, the sign-in
                                     backdrop. Never a control, never state.

   Why blue still owns the buttons even though the logo is green (owner
   decision, 2026-08-26): green already means "delivered / connected" in this
   product, and a green Send button sitting beside a green delivered tick
   teaches the user that green means nothing in particular. Blue also reads as
   "serious business software" to the Indian SMB audience — Razorpay, Paytm,
   Khatabook. Meta's brand guidelines separately discourage a third-party tool
   dressing itself as WhatsApp.

   Audience note: this product is used by non-technical small-business owners
   in India, often on mid-range phones. Base text is 16px, muted text still
   clears 4.5:1, and tap targets are 44px. Do not shrink these.
   ========================================================================= */

:root {
  /* ---- Brand -------------------------------------------------------- */
  --color-primary: #2563eb;
  --color-primary-hover: #1d4ed8;
  --color-primary-active: #1e40af;
  --color-primary-soft: #eff6ff;
  --color-primary-soft-border: #bfdbfe;
  --color-on-primary: #ffffff;

  /* Navy shell used by the sidebar / brand mark. This is the Wazigo logo's
     own navy — the same #0f1b33 the marketing site uses, so app and site
     match without anyone having to remember two values. */
  --color-brand-navy: #0f1b33;
  --color-brand-navy-soft: #1a2a4a;

  /* ---- Wazigo green (BRAND expression only — never a CTA, never state)
     Sampled from the official logo gradient. Read the next block before
     using any of these: this is the second green in the system and the two
     are kept apart on purpose.

     Brand green is a deep FOREST/emerald; WhatsApp green below is a light
     MINT. They are different hues, not two shades of one colour, which is
     what lets both live in the same UI without "green" losing its meaning.

     Use for: the brand mark, the sign-in backdrop, brand illustration.
     Do NOT use for: buttons, links, active nav, focus rings (those stay
     --color-primary blue), or for connected/delivered/window-open state
     (that stays --color-whatsapp). */
  --color-brand-green: #08b74f;       /* vivid mid-gradient — fills on dark only, 2.7:1 on white */
  --color-brand-green-bright: #22d366; /* the gradient's light end — mark and backdrop only, never text */
  --color-brand-green-deep: #05803f;  /* 5.0:1 on white — safe for text and icons */
  --color-brand-green-dark: #00603a;  /* 7.7:1 on white — the gradient's dark end */
  --color-brand-green-soft: #d8f8de;  /* tint for brand surfaces */

  /* ---- WhatsApp semantics (state only — never chrome) ---------------- */
  --color-whatsapp: #25d366;
  --color-whatsapp-deep: #128c7e;
  --color-whatsapp-soft: #e7f9ef;
  /* Text-safe green: --color-whatsapp-deep only reaches 3.78:1 on the soft
     background, under the 4.5:1 minimum. Use this for green text. */
  --color-whatsapp-text: #0b6b52;

  /* ---- Status ------------------------------------------------------- */
  --color-success: #15803d;
  --color-success-soft: #dcfce7;
  --color-success-text: #14532d;

  --color-warning: #b45309;
  --color-warning-soft: #fef3c7;
  --color-warning-text: #78350f;

  --color-danger: #dc2626;
  --color-danger-hover: #b91c1c;
  --color-danger-soft: #fee2e2;
  --color-danger-text: #991b1b;

  --color-info: #2563eb;
  --color-info-soft: #dbeafe;
  --color-info-text: #1e40af;

  /* ---- Chart series -------------------------------------------------
     Identity, not state. Two fixed slots, assigned once and never cycled:
     slot 1 = messages we sent, slot 2 = messages customers sent. A filter
     must never repaint them, and neither may borrow a status colour — a
     teal bar means "from a customer", not "healthy".

     The pair is validated for colour-vision deficiency (OKLab ΔE 20.7
     deutan / 22.6 normal against the white card). Its tritan separation is
     6.4, which is only legal alongside a second, non-colour channel — hence
     the fixed stacking order, the 2px surface gap between segments, the
     legend, and the table view under every chart. Do not drop those. */
  --chart-1: #2563eb;
  --chart-1-soft: #dbeafe;
  --chart-2: #0d9488;
  --chart-2-soft: #ccfbf1;
  /* Gridlines and axis rules: one step off the surface, hairline, solid. */
  --chart-grid: var(--gray-200);

  /* ---- Neutrals (blue-tinted slate, harmonises with the brand) ------- */
  --gray-50: #f8fafc;
  --gray-100: #f1f5f9;
  --gray-200: #e2e8f0;
  --gray-300: #cbd5e1;
  --gray-400: #94a3b8;
  --gray-500: #64748b;
  --gray-600: #475569;
  --gray-700: #334155;
  --gray-800: #1e293b;
  --gray-900: #0f172a;

  /* ---- Semantic surfaces -------------------------------------------- */
  --color-bg: var(--gray-100);
  --color-surface: #ffffff;
  --color-surface-sunken: var(--gray-50);
  --color-surface-hover: var(--gray-100);
  --color-border: var(--gray-200);
  --color-border-strong: var(--gray-300);

  --color-text: var(--gray-900);
  --color-text-muted: var(--gray-600);   /* 7.5:1 on white */
  --color-text-subtle: var(--gray-500);  /* 4.8:1 — labels only, never body */

  /* Sidebar */
  --color-sidebar-bg: var(--color-brand-navy);
  --color-sidebar-text: #c7d2e4;
  --color-sidebar-text-strong: #ffffff;
  --color-sidebar-heading: #7d8db0;
  --color-sidebar-hover: rgba(255, 255, 255, 0.07);
  --color-sidebar-active: var(--color-primary);

  /* ---- Radii --------------------------------------------------------- */
  --radius-xs: 4px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-full: 999px;

  /* ---- Spacing (4pt rhythm) ------------------------------------------ */
  --spacing-2xs: 2px;
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  --spacing-2xl: 48px;

  /* ---- Typography ----------------------------------------------------- */
  /* Plus Jakarta Sans = friendly geometric headings (approachable, not
     corporate-cold). Inter = workhorse UI text, excellent at 14–16px with
     tabular figures. Noto Sans Devanagari keeps Hindi/Marathi contact names
     and message previews from falling back to a mismatched system font. */
  --font-sans: 'Inter', 'Noto Sans Devanagari', -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-display: 'Plus Jakarta Sans', var(--font-sans);
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  --font-size-xs: 12px;
  --font-size-sm: 14px;
  --font-size-md: 16px;
  --font-size-lg: 18px;
  --font-size-xl: 22px;
  --font-size-2xl: 28px;

  --line-height-tight: 1.25;
  --line-height-base: 1.55;

  /* ---- Elevation ------------------------------------------------------ */
  --shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.06);
  --shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-md: 0 4px 12px rgba(15, 23, 42, 0.08);
  --shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.14);

  /* ---- Focus (never remove — keyboard users depend on it) ------------- */
  --focus-ring: 0 0 0 3px rgba(37, 99, 235, 0.35);
  --focus-ring-danger: 0 0 0 3px rgba(220, 38, 38, 0.3);
  --focus-outline: 2px solid var(--color-primary);

  /* ---- Motion ---------------------------------------------------------- */
  --duration-fast: 120ms;
  --duration-base: 200ms;
  --duration-slow: 300ms;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);

  /* ---- Layering -------------------------------------------------------- */
  --z-sticky: 20;
  --z-drawer-scrim: 60;
  --z-drawer: 70;
  --z-modal: 100;
  --z-toast: 200;

  /* ---- Layout ---------------------------------------------------------- */
  --sidebar-width: 264px;
  /* Icon-rail width when the sidebar is collapsed: a 44px target plus gutters. */
  --sidebar-width-collapsed: 76px;
  --header-height: 64px;
  --tap-target: 44px;
  --content-max: 1280px;
  /* Padding around .app-content. Read by the inbox to size its full-height
     3-pane grid, so it must stay in sync with the breakpoint override. */
  --content-padding: var(--spacing-lg);
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--font-sans);
  font-size: var(--font-size-md);
  color: var(--color-text);
  background: var(--color-bg);
  line-height: var(--line-height-base);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Numbers in tables, counters and timers must not jitter as they change. */
  font-variant-numeric: tabular-nums;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  line-height: var(--line-height-tight);
  letter-spacing: -0.01em;
  font-weight: 700;
}

a {
  color: var(--color-primary);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

button {
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
  /* Removes the 300ms tap delay without disabling pinch-zoom. */
  touch-action: manipulation;
}

button:disabled {
  cursor: not-allowed;
}

input,
select,
textarea {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

ul {
  list-style: none;
}

img,
svg {
  max-width: 100%;
}

svg {
  flex-shrink: 0;
}

/* One consistent, always-visible keyboard focus treatment. Pointer users
   never see it; keyboard users always do. */

:focus-visible {
  outline: var(--focus-outline);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

:focus:not(:focus-visible) {
  outline: none;
}

/* Long URLs, webhook secrets, template IDs and pasted customer content must
   reflow instead of blowing out the layout. */

.wrap-anywhere {
  overflow-wrap: anywhere;
}

::selection {
  background: var(--color-primary-soft-border);
  color: var(--color-text);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Page-level headings. Kept deliberately large: the primary audience is
   non-technical, so scanability beats density. */

.page-title {
  font-family: var(--font-display);
  font-size: var(--font-size-xl);
  font-weight: 700;
  color: var(--color-text);
}

.page-subtitle {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin-top: 4px;
  max-width: 68ch;
}

.section-title {
  font-family: var(--font-display);
  font-size: var(--font-size-lg);
  font-weight: 700;
}

.stat-value {
  font-family: var(--font-display);
  font-size: var(--font-size-2xl);
  font-weight: 700;
  line-height: 1.1;
}

.stat-label {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin-top: 4px;
}

/* A one-line, plain-English explanation under a number or field. This is the
   main tool for making the product legible to non-technical owners — use it
   liberally instead of assuming the label is self-evident. */

.hint {
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  line-height: 1.5;
}

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

.text-subtle { color: var(--color-text-subtle); }

.text-danger { color: var(--color-danger); }

.text-success { color: var(--color-success); }

.text-sm { font-size: var(--font-size-sm); }

.text-xs { font-size: var(--font-size-xs); }

.text-strong { font-weight: 600; }

.text-mono { font-family: var(--font-mono); font-size: var(--font-size-sm); }

/* Visually hidden but available to screen readers. */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.flex { display: flex; }

.flex-center { display: flex; align-items: center; justify-content: center; }

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

.grid { display: grid; }

.gap-sm { gap: var(--spacing-sm); }

.gap-md { gap: var(--spacing-md); }

.gap-lg { gap: var(--spacing-lg); }

.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: var(--spacing-lg);
  gap: var(--spacing-md);
}

.page-actions { display: flex; gap: var(--spacing-sm); }

.page-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-md);
  min-height: 60vh;
  text-align: center;
}

/* =========================================================================
   App shell: sidebar + header + content.

   Desktop  (>=1024px): sidebar is a permanent column.
   Below that          : sidebar becomes an off-canvas drawer with a scrim,
                         opened from the hamburger in the header.
   Breakpoint overrides live in responsive.css.
   ========================================================================= */

.app-shell {
  display: flex;
  min-height: 100vh;
  min-height: 100dvh;
}

/* ---- Sidebar ------------------------------------------------------------ */

.app-sidebar {
  width: var(--sidebar-width);
  /* Width, not transform: the sidebar is in normal flow and the main column
     must reflow with it. This fires once per click, not per frame of a
     gesture, so the reflow cost is acceptable here. */
  transition: width var(--duration-base) var(--ease-standard);
  background: var(--color-sidebar-bg);
  color: var(--color-sidebar-text);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-md);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  position: sticky;
  top: 0;
  background: var(--color-sidebar-bg);
  z-index: 1;
}

/* The mark carries its own brand gradient (see BrandMark.jsx) — it must not
   inherit a theme colour, which is why no `color` is set here. */

.brand-mark {
  display: inline-flex;
  flex-shrink: 0;
}

/* On a dark surface, lift the floor of the mark's gradient.
   The logo's own dark end (#00603a) is only 2.23:1 against the navy sidebar,
   so the speech-bubble tail and the bottom of the ring sink into the
   background and the mark reads as clipped. #089a49 is 4.68:1 there, well
   past the 3:1 minimum for non-text.

   This works by re-declaring the brand tokens on the element: the SVG's
   gradient stops are `var(--color-brand-green-*)`, so they pick this up and
   there is still only ONE copy of the artwork. Put this class on the mark
   anywhere it sits on navy. */

.brand-mark-on-dark {
  --color-brand-green-dark: #089a49;
  --color-brand-green: #17c95c;
}

.sidebar-brand-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.3;
}

.sidebar-brand-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--font-size-md);
  color: var(--color-sidebar-text-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-brand-sub {
  font-size: var(--font-size-xs);
  color: var(--color-sidebar-heading);
}

/* Drawer-only control; hidden on desktop where the sidebar is permanent. */

.sidebar-close {
  display: none;
  margin-left: auto;
  background: none;
  border: none;
  color: var(--color-sidebar-text);
  width: var(--tap-target);
  height: var(--tap-target);
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
}

.sidebar-close:hover { background: var(--color-sidebar-hover); }

/* Desktop collapse toggle. Hidden on mobile, where the sidebar is a drawer and
   the header hamburger already opens it. */

.sidebar-collapse-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  height: var(--tap-target);
  flex-shrink: 0;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-sidebar-text);
  cursor: pointer;
}

.sidebar-collapse-btn:hover {
  background: var(--color-sidebar-hover);
  color: var(--color-sidebar-text-strong);
}

.sidebar-collapse-btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: -2px;
}

/* ---- Collapsed (icon rail) ---------------------------------------------- */

.app-sidebar.is-collapsed { width: var(--sidebar-width-collapsed); }

.app-sidebar.is-collapsed .sidebar-brand {
  justify-content: center;
  padding: var(--spacing-md) 0;
}

/* The mark and wordmark have no room at 76px; the toggle stays so the rail can
   always be reopened from the same place it was closed. */

.app-sidebar.is-collapsed .sidebar-brand-logo,
.app-sidebar.is-collapsed .sidebar-brand-text { display: none; }

.app-sidebar.is-collapsed .sidebar-nav { padding-left: 0; padding-right: 0; }

.app-sidebar.is-collapsed .sidebar-link {
  justify-content: center;
  padding: 10px 0;
  margin: 0 auto;
  width: var(--tap-target);
}

/* Visually hidden, NOT removed — this text is the link's accessible name, so
   deleting it would leave screen reader users with an unlabelled icon. */

.app-sidebar.is-collapsed .sidebar-link-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Heading text goes, but the element stays as a rule so the grouping the
   expanded sidebar establishes is still readable as separation. */

.app-sidebar.is-collapsed .sidebar-section-title {
  font-size: 0;
  padding: 0;
  margin: 0 auto var(--spacing-sm);
  width: 28px;
  height: 1px;
  background: rgba(255, 255, 255, 0.14);
}

/* Tooltip for the collapsed rail. position: fixed so it escapes the sidebar's
   scroll container; shown on hover AND keyboard focus (see Sidebar.jsx) so it
   is never a pointer-only affordance. */

.sidebar-tip {
  position: fixed;
  left: calc(var(--sidebar-width-collapsed) + 8px);
  transform: translateY(-50%);
  z-index: var(--z-drawer);
  background: var(--gray-900);
  color: #fff;
  padding: 7px 11px;
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
  font-weight: 600;
  white-space: nowrap;
  box-shadow: var(--shadow-md);
  pointer-events: none;
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-lg);
  padding: var(--spacing-md) var(--spacing-sm) var(--spacing-xl);
}

.sidebar-section {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Section headings are the whole point of the grouping — they tell an owner
   which part of the product they are in before they read any item. */

.sidebar-section-title {
  font-size: var(--font-size-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-sidebar-heading);
  padding: 0 12px var(--spacing-xs);
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap-target);
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  color: var(--color-sidebar-text);
  font-size: var(--font-size-sm);
  font-weight: 500;
  transition: background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.sidebar-link:hover {
  background: var(--color-sidebar-hover);
  color: var(--color-sidebar-text-strong);
  text-decoration: none;
}

.sidebar-link-icon {
  color: var(--color-sidebar-heading);
  transition: color var(--duration-fast) var(--ease-standard);
}

.sidebar-link:hover .sidebar-link-icon { color: var(--color-sidebar-text-strong); }

.sidebar-link.is-active {
  background: var(--color-sidebar-active);
  color: #fff;
  font-weight: 600;
}

.sidebar-link.is-active .sidebar-link-icon { color: #fff; }

.sidebar-link-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-link:focus-visible {
  outline: 2px solid #fff;
  outline-offset: -2px;
}

/* Scrim behind the mobile drawer. */

.app-scrim {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  z-index: var(--z-drawer-scrim);
}

/* ---- Main column -------------------------------------------------------- */

.app-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.app-header {
  height: var(--header-height);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: 0 var(--spacing-md);
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}

.header-menu-btn {
  display: none;
  width: var(--tap-target);
  height: var(--tap-target);
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-text);
}

.header-menu-btn:hover { background: var(--color-surface-hover); }

.app-header-spacer { flex: 1; }

.app-header-user {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

/* Account dropdown */

.header-menu { position: relative; }

.header-avatar-btn {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  min-height: var(--tap-target);
  padding: 4px 8px 4px 4px;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  color: var(--color-text);
}

.header-avatar-btn:hover {
  background: var(--color-surface-hover);
  border-color: var(--color-border);
}

.header-avatar {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
  color: var(--color-primary-active);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: 700;
  flex-shrink: 0;
}

.header-avatar-name {
  font-size: var(--font-size-sm);
  font-weight: 600;
  max-width: 14ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.header-avatar-caret { color: var(--color-text-subtle); }

.header-dropdown {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  min-width: 240px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--spacing-xs);
  z-index: var(--z-sticky);
}

.header-dropdown-identity {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--spacing-xs);
}

.header-dropdown-name { font-weight: 600; font-size: var(--font-size-sm); }

.header-dropdown-email { color: var(--color-text-muted); font-size: var(--font-size-xs); }

.header-dropdown-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--tap-target);
  padding: 10px 12px;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-text);
  text-align: left;
}

.header-dropdown-item:hover { background: var(--color-surface-hover); }

.header-dropdown-item.is-danger { color: var(--color-danger); }

.header-dropdown-item.is-danger:hover { background: var(--color-danger-soft); }

.app-content {
  padding: var(--content-padding);
  flex: 1;
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
}

/* The inbox is a full-bleed app surface: its 3-pane grid needs every pixel of
   width, so it opts out of the reading-width cap that suits normal pages. */

.app-content:has(> .inbox) {
  max-width: none;
}

/* ---- Auth shell --------------------------------------------------------- */

.auth-shell {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-lg) var(--spacing-md);
  /* Navy ground with the logo's green lifting off the top right — the same
     direction the mark's plane travels. Brand expression, not a control, so
     green is right here where it would be wrong on a button. Nothing on this
     screen is read against the glow: the form sits on a white card. */
  background:
    radial-gradient(900px 620px at 88% -12%, rgba(34, 211, 102, 0.30), transparent 62%),
    radial-gradient(760px 520px at 6% 108%, rgba(8, 183, 79, 0.18), transparent 60%),
    linear-gradient(160deg, var(--color-brand-navy), #16294a);
}

.auth-card {
  width: 100%;
  max-width: 440px;
  padding: var(--spacing-xl);
  box-shadow: var(--shadow-lg);
}

.auth-brand {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--font-size-md);
  margin-bottom: var(--spacing-lg);
}

.auth-title { font-size: var(--font-size-xl); }

.auth-subtitle {
  color: var(--color-text-muted);
  margin-top: var(--spacing-xs);
  margin-bottom: var(--spacing-lg);
  font-size: var(--font-size-sm);
}

/* The OAuth handoff's status line. The spinner is held back for half a second
   (useOAuthContinue) because this screen usually forwards faster than that, and
   a spinner that appears and vanishes reads as a fault rather than as progress.
   It keeps its space either way, so nothing moves when it does appear. */

.auth-status {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  min-height: 22px;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

.auth-status .spinner {
  flex: none;
  visibility: hidden;
}

.auth-status.is-working .spinner { visibility: visible; }

.auth-footer {
  margin-top: var(--spacing-lg);
  padding-top: var(--spacing-md);
  border-top: 1px solid var(--color-border);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  text-align: center;
}

/* ---- Small shared blocks ------------------------------------------------ */

.detail-list { display: flex; flex-direction: column; gap: var(--spacing-sm); }

.detail-row {
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: var(--spacing-sm) 0;
  border-bottom: 1px solid var(--color-border);
}

.detail-row:last-child { border-bottom: none; }

.detail-row dt { color: var(--color-text-muted); font-size: var(--font-size-sm); }

.detail-row dd { font-weight: 600; text-align: right; overflow-wrap: anywhere; }

.dashboard-cards { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

/* App-wide alarm bar: a fault that is losing customer messages right now.
   Danger tokens, not warning — this is not "keep an eye on it", it is data
   being dropped — and it carries an icon and words as well as the colour, so
   it does not depend on colour alone to be understood. */

.app-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  margin: var(--spacing-md) var(--spacing-lg) 0;
  padding: 12px var(--spacing-md);
  background: var(--color-danger-soft);
  border: 1px solid var(--color-danger);
  /* Thicker on the leading edge only, matching .dash-warning, so the two read
     as the same kind of object at different volumes. */
  border-left-width: 3px;
  border-radius: var(--radius-md);
  color: var(--color-danger-text);
}

.app-alert > svg { flex-shrink: 0; margin-top: 2px; color: var(--color-danger); }

.app-alert-body { flex: 1; min-width: 240px; }

.app-alert-body strong { display: block; font-weight: 700; }

.app-alert-body p {
  font-size: var(--font-size-sm);
  line-height: 1.55;
  margin-top: 2px;
  /* Kept to a readable measure on a wide desktop rather than running the full
     width of the shell. */
  max-width: 78ch;
  overflow-wrap: anywhere;
}

.app-alert .btn { flex-shrink: 0; }

/* Notification / sound permission banner */

.notif-nudge {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  margin: var(--spacing-md) var(--spacing-lg) 0;
  padding: 12px var(--spacing-md);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
}

.notif-nudge-text {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  flex: 1;
  min-width: 240px;
  color: var(--color-info-text);
}

.notif-nudge-actions { display: flex; gap: var(--spacing-sm); }

.form-group {
  margin-bottom: var(--spacing-md);
}

/* Always a visible label. A placeholder is not a label — it disappears the
   moment someone starts typing, which is exactly when a hesitant user needs
   it most. */

.form-label {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: 600;
  margin-bottom: 6px;
  color: var(--color-text);
}

.form-label-required::after {
  content: ' *';
  color: var(--color-danger);
  font-weight: 700;
}

.form-label-optional {
  font-weight: 400;
  color: var(--color-text-subtle);
  font-size: var(--font-size-xs);
  margin-left: 4px;
}

.form-control {
  width: 100%;
  /* 16px is deliberate: anything smaller makes iOS Safari zoom the page on
     focus, which feels like a bug to a non-technical user. */
  font-size: var(--font-size-md);
  min-height: var(--tap-target);
  padding: 11px 14px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  transition: border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.form-control::placeholder { color: var(--color-text-subtle); }

.form-control:hover:not(:disabled):not(:focus) { border-color: var(--color-text-subtle); }

.form-control:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: var(--focus-ring);
}

.form-control:disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-subtle);
  cursor: not-allowed;
}

/* Read-only is NOT disabled: the value still matters and stays readable. */

.form-control[readonly] {
  background: var(--color-surface-sunken);
  color: var(--color-text);
}

.form-control.is-invalid {
  border-color: var(--color-danger);
  background: #fffafa;
}

.form-control.is-invalid:focus { box-shadow: var(--focus-ring-danger); }

textarea.form-control {
  min-height: 96px;
  resize: vertical;
  line-height: var(--line-height-base);
}

select.form-control {
  appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}

/* Errors sit next to the field they belong to, with an icon so the message
   does not rely on red alone. */

.form-error {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  color: var(--color-danger-text);
  font-size: var(--font-size-xs);
  font-weight: 500;
  margin-top: 6px;
}

.form-helper {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  margin-top: 6px;
  line-height: 1.5;
}

/* Checkbox / radio rows get the full row as a tap target. */

.form-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-height: var(--tap-target);
  padding: 8px 0;
  cursor: pointer;
}

.form-check input[type='checkbox'],
.form-check input[type='radio'] {
  width: 20px;
  height: 20px;
  margin-top: 2px;
  accent-color: var(--color-primary);
  flex-shrink: 0;
  cursor: pointer;
}

.form-check-label { font-size: var(--font-size-sm); }

.form-fieldset {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  margin-bottom: var(--spacing-md);
}

.form-fieldset legend {
  font-size: var(--font-size-sm);
  font-weight: 700;
  padding: 0 6px;
}

/* =========================================================================
   Field rows.

   Every container that places fields side by side hits the same trap: each
   cell stacks its own label above its own control, so the moment one label
   wraps to a second line that cell's control drops below its neighbours —
   the "every second box is going down" effect.

   `align-items: end` bottom-aligns the cells, so the controls always sit on
   one line no matter how tall a label gets. Controls are the strong
   horizontal rule the eye tracks, so aligning those (rather than the labels)
   is what reads as tidy.

   Any new side-by-side field container must be added to this list.
   ========================================================================= */

.form-row,
.sources-form-row,
.sources-map-row,
.fb-field-body,
.seg-rule,
.modal-row {
  align-items: end;
}

/* Fields in a row own no bottom margin — the row's gap handles spacing. */

.form-row > .form-group,
.sources-form-row > .form-group,
.fb-field-body > .form-group,
.form-row > .tpl-field,
.sources-form-row > .tpl-field {
  margin-bottom: 0;
}

/* A control with no label above it still has to line up with ones that do,
   so it gets pushed to the bottom of the row like everything else. */

.form-row > .form-control,
.sources-map-row > .form-control,
.seg-rule > .form-control {
  align-self: end;
}

.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--spacing-md);
}

/* Inline notices inside forms */

.form-notice {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  background: var(--color-success-soft);
  color: var(--color-success-text);
  border: 1px solid #a7f3d0;
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  font-size: var(--font-size-sm);
  margin-bottom: var(--spacing-md);
}

.form-notice-info {
  background: var(--color-primary-soft);
  color: var(--color-info-text);
  border-color: var(--color-primary-soft-border);
}

.form-notice-warning {
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
  border-color: #fcd34d;
}

.otp-input {
  letter-spacing: 0.5em;
  text-align: center;
  font-size: var(--font-size-xl);
  font-weight: 600;
}

.link-button {
  background: none;
  border: none;
  color: var(--color-primary);
  font-size: var(--font-size-sm);
  font-weight: 600;
  padding: 6px 0;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.link-button:hover { color: var(--color-primary-hover); }

/* A linked Send API campaign whose template Meta has stopped. Sits beside the
   campaign name in the list rather than replacing it — the tenant needs to know
   WHICH campaign broke, not just that something did. */

.form-link-warn { margin-left: var(--spacing-xs); }

/* Every button clears the 44px minimum touch target. The audience is
   phone-first and often not tech-confident — mis-taps are expensive here. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-sm);
  min-height: var(--tap-target);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: var(--font-size-sm);
  line-height: 1.2;
  padding: 11px 18px;
  white-space: nowrap;
  transition: background var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.btn:active:not(:disabled) { transform: translateY(1px); }

/* A 55%-opacity blue still reads as a live button that has gone wrong. Solid
   variants instead drop to flat grey, which is unambiguously "not available
   yet"; outline/ghost variants just fade, since they have no fill to lose. */

.btn:disabled {
  cursor: not-allowed;
  box-shadow: none;
}

.btn-primary:disabled,
.btn-danger:disabled,
.btn-whatsapp:disabled,
.btn-secondary:disabled {
  background: var(--gray-200);
  border-color: var(--gray-200);
  color: var(--color-text-muted);
}

.btn-outline:disabled,
.btn-ghost:disabled {
  opacity: 0.5;
}

.btn:disabled:active { transform: none; }

.btn-primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
  box-shadow: var(--shadow-xs);
}

.btn-primary:hover:not(:disabled) { background: var(--color-primary-hover); }

.btn-primary:active:not(:disabled) { background: var(--color-primary-active); }

.btn-secondary {
  background: var(--color-primary-soft);
  color: var(--color-primary-active);
  border-color: var(--color-primary-soft-border);
}

.btn-secondary:hover:not(:disabled) { background: #dbeafe; }

.btn-outline {
  background: var(--color-surface);
  border-color: var(--color-border-strong);
  color: var(--color-text);
}

.btn-outline:hover:not(:disabled) {
  background: var(--color-surface-hover);
  border-color: var(--color-text-subtle);
}

.btn-ghost {
  background: transparent;
  color: var(--color-text-muted);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--color-surface-hover);
  color: var(--color-text);
}

/* Destructive actions get the danger colour AND sit apart from primary
   actions in the markup — colour alone must never be the only signal. */

.btn-danger {
  background: var(--color-danger);
  color: #fff;
}

.btn-danger:hover:not(:disabled) { background: var(--color-danger-hover); }

.btn-danger:focus-visible { box-shadow: var(--focus-ring-danger); }

/* WhatsApp-specific affirmative actions (connect number, send test). Green is
   reserved for genuine WhatsApp state so it keeps its meaning. */

.btn-whatsapp {
  background: var(--color-whatsapp-deep);
  color: #fff;
}

.btn-whatsapp:hover:not(:disabled) { background: #0d7a6d; }

/* `sm` stays visually compact but keeps a 40px hit box on touch. */

.btn-sm {
  padding: 8px 14px;
  font-size: var(--font-size-sm);
  min-height: 40px;
}

.btn-md { padding: 11px 18px; }

.btn-lg {
  padding: 14px 24px;
  font-size: var(--font-size-md);
  min-height: 52px;
}

.btn-block { width: 100%; }

.btn-spinner {
  width: 15px;
  height: 15px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  opacity: 0.8;
  animation: spin 0.6s linear infinite;
}

/* Icon-only button. Always pair with aria-label — see Button/Icon usage. */

.btn-icon {
  width: var(--tap-target);
  height: var(--tap-target);
  min-height: var(--tap-target);
  padding: 0;
  border-radius: var(--radius-sm);
}

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

/* Shared "remove this row" control for repeatable rows (segment rules, source
   field mappings, form fields). Each of these had its own ad-hoc button —
   a bare glyph here, a bordered box there, a saturated red one elsewhere. */

.btn-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text-subtle);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.btn-remove:hover {
  background: var(--color-danger-soft);
  color: var(--color-danger);
}

.btn-remove:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring-danger);
}

.btn-remove:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.btn-remove:disabled:hover {
  background: none;
  color: var(--color-text-subtle);
}

/* Reorder controls, sized to sit beside .btn-remove as one control group. */

.btn-move {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-muted);
  cursor: pointer;
}

.btn-move:hover:not(:disabled) {
  background: var(--color-surface-hover);
  color: var(--color-text);
}

.btn-move:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.btn-move:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Tables scroll inside their own container — the page body must never scroll
   sideways on a phone. */

.table-wrapper {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-sm);
}

.table th,
.table td {
  text-align: left;
  padding: 14px 16px;
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
}

.table-header th {
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: var(--color-surface-sunken);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 1;
}

.table tbody tr:last-child td { border-bottom: none; }

.table-row {
  transition: background var(--duration-fast) var(--ease-standard);
}

.table-row:hover { background: var(--color-primary-soft); }

.table-primary-cell {
  font-weight: 600;
  color: var(--color-text);
}

/* Numeric columns line up and stay aligned as values change. */

.table-numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.table-actions {
  text-align: right;
  white-space: nowrap;
}

.table-actions .btn + .btn { margin-left: var(--spacing-xs); }

.modal-overlay {
  position: fixed;
  inset: 0;
  /* Blur signals that the background is dismissible, not just decorated. */
  background: rgba(15, 23, 42, 0.5);
  backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-lg);
  z-index: var(--z-modal);
  animation: modal-fade var(--duration-base) var(--ease-out);
}

.modal-container {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  width: 100%;
  max-width: 480px;
  box-shadow: var(--shadow-lg);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  animation: modal-rise var(--duration-base) var(--ease-out);
}

.modal-lg { max-width: 720px; }

.modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: var(--spacing-lg) var(--spacing-lg) var(--spacing-md);
  flex-shrink: 0;
}

.modal-title {
  font-family: var(--font-display);
  font-size: var(--font-size-lg);
  font-weight: 700;
}

.modal-subtitle {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin-top: 4px;
}

.modal-close {
  width: var(--tap-target);
  height: var(--tap-target);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  margin: -8px -8px 0 0;
}

.modal-close:hover {
  background: var(--color-surface-hover);
  color: var(--color-text);
}

.modal-body {
  padding: 0 var(--spacing-lg) var(--spacing-lg);
  overflow-y: auto;
  overscroll-behavior: contain;
  flex: 1;
}

/* Scroll shadows. Tall dialogs (source editor, form builder) previously just
   cut off at the footer with no hint that anything was below, which read as a
   broken layout. The `local` gradients mask the `scroll` ones at each end, so a
   shadow appears only on the side that actually has more content. */

.modal-body-scroll {
  background:
    linear-gradient(var(--color-surface) 30%, rgba(255, 255, 255, 0)) top / 100% 22px no-repeat local,
    linear-gradient(rgba(255, 255, 255, 0), var(--color-surface) 70%) bottom / 100% 22px no-repeat local,
    radial-gradient(farthest-side at 50% 0, rgba(15, 23, 42, 0.13), rgba(15, 23, 42, 0)) top / 100% 9px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, rgba(15, 23, 42, 0.13), rgba(15, 23, 42, 0)) bottom / 100% 9px no-repeat scroll;
}

.modal-footer {
  padding: var(--spacing-md) var(--spacing-lg);
  border-top: 1px solid var(--color-border);
  background: var(--color-surface-sunken);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  display: flex;
  justify-content: flex-end;
  gap: var(--spacing-sm);
  flex-shrink: 0;
}

.confirm-message {
  color: var(--color-text);
  font-size: var(--font-size-md);
  line-height: var(--line-height-base);
}

/* Destructive confirmations lead with a warning icon so the consequence is
   visible before the button colour is. */

.confirm-danger {
  display: flex;
  gap: var(--spacing-md);
  align-items: flex-start;
}

.confirm-danger-icon {
  color: var(--color-danger);
  flex-shrink: 0;
  margin-top: 2px;
}

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

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

/* ---- Shared dialog building blocks --------------------------------------
   Repeatable rows (segment rules, source field mappings) and the panels that
   hold them. Previously each modal rolled its own spacing and controls. */

.modal-panel {
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
}

.modal-panel + .modal-panel { margin-top: var(--spacing-md); }

.modal-row {
  display: flex;
  /* end, not center: see the field-row note in forms.css. */
  align-items: end;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.modal-row:last-child { margin-bottom: 0; }

/* Let selects/inputs in a repeatable row shrink instead of overflowing. */

.modal-row > .form-control,
.modal-row > .form-group { flex: 1; min-width: 0; margin-bottom: 0; }

.modal-row-arrow {
  color: var(--color-text-subtle);
  flex-shrink: 0;
}

/* "+ Add rule" style control: clearly secondary to the dialog's Save action. */

.modal-add-row {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  min-height: 40px;
  padding: 8px 14px;
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-primary);
  font-size: var(--font-size-sm);
  font-weight: 600;
  cursor: pointer;
}

.modal-add-row:hover {
  border-color: var(--color-primary);
  background: var(--color-primary-soft);
}

/* Result/summary line under a builder ("Matches 3 contacts right now"). */

.modal-result {
  margin-top: var(--spacing-sm);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  border-bottom: 1px solid var(--color-border);
  font-family: var(--font-display);
  font-weight: 700;
  /* Was 13px uppercase muted — too small and too low-contrast to act as a
     heading. Card titles are now real headings at readable size. */
  font-size: var(--font-size-md);
  color: var(--color-text);
}

.card-header-hint {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

.card-body { padding: var(--spacing-lg); }

.card-footer {
  padding: var(--spacing-md) var(--spacing-lg);
  border-top: 1px solid var(--color-border);
  background: var(--color-surface-sunken);
}

/* Card that is itself a link/button target. */

.card-interactive {
  display: block;
  width: 100%;
  text-align: left;
  transition: border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.card-interactive:hover {
  border-color: var(--color-primary-soft-border);
  box-shadow: var(--shadow-md);
  text-decoration: none;
}

/* Sectioned page: a titled band with its own description. */

.section {
  margin-bottom: var(--spacing-xl);
}

.section-head {
  margin-bottom: var(--spacing-md);
}

.section-head .page-subtitle { margin-top: 2px; }

/* Status pills. Every variant pairs a colour with a distinct label (and, where
   it carries real meaning, an icon) — colour alone must never be the signal. */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
  border: 1px solid transparent;
}

.badge-success {
  background: var(--color-success-soft);
  color: var(--color-success-text);
  border-color: #a7f3d0;
}

.badge-warning {
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
  border-color: #fcd34d;
}

.badge-danger {
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
  border-color: #fecaca;
}

.badge-info {
  background: var(--color-info-soft);
  color: var(--color-info-text);
  border-color: var(--color-primary-soft-border);
}

/* Neutral chip for an inactive/unknown state (not an error). Named -muted
   because that is what the ~10 existing usages already call it; it previously
   lived in contacts.css, far from the other badge variants. */

.badge-muted {
  background: var(--gray-100);
  color: var(--color-text-muted);
  border-color: var(--color-border);
}

/* Live WhatsApp state (connected, 24h window open). */

.badge-whatsapp {
  background: var(--color-whatsapp-soft);
  color: var(--color-whatsapp-text);
  border-color: #a7f3d0;
}

.badge-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

/* ---- Loading ------------------------------------------------------------ */

.loader {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  color: var(--color-text-muted);
  padding: var(--spacing-lg);
  font-size: var(--font-size-sm);
}

.page-loader {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-md);
  min-height: 100vh;
  min-height: 100dvh;
  color: var(--color-text-muted);
}

.spinner {
  width: 22px;
  height: 22px;
  border: 3px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

/* Skeleton placeholders reserve the space real content will occupy, so the
   page does not jump when data lands. */

.skeleton {
  background: linear-gradient(90deg, var(--gray-200) 25%, var(--gray-100) 37%, var(--gray-200) 63%);
  background-size: 400% 100%;
  border-radius: var(--radius-sm);
  animation: skeleton-sheen 1.4s ease infinite;
}

.skeleton-line { height: 14px; margin-bottom: 10px; }

.skeleton-line:last-child { width: 60%; margin-bottom: 0; }

.skeleton-card { height: 108px; }

@keyframes skeleton-sheen {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* ---- Errors -------------------------------------------------------------- */

.error-message {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
  border: 1px solid #fecaca;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  margin-bottom: var(--spacing-md);
  font-size: var(--font-size-sm);
}

.error-message-icon { flex-shrink: 0; margin-top: 1px; }

.error-message-text { flex: 1; overflow-wrap: anywhere; }

/* ---- Empty states -------------------------------------------------------- */

/* An empty state is a teaching moment, not a dead end: say what this screen is
   for and give the one action that fills it. */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--spacing-2xl) var(--spacing-lg);
  color: var(--color-text-muted);
}

.empty-state-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
  color: var(--color-primary);
  margin-bottom: var(--spacing-md);
}

.empty-state-title {
  font-family: var(--font-display);
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--color-text);
}

.empty-state-description {
  margin-top: 6px;
  max-width: 46ch;
  font-size: var(--font-size-sm);
  line-height: var(--line-height-base);
}

.empty-state-action {
  margin-top: var(--spacing-lg);
  display: flex;
  gap: var(--spacing-sm);
  flex-wrap: wrap;
  justify-content: center;
}

/* Home: setup checklist + stat grid. */

/* ---- Setup checklist ---------------------------------------------------- */

.setup-card {
  margin-bottom: var(--spacing-xl);
  border-color: var(--color-primary-soft-border);
  padding: var(--spacing-lg);
}

.setup-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-md);
  flex-wrap: wrap;
}

.setup-title {
  font-family: var(--font-display);
  font-size: var(--font-size-lg);
  font-weight: 700;
}

.setup-sub {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin-top: 2px;
}

.setup-count {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-primary-active);
  background: var(--color-primary-soft);
  border-radius: var(--radius-full);
  padding: 6px 12px;
  white-space: nowrap;
}

.setup-progress {
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--gray-200);
  overflow: hidden;
  margin: var(--spacing-md) 0 var(--spacing-lg);
}

.setup-progress-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  transition: width var(--duration-slow) var(--ease-out);
}

.setup-steps {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
}

.setup-step {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  padding: 14px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
}

/* Exactly one step is highlighted at a time: the next thing to do. */

.setup-step.is-next {
  background: var(--color-primary-soft);
  border-color: var(--color-primary-soft-border);
}

.setup-step-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  border: 2px solid var(--color-border-strong);
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  font-weight: 700;
}

.setup-step.is-next .setup-step-marker {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.setup-step.is-done .setup-step-marker {
  background: var(--color-success);
  border-color: var(--color-success);
  color: #fff;
}

.setup-step-body { flex: 1; min-width: 0; }

.setup-step-title {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text);
}

.setup-step.is-done .setup-step-title {
  color: var(--color-text-muted);
}

.setup-step-desc {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  margin-top: 3px;
  line-height: 1.5;
  max-width: 62ch;
}

.setup-step-cta,
.setup-step-badge { flex-shrink: 0; }

.setup-step-cta:hover { text-decoration: none; }

/* ---- Stat cards --------------------------------------------------------- */

/* 200px min lets all five cards sit on one row at desktop width and fall to
   2-up on a phone, instead of stranding a lone card on its own line. */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--spacing-md);
}

.stat-card {
  padding: var(--spacing-md);
  display: flex;
  flex-direction: column;
  gap: 2px;
  border-left: 3px solid var(--color-border-strong);
}

.stat-card-top {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-xs);
}

.stat-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  background: var(--gray-100);
  color: var(--color-text-muted);
  flex-shrink: 0;
}

.stat-card-label {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text);
}

/* Tone tints the accent bar and icon only — the number itself stays high
   contrast, and every card still carries its own text label. */

.stat-card-info { border-left-color: var(--color-primary); }

.stat-card-info .stat-card-icon { background: var(--color-primary-soft); color: var(--color-primary); }

.stat-card-warning { border-left-color: var(--color-warning); }

.stat-card-warning .stat-card-icon { background: var(--color-warning-soft); color: var(--color-warning); }

.stat-card-danger { border-left-color: var(--color-danger); }

.stat-card-danger .stat-card-icon { background: var(--color-danger-soft); color: var(--color-danger); }

.stat-card-success { border-left-color: var(--color-success); }

.stat-card-success .stat-card-icon { background: var(--color-success-soft); color: var(--color-success); }

/* ---- Number-needs-attention banner -------------------------------------- */

/* Leads the page when WhatsApp has throttled, restricted or stopped the
   number. Warning tone rather than danger: sending is held, not lost. */

.dash-warning {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  margin-bottom: var(--spacing-lg);
  border: 1px solid var(--color-warning);
  border-left-width: 3px;
  border-radius: var(--radius-md);
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
}

.dash-warning > svg { flex-shrink: 0; margin-top: 2px; color: var(--color-warning); }

.dash-warning > div { flex: 1; min-width: 0; }

.dash-warning strong { display: block; font-weight: 700; }

.dash-warning p {
  font-size: var(--font-size-sm);
  line-height: 1.55;
  margin-top: 2px;
  max-width: 72ch;
}

.dash-warning .btn { flex-shrink: 0; background: var(--color-surface); }

/* ---- Two-up row: the wide chart beside a narrow readout ----------------- */

.dash-split {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: var(--spacing-lg);
  align-items: start;
}

/* ---- Chart furniture shared by the widgets on this page ----------------- */

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-md);
  list-style: none;
}

.chart-legend li {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-sm);
  /* Never the series colour: a legend label is text and wears text ink. The
     swatch beside it is what carries the identity. */
  color: var(--color-text-muted);
}

.chart-legend b {
  font-weight: 700;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

.chart-key {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  flex-shrink: 0;
}

.chart-key-1 { background: var(--chart-1); }

.chart-key-2 { background: var(--chart-2); }

/* The table view. A tooltip may enhance a chart; it may never be the only
   way to read a value. */

.chart-table { margin-top: var(--spacing-md); }

.chart-table > summary {
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--color-primary);
  padding: var(--spacing-sm) 0;
}

.chart-table > summary:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }

.chart-table .table { font-variant-numeric: tabular-nums; }

.chart-table .table th[scope='row'] { font-weight: 500; white-space: nowrap; }

/* ---- Message activity: 14 stacked columns ------------------------------- */

.activity-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--spacing-md);
  margin-bottom: var(--spacing-lg);
}

.activity-figure { display: flex; align-items: baseline; gap: var(--spacing-sm); }

/* Proportional figures, not tabular: at this size equal-width digits make a
   number like 121 read loose. */

.activity-figure-value {
  font-family: var(--font-display);
  font-size: var(--font-size-2xl);
  font-weight: 700;
  line-height: 1;
}

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

.activity-plot {
  position: relative;
  height: 180px;
  /* The y-axis labels get a lane of their own. Sat over the plot they would
     occlude the first column the moment it grew tall. */
  --axis-gutter: 32px;
}

/* Gridlines: hairline, solid, one step off the surface. Never dashed — a
   dashed rule reads as a threshold when it is only a grid. */

.activity-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.activity-grid span {
  position: absolute;
  left: var(--axis-gutter);
  right: 0;
  bottom: var(--tick);
  border-top: 1px solid var(--chart-grid);
}

.activity-grid i {
  position: absolute;
  right: calc(100% + 8px);
  top: -0.65em;
  font-style: normal;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-subtle);
  white-space: nowrap;
}

.activity-cols {
  position: absolute;
  inset: 0 0 0 var(--axis-gutter);
  display: flex;
  align-items: flex-end;
  gap: 4px;
}

.activity-col {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  display: flex;
  align-items: flex-end;
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--radius-xs);
  cursor: pointer;
}

.activity-col:hover,
.activity-col:focus-visible { background: var(--color-surface-hover); }

.activity-col:focus-visible { outline: var(--focus-outline); outline-offset: 1px; }

.activity-stack {
  display: flex;
  flex-direction: column;
  width: 100%;
  /* An empty day still shows a hairline on the baseline, so "nothing happened"
     and "the chart is broken" do not look the same. */
  min-height: 2px;
  background: var(--color-border);
  border-radius: 0 0 2px 2px;
  gap: 2px;                 /* the surface gap that separates the two series */
}

.activity-seg { display: block; min-height: 3px; }

/* Rounded at the data end, square at the baseline. */

.activity-seg:first-child { border-radius: var(--radius-xs) var(--radius-xs) 0 0; }

.activity-seg-1 { background: var(--chart-1); }

.activity-seg-2 { background: var(--chart-2); }

.activity-tip {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-sticky);
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: max-content;
  max-width: 200px;
  padding: var(--spacing-sm) 10px;
  border-radius: var(--radius-sm);
  background: var(--gray-900);
  color: #fff;
  font-size: var(--font-size-xs);
  line-height: 1.45;
  text-align: left;
  box-shadow: var(--shadow-md);
  pointer-events: none;
}

.activity-tip b { font-weight: 700; }

.activity-tip span { display: inline-flex; align-items: center; gap: 6px; }

.activity-tip .chart-key { width: 8px; height: 8px; }

/* Keep the first and last tooltips inside the card, which clips its overflow. */

.activity-col:first-child .activity-tip { left: 0; transform: none; }

.activity-col:last-child .activity-tip { left: auto; right: 0; transform: none; }

.activity-axis {
  display: flex;
  gap: 4px;
  margin-top: var(--spacing-sm);
  /* Matches the plot's gutter so each label stays under its own column. */
  padding-left: 32px;
}

.activity-axis span {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-subtle);
}

.activity-axis span.is-today {
  color: var(--color-text);
  font-weight: 600;
  font-variant-numeric: normal;
}

.activity-range {
  margin-top: var(--spacing-sm);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

/* ---- Delivery breakdown -------------------------------------------------- */

.meter {
  display: flex;
  gap: 2px;                 /* surface gap, not a border, separates segments */
  height: 12px;
  border-radius: var(--radius-full);
  overflow: hidden;
  background: var(--color-border);
}

.meter-seg { display: block; min-width: 3px; }

.meter-seg-success { background: var(--color-success); }

.meter-seg-neutral { background: var(--gray-400); }

.meter-seg-danger { background: var(--color-danger); }

.delivery-rows {
  list-style: none;
  margin-top: var(--spacing-md);
  display: flex;
  flex-direction: column;
}

.delivery-row {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  padding: 10px 0;
  border-bottom: 1px solid var(--color-border);
}

.delivery-row:last-child { border-bottom: 0; }

/* Colour is never the only signal: every row also carries an icon and a word. */

.delivery-row > svg { flex-shrink: 0; margin-top: 1px; }

.delivery-row-success > svg { color: var(--color-success); }

.delivery-row-neutral > svg { color: var(--color-text-subtle); }

.delivery-row-danger > svg { color: var(--color-danger); }

.delivery-row-label {
  flex: 1;
  min-width: 0;
  font-size: var(--font-size-sm);
}

.delivery-row-label em {
  display: block;
  font-style: normal;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: 1.5;
  margin-top: 2px;
}

.delivery-row-value {
  flex-shrink: 0;
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.delivery-row-value small {
  font-weight: 500;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

.delivery-read {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-md);
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
  font-size: var(--font-size-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
}

.delivery-read > svg { flex-shrink: 0; margin-top: 2px; color: var(--color-success); }

.delivery-read strong { color: var(--color-text); }

/* ---- Module cards -------------------------------------------------------- */

/* 320px keeps four figures on two comfortable rows; below that the grid drops
   to one column rather than squeezing a label onto three lines. */

.module-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--spacing-lg);
}

.module-card {
  display: flex;
  flex-direction: column;
  padding: var(--spacing-lg);
}

.module-card.is-alert { border-color: var(--color-warning); }

.module-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-md);
}

.module-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--color-primary-soft);
  color: var(--color-primary);
}

.module-title {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--font-size-md);
  font-weight: 700;
}

.module-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  font-weight: 600;
  white-space: nowrap;
  /* 44px tap target without pushing the header taller. */
  padding: 12px 4px;
  margin: -12px -4px;
}

.module-metrics {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spacing-sm) var(--spacing-md);
  margin-bottom: var(--spacing-md);
}

.module-metric {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
  border-left: 2px solid var(--color-border-strong);
}

.module-metric dt {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: 1.35;
}

.module-metric dd {
  font-family: var(--font-display);
  font-size: var(--font-size-xl);
  font-weight: 700;
  line-height: 1.2;
  margin-top: 2px;
}

.module-metric.is-good { border-left-color: var(--color-success); }

.module-metric.is-bad { border-left-color: var(--color-danger); }

.module-metric.is-quiet { border-left-color: var(--color-text-subtle); }

/* Label/value pairs where the value is a word or a badge, not a number. */

.module-facts { margin-bottom: var(--spacing-md); }

.module-facts > div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: 10px 0;
  border-bottom: 1px solid var(--color-border);
}

.module-facts > div:last-child { border-bottom: 0; }

.module-facts dt { font-size: var(--font-size-sm); color: var(--color-text-muted); }

.module-facts dd { font-size: var(--font-size-sm); font-weight: 600; text-align: right; }

.module-note {
  margin-top: auto;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: 1.55;
}

.module-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  margin-top: auto;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  line-height: 1.55;
}

.module-alert > svg { flex-shrink: 0; margin-top: 1px; color: var(--color-warning); }

/* ---- Responsive ---------------------------------------------------------- */

@media (max-width: 1024px) {
  .dash-split { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  .setup-step {
    flex-wrap: wrap;
    row-gap: var(--spacing-sm);
  }

  .setup-step-cta { width: 100%; }

  .dash-warning { flex-wrap: wrap; }
  .dash-warning .btn { width: 100%; }

  /* Fourteen labels will not fit on a phone; every other one still reads as a
     scale, and the tooltip and the table carry the exact days. "Today" is
     wider than its slot, so its neighbour goes too — otherwise the two words
     sit on top of each other. */
  .activity-axis span:nth-child(even):not(.is-today),
  .activity-axis span:nth-last-child(2) { visibility: hidden; }

  .module-grid { grid-template-columns: 1fr; }
}

/* Phase 4 (Team) UI: presence, notifications nudge, conversation filters,
   assignment, and canned messages. Uses the shared CSS variables. */

/* .text-muted is a shared utility — see typography.css. */

/* --- Presence toggle (header) --- */

.presence {
  position: relative;
}

.presence-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--tap-target);
  padding: 8px 12px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  cursor: pointer;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--color-text);
}

.presence-trigger:hover {
  background: var(--color-surface-hover);
}

.presence-caret {
  color: var(--color-text-subtle);
}

.presence-dot {
  width: 9px;
  height: 9px;
  border-radius: var(--radius-full);
  background: var(--color-text-muted);
  flex: 0 0 auto;
}

.presence-dot-online {
  background: var(--color-success);
}

.presence-dot-away {
  background: var(--color-warning);
}

.presence-dot-offline {
  background: var(--color-text-muted);
}

.presence-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 180px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 4px;
  z-index: 30;
}

.presence-menu-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  width: 100%;
  min-height: var(--tap-target);
  padding: 10px 12px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--color-text);
  text-align: left;
}

.presence-menu-item:hover {
  background: var(--color-surface-hover);
}

.presence-menu-item.is-current {
  font-weight: 600;
}

/* Notification nudge banner styles live in layout.css. */

/* --- Conversation filters + assignee tag --- */

.conv-filters {
  display: flex;
  gap: var(--spacing-xs);
  padding: 0 var(--spacing-md) var(--spacing-sm);
}

.conv-filter {
  flex: 1;
  padding: 6px 8px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.conv-filter.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}

.conv-assignee {
  display: inline-block;
  margin-top: 2px;
  font-size: 11px;
  color: var(--color-text-muted);
}

.conv-assignee.is-unassigned {
  color: var(--color-warning);
}

/* --- Reassign control (contact panel) --- */

.reassign-current {
  font-size: var(--font-size-sm);
  font-weight: 600;
  margin-bottom: var(--spacing-sm);
}

.reassign-select {
  width: 100%;
}

/* --- Editable name row (Settings → General) ---
   Field and Save on one line, the button bottom-aligned with the input rather
   than with the label above it. Below 480px they stack, because a 40px-wide
   input beside a button is not a form anyone can type in. */

.name-field {
  display: flex;
  align-items: flex-end;
  gap: var(--spacing-sm);
}

.name-field > .form-group {
  flex: 1 1 auto;
  min-width: 0;
  /* The row owns the spacing under it; the field's own margin would push the
     button out of line with the input it belongs to. */
  margin-bottom: 0;
}

.name-field > .btn {
  flex: 0 0 auto;
  margin-bottom: 2px;
}

/* Reserves its line whether or not there is anything to say, so confirming a
   save never nudges the rest of the card downwards. */

.name-field__status {
  min-height: 1.25rem;
  margin: var(--spacing-2xs) 0 var(--spacing-md);
  font-size: var(--font-size-sm);
  color: var(--color-success);
}

@media (max-width: 480px) {
  .name-field {
    flex-direction: column;
    align-items: stretch;
  }

  .name-field > .btn {
    margin-bottom: 0;
  }
}

/* --- Settings toggles --- */

.settings-toggle {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: 6px 0;
  font-size: var(--font-size-sm);
  cursor: pointer;
}

/* --- Composer takeover / claim states --- */

.composer-locked {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  border-top: 1px solid var(--color-border);
  background: var(--color-bg);
  font-size: var(--font-size-sm);
}

.composer-locked-text {
  font-weight: 600;
  color: var(--color-text);
}

.composer-locked-hint {
  color: var(--color-text-muted);
}

.composer-hint {
  padding: 4px var(--spacing-md);
  font-size: 12px;
  color: var(--color-text-muted);
}

/* --- Canned message picker (composer) --- */

.canned-picker {
  position: relative;
  display: inline-flex;
}

.canned-pop {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  width: 320px;
  max-width: 80vw;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--spacing-sm);
  z-index: 30;
}

.canned-search {
  margin-bottom: var(--spacing-sm);
}

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

.canned-empty {
  padding: var(--spacing-md);
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

.canned-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: left;
}

.canned-item:hover {
  background: var(--color-bg);
}

.canned-item-title {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text);
}

.canned-item-shortcut {
  font-weight: 400;
  font-size: 11px;
  color: var(--color-text-muted);
  background: var(--color-bg);
  padding: 1px 6px;
  border-radius: var(--radius-sm);
}

.canned-item-body {
  font-size: 12px;
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Phase 5: labels, priority and the CRM overview dashboard. */

/* --- Labels --- */

.label-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
  margin-top: var(--spacing-xs);
}

.label-chip {
  --label-color: var(--color-text-muted);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: var(--radius-full);
  font-size: 12px;
  font-weight: 600;
  color: var(--label-color);
  background: color-mix(in srgb, var(--label-color) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--label-color) 35%, transparent);
}

.label-dot {
  --label-color: var(--color-text-muted);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--label-color);
  flex: 0 0 auto;
}

.label-chip-count {
  margin-left: 4px;
  padding-left: 6px;
  border-left: 1px solid color-mix(in srgb, var(--label-color) 35%, transparent);
  opacity: 0.85;
}

/* Label picker inside the ContactPanel */

.label-picker {
  margin-top: var(--spacing-sm);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.label-option {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--font-size-sm);
  cursor: pointer;
}

/* Colour swatches in the label form */

.color-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
}

.color-swatch {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
}

.color-swatch.is-selected {
  border-color: var(--color-text);
  box-shadow: 0 0 0 2px var(--color-surface), 0 0 0 4px var(--color-text);
}

/* --- Priority --- */

.priority-chip {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-full);
  font-size: 12px;
  font-weight: 600;
  border: 1px solid transparent;
}

.priority-chip-sm {
  padding: 1px 7px;
  font-size: 11px;
}

.priority-low { background: #f3f4f6; color: #4b5563; border-color: #e5e7eb; }

.priority-normal { background: #dbeafe; color: #1e40af; border-color: #bfdbfe; }

.priority-high { background: #fef3c7; color: #92400e; border-color: #fde68a; }

.priority-urgent { background: #fee2e2; color: #991b1b; border-color: #fecaca; }

.priority-select { max-width: 100%; }

/* --- Conversation-list tag filter row --- */

.conv-tag-filters {
  display: flex;
  gap: var(--spacing-sm);
  padding: 0 var(--spacing-md) var(--spacing-sm);
}

.conv-tag-filter {
  flex: 1 1 0;
  min-width: 0;
  font-size: var(--font-size-sm);
  padding: 5px 8px;
}

/* --- Conversation-list row tags --- */

.conv-tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 4px;
}

.conv-label-dot {
  --label-color: var(--color-text-muted);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--label-color);
}

/* --- CRM overview dashboard (stat cards live in dashboard.css) --- */

.crm-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spacing-lg);
  margin-bottom: var(--spacing-lg);
}

.crm-bar-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.crm-bar-row:last-child { margin-bottom: 0; }

.crm-agent-name {
  flex: 0 0 120px;
  font-size: var(--font-size-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.crm-bar-track {
  flex: 1 1 auto;
  height: 10px;
  background: var(--color-bg);
  border-radius: var(--radius-full);
  overflow: hidden;
}

.crm-bar-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  min-width: 2px;
  transition: width 0.2s ease;
}

.crm-bar-fill-agent { background: var(--color-primary); }

.crm-bar-fill.priority-low { background: #9ca3af; }

.crm-bar-fill.priority-normal { background: #3b82f6; }

.crm-bar-fill.priority-high { background: #f59e0b; }

.crm-bar-fill.priority-urgent { background: #ef4444; }

.crm-bar-count {
  flex: 0 0 auto;
  min-width: 24px;
  text-align: right;
  font-weight: 600;
  font-size: var(--font-size-sm);
}

@media (max-width: 768px) {
  .crm-columns { grid-template-columns: 1fr; }
}

/* Contacts (Phase 6) — list toolbar, marketing consent switch, eligibility. */

.contacts-toolbar {
  display: flex;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-md);
  flex-wrap: wrap;
}

.contacts-toolbar .form-control {
  margin: 0;
}

.contacts-toolbar input[type='search'] {
  flex: 1 1 260px;
}

.contacts-toolbar select {
  flex: 0 0 auto;
  min-width: 180px;
}

.contacts-loadmore {
  display: flex;
  justify-content: center;
  margin-top: var(--spacing-md);
}

/* Name + phone stacked in a table cell. */

.contact-cell {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.contact-cell-name {
  font-weight: 600;
  color: var(--color-text);
}

.contact-cell-phone {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* .badge-muted is a shared badge variant — see badges.css. */

/* List + segment chips in the contacts table (wraps for multiple memberships). */

.contact-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  max-width: 260px;
}

/* --- Marketing opt-in switch --- */

.optin {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
}

.optin-compact {
  gap: 0;
}

.switch {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 22px;
  border-radius: var(--radius-full);
  border: none;
  background: var(--color-border);
  cursor: pointer;
  transition: background 0.15s ease;
  padding: 0;
}

.switch-on {
  background: var(--color-success);
}

.switch:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-full);
  background: #fff;
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease;
}

.switch-on .switch-knob {
  transform: translateX(18px);
}

.optin-label {
  display: flex;
  flex-direction: column;
  font-size: var(--font-size-sm);
  color: var(--color-text);
}

.optin-note {
  color: var(--color-text-muted);
}

.optin-error {
  color: var(--color-danger);
}

/* Eligibility chip sits to the right of the marketing section title (modifier
   only — the base .contact-section-title styling lives in inbox.css). */

.contact-section-title-inline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
}

/* Row actions stay on one line, right-aligned. */

.table-actions {
  display: flex;
  gap: 4px;
  justify-content: flex-end;
}

/* --- Add / edit contact --- */

.contact-list-picker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
}

.contact-list-option {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  font-size: var(--font-size-sm);
  cursor: pointer;
}

.contact-list-option input {
  margin: 0;
}

/* --- CSV import wizard --- */

.import-hint {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-md);
}

.import-upload .form-control {
  margin-bottom: var(--spacing-sm);
}

/* The sample file sits below the picker, not above it: someone with a file
   ready should not have to read past an offer they do not need. */

.import-sample-file {
  margin-top: var(--spacing-md);
  padding-top: var(--spacing-md);
  border-top: 1px solid var(--color-border);
}

/* The sample link is a <a> wearing .btn — the global anchor hover underline
   would otherwise strike through a button face. */

.import-sample-file .btn:hover {
  text-decoration: none;
}

.import-sample-file .import-hint {
  margin-top: var(--spacing-sm);
  margin-bottom: 0;
}

.import-sample-file code {
  font-family: var(--font-mono, monospace);
  padding: 0 4px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-alt, var(--color-border));
}

.import-map-table td {
  vertical-align: middle;
}

.import-map-table .form-control {
  margin: 0;
  min-width: 200px;
}

/* Sample values are data, not prose — monospace stops a stray number from
   being misread as a formatted phone. */

.import-sample {
  font-family: var(--font-mono, monospace);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  word-break: break-all;
}

.import-warning {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-sm);
  color: var(--color-warning, var(--color-danger));
  margin-bottom: var(--spacing-md);
}

/* The declaration is the one thing on this step that must not be skimmed past,
   so it gets a boxed, full-width treatment rather than a bare checkbox. */

.import-declaration {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-alt, var(--color-surface));
  font-size: var(--font-size-sm);
  line-height: 1.5;
  cursor: pointer;
}

.import-declaration input {
  margin-top: 2px;
  flex: 0 0 auto;
}

.import-running {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-md);
  padding: var(--spacing-lg) 0;
}

.import-stats {
  display: flex;
  gap: var(--spacing-lg);
  margin-bottom: var(--spacing-md);
}

.import-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 84px;
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.import-stat-value {
  font-size: var(--font-size-xl, 1.5rem);
  font-weight: 700;
  color: var(--color-text);
}

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

.import-stat-bad .import-stat-value {
  color: var(--color-danger);
}

/* A locked opt-out: the contact turned marketing off themselves, so the switch
   is not merely disabled but permanently unavailable to the tenant. Uses
   not-allowed rather than the usual dimmed-disabled look so it reads as "this
   is not yours to change" instead of "try again later". */

.switch-locked {
  cursor: not-allowed;
  opacity: 1;
  background: var(--color-border);
  box-shadow: inset 0 0 0 2px var(--color-danger-soft, var(--color-border));
}

.switch-locked:disabled {
  opacity: 1;
}

/* --- Contact attribute registry (Settings) ---------------------------------
   The tenant's own contact fields. Keys are rendered as code so a tenant can
   see at a glance that the key is a machine identifier they'll paste into a
   template, distinct from the human label above it. */

.attr-key {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--font-size-sm);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-muted, var(--color-border));
  color: var(--color-text-muted);
}

.settings-intro {
  margin-bottom: var(--spacing-md);
}

.form-static {
  margin: 0;
}

/* An inline switch inside a table cell: same control, no label text beside it
   (the column heading is the label). */

.settings-toggle-inline {
  padding: 0;
  justify-content: center;
}

.attr-undeclared {
  margin-top: var(--spacing-lg);
  padding-top: var(--spacing-md);
  border-top: 1px solid var(--color-border);
}

.attr-undeclared-keys {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-sm);
}

/* Attribute values on the Contacts table + contact panel. Narrow and truncated:
   an attribute can hold anything, and one long value must not push the action
   column off the screen. */

.contact-attr-cell {
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A declared attribute in the inbox contact panel: label above its box, so the
   agent reads the human name and never has to know the underlying key. */

.cf-field {
  display: block;
  margin-bottom: var(--spacing-sm);
}

.cf-field-label {
  display: block;
  margin-bottom: 4px;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* Attribute inputs on the add/edit contact dialog, two per row on a wide
   dialog so a tenant with several attributes doesn't get an endless column. */

.contact-attr-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 0 var(--spacing-md);
}

/* Templates: the catalogue screen, the composer picker modal, the template
   message tag, and the Phase 7.1 builder (steps, editors, live preview). */

/* --- Status / category badges (shared by the screen + picker) --- */

.tpl-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}

.tpl-status.is-approved { background: #dcfce7; color: #166534; }

.tpl-status.is-pending { background: #fef3c7; color: #92400e; }

.tpl-status.is-rejected { background: #fee2e2; color: #991b1b; }

.tpl-status.is-paused { background: #e5e7eb; color: #374151; }

.tpl-cat.is-marketing { background: #ede9fe; color: #5b21b6; }

.tpl-cat.is-utility { background: #e0f2fe; color: #075985; }

.tpl-cat.is-auth { background: #f1f5f9; color: #334155; }

.tpl-name {
  font-weight: 600;
  color: var(--color-text);
}

.tpl-preview-text {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin-top: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tpl-named-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: var(--radius-full);
  font-size: 11px;
  font-weight: 600;
  background: #f1f5f9;
  color: #475569;
}

.tpl-load-more {
  display: flex;
  justify-content: center;
  margin-top: var(--spacing-md);
}

/* --- Composer picker modal --- */

.tpl-picker-list {
  margin-top: var(--spacing-sm);
  max-height: 340px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
}

.tpl-picker-empty {
  padding: var(--spacing-md);
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

.tpl-picker-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  text-align: left;
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  cursor: pointer;
}

.tpl-picker-item:hover:not(:disabled) { border-color: var(--color-primary); }

.tpl-picker-item:disabled { opacity: 0.6; cursor: not-allowed; }

.tpl-picker-item-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.tpl-picker-lang {
  margin-left: auto;
  font-size: 12px;
  color: var(--color-text-muted);
}

.tpl-picker-block {
  font-size: 12px;
  color: var(--color-danger);
}

/* --- Fill-variables step --- */

.tpl-fill { display: flex; flex-direction: column; gap: var(--spacing-md); }

.tpl-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: var(--spacing-md);
}

/* Matches .form-label exactly — these are field labels like any other, and
   having them muted+lighter made the broadcast/template dialogs look like a
   different product to the source/form dialogs. */

.tpl-field-label {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text);
  margin-bottom: 6px;
}

.tpl-preview-box {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: #f0f7f4;
  padding: var(--spacing-md);
}

.tpl-preview-title {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-sm);
}

.tpl-preview-header { font-weight: 600; margin: 0 0 var(--spacing-xs); white-space: pre-wrap; }

.tpl-preview-body { margin: 0; white-space: pre-wrap; }

.tpl-preview-footer {
  margin: var(--spacing-xs) 0 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  white-space: pre-wrap;
}

/* --- Closed-window composer: template CTA sits under the notice --- */

.composer-closed {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-sm);
}

/* --- Template message tag on the bubble --- */

.msg-template-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--color-info-text);
  background: var(--color-primary-soft);
  border-radius: var(--radius-full);
  padding: 1px 8px;
  margin-bottom: 4px;
}

/* ==========================================================================
   Phase 7.1 — the template builder
   ========================================================================== */

.page-header-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  flex-wrap: wrap;
}

/* --- List additions: row actions, rejection reason, post-submit note --- */

.tpl-row-actions {
  white-space: nowrap;
  text-align: right;
}

.tpl-row-actions .btn { margin-left: var(--spacing-2xs); }

/* The reason WhatsApp gave, on the row itself — a red chip alone tells the
   tenant nothing they can act on. */

.tpl-reject-reason {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: 6px;
  padding: 6px var(--spacing-sm);
  border-radius: var(--radius-sm);
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
  font-size: var(--font-size-xs);
  line-height: 1.5;
  max-width: 46ch;
}

.tpl-reject-reason svg { flex-shrink: 0; margin-top: 1px; }

.tpl-flash {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  margin-bottom: var(--spacing-md);
  border: 1px solid var(--color-success-soft);
  border-radius: var(--radius-md);
  background: var(--color-success-soft);
  color: var(--color-success-text);
}

.tpl-flash p { margin: 0; flex: 1; }

.tpl-flash-close {
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  padding: 4px;
  border-radius: var(--radius-xs);
  display: inline-flex;
}

.tpl-flash-close:hover { background: rgba(0, 0, 0, 0.06); }

/* --- Step rail --- */

.tpl-steps {
  display: flex;
  gap: var(--spacing-xs);
  list-style: none;
  margin: 0 0 var(--spacing-md);
  padding: 0;
  flex-wrap: wrap;
}

.tpl-steps-item button {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  font-weight: 600;
  cursor: pointer;
}

.tpl-steps-item button:disabled { cursor: default; }

.tpl-steps-item.is-current button {
  border-color: var(--color-primary);
  color: var(--color-primary);
  background: var(--color-primary-soft);
}

.tpl-steps-item.is-done button { color: var(--color-success-text); cursor: pointer; }

.tpl-steps-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-full);
  background: var(--color-surface-hover);
  font-size: var(--font-size-xs);
}

.tpl-steps-item.is-current .tpl-steps-num { background: var(--color-primary); color: var(--color-on-primary); }

.tpl-steps-item.is-done .tpl-steps-num { background: var(--color-success-soft); color: var(--color-success-text); }

/* --- Two-column layout: form left, live preview pinned right --- */

.tpl-builder-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--spacing-lg);
  align-items: start;
}

.tpl-builder-form { padding: var(--spacing-lg); }

.tpl-builder-preview { position: sticky; top: var(--spacing-md); }

.tpl-builder-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-lg);
  padding-top: var(--spacing-md);
  border-top: 1px solid var(--color-border);
}

.tpl-step { display: flex; flex-direction: column; gap: var(--spacing-xl); }

.tpl-section { display: flex; flex-direction: column; gap: var(--spacing-sm); }

.tpl-section-head { margin-bottom: var(--spacing-2xs); }

.tpl-section-title {
  font-size: var(--font-size-md);
  font-weight: 600;
  color: var(--color-text);
  margin: 0;
}

.tpl-section-hint {
  margin: 4px 0 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
}

.tpl-optional {
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--color-text-subtle);
  margin-left: 6px;
}

.tpl-note {
  margin: 0;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  line-height: 1.5;
}

.tpl-counter {
  align-self: flex-end;
  font-size: var(--font-size-xs);
  color: var(--color-text-subtle);
}

.tpl-field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--spacing-md);
}

.tpl-field-hint {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: 1.5;
}

.tpl-inline-field { display: flex; align-items: center; gap: var(--spacing-sm); }

.tpl-inline-field .form-control { max-width: 110px; }

/* --- Purpose cards: the decision that drives everything downstream --- */

.tpl-category-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--spacing-sm);
}

.tpl-category,
.tpl-choice {
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: left;
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  cursor: pointer;
}

.tpl-category:hover:not(:disabled),
.tpl-choice:hover:not(:disabled) { border-color: var(--color-border-strong); }

.tpl-category.is-selected,
.tpl-choice.is-selected {
  border-color: var(--color-primary);
  background: var(--color-primary-soft);
  box-shadow: inset 0 0 0 1px var(--color-primary);
}

.tpl-category:disabled,
.tpl-choice:disabled { opacity: 0.6; cursor: not-allowed; }

/* WhatsApp's own word for the category leads; our plain-English gloss sits
   under it in the muted label role. */

.tpl-category-name,
.tpl-choice-label { font-weight: 600; color: var(--color-text); }

.tpl-category-name { font-size: var(--font-size-md); }

.tpl-category-label {
  font-weight: 600;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  margin-bottom: 2px;
}

.tpl-category-hint,
.tpl-choice-hint {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
}

.tpl-category-detail {
  font-size: var(--font-size-xs);
  color: var(--color-text-subtle);
  line-height: 1.5;
  margin-top: 2px;
}

.tpl-choice-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--spacing-xs);
}

.tpl-choice { padding: var(--spacing-sm) var(--spacing-md); }

/* --- Body editor --- */

.tpl-toolbar {
  display: flex;
  align-items: center;
  gap: var(--spacing-2xs);
  flex-wrap: wrap;
}

.tpl-tool {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 32px;
  height: 32px;
  padding: 0 var(--spacing-sm);
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  font-size: var(--font-size-sm);
}

.tpl-tool:hover { background: var(--color-surface-hover); }

.tpl-tool-bold { font-weight: 700; }

.tpl-tool-italic { font-style: italic; }

.tpl-tool-strike { text-decoration: line-through; }

.tpl-tool-var { margin-left: auto; font-weight: 600; color: var(--color-primary); }

.tpl-textarea {
  min-height: 160px;
  resize: vertical;
  line-height: 1.6;
  font-family: inherit;
}

.tpl-footer-group { margin-top: var(--spacing-md); }

.tpl-format-toggle {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.tpl-radio { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }

/* --- Example values: the field that decides whether a template gets approved --- */

.tpl-samples {
  margin-top: var(--spacing-sm);
  padding: var(--spacing-md);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
}

.tpl-samples-title {
  margin: 0 0 var(--spacing-sm);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text);
}

.tpl-samples-hint {
  display: block;
  font-weight: 400;
  color: var(--color-text-muted);
  margin-top: 2px;
}

.tpl-samples-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--spacing-sm);
}

.tpl-sample { display: flex; flex-direction: column; gap: 4px; }

.tpl-sample-token {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--font-size-xs);
  color: var(--color-primary);
  font-weight: 600;
}

/* --- Media upload --- */

.tpl-upload {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-sm);
  padding: var(--spacing-md);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
}

.tpl-upload-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

.tpl-upload-ok {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-sm);
  color: var(--color-success-text);
  font-weight: 600;
}

.tpl-upload-hint { font-size: var(--font-size-sm); color: var(--color-text-muted); }

.tpl-upload-error { margin: 0; font-size: var(--font-size-sm); color: var(--color-danger-text); }

/* --- Buttons editor --- */

.tpl-button-row {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  background: var(--color-surface);
}

.tpl-button-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.tpl-button-type { font-weight: 600; font-size: var(--font-size-sm); }

.tpl-button-actions { display: flex; gap: var(--spacing-2xs); }

.tpl-button-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--spacing-md);
}

.tpl-button-fields .tpl-field { margin-bottom: 0; }

.tpl-add-menu {
  display: grid;
  gap: var(--spacing-xs);
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
}

.tpl-add-option {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  cursor: pointer;
}

.tpl-add-option:hover:not(:disabled) { border-color: var(--color-primary); }

.tpl-add-option:disabled { opacity: 0.55; cursor: not-allowed; }

.tpl-add-option-label { font-weight: 600; }

.tpl-add-option-hint { font-size: var(--font-size-xs); color: var(--color-text-muted); }

.tpl-add-cancel {
  border: 0;
  background: none;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  cursor: pointer;
  padding: var(--spacing-xs);
  justify-self: start;
}

/* --- Authentication options --- */

.tpl-switch {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  cursor: pointer;
}

.tpl-switch input { margin-top: 3px; }

.tpl-switch-label { display: block; font-weight: 600; }

.tpl-switch-hint {
  display: block;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.tpl-auth-app {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  padding: var(--spacing-md);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
}

/* --- Findings: errors block, warnings only advise --- */

.tpl-findings {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  background: var(--color-surface);
}

.tpl-findings-title {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 var(--spacing-sm);
  font-weight: 600;
}

.tpl-findings-list { margin: 0; padding-left: var(--spacing-lg); display: grid; gap: 6px; }

.tpl-findings-list li { line-height: 1.55; }

.tpl-findings-list li.is-error { color: var(--color-danger-text); }

.tpl-findings-list li.is-warning { color: var(--color-warning-text); }

.tpl-finding {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  padding: var(--spacing-md);
  border-radius: var(--radius-md);
}

.tpl-finding.is-ok { background: var(--color-success-soft); color: var(--color-success-text); }

.tpl-finding-title { margin: 0; font-weight: 600; }

.tpl-finding-body { margin: 2px 0 0; font-size: var(--font-size-sm); }

.tpl-step-errors {
  margin: var(--spacing-md) 0 0;
  padding: var(--spacing-sm) var(--spacing-md) var(--spacing-sm) var(--spacing-xl);
  border-radius: var(--radius-md);
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
  display: grid;
  gap: 4px;
}

.tpl-locked {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-md);
  margin-bottom: var(--spacing-md);
  border-radius: var(--radius-md);
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
}

.tpl-locked p { margin: 0; }

.tpl-summary { margin: 0; display: grid; gap: 6px; }

.tpl-summary-row {
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: 6px 0;
  border-bottom: 1px solid var(--color-border);
}

.tpl-summary-row dt { color: var(--color-text-muted); }

.tpl-summary-row dd { margin: 0; font-weight: 600; text-align: right; }

/* --- The live WhatsApp bubble --- */

.tpl-preview-canvas {
  padding: var(--spacing-md);
  border-radius: var(--radius-md);
  /* WhatsApp's own chat ground, so the bubble reads the way it will on a
     phone rather than floating on our app surface. */
  background: #e5ded8;
  min-height: 180px;
  display: flex;
  align-items: flex-start;
}

.tpl-preview-empty {
  margin: auto;
  color: #6b6259;
  font-size: var(--font-size-sm);
  text-align: center;
}

.tpl-preview-bubble {
  position: relative;
  width: 100%;
  background: var(--color-surface);
  border-radius: 8px;
  padding: 7px 9px 6px;
  box-shadow: 0 1px 1px rgba(11, 20, 26, 0.13);
  font-size: var(--font-size-sm);
  line-height: 1.45;
  word-break: break-word;
}

.tpl-preview-bubble .tpl-preview-header { font-weight: 700; margin-bottom: 4px; }

.tpl-preview-bubble .tpl-preview-body { white-space: pre-wrap; }

.tpl-preview-bubble .tpl-preview-body code {
  font-family: var(--font-mono, ui-monospace, monospace);
  background: var(--color-surface-sunken);
  padding: 0 3px;
  border-radius: var(--radius-xs);
}

.tpl-preview-bubble .tpl-preview-footer {
  margin-top: 5px;
  font-size: var(--font-size-xs);
  color: var(--color-text-subtle);
}

.tpl-preview-time {
  text-align: right;
  font-size: 11px;
  color: var(--color-text-subtle);
  margin-top: 2px;
}

.tpl-preview-image {
  display: block;
  width: 100%;
  max-height: 180px;
  object-fit: cover;
  border-radius: 6px;
  margin-bottom: 6px;
}

.tpl-preview-placeholder,
.tpl-preview-doc {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 96px;
  margin-bottom: 6px;
  border-radius: 6px;
  background: var(--color-surface-sunken);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

.tpl-preview-doc { height: 52px; justify-content: flex-start; padding: 0 var(--spacing-sm); }

/* WhatsApp separates buttons with hairlines and centres them in brand blue. */

.tpl-preview-buttons { margin: 6px -9px -6px; }

.tpl-preview-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px;
  border-top: 1px solid var(--color-border);
  color: #00a5f4;
  font-size: var(--font-size-sm);
  font-weight: 500;
}

.tpl-preview-note {
  margin: var(--spacing-sm) 0 0;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: 1.5;
}

@media (max-width: 900px) {
  .tpl-builder-grid { grid-template-columns: minmax(0, 1fr); }
  /* Below the form the preview no longer needs to follow the scroll. */
  .tpl-builder-preview { position: static; order: -1; }
  .tpl-builder-form { padding: var(--spacing-md); }
}

/* Coexistence origin tags on a message bubble (§12 / Coexistence).
   Same shape as .msg-template-tag so a thread reads as one system; the colour
   differs only to separate "a human on their phone" from "before your time",
   and each tag carries an icon and words so colour is never load-bearing. */

.msg-origin-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  min-width: 0;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 600;
  border-radius: var(--radius-full);
  padding: 1px 8px;
  margin-bottom: 4px;
}

/* Both origin tags are NEUTRAL, and deliberately so.
   The first attempt painted "From the app" green, which failed twice: the
   outbound bubble is itself green (#d9fdd3), so a #dcfce7 pill on it was
   invisible — and green in this product means status, which provenance is not.
   A surface pill with a border reads on the green outbound bubble and the white
   inbound one alike, and the two tags are told apart by icon and words rather
   than by colour, which is what makes them survive greyscale too. */

.msg-origin-tag {
  color: var(--color-text-muted, var(--gray-600));
  /* A tinted fill, not white. White read fine on the green outbound bubble and
     vanished on the white inbound one — the tag has to sit on BOTH, and only a
     browser test over an inbound bubble caught it. Grey separates from white
     and from green alike. */
  background: var(--gray-100, #f3f4f6);
  border: 1px solid var(--color-border);
}

/* The header notification bell (Phase 7.2): trigger, unread badge and the
   dropdown panel. */

.notif {
  position: relative;
  display: flex;
  align-items: center;
}

.notif-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 40px keeps it on the 44px-ish touch target the header uses elsewhere. */
  width: 40px;
  height: 40px;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: none;
  color: var(--color-text-muted);
  cursor: pointer;
}

.notif-btn:hover { background: var(--color-surface-hover); color: var(--color-text); }

.notif-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.notif-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--radius-full);
  background: var(--color-danger);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  /* Lifts the badge off the bell so the count stays readable on hover. */
  box-shadow: 0 0 0 2px var(--color-surface);
}

.notif-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: min(380px, calc(100vw - 32px));
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 10px 30px rgba(15, 23, 42, 0.16));
  z-index: 60;
  overflow: hidden;
}

.notif-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  border-bottom: 1px solid var(--color-border);
}

.notif-panel-title { font-weight: 600; }

.notif-mark-all {
  border: 0;
  background: none;
  color: var(--color-primary);
  font-size: var(--font-size-sm);
  font-weight: 600;
  cursor: pointer;
  padding: 4px;
  border-radius: var(--radius-xs);
}

.notif-mark-all:hover { background: var(--color-primary-soft); }

.notif-list {
  max-height: min(60vh, 460px);
  overflow-y: auto;
}

.notif-empty {
  margin: 0;
  padding: var(--spacing-lg) var(--spacing-md);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  text-align: center;
  line-height: 1.55;
}

.notif-item {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  width: 100%;
  padding: var(--spacing-sm) var(--spacing-md);
  border: 0;
  border-bottom: 1px solid var(--color-border);
  background: none;
  text-align: left;
  cursor: pointer;
}

.notif-item:last-child { border-bottom: 0; }

.notif-item:hover { background: var(--color-surface-hover); }

.notif-item.is-unread { background: var(--color-primary-soft); }

.notif-item.is-unread:hover { background: var(--color-primary-soft-border); }

/* Severity colours the icon only — a wash of red across a whole row makes the
   panel unreadable when several land at once. */

.notif-item-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  color: var(--color-text-muted);
}

.notif-item.is-success .notif-item-icon { background: var(--color-success-soft); color: var(--color-success-text); }

.notif-item.is-warning .notif-item-icon { background: var(--color-warning-soft); color: var(--color-warning-text); }

.notif-item.is-danger .notif-item-icon { background: var(--color-danger-soft); color: var(--color-danger-text); }

.notif-item.is-info .notif-item-icon { background: var(--color-info-soft); color: var(--color-info-text); }

.notif-item-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.notif-item-title {
  font-weight: 600;
  font-size: var(--font-size-sm);
  color: var(--color-text);
}

.notif-item-body {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: 1.5;
}

.notif-item-time {
  font-size: var(--font-size-xs);
  color: var(--color-text-subtle);
}

.notif-item-dot {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  margin-top: 6px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
}

@media (max-width: 560px) {
  /* Anchored to the viewport rather than the button, which sits close to the
     right edge on a phone. */
  .notif-panel {
    position: fixed;
    top: 56px;
    right: var(--spacing-sm);
    left: var(--spacing-sm);
    width: auto;
  }
}

/* Broadcasts (Phase 8): list, create wizard, and delivery report. */

.bc-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--color-primary);
  font-weight: 600;
  cursor: pointer;
}

.bc-link:hover { text-decoration: underline; }

.bc-actions {
  display: flex;
  gap: var(--spacing-xs);
  justify-content: flex-end;
}

/* Status badge */

.bc-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

.bc-badge.is-draft { background: #e5e7eb; color: #374151; }

.bc-badge.is-scheduled { background: #dbeafe; color: #1e40af; }

.bc-badge.is-sending { background: #fef3c7; color: #92400e; }

.bc-badge.is-completed { background: #dcfce7; color: #166534; }

.bc-badge.is-cancelled { background: #fee2e2; color: #991b1b; }

/* Auto-paused to protect the number. Deliberately NOT the cancelled red — this
   one resumes by itself once the quality rating recovers, and colouring it like
   a cancellation would read as "your campaign is dead". */

.bc-badge.is-paused { background: #ffedd5; color: #9a3412; }

/* Create wizard */

.bc-form { display: flex; flex-direction: column; gap: var(--spacing-md); }

/* Inline rate control: "[20] messages every [30] seconds". Baseline-aligned so
   the numbers sit on the same line as the words, and wraps as a unit on narrow
   screens rather than letting one field drop out of line. */

.bc-rate {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.bc-rate-num {
  width: 84px;
  flex: 0 0 auto;
  text-align: center;
}

.bc-rate-text {
  font-size: var(--font-size-sm);
  color: var(--color-text);
  white-space: nowrap;
}

.bc-hint { font-size: var(--font-size-sm); color: var(--color-text-muted); margin-top: 4px; }

.bc-audience-note {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-sm) var(--spacing-md);
  font-size: var(--font-size-sm);
  color: var(--color-info-text);
  line-height: var(--line-height-base);
}

/* Detail / report */

.bc-detail { display: flex; flex-direction: column; gap: var(--spacing-md); }

.bc-detail-head { display: flex; align-items: center; gap: var(--spacing-sm); }

.bc-detail-tpl { color: var(--color-text-muted); font-size: var(--font-size-sm); }

/* Five outcomes, not four: skipped is its own column. auto-fit keeps them on
   one row on desktop and wraps to two on a phone rather than crushing the
   numbers. */

.bc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: var(--spacing-sm); }

.bc-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.bc-stat-n { font-size: var(--font-size-lg); font-weight: 700; }

.bc-stat-n.bc-ok { color: var(--color-success); }

.bc-stat-n.bc-bad { color: var(--color-danger); }

/* Excluded on purpose — never the danger red. A skipped recipient is the
   compliance rules working, not a failure the tenant has to chase. */

.bc-stat-n.bc-muted { color: var(--color-text-muted); }

.bc-stat-l { font-size: 11px; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.03em; }

.bc-recip-filter { display: flex; gap: var(--spacing-xs); flex-wrap: wrap; }

.bc-recip-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: var(--radius-full);
  font-size: 11px;
  font-weight: 600;
  text-transform: capitalize;
}

.bc-recip-badge.is-sent { background: #dcfce7; color: #166534; }

.bc-recip-badge.is-failed { background: #fee2e2; color: #991b1b; }

.bc-recip-badge.is-skipped { background: #fef3c7; color: #92400e; }

.bc-recip-badge.is-pending { background: #e5e7eb; color: #374151; }

.bc-recip-detail { color: var(--color-text-muted); font-size: var(--font-size-sm); }

.bc-empty { text-align: center; color: var(--color-text-muted); padding: var(--spacing-md); }

/* ---------------------------------------------------------------------------
 * Phase 8.1 — "Who should get this?"
 *
 * A real radio group styled as cards. The input stays in the DOM (only visually
 * hidden) rather than being replaced by divs with onClick, so arrow-key
 * navigation, the checked state and the group name all work without being
 * re-implemented — and :has() drives the selected styling off the actual state
 * rather than a parallel React copy of it.
 * ------------------------------------------------------------------------- */

.bc-audience {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.bc-who {
  border: 0;
  padding: 0;
  margin: 0;
}

.bc-who-grid {
  display: grid;
  /* Wraps from 3 columns to 2 to 1 on its own — no breakpoint needed. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--spacing-sm);
  margin-top: var(--spacing-xs);
}

.bc-who-card {
  position: relative;
  display: block;
  cursor: pointer;
}

/* Hidden from sight, not from the keyboard or the accessibility tree.
   Clipped to 1px rather than sized 0×0: a zero-size radio is treated as not
   rendered, and the browser then skips it during arrow-key navigation of the
   group — the selection simply refuses to move. Verified in the browser. */

.bc-who-input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  clip-path: inset(50%);
  pointer-events: none;
}

.bc-who-body {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    'icon label'
    'hint hint';
  align-items: center;
  gap: var(--spacing-xs) var(--spacing-sm);
  height: 100%;
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  transition: border-color 120ms ease, background-color 120ms ease;
}

.bc-who-card:hover .bc-who-body {
  border-color: var(--color-primary);
}

.bc-who-input:checked + .bc-who-body {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 8%, transparent);
  /* A second ring rather than a thicker border: a border-width change would
     move the card's contents by a pixel on every selection. */
  box-shadow: inset 0 0 0 1px var(--color-primary);
}

/* Keyboard focus must be visible even though the input itself is not. */

.bc-who-input:focus-visible + .bc-who-body {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

.bc-who-icon {
  grid-area: icon;
  color: var(--color-primary);
}

.bc-who-label {
  grid-area: label;
  font-weight: 600;
  font-size: var(--font-size-sm);
}

.bc-who-hint {
  grid-area: hint;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* --- CSV audience: file summary + column mapping --- */

.bc-csv {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.bc-csv-summary {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* Fixed layout so the three columns keep the widths below instead of being
   sized by their content — the target dropdown is the one column that must
   never truncate, since it is the answer the tenant is here to check. */

.bc-csv-table {
  table-layout: fixed;
  width: 100%;
}

.bc-csv-table th,
.bc-csv-table td {
  vertical-align: middle;
}

.bc-csv-header {
  width: 28%;
  font-weight: 600;
  /* A pasted header can be arbitrarily long; wrap rather than widen the table. */
  overflow-wrap: anywhere;
}

.bc-csv-sample {
  width: 20%;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.bc-csv-target {
  width: 52%;
}

/* The select is the widest thing in its cell and must be allowed to be. A
   native select silently truncates its own label, so every pixel of default
   padding here is a pixel of "Message {{coupon" instead of "{{coupon}}". */

.bc-csv-target .form-control {
  width: 100%;
  min-width: 0;
  padding-inline: var(--spacing-xs);
}

.bc-csv-declare {
  align-items: flex-start;
}

.bc-file-chosen {
  color: var(--color-text);
}

@media (prefers-reduced-motion: reduce) {
  .bc-who-body {
    transition: none;
  }
}

/* --- Retrying the failed recipients of a finished broadcast --- */

.bc-retry {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  margin-top: var(--spacing-md);
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-danger);
  border-radius: var(--radius-md);
  background: var(--color-surface-alt, var(--color-surface));
}

.bc-retry-text {
  font-size: var(--font-size-sm);
  color: var(--color-text);
  min-width: 0;
}

.bc-retry-count {
  color: var(--color-text-muted);
}

.bc-retry-error {
  margin-top: var(--spacing-sm);
  font-size: var(--font-size-sm);
  color: var(--color-danger);
}

/* Shared status vocabulary for the unified Campaigns screen (Phase 8.2 IA).
   Three lifecycles are shown in one column, so they resolve to one set of
   words server-side (App\Support\CampaignKind) and one set of colours here.
   The old per-broadcast names above stay for the delivery report, which still
   speaks the raw broadcast lifecycle. */

.bc-badge.is-live { background: var(--color-warning-soft); color: var(--color-warning-text); }

.bc-badge.is-done { background: var(--color-success-soft); color: var(--color-success-text); }

.bc-badge.is-stopped { background: var(--color-danger-soft); color: var(--color-danger-text); }

/* Lists & Segments (acquisition Phase A) + custom fields + variable chips. */

/* --- Tabs --- */

.lists-tabs {
  display: flex;
  gap: var(--spacing-xs);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--spacing-md);
}

.lists-tab {
  padding: var(--spacing-sm) var(--spacing-md);
  border: none;
  background: none;
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: var(--font-size-md);
  border-bottom: 2px solid transparent;
}

.lists-tab.is-active {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
  font-weight: 600;
}

/* --- Editor / members forms --- */

.lists-form {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

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

.lists-empty {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  padding: var(--spacing-sm) 0;
}

.lists-members { display: flex; flex-direction: column; gap: var(--spacing-lg); }

.lists-members-add { display: flex; flex-direction: column; gap: var(--spacing-sm); }

.lists-search-results,
.lists-members-list { display: flex; flex-direction: column; gap: var(--spacing-xs); }

.lists-search-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.lists-loadmore { padding-top: var(--spacing-sm); }

/* --- Segment builder --- */

.seg-builder {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}

.seg-combinator {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.seg-inline { width: auto; display: inline-block; }

.seg-rule {
  display: grid;
  grid-template-columns: 1.3fr 1fr 1.3fr auto;
  gap: var(--spacing-sm);
  /* end, not center: see the field-row note in forms.css. */
  align-items: end;
}

.seg-novalue { display: block; }

.seg-remove {
  border: none;
  background: none;
  cursor: pointer;
  font-size: var(--font-size-lg);
  color: var(--color-text-muted);
  line-height: 1;
}

.seg-remove:hover { color: var(--color-danger); }

.seg-preview {
  margin-top: var(--spacing-sm);
  font-size: var(--font-size-sm);
  color: var(--color-text);
}

/* --- Custom fields editor (contact panel) --- */

.cf-empty { color: var(--color-text-muted); font-size: var(--font-size-sm); }

.cf-edit-link {
  border: none;
  background: none;
  cursor: pointer;
  color: var(--color-primary);
  font-size: var(--font-size-sm);
}

.cf-editor { display: flex; flex-direction: column; gap: var(--spacing-sm); }

.cf-row {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: var(--spacing-xs);
  align-items: center;
}

.cf-actions { display: flex; gap: var(--spacing-sm); justify-content: flex-end; }

.contact-lists { display: flex; flex-wrap: wrap; gap: var(--spacing-xs); }

/* --- Variable chips (template pickers) --- */

.var-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-xs);
  margin-top: var(--spacing-xs);
}

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

.var-chip {
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  border-radius: var(--radius-full);
  padding: 2px var(--spacing-sm);
  font-size: var(--font-size-sm);
  cursor: pointer;
  color: var(--color-primary);
}

.var-chip:hover { background: var(--color-bg); }

/* --- Broadcast audience block --- */

.bc-audience { display: flex; flex-direction: column; gap: var(--spacing-sm); }

/* Sources (acquisition Phase B) — intake channels, credentials, test panel. */

.sources-form {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.sources-form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--spacing-md);
}

.form-hint {
  margin: var(--spacing-xs) 0 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.form-hint code,
.sources-secret code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
  background: var(--color-bg);
  padding: 0 4px;
  border-radius: var(--radius-sm);
}

.composer-error {
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  color: var(--color-danger);
  font-size: var(--font-size-sm);
}

/* --- Field mapping editor --- */

.sources-map {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.sources-map-row {
  display: grid;
  grid-template-columns: 1fr auto 1.2fr 1.4fr auto;
  /* end, not center: see the field-row note in forms.css. */
  align-items: end;
  gap: var(--spacing-sm);
}

.sources-map-arrow {
  color: var(--color-text-muted);
}

.sources-map-spacer {
  display: block;
}

.sources-toggle {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  font-size: var(--font-size-sm);
  cursor: pointer;
}

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

/* --- Credentials (one-time secret) modal --- */

.sources-secret {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.sources-warn {
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--color-warning) 15%, transparent);
  color: var(--color-text);
  font-size: var(--font-size-sm);
  border: 1px solid color-mix(in srgb, var(--color-warning) 40%, transparent);
}

.sources-copy {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.sources-copy code {
  flex: 1;
  overflow-x: auto;
  white-space: nowrap;
  padding: var(--spacing-sm);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.sources-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.sources-snippet {
  margin: 0;
  padding: var(--spacing-md);
  background: var(--color-sidebar-bg);
  color: var(--color-sidebar-text);
  border-radius: var(--radius-md);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-sm);
  overflow-x: auto;
  white-space: pre;
}

/* --- Test panel --- */

.sources-test {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.sources-preview {
  padding: var(--spacing-md);
}

.sources-preview h4 {
  margin: 0 0 var(--spacing-sm);
}

.sources-preview-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--spacing-xs) var(--spacing-md);
  margin: 0;
  font-size: var(--font-size-sm);
}

.sources-preview-grid dt {
  color: var(--color-text-muted);
}

.sources-preview-grid dd {
  margin: 0;
}

.sources-bad {
  color: var(--color-danger);
}

@media (max-width: 640px) {
  .sources-form-row {
    grid-template-columns: 1fr;
  }
  .sources-map-row {
    grid-template-columns: 1fr auto 1fr auto;
  }
}

/*
 * Credentials and code snippets — the surfaces that hand a developer something
 * to paste (Send API keys, signing secrets, cURL samples). Shared rather than
 * per-feature: the intake API (Phase B) and the Send API (Phase 8.1A) present
 * the identical thing, and two copies would drift.
 */

.copy-field {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.copy-field-value {
  flex: 1;
  min-width: 0;
  overflow-x: auto;
  white-space: nowrap;
  padding: var(--spacing-sm);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-sm);
}

.code-block {
  margin: 0;
  padding: var(--spacing-md);
  background: var(--color-sidebar-bg);
  color: var(--color-sidebar-text);
  border-radius: var(--radius-md);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-sm);
  /* A wrapped shell command is a broken shell command. */
  overflow-x: auto;
  white-space: pre;
}

/* Keyboard users scroll this box with the arrow keys, so it is focusable and
   must show where focus is. */

.code-block:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* A one-time credential the user cannot recover if they navigate away. */

.credential-warning {
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--color-warning) 15%, transparent);
  color: var(--color-text);
  font-size: var(--font-size-sm);
  border: 1px solid color-mix(in srgb, var(--color-warning) 40%, transparent);
}

/* A textarea the user types code/JSON into (test panels). */

.code-input {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-sm);
}

/* Send API (Phase 8.1A) — the developer-integration screen. */

.send-api-form,
.send-api-secret {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

/* --- Variable map: {{token}} ← the key your backend sends --- */

.send-api-map {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

.send-api-map-row {
  display: grid;
  /* The token side gets the wider share: it is fixed copy we cannot shorten,
     while the input beside it is happy at any width. */
  grid-template-columns: minmax(0, 1.4fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--spacing-sm);
}

.send-api-map-token code {
  /* A long token still has to wrap rather than push the input off the row —
     but break between words, so a name only splits mid-token when there is
     genuinely nowhere else to go. */
  overflow-wrap: break-word;
  word-break: normal;
}

.send-api-map-arrow {
  color: var(--color-text-muted);
}

/* A variable the product fills in itself — shown, not editable, so nobody
   wonders why one row has no input. */

.send-api-map-auto {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  font-style: italic;
}

.send-api-map-field {
  display: block;
  min-width: 0;
}

/* --- Test preview --- */

.send-api-preview-heading {
  margin: var(--spacing-md) 0 var(--spacing-sm);
}

.send-api-values {
  margin: 0;
  padding-left: var(--spacing-lg);
  font-size: var(--font-size-sm);
}

/* --- Recent-calls log --- */

.send-api-mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-sm);
}

/* Caller-chosen ids can be arbitrarily long; cap the column and let the rest
   wrap instead of forcing the table into a horizontal scroll. */

.send-api-key-cell,
.send-api-detail {
  max-width: 22ch;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  .send-api-map-row {
    grid-template-columns: 1fr;
    gap: var(--spacing-xs);
  }
  .send-api-map-arrow {
    display: none;
  }
}

/* Signup forms linked to a Send API campaign (2026-08-27). Checkboxes rather
   than a multi-select: a tenant has a handful of forms, and ctrl-click to
   deselect is a trap people lose work to. */

.sa-forms {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2xs);
  max-height: 200px;
  overflow-y: auto;
}

.sa-form-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  /* Comfortably past the 24px pointer-target minimum. */
  padding: 8px var(--spacing-sm);
  border-radius: var(--radius-xs);
  cursor: pointer;
}

.sa-form-row:hover { background: var(--color-surface-hover); }

.sa-form-row:focus-within { outline: var(--focus-outline); outline-offset: -2px; }

.sa-form-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }

.sa-form-note {
  flex: 0 0 auto;
  font-size: 0.75rem;
  color: var(--color-text-subtle);
}

/* Seven columns and five actions overflow this table at common widths, and
   `.card`'s `overflow: hidden` beats `.table-wrapper`'s own `overflow-x: auto`
   — so "Rotate key" sat past the clip with no way to reach it. Restored for
   this table only, rather than reordering the global stylesheets and changing
   every other card in the app. */

.table-wrapper.sa-table { overflow-x: auto; }

/* Acquisition Phase C — form builder + embed UI. Reuses the common form
   controls, badges, and buttons; adds the two-pane builder, field cards, live
   preview, and the embed tabs. */

/* The builder + embed modals are wider than the default 440px dialog. */

.modal-container:has(.form-builder),
.modal-container:has(.form-embed) {
  max-width: 920px;
}

.form-builder { display: flex; flex-direction: column; gap: var(--spacing-lg); }

.form-builder-top {
  display: flex; align-items: flex-end; gap: var(--spacing-lg);
}

.form-builder-top .form-group { flex: 1; margin: 0; }

.form-builder-grid {
  display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--spacing-lg);
  align-items: start;
}

/* --- Palette --- */

.form-builder-palette { margin-bottom: var(--spacing-md); }

.form-builder-palette-btns {
  display: flex; flex-wrap: wrap; gap: var(--spacing-sm); margin-top: var(--spacing-xs);
}

/* .fb-palette-icon removed — field-type icons are SVG now (see Icon.jsx). */

/* --- Canvas / field cards --- */

.form-builder-canvas { display: flex; flex-direction: column; gap: var(--spacing-sm); }

.fb-field-card {
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  background: var(--color-surface); padding: var(--spacing-sm) var(--spacing-md);
}

.fb-field-head {
  display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-sm);
}

.fb-drag {
  display: inline-flex;
  align-items: center;
  cursor: grab;
  color: var(--color-text-subtle);
  user-select: none;
}

.fb-drag:active { cursor: grabbing; }

.fb-field-move { margin-left: auto; display: flex; gap: 4px; }

.fb-field-body {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-sm);
}

.fb-field-body .form-group { margin: 0; }

.fb-span2 { grid-column: 1 / -1; }

/* --- Live preview --- */

.form-builder-preview {
  position: sticky; top: 0;
  border: 1px dashed var(--color-border); border-radius: var(--radius-md);
  padding: var(--spacing-md); background: var(--color-bg);
}

.wa-form-preview { display: flex; flex-direction: column; gap: var(--spacing-sm); margin-top: var(--spacing-sm); }

.wa-form-preview .wa-field { display: flex; flex-direction: column; gap: 4px; position: relative; }

.wa-form-preview .wa-field label { font-size: var(--font-size-sm); font-weight: 600; }

.wa-preview-key {
  position: absolute; top: 0; right: 0; font-size: 11px; color: var(--color-text-muted);
  font-family: monospace;
}

.wa-consent-text { font-size: 12px; color: var(--color-text-muted); line-height: 1.5; margin: 4px 0; }

.wa-consent-check { display: flex; align-items: flex-start; gap: 6px; font-size: var(--font-size-sm); }

.wa-form-submit {
  align-self: flex-start; padding: 8px 18px; border: 0; border-radius: var(--radius-sm);
  background: var(--color-primary); color: #fff; font-weight: 600; cursor: default;
}

/* --- Sections below the two panes --- */

.form-builder-section {
  border-top: 1px solid var(--color-border); padding-top: var(--spacing-md);
}

.form-builder-section-title {
  font-size: var(--font-size-md); font-weight: 700; margin: 0 0 var(--spacing-sm);
}

/* --- Embed modal --- */

.form-embed-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--color-border); margin-bottom: var(--spacing-md); }

.form-embed-tab {
  padding: 8px 14px; border: 0; background: transparent; cursor: pointer;
  font-size: var(--font-size-sm); font-weight: 600; color: var(--color-text-muted);
  border-bottom: 2px solid transparent;
}

.form-embed-tab.is-active { color: var(--color-primary); border-bottom-color: var(--color-primary); }

.form-embed-code { position: relative; }

.form-embed-copy { position: absolute; top: 8px; right: 8px; z-index: 1; }

.form-embed .sources-snippet { max-height: 420px; overflow: auto; }

@media (max-width: 720px) {
  .form-builder-grid { grid-template-columns: 1fr; }
  .fb-field-body { grid-template-columns: 1fr; }
  .form-builder-preview { position: static; }
}

/* Acquisition Phase D — campaigns, analytics, and outbound webhooks. */

/* Widen the modal for the step builder (mirrors forms-builder). */

.modal-container:has(.campaign-form) {
  max-width: 760px;
  width: 92vw;
}

.campaign-form,
.campaign-test,
.campaign-enroll,
.webhook-form {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.campaign-form-row {
  display: flex;
  gap: var(--spacing-md);
  align-items: flex-end;
}

.campaign-form-row > .form-group { flex: 1; }

.campaign-quiet-sep { padding-bottom: 10px; color: var(--color-text-muted); }

.campaign-steps {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

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

.campaign-step {
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
}

.campaign-step-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--spacing-sm);
}

.campaign-step-num { font-weight: 600; }

.campaign-step-actions {
  display: flex;
  gap: var(--spacing-xs);
  align-items: center;
}

.campaign-step-active {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  font-size: var(--font-size-sm);
}

.campaign-step-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.campaign-var-section-label {
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  margin: 6px 0 2px;
}

.campaign-var-row {
  display: flex;
  gap: var(--spacing-sm);
  align-items: center;
  margin-bottom: 4px;
}

.campaign-var-token {
  font-family: var(--font-mono, monospace);
  font-size: var(--font-size-sm);
  min-width: 120px;
  color: var(--color-text-muted);
}

.campaign-var-auto {
  font-size: var(--font-size-sm);
  color: var(--color-success, #166534);
}

.campaign-var-row .form-control { flex: 1; }

.campaign-warning {
  background: #fef3c7;
  color: #92400e;
  border-radius: var(--radius-sm);
  padding: var(--spacing-sm);
  font-size: var(--font-size-sm);
}

/* Test + enroll contact picker */

.campaign-test-results {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 220px;
  overflow-y: auto;
}

.campaign-test-result {
  display: flex;
  justify-content: space-between;
  align-items: center;
  text-align: left;
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface, #fff);
  cursor: pointer;
}

.campaign-test-result:hover { background: var(--color-surface-hover, #f8fafc); }

.campaign-test-result.is-selected { border-color: var(--color-primary, #2563eb); }

.campaign-test-selected {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

/* Members / enrollments */

.campaign-members { display: flex; flex-direction: column; gap: var(--spacing-md); }

.campaign-backfill {
  background: var(--color-surface-hover, #f1f5f9);
  padding: var(--spacing-sm);
  border-radius: var(--radius-sm);
}

.campaign-members-list { display: flex; flex-direction: column; gap: 2px; max-height: 340px; overflow-y: auto; }

.campaign-member-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--spacing-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.campaign-member-actions {
  display: inline-flex;
  gap: var(--spacing-sm);
  align-items: center;
}

.lists-campaign-chip { margin-right: 4px; }

/* Analytics */

.analytics-filters { display: flex; gap: var(--spacing-md); align-items: flex-end; }

.analytics-skips { display: flex; flex-wrap: wrap; gap: 4px; }

/* Webhooks */

.webhook-events {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 4px;
}

.webhook-event {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  font-size: var(--font-size-sm);
}

.webhook-url {
  font-family: var(--font-mono, monospace);
  font-size: var(--font-size-sm);
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.webhook-secret-value {
  display: flex;
  gap: var(--spacing-sm);
  align-items: center;
  background: var(--color-surface-hover, #f1f5f9);
  padding: var(--spacing-sm);
  border-radius: var(--radius-sm);
  word-break: break-all;
}

.webhook-secret-recipe pre {
  background: var(--color-surface-hover, #f1f5f9);
  padding: var(--spacing-sm);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  overflow-x: auto;
}

/* ==========================================================================
   The unified Campaigns screen (Phase 8.2 IA) — `ch-` = campaign hub.
   One screen over one-time broadcasts, automated drips and Send API
   campaigns, replacing three sidebar entries.
   ========================================================================== */

/* --- Type picker: step 1 of every campaign ------------------------------- */

.ch-types { display: flex; flex-direction: column; gap: var(--spacing-sm); }

.ch-type-card {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-md);
  width: 100%;
  padding: var(--spacing-md);
  text-align: left;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.ch-type-card:hover { border-color: var(--color-primary); background: var(--color-primary-soft); }

.ch-type-card:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }

.ch-type-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  background: var(--color-primary-soft);
  color: var(--color-primary);
}

.ch-type-card:hover .ch-type-icon { background: var(--color-surface); }

.ch-type-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }

.ch-type-label { font-weight: 600; color: var(--color-text); }

/* Not decoration: the subtitle is what makes "From your system" mean something
   to a shop owner. See CampaignTypePicker's docblock. */

.ch-type-sub { font-size: 0.875rem; color: var(--color-text-muted); }

.ch-type-example { font-size: 0.8125rem; color: var(--color-text-subtle); }

.ch-type-chevron { flex: 0 0 auto; align-self: center; color: var(--color-text-subtle); }

/* --- Toolbar: view toggle + filters -------------------------------------- */

.ch-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  margin-bottom: var(--spacing-md);
}

.ch-views {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.ch-view-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  /* 8px block padding + 16px line box clears the 24px web target minimum and
     stays comfortable on a touch screen. */
  padding: 8px 14px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-text-muted);
  background: transparent;
  border: 0;
  border-radius: var(--radius-xs);
  cursor: pointer;
}

.ch-view-btn:focus-visible { outline: var(--focus-outline); outline-offset: 1px; }

.ch-view-btn.is-active {
  color: var(--color-text);
  background: var(--color-surface);
  box-shadow: 0 1px 2px rgb(0 0 0 / 8%);
}

/* Wraps rather than clipping — a filter row that hides its own controls at a
   narrow width is worse than one that takes two lines. */

.ch-filters { display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-sm); }

.ch-search { position: relative; display: flex; align-items: center; }

.ch-search .icon {
  position: absolute;
  left: 10px;
  color: var(--color-text-subtle);
  pointer-events: none;
}

.ch-search .form-control { padding-left: 34px; min-width: 220px; }

.ch-select { width: auto; min-width: 140px; }

/* --- List ---------------------------------------------------------------- */

.ch-kind {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  white-space: nowrap;
  color: var(--color-text-muted);
}

.ch-template {
  display: block;
  font-size: 0.8125rem;
  color: var(--color-text-subtle);
}

.ch-failed { color: var(--color-danger-text); }

.ch-skipped { color: var(--color-text-subtle); }

/* --- Calendar ------------------------------------------------------------ */

.ch-calendar { padding: var(--spacing-md); }

.ch-cal-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-md);
  margin-bottom: var(--spacing-sm);
}

.ch-cal-title { font-size: 1.125rem; font-weight: 600; margin: 0; min-width: 200px; text-align: center; }

.ch-cal-prev { transform: rotate(180deg); }

/* The calendar shows one-time campaigns only, and says so rather than looking
   complete while omitting two of the three kinds. */

.ch-cal-scope {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  margin-bottom: var(--spacing-md);
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  background: var(--color-surface-sunken);
  border-radius: var(--radius-sm);
}

.ch-cal-scope .icon { flex: 0 0 auto; margin-top: 1px; }

.ch-cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.ch-cal-weekday {
  padding: var(--spacing-xs) var(--spacing-sm);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-subtle);
  background: var(--color-surface-sunken);
  text-align: center;
}

.ch-cal-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Reserves the row height so a month with one busy day doesn't make every
     other row collapse — and so switching months doesn't shift the layout. */
  min-height: 96px;
  padding: var(--spacing-xs);
  background: var(--color-surface);
}

.ch-cal-cell.is-empty { background: var(--color-surface-sunken); }

.ch-cal-cell.is-today { box-shadow: inset 0 0 0 2px var(--color-primary); }

.ch-cal-daynum { font-size: 0.75rem; font-weight: 600; color: var(--color-text-muted); }

.ch-cal-cell.is-today .ch-cal-daynum { color: var(--color-primary); }

.ch-cal-entry {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: 3px 6px;
  text-align: left;
  font-size: 0.75rem;
  border: 0;
  border-left: 3px solid currentcolor;
  border-radius: var(--radius-xs);
  cursor: pointer;
}

.ch-cal-entry:focus-visible { outline: var(--focus-outline); outline-offset: 1px; }

.ch-cal-entry-time { font-weight: 600; opacity: 0.85; }

.ch-cal-entry-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ch-cal-entry.is-draft { background: var(--gray-100); color: var(--gray-700); }

.ch-cal-entry.is-scheduled { background: var(--color-primary-soft); color: var(--color-primary-active); }

.ch-cal-entry.is-live,
.ch-cal-entry.is-paused { background: var(--color-warning-soft); color: var(--color-warning-text); }

.ch-cal-entry.is-done { background: var(--color-success-soft); color: var(--color-success-text); }

.ch-cal-entry.is-stopped { background: var(--color-danger-soft); color: var(--color-danger-text); }

/* An operable disclosure, never a silent trim. */

.ch-cal-more {
  padding: 2px 6px;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--color-primary);
  background: transparent;
  border: 0;
  border-radius: var(--radius-xs);
  text-align: left;
  cursor: pointer;
}

.ch-cal-more:hover { background: var(--color-surface-hover); }

.ch-cal-more:focus-visible { outline: var(--focus-outline); outline-offset: 1px; }

.ch-cal-empty {
  margin-top: var(--spacing-md);
  text-align: center;
  color: var(--color-text-muted);
}

@media (max-width: 768px) {
  .ch-toolbar { flex-direction: column; align-items: stretch; }
  .ch-search .form-control { min-width: 0; width: 100%; }
  .ch-search { flex: 1 1 100%; }
  /* A seven-column month cannot shrink below legibility, so it scrolls inside
     its own container rather than forcing the page sideways. */
  .ch-calendar { overflow-x: auto; }
  .ch-cal-grid { min-width: 640px; }
}

/* The merged table carries seven columns and, on an automated row, five
   actions. `.card` sets `overflow: hidden`, which beats `.table-wrapper`'s own
   `overflow-x: auto` and leaves a wide table no choice but to squeeze its
   columns — every cell wrapping to two or three lines. Restored here for this
   table only rather than by reordering the global stylesheets, which would
   change every other card in the app. */

.table-wrapper.ch-table { overflow-x: auto; }

/* These read as single facts — "142 messages sent · 3 failed", "30 Aug",
   a person's name — and wrapping them mid-phrase costs more vertical space
   than the horizontal scroll it saves. The Name column stays wrappable, so
   it is the one that gives. */

.ch-table .ch-nowrap { white-space: nowrap; }

.ch-table td:first-child { min-width: 180px; }

/* The actions WRAP rather than scroll. An automated row carries five of them,
   and pushing the table wide enough to fit them on one line put View, Send and
   Cancel off the right edge behind a horizontal scrollbar — hiding the primary
   action of every other row to keep a secondary one visible on this one. Two
   short lines of buttons in a narrow column costs a little height on drip rows
   only, and nothing is hidden. */

.ch-table .bc-actions { flex-wrap: wrap; max-width: 300px; row-gap: var(--spacing-xs); }

/* The name is a <button>, which centres its text by default — invisible until
   a long name wraps in this column and then reads as a stray indent. */

.ch-table td:first-child .bc-link { text-align: left; }

/* ============================================================================
   Chatbots (Phase 12) — the script builder.

   COLOUR RULE: a step's colour answers exactly one question — does it wait for
   the customer to reply? That is the property worth encoding, because it is what
   decides whether the 24-hour window matters at the next step. Colour is never
   used for "category", and it is never the only signal: a waiting step also
   carries a visible "Waits for a reply" marker, since colour alone excludes
   anyone who cannot separate these hues.

   The three tones follow Wati's builder, which gets this right where AiSensy's
   does not: send-and-continue warm, waits-for-reply amber, logic blue.
   ========================================================================== */

.chatbots-page {
  --tone-talk: #be4b5c;
  --tone-talk-soft: #fdf2f3;
  --tone-talk-border: #f5cdd2;

  --tone-ask: var(--color-warning);
  --tone-ask-soft: var(--color-warning-soft);
  --tone-ask-border: #fcd9a0;

  --tone-logic: var(--color-info-text);
  --tone-logic-soft: var(--color-info-soft);
  --tone-logic-border: #bfdbfe;

  --tone-action: var(--gray-600);
  --tone-action-soft: var(--gray-100);
  --tone-action-border: var(--gray-300);
}

/* ---- List page ---------------------------------------------------------- */

.chatbot-list {
  display: grid;
  gap: var(--spacing-md);
}

.chatbot-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  padding: var(--spacing-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.chatbot-row__main {
  flex: 1;
  min-width: 0;
}

.chatbot-row__name {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--color-text);
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  flex-wrap: wrap;
}

.chatbot-row__meta {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  margin-top: var(--spacing-2xs);
  overflow-wrap: anywhere;
}

.chatbot-row__actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  flex-shrink: 0;
}

/* ---- Builder shell ------------------------------------------------------ */

.builder {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.builder__bar {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  padding: var(--spacing-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  position: sticky;
  top: 0;
  z-index: 20;
}

.builder__title {
  flex: 1;
  min-width: 180px;
}

.builder__actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  flex-wrap: wrap;
}

.builder__tabs {
  display: flex;
  gap: var(--spacing-xs);
  border-bottom: 1px solid var(--color-border);
}

.builder__tab {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: var(--spacing-sm) var(--spacing-md);
  font: inherit;
  font-weight: 500;
  color: var(--color-text-muted);
  cursor: pointer;
  /* 44px min touch target */
  min-height: 44px;
}

.builder__tab:hover {
  color: var(--color-text);
}

.builder__tab[aria-selected='true'] {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

.builder__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--spacing-lg);
  align-items: start;
}

@media (max-width: 1024px) {
  .builder__body {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---- The script (Steps view) -------------------------------------------- */

.script {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  min-width: 0;
}

.script__trigger {
  padding: var(--spacing-md);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  border-radius: var(--radius-md);
}

.script__trigger-label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-primary-active);
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

/* Each nesting level is one branch deeper. The rail makes the nesting legible
   without anybody drawing a line. */

.script__row {
  position: relative;
  padding-left: calc(var(--depth, 0) * 20px);
  min-width: 0;
}

.script__branch-label {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  margin: var(--spacing-sm) 0 var(--spacing-2xs);
  padding: 2px var(--spacing-sm);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-text-muted);
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
}

.script__branch-label--fallback {
  color: var(--color-warning-text);
  background: var(--color-warning-soft);
  border-color: var(--tone-ask-border);
}

/* ---- Step card ---------------------------------------------------------- */

.step-card {
  display: flex;
  gap: var(--spacing-sm);
  width: 100%;
  text-align: left;
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--tone-action);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: inherit;
  color: inherit;
  min-height: 44px;
  align-items: flex-start;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.step-card:hover {
  background: var(--color-surface-hover);
}

.step-card[aria-current='true'] {
  border-color: var(--color-primary);
  border-left-color: var(--color-primary);
  background: var(--color-primary-soft);
}

.step-card--talk { border-left-color: var(--tone-talk); }

.step-card--ask { border-left-color: var(--tone-ask); }

.step-card--logic { border-left-color: var(--tone-logic); }

.step-card--action { border-left-color: var(--tone-action); }

.step-card__body {
  flex: 1;
  min-width: 0;
}

.step-card__kind {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-text-muted);
  flex-wrap: wrap;
}

.step-card__text {
  margin-top: var(--spacing-2xs);
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.step-card__text--empty {
  color: var(--color-text-subtle);
  font-style: italic;
}

/* The non-colour half of "does this wait?". Never remove this in favour of the
   border tone alone. */

.waits-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px var(--spacing-sm);
  font-size: 0.6875rem;
  font-weight: 600;
  border-radius: var(--radius-full);
  background: var(--tone-ask-soft);
  color: var(--color-warning-text);
  border: 1px solid var(--tone-ask-border);
}

.step-card__problem {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-danger-text);
}

/* ---- Add-next control --------------------------------------------------- */

/* You add the next step FROM the previous step, never from a palette you then
   have to connect. Dragging and wiring is two skills; "what happens next" is
   none. */

.add-next {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  margin: var(--spacing-xs) 0;
  padding: var(--spacing-xs) var(--spacing-sm);
  min-height: 36px;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--color-primary);
  background: none;
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.add-next:hover {
  background: var(--color-primary-soft);
  border-color: var(--color-primary);
}

/* ---- Step picker -------------------------------------------------------- */

.picker__group + .picker__group {
  margin-top: var(--spacing-md);
}

.picker__group-title {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
  margin-bottom: var(--spacing-sm);
}

.picker__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--spacing-sm);
}

/* Wati's palette cards read "Send a message / With no response required from
   visitor". AiSensy's read "Text Buttons". The subtitle is the difference
   between understanding and guessing, so it is not optional. */

.picker__item {
  display: block;
  text-align: left;
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--tone-action);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: inherit;
  min-height: 44px;
}

.picker__item:hover {
  background: var(--color-surface-hover);
  border-color: var(--color-border-strong);
}

.picker__item--talk { border-left-color: var(--tone-talk); }

.picker__item--ask { border-left-color: var(--tone-ask); }

.picker__item--logic { border-left-color: var(--tone-logic); }

.picker__item--action { border-left-color: var(--tone-action); }

.picker__item-label {
  font-weight: 600;
  color: var(--color-text);
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

/* Pin the (i) to the trailing edge, away from the middle of the card.
   Sitting immediately after the label put it wherever that label happened to
   end — which, for a card this size, was almost exactly the centre. The tip's
   own 4px hit expansion then covered the point a cursor naturally lands on, so
   clicking the middle of "Send a message" opened the tooltip instead of adding
   the step, and the palette looked like it did nothing at all. At the trailing
   edge it is in the same place on every card and collides with nothing. */

.picker__item-label .info-tip,
.step-card__kind .info-tip {
  margin-left: auto;
}

.picker__item-sub {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  margin-top: 2px;
}

/* ---- Inspector (edit one step) ------------------------------------------ */

.inspector {
  position: sticky;
  top: 88px;
  padding: var(--spacing-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  max-height: calc(100vh - 120px);
  overflow-y: auto;
}

.inspector__empty {
  color: var(--color-text-muted);
  font-size: 0.875rem;
}

.inspector__title {
  font-family: var(--font-display);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  margin-bottom: var(--spacing-xs);
}

.inspector__help {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  margin-bottom: var(--spacing-md);
  line-height: 1.55;
}

.choice-row {
  display: flex;
  gap: var(--spacing-sm);
  align-items: flex-start;
  margin-bottom: var(--spacing-sm);
}

.choice-row__input {
  flex: 1;
  min-width: 0;
}

/* ---- Info tip ----------------------------------------------------------- */

.info-tip {
  position: relative;
  display: inline-flex;
}

/* A real button, not a hover target: hover alone is invisible on a phone and
   unreachable by keyboard, and this product is used on phones. */

.info-tip__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  cursor: help;
}

.info-tip__button::after {
  /* Expands the hit area to 24px without moving anything around it. */
  content: '';
  position: absolute;
  inset: -4px;
}

.info-tip__button:hover,
.info-tip__button:focus-visible {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.info-tip__bubble {
  position: absolute;
  z-index: 60;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 280px;
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-brand-navy);
  color: #ffffff;
  font-size: 0.8125rem;
  font-weight: 400;
  line-height: 1.5;
  border-radius: var(--radius-sm);
  box-shadow: 0 6px 20px rgba(15, 27, 51, 0.25);
  text-align: left;
  white-space: normal;
}

@media (max-width: 640px) {
  .info-tip__bubble {
    position: fixed;
    left: var(--spacing-md);
    right: var(--spacing-md);
    transform: none;
    max-width: none;
    width: auto;
  }
}

/* ---- Variable picker ---------------------------------------------------- */

.var-picker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
  margin-top: var(--spacing-xs);
}

.var-chip {
  padding: 2px var(--spacing-sm);
  min-height: 28px;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  cursor: pointer;
  color: var(--color-text-muted);
}

.var-chip:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
  background: var(--color-primary-soft);
}

/* ---- Problems panel ----------------------------------------------------- */

.problems {
  display: grid;
  gap: var(--spacing-xs);
  margin-bottom: var(--spacing-md);
}

.problem {
  display: flex;
  gap: var(--spacing-sm);
  align-items: flex-start;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
  border: 1px solid transparent;
  text-align: left;
  width: 100%;
  font-family: inherit;
  cursor: pointer;
  background: none;
}

.problem--error {
  background: var(--color-danger-soft);
  border-color: #fca5a5;
  color: var(--color-danger-text);
}

.problem--warning {
  background: var(--color-warning-soft);
  border-color: var(--tone-ask-border);
  color: var(--color-warning-text);
}

/* ---- Map view ----------------------------------------------------------- */

.map {
  padding: var(--spacing-lg);
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow-x: auto;
}

.map__col {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  align-items: flex-start;
  min-width: min-content;
}

.map__node {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--tone-action);
  border-radius: var(--radius-sm);
  max-width: 320px;
  min-width: 200px;
}

.map__node--talk { border-left-color: var(--tone-talk); }

.map__node--ask { border-left-color: var(--tone-ask); }

.map__node--logic { border-left-color: var(--tone-logic); }

.map__node--action { border-left-color: var(--tone-action); }

.map__kind {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-subtle);
}

.map__text {
  font-size: 0.875rem;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.map__connector {
  margin-left: 20px;
  padding-left: var(--spacing-md);
  border-left: 2px dashed var(--color-border-strong);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  padding-top: var(--spacing-xs);
  padding-bottom: var(--spacing-xs);
}

.map__edge-label {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-text-muted);
  margin-bottom: 2px;
}

/* ---- Test panel --------------------------------------------------------- */

.test-panel {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  max-height: 420px;
}

.test-thread {
  flex: 1;
  overflow-y: auto;
  padding: var(--spacing-md);
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  min-height: 220px;
}

.test-bubble {
  max-width: 78%;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-md);
  font-size: 0.875rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.test-bubble--bot {
  align-self: flex-start;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-bottom-left-radius: var(--radius-xs);
}

.test-bubble--customer {
  align-self: flex-end;
  background: var(--color-whatsapp-soft);
  border: 1px solid #b7e8cd;
  color: var(--color-whatsapp-text);
  border-bottom-right-radius: var(--radius-xs);
}

.test-bubble--note {
  align-self: center;
  background: var(--color-warning-soft);
  border: 1px solid var(--tone-ask-border);
  color: var(--color-warning-text);
  font-size: 0.8125rem;
  max-width: 100%;
}

.test-choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
}

@media (prefers-reduced-motion: reduce) {
  .step-card {
    transition: none;
  }
}

/* ---- Save / publish banner ---------------------------------------------- */

/* The house system has no generic .alert, so the builder carries its own. Kept
   inside .chatbots-page so it cannot leak into other screens' styling. */

.chatbot-banner {
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-size: 0.875rem;
}

.chatbot-banner--success {
  background: var(--color-success-soft);
  border-color: #a7e3bd;
  color: var(--color-success-text);
}

.chatbot-banner--error {
  background: var(--color-danger-soft);
  border-color: #fca5a5;
  color: var(--color-danger-text);
}

/* ---- Trigger panel ------------------------------------------------------ */

.script__trigger-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  flex-wrap: wrap;
}

/* A chatbot with no starting words can never run, so it reads as a problem
   rather than as a neutral statement of fact. */

.script__trigger-missing {
  color: var(--color-warning-text);
}

.link-button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--color-primary);
  text-decoration: underline;
  cursor: pointer;
}

.link-button:hover {
  color: var(--color-primary-hover);
}

/* ==========================================================================
   Canvas — the script as boxes and lines (Phase 12 v2)

   ⚠️ THE CARD METRICS HERE MIRROR canvasLayout.js AND MUST MATCH IT.
   The connectors are hand-drawn SVG whose endpoints are computed, not measured,
   so that a line never lags a frame behind the card it is attached to during a
   drag. CARD_W / CARD_HEAD_H / CARD_BODY_H / BRANCH_H live in that file; the
   heights below are applied inline from it, and only what cannot be inlined
   (colour, borders, states) lives here.
   ========================================================================== */

.canvas {
  display: flex;
  flex-direction: column;
  min-height: 0;
  gap: var(--spacing-sm);
  /* ⚠️ min-width: 0 is load-bearing. A grid item defaults to min-width: auto,
     so without this the canvas is sized by its widest content — a 1700px
     surface — and pushes the whole PAGE into horizontal scroll. The sideways
     scrolling belongs to .canvas__stage and nowhere else. */
  min-width: 0;
}

.canvas--empty {
  padding: var(--spacing-xl);
  text-align: center;
}

.canvas__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  flex-wrap: wrap;
}

.canvas__hint {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.canvas__toolbar-actions {
  display: flex;
  gap: var(--spacing-sm);
}

/* The scrolling viewport. A canvas is the one place in this product where
   horizontal scrolling is correct rather than a bug — it is bounded to this
   box, so the page itself never scrolls sideways. */

.canvas__stage {
  position: relative;
  overflow: auto;
  background: var(--color-surface-sunken);
  background-image: radial-gradient(var(--gray-300) 1px, transparent 1px);
  background-size: 20px 20px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  min-height: 60vh;
  max-height: 70vh;
}

/* While a connection is half-made, every card is a target. */

.canvas__stage.is-linking .canvas__card {
  cursor: crosshair;
}

.canvas__surface {
  position: relative;
}

.canvas__wires {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.canvas__wire {
  fill: none;
  /* --gray-500, not the border colour. A connector is MEANINGFUL graphics — it
     is the only thing telling you what leads where — so it needs 3:1 against
     the stage, and the usual border grey manages about 1.5:1 on this near-white
     background. It looked tasteful and was unreadable. */
  stroke: var(--gray-500);
  stroke-width: 2;
}

/* The catch-all branch is drawn dashed. It is the path nobody planned for, and
   reading it as "the exception" at a glance is the difference between spotting
   a dead end and not. */

.canvas__wire--fallback {
  stroke-dasharray: 5 4;
}

.canvas__card {
  position: absolute;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  /* The tone is a thick left edge rather than a fill, so the message text keeps
     its full contrast against white in every family. */
  border-left: 4px solid var(--tone-action);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  cursor: grab;
  user-select: none;
}

.canvas__card.is-dragging {
  cursor: grabbing;
  box-shadow: var(--shadow-lg);
  /* Above its neighbours while moving, or it slides underneath them. */
  z-index: 2;
}

.canvas__card.is-selected {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-soft-border);
}

.canvas__card.has-problem {
  border-color: var(--color-danger);
}

/* Colour answers ONE question — does this step wait for the customer? Nothing
   else in this view is coloured, and every waiting card also says so in words. */

.canvas__card--talk { border-left-color: var(--tone-talk); }

.canvas__card--ask { border-left-color: var(--tone-ask); }

.canvas__card--logic { border-left-color: var(--tone-logic); }

.canvas__card--action { border-left-color: var(--tone-action); }

.canvas__card-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  width: 100%;
  padding: 0 var(--spacing-sm);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  font: inherit;
  text-align: left;
  cursor: inherit;
}

.canvas__card-head:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}

.canvas__kind {
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.canvas__waits {
  flex-shrink: 0;
  padding: 1px var(--spacing-xs);
  border-radius: var(--radius-full);
  background: var(--tone-ask-soft);
  border: 1px solid var(--tone-ask-border);
  color: var(--color-warning-text);
  font-size: 10px;
  white-space: nowrap;
}

.canvas__start {
  flex-shrink: 0;
  margin-left: auto;
  padding: 1px var(--spacing-xs);
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary-soft-border);
  color: var(--color-primary-active);
  font-size: 10px;
}

/* The real message, clamped to two lines. Fixed height is what lets the
   connector maths know where the branch rows start without measuring. */

.canvas__body {
  padding: var(--spacing-xs) var(--spacing-sm);
  font-size: var(--font-size-sm);
  color: var(--color-text);
  line-height: 1.4;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow-wrap: anywhere;
}

/* One row per branch, "Anything else" included — the catch-all is part of the
   step rather than something you must remember to add, which is what stops a
   chatbot dead-ending on an unexpected reply. */

.canvas__branch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-xs);
  padding: 0 var(--spacing-xs) 0 var(--spacing-sm);
  border-top: 1px solid var(--color-border);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

.canvas__branch-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.canvas__branch-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-2xs);
  flex-shrink: 0;
}

.canvas__branch-add {
  background: none;
  border: 0;
  padding: var(--spacing-2xs) var(--spacing-xs);
  border-radius: var(--radius-xs);
  color: var(--color-primary);
  font-size: var(--font-size-xs);
  cursor: pointer;
}

.canvas__branch-add:hover {
  background: var(--color-primary-soft);
}

/* The port sits ON the card's right edge, which is exactly where the connector
   maths draws the line from. It is a real button, because clicking it is the
   keyboard-and-single-pointer way to rewire a branch — WCAG 2.2 requires one,
   and dragging a wire cannot be the only road. */

.canvas__port {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  margin-right: -13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  color: var(--color-text-subtle);
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
}

.canvas__port.is-wired {
  color: var(--color-primary);
  border-color: var(--color-primary-soft-border);
}

.canvas__port:hover,
.canvas__port:focus-visible {
  border-color: var(--color-primary);
  color: var(--color-primary);
  outline: none;
}

.canvas__port:focus-visible {
  box-shadow: 0 0 0 2px var(--color-primary-soft-border);
}

.canvas__problem {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--color-danger);
  color: #fff;
  font-size: var(--font-size-xs);
  font-weight: 700;
}

@media (prefers-reduced-motion: no-preference) {
  .canvas__card {
    transition: box-shadow 120ms ease, border-color 120ms ease;
  }
}

/* Canvas with nothing selected: no inspector, so the drawing gets the lot. */

.builder__body--wide {
  grid-template-columns: minmax(0, 1fr);
}

/* ---- "A robot is handling this chat" (inbox thread header) --------------- */

.chatbot-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  padding: var(--spacing-2xs) var(--spacing-sm);
  border-radius: var(--radius-full);
  /* The waiting tone, because that is what a running chatbot usually is: parked
     on a question. Same colour language as the builder, so the two screens read
     as one feature. */
  background: var(--tone-ask-soft);
  border: 1px solid var(--tone-ask-border);
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  white-space: nowrap;
}

.chatbot-chip__text {
  overflow: hidden;
  text-overflow: ellipsis;
  /* The name gives way, never the state — see the component. */
  max-width: 18ch;
}

.chatbot-chip__state {
  flex-shrink: 0;
  font-weight: 600;
}

.chatbot-chip__take {
  background: none;
  border: 0;
  padding: var(--spacing-2xs) var(--spacing-xs);
  border-radius: var(--radius-xs);
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}

.chatbot-chip__take:disabled {
  opacity: 0.6;
  cursor: default;
}

/* ---- Test on a real phone ------------------------------------------------ */

.test-real {
  margin-top: var(--spacing-md);
  padding-top: var(--spacing-md);
  border-top: 1px solid var(--color-border);
}

.test-real__row {
  display: flex;
  gap: var(--spacing-sm);
  align-items: flex-start;
  margin-top: var(--spacing-sm);
}

.test-real__row .form-control {
  flex: 1;
  min-width: 0;
}

/* ---- The Examples gallery ------------------------------------------------ */

.examples__filters {
  display: flex;
  gap: var(--spacing-md);
  margin: var(--spacing-md) 0;
  flex-wrap: wrap;
}

.examples__filter {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2xs);
  min-width: 160px;
}

.examples__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

.examples__item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: var(--spacing-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}

.examples__main {
  min-width: 0;
}

.examples__name {
  margin: 0;
  font-size: var(--font-size-md);
}

.examples__desc {
  margin: var(--spacing-2xs) 0 0;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

.examples__meta {
  margin: var(--spacing-xs) 0 0;
  color: var(--color-text-subtle);
  font-size: var(--font-size-xs);
}

/* What the example cannot know — your order system, your price list. Said in
   the warning tone before the copy, so it reads as an expectation rather than
   as the publish-time error it would otherwise become. */

.examples__needs {
  margin: var(--spacing-xs) 0 0;
  padding: var(--spacing-2xs) var(--spacing-sm);
  border-radius: var(--radius-xs);
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
  font-size: var(--font-size-xs);
}

.examples__item .btn {
  flex-shrink: 0;
}

.examples__foot {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--spacing-md);
}

/* ---- Routing health ------------------------------------------------------
   Why customers are (or are not) reaching a chatbot. Collapsed by default: the
   list screen is mostly opened to click Edit, and a month of decisions counted
   on every visit is a query nobody asked for.

   ⚠️ No green anywhere in here. Green is real WhatsApp state across the whole
   product — connected, delivered — and a metric tinted green reads as a status
   rather than a number. Tone is blue / amber / red only, and it tints the
   accent bar, never the number. */

.routing {
  margin-top: var(--spacing-md);
  padding: var(--spacing-lg);
}

.routing__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  margin-bottom: var(--spacing-sm);
}

.routing__head-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

/* Narrow enough not to look like a text field, wide enough for "Last 90 days"
   without truncating — a period control that clips its own longest option is
   read as broken. */

.routing__period {
  width: auto;
  min-width: 140px;
}

.routing__scope {
  margin: 0 0 var(--spacing-md);
}

.routing__sub {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text);
  margin: var(--spacing-lg) 0 var(--spacing-sm);
}

/* The sentence under each number. It is the reason this panel is usable by
   somebody who has never seen an analytics screen, so it stays legible rather
   than shrinking to fit — the cards grow instead. */

.routing__metric-hint {
  margin: var(--spacing-xs) 0 0;
  font-size: var(--font-size-xs);
}

@media (max-width: 640px) {
  .routing {
    padding: var(--spacing-md);
  }

  .routing__head-actions {
    width: 100%;
  }

  .routing__period {
    flex: 1;
  }
}

/* ---- Understanding customers (Phase 12 Part B) --------------------------- */

/*
   ⚠️ NO GREEN ANYWHERE IN HERE. Green is reserved product-wide for real
   WhatsApp state — connected, delivered — so a usage bar that turned green
   at 10% would read as "everything is fine with your number", which is a
   different claim entirely. The meter runs neutral → amber → red, and every
   state also says what it means in words, because colour alone excludes
   anyone who cannot separate these hues.
*/

.ai-panel__lede {
  margin: 0 0 var(--spacing-md);
  color: var(--color-text-muted);
  max-width: 62ch; /* Readable measure — this is prose, not a data row. */
}

.ai-panel__unavailable {
  margin: 0 0 var(--spacing-md);
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-md);
  background: var(--color-info-soft);
  color: var(--color-info-text);
  border: 1px solid #bfdbfe;
}

.ai-block {
  display: grid;
  gap: var(--spacing-md);
  margin-bottom: var(--spacing-lg);
}

.ai-switch {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-md);
}

.ai-switch__text {
  flex: 1 1 auto;
  min-width: 0; /* Lets the hint wrap instead of forcing the row wider. */
}

/* ---- The meter ----------------------------------------------------------- */

.ai-usage {
  padding: var(--spacing-md);
  border: 1px solid var(--gray-200);
  border-left: 3px solid var(--gray-400);
  border-radius: var(--radius-md);
  background: var(--gray-50, #f8fafc);
  margin-bottom: var(--spacing-lg);
}

.ai-usage--low {
  border-left-color: var(--color-warning);
  background: var(--color-warning-soft);
}

.ai-usage--gone {
  border-left-color: var(--color-danger);
  background: var(--color-danger-soft);
}

.ai-usage__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-sm);
}

.ai-usage__title {
  margin: 0;
  font-size: var(--font-size-md);
  font-weight: 600;
}

.ai-usage__pct {
  /* Tabular figures: the percentage changes on refresh and must not shuffle
     the layout beside it. */
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.ai-usage__bar {
  height: 8px;
  border-radius: 999px;
  background: var(--gray-200);
  overflow: hidden;
}

.ai-usage__fill {
  display: block;
  height: 100%;
  background: var(--gray-500);
  /* Width only, and only on a bar — cheap, and it never reflows anything
     around it. Respected below for anyone who asked for less motion. */
  transition: width 300ms ease-out;
}

.ai-usage--low .ai-usage__fill { background: var(--color-warning); }

.ai-usage--gone .ai-usage__fill { background: var(--color-danger); }

.ai-usage__line {
  margin: var(--spacing-sm) 0 0;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}

.ai-usage__note {
  margin: var(--spacing-sm) 0 0;
}

.ai-usage__buy {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-md);
  flex-wrap: wrap;
  margin-top: var(--spacing-md);
  padding-top: var(--spacing-md);
  border-top: 1px solid var(--gray-200);
}

/* ---- The instruction field ---------------------------------------------- */

.instruction-foot {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-sm);
}

.instruction-foot .form-hint {
  flex: 1 1 auto;
  margin: 0;
}

.instruction-count {
  flex: 0 0 auto;
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums; /* The count must not jiggle as it grows. */
  color: var(--color-text-muted);
}

/* Amber, not red: too short is an unfinished sentence, not an error. Nothing
   has gone wrong and nothing is blocked — the field is optional. */

.instruction-count--short { color: var(--color-warning); }

.instruction-count--long { color: var(--color-danger); }

.instruction-warning {
  margin: var(--spacing-sm) 0 0;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-md);
  background: var(--color-warning-soft);
  color: var(--color-warning-text);
  border: 1px solid #fcd9a0;
}

.ai-log {
  margin-top: var(--spacing-md);
}

.ai-log__empty {
  padding: var(--spacing-lg);
  text-align: center;
  color: var(--color-text-muted);
}

.ai-log__sure {
  color: var(--color-text-muted);
}

@media (max-width: 640px) {
  .ai-switch {
    /* The switch drops below its label rather than squeezing the text into a
       two-word column. */
    flex-direction: column;
    align-items: flex-start;
  }

  .ai-usage__buy {
    flex-direction: column;
    align-items: stretch;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ai-usage__fill { transition: none; }
}

/* =========================================================================
   Settings — section nav, in-page tabs, and the shared form furniture.

   Settings was one page of eight stacked cards; it is now one sub-page per
   concern (see features/Settings/settingsSections.js), with tabs inside the
   sections that still hold several panels. Both levels exist for the same
   reason: a settings screen people scroll for a minute is a settings screen
   people stop reading.
   ========================================================================= */

/* Forms read badly at the full 1280px content width — a "Claim window
   (seconds)" box a metre wide is harder to parse, not easier. Settings keeps
   its own comfortable measure and stays left-aligned with the page heading. */

.settings-page {
  max-width: 960px;
}

/* ---- Section nav (one entry per Settings sub-page) --------------------- */

.settings-nav {
  display: flex;
  gap: var(--spacing-xs);
  padding: var(--spacing-xs);
  margin-bottom: var(--spacing-lg);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  /* On a phone the strip scrolls sideways rather than wrapping into a block
     that pushes the actual settings below the fold. */
  overflow-x: auto;
  scrollbar-width: none;
}

.settings-nav::-webkit-scrollbar {
  display: none;
}

.settings-nav-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-sm);
  flex: 1 0 auto;
  min-height: var(--tap-target);
  padding: 0 var(--spacing-md);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
  transition:
    background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.settings-nav-link:hover {
  background: var(--color-surface-hover);
  color: var(--color-text);
  text-decoration: none;
}

/* The current section is marked three ways — fill, weight and NavLink's
   aria-current — so it never rests on colour alone. */

.settings-nav-link.is-active,
.settings-nav-link.is-active:hover {
  background: var(--color-primary-soft);
  color: var(--color-primary);
  font-weight: 700;
}

/* ---- Section heading --------------------------------------------------- */

.settings-section-head {
  margin-bottom: var(--spacing-md);
}

.settings-section-title {
  font-size: var(--font-size-lg);
}

.settings-section-desc {
  margin-top: var(--spacing-2xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  max-width: 70ch;
}

/* Cards inside a section: the page owns the rhythm, not the card. */

.settings-stack {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-lg);
}

.settings-stack:focus {
  outline: none;
}

/* ---- Tabs inside a section --------------------------------------------- */

.settings-tabs {
  display: flex;
  gap: var(--spacing-lg);
  border-bottom: 1px solid var(--color-border);
  overflow-x: auto;
  scrollbar-width: none;
}

.settings-tabs::-webkit-scrollbar {
  display: none;
}

.settings-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-sm);
  min-height: var(--tap-target);
  padding: 0 var(--spacing-2xs);
  background: none;
  border: none;
  /* Sits on the strip's own border so the indicator reads as one line. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
  transition: color var(--duration-fast) var(--ease-standard);
}

.settings-tab:hover {
  color: var(--color-text);
}

.settings-tab[aria-selected='true'] {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
  font-weight: 700;
}

.settings-tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  padding: 0 6px;
  border-radius: var(--radius-full);
  background: var(--color-surface-hover);
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  font-weight: 600;
}

.settings-tab[aria-selected='true'] .settings-tab-count {
  background: var(--color-primary-soft);
  color: var(--color-primary);
}

/* ---- Form furniture ---------------------------------------------------- */

/* Short, unrelated controls sit two-up on a desktop and collapse to one
   column on a phone, so a five-field panel is a screenful rather than a
   five-screen scroll. */

.settings-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0 var(--spacing-lg);
  align-items: start;
}

/* A field whose answer is a small number or a time: a full-width box invites
   a paragraph. */

.settings-field-narrow .form-control {
  max-width: 240px;
}

.settings-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-md);
  margin-top: var(--spacing-lg);
  padding-top: var(--spacing-md);
  border-top: 1px solid var(--color-border);
}

.settings-actions-note {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.settings-actions-note.is-saved {
  color: var(--color-success);
}

/* Secondary links in the save row belong at the far end, away from Save. */

.settings-actions-aside {
  margin-left: auto;
}

/* Business hours is seven near-identical rows: the default table padding on
   top of a 44px time input makes it the one panel that still scrolls. Tighten
   the cell padding — the inputs themselves keep their full tap height. */

.settings-hours-table th,
.settings-hours-table td {
  padding: var(--spacing-xs) var(--spacing-md);
}

.settings-hours-table td .form-control {
  min-width: 120px;
}

/* The open/closed checkbox is the whole cell's job — give it a real tap
   target instead of a 13px box floating in a wide column. */

.settings-hours-open {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  min-height: var(--tap-target);
  cursor: pointer;
}

/* A long reply preview in a rules table truncates rather than forcing the
   table wider than the panel. */

.settings-cell-clamp {
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 640px) {
  .settings-nav-link {
    /* Below the fold on a phone, equal-width tabs squeeze the labels; let each
       take its natural width and scroll instead. */
    flex: 0 0 auto;
  }

  .settings-actions-aside {
    margin-left: 0;
  }

  .settings-cell-clamp {
    max-width: 180px;
  }
}

/* The day name is a row header, so it is a <th> — give it the weight a header
   deserves without the all-caps treatment the column headers get. */

.settings-hours-day {
  font-weight: 600;
  color: var(--color-text);
  text-align: left;
  white-space: nowrap;
}

/* "Open"/"Closed" beside the box, not just the box. Reading a word is faster
   than working out what an unticked checkbox in a column called "Open?" means,
   and it is the difference between a state anyone can read and one you have to
   infer from a tick. */

.settings-hours-open {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--spacing-sm);
  width: auto;
  min-height: var(--tap-target);
  cursor: pointer;
}

.settings-hours-open-text {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* --- Settings → AI assistants (Phase 13) ------------------------------- */

/* The MCP address a tenant pastes into Claude or ChatGPT. Deliberately a
   separate rule from the WhatsApp screen's .wa-copy-row rather than a reuse of
   it: that one lives in the WhatsApp feature's own stylesheet, and reaching
   across for it would make this screen render unstyled the day that file stops
   being loaded here. Two short rules is a better trade than that coupling. */

.settings-copy-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.settings-copy-value {
  flex: 1;
  display: block;
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-sm);
  /* A URL has no spaces to wrap at, so without this it forces the card wider
     than the viewport on a phone. */
  word-break: break-all;
}

/* The per-client setup steps under the address. `.hint` sets the muted colour
   and size; this only restores the list marker the reset removes. */

.settings-steps {
  margin: var(--spacing-md) 0 0;
  padding-left: var(--spacing-lg);
  list-style: disc;
}

.settings-steps li + li {
  margin-top: 6px;
}

/* The explanation under the AI-assistants toggle. `.hint` carries no margin by
   design — it is meant as one line under a field — so two stacked hints read as
   a single run-on paragraph. This one sits under a toggle and needs the gap. */

.settings-toggle-hint {
  margin-top: var(--spacing-sm);
}

/* =========================================================================
   Breakpoints: 1024 (sidebar becomes a drawer) and 640 (phone).

   The previous rule collapsed the sidebar to a 64px icon-only rail on phones,
   which left unlabelled icons — unusable for someone who does not already know
   the product. Below 1024px the sidebar is now a proper drawer with full
   labels, opened from the header.
   ========================================================================= */

@media (max-width: 1023px) {
  .app-sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: var(--z-drawer);
    width: min(86vw, var(--sidebar-width));
    transform: translateX(-100%);
    /* transform only: the desktop width transition must not run while the
       drawer slides, or the two animate against each other. */
    transition: transform var(--duration-base) var(--ease-out);
    box-shadow: var(--shadow-lg);
  }

  .app-sidebar.is-open { transform: translateX(0); }

  .sidebar-close { display: inline-flex; }
  .header-menu-btn { display: inline-flex; }

  /* Collapsing is a desktop affordance. Below this width the sidebar is an
     overlay drawer that is either fully open with labels or fully off-screen,
     so a persisted collapse must not leak in and produce an unlabelled rail. */
  .sidebar-collapse-btn { display: none; }

  .app-sidebar.is-collapsed { width: min(86vw, var(--sidebar-width)); }
  .app-sidebar.is-collapsed .sidebar-brand { justify-content: flex-start; padding: var(--spacing-md); }
  .app-sidebar.is-collapsed .sidebar-brand-logo,
  .app-sidebar.is-collapsed .sidebar-brand-text { display: flex; }
  .app-sidebar.is-collapsed .sidebar-nav {
    padding-left: var(--spacing-sm);
    padding-right: var(--spacing-sm);
  }
  .app-sidebar.is-collapsed .sidebar-link {
    justify-content: flex-start;
    padding: 10px 12px;
    margin: 0;
    width: auto;
  }
  .app-sidebar.is-collapsed .sidebar-link-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: hidden;
    clip: auto;
    white-space: nowrap;
  }
  .app-sidebar.is-collapsed .sidebar-section-title {
    font-size: var(--font-size-xs);
    padding: 0 12px var(--spacing-xs);
    margin: 0;
    width: auto;
    height: auto;
    background: none;
  }
  .sidebar-tip { display: none; }

  /* Also shrinks the inbox height calc, which reads this token. */
  .app-shell { --content-padding: var(--spacing-md); }
  .notif-nudge,
  .app-alert { margin: var(--spacing-md) var(--spacing-md) 0; }
}

@media (max-width: 640px) {
  .app-header { padding: 0 var(--spacing-sm); }

  /* Keep the avatar; drop the name so presence + account still fit. */
  .header-avatar-name,
  .header-avatar-caret { display: none; }

  .page-header {
    flex-direction: column;
    align-items: stretch;
  }

  .page-actions { flex-wrap: wrap; }

  /* Full-width actions are easier to hit than a cramped row of small buttons. */
  .page-actions .btn { flex: 1 1 auto; }

  .modal-overlay { padding: 0; align-items: flex-end; }

  .modal-container {
    max-width: none;
    max-height: 92vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }

  .modal-footer { flex-direction: column-reverse; }
  .modal-footer .btn { width: 100%; }

  .auth-card { padding: var(--spacing-lg); }
}

/* Respect users who ask the OS to reduce motion: the drawer snaps instead of
   sliding. (The global reduced-motion rule in reset.css already zeroes the
   duration; this keeps the intent explicit at the component level.) */

@media (prefers-reduced-motion: reduce) {
  .app-sidebar { transition: none; }
}
