/* DC.Campaigns -- hand-written, no framework, no build step.
   Neutral grays carry the UI; one blue accent. Compact density: the operator lives in this tool
   during a send. Every interactive element declares default/hover/focus/active/disabled. */

:root {
  --c-bg:            #f8fafc;
  --c-surface:       #ffffff;
  --c-surface-alt:   #f1f5f9;
  --c-border:        #e2e8f0;
  --c-border-strong: #cbd5e1;

  --c-text:          #0f172a;
  --c-text-muted:    #475569;
  --c-text-subtle:   #94a3b8;

  --c-accent:        #2563eb;
  --c-accent-hover:  #1d4ed8;
  --c-accent-active: #1e40af;
  --c-accent-light:  #eff6ff;

  --c-danger:        #b91c1c;
  --c-danger-hover:  #991b1b;
  --c-danger-active: #7f1d1d;
  --c-danger-light:  #fef2f2;
  --c-danger-border: #fecaca;

  --c-warn:          #92400e;
  --c-warn-light:    #fffbeb;
  --c-warn-border:   #fde68a;

  /* Added in gate 7 for the health widgets. Same values the pass badge already used inline. */
  --c-ok:            #047857;
  --c-ok-light:      #ecfdf5;
  --c-ok-border:     #a7f3d0;

  --c-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --c-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --c-font-xs: 11px;
  --c-font-sm: 12px;
  --c-font-base: 14px;
  --c-font-lg: 16px;
  --c-font-xl: 20px;
  --c-font-2xl: 24px;

  --c-space-1: 4px;
  --c-space-2: 8px;
  --c-space-3: 12px;
  --c-space-4: 16px;
  --c-space-6: 24px;
  --c-space-8: 32px;

  --c-radius: 6px;
  --c-radius-sm: 4px;
  --c-transition: 150ms ease;

  --c-nav-width: 208px;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--c-font);
  font-size: var(--c-font-base);
  line-height: 1.5;
  color: var(--c-text);
  background: var(--c-bg);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--c-accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Keyboard focus is visible everywhere. Never remove this without a replacement ring. */
:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
  border-radius: var(--c-radius-sm);
}

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

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

.nav {
  width: var(--c-nav-width);
  flex: 0 0 var(--c-nav-width);
  background: var(--c-surface);
  border-right: 1px solid var(--c-border);
  padding: var(--c-space-4) 0;
  display: flex;
  flex-direction: column;
}

.nav-brand {
  font-size: var(--c-font-base);
  font-weight: 600;
  letter-spacing: -0.01em;
  padding: 0 var(--c-space-4) var(--c-space-4);
  color: var(--c-text);
}

.nav-brand span { color: var(--c-text-subtle); font-weight: 400; }

.nav-items { display: flex; flex-direction: column; gap: 1px; flex: 1; }

.nav-item {
  display: block;
  padding: var(--c-space-2) var(--c-space-4);
  font-size: var(--c-font-base);
  color: var(--c-text-muted);
  border-left: 2px solid transparent;
  transition: background var(--c-transition), color var(--c-transition);
}

.nav-item:hover { background: var(--c-surface-alt); color: var(--c-text); text-decoration: none; }
.nav-item:active { background: var(--c-border); }

.nav-item.is-active {
  background: var(--c-accent-light);
  border-left-color: var(--c-accent);
  color: var(--c-accent);
  font-weight: 500;
}

.nav-item.is-disabled {
  color: var(--c-text-subtle);
  pointer-events: none;
  cursor: default;
}

.nav-item.is-disabled::after {
  content: "soon";
  float: right;
  font-size: var(--c-font-xs);
  color: var(--c-text-subtle);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
  padding: 0 var(--c-space-1);
}

.nav-foot { padding: var(--c-space-4) var(--c-space-4) 0; border-top: 1px solid var(--c-border); }
.nav-user { font-size: var(--c-font-sm); color: var(--c-text-subtle); margin-bottom: var(--c-space-2); }

.main { flex: 1; min-width: 0; padding: var(--c-space-6); }

.page-head { margin-bottom: var(--c-space-6); }
.page-title { font-size: var(--c-font-2xl); font-weight: 600; letter-spacing: -0.02em; margin: 0; }
.page-sub { color: var(--c-text-muted); font-size: var(--c-font-base); margin: var(--c-space-1) 0 0; }

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

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--c-space-4);
}

.card {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius);
  padding: var(--c-space-4);
}

.card-label {
  font-size: var(--c-font-sm);
  font-weight: 500;
  color: var(--c-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 var(--c-space-2);
}

.card-value { font-size: var(--c-font-2xl); font-weight: 600; letter-spacing: -0.02em; }
.card-hint { font-size: var(--c-font-sm); color: var(--c-text-subtle); margin-top: var(--c-space-1); }

/* Empty state: says what is missing and what to do about it. Never a fake zero. */
.card-empty { font-size: var(--c-font-sm); color: var(--c-text-subtle); }
.card-empty strong { display: block; color: var(--c-text-muted); font-weight: 500; font-size: var(--c-font-base); }

/* ---- forms ---- */

.form-narrow { max-width: 360px; }

.field { margin-bottom: var(--c-space-4); }

/* Labels sit ABOVE inputs. A placeholder is not a label. */
.field label {
  display: block;
  font-size: var(--c-font-sm);
  font-weight: 500;
  color: var(--c-text-muted);
  margin-bottom: var(--c-space-1);
}

.field .req { color: var(--c-danger); }

.input {
  width: 100%;
  height: 32px;
  padding: 0 var(--c-space-3);
  font-family: inherit;
  font-size: var(--c-font-base);
  color: var(--c-text);
  background: var(--c-surface);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--c-radius-sm);
  transition: border-color var(--c-transition), box-shadow var(--c-transition);
}

.input:hover { border-color: var(--c-text-subtle); }
.input:focus { outline: none; border-color: var(--c-accent); box-shadow: 0 0 0 3px var(--c-accent-light); }
.input:disabled { background: var(--c-surface-alt); color: var(--c-text-subtle); cursor: not-allowed; }
.input.has-error { border-color: var(--c-danger); }

.field-error { display: block; margin-top: var(--c-space-1); font-size: var(--c-font-sm); color: var(--c-danger); }

/* The consent checkbox on /sms-optin. The label WRAPS the box so the whole disclosure is a click
   target -- the wording is long by law, and a 13px tickbox as the only target is how people miss it.
   align-items:flex-start keeps the box on the first line rather than centred against a paragraph. */
.checkbox-label {
  display: flex;
  gap: var(--c-space-2);
  align-items: flex-start;
  font-weight: 400;
  font-size: var(--c-font-sm);
  line-height: 1.5;
  color: var(--c-text-subtle);
  cursor: pointer;
}

.checkbox-label input[type="checkbox"] { flex: 0 0 auto; margin-top: 2px; width: 16px; height: 16px; cursor: pointer; }
.checkbox-label input[type="checkbox"]:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

/* ---- buttons ---- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--c-space-2);
  height: 32px;
  padding: 0 var(--c-space-4);
  font-family: inherit;
  font-size: var(--c-font-base);
  font-weight: 500;
  border: 1px solid transparent;
  border-radius: var(--c-radius-sm);
  cursor: pointer;
  transition: background var(--c-transition), border-color var(--c-transition), color var(--c-transition);
}

.btn-primary { background: var(--c-accent); color: #fff; }
.btn-primary:hover { background: var(--c-accent-hover); }
.btn-primary:active { background: var(--c-accent-active); }

.btn-secondary { background: var(--c-surface); color: var(--c-text); border-color: var(--c-border-strong); }
.btn-secondary:hover { background: var(--c-surface-alt); }
.btn-secondary:active { background: var(--c-border); }

.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

.btn-block { width: 100%; }

/* Loading state: the spinner replaces the label so the button cannot be double-submitted blind. */
.btn.is-loading { position: relative; color: transparent; pointer-events: none; }
.btn.is-loading::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: #fff;
  animation: spin 600ms linear infinite;
}

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

/* ---- banners ---- */

.banner {
  padding: var(--c-space-3) var(--c-space-4);
  border-radius: var(--c-radius-sm);
  font-size: var(--c-font-base);
  margin-bottom: var(--c-space-4);
}

.banner-error {
  background: var(--c-danger-light);
  border: 1px solid var(--c-danger-border);
  color: var(--c-danger);
}

.banner-info {
  background: var(--c-accent-light);
  border: 1px solid #bfdbfe;
  color: var(--c-accent-active);
}

/* ---- panels ---- */

.panel {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius);
  margin-bottom: var(--c-space-4);
}

.panel.is-hidden { display: none; }

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--c-space-4);
  padding: var(--c-space-3) var(--c-space-4);
  border-bottom: 1px solid var(--c-border);
}

.panel-title {
  font-size: var(--c-font-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
}

.panel-note { font-size: var(--c-font-sm); color: var(--c-text-subtle); margin: 0; }

.panel-body { padding: var(--c-space-4); }
.panel-body .field:last-of-type { margin-bottom: var(--c-space-4); }

.file-path {
  margin: 0;
  padding: var(--c-space-2) var(--c-space-3);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
  word-break: break-all;
}

.btn-hint { margin-left: var(--c-space-3); font-size: var(--c-font-sm); color: var(--c-text-subtle); }

.field-check { margin-bottom: var(--c-space-4); }

.check { display: flex; align-items: flex-start; gap: var(--c-space-2); font-weight: 400; color: var(--c-text); }
.check input { margin: 3px 0 0; width: 14px; height: 14px; accent-color: var(--c-accent); cursor: pointer; }
.check span { font-size: var(--c-font-base); }

/* The one moving element on the page: it means "work is happening", so it stays until it isn't. */
.pulse {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: var(--c-space-2);
  border-radius: 50%;
  background: var(--c-accent);
  animation: pulse 1.4s ease-in-out infinite;
}

@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

.progress-line { margin: 0 0 var(--c-space-2); font-size: var(--c-font-base); }

/* ---- tables ---- */

.table-scroll { overflow-x: auto; }

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

.table th {
  text-align: left;
  font-size: var(--c-font-sm);
  font-weight: 500;
  color: var(--c-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: var(--c-space-2) var(--c-space-4);
  border-bottom: 1px solid var(--c-border);
  white-space: nowrap;
}

.table td {
  padding: var(--c-space-2) var(--c-space-4);
  border-bottom: 1px solid var(--c-border);
  vertical-align: top;
}

.table tbody tr:last-child td { border-bottom: none; }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.table .nowrap { white-space: nowrap; }
.table .file-cell { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.detail-row td { padding-top: 0; border-bottom: 1px solid var(--c-border); }

.assertion-detail {
  margin: 0;
  padding: var(--c-space-2) var(--c-space-3);
  background: var(--c-surface-alt);
  border-radius: var(--c-radius-sm);
  font-family: var(--c-mono);
  font-size: var(--c-font-sm);
  color: var(--c-text-muted);
  white-space: pre-wrap;
  overflow-x: auto;
}

/* ---- badges ---- */

.badge {
  display: inline-block;
  padding: 1px var(--c-space-2);
  border-radius: var(--c-radius-sm);
  font-size: var(--c-font-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  border: 1px solid transparent;
}

.badge-pass { background: #ecfdf5; border-color: #a7f3d0; color: #047857; }
.badge-fail { background: var(--c-danger-light); border-color: var(--c-danger-border); color: var(--c-danger); }
.badge-none { background: var(--c-surface-alt); border-color: var(--c-border); color: var(--c-text-subtle); }

/* ---- empty state ---- */

.empty-state { padding: var(--c-space-8) var(--c-space-4); text-align: center; color: var(--c-text-subtle); }
.empty-icon { font-size: var(--c-font-2xl); color: var(--c-border-strong); line-height: 1; }
.empty-state strong { display: block; margin-top: var(--c-space-2); color: var(--c-text); font-size: var(--c-font-lg); font-weight: 600; }
.empty-state p { max-width: 460px; margin: var(--c-space-1) auto 0; font-size: var(--c-font-sm); }

/* ---- login ---- */

.login-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--c-space-4);
}

.login-card {
  width: 100%;
  max-width: 340px;
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius);
  padding: var(--c-space-6);
}

.login-title { font-size: var(--c-font-xl); font-weight: 600; letter-spacing: -0.02em; margin: 0 0 var(--c-space-6); }

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

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

@media (max-width: 720px) {
  .app { flex-direction: column; }
  .nav { width: 100%; flex: none; border-right: none; border-bottom: 1px solid var(--c-border); }
  .main { padding: var(--c-space-4); }
}

/* ---- segments ---- */

.page-head-actions {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--c-space-4);
}

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

.btn-sm { height: 28px; padding: 0 var(--c-space-3); font-size: var(--c-font-sm); }

/* Criteria summary on the list. What a segment SELECTS, not just what it is called. */
.chip-row { display: flex; flex-wrap: wrap; gap: var(--c-space-1); }

.chip {
  display: inline-block;
  padding: 1px var(--c-space-2);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
  font-size: var(--c-font-sm);
  color: var(--c-text-muted);
  white-space: nowrap;
}

.row-note { display: block; font-size: var(--c-font-xs); color: var(--c-text-subtle); }
.row-actions { display: flex; gap: var(--c-space-2); align-items: center; }
.actions-col { width: 1%; white-space: nowrap; }

.empty-state .btn { margin-top: var(--c-space-4); }

/* ---- confirm dialog ---- */

.modal {
  width: min(420px, calc(100vw - 32px));
  padding: var(--c-space-6);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius);
  background: var(--c-surface);
  color: var(--c-text);
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18);
}

.modal::backdrop { background: rgba(15, 23, 42, 0.4); }

.modal-title { margin: 0 0 var(--c-space-2); font-size: var(--c-font-lg); font-weight: 600; }
.modal-body { margin: 0 0 var(--c-space-6); font-size: var(--c-font-base); color: var(--c-text-muted); }
.modal-actions { display: flex; justify-content: flex-end; gap: var(--c-space-2); }

/* ---- segment editor ---- */

.edit-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: var(--c-space-4);
  align-items: start;
}

.edit-main { min-width: 0; }

.edit-side { position: sticky; top: var(--c-space-6); }

.field-hint { display: block; margin-top: var(--c-space-1); font-size: var(--c-font-sm); color: var(--c-text-subtle); }

.field-warn {
  display: block;
  margin-top: var(--c-space-2);
  padding: var(--c-space-2) var(--c-space-3);
  background: var(--c-warn-light);
  border: 1px solid var(--c-warn-border);
  border-radius: var(--c-radius-sm);
  font-size: var(--c-font-sm);
  color: var(--c-warn);
}

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

.tri-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--c-space-4);
}

.textarea { height: auto; padding: var(--c-space-2) var(--c-space-3); line-height: 1.5; resize: vertical; }

.select { cursor: pointer; }

/* Checkbox grid: 15 states have to be scannable in one glance, not a 15-row column. */
.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
  gap: var(--c-space-1);
}

.check-grid-wide { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }

.check-tile {
  align-items: center;
  padding: var(--c-space-1) var(--c-space-2);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
  background: var(--c-surface);
  cursor: pointer;
  transition: background var(--c-transition), border-color var(--c-transition);
}

.check-tile:hover { background: var(--c-surface-alt); border-color: var(--c-border-strong); }
.check-tile:active { background: var(--c-border); }
.check-tile input { margin: 0; }
.check-tile span { font-size: var(--c-font-sm); overflow: hidden; text-overflow: ellipsis; }
.check-tile:has(input:checked) { background: var(--c-accent-light); border-color: var(--c-accent); color: var(--c-accent-active); }
.check-tile:has(input:focus-visible) { outline: 2px solid var(--c-accent); outline-offset: 1px; }

.form-actions { display: flex; gap: var(--c-space-2); margin-bottom: var(--c-space-6); }

/* ---- live counts ---- */

.counts-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--c-space-4);
  transition: opacity var(--c-transition);
}

/* Busy means the numbers on screen are STALE, so they are dimmed rather than blanked --
   blanking loses the previous answer while the new one is still in flight. */
.counts-panel.is-busy .counts-grid { opacity: 0.45; }

.count-col { min-width: 0; }

.count-head {
  font-size: var(--c-font-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-text-muted);
  padding-bottom: var(--c-space-1);
  border-bottom: 1px solid var(--c-border);
}

.count-hero { padding: var(--c-space-3) 0 var(--c-space-2); }
.count-hero-value { display: block; font-size: var(--c-font-2xl); font-weight: 600; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.count-hero-label { display: block; font-size: var(--c-font-xs); color: var(--c-text-subtle); }

.count-list { margin: 0; }

.count-row { display: flex; justify-content: space-between; gap: var(--c-space-2); padding: 3px 0; }
.count-row dt { font-size: var(--c-font-sm); color: var(--c-text-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.count-row dd { margin: 0; font-size: var(--c-font-sm); font-variant-numeric: tabular-nums; }
.count-row-sub dt, .count-row-sub dd { color: var(--c-text-subtle); }

.count-sub-head {
  margin-top: var(--c-space-3);
  padding-top: var(--c-space-2);
  border-top: 1px solid var(--c-border);
  font-size: var(--c-font-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-text-subtle);
}

.counts-foot {
  margin: var(--c-space-4) 0 0;
  padding-top: var(--c-space-3);
  border-top: 1px solid var(--c-border);
  font-size: var(--c-font-xs);
  color: var(--c-text-subtle);
}

#counts-error { display: flex; align-items: center; justify-content: space-between; gap: var(--c-space-2); }
#counts-error.is-hidden { display: none; }

@media (max-width: 1180px) {
  .edit-layout { grid-template-columns: minmax(0, 1fr); }
  .edit-side { position: static; }
}

/* ---- composer + suppressions (gate 4) ---- */

/* Merge palette: a row of insert buttons, not a dropdown -- the operator uses them repeatedly. */
.palette { display: flex; flex-wrap: wrap; gap: var(--c-space-2); }
.palette-btn { font-family: var(--c-mono); }

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

/* Key/value list used by the header inspector and the test-send receipt. */
.kv { margin: 0; }
.kv-row { display: grid; grid-template-columns: minmax(90px, 30%) 1fr; gap: var(--c-space-2); padding: 3px 0; }
.kv-row dt { font-size: var(--c-font-sm); color: var(--c-text-muted); }
.kv-row dd { margin: 0; font-size: var(--c-font-sm); min-width: 0; }
.break { overflow-wrap: anywhere; }

/* Compliance verdict. Green means it would send; red lists the machine reasons verbatim, because
   those exact strings are what lands in campaign_recipients.error. */
.verdict {
  display: flex;
  gap: var(--c-space-3);
  padding: var(--c-space-3);
  margin-bottom: var(--c-space-4);
  border: 1px solid #a7f3d0;
  background: #ecfdf5;
  border-radius: var(--c-radius-sm);
}

.verdict.is-hidden { display: none; }
.verdict strong { display: block; font-size: var(--c-font-base); color: #047857; }
.verdict p { margin: var(--c-space-1) 0 0; font-size: var(--c-font-sm); color: var(--c-text-muted); }

.verdict-fail { border-color: var(--c-danger-border); background: var(--c-danger-light); }
.verdict-fail strong { color: var(--c-danger); }

.verdict-dot { width: 8px; height: 8px; margin-top: 6px; border-radius: 50%; flex: 0 0 8px; }
.verdict-dot-pass { background: #047857; }
.verdict-dot-fail { background: var(--c-danger); }

.verdict-list { margin: var(--c-space-1) 0 0; padding-left: var(--c-space-4); font-size: var(--c-font-sm); }
.verdict-list li { color: var(--c-danger); }

.audience-note {
  padding: var(--c-space-2) var(--c-space-3);
  margin-bottom: var(--c-space-4);
  background: var(--c-surface-alt);
  border-radius: var(--c-radius-sm);
  font-size: var(--c-font-sm);
  color: var(--c-text-muted);
}

/* Preview tabs: the same message seen three ways, because all three have to be right. */
.preview-tabs { display: flex; gap: var(--c-space-1); border-bottom: 1px solid var(--c-border); margin-bottom: var(--c-space-3); }

.preview-tab {
  padding: var(--c-space-2) var(--c-space-3);
  font-family: inherit;
  font-size: var(--c-font-sm);
  font-weight: 500;
  color: var(--c-text-muted);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  transition: color var(--c-transition), border-color var(--c-transition);
}

.preview-tab:hover { color: var(--c-text); background: var(--c-surface-alt); }
.preview-tab:active { background: var(--c-border); }
.preview-tab.is-active { color: var(--c-accent); border-bottom-color: var(--c-accent); }

.preview-pane.is-hidden { display: none; }

#preview-frame {
  width: 100%;
  height: 340px;
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
  background: #fff;
}

.preview-text {
  margin: 0;
  max-height: 340px;
  overflow: auto;
  padding: var(--c-space-3);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
  font-family: var(--c-mono);
  font-size: var(--c-font-sm);
  color: var(--c-text-muted);
  white-space: pre-wrap;
}

/* Stale numbers are dimmed, never blanked -- blanking loses the previous answer mid-request. */
#preview-panel.is-busy .preview-pane,
#preview-panel.is-busy .verdict,
#preview-panel.is-busy .audience-note { opacity: 0.45; }

#preview-error { display: flex; align-items: center; justify-content: space-between; gap: var(--c-space-2); }
#preview-error.is-hidden { display: none; }

/* One-line forms (suppression add + search). Fields keep labels above inputs. */
.inline-form { display: flex; flex-wrap: wrap; gap: var(--c-space-3); align-items: flex-end; }
.inline-form .field { margin-bottom: 0; }
.inline-form .field-grow { flex: 1 1 220px; min-width: 0; }

/* ---- public pages (unsubscribe) ---- */

.public-card { max-width: 420px; text-align: left; }
.public-lead { margin: 0 0 var(--c-space-4); font-size: var(--c-font-base); color: var(--c-text); }
.public-note { margin: var(--c-space-4) 0 0; font-size: var(--c-font-sm); color: var(--c-text-subtle); }

@media (max-width: 1180px) {
  #preview-frame { height: 260px; }
}

/* ---- SMS composer ---- */

/*
  The TCPA banner. Louder than .banner-error because it is not reporting a problem with the page --
  it is telling the operator that the thing they are about to do carries a per-message statutory
  penalty. .banner-locked carries NO dismiss control and nothing in the stylesheet or the script can
  hide it; that is the point, and it is why the rule is written here rather than as a variant that a
  future edit could add a close button to.
*/
.banner-danger {
  background: var(--c-danger-light);
  border: 1px solid var(--c-danger-border);
  border-left: 4px solid var(--c-danger);
  color: var(--c-danger);
}

.banner-danger strong { display: block; margin-bottom: var(--c-space-1); }
.banner-danger p { margin: 0; font-size: var(--c-font-sm); line-height: 1.5; }

.banner-locked { position: relative; }

/* Segment meter: encoding, count against the cap, and what the run will bill. */
.meter {
  padding: var(--c-space-3);
  margin-bottom: var(--c-space-4);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
}

.meter-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--c-space-3); }
.meter-stat { font-size: var(--c-font-sm); color: var(--c-text-muted); font-variant-numeric: tabular-nums; }
.meter-stat strong { font-size: var(--c-font-base); color: var(--c-text); }
.meter-stat .over-cap { color: var(--c-danger); }

.meter-cost {
  margin: var(--c-space-2) 0 0;
  font-size: var(--c-font-sm);
  color: var(--c-text-muted);
  font-variant-numeric: tabular-nums;
}

.meter-warn { margin: var(--c-space-3) 0 var(--c-space-1); font-size: var(--c-font-sm); color: var(--c-warn); }

.offender-list { margin: 0 0 var(--c-space-2); padding-left: var(--c-space-4); font-size: var(--c-font-sm); }
.offender-list li { color: var(--c-text-muted); }

/*
  The rendered text as a handset shows it. The appended STOP line is <mark>ed so the operator can
  see at a glance that it was added for them -- and that it is inside the character count.
*/
.sms-bubble {
  padding: var(--c-space-3);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius);
  font-size: var(--c-font-base);
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.sms-suffix { background: var(--c-warn-light); color: var(--c-warn); border-radius: var(--c-radius-sm); }

/* ---------------------------------------------------------------------------
   Launch checklist and campaign detail (gate 6)
   --------------------------------------------------------------------------- */

/*
  A checklist row the app verified for itself: a badge and a statement, never a checkbox.
  Ticking a box next to a fact the app already knows only teaches operators to tick without reading.
*/
.checklist-row { display: flex; align-items: flex-start; gap: var(--c-space-2); }
.check-static { display: block; font-size: var(--c-font-base); color: var(--c-text); }

.progress-bar {
  height: 8px;
  margin-bottom: var(--c-space-4);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border);
  border-radius: 999px;
  overflow: hidden;
}

.progress-bar span {
  display: block;
  height: 100%;
  background: var(--c-accent);
  transition: width var(--c-transition);
}

.pager { display: flex; align-items: center; gap: var(--c-space-3); }

/* A link cannot be :disabled, so a paging link at the end of the range says so instead. */
.btn.is-disabled { opacity: 0.5; pointer-events: none; }

/* ---------------------------------------------------------------------------
   Analytics, dashboard and settings (gate 7)
   --------------------------------------------------------------------------- */

/*
  Result tiles. Three lines each -- what it is, how many, and at what rate -- because a count with
  no denominator and a rate with no count are each half an answer.
*/
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--c-space-3);
}

.stat {
  padding: var(--c-space-3);
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
}

.stat-label {
  display: block;
  font-size: var(--c-font-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-text-muted);
}

.stat-value {
  display: block;
  margin-top: var(--c-space-1);
  font-size: var(--c-font-xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.stat-rate { display: block; font-size: var(--c-font-xs); color: var(--c-text-subtle); }

/*
  The "?" beside a metric that is deliberately not a number. Styled as a control rather than as
  decoration so it is obvious there is something to read, and given a real cursor so hovering it
  feels intentional.
*/
abbr[title] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-left: 2px;
  border: 1px solid var(--c-border-strong);
  border-radius: 50%;
  font-size: 9px;
  font-weight: 700;
  color: var(--c-text-subtle);
  text-decoration: none;
  cursor: help;
}

abbr[title]:hover { border-color: var(--c-text-subtle); color: var(--c-text-muted); }

/* Unsubscribe-rate band. Green under 0.5%, amber to 1%, red above -- the plan's thresholds. */
.rate-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: var(--c-space-2);
  border-radius: 50%;
  vertical-align: middle;
}

.rate-ok { background: var(--c-ok); }
.rate-warn { background: var(--c-warn); }
.rate-bad { background: var(--c-danger); }

/* A live campaign on the dashboard: name, state, bar, counts. */
.mini-campaign { padding-bottom: var(--c-space-4); }
.mini-campaign + .mini-campaign { padding-top: var(--c-space-4); border-top: 1px solid var(--c-border); }

.mini-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--c-space-2);
  margin-bottom: var(--c-space-2);
  font-weight: 500;
}

.mini-campaign .progress-bar { margin-bottom: var(--c-space-1); }
.mini-foot { font-size: var(--c-font-sm); color: var(--c-text-subtle); }


/* ---------------------------------------------------------------------------
   Contacts list
   --------------------------------------------------------------------------- */

/* The quick filters take their own row inside the search form: four toggles side by side read
   faster than four stacked fields, and they have to submit with the search box. */
.filter-checks { display: flex; flex-wrap: wrap; gap: var(--c-space-1); flex: 1 1 100%; }

/* An inactive license is not an error -- the dealer may simply have stopped trading -- so it is
   amber rather than the red this app reserves for something being wrong. */
.badge-warn { background: var(--c-warn-light); border-color: var(--c-warn-border); color: var(--c-warn); }

/* Reachability in one column: reachable, nothing on file, or on file but blocked. Blocked is the
   loudest of the three on purpose; it is the state that must never be misread as reachable. */
.reach { white-space: nowrap; }
.reach-icon { margin-right: var(--c-space-1); font-size: var(--c-font-lg); line-height: 1; cursor: help; }
.reach-on { color: var(--c-ok); }
.reach-off { color: var(--c-border-strong); }
.reach-blocked { color: var(--c-danger); }

/* An expanded row and its detail read as one block. Scoped to .contact-detail: the import
   history's own .detail-row is a bare <pre> that already styles itself, and must not inherit this. */
.table tbody tr.is-open > td { background: var(--c-accent-light); border-bottom-color: var(--c-accent-light); }

.detail-row.contact-detail > td {
  background: var(--c-surface-alt);
  padding-top: var(--c-space-4);
  padding-bottom: var(--c-space-4);
}

.detail-row.contact-detail .table {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
}

.detail-head { display: flex; align-items: baseline; gap: var(--c-space-2); margin-top: var(--c-space-6); }
.detail-empty { margin: var(--c-space-2) 0 0; font-size: var(--c-font-sm); color: var(--c-text-subtle); }

/* ---------------------------------------------------------------------------
   Reply inbox, AI composer panel and contact cleanup
   --------------------------------------------------------------------------- */

/* Coloured chips. The neutral .chip already exists and stays the default; these are modifiers so a
   triage label reads at a glance without inventing a second chip component. */
.chip-ok { background: var(--c-ok-light); border-color: var(--c-ok-border); color: var(--c-ok); }
.chip-info { background: var(--c-accent-light); border-color: #bfdbfe; color: var(--c-accent-active); }
.chip-warn { background: var(--c-warn-light); border-color: var(--c-warn-border); color: var(--c-warn); }
.chip-muted { background: var(--c-surface-alt); border-color: var(--c-border); color: var(--c-text-subtle); }

/* The nav badge. Absent at zero (the script hides it) rather than rendering "0", which teaches an
   operator to stop reading it. */
.nav-item { position: relative; }

.nav-badge {
  float: right;
  min-width: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--c-accent);
  color: #fff;
  font-size: var(--c-font-xs);
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.nav-item.is-active .nav-badge { background: var(--c-accent-active); }

/* ---- conversation list ---- */

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

.thread-item + .thread-item { border-top: 1px solid var(--c-border); }

.thread-link {
  display: flex;
  align-items: flex-start;
  gap: var(--c-space-3);
  padding: var(--c-space-3) var(--c-space-4);
  color: var(--c-text);
  transition: background var(--c-transition);
}

.thread-link:hover { background: var(--c-surface-alt); text-decoration: none; }
.thread-link:active { background: var(--c-border); }
.thread-link:focus-visible { outline: 2px solid var(--c-accent); outline-offset: -2px; }

.thread-icon { flex: 0 0 auto; font-size: var(--c-font-lg); line-height: 1.4; color: var(--c-text-subtle); }
.thread-icon-sms { color: var(--c-accent); }

.thread-main { flex: 1; min-width: 0; }

.thread-top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--c-space-2); }
.thread-who { font-weight: 500; }

.thread-preview {
  display: block;
  margin-top: 2px;
  font-size: var(--c-font-sm);
  color: var(--c-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

.thread-when {
  flex: 0 0 auto;
  font-size: var(--c-font-sm);
  color: var(--c-text-subtle);
  white-space: nowrap;
}

/* Unread is carried by weight AND by a rule down the left edge: weight alone is invisible to
   anyone who cannot compare two rows side by side. */
.thread-item.is-unread .thread-link { border-left: 2px solid var(--c-accent); }
.thread-item.is-unread .thread-who { font-weight: 600; }
.thread-item.is-unread .thread-preview { color: var(--c-text); }

.thread-list-compact .thread-link { padding-left: 0; padding-right: 0; }
.thread-list-compact .thread-item.is-unread .thread-link { border-left: none; }

/* ---- one thread ---- */

.msg-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--c-space-4); }

.msg { display: flex; flex-direction: column; max-width: 80%; }
.msg-in { align-self: flex-start; align-items: flex-start; }
.msg-out { align-self: flex-end; align-items: flex-end; }

.msg-bubble {
  padding: var(--c-space-3);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius);
  font-size: var(--c-font-base);
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.msg-in .msg-bubble { background: var(--c-surface-alt); }
.msg-out .msg-bubble { background: var(--c-accent-light); border-color: #bfdbfe; }

.msg-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--c-space-2);
  margin-top: var(--c-space-1);
  font-size: var(--c-font-xs);
  color: var(--c-text-subtle);
}

.triage-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--c-space-2); margin-bottom: var(--c-space-2); }
.triage-summary { margin: 0 0 var(--c-space-3); font-size: var(--c-font-base); color: var(--c-text); }

#suggested-reply { max-height: 200px; margin-bottom: var(--c-space-2); }

/* ---- AI composer panel ---- */

/* Bounded on purpose: everything inside this border is a request to a model, and everything outside
   it is the campaign. The tint is what makes that boundary visible without a second heading. */
.ai-panel { border-color: var(--c-border-strong); background: var(--c-surface); }
.ai-panel .panel-head { background: var(--c-surface-alt); border-top-left-radius: var(--c-radius); border-top-right-radius: var(--c-radius); }

.ai-fieldset { margin: 0; padding: 0; border: none; }
.ai-fieldset:disabled { opacity: 0.55; }
.ai-fieldset .form-actions { margin-bottom: 0; }

.ai-notes { margin: 0 0 var(--c-space-3); font-size: var(--c-font-sm); color: var(--c-text-muted); }

/* ---- contact cleanup ---- */

/* Faceted filters. Chips rather than dropdowns because the COUNT is the thing being read: an
   operator deciding what to do about 5,528 dead findings has to see 5,528 before they choose. */
.filter-facet { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--c-space-3); }
.filter-facet + .filter-facet { margin-top: var(--c-space-2); }

.facet-label {
  flex: 0 0 52px;
  font-size: var(--c-font-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-text-muted);
}

.facet {
  display: inline-flex;
  align-items: center;
  gap: var(--c-space-2);
  padding: 2px var(--c-space-2);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
  background: var(--c-surface);
  font-size: var(--c-font-sm);
  color: var(--c-text-muted);
  transition: background var(--c-transition), border-color var(--c-transition), color var(--c-transition);
}

.facet:hover { background: var(--c-surface-alt); border-color: var(--c-border-strong); color: var(--c-text); text-decoration: none; }
.facet:active { background: var(--c-border); }

.facet.is-active {
  background: var(--c-accent-light);
  border-color: var(--c-accent);
  color: var(--c-accent-active);
  font-weight: 500;
}

.facet-count { font-variant-numeric: tabular-nums; color: var(--c-text-subtle); }
.facet.is-active .facet-count { color: var(--c-accent-active); }

.bulk-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--c-space-3);
  border-bottom: 1px solid var(--c-border);
}

/* The filter-wide apply sits above the per-row one and is tinted, because the two mean different
   things: this one acts on everything the filter selects, including the 5,478 rows not on screen. */
.bulk-bar-all { background: var(--c-warn-light); border-bottom-color: var(--c-warn-border); }

.confirm-list { margin: var(--c-space-2) 0; padding-left: var(--c-space-4); font-size: var(--c-font-sm); }
.confirm-list li { color: var(--c-text-muted); }

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

.finding { padding: var(--c-space-4); }
.finding + .finding { border-top: 1px solid var(--c-border); }

/* A decided finding recedes. It is still readable -- the audit trail is the point -- but it stops
   competing with the ones that still need someone. */
.finding-applied, .finding-dismissed { background: var(--c-surface-alt); }
.finding-applied .finding-cards, .finding-dismissed .finding-cards { opacity: 0.7; }

.finding-head { display: flex; align-items: flex-start; gap: var(--c-space-3); }
.finding-check { flex: 0 0 20px; margin: 2px 0 0; }
.finding-headings { flex: 1; min-width: 0; }

.finding-title { display: flex; flex-wrap: wrap; align-items: center; gap: var(--c-space-2); }
.finding-reason { margin: var(--c-space-1) 0 0; font-size: var(--c-font-sm); color: var(--c-text-muted); }

.finding-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--c-space-3);
  margin-top: var(--c-space-3);
}

.finding-card {
  padding: var(--c-space-3);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--c-radius-sm);
}

/* The two that carry the decision: what survives, and what goes. Border-left rather than a fill, so
   the cards stay comparable at a glance -- comparing them is the whole job. */
.finding-card.is-keeper { border-left: 3px solid var(--c-ok); background: var(--c-ok-light); }
.finding-card.is-target { border-left: 3px solid var(--c-warn); }

.finding-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--c-space-2); margin-bottom: var(--c-space-2); }

/* An archived contact's row, on the contact list. Muted, never hidden. */
.table tbody tr.is-archived td { color: var(--c-text-subtle); }
.table tbody tr.is-archived td:first-child { color: var(--c-text-muted); }

/* Label text for a control that carries its meaning in its position rather than in words. */
.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;
}

@media (max-width: 720px) {
  .msg { max-width: 100%; }
}

/* ---------------------------------------------------------------------------
   Demo funnel and conversation pipeline
   --------------------------------------------------------------------------- */

/*
  The honeypot on the public demo form. Positioned off-screen rather than display:none, because some
  form-filling bots skip hidden inputs and happily fill one that is merely out of view -- which is
  the whole point of the field. aria-hidden and tabindex="-1" in the markup keep it away from
  keyboard and screen-reader users; this only keeps it away from sighted ones.
*/
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/*
  A conversation row that carries a stage control. A MODIFIER rather than a change to .thread-item,
  because the dashboard's compact list uses the same rows without one and must not gain a flex
  context it never asked for.
*/
.thread-item-staged {
  display: flex;
  align-items: center;
  gap: var(--c-space-2);
  padding-right: var(--c-space-4);
}

.thread-item-staged .thread-link { flex: 1 1 auto; min-width: 0; }

.thread-stage { flex: 0 0 auto; display: flex; align-items: center; gap: var(--c-space-2); }

/* Matches .btn-sm, so a select and a button on the same row line up. */
.input-sm {
  height: 28px;
  padding: 0 var(--c-space-2);
  font-size: var(--c-font-sm);
}

/* The due-follow-ups pill is a work queue, not a stage, so it reads as one. */
.facet-due.is-active {
  background: var(--c-warn-light);
  border-color: var(--c-warn-border);
  color: var(--c-warn);
}

.facet-due.is-active .facet-count { color: var(--c-warn); }

/* A handled demo request recedes -- still readable, no longer competing with the ones that are not. */
.table tbody tr.is-handled td { color: var(--c-text-subtle); }

@media (max-width: 720px) {
  /* The stage control drops under the row rather than squeezing the preview to nothing. */
  .thread-item-staged { flex-wrap: wrap; }
  .thread-item-staged .thread-link { flex: 1 1 100%; }
  .thread-stage { padding: 0 0 var(--c-space-3) var(--c-space-4); }
}

/* Utilities last: a single-class hide has to beat the single-class rule it is hiding. */
.is-hidden { display: none; }
