:root {
  --bg: #0f1115;
  --panel: #171a21;
  --border: #2a2f3a;
  --text: #e5e7eb;
  --muted: #9ca3af;
  --accent: #5b8def;
  --good: #34d399;
  --warn: #fbbf24;
  --bad: #f87171;
  --orange: #fb923c;
  --blue: #60a5fa;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

body {
  background: var(--bg);
  color: var(--text);
  margin: 0;
  padding: 0;
}

header.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* Slightly taller than a plain nav bar needs on its own -- gives the
     larger brand-logo-header room to breathe rather than touching the
     top/bottom border. */
  padding: 1rem 1.5rem;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  /* Responsive audit A2: harmless at desktop widths (brand+nav+logout
     already fit on one line above 768px, so this never triggers there) --
     the safety net that lets the logout form drop to its own line on a
     genuinely cramped viewport instead of being forced to overlap/clip. */
  flex-wrap: wrap;
}

.topbar-brand {
  display: flex;
  align-items: center;
  gap: 1.75rem;
}

/* Dashboard header wordmark -- proportional to the topbar's own height
   (see header.topbar's padding above), not a small icon-sized afterthought
   next to the nav links. max-width/height:auto keeps it a responsive,
   correctly-proportioned image regardless of viewport rather than a fixed
   raster size. */
.brand-logo-header {
  height: 46px;
  width: auto;
  max-width: 280px;
}

/* Responsive audit A2: hamburger toggle -- hidden above 768px (the nav
   bar already fits there, unchanged), shown only below it. Three plain
   CSS bars rather than an icon font/SVG sprite, matching this codebase's
   existing "plain text/emoji, no icon library" convention elsewhere
   (e.g. campaign_new.html's "+ Add segment", "✨ Generate with AI"). */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  width: 40px;
  height: 40px;
  padding: 0;
  cursor: pointer;
}
.nav-toggle-bar {
  display: block;
  width: 18px;
  height: 2px;
  margin: 0 auto;
  background: var(--text);
  border-radius: 1px;
}

header.topbar nav a {
  color: var(--muted);
  text-decoration: none;
  margin-right: 1.25rem;
  font-size: 0.9rem;
}

header.topbar nav a:hover, header.topbar nav a.active {
  color: var(--text);
}

/* Responsive audit A2: below 768px, the nav collapses behind the
   hamburger toggle above -- purely presentational, the exact same links
   the role/impersonation Jinja conditions above already produce, just
   hidden until opened and laid out as a full-width stacked column instead
   of an inline row. At/above 768px this whole block is inert (the media
   query never matches), so the existing desktop nav is untouched. */
@media (max-width: 768px) {
  .topbar-brand {
    width: 100%;
    justify-content: space-between;
    flex-wrap: wrap;
  }
  .nav-toggle {
    display: flex;
  }
  #topbar-nav {
    display: none;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    /* Forces the nav onto its own full-width row below the logo+toggle
       row, since header.topbar.topbar-brand is a flex row and this is
       its 3rd child -- flex-basis:100% is what actually triggers the
       wrap, gap alone would not. */
    flex-basis: 100%;
  }
  #topbar-nav.nav-open {
    display: flex;
  }
  #topbar-nav a {
    margin-right: 0;
    padding: 0.85rem 0.25rem;
    border-top: 1px solid var(--border);
    font-size: 1rem;
  }
  /* Responsive audit B4: the logout button was ~24px tall
     (padding:0.3rem 0.7rem), under the ~44px comfortable-tap minimum.
     Only overridden below 768px -- desktop keeps its original compact
     sizing untouched. */
  .topbar-logout-btn {
    padding: 0.65rem 1.1rem;
    font-size: 0.95rem;
  }
}

/* Reseller impersonation banner -- deliberately NOT the subtle translucent
   .alert-banner/.flash-error idiom used elsewhere on this page: a solid,
   saturated bar is the whole point here (a safety feature -- the operator
   must never mistake it for a dismissible notice). Sticky so it stays
   visible while scrolling a long page, since "impossible to miss" only
   holds if it can't scroll out of view. */
.impersonation-banner {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  background: var(--orange);
  color: #1a1000;
  font-weight: 600;
  font-size: 0.9rem;
  padding: 0.65rem 1.5rem;
  border-bottom: 2px solid #c2410c;
}

.impersonation-countdown {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-weight: 700;
}

.impersonation-exit-btn {
  background: #1a1000;
  color: var(--orange);
  border: none;
  padding: 0.4rem 1rem;
  border-radius: 4px;
  font-weight: 700;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}

.impersonation-exit-btn:hover {
  opacity: 0.85;
}

/* Responsive audit B3 -- SAFETY-RELEVANT, do not treat as a cosmetic
   tweak: this banner's whole purpose is that a super_admin can never
   mistake or miss that they're impersonating. Below 640px the unwrapped
   message/countdown/exit-button row would squeeze the sentence into an
   unreadably narrow column. Fix stacks all three full-width instead --
   message first (still the loud orange bar, still sticky, still exactly
   as unmissable), then the countdown, then the exit button spanning the
   FULL width of the banner -- deliberately the most prominent, easiest-
   to-thumb-tap shape available, not a small corner button. Desktop
   (>=640px) is completely unchanged; this block only ever applies below
   that width. */
@media (max-width: 640px) {
  .impersonation-banner {
    flex-wrap: wrap;
    align-items: stretch;
    gap: 0.6rem;
    padding: 0.85rem 1rem;
  }
  .impersonation-message {
    flex: 1 1 100%;
    line-height: 1.4;
  }
  .impersonation-countdown {
    flex: 1 1 100%;
    font-size: 1rem;
    text-align: center;
  }
  .impersonation-banner form {
    flex: 1 1 100%;
  }
  .impersonation-exit-btn {
    width: 100%;
    padding: 0.8rem 1rem;
    font-size: 0.95rem;
  }
}

main {
  padding: 1.5rem;
  max-width: 1100px;
  margin: 0 auto;
}

/* Responsive audit B4: general small-action-button tap-target bump
   (Archive/Unarchive, Remove, +Add segment, Approve & activate, form
   submits, etc.) -- everything in the page content area that's an
   ordinary button, under the ~44px comfortable-tap minimum today since
   no shared base `button` rule exists in this file at all (every button
   either relies on bare browser defaults or has its own specific class).
   Deliberately does NOT touch icon-only buttons (.template-preview-btn's
   👁️, the preview modal's × close button) -- both already set their own
   compact padding/font-size via a more specific class selector, which
   wins regardless of this rule's source order, so they're naturally
   unaffected without needing an explicit :not() exclusion. Only active
   below 640px; desktop button sizing is completely untouched. */
@media (max-width: 640px) {
  main button,
  main input[type="submit"],
  main input[type="button"] {
    min-height: 44px;
    padding: 0.6rem 1rem;
  }
}

h1 { font-size: 1.4rem; margin-bottom: 1rem; }
h2 { font-size: 1.1rem; margin: 1.5rem 0 0.75rem; }

/* Responsive audit B1: every dashboard <table> gets wrapped in this --
   without it, a table whose columns can't shrink to fit (most can't:
   names/statuses/dates/badges all need real space) forces the table
   wider than its box, and since nothing else constrains that overflow,
   the WHOLE PAGE gains horizontal scroll (nav bar included) rather than
   a clean scroll confined to just the table. This confines it to the
   table itself, the classic and simplest fix for the "wide table on a
   narrow phone" problem -- a full card-reflow (row -> stacked card) is a
   deliberately later, per-page decision (see the audit's own Hard/
   Rethink tier), not built here. `table` itself keeps width:100% below
   (fills the scroll container when content fits; scrolls when it
   doesn't), so this wrapper is additive, not a replacement. */
.table-scroll {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

table {
  width: 100%;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}

th, td {
  text-align: left;
  padding: 0.6rem 0.9rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}

th { color: var(--muted); font-weight: 600; text-transform: uppercase; font-size: 0.75rem; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: #1d212b; }

a.row-link { color: var(--text); text-decoration: none; }
a.row-link:hover { color: var(--accent); }

.badge {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 4px;
  border: 1px solid transparent;
  font-size: 0.75rem;
  font-weight: 600;
  margin-right: 0.3rem;
  /* Vertical gap for badge groups that wrap onto multiple lines (e.g. the
     User Activity table's per-recipient action list) -- margin-bottom
     rather than margin-top so it's consistent regardless of how many
     badges/rows exist, with no doubled-up gap between adjacent badges. */
  margin-bottom: 6px;
}
/* Unified status badge color hierarchy -- one semantic set shared across
   campaign, authorization, delivery-integration, and client/recipient
   status everywhere in the dashboard, rather than each template picking
   its own ad hoc combination. good=healthy/active/positive-defensive-
   adjacent, warn=needs attention/at risk, bad=broken/expired/failed,
   orange=paused/cancelled (deliberately distinct from both "healthy" and
   "broken" -- an operator paused this on purpose, it isn't an error),
   blue=a specific positive employee action (reporting a phish) worth
   visually distinguishing from the generic "good" used for account/
   integration health, neutral=inactive/archived/not-yet-configured. */
.badge.good { background: rgba(52,211,153,0.15); color: var(--good); border-color: rgba(52,211,153,0.4); }
.badge.warn { background: rgba(251,191,36,0.15); color: var(--warn); border-color: rgba(251,191,36,0.4); }
.badge.bad { background: rgba(248,113,113,0.15); color: var(--bad); border-color: rgba(248,113,113,0.4); }
.badge.orange { background: rgba(251,146,60,0.15); color: var(--orange); border-color: rgba(251,146,60,0.4); }
.badge.blue { background: rgba(96,165,250,0.15); color: var(--blue); border-color: rgba(96,165,250,0.4); }
.badge.neutral { background: rgba(156,163,175,0.15); color: var(--muted); border-color: rgba(156,163,175,0.4); }

/* Time-to-Report triad (Quickest/Median/Slowest) -- Speed of Response
   table and Vector Performance Heatmap cells. Deliberately opaque light
   chips (not the translucent-on-dark-page .badge.* palette above), since
   these need to stay legible sitting on top of an arbitrarily colored
   heatmap cell background (green/amber/red/gray resilience status), not
   just the app's own dark page background -- a translucent tint would
   blend into whatever color is underneath and could lose contrast
   entirely on a same-hue cell. Text colors chosen for >=4.5:1 contrast
   against their own chip background (WCAG AA, small bold text). */
.ttr-badge {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: 3px;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1.4;
}
.ttr-badge-quickest { background: #dcfce7; color: #15803d; }
.ttr-badge-median { background: #fef3c7; color: #92400e; }
.ttr-badge-slowest { background: #fee2e2; color: #b91c1c; }

/* Client Overview header -- navigation actions and status alerts are
   deliberately separate containers (not one card with float:right links
   sitting beside badges), so a page with zero active alerts doesn't
   render an empty-looking box and a page WITH alerts doesn't visually
   compete with the nav links for attention. */
.action-toolbar {
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}
.action-toolbar a {
  display: inline-block;
  color: var(--text);
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 600;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.5rem 0.9rem;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.action-toolbar a:hover { border-color: var(--accent); color: var(--accent); }

/* Archival System's Active/Archived/All filter tabs (clients.html,
   client_detail.html's per-client campaign table, template_library.html)
   -- plain server-rendered links (a query-param GET, not JS), so the
   filter state is always a real, bookmarkable/shareable URL rather than
   client-side-only state. */
.filter-tabs { display: flex; gap: 0.4rem; margin-bottom: 1rem; }
.filter-tabs a {
  display: inline-block;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.filter-tabs a:hover { color: var(--text); }
.filter-tabs a.active { color: var(--accent); background: rgba(96,165,250,0.12); border-color: rgba(96,165,250,0.35); }
.alert-banner {
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--warn);
  border-radius: 8px;
  padding: 0.75rem 1.25rem;
  margin-bottom: 1rem;
}

.stat-row { display: flex; gap: 1rem; margin-bottom: 1rem; }
.stat-tile {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1rem;
  flex: 1;
}
.stat-tile .value { font-size: 1.6rem; font-weight: 700; }
.stat-tile .label { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; }

form label { display: block; margin-bottom: 0.3rem; font-size: 0.85rem; color: var(--muted); }
form input[type=text], form input[type=email], form input[type=number], form input[type=password], form select {
  width: 100%;
  padding: 0.5rem;
  margin-bottom: 1rem;
  background: #0f1115;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
}

/* Same field styling as inside a real <form> (above), scoped to modal
   content instead -- the AI Generator overlay (campaign_new.html) lives
   outside the page's <form> element (a modal is a sibling, not nested
   inside it, same as the pre-existing template-preview modal), so its
   own labeled inputs/selects would otherwise fall back to unstyled
   browser defaults. */
.template-preview-modal label { display: block; margin-bottom: 0.3rem; font-size: 0.85rem; color: var(--muted); }
.template-preview-modal input[type=text], .template-preview-modal select {
  width: 100%;
  padding: 0.5rem;
  margin-bottom: 1rem;
  background: #0f1115;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
}

form.auth-form {
  max-width: 340px;
  margin: 4rem auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 2rem;
}
form.auth-form button, button.primary, form button[type=submit] {
  background: var(--accent);
  color: white;
  border: none;
  padding: 0.55rem 1rem;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
}
/* Every "Generate with AI" trigger across the app shares this one class --
   AI generation is a primary/encouraged action, so it always renders
   large and prominent (same look as button.primary) regardless of which
   surface it's triggered from (the Primary template slot, a segment row,
   or any future AI-generate surface). A dedicated class rather than just
   reusing button.primary directly, so this stays locked to "the AI
   button's look" even if the generic .primary style is ever changed for
   unrelated reasons -- one style can't silently drift out of sync with
   the others again. */
button.ai-generate-trigger {
  background: var(--accent);
  color: white;
  border: none;
  padding: 0.55rem 1rem;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
}
.error-message { color: var(--bad); font-size: 0.85rem; margin-bottom: 1rem; }
.flash-success {
  background: rgba(52,211,153,0.15);
  color: var(--good);
  border: 1px solid rgba(52,211,153,0.4);
  border-radius: 6px;
  padding: 0.6rem 0.9rem;
  font-size: 0.85rem;
  margin-bottom: 1rem;
}
.flash-error {
  background: rgba(248,113,113,0.15);
  color: var(--bad);
  border: 1px solid rgba(248,113,113,0.4);
  border-radius: 6px;
  padding: 0.6rem 0.9rem;
  font-size: 0.85rem;
  margin-bottom: 1rem;
}
/* Content-flow investigation Finding 4 (build): same shape as .flash-error/
   .flash-success, --warn instead -- for informational, non-blocking
   notices (e.g. campaign_new.html's early authorization-coverage warning)
   that shouldn't read as a hard error before the user's even submitted
   anything. */
.flash-warning {
  background: rgba(251,191,36,0.15);
  color: var(--warn);
  border: 1px solid rgba(251,191,36,0.4);
  border-radius: 6px;
  padding: 0.6rem 0.9rem;
  font-size: 0.85rem;
  margin-bottom: 1rem;
}

.muted { color: var(--muted); }
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1rem 1.25rem;
  margin-bottom: 1rem;
}

/* Unified settings save bar (app/static/unified-save-bar.js) -- a centered
   floating toast/dock, not a full-width bar pinned to the raw viewport
   edge, so it reads as a deliberate overlay rather than part of the page
   layout. Hidden until a tracked form actually has unsaved changes.
   ".has-unified-save-bar" is added to <body> by the script itself (not a
   :has() selector) so the bottom-padding reservation doesn't depend on
   :has() support. */
body.has-unified-save-bar { padding-bottom: 5.5rem; }
.unified-save-bar {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  background: rgba(23, 26, 33, 0.85);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.75rem 1rem 0.75rem 1.5rem;
  box-shadow: 0 12px 32px rgba(0,0,0,0.45);
  transform: translateX(-50%) translateY(150%);
  opacity: 0;
  transition: transform 0.18s ease-out, opacity 0.18s ease-out;
  z-index: 1000;
}
.unified-save-bar.visible { transform: translateX(-50%) translateY(0); opacity: 1; }
.unified-save-bar-status { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }
.unified-save-bar-cancel {
  background: none;
  border: 1px solid var(--border);
  color: var(--text);
  padding: 0.55rem 1rem;
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.9rem;
}
.unified-save-bar-save { border-radius: 999px; }

/* Past-versions disclosure inside the Operational Ledger card -- every
   "Generate report"/"Regenerate" click writes a new timestamped PDF and a
   new Report row (app/services/report_engine.py's generate_report never
   overwrites a file or mutates an existing row), so nothing here is ever
   destroyed; this is just a compact way to reach an older snapshot without
   a full history table competing with the card grid for attention. */
.artifact-past-versions {
  margin-top: 0.6rem;
  font-size: 0.78rem;
}
.artifact-past-versions summary {
  color: var(--muted);
  cursor: pointer;
  list-style: none;
}
.artifact-past-versions summary::-webkit-details-marker { display: none; }
.artifact-past-versions summary::before {
  content: "▸ ";
  font-size: 0.7em;
}
.artifact-past-versions[open] summary::before { content: "▾ "; }
.artifact-past-versions summary:hover { color: var(--text); }
.artifact-past-versions ul {
  list-style: none;
  margin: 0.4rem 0 0 0;
  padding: 0;
}
.artifact-past-versions li { margin-bottom: 0.25rem; }
.artifact-past-versions li a {
  color: var(--muted);
  font-size: 0.78rem;
}
.artifact-past-versions li a:hover { color: var(--text); }

/* Reporting Artifacts grid -- mirrors the 4-card audience layout on the
   marketing site (marketing/index.html's #reporting section): same amber
   badge treatment, same "one audience per card" structure, so an operator
   who's seen the marketing page recognizes this immediately. */
.report-artifact-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.85rem;
  margin-top: 0.85rem;
}
@media (max-width: 1100px) {
  .report-artifact-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .report-artifact-grid { grid-template-columns: 1fr; }
}

.report-artifact {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.1rem 1.1rem 1rem 1.1rem;
  background: linear-gradient(180deg, var(--panel) 0%, var(--bg) 100%);
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.report-artifact:hover {
  border-color: var(--warn);
  transform: translateY(-2px);
}

.artifact-badge {
  display: inline-block;
  align-self: flex-start;
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--warn);
  background: rgba(251, 191, 36, 0.12);
  border: 1px solid rgba(251, 191, 36, 0.35);
  border-radius: 999px;
  padding: 0.22rem 0.65rem;
  margin-bottom: 0.65rem;
}
.artifact-title {
  font-weight: 600;
  font-size: 0.98rem;
  color: var(--text);
  margin-bottom: 0.4rem;
}
.artifact-description {
  color: var(--muted);
  font-size: 0.84rem;
  line-height: 1.45;
  flex-grow: 1;
  margin: 0 0 0.85rem 0;
}
.artifact-note {
  color: var(--muted);
  font-size: 0.76rem;
  line-height: 1.4;
  margin: 0 0 0.6rem 0;
}

/* margin-top: auto pins this row to the bottom of the card regardless of
   how much description/note text sits above it, so every card's action
   row lines up horizontally across the grid. */
.artifact-actions {
  margin-top: auto;
  padding-top: 0.65rem;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.artifact-actions .btn-download {
  display: inline-block;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--bg);
  background: var(--warn);
  border-radius: 5px;
  padding: 0.42rem 0.8rem;
  text-decoration: none;
}
.artifact-actions .btn-download:hover { opacity: 0.88; }
.artifact-actions .btn-download-secondary {
  font-size: 0.8rem;
  color: var(--muted);
  text-decoration: underline;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
}
.artifact-actions .btn-download-secondary:hover { color: var(--text); }

.artifact-unavailable {
  margin-top: auto;
  padding-top: 0.65rem;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.82rem;
}

/* AI generator staged progress indicator (campaign_new.html) -- the
   backend call behind this is a single synchronous request/response with
   no real multi-step signal to report on, so the step labels below are a
   client-side simulated progression to reduce perceived wait time, not a
   claim that the server actually told us which step it's on. The bar
   itself still only ever reaches 100% when the real fetch() resolves --
   the simulated steps advance the label and a partial fill, never the
   completion state itself. */
.ai-generate-progress { margin-top: 0.75rem; }
.ai-generate-progress-track {
  width: 100%;
  height: 6px;
  background: var(--border);
  border-radius: 3px;
  overflow: hidden;
}
.ai-generate-progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 3px;
  width: 8%;
  transition: width 0.4s ease-out;
}
.ai-generate-progress-label {
  margin-top: 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Template preview (👁️) button + modal -- campaign_detail.html's Waves
   table and Template Bucket rows. */
.template-preview-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.85rem;
  padding: 0 0.2rem;
  line-height: 1;
  vertical-align: middle;
}
.template-preview-btn:hover { transform: scale(1.15); }
.template-preview-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}
.template-preview-modal {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  max-width: 960px;
  width: 100%;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
}
.template-preview-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--border);
}
.template-preview-header button {
  background: none;
  border: none;
  color: var(--text);
  font-size: 1.3rem;
  cursor: pointer;
  line-height: 1;
}
.template-preview-body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  padding: 1rem;
  overflow-y: auto;
}
.template-preview-body iframe {
  width: 100%;
  height: 55vh;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: #fff;
  display: block;
}
@media (max-width: 720px) {
  .template-preview-body { grid-template-columns: 1fr; }
}

/* AI-generation overlay's own scrollable body (campaign_new.html) --
   .template-preview-modal is a max-height:90vh flex column, but its
   generated-draft content (fields + a full subject/HTML/landing-page
   split-view editor with two 520px preview iframes) regularly runs to
   1500-2000px tall, well past that cap. Without overflow-y:auto here the
   overflow was simply invisible -- clipped by the viewport with no way to
   reach it, including the Save/Discard buttons at the bottom (a real
   blocker, not just a cosmetic clip). overflow-y:auto on this flex ITEM
   also resolves its automatic min-height to 0 per the flexbox spec (the
   same reason .template-preview-body's identical overflow-y:auto already
   works as a flex child above) -- min-height:0 is added explicitly too,
   redundant but explicit rather than relying on that spec detail alone. A
   plain block div (not .template-preview-body's grid) since this modal's
   content is a single-column form, not a side-by-side split. */
.ai-generate-body {
  padding: 1rem;
  overflow-y: auto;
  min-height: 0;
}
/* The draft's Save/Discard actions (plus the save-progress bar) are a
   fixed footer BELOW the scrolling draft content, structurally -- not a
   sticky element nested inside .ai-generate-body's own scroll. Two
   earlier approaches were tried and abandoned here, in order:
     1. position:sticky nested inside .ai-generate-body, opaque background
        + z-index alone -- didn't reliably stop a sandboxed srcdoc
        <iframe> sibling (the draft's own email/landing-page previews)
        from bleeding through during scroll.
     2. The same sticky element additionally promoted onto its own GPU
        compositing layer (transform:translateZ(0)/will-change:transform/
        backface-visibility:hidden alongside a higher z-index) -- verified
        clean in an isolated standalone reproduction, but the bleed still
        recurred against a REAL template's full-bleed gradient-background
        iframe in the actual deployed modal. An <iframe> sharing scroll/
        paint space with a sticky sibling turned out not to be something
        layer promotion can be trusted to win against in every case.
   This is the structural fix instead: campaign_new.html now renders this
   element (id="ai-draft-actions") as a THIRD flex child of
   .template-preview-modal, a SIBLING of .ai-generate-body rather than
   nested inside it -- see that markup for the visibility-toggle notes
   (phishshieldResetAIDraftUI/phishshieldGenerateAIPackage show/hide this
   element directly now, since it's no longer inside #ai-draft-section).
   Structurally, this element and the draft's iframes are never in the
   same scrolling/paint region at all, so there is no compositing contest
   to win or lose -- no iframe pixels can ever be "behind" it. Ordinary
   block-level footer, no position/z-index/transform of any kind needed.
   flex-shrink:0 is explicit (not relied on as emergent behavior) so this
   never gets visually squeezed if the modal's content is tall enough to
   need .ai-generate-body's own shrink-to-scroll behavior to kick in.
   padding matches .template-preview-header's own 0.85rem/1rem exactly,
   for the same left/right visual inset the header already establishes
   (no negative-margin/parent-padding-cancelling trick needed anymore --
   this element isn't nested inside .ai-generate-body's own padding box,
   so there's nothing to cancel out; it naturally spans the modal's full
   width like the header does). */
.ai-draft-actions {
  flex-shrink: 0;
  background: var(--panel);
  padding: 0.85rem 1rem;
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 8px -4px rgba(0, 0, 0, 0.25);
}

/* Multi-Vector Template Bucket cards -- shared visual language for the
   Primary row and every cloned bucket row (campaign_new.html). Each row is
   now a fully self-contained card (Template/Vector/Landing page all live
   here, not split off into top-of-form controls), so this replaced the old
   plain inline-flex row. Uses --bg (darker than the outer <form class="card">'s
   own --panel background) rather than --panel, so a card actually reads as
   nested/inset against the form instead of blending into an identical
   background color. */
.bucket-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  margin-bottom: 0.75rem;
}
.bucket-card-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}
.bucket-row-badge {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 4px;
  border: 1px solid rgba(96, 165, 250, 0.35);
  background: rgba(96, 165, 250, 0.12);
  color: var(--blue);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
}
.bucket-vector-badge {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 4px;
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
}
.bucket-vector-badge:empty { display: none; }
.bucket-card-fields {
  display: grid;
  grid-template-columns: 2fr 1fr 1.5fr;
  gap: 0.75rem;
  align-items: end;
}
/* Bucket rows (not Primary) get a 4th column for Department-Based
   Routing's optional "Target departments" field -- Primary has no
   backing CampaignTemplateBucketItem row to store a filter on, so it
   keeps the original 3-column layout. */
.bucket-card-fields-with-dept {
  grid-template-columns: 1.8fr 0.9fr 1.3fr 1.3fr;
}
.bucket-card-field label {
  margin-bottom: 0.3rem;
}
.bucket-card-field select,
.bucket-card-field input[type=text] {
  margin-bottom: 0;
}
.bucket-card-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.6rem;
}
.bucket-row-targets {
  font-size: 0.75rem;
  margin-top: 0.5rem;
  min-height: 1em;
}
@media (max-width: 640px) {
  .bucket-card-fields,
  .bucket-card-fields-with-dept { grid-template-columns: 1fr; }
}

/* Responsive audit B2: template_form.html's split-view (textarea + live
   iframe preview, one pair for email, one for landing page). The
   surrounding flex-wrap already stacks these vertically below ~660px
   (no change needed there) -- what's left is that each pair's fixed
   rows="20" textarea + 520px iframe, stacked twice on one page, adds up
   to roughly 2,000px of scroll on a phone. .editor-iframe's 520px base
   rule below reproduces the exact value that used to be inline (zero
   visual change at any width until the mobile override applies);
   .editor-textarea has no base height rule at all -- desktop keeps
   relying on rows="20" exactly as before, only the mobile override
   introduces an explicit height. */
.editor-iframe {
  height: 520px;
}
@media (max-width: 640px) {
  .editor-textarea {
    height: 220px;
  }
  .editor-iframe {
    height: 260px;
  }
}

/* Control-code chip tagging (Phase 5, Track 3) -- template_form.html.
   Pill shape/blue tint matches campaign_index.html's control-code
   .badge.blue chips so the same tag reads consistently whether it's being
   authored here or displayed read-only on a campaign's Summary page. */
.control-code-chip-container {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  min-height: 1.6rem;
  margin-bottom: 0.75rem;
}
.control-code-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  background: rgba(96, 165, 250, 0.12);
  border: 1px solid rgba(96, 165, 250, 0.35);
  color: var(--blue);
  font-size: 0.8rem;
  font-weight: 600;
}
.control-code-chip button {
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
  padding: 0;
}
.control-code-chip button:hover { color: var(--bad); }
