/* Onbixo dashboard design system.
   Tokens are CSS custom properties that flip on the root data-theme attribute
   (light / dark / auto). Auto follows prefers-color-scheme. WCAG-contrast palette.
   Self-hosted; no external fonts or CDNs (strict CSP). */

:root {
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* light theme tokens (default) */
  --bg: #f6f8fb;
  --surface: #ffffff;
  --surface-2: #f0f3f8;
  --border: #e2e7ef;
  --border-strong: #cdd5e1;
  --ink: #16202e;
  --ink-soft: #4a5567;
  --ink-faint: #7a8698;
  /* Brand accent - the Onbixo logo indigo/blurple. Light theme uses the deeper indigo (#4F46E5)
     for AA contrast on white; dark theme uses the lighter blurple (#9184D9). hover/pressed tune the
     button + link interaction states. All pairings verified WCAG AA in both themes. */
  --accent: #4F46E5;
  --accent-ink: #ffffff;
  --accent-soft: #ECEBFB;
  --accent-hover: #4338CA;
  --accent-pressed: #3730A3;
  --good: #1f9d63;
  --warn: #b5751a;
  --crit: #d5442f;
  --radius: 10px;
  --radius-sm: 7px;
  --shadow: 0 1px 2px rgba(16,32,46,.04), 0 6px 20px rgba(16,32,46,.06);
  --sidebar-w: 236px;
  --sidebar-mini: 64px; /* collapsed icons-only rail (Google-Cloud style) */
}

@media (prefers-color-scheme: dark) {
:root {
    --bg: #0c111b; --surface: #131a27; --surface-2: #0f1622; --border: #232d3d;
    --border-strong: #303d52; --ink: #e6ecf5; --ink-soft: #a7b2c4; --ink-faint: #6d7a8f;
    --accent: #9184D9; --accent-ink: #0c111b; --accent-soft: #211f3a;
    --accent-hover: #A99EE3; --accent-pressed: #7A6DC7;
    --good: #48c98a; --warn: #e0a94a; --crit: #f0745e;
    --shadow: 0 1px 2px rgba(0,0,0,.3), 0 8px 26px rgba(0,0,0,.35);
  }
}
/* explicit overrides win over the media query in both directions */
:root[data-theme="light"] {
  --bg: #f6f8fb; --surface: #fff; --surface-2: #f0f3f8; --border: #e2e7ef;
  --border-strong: #cdd5e1; --ink: #16202e; --ink-soft: #4a5567; --ink-faint: #7a8698;
  --accent: #4F46E5; --accent-ink: #fff; --accent-soft: #ECEBFB;
  --accent-hover: #4338CA; --accent-pressed: #3730A3;
  --good: #1f9d63; --warn: #b5751a; --crit: #d5442f;
  --shadow: 0 1px 2px rgba(16,32,46,.04), 0 6px 20px rgba(16,32,46,.06);
}
:root[data-theme="dark"] {
  --bg: #0c111b; --surface: #131a27; --surface-2: #0f1622; --border: #232d3d;
  --border-strong: #303d52; --ink: #e6ecf5; --ink-soft: #a7b2c4; --ink-faint: #6d7a8f;
  --accent: #9184D9; --accent-ink: #0c111b; --accent-soft: #211f3a;
  --accent-hover: #A99EE3; --accent-pressed: #7A6DC7;
  --good: #48c98a; --warn: #e0a94a; --crit: #f0745e;
  --shadow: 0 1px 2px rgba(0,0,0,.3), 0 8px 26px rgba(0,0,0,.35);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--font); font-size: 14px; line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
h1 { font-size: 22px; letter-spacing: -.02em; margin: 0 0 4px; }
h2 { font-size: 16px; letter-spacing: -.01em; margin: 0; }
.muted { color: var(--ink-soft); }
/* small spacing utilities (CSP forbids inline style="" - style-src 'self') */
.m-0 { margin: 0; }
.mt-1 { margin-top: 4px; }
.mt-2 { margin-top: 8px; }
.mt-3 { margin-top: 12px; }
.mt-4 { margin-top: 16px; }
.mt-tight { margin: 2px 0 0; }
/* Small component styles that replaced inline style attributes (same CSP rule as above). */
.support-carryover-note { margin: -4px 0 4px; }
.signup-brand-mark { color: #e9e9ed; display: inline-flex; }
kbd {
  font-family: var(--mono); font-size: 11px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 5px; padding: 1px 6px;
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* focused main region should not draw its own ring (skip-link target) */
#main-content:focus { outline: none; }
/* skip-to-content link: off-screen until focused, then pinned top-left */
.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  background: var(--surface); color: var(--ink); border: 1px solid var(--border);
  border-radius: 8px; padding: 9px 14px; font-size: 13px; font-weight: 600;
  box-shadow: var(--shadow); transform: translateY(-160%); transition: transform .15s;
}
.skip-link:focus { transform: none; }

/* ---- app shell ---- */
.app-shell { display: flex; min-height: 100vh; }
.app-sidebar {
  width: var(--sidebar-w); flex: none; background: var(--surface);
  border-right: 1px solid var(--border); display: flex; flex-direction: column;
  padding: 14px 12px;
  /* The sidebar is a full-height flex column that stretches to the shell (so its
     background never leaves a blank gap when content is short OR taller than the
     viewport). A sticky inner wrapper keeps the nav in view while the page scrolls;
     see .sidebar-inner. */
  align-self: stretch;
}
/* wrapper that actually holds the nav; sticks to the top of the viewport and caps
   its own height so a long nav can scroll internally without breaking the layout */
.sidebar-inner {
  display: flex; flex-direction: column; flex: 1;
  position: sticky; top: 14px; max-height: calc(100vh - 28px);
}
/* ---- collapsed = a narrow icons-only mini rail (Google-Cloud style), NOT hidden ---- */
.app-shell.sidebar-collapsed .app-sidebar { width: var(--sidebar-mini); padding: 14px 8px; }
/* brand shrinks to just the mark; hide the wordmark */
.app-shell.sidebar-collapsed .brand-name { display: none; }
.app-shell.sidebar-collapsed .sidebar-brand { justify-content: center; padding: 6px 0 16px; }
/* nav items become centered icon squares; labels + counts + soon tags + subnavs hide */
.app-shell.sidebar-collapsed .nav-item { justify-content: center; padding: 9px 0; gap: 0; }
.app-shell.sidebar-collapsed .nav-label,
.app-shell.sidebar-collapsed .nav-soon,
.app-shell.sidebar-collapsed .nav-subnav,
.app-shell.sidebar-collapsed .ws-name,
.app-shell.sidebar-collapsed .ws-role,
.app-shell.sidebar-collapsed .ws-caret,
.app-shell.sidebar-collapsed .user-meta,
.app-shell.sidebar-collapsed .user-caret,
.app-shell.sidebar-collapsed .sidebar-usage-head,
.app-shell.sidebar-collapsed .sidebar-usage-sub,
.app-shell.sidebar-collapsed .usage-bar { display: none; }
/* switcher + user menu collapse to just their avatar, centered as square tap targets */
.app-shell.sidebar-collapsed .ws-current,
.app-shell.sidebar-collapsed .user-current { justify-content: center; padding: 6px; gap: 0; }
.app-shell.sidebar-collapsed .ws-switcher { margin: 0 0 10px; display: flex; justify-content: center; }
.app-shell.sidebar-collapsed .user-menu { margin: 0; width: 100%; display: flex; justify-content: center; }
/* uniform avatar size across switcher + user in the rail */
.app-shell.sidebar-collapsed .ws-avatar,
.app-shell.sidebar-collapsed .user-avatar { width: 30px; height: 30px; }
/* collapsed: the trigger is just the avatar, so show the identity header in the dropdown */
.app-shell.sidebar-collapsed .user-dropdown-head { display: block; }
/* the dropdowns should still be usable when open in the mini rail: give them a real width and
   let them overhang the narrow rail instead of being clamped to it */
.app-shell.sidebar-collapsed .ws-dropdown,
.app-shell.sidebar-collapsed .user-dropdown { left: 0; right: auto; width: 216px; }
/* the foot (usage + user menu) centers its items on the same axis as the nav icons */
.app-shell.sidebar-collapsed .sidebar-foot { padding: 8px 0; display: flex; flex-direction: column; align-items: center; gap: 8px; }
/* non-owner plan-only footer: a plain read-only pill (no MAU, no link) */
.sidebar-usage-plan { display: block; padding: 6px 8px; }
/* usage footer collapses to a compact chip (the % when metered, else a plan initial), centered */
.sidebar-usage-mini { display: none; }
/* in the collapsed rail the non-owner pill hides and its mini chip shows instead */
.app-shell.sidebar-collapsed .sidebar-usage-plan .plan-pill { display: none; }
.app-shell.sidebar-collapsed .sidebar-usage {
  display: grid; place-items: center; padding: 6px 0; border-radius: 9px; width: 100%;
}
.app-shell.sidebar-collapsed .sidebar-usage-mini {
  display: grid; place-items: center; width: 40px; height: 24px;
  border-radius: 7px; background: var(--surface-2);
  color: var(--ink-soft); font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
}
/* carry the near/over warning colours onto the collapsed chip so the state still reads */
.app-shell.sidebar-collapsed .sidebar-usage.is-near .sidebar-usage-mini {
  color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, var(--border));
}
.app-shell.sidebar-collapsed .sidebar-usage.is-over .sidebar-usage-mini {
  color: var(--crit); border-color: color-mix(in srgb, var(--crit) 45%, var(--border));
}

/* ---- hover tooltip on a collapsed rail item (pure CSS, no JS) ---- */
.app-shell.sidebar-collapsed .nav-item[data-tip],
.app-shell.sidebar-collapsed .sidebar-usage[data-tip],
.app-shell.sidebar-collapsed .ws-current[data-tip],
.app-shell.sidebar-collapsed .user-current[data-tip] { position: relative; }
.app-shell.sidebar-collapsed .nav-item[data-tip]::after,
.app-shell.sidebar-collapsed .sidebar-usage[data-tip]::after,
.app-shell.sidebar-collapsed .ws-current[data-tip]::after,
.app-shell.sidebar-collapsed .user-current[data-tip]::after {
  content: attr(data-tip);
  position: absolute; left: calc(100% + 10px); top: 50%; transform: translateY(-50%);
  background: var(--ink); color: var(--surface); font-size: 12px; font-weight: 550;
  padding: 4px 8px; border-radius: 6px; white-space: nowrap; pointer-events: none;
  opacity: 0; z-index: 60; box-shadow: var(--shadow);
  transition: opacity .12s ease;
}
.app-shell.sidebar-collapsed .nav-item[data-tip]:hover::after,
.app-shell.sidebar-collapsed .sidebar-usage[data-tip]:hover::after,
.app-shell.sidebar-collapsed .ws-current[data-tip]:hover::after,
.app-shell.sidebar-collapsed .user-current[data-tip]:hover::after { opacity: 1; }
/* don't show the hover tooltip while that menu's dropdown is open (avoids overlap clutter) */
.app-shell.sidebar-collapsed .ws-switcher.is-open .ws-current[data-tip]::after,
.app-shell.sidebar-collapsed .user-menu.is-open .user-current[data-tip]::after { content: none; }

.app-shell.sidebar-collapsed .app-sidebar[hidden] { display: none; }
/* backdrop behind the mobile sidebar drawer (hidden on desktop) */
.sidebar-backdrop { display: none; }
.sidebar-brand { display: flex; align-items: center; gap: 7px; font-weight: 700; padding: 6px 8px 16px;
  color: var(--ink); text-decoration: none; border-radius: 8px; }
.sidebar-brand:hover { text-decoration: none; }
.sidebar-brand:hover .brand-name { color: var(--accent); }
.sidebar-brand:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Brand mark: the real Onbixo logo (three stacked bars, indigo accent bar). Theme-specific
   variants keep the ink bars readable on either ground; the file swaps under prefers-color-scheme
   and the explicit data-theme overrides (which win over the media query in both directions). */
.brand-mark, .auth-mark {
  width: 24px; height: 24px; flex: none;
  background: url('/assets/img/onbixo-mark-light-indigo.svg') center / contain no-repeat;
}
@media (prefers-color-scheme: dark) {
  .brand-mark, .auth-mark { background-image: url('/assets/img/onbixo-mark-dark-indigo.svg'); }
}
:root[data-theme="dark"] .brand-mark,
:root[data-theme="dark"] .auth-mark { background-image: url('/assets/img/onbixo-mark-dark-indigo.svg'); }
:root[data-theme="light"] .brand-mark,
:root[data-theme="light"] .auth-mark { background-image: url('/assets/img/onbixo-mark-light-indigo.svg'); }
.sidebar-nav { display: flex; flex-direction: column; gap: 2px; }
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 11px; border-radius: 8px;
  color: var(--ink-soft); font-weight: 550; text-decoration: none;
}
.nav-item:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.nav-item.is-active { background: var(--accent-soft); color: var(--accent); }
/* disabled nav item (e.g. Analytics "coming soon"): muted, not clickable, with a Soon tag */
.nav-item.is-disabled { color: var(--ink-faint); cursor: default; }
.nav-item.is-disabled:hover { background: none; color: var(--ink-faint); }
.nav-soon {
  margin-left: auto; font-size: 9.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-faint); background: var(--surface-2);
  border-radius: 999px; padding: 1px 6px;
}
.nav-ico {
  flex: none; width: 22px; height: 22px; display: grid; place-items: center; color: inherit;
}
.nav-ico .ico-svg { display: block; }
.nav-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* sub-navigation under a nav item (e.g. Flows views), indented with a left rail */
.nav-subnav {
  display: flex; flex-direction: column; gap: 1px;
  margin: 2px 0 4px 22px; padding-left: 10px; border-left: 1px solid var(--border);
}
.nav-subitem {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 10px; border-radius: 7px; font-size: 13px;
  color: var(--ink-faint); text-decoration: none;
}
.nav-subitem:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.nav-subitem.is-active { color: var(--accent); font-weight: 600; }
.nav-subitem-count {
  font-size: 11px; font-variant-numeric: tabular-nums; color: var(--ink-faint);
  background: var(--surface-2); border-radius: 999px; padding: 0 7px; min-width: 20px; text-align: center;
}
.nav-subitem.is-active .nav-subitem-count { color: var(--accent); background: var(--accent-soft); }

/* ---- admin console shell ---- */
.admin-badge {
  margin-left: auto; font-size: 9.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--accent); background: var(--accent-soft);
  border-radius: 999px; padding: 2px 8px;
}
/* The admin nav is long (many groups). Make IT the scrollable region so the brand stays pinned at
   the top and the footer (Back to app / identity) stays pinned at the bottom, while the nav scrolls
   internally - instead of the whole sidebar scrolling away with the page. min-height:0 lets the
   flex child actually shrink so overflow-y engages. */
.admin-sidebar .sidebar-nav { gap: 1px; flex: 1; min-height: 0; overflow-y: auto; }
.admin-sidebar .sidebar-brand { flex: none; }
.nav-group-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-faint); padding: 14px 11px 5px;
}
.admin-sidebar .sidebar-foot { flex: none; display: flex; flex-direction: column; gap: 6px; }
.admin-back { border: 1px solid var(--border); }
.admin-identity {
  display: flex; align-items: center; gap: 10px; padding: 8px 11px; border-radius: 8px;
  color: var(--ink-soft);
}
.admin-identity .user-avatar { flex: none; }
.app-shell.sidebar-collapsed .nav-group-label { text-align: center; padding: 10px 0 4px; font-size: 8.5px; }
.app-shell.sidebar-collapsed .admin-identity { justify-content: center; padding: 8px 0; gap: 0; }
.app-shell.sidebar-collapsed .admin-identity .user-meta { display: none; }
.app-shell.sidebar-collapsed .admin-badge { display: none; }

/* ---- impersonation banner (shown in the app while a staff member is viewing as a user) ---- */
.impersonation-banner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 16px; background: color-mix(in srgb, var(--warn) 16%, var(--surface));
  border-bottom: 1px solid color-mix(in srgb, var(--warn) 45%, var(--border));
  color: var(--ink); font-size: 13px;
}
.impersonation-banner .imp-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--warn); flex: none;
}
.impersonation-banner .imp-text { min-width: 0; }
.impersonation-banner .imp-exit { margin-left: auto; }

/* ---- admin overview ---- */
.admin-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; }
@media (max-width: 720px) { .admin-cols { grid-template-columns: 1fr; } }
.signup-figures { display: flex; gap: 24px; flex-wrap: wrap; margin: 4px 0 14px; }
.signup-figures .sf-num { display: block; font-size: 22px; font-weight: 750; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.signup-figures .sf-lbl { font-size: 12px; color: var(--ink-faint); }
.sparkline { display: block; width: 100%; }
.spark-line { stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.spark-area { fill: var(--accent-soft); stroke: none; }
.spark-caption { font-size: 12px; margin-top: 6px; }

/* ---- analytics charts + bars ---- */
.big-chart { display: block; width: 100%; }
.chart-area { fill: var(--accent-soft); stroke: none; }
.chart-line { stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chart-line-green { stroke: var(--good); }
.chart-line-purple { stroke: color-mix(in srgb, var(--accent) 50%, #8b5cf6); }
.plan-bars { display: flex; flex-direction: column; gap: 12px; }
.plan-bar-row { display: grid; grid-template-columns: 120px 1fr auto; gap: 12px; align-items: center; font-size: 13px; }
.plan-bar-label { color: var(--ink-soft); }
.plan-bar-track { height: 8px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.plan-bar-fill { height: 100%; border-radius: 999px; background: var(--accent); }
.plan-bar-val { font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 560px) { .plan-bar-row { grid-template-columns: 90px 1fr; } .plan-bar-val { grid-column: 2; text-align: right; } }
.health-strip { display: flex; gap: 12px; flex-wrap: wrap; }
.health-item {
  display: flex; flex-direction: column; gap: 2px; min-width: 120px; flex: 1;
  padding: 12px 14px; border-radius: 12px; border: 1px solid var(--border);
  background: var(--surface); text-decoration: none; color: var(--ink);
}
.health-item:hover { border-color: var(--ink-faint); text-decoration: none; }
.health-item.is-warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--border)); background: color-mix(in srgb, var(--warn) 8%, var(--surface)); }
.health-num { font-size: 22px; font-weight: 750; font-variant-numeric: tabular-nums; }
.health-item.is-warn .health-num { color: var(--warn); }
.health-lbl { font-size: 12px; color: var(--ink-faint); }

/* ---- admin date-range picker (presets + custom from/to) ---- */
.date-range { display: flex; flex-direction: column; gap: 10px; }
/* Range picker used bare on a page (no wrapping panel), right-aligned above the content it filters.
   .date-range is a COLUMN flex, so align-items moves the tab row and the custom panel to the end. */
.range-bar { margin: -4px 0 16px; }
.range-bar .date-range { align-items: flex-end; }
.range-bar .date-range-custom { justify-content: flex-end; }
@media (max-width: 560px) {
  /* Narrow: let the tabs wrap from the left instead of hanging off the right edge. */
  .range-bar .date-range { align-items: stretch; }
  .range-bar .date-range-custom { justify-content: flex-start; }
}
.date-range-custom { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
/* [hidden] must beat the display:flex above (documented codebase trap) */
.date-range-custom[hidden] { display: none; }
.date-range-field { display: flex; flex-direction: column; gap: 3px; font-size: 12px; color: var(--ink-faint); }
.date-range-field input[type="date"] {
  font: inherit; font-size: 13px; padding: 6px 9px; border-radius: 8px; color: var(--ink);
  border: 1px solid var(--border); background: var(--surface); color-scheme: light dark;
}

/* ---- admin tables / lists / detail ---- */
.pill-accent { background: var(--accent-soft); color: var(--accent); }
.cell-strong { font-weight: 600; color: var(--ink); text-decoration: none; }
.cell-strong:hover { text-decoration: underline; }
.cell-sub { font-size: 12px; margin-top: 1px; }
/* A small explanatory line at the foot of a panel (below the panel's content), separated from it. */
.panel-foot-note { font-size: 12.5px; margin: 12px 0 0; line-height: 1.5; }
/* Ticket source tag (Dashboard / Contact form) sits under the sender on its own line. */
.cell-source { display: inline-block; margin-top: 4px; font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .03em; }
.admin-search { display: flex; gap: 6px; align-items: center; }
.admin-search input[type="search"] {
  font: inherit; font-size: 13px; padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); min-width: 220px;
}
/* Filter form: a search input + a row of <select>s + Apply. Style the selects to match the search
   input (native select chrome is chunkier and misaligns the row); wrap on narrow widths. */
.admin-filter-form { flex-wrap: wrap; }
.admin-filter-form select {
  font: inherit; font-size: 13px; padding: 6px 28px 6px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
  height: 33px; line-height: 1.2; cursor: pointer;
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center;
}
.admin-filter-form input[type="search"] { height: 33px; box-sizing: border-box; }
.admin-filter-form select:focus, .admin-filter-form input[type="search"]:focus {
  outline: none; border-color: var(--accent);
}
.admin-detail-grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 860px) { .admin-detail-grid { grid-template-columns: 1fr; } }
.kv { display: grid; grid-template-columns: minmax(120px, auto) 1fr; gap: 10px 16px; font-size: 14px; align-items: center; }
.kv dt { color: var(--ink-faint); }
.kv dd { color: var(--ink); margin: 0; }
/* A pill or control in a kv value can be taller than its plain-text label; align-items:center on
   the grid keeps the row visually centred so the label and value line up. */
/* Status strip: short account states as chips rather than a key-value list. Five one-word values
   in a dl produced five near-empty rows with a lot of vertical travel between them; side by side
   they read in a single glance. auto-fit so they reflow rather than truncate. */
.status-strip { display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.status-chip { display: flex; flex-direction: column; gap: 5px; padding: 11px 13px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2); }
.status-chip-label { font-size: 11.5px; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-faint); }
.status-chip-value { font-size: 13.5px; color: var(--ink); display: flex; align-items: center;
  gap: 6px; flex-wrap: wrap; }
/* A suspension reason is prose, so it gets a line of its own under the strip. */
.status-reason { margin: 14px 0 0; padding: 10px 13px; border-radius: 9px; font-size: 13px;
  line-height: 1.5; color: var(--ink-soft);
  background: color-mix(in srgb, var(--warn, #b5751a) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn, #b5751a) 30%, var(--border)); }
.status-reason strong { color: var(--ink); }

/* Action TILES. Each action is a bounded card, which is what stops the forms sprawling: an input
   can only be as wide as its own tile, so "edit profile" and "erase" no longer stretch their
   fields across the panel with the button drifting to the far right.
   auto-fit means the grid reflows to one column on a narrow screen with no media query. */
/* align-items: start - tiles size to their OWN content. Stretched to the row's tallest, a simple
   tile ("Make staff": one line + one button) grew to match a tile with two inputs, and the
   bottom-pushed button then left a large empty gap in the middle of it. */
.action-tiles { display: grid; gap: 12px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.action-tile { display: flex; flex-direction: column; gap: 8px; padding: 14px;
  border: 1px solid var(--border); border-radius: 11px; background: var(--surface); }
.action-tile h3 { margin: 0; font-size: 14px; font-weight: 640; color: var(--ink); }
.action-tile p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-soft); }
/* No bottom-push: that only made sense while tiles were stretched to a shared height. With each
   tile sized to its own content the button follows its description directly, and a short tile is
   simply a short tile. */
.action-tile-row { display: flex; flex-wrap: wrap; gap: 8px; }
/* Two inputs will not fit a 260px column, so these span two tracks where there is room. */
.action-tile-wide { grid-column: span 2; }
@media (max-width: 620px) { .action-tile-wide { grid-column: span 1; } }
/* Why an action is unavailable, when the tile is worth keeping for context (a suspended user
   cannot be impersonated) rather than hiding outright. */
.action-tile-blocked { font-style: italic; color: var(--ink-faint) !important; }
/* Consequence, shown rather than described: reversible-but-disruptive is warn-tinted, permanent is
   danger-tinted. In a flat list these looked identical to "Impersonate". */
.action-tile-warn { border-color: color-mix(in srgb, var(--warn, #b5751a) 35%, var(--border));
  background: color-mix(in srgb, var(--warn, #b5751a) 4%, var(--surface)); }
.action-tile-danger { border-color: color-mix(in srgb, var(--danger, #d64545) 38%, var(--border));
  background: color-mix(in srgb, var(--danger, #d64545) 5%, var(--surface)); }
.action-tile-danger h3 { color: var(--danger, #d64545); }
/* Group label for the destructive block. */
.action-group-head { margin: 20px 0 10px; font-size: 12px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-faint); }

/* Actions read as a LIST of separate decisions, not a paragraph of controls. They were 8px apart
   with no rule between them, which at sidebar width was tolerable and at full width became a wall
   of text with buttons floating at the right margin. A divider and real padding give each row its
   own line to sit on. */
.action-list { display: flex; flex-direction: column; }
/* The control follows the description instead of being pushed to the far right edge. At sidebar
   width space-between was fine; at full width it put a button a screen away from the sentence
   explaining it, so every row cost a long horizontal saccade to read. Capping the description and
   letting the row's free space sit AFTER the control keeps the pair together and left-aligned. */
.action-row { display: flex; align-items: center; gap: 14px;
  flex-wrap: wrap; padding: 12px 0; border-top: 1px solid var(--border); }
.action-list > .action-row:first-child { border-top: 0; padding-top: 4px; }
/* The description reads first and carries the weight - it is what staff scan. Slightly larger and
   darker than the old 12.5px faint, which was hard to read at a glance. */
/* A readable measure, not "all remaining space": beyond ~60ch a one-line description is harder to
   scan, and the leftover width is what was separating it from its own button. */
.action-row .action-desc { font-size: 13px; color: var(--ink-soft);
  flex: 0 1 auto; max-width: 52ch; min-width: 0; }
.stack-16 { display: flex; flex-direction: column; gap: 16px; }
/* Inside a stack the flex gap is the single source of vertical spacing - fields must not also carry
   their own margin-bottom (that double-spaces them). */
.stack-8 > .field, .stack-16 > .field { margin-bottom: 0; }
/* Clear the sticky app-topnav (~46px) + a gap, so a stuck action panel sits BELOW the header
   instead of sliding under it and touching it. */
/* Not sticky any more - that existed to keep the panel visible while scrolling the OTHER column,
   and there is no other column now. Sticky at full width just pinned a tall panel over the page. */
.action-panel { position: static; }
.action-divider { height: 1px; background: var(--border); margin: 4px 0; }
/* Sized to what it holds - a short reason - rather than stretching across whatever is left. */
/* Font size, padding and radius match .btn-sm exactly, so an input sitting beside a button is the
   same height. They were 12.5px/5px against the button's 13px/7px - about 5px shorter, which reads
   as a misaligned pair rather than one control group. */
.reason-input {
  font: inherit; font-size: 13px; padding: 7px 11px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
  min-width: 0; flex: 0 1 220px;
}
/* A form action row whose form has several inputs: let the desc sit full-width on top and the form
   fields wrap onto their own line, each stretching to fill. */
.action-row-form { flex-direction: column; align-items: stretch; }
.action-row-form .action-desc { flex: none; min-width: 0; max-width: none; }
/* The multi-input forms (edit profile, erase) had no styling at all, so their inputs were block
   elements filling the row - which at full width meant a name box the width of the page. Lay them
   out inline, each field sized to what it holds, with the button beside them. */
/* Multi-input forms stack VERTICALLY inside their tile. Side by side, two 200px inputs plus a
   button either overflowed a 260px tile or wrapped unpredictably - one field per line is
   predictable at every tile width, and each input can then use the tile's full measure instead of
   competing for it. The button sits under them, right-aligned, so it reads as the end of the form.
   A single-input form keeps its input and button on one row (see .billing-op-controls). */
.edit-profile-form,
.erase-form { display: flex; flex-direction: column; align-items: stretch; gap: 8px; }
.edit-profile-form .reason-input,
.erase-form .reason-input { flex: none; width: 100%; }
.edit-profile-form .btn,
.erase-form .btn { align-self: flex-end; }
/* One input + one button fits a single row at any tile width, so stacking it would add a line for
   nothing. Used by suspend (reason) and erase (confirm email). */
.tile-form-inline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.tile-form-inline .reason-input { flex: 1 1 140px; min-width: 0; }
.tile-form-inline .btn { flex: none; }
.action-row-form form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.action-row-form form .reason-input { flex: 1 1 140px; }
.action-row-danger .action-desc strong { color: var(--danger, #d64545); }

/* Billing / plan operations on the user detail page. Each op is a labelled control row with a hint. */
/* Billing operations as TILES, matching the Actions panel. They were a single stacked column, so
   eight operations ran the full width of the panel one after another - a select and a button with
   a screen of empty space between them, and no way to see at a glance which ops applied to this
   account. Bounding each one keeps its controls together and lets three sit on a row. */
/* align-items: start so a tile with one button does not stretch to match a tile with two stacked
   fields - which left a lot of empty box around a single control. */
.billing-ops { display: grid; gap: 12px; margin-top: 14px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.billing-op { display: flex; flex-direction: column; gap: 7px; padding: 13px;
  border: 1px solid var(--border); border-radius: 11px; background: var(--surface-2); }
.billing-op-label { font-size: 12px; font-weight: 650; color: var(--ink); text-transform: none;
  letter-spacing: 0; }
/* Controls sit at the bottom so buttons align across a row even when one hint wraps.
   A single control keeps its button beside it; TWO or more stack, because a select plus a text
   field plus a button cannot share a 250px tile without wrapping unpredictably. :has() picks the
   crowded ones out, so the simple forms are untouched. */
/* No margin-top:auto. It existed to bottom-align buttons across a stretched row, but the grid is
   align-items:start now, so tiles size to their own content - and the push only stranded a lone
   button at the bottom of an otherwise empty tile with a gap above it. */
.billing-op-controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* Sizing applies only to the ROW layout. `flex: 1 1 120px` sets a basis along the MAIN axis, so
   the moment the container becomes a column that 120px is a HEIGHT and flex-grow stretches each
   field to fill the tile - which is exactly what it did. Scoped with :not() so the column case
   never sees it. */
.billing-op-controls:not(.is-stacked) > .billing-select,
.billing-op-controls:not(.is-stacked) > .billing-note,
.billing-op-controls:not(.is-stacked) > input[type="number"] { flex: 1 1 120px; min-width: 0; }

/* Two or more controls stack. flex: none keeps each field at its natural height; width comes from
   align-items: stretch, not from growing. */
.billing-op-controls.is-stacked { flex-direction: column; align-items: stretch; }
.billing-op-controls.is-stacked > select,
.billing-op-controls.is-stacked > input { flex: none; width: 100%; min-width: 0; }
.billing-op-controls.is-stacked > .btn { flex: none; align-self: flex-end; }
/* All three share .btn-sm's font size, padding and radius, so any of them sitting beside a button
   is exactly the same height. They previously ran at three different sizes (13/6, 12.5/5, 12.5/5)
   against the button's 13/7, so a row of controls stepped up and down. */
.billing-select,
.billing-note,
.billing-days {
  font: inherit; font-size: 13px; padding: 7px 11px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
}
.billing-select { width: auto; min-width: 110px; appearance: auto; }
.billing-note { flex: 1 1 120px; min-width: 0; }
.billing-days { width: 92px; }
.billing-op-hint { font-size: 11.5px; line-height: 1.45; color: var(--ink-faint); }
/* A destructive billing op (revoke a trial) reads as one, the same way the action tiles do. */
.billing-op-danger { border-color: color-mix(in srgb, var(--danger, #d64545) 32%, var(--border));
  background: color-mix(in srgb, var(--danger, #d64545) 4%, var(--surface)); }

/* Inline refund form on the payments table. (.btn-xs itself is defined after .btn below, so its
   smaller padding wins the cascade - defining it here would be overridden by .btn's padding.) */
/* Payments row actions: View + Refund on one line, right-aligned. The refund reason is collected in
   the confirm dialog now, so no inline input. */
.payment-row-actions, .invoice-row-actions { display: flex; gap: 6px; align-items: center; justify-content: flex-end; flex-wrap: nowrap; }
/* Merged provider+description cell: pill then description inline, only wrapping between words. */
.pay-cell { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pay-cell .pill { flex: none; }
.pay-amount { white-space: nowrap; }
.payment-row-actions .inline-form { display: inline; }
/* Optional input inside the confirm dialog (e.g. a refund reason). */
.cd-input-wrap { margin: 0 0 18px; }
.stack-8 { display: flex; flex-direction: column; gap: 8px; }
.stack-12 { display: flex; flex-direction: column; gap: 12px; }
.stack-12 > .field { margin-bottom: 0; }
.admin-select {
  font: inherit; font-size: 13px; padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); width: 100%;
}
.audit-filters { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.audit-filters input[type="search"] {
  font: inherit; font-size: 13px; padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); min-width: 200px;
}
.audit-action-select { width: auto; min-width: 180px; }
.audit-meta { font-size: 11.5px; max-width: 320px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
/* The message truncates to whatever the Message cell allows, so the table never forces itself wider
   than the card. The cell caps its width; the span fills it and ellipsises. (Was a rigid 520px, which
   - with the Count column added - pushed the table past the panel.) */
.data-table td[data-label="Message"] { max-width: 0; width: 55%; }
.err-msg { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The request sub-line (GET /some/long/url) under the message also truncates, so a long URL cannot
   widen the table either. */
.data-table td[data-label="Message"] .cell-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Error-logs table: slightly smaller content so more fits without overflow, and a wider, no-wrap
   "Last seen" column so "3 hours ago" stays on one line instead of stacking. */
.errlog-table { font-size: 13px; }
.errlog-table td[data-label="Last seen"] { white-space: nowrap; width: 1%; }
.errlog-table td[data-label="Location"] { white-space: nowrap; }
.pill-lg { font-size: 12.5px; padding: 5px 12px; }

/* ---- comms: mail template editor + broadcast ---- */
.form-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
/* The [hidden] attribute must beat an explicit display (e.g. a .form-actions flex bar that the
   admin-tabs island hides via [data-tab-hide]); a plain display rule otherwise overrides it. */
[hidden] { display: none !important; }
.mail-preview { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface-2); }
.mail-preview-subject { font-weight: 650; padding: 10px 14px; border-bottom: 1px solid var(--border); background: var(--surface); min-height: 20px; }
.mail-preview-body { padding: 14px; white-space: pre-wrap; word-break: break-word; font-size: 13.5px; line-height: 1.55; min-height: 120px; }
/* ---- analytics & ads: provider cards ---- */
.panel-note { background: var(--surface-2); }
.provider-card { padding: 0; overflow: hidden; }
.provider-card-head { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.provider-card-icon {
  width: 38px; height: 38px; border-radius: 10px; flex: none; display: grid; place-items: center;
  font-weight: 750; color: #fff; font-size: 16px; background: var(--accent);
}
.provider-card-icon.icon-ga4 { background: #f9ab00; }
.provider-card-icon.icon-gtm { background: #4285f4; }
.provider-card-icon.icon-meta { background: #0866ff; }
.provider-card-icon.icon-linkedin { background: #0a66c2; }
.provider-card-meta { flex: 1; min-width: 0; }
.provider-card-name { font-weight: 650; font-size: 14px; }
.provider-card-desc { font-size: 12.5px; margin-top: 1px; }
.provider-card-status { flex: none; }
.provider-card-body { padding: 16px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 720px) { .provider-card-body { grid-template-columns: 1fr; } }
.provider-card-fields { display: flex; flex-direction: column; gap: 12px; }
.provider-card-note {
  font-size: 12.5px; line-height: 1.55; color: var(--ink-soft);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px;
}
.field-hint { font-size: 11.5px; margin-top: 3px; display: block; }
.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; }

/* segment recipient-count row: info text on the left, an auto-width button on the right */
.broadcast-count-row { display: flex; align-items: center; gap: 12px; }
.broadcast-count { font-size: 13px; color: var(--ink-soft); flex: 1; min-width: 0; }
.broadcast-count-row .btn { flex: none; margin-left: auto; }
.broadcast-starters { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 18px; }
@media (max-width: 620px) { .broadcast-starters { grid-template-columns: 1fr; } }

/* ---- rich text editor ---- */
.rich-editor { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface); }
.rich-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; padding: 6px 8px; border-bottom: 1px solid var(--border); background: var(--surface-2); }
.rich-toolbar .rich-block {
  font: inherit; font-size: 12.5px; padding: 4px 26px 4px 9px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); cursor: pointer;
  width: auto; flex: none; min-width: 0; max-width: 150px;
  appearance: auto; -webkit-appearance: auto;
}
.rich-area h1, .mail-preview-body h1 { font-size: 20px; font-weight: 750; margin: 0 0 10px; }
.rich-area h2, .mail-preview-body h2 { font-size: 17px; font-weight: 700; margin: 0 0 8px; }
.rich-area h3, .mail-preview-body h3 { font-size: 15px; font-weight: 650; margin: 0 0 6px; }
.rich-btn {
  font: inherit; font-size: 12.5px; padding: 4px 9px; border-radius: 6px; border: 1px solid transparent;
  background: none; color: var(--ink); cursor: pointer; line-height: 1.2;
}
.rich-btn:hover { background: var(--surface); border-color: var(--border); }
.rich-sep { width: 1px; height: 18px; background: var(--border); margin: 0 4px; }
.rich-linkbar { display: flex; gap: 8px; align-items: center; padding: 8px; border-bottom: 1px solid var(--border); background: var(--surface-2); }
.rich-linkbar[hidden] { display: none; }
.rich-linkbar .admin-search-input { flex: 1; }
.rich-area { min-height: 200px; max-height: 460px; overflow-y: auto; padding: 12px 14px; font-size: 13.5px; line-height: 1.55; outline: none; }
.rich-area:focus { outline: none; }
.rich-area p { margin: 0 0 10px; }
.rich-area ul, .rich-area ol { margin: 0 0 10px; padding-left: 22px; }
.rich-area a { color: var(--accent); }
.rich-area:empty::before { content: "Write your message..."; color: var(--ink-faint); }
/* the HTML preview pane renders the authored markup */
.mail-preview-body :where(p, ul, ol) { margin: 0 0 10px; }
.mail-preview-body a { color: var(--accent); }

/* notification composer: 2-column field grid, Body spans both columns */
.notif-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 18px; margin-bottom: 16px; }
.notif-grid .field { margin-bottom: 0; }
.notif-grid .notif-field-wide { grid-column: 1 / -1; }
@media (max-width: 620px) { .notif-grid { grid-template-columns: 1fr; } }
/* notification "View" dialog */
.notif-view-meta { margin: 4px 0 14px; }
.notif-view-body-wrap .field-label { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 5px; }
.notif-view-body { white-space: pre-wrap; word-break: break-word; font-size: 13.5px; line-height: 1.55; padding: 12px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2); }
#notif-view-overlay .form-actions { margin-top: 16px; }

/* error-log detail dialog */
.errlog-section { margin-top: 14px; }
/* Resolution note field in the error-detail dialog. */
.errlog-resolve { border-top: 1px solid var(--border); padding-top: 14px; }
.errlog-note { width: 100%; font: inherit; font-size: 13.5px; color: var(--ink); background: var(--surface);
  border: 1px solid var(--border); border-radius: 8px; padding: 9px 11px; resize: vertical; min-height: 62px; margin-top: 6px; }
.errlog-note:focus { outline: none; border-color: var(--accent); }
.errlog-section .field-label { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 5px; }
.errlog-message { font-size: 13px; line-height: 1.5; word-break: break-word; padding: 12px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2); }
.errlog-trace { margin: 0; max-height: 320px; overflow: auto; padding: 12px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2); }
.errlog-trace code { font-size: 12px; line-height: 1.5; white-space: pre; font-family: var(--mono, monospace); color: var(--ink); }
#errlog-view-overlay .form-actions { margin-top: 16px; }
/* [hidden] must beat .btn's display:inline-flex so Mark-resolved / Reopen toggle by state. */
#errlog-view-overlay .btn[hidden] { display: none; }

/* ---- topnav notification bell dropdown ---- */
.notif-panel {
  position: absolute; top: calc(100% + 6px); right: 12px; z-index: 60; width: 340px; max-width: calc(100vw - 24px);
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow);
  overflow: hidden;
}
.notif-panel-head { padding: 11px 14px; border-bottom: 1px solid var(--border); font-weight: 650; font-size: 13.5px; }
.notif-panel-list { max-height: 380px; overflow-y: auto; }
.notif-panel-empty { padding: 20px 14px; text-align: center; font-size: 13px; }
.notif-item { padding: 11px 14px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 3px; }
.notif-item:last-child { border-bottom: none; }
.notif-item.is-unread { background: var(--accent-soft); }
.notif-item-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.notif-item-title { font-weight: 600; font-size: 13px; min-width: 0; }
.notif-item-body { font-size: 12.5px; line-height: 1.5; color: var(--ink-soft); white-space: pre-wrap; word-break: break-word; }
.notif-item-meta { font-size: 11.5px; }
.notif-panel-foot { padding: 9px 14px; border-top: 1px solid var(--border); text-align: center; }
.notif-panel-all { font-size: 12.5px; color: var(--accent); text-decoration: none; }
.notif-panel-all:hover { text-decoration: underline; }

/* ---- /notifications page feed ---- */
.notif-feed { display: flex; flex-direction: column; }
.notif-feed-item { padding: 14px 2px; border-bottom: 1px solid var(--border); }
.notif-feed-item:last-child { border-bottom: none; }
.notif-feed-item.is-unread { position: relative; padding-left: 14px; }
.notif-feed-item.is-unread::before { content: ""; position: absolute; left: 0; top: 18px; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
.notif-feed-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.notif-feed-title { font-weight: 650; font-size: 14px; }
.notif-feed-body { font-size: 13.5px; line-height: 1.55; color: var(--ink); margin-bottom: 5px; white-space: pre-wrap; word-break: break-word; }
.notif-feed-meta { font-size: 12px; }

/* ---- shared staff user-search picker ---- */
.admin-search-input {
  font: inherit; font-size: 13px; padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); width: 100%;
}
.user-search-results {
  display: flex; flex-direction: column; margin-top: 4px; border: 1px solid var(--border);
  border-radius: 8px; overflow: hidden; background: var(--surface); box-shadow: var(--shadow);
}
.user-search-results[hidden] { display: none; }
.user-search-item {
  font: inherit; font-size: 13px; text-align: left; padding: 8px 11px; border: none;
  background: none; color: var(--ink); cursor: pointer; border-bottom: 1px solid var(--border);
}
.user-search-item:last-child { border-bottom: none; }
.user-search-item:hover { background: var(--surface-2); }
[data-notif-ws-wrap][hidden], [data-broadcast-user-wrap][hidden] { display: none; }

/* ---- support ticket thread ---- */
.ticket-thread { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; }
.ticket-msg { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; background: var(--surface); max-width: 78%; width: fit-content; align-self: flex-start; }
.ticket-msg.is-staff { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); align-self: flex-end; }
/* Author + timestamp sit together with a comfortable gap, not pushed to opposite edges. */
.ticket-msg-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 5px; font-size: 12px; }
.ticket-msg-head time { margin-left: auto; color: var(--ink-faint); }
.ticket-msg-author { font-weight: 650; }
.ticket-msg-body { white-space: pre-wrap; word-break: break-word; font-size: 13.5px; line-height: 1.55; }
/* Full-width separator, but keep the compose controls to a comfortable width. */
.ticket-reply { border-top: 1px solid var(--border); padding-top: 14px; }
.ticket-reply > .field, .ticket-reply > .btn { max-width: 640px; }
/* image attachments on a ticket message: small thumbnails, click to open full size */
.ticket-attachments { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.ticket-attachment { display: block; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; line-height: 0; }
.ticket-attachment img { display: block; max-width: 160px; max-height: 160px; object-fit: cover; }
.ticket-attachment:hover { border-color: var(--accent); }
/* native file input, compact - no bulky box, just the button + filename inline */
.file-input {
  font: inherit; font-size: 12.5px; color: var(--ink-faint); cursor: pointer; padding: 0; border: none; background: none;
}
.file-input::file-selector-button {
  font: inherit; font-size: 12px; margin-right: 10px; padding: 5px 11px; border-radius: 7px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); cursor: pointer;
}
.file-input::file-selector-button:hover { border-color: var(--ink-faint); }
/* the reply compose form: button hugs its content instead of stretching full width */
.ticket-reply .form-actions, .ticket-reply .btn { align-self: flex-start; }
.ticket-reply > .btn { align-self: flex-start; }
/* clickable stat cards (support inbox) */
.stat-tile-link { text-decoration: none; color: var(--ink); display: block; transition: border-color .12s; }
.stat-tile-link:hover { border-color: var(--ink-faint); text-decoration: none; }
.stat-tile-link.is-warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--border)); background: color-mix(in srgb, var(--warn) 8%, var(--surface)); }
.stat-tile-link.is-warn .stat-num { color: var(--warn); }
/* secondary filter rows (priority / category) under the main tabs */
.panel-subfilters { display: flex; flex-wrap: wrap; gap: 8px 24px; margin: 12px 0 4px; }
.filter-group { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.filter-group-label { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
/* Support link pinned in the sidebar footer, above the username menu */
.sidebar-foot-item { margin-top: 8px; margin-bottom: 6px; }
/* In the collapsed rail, drop the custom margins so it matches the sibling nav items exactly - the
   generic collapsed .nav-item rule (padding 9px 0, centered) then styles it identically. The foot's
   flex gap handles spacing. */
.app-shell.sidebar-collapsed .sidebar-foot-item { margin: 0; width: 100%; }

/* ---- admin guide + DPA ---- */
.guide-list { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; }
.guide-list li { font-size: 13.5px; line-height: 1.55; color: var(--ink); }
.dpa-doc { line-height: 1.62; }
.dpa-title { text-align: center; margin: 4px 0 6px; font-size: 22px; letter-spacing: -.01em; }
.dpa-intro { margin: 0 0 18px; font-size: 13.5px; color: var(--ink); }
.dpa-doc h3 { margin: 22px 0 7px; font-size: 15px; font-weight: 700; }
.dpa-doc p { margin: 0 0 10px; font-size: 13.5px; color: var(--ink); }
/* parties block */
.dpa-parties { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 6px 0 8px; }
.dpa-party { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; background: var(--surface-2); }
.dpa-party h3 { margin: 0 0 6px; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); }
.dpa-party p { margin: 0; font-size: 13px; }
/* numbered clause lists */
.dpa-defs, .dpa-ol { margin: 0 0 10px; padding-left: 22px; display: flex; flex-direction: column; gap: 7px; }
.dpa-defs li, .dpa-ol li { font-size: 13.5px; line-height: 1.55; }
.dpa-defs { list-style: none; padding-left: 0; }
/* processing / sub-processor tables */
.dpa-table { width: 100%; border-collapse: collapse; margin: 4px 0 12px; font-size: 13px; }
.dpa-table th, .dpa-table td { border: 1px solid var(--border); padding: 8px 11px; text-align: left; vertical-align: top; }
.dpa-table tbody th { width: 200px; background: var(--surface-2); font-weight: 650; color: var(--ink); }
.dpa-table-cols thead th { background: var(--surface-2); font-weight: 700; }
/* signatures */
.dpa-signatures { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 26px; padding-top: 16px; border-top: 1px solid var(--border); }
.dpa-sigblock h3 { margin: 0 0 10px; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); }
.dpa-sig-line { margin: 0 0 9px; font-size: 13px; font-variant-numeric: tabular-nums; }
/* "Your details" now sits ABOVE the agreement, full width, with fields in two columns. */
.dpa-config { margin-bottom: 16px; }
.dpa-fields-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; margin-bottom: 14px; }
.dpa-fields-grid .dpa-field-wide { grid-column: 1 / -1; }
@media (max-width: 620px) {
  .dpa-parties, .dpa-signatures, .dpa-fields-grid { grid-template-columns: 1fr; }
}
[data-dpa-wrap][hidden] { display: none; }
/* Table-of-contents sidebar on the guide */
.guide-toc { position: sticky; top: 76px; align-self: start; }  /* clear the sticky topnav (see .action-panel) */
.guide-toc-nav { display: flex; flex-direction: column; gap: 2px; }
.guide-toc-nav a { font-size: 13px; padding: 5px 9px; border-radius: 7px; color: var(--ink-soft); text-decoration: none; }
.guide-toc-nav a:hover { background: var(--surface-2); color: var(--ink); }
.guide-toc-nav a.toc-l3 { padding-left: 20px; font-size: 12.5px; color: var(--ink-faint); }
/* The current page in the sidebar's all-areas list. */
.guide-toc-nav a.is-active { background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  color: var(--accent); font-weight: 600; }
/* Group label above each run of pages - a heading, not a link. */
.guide-side-group { display: block; font-size: 10.5px; font-weight: 800; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-faint); padding: 12px 9px 4px; }
.guide-side-nav { margin-bottom: 4px; }
/* Landing page: a card per manual page, grouped by area. */
.guide-cards { display: grid; gap: 10px; }
@media (min-width: 720px) { .guide-cards { grid-template-columns: 1fr 1fr; } }
.guide-card { display: grid; gap: 3px; padding: 13px 15px; border: 1px solid var(--border);
  border-radius: 11px; text-decoration: none; color: inherit; background: var(--surface); }
/* The global a:hover underlines every link; a whole card does not want one - the border and
   background tint already say it is hoverable, and the underline lands on both lines of text. */
.guide-card:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 4%, var(--surface)); text-decoration: none; }
/* Belt and braces: the underline can also land on the children, so kill it there too. */
.guide-card:hover strong, .guide-card:hover .muted { text-decoration: none; }
.guide-card strong { font-size: 14px; }
.guide-card .muted { font-size: 12.5px; line-height: 1.45; }
/* Prev/next at the foot of a page, so the manual still reads straight through. */
.guide-pager { display: flex; justify-content: space-between; gap: 12px; margin-top: 28px;
  padding-top: 18px; border-top: 1px solid var(--border); }

/* Blog list: a cover thumbnail beside the title, so a post that is missing one is obvious at a
   glance rather than only on opening it. 16:10, matching the shape a cover renders at on the blog. */
.blog-title-cell { display: flex; align-items: flex-start; gap: 12px; }
.blog-title-text { min-width: 0; }   /* lets a long title wrap instead of stretching the cell */
.blog-cover-thumb { flex: none; width: 96px; height: 60px; border-radius: 6px; object-fit: cover;
  background: var(--surface-2); border: 1px solid var(--border); }
/* The no-cover case is the common one here, so it gets a real placeholder - a bare gap reads as a
   broken image rather than a deliberate empty. */
.blog-cover-thumb.is-empty { display: grid; place-items: center; color: var(--ink-faint); }
.blog-cover-thumb.is-empty svg { width: 22px; height: 22px; }

/* Whole-manual search. Each hit is a BLOCK - heading, which page it is on, and an excerpt on
   its own line - because the three run together as inline text otherwise, which is unreadable. */
.guide-search { margin-bottom: 16px; }
.guide-search .field { margin: 0; }
.guide-search-results { display: grid; gap: 2px; margin-top: 10px;
  max-height: 420px; overflow-y: auto; }
/* Two rows: [heading + page pill] then the excerpt. The pill sits BESIDE the title rather than
   under it - stacked, it pushed the excerpt down and made each hit twice as tall for no gain. */
.guide-search-hit { display: grid; grid-template-columns: 1fr; gap: 4px; padding: 11px 13px;
  border-radius: 9px; text-decoration: none; color: inherit; border: 1px solid transparent; }
.guide-search-head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.guide-search-hit:hover { background: color-mix(in srgb, var(--accent) 5%, var(--surface-2));
  border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); text-decoration: none; }
.guide-search-hit strong { font-size: 13.5px; color: var(--ink); }
.guide-search-hit:hover strong, .guide-search-hit:hover .muted { text-decoration: none; }
/* Which page the hit lives on - a small pill so it reads as metadata, not part of the sentence. */
.guide-search-meta { flex: none; font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-faint); background: var(--surface-2);
  border-radius: 999px; padding: 2px 8px; }
.guide-search-hit .muted { font-size: 12.5px; line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.guide-search-empty { margin: 10px 0 2px; font-size: 13px; }
.guide-toc { max-height: calc(100vh - 90px); overflow-y: auto; }
@media (max-width: 860px) { .guide-toc, .dpa-config { position: static; } }

/* ---- rendered markdown prose (admin guide) ---- */
/* Anchor jumps (from the TOC) must clear the sticky ~46px topnav, and scroll smoothly. The window
   is the scroll container, so scroll-padding-top goes on the root; scroll-margin-top on each heading
   is the belt-and-braces that also covers browsers keying off the target element. */
html { scroll-padding-top: 40px; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
.guide-prose h1, .guide-prose h2, .guide-prose h3 { scroll-margin-top: 40px; }
/* The manual reads better with a wide prose column: the sidebar is a nav list of short labels,
   so admin-detail-grid's 1.6fr/1fr gave it far more room than it needs.
   minmax(0, 1fr) on the prose column is the important half - a grid track defaults to
   min-content:auto, so a wide table inside FORCES the column open and pushes content under the
   sidebar instead of scrolling inside its own box. */
.guide-layout { grid-template-columns: minmax(0, 1fr) 264px; gap: 20px;
  max-width: 1180px; }
@media (max-width: 1100px) { .guide-layout { grid-template-columns: minmax(0, 1fr); } }
/* The 80ch cap belonged on the PROSE, which left ~200px of dead space inside a panel that filled
   the grid track - read as a huge gap between the columns. Capping the panel instead just moves
   that space outside it, since the track is still 1fr and the sidebar stays pinned right.
   So the cap goes on the GRID: both columns stay adjacent, the pair centres on a wide screen, and
   the prose measure is set by the track rather than by a rule fighting it. */
.guide-prose { line-height: 1.62; min-width: 0; max-width: none; }
/* A wide table scrolls inside its own WRAPPER (Markdown emits .md-table-wrap) rather than
   widening the prose column and pushing content under the sidebar. Scrolling the table itself
   would need display:block, which stops thead and tbody sharing column widths - and forced the
   `overflow-wrap: anywhere` that was breaking filenames like auto_promote_experiments.php one
   character per line. A wrapper keeps the table a real table. */
.md-table-wrap { max-width: 100%; overflow-x: auto; margin: 4px 0 14px; }
.guide-prose .md-table-wrap table { margin: 0; min-width: 620px; }
/* Code in a cell must not be broken mid-token - it is a command you copy. */
.guide-prose table code { white-space: nowrap; }
.guide-prose h1 { font-size: 22px; margin: 4px 0 12px; letter-spacing: -.01em; }
.guide-prose h2 { font-size: 18px; margin: 26px 0 8px; padding-top: 8px; border-top: 1px solid var(--border); }
.guide-prose h2:first-of-type { border-top: none; padding-top: 0; }
.guide-prose h3 { font-size: 15px; margin: 18px 0 6px; }
.guide-prose p { margin: 0 0 11px; font-size: 13.5px; color: var(--ink); }
.guide-prose ul.md-ul, .guide-prose ol.md-ol { margin: 0 0 12px; padding-left: 22px; display: flex; flex-direction: column; gap: 5px; }
.guide-prose li { font-size: 13.5px; line-height: 1.55; }
.guide-prose a { color: var(--accent); text-decoration: none; }
.guide-prose a:hover { text-decoration: underline; }
.guide-prose code { font-size: 12px; background: var(--surface-2); border-radius: 5px; padding: 1px 5px; font-family: var(--mono, monospace); }
.guide-prose pre.md-code { background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; overflow-x: auto; margin: 0 0 12px; }
.guide-prose pre.md-code code { background: none; padding: 0; font-size: 12.5px; }
.guide-prose blockquote { margin: 0 0 12px; padding: 8px 14px; border-left: 3px solid var(--accent); background: var(--surface-2); border-radius: 0 8px 8px 0; font-size: 13px; }
.guide-prose hr { border: none; border-top: 1px solid var(--border); margin: 22px 0; }
.guide-prose table.md-table { width: 100%; border-collapse: collapse; margin: 4px 0 14px; font-size: 12.5px; }
.guide-prose table.md-table th, .guide-prose table.md-table td { border: 1px solid var(--border); padding: 7px 10px; text-align: left; vertical-align: top; }
.guide-prose table.md-table thead th { background: var(--surface-2); font-weight: 650; }

/* Deploy help: the env-var tables can be wide, so let each scroll inside its own box rather than
   pushing the page sideways. The intro notice needs a little breathing room above the grid. */
.deploy-env-note { margin-bottom: 16px; }
.deploy-env-table { overflow-x: auto; }
.deploy-env-table table.md-table { min-width: 480px; }
/* Per-provider payment webhook setup block on the deploy guide. */
.deploy-webhook { border: 1px solid var(--border); border-radius: 10px; padding: 12px 16px; margin: 12px 0; background: var(--surface); }
.deploy-webhook h4 { margin: 0 0 8px; font-size: 14px; font-weight: 700; }
.deploy-webhook-events { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.deploy-webhook-events code { white-space: nowrap; }

/* Print: for the DPA, hide everything except the agreement itself so it prints clean. */
@page { size: A4; margin: 18mm 16mm; }
@media print {
  .app-sidebar, .app-topnav, .sidebar-backdrop, #onbixo-toast-root,
  .dpa-config, .page-head, .guide-toc, .skip-link { display: none !important; }
  .app-shell, .app-main, .app-content { display: block !important; padding: 0 !important; margin: 0 !important; }
  .admin-detail-grid { display: block !important; }
  /* The doc panel must not be a print container that clips or reserves whitespace. */
  .panel.dpa-doc {
    border: none !important; box-shadow: none !important; max-width: none; width: 100%;
    padding: 0 !important; margin: 0 !important; color: #000; background: #fff;
    font-size: 11pt; line-height: 1.5; overflow: visible !important; border-radius: 0 !important;
  }
  .dpa-title { font-size: 18pt; }
  .dpa-doc h3 { font-size: 12pt; break-after: avoid; page-break-after: avoid; }
  /* Keep small blocks together, but let the big processing/sub-processor TABLES break across pages
     so they do not shove a whole clause to the next page and leave a huge gap. Rows stay intact. */
  .dpa-defs, .dpa-ol, .dpa-parties, .dpa-signatures { break-inside: avoid; page-break-inside: avoid; }
  .dpa-table tr, .dpa-party, .dpa-sigblock { break-inside: avoid; page-break-inside: avoid; }
  .dpa-table { break-inside: auto; page-break-inside: auto; }
  .dpa-table th, .dpa-table td { border-color: #999 !important; background: #fff !important; }
}

/* ---- workspace switcher (top of sidebar) ---- */
.ws-switcher { position: relative; margin: 0 0 12px; }
.ws-current {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 8px 9px; border-radius: 9px; background: var(--surface-2);
  border: 1px solid var(--border); color: var(--ink); cursor: pointer; font: inherit;
}
.ws-current:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.ws-switcher.is-open .ws-current { border-color: var(--accent); }
.ws-avatar {
  width: 26px; height: 26px; border-radius: 7px; flex: none; display: grid; place-items: center;
  font-size: 12px; font-weight: 700; color: #fff;
  background: linear-gradient(140deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #8b5cf6));
}
.ws-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.ws-name { font-weight: 650; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-role { font-size: 11px; color: var(--ink-faint); }
.ws-caret { margin-left: auto; flex: none; color: var(--ink-faint); font-size: 11px; transition: transform .15s ease; }
.ws-switcher.is-open .ws-caret { transform: rotate(180deg); }
.ws-dropdown {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 45;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 6px; max-height: 60vh; display: flex; flex-direction: column;
}
/* display:flex outranks the UA's [hidden]{display:none} - restore it so the closed menu hides */
.ws-dropdown[hidden] { display: none; }
.ws-search-wrap { padding: 2px 2px 6px; }
.ws-search {
  width: 100%; padding: 7px 9px; border-radius: 7px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--ink); font: inherit; font-size: 13px;
}
.ws-search:focus { outline: none; border-color: var(--accent); }
.ws-list { overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.ws-switch-form { margin: 0; }
.ws-item {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 8px 9px; border-radius: 7px; color: var(--ink-soft);
  background: none; border: none; cursor: pointer; font: inherit; font-size: 13.5px;
  text-decoration: none;
}
.ws-item:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.ws-item-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-item .pill { flex: none; margin-left: auto; }
.ws-item.is-current { color: var(--ink); font-weight: 600; background: var(--surface-2); cursor: default; }
.ws-item.is-current .pill { margin-left: auto; }
.ws-check { flex: none; color: var(--accent); font-weight: 700; }
.ws-empty { padding: 10px 9px; font-size: 13px; }
.ws-dropdown-foot { border-top: 1px solid var(--border); margin-top: 6px; padding-top: 6px; }
.ws-item-add { color: var(--accent); font-weight: 550; }

/* ---- username menu (bottom of sidebar; person / whole-account controls; opens upward) ---- */
.user-menu { position: relative; margin-top: 8px; }
.user-current {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 7px 8px; border-radius: 9px; background: none; border: 1px solid transparent;
  color: var(--ink); cursor: pointer; font: inherit;
}
.user-current:hover { background: var(--surface-2); }
.user-menu.is-open .user-current { background: var(--surface-2); border-color: var(--border); }
.user-avatar {
  width: 26px; height: 26px; border-radius: 999px; flex: none; display: grid; place-items: center;
  font-size: 12px; font-weight: 700; color: var(--ink-soft); background: var(--surface-2);
  border: 1px solid var(--border);
}
.user-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.user-name { font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-email { font-size: 11px; color: var(--ink-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Crisp SVG chevron; rotates to point up when the menu is open. */
.user-caret { margin-left: auto; flex: none; color: var(--ink-faint); transition: transform .15s ease, color .15s ease; }
.user-current:hover .user-caret { color: var(--ink-soft); }
.user-menu.is-open .user-caret { transform: rotate(180deg); color: var(--ink-soft); }
.user-dropdown {
  position: absolute; left: 0; right: 0; bottom: calc(100% + 6px); z-index: 45;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 6px;
}
/* block display outranks the UA's [hidden]{display:none} - restore it so the closed menu hides */
.user-dropdown[hidden] { display: none; }
/* identity header at the top of the user dropdown. Hidden by default: when the sidebar is
   expanded the trigger already shows name + email inline, so it would be redundant. It is
   revealed only in the collapsed rail (below), where the trigger is just the avatar. */
/* Identity header (name + email) shown at the top of the open menu in BOTH states - the closed trigger
   now shows only a single name line, so this is where the full email lives. A subtly tinted background
   sets it apart from the menu items below. Negative margins let the tint span the dropdown's full width
   and meet its rounded top corners. */
.user-dropdown-head {
  display: block; padding: 10px 12px; margin: -6px -6px 6px; border-radius: 9px 9px 0 0;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
}
.user-dropdown-name { font-weight: 650; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-dropdown-email { font-size: 11.5px; margin-top: 2px; word-break: break-all; }
.user-item {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 8px 10px; border-radius: 7px; color: var(--ink-soft);
  background: none; border: none; cursor: pointer; font: inherit; font-size: 13.5px; text-decoration: none;
}
.user-item:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.user-item.is-disabled { color: var(--ink-faint); cursor: default; }
.user-item.is-disabled:hover { background: none; }
.user-item.is-disabled .nav-soon { margin-left: auto; }
.user-divider { height: 1px; background: var(--border); margin: 6px 0; }
.user-logout { margin: 0; }
.user-logout .user-item-btn { color: var(--crit); }

.sidebar-foot { margin-top: auto; padding: 8px; }
/* Plan badge: accent-tinted so it reads as an active plan, not a disabled/grey chip. */
.plan-pill {
  font-size: 11px; font-weight: 650; letter-spacing: .01em; color: var(--accent);
  background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 999px; padding: 3px 10px;
}
/* Expired/paused trial: red-tinted (not accent-positive), matching the "paused" crit chip beside it. */
.plan-pill.is-expired {
  color: var(--crit); background: color-mix(in srgb, var(--crit) 12%, transparent);
  border-color: color-mix(in srgb, var(--crit) 30%, transparent);
}
/* Trial "N days left" chip next to the plan pill - accent normally, escalating amber/red as the
   trial runs out (<=5 / <=3 days) so an ending trial visibly demands attention. */
.trial-chip {
  font-size: 10.5px; font-weight: 700; color: var(--accent);
  background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius: 999px; padding: 2px 8px; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.trial-chip.is-warn {
  color: var(--warn); background: color-mix(in srgb, var(--warn) 15%, var(--surface));
  border-color: color-mix(in srgb, var(--warn) 40%, transparent);
}
.trial-chip.is-crit {
  color: var(--crit); background: color-mix(in srgb, var(--crit) 15%, var(--surface));
  border-color: color-mix(in srgb, var(--crit) 45%, transparent);
}
/* plan / MAU usage widget in the sidebar footer (links to plan settings) */
.sidebar-usage {
  display: block; text-decoration: none; padding: 10px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface);
}
.sidebar-usage:hover { background: var(--surface-2); text-decoration: none; border-color: var(--border-strong); }
/* On hover the card becomes --surface-2, the SAME colour as the usage-bar track, so the (empty at
   0%) bar visually vanished. Give the track a distinct tint on hover so it stays visible. */
.sidebar-usage:hover .usage-bar { background: var(--border); }
.sidebar-usage-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.sidebar-usage-pct { font-size: 11px; font-weight: 600; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.sidebar-usage .usage-bar { height: 5px; margin-top: 8px; }
.sidebar-usage-sub { font-size: 11px; color: var(--ink-faint); margin-top: 6px; }
.sidebar-usage.is-near .sidebar-usage-pct { color: var(--warn); }
.sidebar-usage.is-over .sidebar-usage-pct { color: var(--crit); }

.app-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.app-topnav {
  display: flex; align-items: center; gap: 12px; padding: 10px 14px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 20;
  backdrop-filter: saturate(160%) blur(10px);
}
.breadcrumbs { display: flex; align-items: center; gap: 8px; font-size: 15px; color: var(--ink-soft); }
.breadcrumbs a { color: var(--ink-soft); }
.breadcrumbs .sep { color: var(--ink-faint); }
.breadcrumbs [aria-current="page"] { color: var(--ink); font-weight: 600; }
.breadcrumbs-sm { font-size: 13px; margin-bottom: 6px; }
.topnav-spacer { flex: 1; }
.icon-btn {
  width: 34px; height: 34px; display: grid; place-items: center; cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: 8px;
  color: var(--ink-soft); font-size: 15px; position: relative;
}
.icon-btn:hover { background: var(--surface-2); color: var(--ink); border-color: var(--border); }
.notif-dot { position: absolute; top: 6px; right: 7px; width: 7px; height: 7px; border-radius: 50%; background: var(--crit); }
.search-trigger {
  display: flex; align-items: center; gap: 10px; padding: 7px 12px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  color: var(--ink-faint); font: inherit; font-size: 13px;
}
.search-trigger:hover { border-color: var(--border-strong); }
.profile-btn { background: transparent; border: none; cursor: pointer; padding: 0; }
.avatar {
  width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink); font-weight: 700; font-size: 13px;
}

.app-content { padding: 24px 24px 60px; max-width: 1180px; width: 100%; margin: 0 auto; }
.page-head { margin-bottom: 20px; }
/* the sidebar toggle is available on both desktop (collapse/expand) and mobile
   (open/close the drawer); larger than a normal icon button so it reads as the
   primary nav control */
.sidebar-toggle { display: grid; width: 40px; height: 40px; font-size: 23px; }

/* ---- mobile (<= 820px): sidebar becomes an off-canvas drawer ---- */
@media (max-width: 820px) {
  .app-sidebar {
    position: fixed; top: 0; left: 0; height: 100vh; z-index: 60;
    transform: translateX(-100%); transition: transform .22s ease;
  }
  /* on mobile the .sidebar-open class slides the drawer in (shadow only when open,
     so the off-screen drawer's shadow does not bleed onto the page edge) */
  .app-shell.sidebar-open .app-sidebar {
    transform: none; box-shadow: 0 0 40px rgba(0,0,0,.25);
  }
  /* the desktop-collapse (mini rail) class has no effect on mobile (drawer model instead):
     the drawer stays full-width with its labels, whatever the collapsed class says */
  .app-shell.sidebar-collapsed .app-sidebar { display: flex; width: var(--sidebar-w); padding: 14px 12px; }
  .app-shell.sidebar-collapsed .brand-name,
  .app-shell.sidebar-collapsed .nav-label,
  .app-shell.sidebar-collapsed .nav-soon,
  .app-shell.sidebar-collapsed .ws-name,
  .app-shell.sidebar-collapsed .ws-role,
  .app-shell.sidebar-collapsed .ws-caret,
  .app-shell.sidebar-collapsed .user-meta,
  .app-shell.sidebar-collapsed .user-caret,
  .app-shell.sidebar-collapsed .sidebar-usage-head,
  .app-shell.sidebar-collapsed .sidebar-usage-sub,
  .app-shell.sidebar-collapsed .usage-bar { display: revert; }
  .app-shell.sidebar-collapsed .nav-item,
  .app-shell.sidebar-collapsed .ws-current,
  .app-shell.sidebar-collapsed .user-current { justify-content: flex-start; }
  .app-shell.sidebar-collapsed .nav-item[data-tip]::after,
  .app-shell.sidebar-collapsed .sidebar-usage[data-tip]::after,
  .app-shell.sidebar-collapsed .ws-current[data-tip]::after,
  .app-shell.sidebar-collapsed .user-current[data-tip]::after { content: none; }
  /* mobile drawer keeps the full usage block, not the mini chip */
  .app-shell.sidebar-collapsed .sidebar-usage { display: block; padding: 10px; }
  .app-shell.sidebar-collapsed .sidebar-usage-mini { display: none; }
  /* the trigger now shows only a single name line, so keep the identity header (name + email) in the
     open menu on mobile too. */
  .app-shell.sidebar-collapsed .user-dropdown-head { display: block; }
  .app-shell.sidebar-open .sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(0,0,0,.4);
  }
  .app-content { padding: 16px 14px 48px; }
  .app-topnav { gap: 10px; }
  /* trim the breadcrumb to the current page only (it reads as the page title) */
  .breadcrumbs a, .breadcrumbs .sep { display: none; }
  .breadcrumbs [aria-current="page"] { font-weight: 650; }
  .search-trigger span, .search-trigger kbd { display: none; }
  .search-trigger { padding: 9px; }
  .search-trigger::before { content: "\1F50D"; font-size: 15px; }
}

/* ---- cards / panels ---- */
.kpi-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 20px; }
@media (max-width: 820px) {
.kpi-row { grid-template-columns: repeat(2, 1fr); }
}
/* secondary KPI row (e.g. announcement engagement): a partial-width row of extra context
   cards that should NOT stretch to the full 4-column grid. Cards keep the main row's width
   (a quarter each) and sit left-aligned, so a 2-card row does not leave two blank columns. */
.kpi-row-secondary { grid-template-columns: repeat(4, 1fr); margin-top: -6px; }
.kpi-row-secondary .kpi-card { grid-column: span 1; }
@media (max-width: 820px) {
.kpi-row-secondary { grid-template-columns: repeat(2, 1fr); }
}
.kpi-card {
  position: relative;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px; box-shadow: var(--shadow);
}
.kpi-label { font-size: 12px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .05em; }
.kpi-value { font-size: 26px; font-weight: 750; letter-spacing: -.02em; margin-top: 4px; font-variant-numeric: tabular-nums; }
.kpi-sub { font-size: 11.5px; color: var(--ink-faint); margin-top: 3px; }
/* inline sparkline pinned to the card's top-right; decorative, sits behind text */
.kpi-spark { position: absolute; top: 14px; right: 16px; opacity: .8; overflow: visible; }
@media (max-width: 480px) {
.kpi-spark { display: none; }
}
/* At-a-glance strip above a trend chart: light stat blocks (subtler than kpi-cards) that
   summarize the very series the chart plots. Divided by hairlines, wraps to 2-up then 1-up. */
.trend-glance {
  display: grid; grid-template-columns: repeat(3, 1fr);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); margin-bottom: 18px; overflow: hidden;
}
.trend-glance-item { padding: 14px 18px; border-left: 1px solid var(--border); }
.trend-glance-item:first-child { border-left: 0; }
.tg-label { display: block; font-size: 11.5px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .05em; }
.tg-value { display: block; font-size: 21px; font-weight: 750; letter-spacing: -.02em; margin-top: 4px; font-variant-numeric: tabular-nums; }
.tg-sub { display: block; font-size: 11.5px; color: var(--ink-faint); margin-top: 3px; }
@media (max-width: 640px) {
.trend-glance { grid-template-columns: 1fr; }
  .trend-glance-item { border-left: 0; border-top: 1px solid var(--border); }
  .trend-glance-item:first-child { border-top: 0; }
}
.panel {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 20px 22px; box-shadow: var(--shadow); margin-bottom: 18px;
}
.panel-head { margin-bottom: 10px; }
.panel-hint { font-size: 13px; margin: -4px 0 14px; max-width: 68ch; }
/* destructive panel (e.g. delete workspace): a subtle red frame so it reads as dangerous */
.panel-danger { border-color: color-mix(in srgb, var(--crit) 35%, var(--border)); }
.panel-danger .panel-head h2 { color: var(--crit); }

/* ---- billing page ---- */
.bill-summary { display: flex; gap: 32px; flex-wrap: wrap; align-items: flex-start; }
.bill-figure { display: flex; align-items: baseline; gap: 6px; flex: none; }
.bill-price { font-size: 30px; font-weight: 750; letter-spacing: -.02em; }
.bill-price-per { font-size: 13px; color: var(--ink-faint); }
.bill-usage { flex: 1; min-width: 220px; }
.bill-usage-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 13px; margin-bottom: 6px; }
.bill-usage-head > span:first-child { color: var(--ink-soft); }
.bill-usage-val { font-weight: 650; color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
.bill-usage-val { font-variant-numeric: tabular-nums; color: var(--ink-soft); }
.bill-usage-note { font-size: 12px; margin: 8px 0 0; }

.plan-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
@media (max-width: 900px) { .plan-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .plan-grid { grid-template-columns: 1fr; } }
/* AI credit costs (plan page): a label+description on the left, the credit pill on the right.
   Rows are a plain list, not a table - it stays readable on a phone without a horizontal scroll. */
/* Workspace activity feed (Team page). A table so it matches the members table above it and the
   columns line up down the page; the shared .data-table handles the phone stacking via data-label. */
.activity-table .col-who    { width: 22%; }
.activity-table .col-detail { width: 24%; }
.activity-table td[data-label="When"] { white-space: nowrap; }
/* A dot before the action marks AI-powered rows, so credit-spending work is scannable at a glance. */
.activity-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 999px;
  background: var(--ink-faint); margin-right: 7px; vertical-align: middle;
}
.activity-dot.is-ai { background: var(--accent); }
.activity-target { font-weight: 600; }
.activity-filter { flex: none; }
.pager-info { font-size: 12px; }

/* No-JS fallback for the assistant's empty state. The markup ships [hidden] so the suggested
   questions do not flash before the resume check finishes; the chat island adds .js-chat to <html>,
   so when the island never runs (JS off) this reveals them again. A <noscript><style> would be
   refused by the strict style-src 'self' CSP, hence the marker-class approach. */
html:not(.js-chat) [data-assistant-empty][hidden] { display: block; }

/* Admin AI cost inputs. The house input styling is scoped to .field, so these needed their own
   rule - unstyled they render as full-width browser defaults with square corners. A credit cost is
   1-2 digits, so the box is sized to the content and right-aligned under a numeric column header. */
.cost-input {
  font: inherit; width: 78px; padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
  text-align: right; font-variant-numeric: tabular-nums;
  -moz-appearance: textfield; appearance: textfield;
}
.cost-input:focus { outline: none; border-color: var(--accent); }
/* The number spinners steal width from a 2-digit field and are not useful here. */
.cost-input::-webkit-outer-spin-button,
.cost-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* AI share cell: the select and its "= N credits" hint sit on ONE line. Without this the hint wraps
   under the select and makes that row taller than every other row in the members table. */
form[data-team-aicap] { display: flex; align-items: center; gap: 8px; margin: 0; flex-wrap: nowrap; }
/* .item-selector is width:100% by default, which fills the cell and pushes the hint onto a second
   line. In this cell the select only ever holds "10%".."No limit", so size it to its content. */
form[data-team-aicap] .item-selector { width: auto; min-width: 96px; flex: none; }
.team-aicap-hint { font-size: 12px; white-space: nowrap; }

.ai-cost-list { list-style: none; margin: 0; padding: 0; }
.ai-cost-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 10px 0; border-bottom: 1px solid var(--border);
}
.ai-cost-row:last-child { border-bottom: 0; }
.ai-cost-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ai-cost-label { font-weight: 600; }
.ai-cost-desc { font-size: 13px; }
.ai-cost-pill { flex: none; font-variant-numeric: tabular-nums; }

.plan-tier {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: 14px; background: var(--surface);
  overflow: hidden;   /* let the tinted header band meet the rounded corners cleanly */
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.plan-tier.is-upgrade:hover { transform: translateY(-2px); box-shadow: 0 10px 28px -12px color-mix(in srgb, var(--accent) 45%, transparent); }
.plan-tier.is-current { border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent); }

/* Tinted header BAND: plan name + price sit on an accent-tinted cap, visually distinct from the specs
   below. Upgrade cards get the accent tint; other cards a neutral tint. */
.plan-tier-cap { padding: 16px 18px 14px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.plan-tier.is-upgrade .plan-tier-cap { background: var(--accent-soft); border-bottom-color: color-mix(in srgb, var(--accent) 22%, transparent); }
.plan-tier-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.plan-tier-name {
  font-weight: 700; font-size: 11.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-soft);
}
.plan-tier.is-upgrade .plan-tier-name { color: var(--accent); }
.plan-tier-price {
  display: flex; align-items: baseline; gap: 4px; flex-wrap: wrap;
  font-size: 30px; font-weight: 800; letter-spacing: -.03em; line-height: 1.05; color: var(--ink);
}
.plan-tier-per { font-size: 13px; font-weight: 500; color: var(--ink-faint); letter-spacing: 0; }
/* The body (specs + perks + CTA) is the padded area below the cap. */
.plan-tier-body { display: flex; flex-direction: column; flex: 1; padding: 4px 18px 18px; }
.plan-tier-mau { font-size: 12px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
/* Aligned spec rows: the SAME ordered metrics in every card, label left + value right, so a value sits
   on the same line across all plans (scannable). Replaces the old freeform per-plan bullet list. */
.plan-tier-specs { margin: 8px 0 14px; padding: 0; flex: 1; display: flex; flex-direction: column; }
.plan-spec-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 7px 0; font-size: 12.5px; border-top: 1px solid var(--border);
}
.plan-spec-row:first-child { border-top: 0; }
.plan-spec-row dt { color: var(--ink-soft); margin: 0; }
.plan-spec-row dd { margin: 0; font-weight: 650; color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A perk row is a spec row whose "value" is a green tick (included), so it aligns with the numbers above. */
.plan-perk-row dt { color: var(--ink); }
.plan-perk-tick { color: var(--good); font-weight: 700; }
/* Row holding the disclosure (left, grows + expands its cards full-width below) + the full-comparison
   dialog link (right, pinned to the top of the row). Both always visible. */
.plan-compare-bar { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.plan-compare-bar > .plan-compare { flex: 1; min-width: 0; margin-top: 0; }
/* Match the left disclosure toggle EXACTLY: same inline-flex box, size, weight, colour, gap + padding
   AND line-height, no underline (underline on hover). Scoped under .plan-compare-bar so it beats the
   later .linklike base rule (equal specificity would otherwise let .linklike's underline win). Same
   line-height as the summary + identical vertical padding keeps both text lines at the same height. */
.plan-compare-bar > .plan-compare > summary.plan-compare-toggle,
.plan-compare-bar .plan-compare-full-link {
  line-height: 1.5;
}
.plan-compare-bar .plan-compare-full-link {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 600; color: var(--accent);
  text-decoration: none; padding: 6px 0; white-space: nowrap;
}
.plan-compare-bar .plan-compare-full-link:hover { text-decoration: underline; }
/* ---- full feature-comparison dialog (settings/plan) ---- */
.modal.modal-xwide { max-width: 900px; max-height: 88vh; display: flex; flex-direction: column; }
.modal-head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.modal-x {
  border: 0; background: transparent; color: var(--ink-faint); cursor: pointer;
  font-size: 22px; line-height: 1; padding: 2px 6px; border-radius: 8px;
}
.modal-x:hover { background: var(--surface-2); color: var(--ink); }
.plan-matrix-wrap { overflow: auto; }
.plan-matrix { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.plan-matrix th, .plan-matrix td { padding: 9px 12px; text-align: center; border-bottom: 1px solid var(--border); }
.plan-matrix thead th { position: sticky; top: 0; background: var(--surface); z-index: 1; vertical-align: bottom; }
.plan-matrix .plan-matrix-feat { text-align: left; color: var(--ink-soft); white-space: nowrap; }
.plan-matrix-plan { display: block; font-weight: 700; font-size: 13px; color: var(--ink); }
.plan-matrix thead .pill { margin-top: 4px; }
.plan-matrix-section td {
  text-align: left; font-weight: 700; font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink-faint); background: var(--surface-2); padding-top: 12px; padding-bottom: 6px;
}
.plan-matrix-yes { color: var(--good); font-weight: 800; }
.plan-matrix-no { color: var(--ink-faint); }
.plan-matrix-txt { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
/* Current-plan column highlight: a soft accent tint running the full height of that column. */
.plan-matrix th.is-current-col, .plan-matrix td.is-current-col { background: var(--accent-soft); }
.plan-matrix th.is-current-col { border-bottom: 2px solid var(--accent); }
.plan-matrix td.is-current-col { border-left: 1px solid color-mix(in srgb, var(--accent) 25%, transparent); border-right: 1px solid color-mix(in srgb, var(--accent) 25%, transparent); }
/* legacy check-list bullets (kept if any other view still uses .plan-tier-features) */
.plan-tier-features { list-style: none; margin: 8px 0 14px; padding: 0; flex: 1; display: flex; flex-direction: column; gap: 6px; }
.plan-tier-features li { font-size: 12px; color: var(--ink-soft); line-height: 1.4; padding-left: 18px; position: relative; }
.plan-tier-features li::before {
  content: "\2713"; position: absolute; left: 0; top: 0; color: var(--good); font-weight: 700; font-size: 11px;
}
/* "Current + upgrade focus" layout. Upgrade tiers sit in their own responsive row (auto-fit so 1-4
   upgrades all lay out evenly, no orphaned card); an upgrade card is lifted with the accent. */
.plan-grid-upgrades { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); margin-bottom: 14px; }
.plan-tier.is-upgrade { border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); }
.plan-tier.is-upgrade:hover { border-color: var(--accent); box-shadow: 0 4px 16px -6px color-mix(in srgb, var(--accent) 40%, transparent); }
/* "Compare all plans / downgrade" disclosure - the current tier + downgrades + Free tuck in here so the
   page leads with the upgrade, not a wall of 5 cards. */
.plan-compare { margin-top: 4px; }
.plan-compare > summary.plan-compare-toggle {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 600; color: var(--accent); padding: 6px 0; user-select: none;
}
.plan-compare > summary.plan-compare-toggle::-webkit-details-marker { display: none; }
.plan-compare > summary.plan-compare-toggle::before {
  content: "\25B8"; font-size: 10px; transition: transform .15s ease; color: var(--ink-faint);
}
.plan-compare[open] > summary.plan-compare-toggle::before { transform: rotate(90deg); }
.plan-grid-others { margin-top: 12px; }
/* Plan-change confirm dialog: the proration breakdown table. */
.plan-confirm { margin: 4px 0 6px; }
.plan-confirm-table {
  border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin-bottom: 12px;
  font-variant-numeric: tabular-nums;
}
.plan-confirm-row { display: flex; justify-content: space-between; gap: 16px; padding: 10px 14px; font-size: 13.5px; }
.plan-confirm-row + .plan-confirm-row { border-top: 1px solid var(--border); }
.plan-confirm-row > span:first-child { color: var(--ink-soft); }
/* Amount (right) column never wraps - the label can wrap, the number stays on one line. */
.plan-confirm-row > span:last-child { white-space: nowrap; }
.plan-confirm-credit > span:last-child { color: var(--good); font-weight: 600; }
.plan-confirm-total { background: var(--surface-2, var(--surface)); font-weight: 700; }
.plan-confirm-total > span:first-child { color: var(--ink); }
.plan-confirm-total > span:last-child { font-size: 16px; }
.plan-confirm-note { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; margin: 0; }
/* the billing usage bar flags a near-limit fill directly on the fill element */
.usage-fill.is-near { background: var(--warn); }
/* current-plan card at the top of the Plan page */
/* The card is a plain block: the heading + sub-text are normal full-width flow; the two columns live in
   their own flex row (.current-plan-cols) so nothing above forces them to wrap or change direction. */
.current-plan { display: block; }
.current-plan > .panel-head { margin-bottom: 10px; }
/* Sub-text spacing matches the "Upgrade your plan" panel-hint (its base gives -4px top / 14px bottom). */
.current-plan-cols { display: flex; gap: 24px; align-items: stretch; flex-wrap: wrap; }
.current-plan-main { flex: 1 1 240px; min-width: 220px; display: flex; flex-direction: column; }
/* Lapsed reactivate CTA: pinned to the bottom-left of the card (main column). */
.current-plan-cta { margin-top: auto; padding-top: 16px; }
.current-plan-head { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.current-plan-action { margin-left: auto; }
/* Stacked title: plan name as an accent eyebrow label above the big price. */
.current-plan-title { display: flex; flex-direction: column; gap: 4px; }
.current-plan-name { font-size: 12px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--accent); }
.current-plan-price { display: inline-flex; align-items: baseline; gap: 2px; font-size: 32px; font-weight: 800; letter-spacing: -.03em; line-height: 1; color: var(--ink); }
.current-plan-per { font-size: 13px; font-weight: 500; color: var(--ink-faint); margin-left: 4px; }
.current-plan-blurb { font-size: 13px; margin: 12px 0 0; line-height: 1.5; }
/* The current plan's perks (what it unlocks), a compact check-marked list under the blurb. */
.current-plan-perks { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.current-plan-perks li { font-size: 12.5px; color: var(--ink-soft); line-height: 1.4; padding-left: 20px; position: relative; }
.current-plan-perks li::before {
  content: "\2713"; position: absolute; left: 0; top: 0; color: var(--good); font-weight: 700; font-size: 12px;
}
/* renewal/access status row (Active until / Renews on / Ends on) - a subtle chip so it reads as metadata. */
.current-plan-status {
  display: inline-flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-top: 14px;
  padding: 7px 12px; border-radius: 8px; background: var(--surface-2); align-self: flex-start;
}
.cps-label { font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
.cps-value { font-size: 13px; font-weight: 650; color: var(--ink); }
.current-plan-status.is-warn { background: color-mix(in srgb, var(--warn) 12%, transparent); }
.current-plan-status.is-warn .cps-value { color: var(--warn); }
.cps-note { font-size: 12px; color: var(--ink-faint); }
/* Usage column: a distinct, tinted "usage" zone separated from the plan info by a rounded panel. */
.current-plan-usage {
  flex: 1 1 300px; min-width: 260px; display: flex; flex-direction: column; gap: 18px;
  padding: 16px 18px; border-radius: 12px; background: var(--surface-2); align-self: stretch;
}
.usage-meter { display: flex; flex-direction: column; gap: 0; }
/* Over-limit fill (workspaces at 2/1 etc.): the is-near class turns the bar warn; a full/over bar reads red. */
.current-plan-usage .usage-fill.is-near { background: var(--warn); }
/* monthly / yearly segmented toggle on the Plan page */
.interval-toggle { display: inline-flex; background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 2px; }
.interval-opt {
  font: inherit; font-size: 12.5px; font-weight: 600; padding: 5px 14px; border-radius: 999px;
  border: none; background: none; color: var(--ink-soft); cursor: pointer;
}
.interval-opt.is-active { background: var(--surface); color: var(--ink); box-shadow: var(--shadow); }
/* "Save N%" badge on the Yearly toggle - a small green pill so the discount reads at a glance. */
.interval-save {
  display: inline-block; margin-left: 5px; padding: 1px 6px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .01em; line-height: 1.5;
  color: var(--good); background: color-mix(in srgb, var(--good) 15%, transparent);
}

/* ---- admin providers page ---- */
.provider-choice { display: flex; gap: 12px; flex-wrap: wrap; }
.provider-opt {
  display: flex; align-items: center; gap: 10px; font: inherit; text-align: left;
  padding: 12px 16px; border-radius: 12px; border: 1px solid var(--border);
  background: var(--surface); color: var(--ink); cursor: pointer; min-width: 180px;
}
.provider-opt:hover { border-color: var(--ink-faint); }
.provider-opt.is-active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.provider-name { font-weight: 650; }
.provider-model { font-size: 11px; }
.provider-active { margin-left: auto; font-size: 11px; font-weight: 650; color: var(--accent); text-transform: uppercase; letter-spacing: .04em; }
.ai-test-form { display: flex; align-items: center; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
.ai-test-form .btn { flex: none; }
/* The helper text sits inline with the button, vertically centered (no panel-hint negative margin). */
.ai-test-hint { margin: 0; font-size: 13px; line-height: 1.4; }
.price-id { font-size: 12px; color: var(--ink-soft); font-family: var(--mono, monospace); }

/* ---- admin e-invoices page ---- */
.row-actions { display: flex; gap: 8px; align-items: center; }
.stat-tiles { display: flex; gap: 14px; margin-bottom: 18px; flex-wrap: wrap; }
.stat-tile {
  flex: 1; min-width: 140px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; padding: 14px 16px; box-shadow: var(--shadow);
}
.stat-num { font-size: 26px; font-weight: 750; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
/* Smaller variant for a text value (e.g. a date) in a stat tile. */
.stat-num.stat-num-sm { font-size: 15px; font-weight: 650; }
.stat-lbl { font-size: 12px; color: var(--ink-faint); margin-top: 2px; }

/* ---- Monitoring page ------------------------------------------------------ */
/* Health component rows: a pill + label + optional note, wrapping on narrow screens. */
.monitor-components { display: flex; flex-direction: column; gap: 8px; margin-top: 6px; }
.monitor-comp { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; font-size: 13px; }
.monitor-comp-label { font-weight: 600; }
.monitor-comp-note { font-size: 12px; }
/* Alert tiles: a grid of clickable counters that go hot (coloured) when non-zero. */
.monitor-alerts { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.monitor-alert {
  display: flex; flex-direction: column; gap: 4px; padding: 12px 14px; text-decoration: none;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface); color: var(--ink);
  transition: border-color .12s;
}
.monitor-alert:hover { border-color: var(--ink-faint); text-decoration: none; }
.monitor-alert-num { font-size: 22px; font-weight: 750; font-variant-numeric: tabular-nums; }
.monitor-alert-num.is-hot { color: var(--warn); }
.monitor-alert-lbl { font-size: 12px; color: var(--ink-faint); }
.monitor-alert .pill { align-self: flex-start; margin-top: 2px; }
.monitor-hint { font-size: 11.5px; margin-top: 8px; }
.monitor-revenue { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.monitor-revenue-lbl { font-size: 12px; color: var(--ink-faint); }
.monitor-revenue-val { font-weight: 700; font-variant-numeric: tabular-nums; }

.filter-tabs { display: flex; gap: 4px; flex-wrap: wrap; }
/* Tabs are a mix of <a> presets and a <button> ("Custom", which toggles a panel rather than
   navigating). Reset the button-only UA defaults - border, grey background, system font - or the
   button renders as an outlined pill next to flat text links. */
.filter-tab {
  font-size: 12.5px; font-weight: 550; padding: 4px 10px; border-radius: 999px;
  color: var(--ink-soft); text-decoration: none;
  font-family: inherit; line-height: inherit;
  border: 0; background: none; cursor: pointer;
}
.filter-tab:hover { background: var(--surface-2); text-decoration: none; }
.filter-tab.is-active { background: var(--accent-soft); color: var(--accent); }

/* Labeled single-row filter toolbar (error logs): Range | Level | Status on one line, each with a
   small muted label so the three axes read as distinct instead of one repetitive block. Wraps
   gracefully on narrow screens. */
.filter-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 22px; }
.filter-group { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* The Range group holds the date-range picker, whose custom-date panel drops BELOW the presets, so
   that group aligns its label to the top and keeps the picker in its natural column layout. */
.filter-group-range { align-items: flex-start; }
.filter-group-range .filter-label { padding-top: 6px; }
.filter-label { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-faint); white-space: nowrap; }
.filter-select {
  font: inherit; font-size: 13px; padding: 5px 28px 5px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center;
}
/* Text input inside a filter toolbar - matches .filter-select so a search box and a dropdown sit
   on the same line without one looking taller or squarer than the other. */
.filter-input {
  font: inherit; font-size: 13px; padding: 5px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink); min-width: 180px;
}
.filter-input:hover { border-color: var(--accent); }
.filter-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Leads table: the status cell holds a select + Set button on ONE line. nowrap (not the usual
   flex-wrap) because wrapping is exactly the bug here - the button dropping below the select made
   every row two lines tall. The column is sized to fit both instead. */
.leads-table .col-status { width: 200px; }
.leads-table .col-page { width: 170px; }
.lead-status-form { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.lead-status-form .filter-select { flex: 1 1 auto; min-width: 0; }
.lead-status-set { flex: 0 0 auto; }
@media (max-width: 900px) {
  /* Stacked card layout: the cell is no longer in a fixed-width column, so let it size naturally. */
  .leads-table .lead-status-form { justify-content: flex-end; }
  .leads-table .lead-status-form .filter-select { flex: 0 1 auto; }
}
.subscribers-table .col-status { width: 110px; }
.subscribers-table .col-source { width: 132px; }
.filter-select:hover { border-color: var(--accent); }
.filter-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.irn-counts { display: flex; gap: 8px; flex-wrap: wrap; margin: 4px 0 12px; }
.irn-rows { max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.irn-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 5px 8px; border-radius: 6px; font-size: 12.5px; }
.irn-row:hover { background: var(--surface-2); }
.irn-row code { word-break: break-all; }

/* ---- workspaces cards page (modern, compact) ---- */
.ws-section { margin-bottom: 30px; }
/* Separate the groups properly. The old 30px bottom margin was barely more than the 25px grid gap,
   so "Your workspaces" and "Shared with you" ran together as one long list. Any section that FOLLOWS
   another gets a rule + real space above it, so the split is structural and not just a heading. */
.ws-section + .ws-section {
  margin-top: 38px; padding-top: 32px; border-top: 1px solid var(--border);
}
.ws-section-title { display: flex; align-items: center; gap: 9px; font-size: 18px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 3px; }
.ws-section-count {
  font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink-soft);
  background: var(--surface-2); border-radius: 999px; padding: 2px 9px; min-width: 22px; text-align: center;
}
.ws-section-sub { font-size: 13px; margin: 0 0 18px; max-width: 62ch; }
.ws-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 25px; }
@media (max-width: 900px) { .ws-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ws-grid { grid-template-columns: 1fr; } }

/* Own vs shared workspaces. The two sections are otherwise identical grids, so the cards carry the
   distinction by HUE: your own are accent/indigo (your plan, your billing), shared ones are green.
   Both get the same treatment - a 3px spine plus a matching avatar - so neither looks weaker than
   the other. Kept to the border + avatar so card size and grid alignment are untouched. */
.ws-card-own { border-left: 3px solid color-mix(in srgb, var(--accent) 55%, var(--border)); }
.ws-card-own:hover { border-left-color: var(--accent); }
/* Shared cards get their OWN colour, not a muted one: a dashed border + grey avatar read as
   "disabled", which is wrong for a workspace you actively work in. Same weight as your own cards,
   different hue. */
.ws-card-shared { border-left: 3px solid color-mix(in srgb, var(--good) 55%, var(--border)); }
.ws-card-shared:hover { border-left-color: var(--good); }
.ws-card-shared .ws-card-avatar {
  background: linear-gradient(140deg, var(--good), color-mix(in srgb, var(--good) 55%, #0ea5e9));
}
/* Carry the hue through the whole card: an indigo button and meter inside a green-spined card read
   as a mistake. The MAU meter keeps its --warn "near the cap" state, which is a status, not a theme. */
.ws-card-shared .ws-meter-fill:not(.is-near) { background: var(--good); }
.ws-card-shared .btn-primary {
  background: var(--good); border-color: var(--good); color: #fff;
}
.ws-card-shared .btn-primary:hover,
.ws-card-shared .btn-primary:active {
  background: color-mix(in srgb, var(--good) 82%, #000);
  border-color: color-mix(in srgb, var(--good) 82%, #000);
}
/* Section headings echo the same cue, so the grouping reads before you get to the cards. */
.ws-section-own .ws-section-count { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.ws-section-shared .ws-section-count { color: var(--good); background: color-mix(in srgb, var(--good) 12%, transparent); }

.ws-card {
  display: flex; flex-direction: column; gap: 15px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 15px; box-shadow: var(--shadow);
  transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease;
}
.ws-card:hover {
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(16,32,46,.05), 0 8px 22px rgba(16,32,46,.08);
  border-color: color-mix(in srgb, var(--accent) 28%, var(--border));
}
/* the whole card is clickable (opens the workspace); keyboard-focusable via role=link */
.ws-card.is-clickable { cursor: pointer; }
.ws-card.is-clickable:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

.ws-card-top { display: flex; align-items: center; gap: 10px; }
.ws-card-avatar {
  width: 34px; height: 34px; border-radius: 9px; flex: none; display: grid; place-items: center;
  font-size: 15px; font-weight: 700; color: #fff; letter-spacing: -.01em;
  background: linear-gradient(140deg, var(--accent), color-mix(in srgb, var(--accent) 50%, #8b5cf6));
}
.ws-card-id { min-width: 0; flex: 1; }
.ws-card-name {
  font-size: 14.5px; font-weight: 650; letter-spacing: -.01em; margin: 0 0 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ws-card-meta { display: flex; align-items: center; gap: 7px; font-size: 12px; }
.ws-role { font-weight: 600; color: var(--ink-soft); }
.ws-role.role-owner { color: var(--accent); }
.ws-plan { color: var(--ink-faint); }
.ws-dot { width: 3px; height: 3px; border-radius: 999px; background: var(--ink-faint); flex: none; }
/* Locked (read-only) workspace card: over the owner's plan cap. Crit pill + a subtle dimmed card so
   it reads as "paused" at a glance. */
.ws-card-locked {
  flex: none; font-size: 10px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  color: var(--crit); background: color-mix(in srgb, var(--crit) 14%, transparent);
  padding: 3px 7px; border-radius: 999px; white-space: nowrap;
}
.ws-card.is-locked { border-color: color-mix(in srgb, var(--crit) 35%, var(--border)); }
.ws-card.is-locked .ws-card-avatar { opacity: .7; }
.ws-card.is-locked .ws-card-name { color: var(--ink-soft); }

.ws-card-stats { display: flex; gap: 22px; }
.ws-stat { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ws-stat-num { font-size: 19px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.ws-stat-lbl { font-size: 11px; color: var(--ink-faint); }

.ws-meter { height: 5px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.ws-meter-fill { height: 100%; width: 0; border-radius: 999px; background: var(--accent); transition: width .4s ease; }
.ws-meter-fill.is-near { background: var(--warn); }
.ws-meter-note { font-size: 11.5px; }

.ws-card-foot { margin-top: auto; display: flex; justify-content: flex-end; }
.ws-open-form { margin: 0; display: flex; justify-content: flex-end; }
@media (prefers-reduced-motion: reduce) {
  .ws-card, .ws-meter-fill { transition: none; }
  .ws-card:hover { transform: none; }
}
/* Compact horizontal funnel: a few stat chips joined by arrows, so a narrowing (e.g. viewers
   -> never-opened -> openers) reads at a glance above a table. Wraps on narrow screens. */
.mini-funnel { display: flex; align-items: stretch; justify-content: center; gap: 22px; flex-wrap: wrap; margin: 8px auto 20px; }
.mini-funnel-step {
  display: flex; flex-direction: column; gap: 3px; padding: 12px 22px; min-width: 120px;
  text-align: center;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px;
}
.mini-funnel-step.is-drop { background: color-mix(in srgb, var(--warn) 10%, var(--surface-2)); border-color: color-mix(in srgb, var(--warn) 30%, var(--border)); }
.mini-funnel-step.is-base { background: color-mix(in srgb, var(--accent) 8%, var(--surface-2)); border-color: color-mix(in srgb, var(--accent) 34%, var(--border)); }
.mini-funnel-n { font-size: 22px; font-weight: 750; line-height: 1.1; letter-spacing: -0.02em; }
.mini-funnel-step.is-drop .mini-funnel-n { color: var(--warn); }
.mini-funnel-step.is-base .mini-funnel-n { color: var(--accent); }
.mini-funnel-l { font-size: 11.5px; color: var(--ink-soft); font-weight: 550; }
.mini-funnel-arrow { align-self: center; color: var(--ink-faint); font-size: 18px; }
/* Small secondary note inside a table cell (e.g. "3 dismissed" under a skipped count). */
.cell-note { display: block; font-size: 11px; color: var(--ink-faint); font-weight: 500; margin-top: 1px; }
/* Muted legend under a table, explaining a column's meaning. */
.table-legend { font-size: 12px; color: var(--ink-soft); margin: 10px 0 0; max-width: 72ch; line-height: 1.5; }
.table-legend strong { color: var(--ink); font-weight: 600; }
/* panel head with a title on the left and an action/pill on the right */
.panel-head-split { display: flex; align-items: center; justify-content: space-between; }
/* sub-heading inside a panel (e.g. a second chart under the per-step table) */
.panel-subhead { margin: 18px 0 8px; padding-top: 16px; border-top: 1px solid var(--border); }
/* extra top gap for a SECONDARY sub-section (e.g. the dismiss detail under viewer outcomes),
   so it reads as a distinct block rather than crowding the chart above it. */
.panel-subhead-spaced { margin-top: 30px; }
/* a subhead at the top of a column (e.g. the segment-completion 2col) needs no
   divider above it */
.analytics-2col > div > .panel-subhead:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.panel-subhead h3 { font-size: 14px; font-weight: 650; }
/* headline insight banner: worst step, new-vs-returning */
/* Free-plan gate for the deep analytics: basic KPIs stay visible, the detail panel is replaced
   by this locked upgrade nudge. Sits in the same .panel frame as the content it stands in for. */
.upgrade-lock { display: flex; gap: 16px; align-items: flex-start; }
.upgrade-lock-icon { font-size: 24px; line-height: 1; flex: 0 0 auto; opacity: .8; }
.upgrade-lock-body { min-width: 0; }
.upgrade-lock-body h2 { margin: 0 0 6px; }
.upgrade-lock-body p { margin: 0 0 14px; max-width: 60ch; }
/* Inline "you're at your plan limit" warning under a capped list (keys/extensions). Amber warn
   treatment (a left rule + tinted background) so it reads as "action needed", not a neutral tip.
   The Upgrade link stays accent - it is the primary action. */
.upgrade-nudge { font-size: 13px; margin: 10px 0 14px; padding: 9px 12px; border-radius: 9px;
  background: color-mix(in srgb, var(--warn) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--border));
  border-left: 3px solid var(--warn); color: var(--ink-soft); }
.upgrade-nudge::before { content: "\26A0\FE0F"; margin-right: 6px; }
.upgrade-nudge a { color: var(--accent); font-weight: 600; text-decoration: none; }
.upgrade-nudge a:hover { text-decoration: underline; }

/* A larger plan-gate card, for where the gate IS the content of the panel rather than an
   aside next to something usable. Accent-tinted, not warning-tinted: nothing has gone wrong,
   this is an offer - the warning colours of .upgrade-nudge read as an error at this size. */
.upgrade-card { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
  margin: 4px 0 2px; padding: 18px 20px; border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--border)); }
.upgrade-card-icon { flex: none; width: 38px; height: 38px; border-radius: 10px;
  display: grid; place-items: center; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border)); }
.upgrade-card-icon svg { width: 19px; height: 19px; display: block; }
/* Takes the free space so the CTA sits at the far end on one line, and wraps under on narrow. */
.upgrade-card-body { flex: 1 1 300px; min-width: 0; }
.upgrade-card-body h3 { margin: 0 0 4px; font-size: 15px; font-weight: 650; color: var(--ink); }
.upgrade-card-body p { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-soft); }
.upgrade-card-cta { flex: none; align-self: center; }
.insight-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; margin-bottom: 18px; }
@media (max-width: 820px) {
.insight-row { grid-template-columns: 1fr; }
}
.insight-card {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; box-shadow: var(--shadow);
}
.insight-card.insight-warn { border-color: var(--warn, #f0a020); }
/* segment callout: a one-line lead above the completion-by-segment bars that names the
   weakest group (warn) or confirms parity (neutral/good), so the panel reads as RATE
   diagnostics rather than a repeat of the Audience view-count charts. */
.seg-callout {
  display: flex; gap: 9px; align-items: flex-start; margin: 0 0 16px;
  padding: 10px 13px; border-radius: var(--radius); font-size: 13px; line-height: 1.45;
  background: var(--surface-2); border: 1px solid var(--border);
}
.seg-callout-ico { flex: none; font-size: 14px; line-height: 1.35; }
.seg-callout-warn {
  background: color-mix(in srgb, var(--warn) 10%, var(--surface));
  border-color: color-mix(in srgb, var(--warn) 34%, var(--border));
}
.seg-callout-warn .seg-callout-ico { color: var(--warn); }
.insight-icon { font-size: 20px; line-height: 1.2; flex: none; }
.insight-title { font-weight: 650; font-size: 15px; }
.insight-sub { font-size: 12.5px; color: var(--ink-soft); margin-top: 2px; }
/* drop-off cause chips: show that "drop-off" is composed of distinct causes
   (skip is only one of them), each a small colored tag. */
.cause-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.cause-chip {
  font-size: 11.5px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--ink-soft); background: var(--surface-2);
}
.cause-chip::before { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 5px; vertical-align: middle; background: currentColor; }
.cause-skip { color: #8c52ff; }
.cause-back { color: #ff922b; }
.cause-dismiss { color: var(--crit); }
.cause-other { color: var(--ink-faint); }
.cause-note { font-size: 11.5px; color: var(--ink-faint); margin-top: 6px; }
/* small spacing utilities (CSP forbids inline style attributes) */
.mt-12 { margin-top: 12px; }
.mt-14 { margin-top: 14px; }
.mt-16 { margin-top: 16px; }
/* screen-reader-only (data-table fallback for charts, etc.) */
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- analytics date range ---- */
.analytics-range {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  flex-wrap: wrap; margin-bottom: 18px;
}
.analytics-range .field { margin-bottom: 0; }
.analytics-range .range-preset select {
  font: inherit; padding: 7px 28px 7px 10px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%237a8296' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
}
.range-custom { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.range-custom input[type="date"] {
  font: inherit; padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
}
.range-sep { color: var(--ink-faint); font-size: 13px; }
.analytics-range.is-loading { opacity: .55; pointer-events: none; }

/* KPI delta badge (vs previous period) */
.kpi-delta { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; font-weight: 600; margin-top: 4px; }
.kpi-delta-up { color: var(--good); }
.kpi-delta-down { color: var(--crit); }
.kpi-delta-flat { color: var(--ink-faint); }

/* two side-by-side panels that stack on narrow screens */
.analytics-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 720px) {
.analytics-2col { grid-template-columns: 1fr; }
}

/* ---- overview (dashboard home) ---- */
/* getting-started checklist (hides once complete) */
.setup-panel { border-left: 3px solid var(--accent); }
.setup-progress { font-size: 12.5px; font-variant-numeric: tabular-nums; }
/* Persistent "install the builder extension" nudge on the overview (until the workspace pairs). */
.install-ext-card { display: flex; gap: 16px; align-items: flex-start; border-left: 3px solid var(--accent);
  position: relative; }

/* Session dismiss on a persistent card. Quiet by default and only fully visible on hover/focus:
   it is an escape hatch, not an action being offered - the card's own buttons are the point. */
.card-dismiss {
  position: absolute; top: 10px; right: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: 7px;
  color: var(--ink-faint); opacity: .6;
}
.card-dismiss:hover { opacity: 1; color: var(--ink); background: var(--surface-2); }
.card-dismiss:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: 2px; }
.install-ext-icon { font-size: 26px; line-height: 1; flex: 0 0 auto; }
/* padding-right keeps a long heading clear of the dismiss button in the corner. */
.install-ext-body { min-width: 0; padding-right: 30px; }
.install-ext-body h2 { margin: 0 0 6px; }
.install-ext-body p { margin: 0 0 14px; max-width: 64ch; }
.install-ext-actions { display: flex; gap: 8px; flex-wrap: wrap; }
/* two-column checklist so 4 short steps stay compact instead of stretching wide */
.setup-list {
  list-style: none; margin: 4px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px 24px;
}
@media (max-width: 640px) {
.setup-list { grid-template-columns: 1fr; }
}
.setup-item { display: flex; align-items: center; gap: 10px; padding: 7px 0; font-size: 14px; }
.setup-check {
  flex: none; width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center;
  border: 1.5px solid var(--border); color: var(--good); font-size: 12px; font-weight: 700;
}
.setup-item.is-done .setup-check { border-color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent); }
.setup-item.is-done .setup-label { color: var(--ink-faint); text-decoration: line-through; }
.setup-label a { font-weight: 600; }
/* plan / MAU usage meter */
.usage-strip {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 12px 16px; margin-bottom: 18px; box-shadow: var(--shadow);
}
.usage-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 13.5px; color: var(--ink-soft); flex-wrap: wrap; }
.usage-bar { height: 7px; border-radius: 999px; background: var(--surface-2); overflow: hidden; margin-top: 9px; }
/* Inside the tinted usage panel the track must contrast against the panel (which is also surface-2). */
.current-plan-usage .usage-bar { height: 8px; background: var(--border); }
.usage-fill { height: 100%; width: 0; border-radius: 999px; background: var(--accent); transition: width .4s ease; }
.usage-strip.is-near .usage-fill { background: var(--warn); }
.usage-strip.is-over .usage-fill { background: var(--crit); }
/* Sidebar MAU meter: the near/over state lives on the parent .sidebar-usage, so colour its fill from
   there too (mirrors .usage-strip). Without this the bar stayed accent even when over the cap. */
.sidebar-usage.is-near .usage-fill { background: var(--warn); }
.sidebar-usage.is-over .usage-fill { background: var(--crit); }
/* Trial countdown fill: colour shifts as the trial elapses - green -> amber (>=50%) -> red (>=80%). */
.trial-fill.is-ok { background: var(--good); }
.trial-fill.is-warn { background: var(--warn); }
.trial-fill.is-crit { background: var(--crit); }
.usage-note { font-size: 12.5px; color: var(--ink-soft); margin: 8px 0 0; }
.usage-strip.is-over .usage-note { color: var(--crit); }
.overview-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 860px) {
.overview-2col { grid-template-columns: 1fr; }
}
/* Analytics page: header export buttons, per-flow completion-rate bar, device/country bar lists. */
.head-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.rate-bar { display: inline-block; width: 90px; height: 7px; border-radius: 4px; background: var(--accent-soft);
  overflow: hidden; vertical-align: middle; }
.rate-bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
.rate-bar-num { font-size: 12px; color: var(--ink-soft); margin-left: 8px; font-variant-numeric: tabular-nums; }
.bar-list { list-style: none; margin: 0; padding: 4px 0 0; display: flex; flex-direction: column; gap: 10px; }
.bar-list li { display: grid; grid-template-columns: minmax(90px, 150px) 1fr auto; gap: 12px; align-items: center; }
.bar-list-label { font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-list-track { height: 8px; border-radius: 5px; background: var(--accent-soft); overflow: hidden; }
.bar-list-fill { display: block; height: 100%; background: var(--accent); border-radius: 5px; }
.bar-list-num { font-size: 12.5px; color: var(--ink-soft); font-variant-numeric: tabular-nums; text-align: right; min-width: 44px; }
.link-sm { font-size: 13px; }
/* recent activity feed */
.activity-feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.activity-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid var(--border); font-size: 13.5px;
}
.activity-item:last-child { border-bottom: none; }
.activity-ico {
  flex: none; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  font-size: 11px; background: var(--surface-2); color: var(--ink-soft);
}
.activity-flow_complete .activity-ico { color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent); }
.activity-flow_dismiss .activity-ico  { color: var(--crit); background: color-mix(in srgb, var(--crit) 12%, transparent); }
.activity-goal .activity-ico          { color: var(--accent); background: var(--accent-soft); }
.activity-text { flex: 1 1 auto; color: var(--ink-soft); }
.activity-text a { color: var(--ink); font-weight: 600; }
.activity-flag { margin-left: 4px; }
.activity-time { flex: none; font-size: 12px; color: var(--ink-faint); white-space: nowrap; }
/* quick actions */
.quick-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 18px; }
@media (max-width: 720px) {
.quick-actions { grid-template-columns: 1fr; }
}
.quick-action {
  display: flex; flex-direction: column; gap: 3px; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: var(--radius); text-decoration: none;
  background: var(--surface); transition: border-color .12s, background .12s;
}
.quick-action:hover { border-color: var(--accent); background: var(--surface-2); text-decoration: none; }
.quick-action-title { font-weight: 650; color: var(--ink); }
.quick-action-sub { font-size: 12.5px; color: var(--ink-faint); }
/* activity page: filter tabs + pager */
.activity-tabs { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.activity-tab {
  /* reset so a <button> tab (in the flow dialog) matches an <a> tab (account page) */
  appearance: none; background: none; border: none; cursor: pointer; font: inherit;
  padding: 8px 14px; font-size: 13.5px; font-weight: 600; color: var(--ink-soft);
  text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: 6px 6px 0 0;
}
.activity-tab:hover { color: var(--ink); background: var(--surface-2); text-decoration: none; }
.activity-tab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.pager-status { font-size: 12.5px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
/* Editable page-number jump input inside a pager (type a page + Enter). */
.pager-jump { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; }
.pager-input {
  width: 3.2em; text-align: center; font: inherit; font-size: 12.5px; font-variant-numeric: tabular-nums;
  padding: 3px 4px; border: 1px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); color: var(--ink);
}
.pager-input:focus { outline: none; border-color: var(--accent); }
/* AJAX-loading state for any pager-driven table container. */
[data-ajax-pager].is-loading { opacity: .55; pointer-events: none; transition: opacity .15s ease; }
/* Fixed layout so column widths do NOT shift when the AJAX pager swaps in a page with different
   content lengths. Only the Activity column (no width set) flexes to fill the rest. */
.ai-usage-table { table-layout: fixed; width: 100%; }
.ai-usage-table .col-user { width: 22%; }
.ai-usage-table .col-num  { width: 84px; }
/* Tokens now shows "in / out" plus the cost beneath, so it needs more room than the Credits column
   next to it. col-num applies to both, so the Tokens col (the last of the two) is widened here. */
.ai-usage-table .col-num + .col-num { width: 128px; }
.ai-usage-table .col-act  { width: 150px; }
/* The action button must not wrap - "View chat" was breaking onto two lines at 125px. */
.ai-usage-table .col-act, .ai-usage-table td:last-child .btn { white-space: nowrap; }
/* The merged "Activity" cell: the feature name on top, the prompt (if any) beneath it, muted + ellipsised. */
.ai-usage-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ai-usage-feat { font-weight: 600; }
.ai-usage-prompt { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: var(--ink-soft); }
/* Estimated provider cost, stacked under the token count in the same cell. Secondary to the number
   it is derived from, and tabular so the amounts align down the column. */
.ai-usage-cost { display: block; font-size: 12px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
/* Themed hover tip for table cells, matching the collapsed-sidebar tooltip rather than the browser's
   native title bubble (which is slow to appear, unstyled, and invisible in dark mode). Positioned
   above the element and centred; the row sets no overflow so it is free to escape the cell. */
.tip-up { position: relative; }
/* A <time> is inline, so it has no reliable box for the bubble to anchor to - and inside a table
   cell the bubble would be clipped by the row. inline-block gives it one without changing flow. */
time.tip-up { display: inline-block; }
.tip-up::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  background: var(--ink); color: var(--surface); font-size: 11.5px; font-weight: 550; line-height: 1.45;
  padding: 5px 9px; border-radius: 6px; white-space: nowrap; pointer-events: none;
  opacity: 0; z-index: 60; box-shadow: var(--shadow); transition: opacity .12s ease;
}
.tip-up:hover::after { opacity: 1; }
/* Near the right edge of a table the centred bubble would overflow, so the last columns anchor right. */
.data-table td:last-child .tip-up::after,
.data-table td:nth-last-child(2) .tip-up::after { left: auto; right: 0; transform: none; }
.ai-usage-tok { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The separator is quieter than the two numbers it divides, so the figures read first. */
.ai-usage-tok-sep { color: var(--ink-soft); opacity: .65; margin: 0 1px; }
/* Small qualifier inside a table header ("in / out") - normal case + weight against the uppercase th. */
.data-table th .th-sub { font-weight: 400; text-transform: none; letter-spacing: 0; opacity: .7; }
/* Same fixed layout for the user's chats table so page swaps don't reflow columns. */
.user-chats-table { table-layout: fixed; width: 100%; }
.user-chats-table .col-ws   { width: 26%; }
.user-chats-table .col-num  { width: 84px; }
.user-chats-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Assistant "Recent conversations": user in its own column, wider "When", fixed so paging doesn't reflow. */
.conversations-table { table-layout: fixed; width: 100%; }
.conversations-table .col-user { width: 20%; }
.conversations-table .col-ws   { width: 16%; }
.conversations-table .col-num  { width: 74px; }
.conversations-table td { overflow: hidden; text-overflow: ellipsis; }
.conversations-table td[data-label="When"] { white-space: nowrap; }
/* Segmented filter (All / Helpful / Not helpful) above the feedback table. */
.seg-group { display: inline-flex; gap: 2px; padding: 3px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2, var(--surface)); margin-bottom: 14px; flex-wrap: wrap; }
.seg-group .seg {
  font: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
  border: 0; background: transparent; color: var(--ink-soft);
  padding: 6px 12px; border-radius: 7px; line-height: 1.2;
}
.seg-group .seg:hover { color: var(--ink); }
.seg-group .seg.is-active { background: var(--accent); color: #fff; }
.seg-group .seg:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Feedback table: rating pill fixed-width, question+answer flexes, who/when fixed - stable across pages. */
.feedback-table { table-layout: fixed; width: 100%; }
.feedback-table .col-rating { width: 128px; }
.feedback-table .col-who    { width: 22%; }
.feedback-table td { vertical-align: top; }
.feedback-answer { font-size: 12.5px; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.feedback-note { font-size: 12.5px; margin-top: 3px; color: var(--ink-soft); font-style: italic; }
.feedback-ws { font-size: 11.5px; margin-top: 1px; }
/* "Usage by users" (admin AI): rank + user flex + fixed numeric/when columns so paging doesn't reflow. */
.ai-users-table { table-layout: fixed; width: 100%; }
.ai-users-table .col-rank { width: 44px; }
.ai-users-table .col-ws     { width: 20%; }
.ai-users-table .col-credit { width: 125px; }
.ai-users-table .col-num    { width: 84px; }
.ai-users-table td[data-label="User"] { overflow: hidden; text-overflow: ellipsis; }
.btn.is-disabled { opacity: .5; pointer-events: none; }
/* Native disabled state (attribute, not just the .is-disabled class): grey it out + block interaction so
   a plan-gated button (e.g. free-plan "Add Language") clearly reads and behaves as unavailable. */
.btn:disabled, .btn[disabled] { opacity: .5; pointer-events: none; cursor: not-allowed; }
/* dim the activity list while an AJAX tab/page load is in flight */
[data-activity].is-loading [data-activity-list] { opacity: .5; transition: opacity .1s; pointer-events: none; }
/* flow "View activity" dialog: scrollable list body inside the modal */
.page-head-actions { display: flex; align-items: center; gap: 8px; }
/* AI "Generate an audience" form: a description sentence needs a roomier field than the 360px
   form-narrow, so give it a comfortable reading width (the field fills it) without stretching
   full-bleed across the panel. */
.audience-ai-form { max-width: min(620px, 100%); margin-top: 10px; }
.audience-ai-form .field { width: 100%; }

/* AI analytics insights panel (Phase 4): styled distinctly from the plain metric cards so it reads as
   the AI feature - an accent-tinted surface with an accent hairline, matching the "Get insights" button.
   Diagnosis + observations + suggestions, with an optional rewrite card that deep-links into the editor. */
/* A subtle accent tint (accent-soft blended toward the surface), not a full lavender fill - keeps the
   card light while still reading as the AI feature; the accent border + sparkle carry the identity. */
.panel.ai-insights {
  background: var(--accent-soft); /* fallback for browsers without color-mix */
  background: color-mix(in srgb, var(--accent-soft) 45%, var(--surface));
  border-color: var(--accent);
}
/* Deep-link arrival highlight (from the help assistant's "Get AI insights" / "where to focus" links): a
   brief accent glow so the user notices where to click. */
.panel.is-flash { animation: ai-panel-flash 1.6s ease; }
@keyframes ai-panel-flash {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  15%, 60% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .panel.is-flash { animation: none; } }
/* Head carries no bottom margin: when the body is empty (before "Get insights"), the panel's own
   padding is the only gap; once the body renders it adds its own top margin. */
.panel.ai-insights > .panel-head { margin-bottom: 0; }
/* Unrun, this panel is a one-line offer, and full panel padding gave it the height of a results
   card - on the analytics page it stacked with the past-test panel into a screen of chrome above
   the first real metric. Tightened only while the body is hidden, so a rendered insight still
   gets the full panel. Keyed off [hidden] rather than a class, because that is the same flag the
   island already toggles when the results arrive. */
.panel.ai-insights:has(> .ai-insights-body[hidden]) { padding: 14px 18px; }
.panel.ai-insights:has(> .ai-insights-body[hidden]) .panel-head p { font-size: 12.5px; }
.panel.ai-insights > .panel-head h2 { display: inline-flex; align-items: center; gap: 7px; margin: 0; }
.panel.ai-insights > .panel-head p { margin-bottom: 0; }
.panel.ai-insights > .panel-head h2::before {
  content: ""; width: 16px; height: 16px; flex: none;
  background: var(--accent);
  /* sparkle glyph as a mask so it takes the accent colour */
  -webkit-mask: var(--ai-spark-mask) center / contain no-repeat;
          mask: var(--ai-spark-mask) center / contain no-repeat;
}
:root { --ai-spark-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.5l1.6 4.4a4 4 0 0 0 2.4 2.4l4.4 1.6-4.4 1.6a4 4 0 0 0-2.4 2.4L12 19.3l-1.6-4.4a4 4 0 0 0-2.4-2.4L3.6 10.9l4.4-1.6a4 4 0 0 0 2.4-2.4z'/%3E%3C/svg%3E"); }
/* Account-level "Flows to fix" panel: same accent-tinted treatment as the per-flow insights panel. */
.panel.ai-attention {
  background: var(--accent-soft);
  background: color-mix(in srgb, var(--accent-soft) 45%, var(--surface));
  border-color: var(--accent);
}
.panel.ai-attention > .panel-head { margin-bottom: 0; }
.panel.ai-attention > .panel-head h2 { margin: 0; }
.panel.ai-attention > .panel-head p { margin-bottom: 0; }
.ai-attention-body { margin-top: 16px; display: flex; flex-direction: column; gap: 12px; }
.ai-attention-empty { margin: 0; font-size: 13.5px; color: var(--ink-soft); }
.ai-attention-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
.ai-attention-item {
  display: flex; align-items: flex-start; gap: 12px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 11px 13px;
}
.ai-attention-main { flex: 1; min-width: 0; } /* min-width:0 lets the note wrap instead of overflowing */
.ai-attention-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ai-attention-name { font-size: 14px; font-weight: 600; color: var(--ink); }
.ai-attention-metrics { font-size: 12px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.ai-attention-note { margin: 3px 0 0; font-size: 13px; line-height: 1.45; color: var(--ink-soft); overflow-wrap: anywhere; }
.ai-attention-link { flex: none; align-self: center; }
/* The cache note + footer note inside this panel get clear separation from the cards around them. */
.ai-attention-body > .ai-insights-cachenote { margin: 0; }

.ai-insights-body { margin-top: 14px; display: flex; flex-direction: column; gap: 14px; max-width: 720px; }
.ai-insights-cachenote { margin: 0; font-size: 12px; color: var(--ink-faint); font-style: italic; }
/* "AI can be wrong" disclaimer footnote - quiet, sits under the whole insight. */
.ai-insights-disclaimer {
  display: flex; gap: 7px; align-items: flex-start;
  margin-top: 2px; padding-top: 12px; border-top: 1px solid var(--border);
  font-size: 12px; line-height: 1.5; color: var(--ink-faint);
}
.ai-insights-disclaimer-ico { flex: none; }
.ai-insights-headline { margin: 0; font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.5; }
/* Applied rewrite: the Apply button becomes a quiet confirmed state. */
.ai-rw-apply.is-applied { background: var(--surface-2); color: var(--ink-soft); border-color: var(--border); cursor: default; }
.ai-insights-section h3 { margin: 0 0 6px; font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft); }
/* Rewrites section header: the "Suggested rewrites" label with an "Apply all" button on the right. */
.ai-insights-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 6px; }
.ai-insights-section-head h3 { margin: 0; }
.ai-apply-all { flex: none; color: var(--accent); border-color: var(--accent); background: transparent; }
.ai-apply-all:hover { background: var(--accent-soft); border-color: var(--accent); }
.ai-apply-all.is-applied { color: var(--ink-soft); border-color: var(--border); background: var(--surface-2); cursor: default; }
.ai-insights-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 5px; }
.ai-insights-list li { font-size: 13.5px; line-height: 1.5; color: var(--ink); }
.ai-insights-step {
  display: inline-block; font-size: 11px; font-weight: 700; color: var(--accent);
  background: var(--accent-soft); border-radius: 5px; padding: 1px 6px; margin-right: 2px;
}
/* Rewrite rows: a header (step + reason + Apply) over a two-COLUMN diff (Current | Suggested), so the
   change is always visible but compact - side by side, not stacked - keeping 2-3 rewrites short. */
.ai-rw-group { display: flex; flex-direction: column; gap: 10px; }
.ai-rw { border: 1px solid var(--border); border-radius: 10px; background: var(--surface); overflow: hidden; }
.ai-rw-head { display: flex; align-items: center; gap: 10px; padding: 9px 12px; }
.ai-rw-reason { flex: 1; min-width: 0; font-size: 13px; color: var(--ink-soft); line-height: 1.4; }
.ai-rw-apply { flex: none; }
.ai-rw-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--border); border-top: 1px solid var(--border);
}
.ai-rw-col { background: var(--surface); padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; }
.ai-rw-col.is-new { background: var(--accent-soft); }
.ai-rw-col-tag {
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-faint);
}
.ai-rw-col.is-new .ai-rw-col-tag { color: var(--accent); }
.ai-rw-field { display: flex; flex-direction: column; gap: 2px; }
.ai-insights-rewrite-label { font-size: 10px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; color: var(--ink-faint); }
.ai-rw-val { font-size: 13px; line-height: 1.45; color: var(--ink); }
.ai-rw-val a { color: var(--accent); }
.ai-rw-col.is-current .ai-rw-val { color: var(--ink-soft); }
.ai-rw-empty { color: var(--ink-faint); font-style: italic; }
/* On narrow screens the two columns stack (Current above Suggested). */
@media (max-width: 620px) { .ai-rw-grid { grid-template-columns: 1fr; } }
.fa-list { margin-top: 4px; max-height: 60vh; overflow-y: auto; }
.fa-list.is-loading { opacity: .5; pointer-events: none; transition: opacity .1s; }

/* tabbed analytics groups: KPIs/insights stay above; the chart panels split into
   a Performance tab and an Audience tab so the report is not one long scroll. */
/* Clean underline tabs: no tray/background box (which read as a clunky floating panel).
   The active tab is marked by its accent color + a solid accent underline that sits on the
   bar's hairline, so the current section is obvious without any nested-pill geometry.
   Driven by aria-selected (the analytics tabs use ARIA, not .is-active). */
.analytics-tabs .tab-bar {
  display: flex; gap: 2px; margin-bottom: 20px;
  /* Explicitly clear the generic .tab-bar pill-tray look (background/padding/rounded border)
     - only the bottom hairline remains, so the tabs are a clean underline row, not a box. */
  background: none; padding: 0; border: none;
  border-bottom: 1px solid var(--border); border-radius: 0;
}
.analytics-tabs .tab {
  appearance: none; background: none; border: none; cursor: pointer;
  padding: 11px 18px; font-size: 14px; font-weight: 600; line-height: 1;
  color: var(--ink-soft); position: relative; margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  transition: color .12s ease, border-color .12s ease;
}
.analytics-tabs .tab:hover { color: var(--ink); }
.analytics-tabs .tab[aria-selected="true"] {
  color: var(--accent); border-bottom-color: var(--accent); font-weight: 700;
}
.analytics-tabs .tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.tab-panel[hidden] { display: none; }
.tab-intro { font-size: 13.5px; color: var(--ink-soft); max-width: 74ch; margin: 0 0 18px; }

/* ---- charts ---- */
.chart { width: 100%; position: relative; }
/* shared hover tooltip for the hand-rolled charts (fixed, follows the cursor) */
.chart-tip {
  position: fixed; z-index: 600; pointer-events: none;
  background: var(--surface); color: var(--ink); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: var(--shadow); padding: 7px 10px;
  font-size: 12.5px; line-height: 1.4; max-width: 220px;
}
.chart-tip-label { display: block; font-weight: 650; margin-bottom: 2px; }
.chart-tip-row { display: flex; align-items: center; gap: 6px; color: var(--ink-soft); }
.chart-tip-row b { color: var(--ink); font-weight: 700; }
.chart-tip-dot { width: 9px; height: 9px; border-radius: 3px; flex: none; }
/* legend (line multi-series, pie) */
.chart-legend { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 10px; font-size: 12.5px; color: var(--ink-soft); }
.chart-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend-dot { width: 10px; height: 10px; border-radius: 3px; flex: none; }
/* conversion funnel */
.chart-funnel { display: flex; flex-direction: column; gap: 12px; }
.funnel-head { display: flex; justify-content: space-between; align-items: center; font-size: 13px; margin-bottom: 4px; }
.funnel-label { display: inline-flex; align-items: center; gap: 8px; }
.funnel-pill {
  font-size: 11px; font-weight: 600; color: var(--accent);
  background: var(--accent-soft); border-radius: 999px; padding: 2px 9px;
}
.funnel-note { font-size: 11px; color: var(--ink-faint); font-weight: 500; }
.funnel-meta { font-variant-numeric: tabular-nums; }
/* inline KPI (a headline number + label above a chart, e.g. activation rate) */
.kpi-inline { display: flex; align-items: baseline; gap: 8px; margin-bottom: 14px; }
.kpi-inline-value { font-size: 24px; font-weight: 750; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.kpi-inline-label { font-size: 13px; color: var(--ink-soft); }
.funnel-track { height: 20px; border-radius: 6px; overflow: hidden; }
.funnel-fill { height: 100%; border-radius: 6px; transition: width .3s ease; min-width: 2px; }
/* pie/donut */
.chart-pie { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.chart-pie .chart-legend { flex-direction: column; gap: 8px; margin-top: 0; }
.chart svg { display: block; max-width: 100%; }
/* ONE shared grid for all rows (not a flex column of independent per-row grids). With each
   row its own grid, the `auto` value column sized to THAT row's text - so a shorter value
   ("100% - 1 left") gave that row a wider track than a longer value ("0% - no drop-off"), and
   the bars did not line up. A single 3-column grid, with each row using display:contents so its
   cells become items of the shared grid, makes the value column the same width for every row -
   so every track starts and ends at the same x and the bars align. */
.chart-bars { display: grid; grid-template-columns: 120px 1fr auto; align-items: center; gap: 10px 14px; }
.chart-bar-row { display: contents; }
.chart-bar-label { font-size: 13px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chart-bar-track { height: 12px; border-radius: 999px; overflow: hidden; }
.chart-bar-fill { height: 100%; border-radius: 999px; transition: width .3s ease; min-width: 2px; }
.chart-bar-value { font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 600px) {
.chart-bars { grid-template-columns: 84px 1fr auto; gap: 8px; }
}
/* read-only field (e.g. the account/username shown on the change-password form) */
.input-readonly { background: var(--surface-2); color: var(--ink-soft); cursor: default; }

/* ---- buttons / forms ---- */
.btn {
  display: inline-flex; align-items: center; gap: 8px; font: inherit; font-weight: 600;
  border-radius: 8px; padding: 9px 16px; cursor: pointer; border: 1px solid var(--border);
  background: var(--surface); color: var(--ink);
}
.btn:hover { border-color: var(--border-strong); text-decoration: none; }
/* Size variants MUST come after .btn so their smaller padding wins the cascade (same specificity). */
.btn-xs { padding: 3px 10px; font-size: 12px; border-radius: 7px; font-weight: 500; }
.btn-primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
/* keep the accent border on hover: the base .btn:hover swaps in the grey --border-strong,
   which on an accent button is a pale seam - and on the split button it washed out the
   divider between the two halves. hover/pressed use the brand accent-state tokens (a real
   colour step, not a brightness filter) so the interaction reads as intentional. */
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-primary:active { background: var(--accent-pressed); border-color: var(--accent-pressed); }
/* AI action button: accent-tinted (soft fill + accent text/border), distinct from both the plain
   grey .btn and the solid .btn-primary, so "Generate with AI" reads as a special capability. */
.btn-ai {
  background: var(--accent-soft); color: var(--accent); border-color: var(--accent);
}
.btn-ai:hover { border-color: var(--accent); background: var(--accent-soft); }
.btn-ai .btn-ai-ico { width: 16px; height: 16px; flex: none; }
.btn-block { width: 100%; justify-content: center; }
/* visual "not ready yet" hint - still clickable (so it can explain why on click) */
.btn.is-not-ready { opacity: .6; }
/* Busy/processing state: a spinner before the label, applied by islands during an async action
   (e.g. checkout save-and-continue -> save details -> re-run checkout -> provider redirect, which
   takes a moment). The button is disabled while busy; the spinner makes the wait legible. */
.btn.is-busy { cursor: progress; position: relative; }
.btn.is-busy::before {
  content: ""; width: 14px; height: 14px; flex: none; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: btn-spin .6s linear infinite; opacity: .9;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn.is-busy::before { animation-duration: 1.4s; } }
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
/* two fields side by side (e.g. state + country on the billing-details form) */
.field-row { display: flex; gap: 14px; }
.field-row .field { flex: 1; min-width: 0; }
/* Compact variant for a single short input + button (e.g. an API origin / URL): cap the field
   so it does not stretch across the whole panel, and keep the button beside it. */
/* Single short input + button on one row (e.g. an API origin / URL): cap the field width and align
   on the row. The .field's default margin-bottom (14px) would offset it against the button and read
   as a height mismatch - drop it here so input and button sit level. */
.field-row-compact { display: flex; gap: 10px; align-items: flex-end; margin-bottom: 14px; flex-wrap: wrap; }
.field-row-compact .field { flex: 0 1 420px; max-width: 420px; margin-bottom: 0; }
.field-row-compact .field-narrow { flex: 0 0 130px; max-width: 130px; }
/* The submit button aligns to the bottom of the (label + input) fields, matching the input height. */
.field-row-compact > .btn { flex: none; height: 38px; }
.field-row-compact .field input { height: 38px; box-sizing: border-box; }
.ext-pkg-actions { display: flex; gap: 10px; flex-wrap: wrap; }
/* segmented "You are: individual / organization" choice */
.seg-field { border: none; margin: 0 0 14px; padding: 0; }
.seg-label { font-size: 13px; font-weight: 600; color: var(--ink-soft); display: block; margin-bottom: 6px; }
.seg-choice { display: flex; gap: 8px; flex-wrap: wrap; }
.seg-opt {
  display: flex; align-items: center; gap: 7px; cursor: pointer;
  border: 1px solid var(--border-strong); border-radius: 8px; padding: 9px 14px;
  font-size: 13.5px; color: var(--ink); white-space: nowrap;
}
.seg-opt span { white-space: nowrap; }
.seg-opt:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.seg-opt input { margin: 0; }

/* searchable-select combobox (enhances <select data-searchable>) */
.ss-wrap { position: relative; }
.ss-native { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; } /* stays for form submit */
.ss-btn {
  width: 100%; text-align: left; font: inherit; padding: 9px 12px; border-radius: 8px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink); cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  /* Both MUST be explicit, for two separate reasons.
     line-height: `font: inherit` on a <button> resets it to `normal` (~1.2) while a sibling <input>
     inherits body's 1.5, so identical padding still left the button ~4px shorter.
     min-height: the button is a FLEX container, so its height comes from the tallest flex item's
     line box rather than a plain text line box like the input's - which left a ~1.5px residual even
     with line-height matched. Pinning the box to the same total the input computes (14px * 1.5 +
     9px padding * 2 + 1px border * 2 = 41px) makes the two identical at any zoom. */
  line-height: 1.5;
  min-height: 41px;
}
/* Truncate a long option rather than pushing the caret out of the control. */
.ss-btn-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A compact filter sits in a toolbar row and sizes to its content, like the native select it
   replaced - not to the container. Without this the wrapper stretches and the label truncates. */
.dt-filters .ss-wrap { flex: 0 0 auto; width: auto; }
/* min-height is reset here: the base .ss-btn pins 41px to match a .field input, but a toolbar
   filter sits next to .dt-search (7px padding -> 37px) and must match THAT instead. */
.dt-filters .ss-btn { width: auto; padding: 7px 10px; min-height: 37px; }
/* Real SVG chevron (same shape + rotation as the theme-preset trigger), not a font glyph. */
.ss-caret { flex: none; color: var(--ink-faint); transition: transform .15s ease, color .15s ease; }
.ss-btn:hover .ss-caret { color: var(--ink-soft); }
.ss-wrap.is-open .ss-caret { transform: rotate(180deg); color: var(--accent); }
.ss-wrap.is-open .ss-btn { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .ss-caret { transition: none; } }
.ss-pop {
  /* min-width (not left+right) so the panel is AT LEAST as wide as the trigger but can grow to fit
     the longest option. Pinning both edges made every option truncate on a compact control - a
     filter labelled "All types" cannot show "Announcement" in its own width. max-width keeps a long
     option from running off a narrow viewport. */
  position: absolute; left: 0; min-width: 100%; max-width: min(320px, 90vw); width: max-content;
  top: calc(100% + 4px); z-index: 50;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 6px; display: flex; flex-direction: column; gap: 6px;
}
.ss-pop[hidden] { display: none; }
.ss-search {
  width: 100%; font: inherit; font-size: 13px; padding: 7px 9px; border-radius: 7px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--ink);
}
.ss-search:focus { outline: none; border-color: var(--accent); }
.ss-list { max-height: 240px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.ss-opt {
  text-align: left; font: inherit; font-size: 13.5px; padding: 7px 9px; border-radius: 6px;
  border: none; background: none; color: var(--ink-soft); cursor: pointer;
}
.ss-opt:hover, .ss-opt:focus-visible { background: var(--surface-2); color: var(--ink); }
.ss-opt.is-sel { color: var(--accent); font-weight: 600; }
/* Short lists render without the search field, so the popover hugs the options. */
.ss-pop-nosearch { padding-top: 4px; }
/* Row = label + a trailing tick on the current choice (mirrors the theme-preset menu). */
.ss-opt { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ss-opt-label { min-width: 0; white-space: nowrap; }
.ss-opt-check { flex: none; font-size: 12px; line-height: 1; color: var(--accent); }

/* --- Admin console: keep each themed combobox at the height of the control it replaced -----------
   The base .ss-btn is 41px (sized for a full-width .field input). Every admin select is smaller
   than that and sits beside a specific neighbour, so each context resets min-height and restates
   its own metrics. Heights below are (font-size * 1.5) + vertical padding * 2 + border * 2. */

/* .admin-select - 13px/7px -> 35.5px, matching the .btn-sm it submits with. */
.ss-wrap.admin-select-wrap { width: 100%; }
.ss-wrap.admin-select-wrap .ss-btn {
  min-height: 0; padding: 7px 28px 7px 10px; border-radius: 8px;
  border-color: var(--border); font-size: 13px;
}

/* .filter-select - 13px/5px -> 31.5px. A compact list-filter pill. */
.ss-wrap.filter-select-wrap { width: auto; display: inline-flex; position: relative; }
.ss-wrap.filter-select-wrap .ss-btn {
  min-height: 0; width: auto; padding: 5px 26px 5px 10px; border-radius: 8px;
  border-color: var(--border); font-size: 13px;
}

/* .admin-filter-form select - an EXPLICIT 33px, shared with the search input beside it, so match
   the height directly rather than deriving it from padding. */
.admin-filter-form .ss-wrap { width: auto; display: inline-flex; position: relative; }
.admin-filter-form .ss-btn {
  height: 33px; min-height: 0; width: auto; padding: 0 26px 0 10px; border-radius: 8px;
  border-color: var(--border); font-size: 13px;
}
.admin-filter-form .ss-wrap.audit-action-select-wrap { min-width: 180px; }
.admin-filter-form .ss-wrap.audit-action-select-wrap .ss-btn { width: 100%; }

/* .billing-select - sits in a row with .billing-note and a .btn-sm (35.5px). */
.ss-wrap.billing-select-wrap { width: auto; min-width: 110px; display: inline-flex; position: relative; }
.ss-wrap.billing-select-wrap .ss-btn {
  min-height: 0; width: 100%; padding: 7px 28px 7px 10px; border-radius: 8px;
  border-color: var(--border); font-size: 13px;
}

/* Carets for the compact admin variants sit inside the reserved right padding. */
.ss-wrap.filter-select-wrap .ss-caret,
.ss-wrap.billing-select-wrap .ss-caret,
.admin-filter-form .ss-caret { position: absolute; right: 9px; top: 50%; margin-top: -4px; }
.ss-none { padding: 8px 9px; font-size: 13px; }
/* right-aligned table cell (e.g. invoice amount column) */
.data-table th.col-r, .data-table td.col-r { text-align: right; font-variant-numeric: tabular-nums; }
/* Timestamp columns: ONE width for every table, so the time columns line up down the page. 132px
   fits "17 Aug, 10:22" on a single line - the 125px some tables used wrapped it onto two. Applies
   to both spellings in use (.col-when and .col-time). */
.col-when, .col-time { width: 132px; }
.data-table td[data-label="When"],
.data-table td[data-label="Time"] { white-space: nowrap; }
/* .field sets display:flex, which overrides the [hidden] attribute's default
   display:none - so a hidden .field would still show. Restore it explicitly. */
.field[hidden] { display: none; }
/* a field label with an inline control (e.g. an "Auto" checkbox) sitting just after it */
.field-label-row { display: flex; align-items: center; gap: 14px; }
.check-inline { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 400; color: var(--ink-soft); cursor: pointer; white-space: nowrap; }
/* the label sits inside a .field, so the later-in-file `.field span` (13px/600) would win a
   specificity tie - bump specificity here so the inline control's own normal weight wins. */
.field .check-inline span,
.field-label-row .check-inline span { font-size: 12px; font-weight: 400; color: var(--ink-soft); }
.check-inline input { margin: 0; }
.field span { font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.field input,
.field select,
.field textarea {
  font: inherit; padding: 9px 12px; border-radius: 8px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
  width: 100%;
}
.field input:focus,
.field select:focus,
.field textarea:focus { outline: none; border-color: var(--accent); }
/* custom dropdown chevron (native arrow removed by appearance:none), theme-aware */
.field select {
  appearance: none; -webkit-appearance: none; padding-right: 34px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%237a8296' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
}
.field-hint { font-size: 11.5px; color: var(--ink-faint); font-weight: 400; }
/* Inline validation warning under a field (e.g. a click item with no selector). */
.field-warn { font-size: 11.5px; color: var(--warn); font-weight: 500; }
.field-warn[hidden] { display: none; }

/* a constrained form inside a wide panel (e.g. change password) */
.form-narrow { max-width: min(360px, 100%); margin-top: 10px; }
.form-narrow .field, .form-narrow .pw-toggle-wrap { width: 100%; }
/* Billing-details panel: form on the left, an explanatory aside on the right, so the panel is a
   balanced two-column block rather than a narrow form with a big empty right side. Collapses to a
   single column (aside below the form) on narrow screens. */
.billing-details-grid {
  display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 32px; align-items: start; margin-top: 10px;
}
.billing-details-grid .field, .billing-details-grid .pw-toggle-wrap { width: 100%; }
.billing-details-aside {
  border-left: 1px solid var(--border); padding-left: 28px;
  display: flex; flex-direction: column; gap: 12px;
}
.billing-details-aside h3 { font-size: 14px; font-weight: 700; color: var(--ink); margin: 0; }
.billing-details-aside p { font-size: 13px; line-height: 1.55; color: var(--ink-soft); margin: 0; }
.billing-details-aside strong { color: var(--ink); }
.billing-aside-note { color: var(--ink-faint) !important; font-size: 12px !important; margin-top: 4px !important; }
@media (max-width: 760px) {
  .billing-details-grid { grid-template-columns: 1fr; gap: 20px; }
  .billing-details-aside { border-left: none; padding-left: 0; border-top: 1px solid var(--border); padding-top: 18px; }
}

/* show/hide password toggle */
.pw-toggle-wrap { position: relative; display: flex; width: 100%; }
.pw-toggle-wrap input { flex: 1; min-width: 0; padding-right: 42px; }
.pw-toggle-btn {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px; display: grid; place-items: center;
  background: transparent; border: none; border-radius: 6px; cursor: pointer;
  color: var(--ink-faint); padding: 0;
}
.pw-toggle-btn:hover { color: var(--ink); background: var(--surface-2); }
.pw-toggle-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- auth ---- */
.auth-body { display: grid; place-items: center; min-height: 100vh; background: var(--bg); }
.auth-card-wrap { width: 100%; max-width: 440px; padding: 24px; }
/* Brand lockup on the auth pages (login/signup/forgot/reset/verify). Larger than the in-app
   sidebar mark so it reads as the page's identity, and a link back to the homepage. */
.auth-brand { display: inline-flex; align-items: center; gap: 8px; font-weight: 700; font-size: 22px;
  letter-spacing: -.02em; color: var(--ink); text-decoration: none; justify-content: center; margin: 0 auto 24px; }
.auth-brand:hover { color: var(--accent); text-decoration: none; }
.auth-brand:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
.auth-mark { width: 36px; height: 36px; flex: none; }
/* keep the row centred even though the link is now inline-flex */
.auth-card-wrap > .auth-brand { display: flex; }
.auth-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 26px 26px 22px; box-shadow: var(--shadow); }
.auth-card h1 { text-align: center; }
.auth-card-centered { text-align: center; }
.verify-mark {
  width: 52px; height: 52px; margin: 4px auto 14px; border-radius: 50%;
  display: grid; place-items: center; font-size: 26px; line-height: 1;
  color: #fff; background: var(--good);
}
.verify-mark-bad { background: var(--crit); }
.verify-mark-info { background: var(--accent); }
.auth-sent-hint { font-size: 12.5px; margin-top: 10px; }
.auth-card > .muted { text-align: center; margin: 0 0 18px; }
.auth-form { margin-top: 4px; }
/* inline checkbox + label row (e.g. "remember this device") */
/* Languages editor (Phase 5 - translate): base-language row, add-language row, and a list of translation
   rows (name + count + enable switch, with re-translate/review/remove actions). */
/* Shared select chrome, matching .field select (native <select> chrome misaligns the rows + looks
   unstyled next to the rest of the editor): border, radius, custom chevron, consistent height. */
.lang-add select {
  font: inherit; padding: 9px 34px 9px 12px; border-radius: 8px; height: 40px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%237a8296' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
}
.lang-add select:focus { outline: none; border-color: var(--accent); }
.lang-add select:disabled { opacity: .55; cursor: not-allowed; }

/* Base language: a quiet, set-once control beside the Languages heading. Low visual weight since it is
   rarely touched; the compact dropdown is revealed inline (same row height) when Change is clicked, so
   toggling it causes no layout shift. */
.lang-base { display: inline-flex; align-items: center; gap: 8px; min-height: 30px; flex-wrap: nowrap; white-space: nowrap; }
.lang-base-text { font-size: 12.5px; color: var(--ink-faint); }
.lang-base-text strong { color: var(--ink-soft); font-weight: 600; }
.lang-base-change {
  font: inherit; font-size: 12.5px; color: var(--accent); background: none; border: none; padding: 0; cursor: pointer;
}
.lang-base-change:hover { text-decoration: underline; }
/* Compact revealed select: sized to the line, NOT the 40px form select, so the header row height is
   identical open or closed (no jump). */
.lang-base-edit { display: inline-flex; }
.lang-base-edit select {
  font: inherit; font-size: 12.5px; height: 30px; padding: 0 30px 0 10px; border-radius: 7px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
  appearance: none; -webkit-appearance: none; cursor: pointer; max-width: 260px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%237a8296' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
}
.lang-base-edit select:focus { outline: none; border-color: var(--accent); }

/* Languages panel header: heading/description, then the quiet default-language line below it. Tighten
   the gap ABOVE the default-language line and give it room BELOW before the Add row. */
.lang-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px 24px; flex-wrap: wrap; margin-bottom: 18px; }
.lang-head-main { min-width: 0; }
.lang-head-main p { margin-bottom: 6px; }   /* was the source of the big gap above the default-language line */
.lang-head .lang-base { margin-top: 0; flex: none; }
.lang-add { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 18px; }
.lang-add select { max-width: 220px; }
.lang-add .btn { height: 40px; }
.lang-cost { font-size: 12.5px; }
/* Searchable "Add a language" combobox: a text input with a filtered popover list of languages. */
.lang-combo { position: relative; width: 260px; }
.lang-combo-input {
  font: inherit; font-size: 13.5px; width: 100%; height: 40px; padding: 0 12px; border-radius: 8px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
}
.lang-combo-input:focus { outline: none; border-color: var(--accent); }
.lang-combo-input:disabled { opacity: .55; cursor: not-allowed; }
.lang-combo-pop {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 30; max-height: 260px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,.14); padding: 4px;
}
.lang-combo-opt { padding: 7px 10px; border-radius: 6px; font-size: 13.5px; color: var(--ink); cursor: pointer; }
.lang-combo-opt:hover, .lang-combo-opt.is-active { background: var(--surface-2); }
.lang-combo-empty { padding: 8px 10px; font-size: 13px; }
.lang-list { display: flex; flex-direction: column; gap: 8px; }
.lang-empty { font-size: 13px; margin-top: 4px; }
.lang-row { border: 1px solid var(--border); border-radius: 10px; background: var(--surface); padding: 12px 14px; }
.lang-row-head { display: flex; align-items: center; gap: 10px; }
.lang-name { font-size: 14px; font-weight: 600; color: var(--ink); }
.lang-count { font-size: 12px; }
.lang-code { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.lang-switch { margin-left: auto; }
.lang-row-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.lang-row-actions .is-danger { color: var(--crit); }

/* Manual edit panel: a "source -> translation" form. Each group is a base field (Step / Item / Intro
   ...) with the source text shown muted above an editable input. */
.lang-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 12px; }
.lang-edit-group { display: flex; flex-direction: column; gap: 8px; }
.lang-edit-label { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
.lang-edit-field { display: flex; flex-direction: column; gap: 4px; }
/* Source (read-only reference): a muted, borderless tinted line so it reads as "the original", clearly
   subordinate to the editable Translation input below it. */
.lang-edit-src { font-size: 12.5px; line-height: 1.4; color: var(--ink-soft); padding: 5px 10px; background: var(--surface-2); border-radius: 6px; }
.lang-edit-input {
  font: inherit; font-size: 13.5px; padding: 8px 10px; border-radius: 7px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink); width: 100%; resize: vertical;
}
.lang-edit-input:focus { outline: none; border-color: var(--accent); }
/* Source preview may carry allowlist HTML (bold/italic/links) - render it, don't show raw tags. */
.lang-edit-src b, .lang-edit-src strong { font-weight: 650; color: var(--ink); }
.lang-edit-src a { color: var(--accent); text-decoration: underline; }
/* The translation body reuses the shared .rich-editor; make it compact for a per-string field and
   show the placeholder when empty (the full-height editor default is too tall here). */
.lang-rich { border-color: var(--border-strong); }
.lang-rich:focus-within { border-color: var(--accent); }
.lang-rich .rich-area { min-height: 44px; max-height: 200px; padding: 8px 10px; }
.lang-rich .rich-area:empty::before { content: attr(data-placeholder); color: var(--ink-faint); }
.lang-edit-actions { margin-top: 8px; }
/* Section header: a clear label with a hairline underneath, grouping the fields below it. The extra top
   margin (beyond the flex gap) is what visually separates one section from the previous one. */
.lang-edit-section {
  font-size: 12px; font-weight: 700; letter-spacing: .03em; color: var(--ink);
  margin-top: 22px; padding-bottom: 7px; border-bottom: 1px solid var(--border);
}
/* the first section sits right under the panel divider, so it needs no extra top gap */
.lang-edit > .lang-edit-section:first-child { margin-top: 0; }
/* Compact grid for the short chrome labels (Next / Back / Done ...): auto-fit ~3 per row, one column
   on narrow panels. Each cell keeps the source-over-input stack. */
.lang-edit-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.lang-edit-grid .lang-edit-group { gap: 6px; }
.lang-edit-grid .lang-edit-src { font-size: 12px; padding: 4px 8px; }
.lang-edit-grid .lang-edit-input { font-size: 13px; padding: 6px 9px; }

/* Email preferences: a constrained column of bordered toggle cards. */
.pref-list { display: flex; flex-direction: column; gap: 10px; max-width: 560px; margin-top: 4px; }
.pref-card {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; background: var(--surface);
}
.pref-card.is-locked { background: var(--surface-2); }
.pref-text { flex: 1; min-width: 0; }
.pref-title { font-size: 14px; font-weight: 600; color: var(--ink); }
.pref-desc { margin: 3px 0 0; font-size: 12.5px; line-height: 1.5; }

/* Assistant admin: the master switch is the parent (a prominent accent-tinted card); the capability
   toggles are a subordinate group, dimmed + non-interactive when the master is off. */
.assistant-master {
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  background: var(--accent-soft); margin-bottom: 6px;
}
.assistant-master .pref-title { font-size: 15px; }
.assistant-caps { margin-top: 14px; padding-top: 4px; }
.assistant-caps-label { font-size: 12px; color: var(--ink-soft); font-weight: 600; margin: 0 0 8px 2px; }
/* Off state: dim the whole group + block interaction, so it reads as "turn the master on first". */
.assistant-caps.is-off { opacity: .5; pointer-events: none; filter: grayscale(0.3); }
/* Save row: clear separation from the toggle cards above. */
.assistant-save { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border); }
/* Toggle switch (a styled checkbox). */
.switch { position: relative; flex: none; display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  width: 42px; height: 24px; border-radius: 999px; background: var(--border-strong);
  transition: background .15s ease; display: inline-block; position: relative;
}
.switch-track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.2); transition: transform .15s ease;
}
.switch input:checked + .switch-track { background: var(--accent); }
.switch input:checked + .switch-track::after { transform: translateX(18px); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Admin conversation review: a readable transcript - user turns plain, assistant turns tinted, each with
   its token cost + any citations/proposed action the answer carried. */
.chat-review { display: flex; flex-direction: column; gap: 12px; }
.chat-review-msg { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; }
.chat-review-user { background: var(--surface); }
.chat-review-assistant { background: var(--surface-2); border-color: color-mix(in srgb, var(--accent) 22%, var(--border)); }
.chat-review-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 6px; }
.chat-review-role { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
.chat-review-head .pill { flex: 0 0 auto; }
.chat-review-tok { margin-left: auto; font-size: 11.5px; font-variant-numeric: tabular-nums; color: var(--ink-faint); white-space: nowrap; }
.chat-review-body { font-size: 13.5px; line-height: 1.55; color: var(--ink); }
.chat-review-cites { margin-top: 8px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 12px; }
.chat-review-cite { font-size: 12px; }
.chat-review-action { margin-top: 8px; font-size: 12px; }
.chat-review-action code { font-size: 11.5px; }

/* ---- Customer help assistant chat (/assistant) ------------------------------------------------ */
.assistant { display: flex; flex-direction: column; max-width: 820px; }
/* The scrolling message thread. Caps its height so the input stays visible; the page itself does not
   scroll for the chat. */
.assistant-thread {
  display: flex; flex-direction: column; gap: 12px; min-height: 240px;
  max-height: calc(100vh - 340px); overflow-y: auto; padding: 4px 2px 8px;
}
/* Empty state (the example prompts) sits at the TOP of the thread, not floated to the middle. */
.assistant-empty { margin: 0; }
.assistant-examples { list-style: none; padding: 0; margin: 8px 0 0; display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.assistant-example {
  font: inherit; font-size: 13.5px; text-align: left; cursor: pointer; color: var(--ink);
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 7px 14px;
}
.assistant-example:hover { border-color: var(--accent); color: var(--accent); }

/* A message bubble. User right-aligned + accent; assistant left-aligned + surface. */
.chat-msg { max-width: 88%; display: flex; flex-direction: column; gap: 6px; }
.chat-msg-user { align-self: flex-end; align-items: flex-end; }
.chat-msg-assistant { align-self: flex-start; align-items: flex-start; }
.chat-msg-body {
  font-size: 14px; line-height: 1.55; padding: 10px 14px; border-radius: 14px; white-space: pre-wrap;
  word-break: break-word;
}
.chat-msg-user .chat-msg-body { background: var(--accent); color: var(--accent-ink); border-bottom-right-radius: 4px; }
.chat-msg-assistant .chat-msg-body { background: var(--surface-2); color: var(--ink); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
.chat-msg-error .chat-msg-body { background: color-mix(in srgb, var(--crit) 10%, var(--surface)); border-color: color-mix(in srgb, var(--crit) 30%, var(--border)); color: var(--ink); }
/* Typing indicator: three bouncing dots while the answer generates. */
.chat-msg-body.is-typing { padding: 12px 16px; }
.chat-typing { display: inline-flex; align-items: center; gap: 4px; }
.chat-typing-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-faint); animation: chat-bounce 1.2s infinite ease-in-out; }
.chat-typing-dot:nth-child(2) { animation-delay: .18s; }
.chat-typing-dot:nth-child(3) { animation-delay: .36s; }
@keyframes chat-bounce { 0%, 60%, 100% { transform: translateY(0); opacity: .5; } 30% { transform: translateY(-4px); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .chat-typing-dot { animation: none; opacity: .7; } }
/* Full-thread loader while restoring a conversation. */
.chat-loader { display: flex; align-items: center; justify-content: center; margin: auto 0; padding: 24px 0; }
.chat-loader-spin { width: 34px; height: 34px; border-radius: 50%; border: 3px solid var(--border); border-top-color: var(--accent); animation: chat-spin .7s linear infinite; }
@keyframes chat-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .chat-loader-spin { animation-duration: 1.6s; } }

/* Citations under an assistant answer. */
.chat-cites { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-left: 4px; }
.chat-cites-label { font-size: 12px; }
.chat-cite {
  font-size: 12px; padding: 3px 9px; border-radius: 999px; background: var(--surface);
  border: 1px solid var(--border); color: var(--accent); text-decoration: none;
}
.chat-cite:hover { border-color: var(--accent); text-decoration: none; }

/* A proposed-action confirm button (never auto-run). */
.chat-action { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding-left: 4px; margin-top: 2px; }
.chat-action-hint { font-size: 11.5px; }

/* "Was this helpful?" thumbs on an answer. */
.chat-feedback { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding-left: 4px; margin-top: 6px; }
.chat-feedback-label, .chat-feedback-thanks { font-size: 11.5px; }
.chat-feedback-btn {
  width: 26px; height: 24px; display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1; background: none; border: 1px solid var(--border); border-radius: 6px;
  cursor: pointer; opacity: .7; transition: opacity .12s, border-color .12s, background .12s;
}
.chat-feedback-btn:hover { opacity: 1; background: var(--surface-2); }
.chat-feedback-btn.is-on { opacity: 1; border-color: var(--accent); background: var(--accent-soft); }
.chat-feedback-note { display: flex; align-items: flex-end; gap: 6px; width: 100%; margin-top: 4px; }
.chat-feedback-input {
  flex: 1 1 auto; font: inherit; font-size: 12.5px; padding: 6px 8px; border: 1px solid var(--border);
  border-radius: 7px; background: var(--surface); color: var(--ink); resize: vertical; min-height: 34px;
}

/* The input row: a growing textarea + Send. */
.assistant-input { display: flex; gap: 10px; align-items: flex-end; margin-top: 12px; }
.assistant-input textarea {
  flex: 1; box-sizing: border-box; font: inherit; font-size: 14px; line-height: 1.45;
  min-height: 42px; height: 42px; padding: 10px 14px; border-radius: 10px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink); resize: none; overflow-y: auto;
}
.assistant-input textarea:focus { outline: none; border-color: var(--accent); }
.assistant-input .btn { flex: none; height: 42px; }
.assistant-disclaimer { font-size: 11.5px; margin-top: 8px; }

/* Assistant QA harness (staff /preview/assistant-qa). */
.qa-legend { font-size: 12px; margin-bottom: 12px; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.qa-table td { vertical-align: top; }
.qa-ctx { font-size: 12.5px; font-weight: 600; }
.qa-url { display: inline-block; margin-top: 3px; font-size: 11px; color: var(--ink-faint); }
.qa-q { font-size: 13.5px; }
.qa-answer { font-size: 13px; line-height: 1.5; }
.qa-actions { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.qa-btn { font-size: 11.5px; color: var(--accent); }
.qa-btn code { font-size: 10.5px; color: var(--ink-faint); margin-left: 4px; }
.qa-cites { font-size: 11px; margin-top: 5px; }
.qa-tok { font-size: 11px; margin-top: 3px; font-variant-numeric: tabular-nums; }
.qa-review { display: flex; flex-direction: column; gap: 6px; min-width: 150px; }
.qa-mark-btns { display: flex; gap: 5px; }
.qa-mark.is-on[data-qa-mark="good"] { background: var(--good, #16a34a); color: #fff; border-color: transparent; }
.qa-mark.is-on[data-qa-mark="bad"] { background: var(--crit); color: #fff; border-color: transparent; }
.qa-note { font: inherit; font-size: 12px; padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface); color: var(--ink); resize: vertical; width: 100%; }
.qa-note:focus { outline: none; border-color: var(--accent); }

/* ---- Floating launcher + slide-up panel (on every dashboard page) ----------------------------- */
.assistant-launcher { position: fixed; right: 20px; bottom: 20px; z-index: 60; }
/* The bubble button. Shows a chat glyph; swaps to an x when open. */
.assistant-fab {
  width: 54px; height: 54px; border-radius: 50%; border: none; cursor: pointer;
  background: var(--accent); color: var(--accent-ink); box-shadow: 0 6px 20px rgba(0,0,0,.22);
  display: flex; align-items: center; justify-content: center; transition: transform .12s ease, background .12s ease;
}
.assistant-fab:hover { background: var(--accent-hover, var(--accent)); transform: translateY(-1px); }
.assistant-fab-ico svg { width: 24px; height: 24px; display: block; }
.assistant-fab-close { display: none; font-size: 26px; line-height: 1; }
.assistant-launcher.is-open .assistant-fab-ico { display: none; }
.assistant-launcher.is-open .assistant-fab-close { display: block; }

/* The panel that slides up above the bubble. */
.assistant-panel {
  position: absolute; right: 0; bottom: 66px; width: 380px; max-width: calc(100vw - 40px);
  height: 560px; max-height: calc(100vh - 120px); display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  box-shadow: 0 12px 40px rgba(0,0,0,.24); overflow: hidden;
  animation: assistant-pop .14s ease;
}
@keyframes assistant-pop { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .assistant-panel { animation: none; } }
.assistant-panel-head { position: relative; display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.assistant-panel-heading { flex: 1 1 auto; min-width: 0; }
.assistant-panel-title { font-size: 15px; font-weight: 700; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* One line - never wrap the subtitle to a second row (cramped in the panel header). */
.assistant-panel-sub { font-size: 11.5px; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.assistant-panel-actions { display: flex; align-items: center; gap: 4px; flex: none; }
.assistant-hist-btn, .assistant-new-btn {
  display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px;
  border-radius: 8px; background: none; border: none; color: var(--ink-faint); cursor: pointer;
}
.assistant-hist-btn:hover, .assistant-new-btn:hover { background: var(--surface-2); color: var(--ink); }
.assistant-hist-btn svg, .assistant-new-btn svg { width: 17px; height: 17px; }
.assistant-panel-x { font: inherit; font-size: 22px; line-height: 1; background: none; border: none; color: var(--ink-faint); cursor: pointer; padding: 0 4px; }
.assistant-panel-x:hover { color: var(--ink); }
/* Recent-conversations dropdown, overlaying below the header. */
.assistant-history {
  position: absolute; top: calc(100% - 1px); right: 12px; z-index: 5; width: 260px; max-width: calc(100% - 24px);
  max-height: 320px; overflow-y: auto; background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.2); padding: 6px;
}
.assistant-history-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: 6px 8px 4px; }
.assistant-history-row { display: flex; align-items: center; gap: 2px; border-radius: 7px; }
.assistant-history-row:hover { background: var(--surface-2); }
.assistant-history-item {
  flex: 1 1 auto; min-width: 0; display: block; text-align: left; font: inherit; font-size: 13px; color: var(--ink);
  background: none; border: none; border-radius: 7px; padding: 8px 10px; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.assistant-history-del {
  flex: 0 0 auto; width: 26px; height: 26px; margin-right: 4px; display: inline-flex; align-items: center;
  justify-content: center; font-size: 17px; line-height: 1; color: var(--ink-faint); background: none;
  border: none; border-radius: 6px; cursor: pointer; opacity: 0; transition: opacity .12s, color .12s, background .12s;
}
.assistant-history-row:hover .assistant-history-del,
.assistant-history-del:focus-visible { opacity: 1; }
.assistant-history-del:hover { color: var(--crit); background: color-mix(in srgb, var(--crit) 12%, transparent); }
.assistant-history-empty { font-size: 13px; padding: 8px 10px; }
/* Full /assistant page: history/new-chat controls in the page header. */
.page-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.assistant-page-actions { display: flex; align-items: center; gap: 8px; flex: none; }
.assistant-hist-wrap { position: relative; }
.assistant-hist-wrap .assistant-history { top: calc(100% + 6px); right: 0; }

/* The chat surface inside the panel: fill the panel, thread scrolls, input pinned at the bottom. */
.assistant-in-panel { max-width: none; flex: 1; min-height: 0; padding: 12px 14px 14px; }
.assistant-in-panel .assistant-thread { flex: 1; min-height: 0; max-height: none; }
.assistant-in-panel .assistant-input { margin-top: 10px; }
.assistant-in-panel .assistant-disclaimer { display: none; }
.assistant-in-panel .chat-msg { max-width: 92%; }

/* On small screens the panel goes near-fullscreen. */
@media (max-width: 480px) {
  .assistant-panel { width: calc(100vw - 24px); height: calc(100vh - 100px); right: -8px; }
}
/* Locked (below the required plan): the switch is dimmed + non-interactive, with an upgrade nudge. */
.switch.is-disabled { cursor: not-allowed; opacity: .55; }
.pref-lock-pill {
  display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--accent); background: var(--accent-soft); border-radius: 5px; padding: 1px 6px; margin-left: 6px; vertical-align: middle;
}
.pref-upgrade { color: var(--accent); font-weight: 600; }

.check-row { display: flex; align-items: center; gap: 8px; font-size: 13px; margin: 2px 0 12px; cursor: pointer; }
.check-row input { width: auto; margin: 0; }
/* In the appearance editor, a STANDALONE checkbox option's label should read like the
   other field labels (.field span): same weight + muted ink. Scoped to [data-appearance]
   so app-wide checkbox rows (auth, modals) keep their normal-weight body text. */
[data-appearance] .check-row:not(.check-row-tight):not(.check-row-field) span { font-weight: 600; color: var(--ink-soft); }
/* A .check-row-tight is a sub-control INSIDE a .field (e.g. the radius "Auto" toggle), so
   the .field span rule above bolds its label like a heading. Override it back to a simple,
   subtle inline label so it reads as a secondary control, not a field title. */
[data-appearance] .check-row-tight span { font-weight: 500; color: var(--ink-soft); }
/* A .check-row-field is a standalone checkbox that now sits UNDER a field label (a bold
   category span). Its own option text should read as normal-weight body, so there is clear
   hierarchy: bold category label on top, plain option below. Beats `.field span` on
   specificity so the field-label rule does not bold it. */
[data-appearance] .field .check-row-field span { font-weight: 400; color: var(--ink-soft); }
.check-row-field { margin: 0; }
/* brand-blue, appropriately-sized native checkboxes/radios app-wide */
.check-row input[type="checkbox"], .check-row input[type="radio"] {
  width: 16px; height: 16px; flex: none; accent-color: var(--accent); cursor: pointer;
}
.auth-alt { text-align: center; font-size: 13px; margin: 18px 0 0; }
.auth-alt > a { display: inline-block; }
/* unverified-email nudge banner (dashboard, non-blocking) */
.verify-banner {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; margin-bottom: 18px; border-radius: 10px;
  border: 1px solid var(--border); border-left: 3px solid var(--warn);
  background: var(--surface); font-size: 13.5px;
}
.verify-banner-ico { font-size: 16px; line-height: 1; }
.verify-banner-text { flex: 1 1 240px; color: var(--ink-soft); }
.verify-banner-form { margin: 0; }
/* After a successful resend the banner turns green (from the warn amber) with a check, so the
   "email sent - check your inbox" confirmation is unmistakable and stays put (no page reload). */
.verify-banner-sent { border-left-color: var(--good, #16a34a); background: color-mix(in srgb, var(--good, #16a34a) 7%, var(--surface)); }
.verify-banner-sent .verify-banner-ico { color: var(--good, #16a34a); }

/* MAU-limit banner (Pricing v2 graceful block). Same shape as the verify banner; the left rule and
   icon go critical when over the cap, warn when merely near it. */
.mau-banner {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; margin-bottom: 18px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface); font-size: 13.5px;
}
.mau-banner.is-over { border-left: 3px solid var(--crit); }
.mau-banner.is-near { border-left: 3px solid var(--warn); }
.mau-banner-ico { font-size: 16px; line-height: 1; }
.mau-banner.is-over .mau-banner-ico { color: var(--crit); }
.mau-banner.is-near .mau-banner-ico { color: var(--warn); }
.mau-banner-text { flex: 1 1 260px; color: var(--ink-soft); }

/* Locked (read-only) workspace banner: an over-cap excess of the owner's plan. Persistent + crit
   accent - flows have stopped serving and writes are blocked until they upgrade or reduce count. */
.locked-ws-banner {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; margin-bottom: 18px; border-radius: 10px; font-size: 13.5px;
  border: 1px solid color-mix(in srgb, var(--crit) 40%, var(--border)); border-left: 3px solid var(--crit);
  background: color-mix(in srgb, var(--crit) 9%, var(--surface));
}
.locked-ws-ico { font-size: 16px; line-height: 1; color: var(--crit); }
.locked-ws-text { flex: 1 1 260px; color: var(--ink-soft); }
.locked-ws-text strong { color: var(--ink); }

/* Persistent trial-ending banner (<=3 days), shown across the dashboard. Red, demands attention. */
.trial-global-banner {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; margin-bottom: 18px; border-radius: 10px; font-size: 13.5px;
  border: 1px solid color-mix(in srgb, var(--crit) 40%, var(--border)); border-left: 3px solid var(--crit);
  background: color-mix(in srgb, var(--crit) 9%, var(--surface));
}
.trial-global-ico { font-size: 16px; line-height: 1; color: var(--crit); }
.trial-global-text { flex: 1 1 260px; color: var(--ink-soft); }
.trial-global-text strong { color: var(--ink); }
/* The Keep-<plan> CTA is now a form wrapping the button (in-place checkout); keep it a tidy flex
   child so it sits like the old inline button did. */
.trial-global-cta { flex: none; display: flex; margin: 0; }

/* a submit button that reads as a plain text link (e.g. "Back to sign in") */
.linklike {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--accent); text-decoration: underline;
}
.linklike:hover { color: var(--accent-strong, var(--accent)); }
.auth-recovery { margin-top: 18px; font-size: 13px; }
.auth-recovery > summary { cursor: pointer; text-align: center; }
.auth-alt > .muted { margin: 0; font-size: 12.5px; }
/* divider only when the muted line follows another item (login page) */
.auth-alt > * + .muted {
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--border);
}
.auth-flash {
  padding: 10px 14px; border-radius: 9px; font-size: 13.5px; margin-bottom: 14px;
  border: 1px solid var(--border); background: var(--surface);
  border-left: 3px solid var(--accent);
}
.auth-flash.success {
  border-color: color-mix(in srgb, var(--good) 45%, var(--border));
  border-left-color: var(--good);
  background: color-mix(in srgb, var(--good) 12%, var(--surface));
  color: color-mix(in srgb, var(--good) 65%, var(--ink));
}
.auth-flash.info {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
}
.auth-flash.warning {
  border-color: color-mix(in srgb, var(--warn) 45%, var(--border));
  border-left-color: var(--warn);
  background: color-mix(in srgb, var(--warn) 12%, var(--surface));
}
.auth-flash.error,
.auth-flash.critical {
  border-color: color-mix(in srgb, var(--crit) 45%, var(--border));
  border-left-color: var(--crit);
  background: color-mix(in srgb, var(--crit) 12%, var(--surface));
  color: color-mix(in srgb, var(--crit) 70%, var(--ink));
}
/* inline message rendered by the auth-ajax island (mirrors .auth-flash) */
.auth-ajax-msg {
  padding: 11px 14px; border-radius: 9px; font-size: 13.5px; margin-bottom: 14px;
  border: 1px solid var(--border); background: var(--surface);
  border-left: 3px solid var(--accent);
  display: inline-flex; align-items: center; gap: 9px;
  /* size to the message text (so short confirmations stay on one line) rather
     than stretching/wrapping to the narrow form width; cap it on small screens */
  width: max-content; max-width: 100%;
}
/* an icon glyph before the text, set per level via ::before content */
.auth-ajax-msg::before { font-weight: 700; font-size: 14px; line-height: 1; }
.auth-ajax-msg.success {
  border-color: color-mix(in srgb, var(--good) 45%, var(--border));
  border-left-color: var(--good);
  background: color-mix(in srgb, var(--good) 12%, var(--surface));
  color: color-mix(in srgb, var(--good) 65%, var(--ink));
}
.auth-ajax-msg.success::before { content: "\2713"; color: var(--good); }
.auth-ajax-msg.info {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
}
.auth-ajax-msg.info::before { content: "\2139"; color: var(--accent); }
.auth-ajax-msg.error,
.auth-ajax-msg.critical {
  border-color: color-mix(in srgb, var(--crit) 45%, var(--border));
  border-left-color: var(--crit);
  background: color-mix(in srgb, var(--crit) 12%, var(--surface));
  color: color-mix(in srgb, var(--crit) 70%, var(--ink));
}
.auth-ajax-msg.error::before,
.auth-ajax-msg.critical::before { content: "\26A0"; color: var(--crit); }

/* ---- errors ---- */
.error-body { display: grid; place-items: center; min-height: 100vh; text-align: center; }
.error-card { max-width: 380px; padding: 30px; }
.error-code { font-size: 54px; font-weight: 800; color: var(--accent); letter-spacing: -.03em; }
.error-card h1 { margin: 6px 0 8px; }
.error-card .btn { margin-top: 16px; }
.error-actions { margin-top: 16px; display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.error-actions .btn { margin-top: 0; }

/* ---- toast root ---- */
#onbixo-toast-root {
  /* Center horizontally within the CONTENT area (right of the sidebar), and sit
     just below the sticky top nav. Centering is done with left/right insets +
     margin (NOT a transform on the root, which would create a containing block /
     stacking context that fights the toast's own translateY animation). */
  position: fixed; top: 66px; bottom: auto;
  left: var(--sidebar-w); right: 0; margin: 0 auto;
  width: max-content; max-width: calc(100vw - var(--sidebar-w) - 32px);
  display: flex; flex-direction: column; align-items: center;
  /* Toasts are how EVERY error/confirmation surfaces, so they must sit above all overlays -
     including the media dialog (.media-dlg-back, z-index 4000) and any future modal. Keep this
     the highest stacking layer in the app. */
  gap: 10px; z-index: 5000; pointer-events: none;
}
/* when the sidebar is the collapsed mini rail, offset by the rail width (toast root is a sibling
   of .app-shell, so target it with the general-sibling combinator) */
.app-shell.sidebar-collapsed ~ #onbixo-toast-root {
  left: var(--sidebar-mini); max-width: calc(100vw - var(--sidebar-mini) - 32px);
}
/* auth pages have no sidebar or top nav: center on the full viewport, near the top */
.auth-body #onbixo-toast-root { top: 18px; left: 0; max-width: calc(100vw - 32px); }
/* mobile: the sidebar is an off-canvas drawer, so center on the full viewport */
@media (max-width: 820px) {
  #onbixo-toast-root { top: 60px; left: 0; max-width: calc(100vw - 24px); }
}
.onbixo-toast {
  pointer-events: auto; min-width: 240px; max-width: 380px; padding: 13px 16px;
  background: var(--surface); color: var(--ink); border: 1px solid var(--border);
  border-left: 3px solid var(--accent); border-radius: 9px; box-shadow: var(--shadow);
  font-size: 13.5px; opacity: 0; transform: translateY(-10px); transition: opacity .18s, transform .18s;
}
.onbixo-toast.show { opacity: 1; transform: none; }
.onbixo-toast.success {
  border-left-color: var(--good);
  background: color-mix(in srgb, var(--good) 12%, var(--surface));
}
.onbixo-toast.info {
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
}
.onbixo-toast.warning {
  border-left-color: var(--warn);
  background: color-mix(in srgb, var(--warn) 12%, var(--surface));
}
.onbixo-toast.error,
.onbixo-toast.critical {
  border-left-color: var(--crit);
  background: color-mix(in srgb, var(--crit) 12%, var(--surface));
}

/* ---- public marketing pages ---- */
.public-body { display: flex; flex-direction: column; min-height: 100vh; }
.public-nav {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 28px; max-width: 1080px; width: 100%; margin: 0 auto;
}
.public-brand { display: flex; align-items: center; gap: 7px; font-weight: 700; color: var(--ink); }
.public-brand:hover { text-decoration: none; }
.public-links { display: flex; align-items: center; gap: 16px; }
/* plain nav links only - do not restyle the button anchor (.btn keeps its own color) */
.public-links a:not(.btn) { color: var(--ink-soft); font-weight: 550; }
.btn-sm { padding: 7px 13px; font-size: 13px; }
/* keep Publish and Unpublish the same width so the actions column does not shift */
.flow-toggle-btn { min-width: 92px; justify-content: center; }
.hero { padding: 70px 28px 40px; text-align: center; }
.hero-inner { max-width: 720px; margin: 0 auto; }
.hero-title { font-size: clamp(30px, 5vw, 46px); line-height: 1.08; letter-spacing: -.03em; text-wrap: balance; }
.hero-sub { font-size: 18px; color: var(--ink-soft); max-width: 560px; margin: 14px auto 26px; text-wrap: pretty; }
.hero-cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.home-features {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px;
  max-width: 940px; margin: 30px auto 60px; padding: 0 28px; width: 100%;
}
@media (max-width: 760px) {
.home-features { grid-template-columns: 1fr; }
}
.feature {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px 22px; box-shadow: var(--shadow);
}
.feature h3 { margin: 0 0 6px; font-size: 16px; }
.public-foot {
  margin-top: auto; display: flex; gap: 12px; align-items: center;
  padding: 20px 28px; border-top: 1px solid var(--border);
  max-width: 1080px; width: 100%; margin-left: auto; margin-right: auto;
  font-size: 13px;
}

/* ---- pills / badges ---- */
.pill {
  font-size: 11px; font-weight: 600; color: var(--ink-soft);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 2px 9px;
}
.pill-good { color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent); border-color: transparent; }
.pill-warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 15%, transparent); border-color: transparent; }
.pill-crit { color: var(--crit); background: color-mix(in srgb, var(--crit) 14%, transparent); border-color: transparent; }
.pill-muted { color: var(--ink-faint); background: var(--surface-2); border-color: var(--border); }
.pill-bad { color: var(--crit); background: color-mix(in srgb, var(--crit) 15%, transparent); border-color: transparent; }
/* A pill sitting inline after some text (e.g. "Featured" beside a blog title) needs a little gap.
   A class, not an inline style: style attributes are refused by the strict style-src 'self' CSP. */
.pill-inline { margin-left: 6px; }
/* Admin status-page editor: the live snapshot list + the per-component override fields. */
.admin-status-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.admin-status-row { display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 10px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); }
.admin-status-row b { display: inline; margin-right: 8px; }
.admin-status-field > span { display: block; margin-bottom: 4px; }
.admin-status-field em { font-style: normal; margin-left: 6px; font-size: 12.5px; }
.btn-danger { color: var(--crit); border-color: color-mix(in srgb, var(--crit) 40%, var(--border)); }
.btn-danger:hover { background: color-mix(in srgb, var(--crit) 10%, transparent); }

/* read-only key/value list (e.g. workspace details) */
.detail-list { display: flex; flex-direction: column; gap: 2px; margin: 0; }
.detail-row { display: flex; gap: 16px; align-items: baseline; padding: 8px 0; border-bottom: 1px solid var(--border); }
.detail-row:last-child { border-bottom: none; }
.detail-row dt { flex: none; width: 120px; color: var(--ink-faint); font-size: 13px; }
.detail-row dd { margin: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.detail-row dd code { word-break: break-all; }

/* ---- profile dropdown ---- */
.profile-menu { position: relative; }
.profile-dropdown {
  position: absolute; right: 0; top: calc(100% + 8px); width: 200px; z-index: 40;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 6px; overflow: hidden;
}
.profile-head { padding: 8px 10px 10px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.profile-name { font-weight: 650; font-size: 13px; }
.profile-sub { font-size: 11.5px; margin-top: 2px; word-break: break-all; }
.profile-item {
  display: block; width: 100%; text-align: left; padding: 8px 10px; border-radius: 7px;
  color: var(--ink-soft); font: inherit; font-size: 13.5px; background: none; border: none; cursor: pointer;
}
.profile-item:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.profile-logout { margin: 0; }
/* only the Sign out button is red; other profile buttons (account switch) inherit the normal
   item colour. */
.profile-logout .profile-item-btn { color: var(--crit); }
/* account switcher inside the profile menu */
.profile-section-label { font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); padding: 8px 10px 2px; }
.profile-switch { margin: 0; }
.profile-item-current, .profile-switch .profile-item-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.profile-item-current { color: var(--ink); font-weight: 600; background: var(--surface-2); }
.profile-item-current .pill, .profile-switch .pill { flex: none; }
.profile-item-add { color: var(--accent); font-weight: 500; }
.profile-divider { height: 1px; background: var(--border); margin: 6px 0; }

/* ---- sessions list ---- */
.session-list { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
.session-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 4px; border-top: 1px solid var(--border);
}
.session-row:first-child { border-top: none; }
.session-title { font-weight: 600; display: flex; align-items: center; gap: 8px; }
.session-meta { font-size: 12.5px; margin-top: 2px; }

/* ---- TOTP enroll ---- */
.totp-steps { margin: 4px 0 16px; padding-left: 18px; color: var(--ink-soft); }
.totp-steps li { margin: 5px 0; }
.totp-enroll { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.totp-qr {
  width: 196px; height: 196px; display: grid; place-items: center; text-align: center;
  background: #fff; border: 1px solid var(--border-strong); border-radius: 10px;
  padding: 12px; font-size: 13px; color: #16202e;
}
/* the generated QR SVG fills the padded box and never overflows it */
.totp-qr svg { display: block; width: 100%; height: auto; max-width: 100%; }
.totp-otpauth-link { font-weight: 600; }
.totp-key-hint { font-size: 12px; margin: 8px 0 0; }
/* 2FA confirm: divider + separation from the QR block, and a compact code field */
.totp-confirm-form {
  max-width: 220px; margin-top: 24px; padding-top: 20px;
  border-top: 1px solid var(--border);
}
.code-input {
  width: 150px; text-align: center; font-family: var(--mono);
  font-size: 20px; letter-spacing: 6px; padding-left: 12px;
}
.totp-qr-note { font-size: 11px; color: var(--ink-faint); margin-top: 8px; }
.totp-key-label { font-size: 12px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 6px; }
.totp-secret { font-family: var(--mono); font-size: 15px; letter-spacing: 1px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; display: inline-block; }
.recovery-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; max-width: 360px; }
.recovery-code { font-family: var(--mono); background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 8px 10px; text-align: center; letter-spacing: 1px; }

/* ---- install page: snippet block + API keys ---- */
/* inline notice/callout (info variant) - reuses the verify-banner idiom */
.notice {
  padding: 11px 14px; margin: 0 0 14px; border-radius: 10px; font-size: 13.5px;
  border: 1px solid var(--border); border-left: 3px solid var(--accent);
  background: var(--surface); color: var(--ink-soft);
}
.notice strong { color: var(--ink); }
.notice-info { border-left-color: var(--accent); }
.notice-warn { border-left-color: var(--warn); background: color-mix(in srgb, var(--warn) 8%, var(--surface)); }
/* a snippet shown for reference before the runtime is live: dimmed, not copyable */
.code-block.is-preview { opacity: .6; }
.code-block.is-preview .code-copy { pointer-events: none; }
.code-block { position: relative; margin: 4px 0 2px; }
/* copy button reveals on hover/focus so it does not sit over the code; it stays
   visible while keyboard-focused (reachable via Tab) and after a successful copy */
.code-block .code-copy {
  position: absolute; top: 8px; right: 8px; z-index: 1;
  opacity: 0; transition: opacity .12s ease;
}
.code-block:hover .code-copy,
.code-block:focus-within .code-copy,
.code-block .code-copy:focus-visible,
.code-block .code-copy.is-ok { opacity: 1; }
@media (hover: none) {
.code-block .code-copy { opacity: 1; }
} /* touch: always show */
.code-copy.is-ok { color: var(--good); border-color: color-mix(in srgb, var(--good) 40%, var(--border)); }
.code-pre {
  margin: 0; padding: 14px 16px; padding-right: 84px; overflow-x: auto;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
}
/* Admin SEO: default OG share-card preview */
.og-preview { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.og-preview-img { display: block; width: 100%; max-width: 420px; aspect-ratio: 1200 / 630;
  object-fit: cover; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2); }
/* Social-channel fields in two columns; collapses to one on narrow screens */
.seo-social-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 20px; }
@media (max-width: 640px) { .seo-social-grid { grid-template-columns: 1fr; } }

/* Ready-to-post social copy: per channel -> per profile field -> read-only variants, each copyable. */
.social-copy-intro { margin-top: 0; }
.social-copy-list { display: flex; flex-direction: column; gap: 28px; }
.social-copy-channel { display: flex; flex-direction: column; gap: 14px; }
.social-copy-head { display: flex; align-items: baseline; gap: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
.social-copy-head h3 { margin: 0; font-size: 1rem; }
.social-copy-field { display: flex; flex-direction: column; gap: 8px; }
.social-copy-fieldhead { display: flex; align-items: baseline; gap: 10px; }
.social-copy-fieldname { font-size: 0.85rem; font-weight: 600; }
.social-copy-lim { font-size: 0.78em; }
.social-copy-card { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; background: var(--surface-2, var(--surface)); }
.social-copy-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 7px; }
.social-copy-tag { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.social-copy-count { font-size: 0.78rem; font-variant-numeric: tabular-nums; margin-left: auto; }
.social-copy-count.is-over { color: var(--danger, #dc2626); font-weight: 600; }
.social-copy-btn.is-copied { color: var(--success, #16a34a); border-color: var(--success, #16a34a); }
.social-copy-text { margin: 0; line-height: 1.5; white-space: pre-wrap; }

/* ---- Admin blog editor ------------------------------------------------- */
.blog-editor-grid { display: grid; grid-template-columns: minmax(0, 1fr) 306px; gap: 18px; align-items: start; }
@media (max-width: 900px) { .blog-editor-grid { grid-template-columns: 1fr; } }
.blog-editor-main, .blog-editor-side { display: flex; flex-direction: column; gap: 16px; }
.blog-editor-actions { margin-top: 18px; }

/* Quill chrome: match the app's surface + border tokens (snow theme draws its own toolbar). */
.blog-editor .ql-toolbar.ql-snow,
.blog-editor .ql-container.ql-snow { border-color: var(--border); }
.blog-editor .ql-toolbar.ql-snow { border-radius: 8px 8px 0 0; background: var(--surface-2); }
.blog-editor .ql-container.ql-snow { border-radius: 0 0 8px 8px; }
.blog-editor .ql-editor.ql-blank::before { color: var(--ink-faint); font-style: normal; left: 20px; }
.blog-editor .ql-snow .ql-stroke { stroke: var(--ink-soft); }
.blog-editor .ql-snow .ql-fill { fill: var(--ink-soft); }
.blog-editor .ql-snow .ql-picker { color: var(--ink-soft); }

/* The editor content area is styled to MATCH the real public blog post (.blog-prose), so what you
   write looks like what ships. The display serif (Fraunces) is self-hosted, same file the public
   site uses; --bcat is the category accent (falls back to the app accent). */
@font-face {
  font-family: "Fraunces";
  src: url("/assets/vendor/fonts/fraunces-var.woff2") format("woff2");
  font-weight: 300 900; font-style: normal; font-display: swap;
}
/* Category accent (--bcat): matches the live post, which tints blockquote + list markers by the
   post's category. Default is the app accent; the bcat-<slug> class (set from the category field)
   overrides it, mirroring public.css's per-category palette. */
.blog-editor { --bcat: var(--accent); }
.blog-editor.bcat-onboarding  { --bcat: #6d5efc; }
.blog-editor.bcat-activation  { --bcat: #12a5a5; }
.blog-editor.bcat-pricing     { --bcat: #f0653e; }
.blog-editor.bcat-engineering { --bcat: #1f6feb; }
.blog-editor.bcat-product     { --bcat: #db2777; }
.blog-editor .ql-editor {
  --pub-display: "Fraunces", Georgia, "Times New Roman", serif;
  min-height: 340px; padding: 22px 24px;
  font-size: 17.5px; line-height: 1.72; color: var(--ink-soft);
}
.blog-editor .ql-editor > *:first-child { margin-top: 0; }
.blog-editor .ql-snow .ql-editor h2 { font-family: var(--pub-display); font-weight: 400; font-size: 27px;
  letter-spacing: -.015em; line-height: 1.2; color: var(--ink); margin: 44px 0 14px; text-wrap: balance; }
.blog-editor .ql-snow .ql-editor h3 { font-size: 20px; font-weight: 700; letter-spacing: -.01em; color: var(--ink); margin: 32px 0 10px; }
.blog-editor .ql-snow .ql-editor h4 { font-size: 16.5px; font-weight: 700; color: var(--ink); margin: 26px 0 8px; }
.blog-editor .ql-snow .ql-editor p { margin: 0 0 20px; }
/* Quill keeps empty paragraphs (just a <br>) in its DOM as blank lines. On save the sanitizer
   drops them, so the published post uses margin-collapse for tight spacing. In the editor they
   remain, so shrink them to one blank line (no extra 20px block margin) - matching how the saved
   post will look, not a bloated double gap. :has() targets a <p> whose only child is a <br>. */
.blog-editor .ql-snow .ql-editor p:has(> br:only-child) { margin: 0; min-height: 1em; line-height: 1; }
.blog-editor .ql-snow .ql-editor a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
.blog-editor .ql-snow .ql-editor strong { color: var(--ink); font-weight: 700; }
.blog-editor .ql-snow .ql-editor ul, .blog-editor .ql-snow .ql-editor ol { margin: 0 0 22px; padding-left: 24px; }
.blog-editor .ql-snow .ql-editor li { margin: 7px 0; padding-left: 4px; }
.blog-editor .ql-snow .ql-editor ul li::marker { color: var(--bcat); }
.blog-editor .ql-snow .ql-editor ol li::marker { color: var(--ink-faint); font-weight: 700; }
/* Quill v2 renders ALL lists as <ol> with li[data-list] and draws the marker on a nested
   <span class="ql-ui">::before (list-style:none), so the native ::marker rules above don't apply in
   the editor. Colour that marker span to the category accent (bullets) / muted ink (numbers) so the
   editor matches the published post. */
.blog-editor .ql-snow .ql-editor li[data-list="bullet"] > .ql-ui::before { color: var(--bcat); }
.blog-editor .ql-snow .ql-editor li[data-list="ordered"] > .ql-ui::before { color: var(--ink-faint); font-weight: 700; }
/* Higher specificity (extra .ql-snow) so these beat Quill's own .ql-snow .ql-editor blockquote
   rule, which is equal-specificity and loads after app.css in the head. */
.blog-editor .ql-snow .ql-editor blockquote { margin: 28px 0; padding: 4px 0 4px 22px; border-left: 3px solid var(--bcat);
  font-family: var(--pub-display); font-weight: 400; font-size: 22px; line-height: 1.45; color: var(--ink); font-style: normal; }
.blog-editor .ql-editor blockquote p { margin: 0; }
.blog-editor .ql-snow .ql-editor hr { border: 0; border-top: 1px solid var(--border); margin: 40px 0; }
.blog-editor .ql-snow .ql-editor img { display: block; width: 100%; max-width: 100%; height: auto; border-radius: 14px; border: 1px solid var(--border); }
.blog-editor .ql-snow .ql-editor code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px; color: var(--ink); }
.blog-editor .ql-snow .ql-editor pre, .blog-editor .ql-editor .ql-syntax { margin: 26px 0; background: #0e1626; border-radius: 14px;
  padding: 18px; overflow-x: auto; border: 1px solid rgba(255,255,255,.06); color: #d7deea; font-size: 13.5px; line-height: 1.6; }
/* Quill v2 code blocks are <div class="ql-code-block-container"> (light grey by default); style them
   like the published dark <pre> so the editor matches. Each line is a nested .ql-code-block div. */
.blog-editor .ql-snow .ql-editor .ql-code-block-container { margin: 26px 0; background: #0e1626; border-radius: 14px;
  padding: 14px 18px; overflow-x: auto; border: 1px solid rgba(255,255,255,.06); }
.blog-editor .ql-snow .ql-editor .ql-code-block { color: #d7deea; font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13.5px; line-height: 1.6; background: none; padding: 0; }
/* Tables (mirror the live post's .blog-prose table styling). */
.blog-editor .ql-editor table { width: 100%; border-collapse: collapse; margin: 26px 0; font-size: 14.5px; display: block; overflow-x: auto; }
.blog-editor .ql-editor thead th { text-align: left; font-size: 12px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  color: var(--ink-faint); background: var(--surface-2); padding: 11px 14px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.blog-editor .ql-editor tbody td { padding: 11px 14px; border-bottom: 1px solid var(--border); color: var(--ink-soft); vertical-align: top; }
.blog-editor .ql-editor tbody tr:hover { background: color-mix(in srgb, var(--bcat) 4%, var(--surface)); }
.blog-editor .ql-editor td strong { color: var(--ink); }
.blog-editor .ql-editor figure { margin: 30px 0; }
.blog-editor .ql-editor figure img { margin: 0; }
.blog-editor .ql-editor figcaption { font-size: 13px; color: var(--ink-faint); text-align: center; margin-top: 10px; line-height: 1.5; }
/* The figure blot's caption is an editable field; show a themed placeholder when empty, and a
   subtle focus outline so the author knows it's typeable. The figure itself is contenteditable=false. */
.blog-editor .ql-editor figure figcaption:empty::before { content: attr(data-placeholder); color: var(--ink-faint); opacity: .6; }
.blog-editor .ql-editor figure figcaption:focus { outline: none; color: var(--ink-soft); }
.blog-editor .ql-editor figure:hover figcaption:empty::before { opacity: .85; }
/* Editor tables (quill-better-table): match the LIVE post's editorial table look - horizontal
   hairlines only (border-bottom), no side borders, no cell background. The plugin keeps its own
   layout logic; these override its default full-grid appearance. First row reads as the header. */
/* Spacing lives on the wrapper (the plugin's block element); the table itself has no margin. */
.blog-editor .ql-editor .quill-better-table-wrapper { margin: 26px 0; }
/* Force full width + let columns spread evenly (the plugin sets fixed <col width> + table width). */
.blog-editor .ql-editor table.quill-better-table { border-collapse: collapse; margin: 0; font-size: 14.5px;
  width: 100% !important; table-layout: auto; }
.blog-editor .ql-editor table.quill-better-table col { width: auto !important; }
.blog-editor .ql-editor table.quill-better-table td {
  border: 0; border-bottom: 1px solid var(--border); padding: 11px 14px;
  color: var(--ink-soft); vertical-align: top; background: none; }
.blog-editor .ql-editor table.quill-better-table tr:first-child td {
  font-size: 12px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  color: var(--ink-faint); background: var(--surface-2); white-space: nowrap; }
.blog-editor .ql-editor table.quill-better-table td .qlbt-cell-line { margin: 0; }
.blog-editor .ql-editor table.quill-better-table td strong { color: var(--ink); }
/* "Insert table" toolbar button (the plugin has no icon; render a small labelled button). */
.blog-editor .ql-toolbar .onbixo-ql-table-btn { width: auto; padding: 0 8px; font-size: 12px; font-weight: 600;
  color: var(--ink-soft); border: 1px solid var(--border); border-radius: 6px; height: 24px; line-height: 22px; }
.blog-editor .ql-toolbar .onbixo-ql-table-btn:hover { color: var(--accent); border-color: var(--accent); }
/* "Source" (HTML view) toolbar button - same pill as the table button; .ql-active when in source view. */
.blog-editor .ql-toolbar .onbixo-ql-source-btn { width: auto; padding: 0 8px; font-size: 12px; font-weight: 600;
  color: var(--ink-soft); border: 1px solid var(--border); border-radius: 6px; height: 24px; line-height: 22px; }
.blog-editor .ql-toolbar .onbixo-ql-source-btn:hover { color: var(--accent); border-color: var(--accent); }
.blog-editor .ql-toolbar .onbixo-ql-source-btn.ql-active { color: #fff; background: var(--accent); border-color: var(--accent); }
/* Raw HTML source textarea (shown when the Source toggle is on; sits where the editor was). */
.blog-editor .blog-source-hint { margin: 10px 0 6px; font-size: 12px; line-height: 1.5; }
.blog-editor .blog-source-area { display: block; width: 100%; min-height: 420px; box-sizing: border-box;
  border: 1px solid var(--border); border-top: 0; border-radius: 0 0 8px 8px; padding: 16px 18px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 13px; line-height: 1.6;
  color: var(--ink); background: var(--surface-2); resize: vertical; tab-size: 2; }
.blog-editor .blog-source-area:focus { outline: none; border-color: var(--accent); }

/* Cover image preview */
.blog-cover-preview { border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
  background: var(--surface-2); margin-bottom: 10px; }
.blog-cover-preview img { display: block; width: 100%; aspect-ratio: 1200 / 630; object-fit: cover; }
.blog-cover-empty { display: grid; place-items: center; aspect-ratio: 1200 / 630; font-size: 12.5px; }

/* Media library modal */
.blog-media-modal { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; }
.blog-media-modal[hidden] { display: none; }
.blog-media-backdrop { position: absolute; inset: 0; background: rgba(10, 14, 22, 0.55); }
.blog-media-dialog { position: relative; width: min(820px, 92vw); max-height: 84vh; display: flex;
  flex-direction: column; background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; box-shadow: var(--shadow); overflow: hidden; }
.blog-media-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--border); }
.blog-media-head h2 { margin: 0; font-size: 15px; }
.blog-media-head-actions { display: flex; gap: 8px; align-items: center; }
.blog-media-status { padding: 8px 16px; border-bottom: 1px solid var(--border); font-size: 12.5px; }
.blog-media-grid { padding: 16px; overflow-y: auto; display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.blog-media-item { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface-2); }
.blog-media-item img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.blog-media-item-bar { display: flex; gap: 6px; padding: 8px; justify-content: space-between; }
.code-pre code { font-family: var(--mono); font-size: 12.5px; line-height: 1.6; color: var(--ink); white-space: pre; }
/* syntax tokens for the embed snippet (self-hosted highlighter, theme-aware). On
   the light code surface the tokens are deep + fully saturated so they read crisp,
   not faded; dark theme uses brighter variants. */
.tok-comment { color: var(--ink-faint); font-style: italic; }
.tok-tag  { color: #b00034; }
.tok-attr { color: #9a4a00; }
.tok-str  { color: #067a4e; }
.tok-kw   { color: #6321c7; font-weight: 600; }
.tok-punc { color: var(--ink-soft); }
.tok-obj  { color: #0a5ad6; }   /* object / namespace (Onbixo) */
.tok-fn   { color: #9a4a00; }   /* method / function name (show) */
/* When a snippet sits inside a <label class="field"> (e.g. the step editor's manual-advance
   hint), the greedy `.field span` rule (0,1,1) outranks the base `.tok-*` colors (0,1,0) and
   flattens every token to the muted label grey - the code showed uncolored there while the same
   snippet outside a .field (targeting) stayed colored. Scoping the tokens to .code-snippet makes
   them (0,2,0), which wins; also restore the mono weight/size the label rule forces to 13px/600.
   The dark-theme rules above are already (0,2,0) via their :root prefix, so only light needs this. */
.code-snippet .tok-comment { color: var(--ink-faint); }
.code-snippet .tok-tag  { color: #b00034; }
.code-snippet .tok-attr { color: #9a4a00; }
.code-snippet .tok-str  { color: #067a4e; }
.code-snippet .tok-kw   { color: #6321c7; }
.code-snippet .tok-punc { color: var(--ink-soft); }
.code-snippet .tok-obj  { color: #0a5ad6; }
.code-snippet .tok-fn   { color: #9a4a00; }
.code-snippet span { font-size: 12px; font-weight: 400; }
/* Dark-theme equivalents at matching scope. The plain :root[data-theme=dark] .tok-* rules are
   (0,2,0) - same as .code-snippet .tok-* above - so without these the light-scoped rules would
   win by source order in dark mode. These are (0,3,0), so dark wins in dark, light wins in light. */
:root[data-theme="dark"] .code-snippet .tok-tag  { color: #ff8aa8; }
:root[data-theme="dark"] .code-snippet .tok-attr { color: #f0c674; }
:root[data-theme="dark"] .code-snippet .tok-str  { color: #4be8b0; }
:root[data-theme="dark"] .code-snippet .tok-kw   { color: #c9a7ff; }
:root[data-theme="dark"] .code-snippet .tok-obj  { color: #79b8ff; }
:root[data-theme="dark"] .code-snippet .tok-fn   { color: #f0c674; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .code-snippet .tok-tag  { color: #ff8aa8; }
  :root:not([data-theme="light"]) .code-snippet .tok-attr { color: #f0c674; }
  :root:not([data-theme="light"]) .code-snippet .tok-str  { color: #4be8b0; }
  :root:not([data-theme="light"]) .code-snippet .tok-kw   { color: #c9a7ff; }
  :root:not([data-theme="light"]) .code-snippet .tok-obj  { color: #79b8ff; }
  :root:not([data-theme="light"]) .code-snippet .tok-fn   { color: #f0c674; }
}
:root[data-theme="dark"] .tok-tag  { color: #ff8aa8; }
:root[data-theme="dark"] .tok-attr { color: #f0c674; }
:root[data-theme="dark"] .tok-str  { color: #4be8b0; }
:root[data-theme="dark"] .tok-kw   { color: #c9a7ff; }
:root[data-theme="dark"] .tok-obj  { color: #79b8ff; }
:root[data-theme="dark"] .tok-fn   { color: #f0c674; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .tok-tag  { color: #ff8aa8; }
  :root:not([data-theme="light"]) .tok-attr { color: #f0c674; }
  :root:not([data-theme="light"]) .tok-str  { color: #4be8b0; }
  :root:not([data-theme="light"]) .tok-kw   { color: #c9a7ff; }
  :root:not([data-theme="light"]) .tok-obj  { color: #79b8ff; }
  :root:not([data-theme="light"]) .tok-fn   { color: #f0c674; }
}
.key-token {
  display: inline-block; font-family: var(--mono); font-size: 12px; color: var(--ink-soft);
  background: color-mix(in srgb, var(--surface-2) 70%, transparent); border: 1px solid var(--border);
  border-radius: 6px; padding: 2px 8px; letter-spacing: .02em;
}
.key-meta { font-size: 11.5px; margin-top: 3px; }
/* collapsible "what is this key for" explainer */
.key-explainer { margin: 4px 0 16px; font-size: 13px; }
.key-explainer > summary {
  cursor: pointer; color: var(--accent); font-weight: 600; width: fit-content;
  list-style: none;
}
.key-explainer > summary::-webkit-details-marker { display: none; }
.key-explainer > summary::before { content: "\24D8"; margin-right: 6px; }
.key-explainer[open] > summary { margin-bottom: 8px; }
.key-explainer-body { color: var(--ink-soft); max-width: 72ch; line-height: 1.55; }
.key-explainer-body p { margin: 0 0 8px; }
.key-explainer-body ul { margin: 0 0 8px; padding-left: 20px; }
.key-explainer-body li { margin-bottom: 4px; }
.key-explainer-body strong { color: var(--ink); }
.panel-head-actions { display: flex; align-items: center; gap: 12px; }
.origin-chip {
  display: inline-block; font-family: var(--mono); font-size: 11.5px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 1px 7px; margin: 1px 4px 1px 0; color: var(--ink-soft);
}
.field-hint { display: block; font-size: 11.5px; color: var(--ink-faint); margin-top: 4px; line-height: 1.45; }
/* display:block overrides the [hidden] attribute's display:none - restore it so a hidden
   hint (e.g. the manual-trigger hint when the trigger is not 'manual') actually hides. */
.field-hint[hidden] { display: none; }
/* manual-trigger hint: a label line + the show() call in a code box. align-items:flex-start
   so the code box shrinks to its content instead of stretching to the container width. */
.trigger-manual-hint { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
/* code box with a JS language badge in the corner. */
.code-snippet-wrap { position: relative; width: 100%; max-width: 400px; }
.code-lang {
  position: absolute; top: 0; right: 0; z-index: 1;
  font-family: var(--font); font-size: 9.5px; font-weight: 700; letter-spacing: .05em;
  color: var(--ink-faint); background: var(--surface); border: 1px solid var(--border);
  border-top-right-radius: 7px; border-bottom-left-radius: 6px; padding: 2px 6px;
  pointer-events: none;
}
.code-snippet {
  font-family: var(--mono); font-size: 12px; color: var(--ink); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 7px; padding: 8px 34px 8px 11px;
  /* fill the wrapper (which caps at 400px); scroll horizontally inside rather than stretch. */
  display: block; width: 100%; overflow-x: auto; white-space: nowrap;
  user-select: all; box-sizing: border-box;
}
/* Hover copy button. It occupies the same corner as the JS badge, so the badge fades out
   as the button fades in - two things stacked in one corner would overlap. The button stays
   in the DOM (opacity, not display) so it can be reached by keyboard: :focus-within keeps it
   visible for tab users, who never generate a hover. */
.code-snippet-copy {
  position: absolute; top: 0; right: 0; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  font-family: var(--font); font-size: 10px; font-weight: 700; letter-spacing: .04em;
  color: var(--ink-soft); background: var(--surface); border: 1px solid var(--border);
  border-top-right-radius: 7px; border-bottom-left-radius: 6px; padding: 3px 7px;
  cursor: pointer; opacity: 0; transition: opacity .12s ease, color .12s ease;
  /* invisible must also mean inert: at opacity 0 the button still sat over the snippet's
     top-right corner and swallowed clicks meant for the code itself. */
  pointer-events: none;
}
/* :focus-VISIBLE, not :focus-within. A mouse click focuses the button, and that focus
   outlives the pointer - with :focus-within the button stayed pinned at opacity 1 long
   after the cursor left. :focus-visible only matches keyboard focus, which is the case
   that actually needs it. */
.code-snippet-wrap:hover .code-snippet-copy,
.code-snippet-copy:focus-visible { opacity: 1; pointer-events: auto; }
.code-snippet-wrap:hover .code-lang,
.code-snippet-wrap:has(.code-snippet-copy:focus-visible) .code-lang { opacity: 0; }
.code-lang { transition: opacity .12s ease; }
.code-snippet-copy:hover { color: var(--ink); }
.code-snippet-copy.is-copied { color: var(--ok, #067a4e); }
.code-snippet-copy svg { width: 11px; height: 11px; display: block; }
@media (prefers-reduced-motion: reduce) {
.code-snippet-copy, .code-lang { transition: none; }
}
.key-create-form-dialog textarea {
  width: 100%; font-family: var(--mono); font-size: 12.5px; resize: vertical;
}
.data-table tr.is-revoked td { opacity: .55; }
.data-table tr.is-revoked .key-token { text-decoration: line-through; }

/* ---- command palette (Cmd+K) ---- */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 200; display: none;
  background: rgba(16,32,46,.4); backdrop-filter: blur(2px);
  align-items: flex-start; justify-content: center; padding-top: 14vh;
}
.cmdk-overlay.show { display: flex; }
.cmdk { width: 100%; max-width: 520px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); overflow: hidden; }
.cmdk-input { width: 100%; font: inherit; font-size: 15px; padding: 15px 18px; border: none; border-bottom: 1px solid var(--border); background: transparent; color: var(--ink); }
.cmdk-input:focus { outline: none; }
.cmdk-list { max-height: 320px; overflow: auto; padding: 6px; }
.cmdk-item { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; border-radius: 8px; color: var(--ink); }
.cmdk-item:hover, .cmdk-item.active { background: var(--accent-soft); color: var(--accent); text-decoration: none; }
.cmdk-kind { font-size: 11px; color: var(--ink-faint); }
.cmdk-item.active .cmdk-kind { color: var(--accent); }
.cmdk-empty { padding: 18px 12px; text-align: center; font-size: 13px; color: var(--ink-faint); }

/* ---- verify-email banner ---- */
.verify-banner {
  display: flex; align-items: center; gap: 10px; padding: 11px 16px; margin-bottom: 18px;
  background: color-mix(in srgb, var(--warn) 12%, var(--surface)); border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--border));
  border-radius: var(--radius); font-size: 13.5px;
}

/* ---- skeleton loaders + empty state ---- */
.skeleton { position: relative; overflow: hidden; background: var(--surface-2); border-radius: 6px; }
.skeleton::after {
  content: ""; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--ink) 6%, transparent), transparent);
  animation: sk 1.3s infinite;
}
@keyframes sk { 100% { transform: translateX(100%); } }
.empty-state { text-align: center; padding: 40px 20px; color: var(--ink-soft); }
.empty-state h3 { color: var(--ink); margin: 0 0 6px; }
.empty-title { font-weight: 650; font-size: 15px; color: var(--ink); margin-bottom: 6px; }

/* ---- page head with an action on the right ---- */
/* Split header: heading/description column on the left, actions pinned top-right on the SAME row. The
   text column shrinks and wraps its paragraph (min-width:0 lets a flex child shrink below its content
   width); the actions never shrink or wrap (nowrap), so a long description can't push them onto their
   own line. On narrow screens it stacks (wrap). */
.page-head-split { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: nowrap; }
.page-head-split > :first-child { min-width: 0; flex: 1 1 auto; }
.page-head-split > .page-head-actions { flex: 0 0 auto; margin-left: auto; }
@media (max-width: 900px) { .page-head-split { flex-wrap: wrap; } }

/* Flow name + its Type . Status badge on one line (badge to the RIGHT of the name), aligned
   on the text baseline. The meta is a <p> whose default margins are cleared here so it sits
   inline next to the heading rather than stacking under it. */
.flow-title-row { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.flow-title-row h1 { margin: 0; }
.flow-title-row .flow-meta { margin: 0; }

/* ---- data table ---- */
.table-panel { padding: 0; overflow: hidden; }
.table-scroll { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.data-table th, .data-table td { text-align: left; padding: 14px 18px; vertical-align: middle; }
/* Header: no heavy solid fill - a lighter tinted strip with a single divider rule below, so it reads
   as a quiet label row rather than a boxed-in bar. */
.data-table thead th {
  font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-faint);
  background: color-mix(in srgb, var(--surface-2) 55%, transparent);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.data-table thead th:first-child { border-top-left-radius: inherit; }
.data-table thead th:last-child { border-top-right-radius: inherit; }
/* Body rows: hairline dividers that stop short of the edge feel lighter than full-bleed borders. */
.data-table tbody td { border-bottom: 1px solid color-mix(in srgb, var(--border) 65%, transparent); }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr { transition: background .12s; }
.data-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
/* First cell (usually the name) carries a touch more weight for scannability. */
.data-table tbody td.cell-name, .data-table tbody td:first-child { color: var(--ink); }

/* Audiences table: uses the shared .dt / .data-table island (same header + sort + search as Flows).
   Only the cell CONTENT is custom: an icon + name link, a conditions chip, and a usage number. */
.aud-dt .col-c { text-align: center; }
.aud-name-link { display: inline-flex; align-items: center; gap: 11px; color: inherit; text-decoration: none; min-width: 0; }
.aud-name-link:hover { text-decoration: none; }
.aud-name-link .aud-avatar {
  flex: none; width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.aud-name-link .aud-avatar svg { width: 18px; height: 18px; }
.aud-name-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.aud-name { font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.aud-name-link:hover .aud-name { color: var(--accent); }
.aud-sub { font-size: 12.5px; color: var(--ink-faint); text-transform: capitalize; white-space: nowrap; }
.aud-used { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); }
.aud-used.is-zero { color: var(--ink-faint); font-weight: 500; }

/* Mail-templates table: give the columns sane, stable widths so Subject does not hog the row and
   the others stop jumping. Desktop only (>600px); below that the shared card layout takes over.
   Name/Slug/Updated are content-sized; Subject takes the remaining space and truncates; Actions
   hug the right. */
@media (min-width: 601px) {
  .mail-tpl-table { table-layout: fixed; }
  .mail-tpl-table th:nth-child(1), .mail-tpl-table td:nth-child(1) { width: 40%; }   /* Name (+ slug/desc) */
  .mail-tpl-table th.tpl-updated,  .mail-tpl-table td.tpl-updated  { width: 110px; white-space: nowrap; }
  .mail-tpl-table th.tpl-actions,  .mail-tpl-table td.tpl-actions  { width: 220px; white-space: nowrap; }
  /* Subject (2nd col) takes the remaining space and truncates instead of pushing the row wider. */
  .mail-tpl-table td[data-label="Subject"] .err-msg { max-width: 100%; }
}
/* The slug shown as a small mono tag under the template name (moved out of its own column). */
.tpl-slug { font-family: var(--mono, monospace); font-size: 11.5px; }
/* Compact + horizontally-scrollable variant for wide metric tables (per-step
   detail): tighter type, no header/cell wrapping, and a first column pinned to the
   left so the step label stays visible while the numeric columns scroll. */
.data-table-compact { font-size: 13px; }
.data-table-compact th, .data-table-compact td { padding: 9px 14px; white-space: nowrap; }
.data-table-compact thead th { font-size: 11px; letter-spacing: .02em; }
.data-table-compact td { font-variant-numeric: tabular-nums; }
.data-table-compact th:first-child,
.data-table-compact td:first-child {
  position: sticky; left: 0; z-index: 1; background: var(--surface); font-weight: 600;
  box-shadow: 1px 0 0 var(--border);
}
.data-table-compact thead th:first-child { background: var(--surface-2); z-index: 2; }
.data-table-compact tbody tr:hover td:first-child { background: color-mix(in srgb, var(--accent) 5%, var(--surface)); }
.data-table .cell-name { font-weight: 600; color: var(--ink); }
/* right-aligned numeric columns + a subtle totals footer row */
.data-table th.num, .data-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table tfoot th { font-size: 12px; letter-spacing: .02em; text-transform: uppercase;
  color: var(--ink-faint); background: var(--surface-2); border-top: 2px solid var(--border); border-bottom: none; }
.data-table tfoot th.num { font-variant-numeric: tabular-nums; }

/* "When it's seen" heatmap: weekday rows x 24 hour columns. Cells are small fixed squares
   with a bucketed accent tint (heat-0..4). No inline styles (CSP), no chart JS. */
.heatmap { border-collapse: separate; border-spacing: 3px; font-variant-numeric: tabular-nums; }
.heatmap th { font-size: 11px; font-weight: 600; color: var(--ink-faint); padding: 0; }
.heatmap .heatmap-day { text-align: right; padding-right: 8px; white-space: nowrap; }
.heatmap .heatmap-hour { text-align: center; font-size: 10px; min-width: 20px; padding-bottom: 4px; }
.heatmap .heatmap-corner { width: 34px; }
.heatmap .heatmap-total { text-align: right; padding-left: 10px; font-size: 12px; color: var(--ink); font-weight: 600; }
.heatmap td.heatmap-total { font-variant-numeric: tabular-nums; }
.heatmap-cell { width: 20px; height: 20px; border-radius: 4px; padding: 0; }
/* five buckets from empty (surface-2) to full accent; keeps text-free cells legible in both themes */
.heat-0 { background: var(--surface-2); }
.heat-1 { background: color-mix(in srgb, var(--accent) 22%, var(--surface-2)); }
.heat-2 { background: color-mix(in srgb, var(--accent) 45%, var(--surface-2)); }
.heat-3 { background: color-mix(in srgb, var(--accent) 70%, var(--surface-2)); }
.heat-4 { background: var(--accent); }
.heatmap-legend { display: flex; align-items: center; gap: 6px; margin-top: 12px; font-size: 11px; color: var(--ink-faint); }
.heat-swatch { width: 16px; height: 16px; border-radius: 4px; display: inline-block; }
/* Keep the actions cell a REAL table-cell so it vertically centers within a tall row and never spills
   outside its column. Making the <td> itself display:flex removed it from table layout: it no longer
   stretched to the row height (set by the tallest cell), so its content floated to the top-right.
   Instead: the cell is a normal middle-aligned, right-aligned cell; its direct children (forms/links/
   buttons) sit inline-middle; and the gap between multiple actions comes from a small left margin on
   each item after the first. Works for one button or several, server-rendered or JS-injected. */
.data-table td.col-actions { text-align: right; vertical-align: middle; white-space: nowrap; }
.data-table td.col-actions > *,
.data-table td.col-actions .inline-form { display: inline-flex; align-items: center; vertical-align: middle; margin: 0; }
.data-table td.col-actions > * + * { margin-left: 8px; }

/* row "..." overflow menu for secondary actions */
.row-menu { position: relative; display: inline-block; }
.row-menu-btn { padding: 6px 10px; font-size: 16px; line-height: 1; }
.row-menu-list {
  /* JS positions it fixed from the button's viewport rect (escapes table overflow
     clipping); this z-index sits above the table panel + footer */
  z-index: 500; min-width: 150px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 6px; text-align: left;
}
.row-menu-list form { margin: 0; }
.row-menu-item {
  display: block; width: 100%; text-align: left; box-sizing: border-box;
  background: none; border: none; border-radius: 6px; cursor: pointer;
  font: inherit; font-size: 13.5px; color: var(--ink); padding: 8px 10px; text-decoration: none;
}
.row-menu-item:hover { background: var(--surface-2); text-decoration: none; }
.row-menu-danger { color: var(--crit); }

/* ---- split button (primary action + attached menu) ----
   Used by the flow editor for Publish/Republish with Unpublish in the menu: one decision,
   one control, instead of three buttons wrapping onto a second row on narrow screens. */
.split-btn { position: relative; display: inline-flex; }
.split-btn .inline-form { margin: 0; display: inline-flex; }
/* The two halves are separate elements (the main one is a form submit), so square the
   facing corners and hide the seam to read as a single control. Only when the toggle is
   actually attached (a published flow) - otherwise the lone Publish button keeps its full
   rounded corners instead of a squared-off right edge with nothing beside it. */
.split-btn-has-toggle .split-btn-main { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.split-btn-toggle {
  position: relative; /* anchors the ::before divider */
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  margin-left: -1px; padding-left: 8px; padding-right: 8px;
  display: inline-flex; align-items: center; justify-content: center;
}
/* hairline divider between the halves, in the button's own text colour */
.split-btn-toggle::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 1px; background: currentColor; opacity: .28;
}
/* caret, drawn in CSS so the control carries no icon dependency */
.split-btn-toggle::after {
  content: ""; width: 0; height: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 4.5px solid currentColor;
}
.split-btn-list {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 500; min-width: 150px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 6px; text-align: left;
}
.split-btn-list form { margin: 0; }
.split-btn-item {
  display: block; width: 100%; text-align: left; box-sizing: border-box;
  background: none; border: none; border-radius: 6px; cursor: pointer;
  font: inherit; font-size: 13.5px; color: var(--ink); padding: 8px 10px; text-decoration: none;
}
.split-btn-item:hover { background: var(--surface-2); text-decoration: none; }
/* .split-btn-toggle sets display:inline-flex, which outranks the UA's [hidden]{display:none}
   - without this the caret stayed on screen after unpublishing. Same for the list. */
.split-btn-toggle[hidden], .split-btn-list[hidden] { display: none; }
/* Both halves are .btn-primary, so :hover brightens only the one under the cursor -
   leaving the two at different blues with a seam between them. Brighten both together
   whenever either half is hovered, so the split reads as a single control. */
.split-btn:hover .split-btn-main,
.split-btn:hover .split-btn-toggle { filter: brightness(1.05); }

/* ---- data-table toolbar / sort / pagination / columns ---- */
.dt-toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.dt-toolbar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; flex: none; }
.dt-filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* subtle reset link: muted text, no border, appears only while filtering */
.dt-reset {
  background: none; border: none; padding: 6px 8px; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--ink-faint); text-decoration: underline;
}
.dt-reset:hover { color: var(--ink); }
.dt-search {
  font: inherit; padding: 7px 11px; border-radius: 8px; width: 220px; flex: none;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
}
.dt-search:focus { outline: none; border-color: var(--accent); }
.dt-filter {
  font: inherit; padding: 7px 28px 7px 10px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%237a8296' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
}
/* sortable header affordance + active-sort arrow */
.data-table th.dt-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.data-table th.dt-sortable:hover { color: var(--ink); }
.data-table th.dt-sortable::after { content: "\2195"; opacity: .35; margin-left: 5px; font-size: 11px; }
.data-table th.dt-sort-asc::after { content: "\2191"; opacity: 1; }
.data-table th.dt-sort-desc::after { content: "\2193"; opacity: 1; }
.dt-empty-row td { border-bottom: none; }
.dt-empty {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  text-align: center; padding: 42px 16px; color: var(--ink-soft);
}
.dt-empty-ico {
  font-size: 26px; line-height: 1; margin-bottom: 4px;
  width: 52px; height: 52px; display: grid; place-items: center; border-radius: 50%;
  background: var(--surface-2);
}
.dt-empty-title { font-weight: 650; font-size: 15px; color: var(--ink); }
.dt-empty-hint { font-size: 13px; }
/* footer: count + pager */
.dt-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding: 11px 16px; border-top: 1px solid var(--border);
  font-size: 13px; color: var(--ink-soft);
}
.dt-pager { display: flex; align-items: center; gap: 10px; }
.dt-page-label { font-variant-numeric: tabular-nums; }
.dt-loading { opacity: .55; pointer-events: none; }
/* columns dropdown */
.dt-columns { position: relative; }
.dt-col-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; min-width: 160px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 8px;
}
.dt-col-item { display: flex; align-items: center; gap: 8px; padding: 5px 6px; font-size: 13px; cursor: pointer; }
.dt-col-item:hover { background: var(--surface-2); border-radius: 6px; }
@media (max-width: 820px) {
.dt-toolbar { padding: 10px 12px; }
  /* Let the toolbar groups flow side-by-side and wrap only when they run out of
     room, instead of each forcing its own full-width row. */
  .dt-toolbar-right { margin-left: auto; }
  .dt-search { flex: 1 1 160px; min-width: 0; max-width: 260px; }
}

/* stacked-card table kicks in only when the table itself gets cramped (below the
   toolbar breakpoint), so the normal table is used for as long as it fits */
@media (max-width: 600px) {
  .table-scroll { overflow-x: visible; }
  .data-table thead { display: none; }             /* labels move into each cell */
  .data-table, .data-table tbody, .data-table tr, .data-table td { display: block; width: auto; }
  .data-table tr {
    border: 1px solid var(--border); border-radius: 10px;
    padding: 12px 14px; margin: 12px; background: var(--surface);
  }
  .data-table td { border: none; padding: 6px 0; }
  /* label:value rows for the middle cells */
  .data-table td[data-label] {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
  }
  .data-table td[data-label]::before {
    content: attr(data-label); font-size: 12px; font-weight: 600;
    color: var(--ink-faint); text-transform: uppercase; letter-spacing: .03em;
  }
  /* name = the card title */
  .data-table td.cell-name { padding-bottom: 8px; }
  .data-table td.cell-name .flow-name-btn { font-size: 16px; }
  /* actions = a full-width footer row */
  .data-table td.col-actions {
    display: flex; justify-content: flex-end; gap: 8px;
    margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--border);
  }
  /* the styled empty state should not become a card */
  .data-table tr.dt-empty-row { border: none; padding: 0; margin: 0; background: none; }
  .data-table tr.dt-empty-row td { display: block; }

  /* Compact metric tables (no data-label attributes) must NOT stack into cards -
     bare numbers with no labels are unreadable. Keep them as a real table that
     scrolls horizontally with the Step column pinned. */
  .table-scroll-compact { overflow-x: auto; }
  .data-table-compact { display: table; width: 100%; }
  .data-table-compact thead { display: table-header-group; }
  .data-table-compact tbody { display: table-row-group; }
  .data-table-compact tr { display: table-row; border: none; border-radius: 0; padding: 0; margin: 0; background: none; }
  .data-table-compact th, .data-table-compact td { display: table-cell; border-bottom: 1px solid var(--border); }
}

/* inline-editable flow name: reads as text, hints editability on hover */
/* button and input share the same box (padding, border, negative margin, radius,
   font) so switching between them does not shift the cell */
.flow-name-btn,
.flow-rename-input {
  font: inherit; font-weight: 600; padding: 4px 7px; margin: -4px -7px;
  border-radius: 7px; border: 1px solid transparent; text-align: left;
  box-sizing: content-box; line-height: inherit;
}
.flow-name-btn {
  background: none; color: var(--ink); cursor: text; max-width: 100%;
}
.flow-name-btn:hover { background: var(--surface-2); border-color: var(--border); }
.flow-rename-form { margin: 0; display: inline-block; }
.flow-rename-input {
  background: var(--surface); color: var(--ink); border-color: var(--accent);
  /* width is set inline by JS to match the name button, so the column does not
     jump when editing; this is just a floor/ceiling */
  max-width: 100%; min-width: 100px;
}
.flow-rename-input:focus { outline: none; }

/* ---- confirm / alert modal (themed, replaces native dialogs) ---- */
.modal-overlay {
  /* Above the media library dialog (.media-dlg-back, 4000) so a confirm/alert triggered FROM the
     media dialog (e.g. delete an image) is not hidden behind it. Still below the toast root (5000),
     which must show over everything. */
  position: fixed; inset: 0; z-index: 4500; display: none;
  align-items: center; justify-content: center; padding: 20px;
  background: color-mix(in srgb, #000 45%, transparent);
}
.modal-overlay.show { display: flex; }
.modal {
  background: var(--surface); color: var(--ink); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow); padding: 22px 22px 18px;
  /* 400px was tight for anything past one sentence: a confirm that has to explain a consequence
     wrapped into six or seven short lines and read as a wall. Relative so it still fits a phone. */
  width: 100%; max-width: 480px;
  position: relative;
}
/* "Restarted at" note on a running test. Quiet and inline rather than a tinted band: it explains
   an empty panel, it is not a warning, and it sits directly under the verdict line it qualifies. */
.exp-restart-note { display: flex; align-items: flex-start; gap: 7px;
  margin: -4px 0 14px; font-size: 12.5px; line-height: 1.5; }
.exp-restart-note svg { width: 14px; height: 14px; flex: none; margin-top: 2px; opacity: .8; }

/* Grant-a-feature form: fields on one row on a wide screen, stacked on a narrow one. The button
   aligns to the field boxes rather than the labels above them. */
/* align-items: end lines up the BOTTOM of each field box - so a field carrying a <small> hint
   underneath sits higher than one without, and the input and select stop lining up. Baseline
   alignment uses the controls themselves, which is what the eye actually reads. */
.flag-grant-form { display: flex; flex-wrap: wrap; align-items: end; gap: 14px; }
.flag-grant-form .field { display: flex; flex-direction: column; justify-content: flex-end; }
/* Sized to their content rather than stretching: the flag select holds two short labels, so a
   full-width control made a two-field form look like a page section. */
.flag-grant-form .field { margin-bottom: 0; flex: 0 1 auto; min-width: 0; }
.flag-grant-form select { min-width: 200px; }
.flag-grant-form input[type="number"] { max-width: 140px; }
.flag-grant-form .btn { flex: none; margin-bottom: 2px; }
/* The selected flag explains itself on its own line below the controls, rather than a static hint
   under each field trying to describe every flag at once. Full width so it never squeezes the
   inputs, and empty until the island fills it. */
.flag-help { flex-basis: 100%; margin: 2px 0 0; font-size: 12.5px; line-height: 1.5; }
.flag-help:empty { display: none; }

/* "Your test has a result" row on the dashboard overview.
   GREEN, not accent: the AI panel directly below it is accent-tinted, and at the same hue the two
   read as a matched pair rather than as two different kinds of thing - the alert lost its urgency
   by association. Green also carries through to the result page, where the winning arm's pill uses
   the same token, so the colour means "a version won" in both places.
   Rendered only when a result exists, so it can afford the top of the page without becoming
   background furniture. */
.ab-decision { display: flex; align-items: center; gap: 12px; margin: 0 0 18px;
  padding: 13px 16px; border-radius: 11px; font-size: 13.5px; line-height: 1.5;
  /* Text tinted 15% toward the card's green so it belongs to the card rather than sitting on it
     as unrelated neutral grey. Subtle on purpose, and measured: 6.17:1 for the body and 12.28:1
     for the bold lead against this background, both well clear of the 4.5:1 minimum. */
  color: color-mix(in srgb, var(--good) 15%, var(--ink-soft));
  background: color-mix(in srgb, var(--good) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--good) 32%, var(--border)); }
.ab-decision b { color: color-mix(in srgb, var(--good) 15%, var(--ink)); font-weight: 640; }
.ab-decision-txt { flex: 1; min-width: 0; }
/* The action button matches the card rather than the app-wide accent: a purple button inside a
   green card reads as a mismatch, not as a convention.
   DARK text, not white - measured, not assumed: white on this green is 3.46:1 in light and 2.10:1
   in dark, both below the 4.5:1 minimum, while near-black gives 5.45:1 and 8.99:1. The accent
   button can use white because its purple is much darker; this green cannot. */
.ab-decision .btn { flex: none; }
.ab-decision .btn-primary {
  /* White text needs a deeper green than the token: on plain var(--good) white is 3.46:1 in light
     and 2.10:1 in dark, both under the 4.5:1 minimum.
     Per-theme, because one value has to satisfy the WORSE case and that made light mode needlessly
     dark: light --good starts at #1f9d63 and carries white at 82% (4.88:1), while dark --good is
     brighter (#48c98a) and needs 65% (4.65:1). Splitting them keeps light mode close to the brand
     green instead of dragging it down to dark mode's floor. */
  background: color-mix(in srgb, var(--good) 82%, #000);
  border-color: color-mix(in srgb, var(--good) 82%, #000);
  color: #fff;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ab-decision .btn-primary {
    background: color-mix(in srgb, var(--good) 65%, #000);
    border-color: color-mix(in srgb, var(--good) 65%, #000);
  }
}
:root[data-theme="dark"] .ab-decision .btn-primary {
  background: color-mix(in srgb, var(--good) 65%, #000);
  border-color: color-mix(in srgb, var(--good) 65%, #000);
}
/* Hover LIGHTENS rather than darkens. Darkening is the usual move, but it drops light-theme
   contrast against the dark label to 3.86:1 - below the minimum. Lightening keeps the same label
   readable and still reads as a state change: 6.32:1 light, 9.50:1 dark. */
/* Hover darkens further, which keeps white readable and reads as a press. (The old rule lightened
   instead - correct while the label was dark, wrong now that it is white.) */
/* Hover darkens by the same proportion in both themes, so white only gets more readable. */
.ab-decision .btn-primary:hover,
.ab-decision .btn-primary:focus-visible {
  background: color-mix(in srgb, var(--good) 68%, #000);
  border-color: color-mix(in srgb, var(--good) 68%, #000);
  color: #fff;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ab-decision .btn-primary:hover,
  :root:not([data-theme="light"]) .ab-decision .btn-primary:focus-visible {
    background: color-mix(in srgb, var(--good) 54%, #000);
    border-color: color-mix(in srgb, var(--good) 54%, #000);
  }
}
:root[data-theme="dark"] .ab-decision .btn-primary:hover,
:root[data-theme="dark"] .ab-decision .btn-primary:focus-visible {
  background: color-mix(in srgb, var(--good) 54%, #000);
  border-color: color-mix(in srgb, var(--good) 54%, #000);
}
.ab-decision-ico { flex: none; display: inline-flex; color: var(--good); }
.ab-decision-ico svg { width: 18px; height: 18px; display: block; }
@media (max-width: 620px) {
  /* The action must not be squeezed to an unreadable width beside the sentence. */
  .ab-decision { flex-direction: column; align-items: flex-start; }
  .ab-decision .btn { width: 100%; justify-content: center; }
}

/* A dialog presenting three real choices needs room its two-button sibling does not: the actions
   sit in one row, and each label is a phrase ("Publish and restart the test") rather than a verb.
   Applied only when the third action is present, so an ordinary confirm keeps its narrower,
   easier-to-read measure. */
.modal:has(.cd-alt:not([hidden])) { max-width: 640px; }

/* Close affordance. Escape and the overlay click already dismiss, but neither is visible - a
   modal with only two committing-sounding buttons reads as though it must be answered. */
.modal-close {
  position: absolute; top: 12px; right: 12px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: 8px;
  color: var(--ink-faint);
}
.modal-close:hover { color: var(--ink); background: var(--surface-2); }
.modal-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The title must not run under the close button. */
.modal .modal-title { padding-right: 34px; }
/* a wider modal for a form with more fields (e.g. the pre-checkout billing details), scrollable */
.modal-wide { max-width: 520px; max-height: 90vh; overflow-y: auto; }
/* wider modal for the location map + table */
.modal-lg { max-width: 720px; max-height: 88vh; overflow-y: auto; }
.modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.modal-head .modal-title { margin: 0; }
/* world map: land + stroke colors follow the theme via custom props */
.loc-map { margin-bottom: 16px; }
.world-map { width: 100%; height: auto; --wm-land: var(--surface-2); --wm-stroke: var(--border); }
:root[data-theme="dark"] .world-map { --wm-land: #1d2740; --wm-stroke: #2b3852; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .world-map { --wm-land: #1d2740; --wm-stroke: #2b3852; }
}
.world-map path { transition: fill-opacity .2s; }
.loc-nomap { text-align: center; padding: 20px; }
/* vendored svgMap container (the lib brings its own svgMap.min.css) */
.loc-svgmap { width: 100%; }
/* svgMap sizes the wrapper via padding-top (its aspect ratio; default 50% = 2:1).
   The world map has empty ocean top/bottom, so a shorter ratio trims dead space. */
.loc-svgmap .svgMap-map-wrapper {
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding-top: 42% !important; padding-bottom: 0 !important;
}
/* svgMap draws country borders in white by default (invisible on a light panel);
   give them a visible neutral stroke instead */
.loc-svgmap .svgMap-country { stroke: var(--border-strong); stroke-width: 0.6; }
.loc-svgmap .svgMap-country:hover, .loc-svgmap .svgMap-country.svgMap-active { stroke: var(--ink-soft); stroke-width: 1.2; }
/* zoom/pan controls follow the theme. svgMap positions the wrapper absolutely and
   gives the zoom/move groups a hardcoded white background + a light hairline shadow
   that looks wrong on our panels (esp. dark), so restyle the whole cluster. */
.loc-svgmap .svgMap-map-controls-wrapper {
  bottom: 12px !important; left: 12px !important; border-radius: 8px !important;
  box-shadow: none !important; overflow: visible !important; gap: 6px;
}
.loc-svgmap .svgMap-map-controls-move,
.loc-svgmap .svgMap-map-controls-zoom {
  background: var(--surface) !important; border: 1px solid var(--border) !important;
  border-radius: 8px !important; overflow: hidden !important; box-shadow: var(--shadow) !important;
}
.loc-svgmap .svgMap-control-button { background: var(--surface); border: none; color: var(--ink); }
.loc-svgmap .svgMap-control-button:hover { background: var(--surface-2); }
/* svgMap appends its tooltip to <body> with z-index:2 (below our modal at 300) and
   ships large, hardcoded-white styling. Lift it above the modal and restyle it
   compact + on-brand + theme-aware. */
.svgMap-tooltip {
  z-index: 600 !important;
  background: var(--surface) !important; color: var(--ink) !important;
  border: 1px solid var(--border) !important; border-radius: 8px !important;
  box-shadow: var(--shadow) !important; min-width: 0 !important;
}
.svgMap-tooltip .svgMap-tooltip-content-container { padding: 7px 10px !important; }
/* compact emoji flag inline-ish with tight spacing (matches chart-tip density) */
.svgMap-tooltip .svgMap-tooltip-flag-container.svgMap-tooltip-flag-container-emoji {
  font-size: 18px !important; padding: 0 !important; margin: 0 0 2px !important; line-height: 1 !important;
}
.svgMap-tooltip .svgMap-tooltip-title {
  font-size: 12.5px !important; line-height: 1.4 !important; font-weight: 650 !important;
  padding: 0 0 4px !important; color: var(--ink) !important;
}
.svgMap-tooltip .svgMap-tooltip-content { font-size: 12.5px !important; color: var(--ink-soft) !important; line-height: 1.4 !important; margin: 0 !important; }
.svgMap-tooltip .svgMap-tooltip-content table td { padding: 1px 0 !important; }
.svgMap-tooltip .svgMap-tooltip-content table td:first-child { padding-right: 10px !important; color: var(--ink-faint) !important; }
.svgMap-tooltip .svgMap-tooltip-content table td span { color: var(--ink) !important; font-weight: 700 !important; }
/* the little pointer triangle: match the themed background + border */
.svgMap-tooltip .svgMap-tooltip-pointer::after {
  background: var(--surface) !important; border: 1px solid var(--border) !important;
}
.modal-title { font-size: 17px; margin: 0 0 8px; }
.modal-msg { color: var(--ink-soft); font-size: 14px; margin: 0 0 18px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
/* THREE actions: one row on desktop, stacked only when the row genuinely cannot fit.
   The dialog that uses this (.modal-choices) is widened to hold them, so the row is the normal
   case - stacking every screen size turned a desktop dialog into a phone layout. nowrap is
   deliberate: wrapping is what produced the broken-looking two-row layout, where the primary
   action ended up stranded on its own line. :has() scopes all of this to the three-button case,
   so every existing two-button confirm is untouched. */
.modal-actions:has(> .cd-alt:not([hidden])) { flex-wrap: nowrap; }
.modal-actions:has(> .cd-alt:not([hidden])) .btn { white-space: nowrap; }
/* The alternative sits apart from the dismiss/confirm pair: it is a different KIND of answer. */
.modal-actions:has(> .cd-alt:not([hidden])) .cd-alt { margin-right: auto; }
@media (max-width: 640px) {
  /* Below this the three labels cannot share a row without truncating, so stack them - most
     recommended first, destructive last. `order` rather than reordering the DOM, so the focus
     trap's tab order is unchanged. */
  .modal-actions:has(> .cd-alt:not([hidden])) {
    flex-direction: column; align-items: stretch; gap: 8px;
  }
  .modal-actions:has(> .cd-alt:not([hidden])) .btn { width: 100%; justify-content: center; }
  .modal-actions:has(> .cd-alt:not([hidden])) .cd-alt    { order: 1; margin-right: 0; }
  .modal-actions:has(> .cd-alt:not([hidden])) .cd-cancel { order: 2; }
  .modal-actions:has(> .cd-alt:not([hidden])) .cd-ok     { order: 3; }
}

/* delete-workspace dialog: give the intro line room above the fields, and stack the fields with
   even spacing (the last field's default bottom margin is dropped so the actions row sits close). */
.ws-delete-form .modal-actions { margin-top: 4px; }
.ws-delete-intro { margin: 0 0 18px; line-height: 1.5; } /* intro line only - NOT the hint */
.ws-delete-form .field { margin-bottom: 16px; }
/* The mismatch hint sits snug under the confirm-name input: collapse the field's bottom margin
   ONLY while a VISIBLE hint follows it (the hint is a sibling, hidden by [hidden] when valid).
   When the hint is hidden, the field keeps its normal 16px gap to the password field below. */
.ws-delete-form .ws-delete-name-field:has(+ .ws-delete-hint:not([hidden])) { margin-bottom: 5px; }
.ws-delete-hint { margin: 0 0 16px; font-size: 12.5px; color: var(--crit); }
.ws-delete-form input.has-error { border-color: var(--crit); }

/* new-flow form: label rows stack, control width capped */
.flow-create-form { display: flex; flex-direction: column; gap: 12px; max-width: min(360px, 100%); }
.flow-create-form .field { width: 100%; }

/* ---- honeypot (bot trap) ---- */
/* Off-screen rather than display:none so naive bots still see and fill it, while
   humans never do. Not announced to assistive tech (aria-hidden on the wrapper). */
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  width: 1px; height: 1px; overflow: hidden;
}

/* ---- password strength meter ---- */
.pw-strength { margin: 6px 0 2px; overflow: hidden; max-height: 46px; transition: max-height .18s ease, opacity .18s ease, margin .18s ease; }
.pw-strength.is-empty { max-height: 0; opacity: 0; margin: 0; }
.pw-bar { height: 5px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.pw-bar-fill {
  display: block; height: 100%; width: 0; border-radius: 999px;
  transition: width .18s ease, background-color .18s ease; background: var(--crit);
}
.pw-bar-fill.lvl-0 { background: var(--crit); }
.pw-bar-fill.lvl-1 { background: var(--crit); }
.pw-bar-fill.lvl-2 { background: var(--warn); }
.pw-bar-fill.lvl-3 { background: color-mix(in srgb, var(--good) 70%, var(--warn)); }
.pw-bar-fill.lvl-4 { background: var(--good); }
.pw-strength-row { display: flex; justify-content: space-between; gap: 10px; margin-top: 5px; font-size: 11.5px; }
.pw-label { font-weight: 600; color: var(--ink-soft); }
.pw-hint { color: var(--ink-faint); text-align: right; }
.pw-hint-error { color: var(--crit) !important; font-weight: 600; }
/* prominent inline error banner (shown when the app toast is not available) */
.pw-form-error {
  padding: 10px 14px; border-radius: 9px; font-size: 13px; margin-bottom: 14px;
  color: var(--crit); background: color-mix(in srgb, var(--crit) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--crit) 35%, var(--border));
}
/* shake the field to draw attention on a blocked submit */
@keyframes pw-shake {
  0%,100% { transform: translateX(0); }
  20%,60% { transform: translateX(-5px); }
  40%,80% { transform: translateX(5px); }
}
.pw-shake { animation: pw-shake .32s; }
@media (prefers-reduced-motion: reduce) {
.pw-shake { animation: none; }
}

/* ---- flow step editor (4d) ---- */
.editor-save-state { font-size: 12px; margin-left: 8px; }
/* In the header action row the flex gap already spaces it, so drop the inline-text margin and let
   it sit tight against the Save button it describes. white-space stops "Save failed - retry"
   wrapping the row onto two lines. */
.page-head-actions .editor-save-state { margin-left: 0; white-space: nowrap; }
.editor-save-state.is-saving { color: var(--ink-faint); }
.editor-save-state.is-saved  { color: var(--good); }
.editor-save-state.is-unsaved { color: var(--warn); }
.editor-save-state.is-error  { color: var(--crit); }
.editor-grid { display: grid; grid-template-columns: 300px 1fr; gap: 18px; align-items: start; }
/* Grid children default to min-width:auto, so a wide descendant (a long unbreakable
   title, a preview stage, a <pre>) forces its whole column past the viewport - which on
   mobile clips the Steps panel's "Add step" button and runs step rows off-screen. Let the
   columns shrink to their container instead. */
.editor-grid > .panel { min-width: 0; }
@media (max-width: 820px) {
.editor-grid { grid-template-columns: 1fr; }
}
/* A tooltip has no step-list column, so its editor is a single full-width form. */
.editor-grid-solo { grid-template-columns: 1fr; }
.editor-steps { padding: 16px 16px 18px; }
/* Numbered instruction list (e.g. the manual extension-install steps on the Install page). The
   .doc-steps class is also used on the public docs; give it dashboard styling here so the ordered
   list has proper spacing + gap between items instead of a bare browser <ol>. */
/* No top margin: the preceding .panel-hint already spaces it (its 14px bottom margin is enough);
   adding more here double-gapped it. A generous bottom margin so it is not cramped against whatever
   follows (e.g. the "How does pairing work" explainer). */
.doc-steps { margin: 0 0 4px; padding-left: 22px; display: flex; flex-direction: column; gap: 6px; }
.doc-steps li { font-size: 13.5px; line-height: 1.5; }
/* Breathing room below the manual-install block before the pairing-work explainer. */
.ext-getit { margin-bottom: 18px; }
/* Scoped tightening: the intro line's shared .panel-hint has a 14px bottom margin that is too much
   right above the numbered steps here. Override ONLY in this context (not the global .panel-hint used
   across 13 pages) so the note sits close to the list it introduces. */
.ext-getit .panel-hint { margin-bottom: 8px; }

.step-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.step-row {
  display: flex; align-items: center; gap: 8px; padding: 9px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); cursor: pointer;
  font-size: 13.5px; user-select: none;
}
.step-row:hover { background: var(--surface-2); }
.step-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.step-row.is-selected { border-color: var(--accent); background: var(--accent-soft); }
.step-row.is-dragging { opacity: .5; }
.step-row.is-drop-target { border-top: 2px solid var(--accent); }
.step-handle { color: var(--ink-faint); cursor: grab; font-size: 15px; line-height: 1; }
.step-num {
  flex: none; width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center;
  font-size: 11px; font-weight: 700; background: var(--surface-2); color: var(--ink-soft);
}
.step-row.is-selected .step-num { background: var(--accent); color: #fff; }
.step-row-title { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.step-empty { font-size: 13px; margin-top: 10px; }
.editor-form { padding: 20px 22px; }
.editor-form-empty { font-size: 14px; }
.editor-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* display:grid overrides the [hidden] attribute's default display:none - restore it so a
   hidden row (e.g. the Back/Next labels when nav is not text buttons) actually hides. */
.editor-field-row[hidden] { display: none; }
/* A survey's two placement toggles stack instead of sharing the row's two columns: each
   carries a sentence of explanation, so side by side gave two narrow wrapping paragraphs
   rather than two readable lines. */
.survey-extra-row { grid-template-columns: 1fr; gap: 10px; }
/* Announcement cards have no target element, so the per-step controls that only make
   sense against a target (placement, show-on trigger, the captured target readout) are
   hidden for that flow type. The step editor stays otherwise identical (title/body/image
   /advance). Keyed off the editor container's data-flow-type. */
.editor[data-flow-type="announcement"] [data-target-only] { display: none; }
@media (max-width: 520px) {
.editor-field-row { grid-template-columns: 1fr; }
}
.editor-target {
  display: flex; flex-direction: column; gap: 3px; margin: 6px 0 4px;
  padding: 10px 12px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
}
/* display:flex overrides the [hidden] attribute's default display:none - restore it so the
   target readout actually hides for trigger options that need no target. */
.editor-target[hidden] { display: none; }
/* manual CSS-selector input (tooltip target) - monospace, matches the item selector input. */
.field-mono { font-family: var(--mono); }
.field-mono[hidden] { display: none; }
.editor-target-label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); font-weight: 600; }
/* Value and its inline-edit input share identical block metrics (same font, line-height and
   min-height, no vertical padding/border) so toggling between them never shifts the panel. */
.editor-target-value,
.editor-target-input {
  display: block; font-family: var(--mono); font-size: 12.5px; line-height: 20px;
  min-height: 20px; color: var(--ink); word-break: break-all;
}
.editor-target-value.is-missing { font-family: inherit; color: var(--ink-faint); font-style: italic; word-break: normal; }
/* Captured-target header: label on the left, a subtle "Edit selector" text link on the right. */
.editor-target-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.editor-target-edit {
  background: none; border: none; padding: 0; cursor: pointer;
  font-size: 11px; font-weight: 600; color: var(--accent); letter-spacing: .01em;
}
.editor-target-edit:hover { text-decoration: underline; }
.editor-target-edit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
/* Inline selector input inside the target panel - borderless + zero vertical padding so its
   content box equals the read-only value's box (metrics come from the shared rule above). */
.editor-target-input {
  width: 100%; padding: 0; margin: 0; border: none; background: none; outline: none;
  box-sizing: content-box; -webkit-appearance: none; appearance: none;
}
.editor-target-input::placeholder { color: var(--ink-faint); }
/* Compact Cancel / Update text buttons in the header (same size as the "Edit selector" link
   they replace). Update keeps the accent colour; Cancel is muted. */
.editor-target-edit-actions { display: inline-flex; align-items: baseline; gap: 12px; }
.editor-target-edit.is-muted { color: var(--ink-faint); }
.editor-target-edit.is-muted:hover { color: var(--ink-soft); }
.editor-form-foot { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
/* field label (for the non-<label> rich-text field) + foot (hint + char count) */
.field-label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 5px; color: var(--ink-soft); }
.field-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-top: 4px; }
.char-count { font-size: 11px; color: var(--ink-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
.char-count.is-over { color: var(--crit); font-weight: 600; }
/* rich-text (body) editor */
.rte { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: var(--surface); }
.rte:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.rte-toolbar { display: flex; flex-wrap: wrap; gap: 2px; padding: 5px 6px; border-bottom: 1px solid var(--border); background: var(--surface-2); }
.rte-btn {
  appearance: none; border: none; background: none; cursor: pointer; font: inherit;
  width: 28px; height: 28px; border-radius: 6px; color: var(--ink-soft); font-size: 14px;
  display: grid; place-items: center;
}
.rte-btn:hover { background: var(--surface); color: var(--ink); }
.rte-btn.is-active { background: var(--accent-soft); color: var(--accent); }
.rte-btn svg { width: 16px; height: 16px; display: block; }
/* link/unlink art fills the viewBox, so scale it down to match the other icons. */
.rte-btn[data-rte-cmd="link"] svg,
.rte-btn[data-rte-cmd="unlink"] svg { width: 14px; height: 14px; }

/* AI copywriting button + menu (Phase 1). Sits next to the Title label and in the body RTE toolbar. */
.ai-copy { position: relative; display: inline-flex; }
.rte-ai.ai-copy-host { margin-left: auto; }   /* push the AI button to the toolbar's right end */
.ai-copy-btn {
  display: inline-flex; align-items: center; gap: 4px; font: inherit; font-size: 11.5px; font-weight: 650;
  color: var(--accent); background: var(--accent-soft); border: 1px solid transparent;
  border-radius: 7px; padding: 3px 9px; cursor: pointer; line-height: 1.4;
}
.ai-copy-btn:hover { border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.ai-copy-btn.is-busy { pointer-events: none; }
/* Fixed-size icon slot so the emoji and the spinner occupy the SAME box - no width/height shift when
   swapping between them. */
.ai-copy-spark { width: 12px; height: 12px; font-size: 11px; line-height: 12px; text-align: center;
  display: inline-flex; align-items: center; justify-content: center; flex: none; }
.ai-copy-btn.is-busy .ai-copy-spark { font-size: 0; }
.ai-copy-btn.is-busy .ai-copy-spark::before {
  content: ""; box-sizing: border-box; display: block; flex: none;
  width: 11px; height: 11px; min-width: 11px; min-height: 11px; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: btn-spin .6s linear infinite;
}
/* Fixed (not absolute) so it escapes the RTE's overflow:hidden - positioned by JS at the button.
   Uses the raised panel colour + a stronger shadow so it reads clearly over ANY background
   (including the body toolbar's tinted surface). */
.ai-copy-menu {
  position: fixed; z-index: 1000; min-width: 200px;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0,0,0,.22), 0 1px 3px rgba(0,0,0,.12);
  padding: 5px; display: flex; flex-direction: column;
}
:root[data-theme="dark"] .ai-copy-menu { background: var(--surface-2); }
/* A menu row: the mode label (flex-fills) + an optional regenerate icon shown once cached. The WHOLE
   row is the hover target (one continuous highlight), so the label + regen icon never look like two
   separate pills. */
.ai-copy-row { display: flex; align-items: stretch; gap: 0; border-radius: 6px; }
.ai-copy-row:hover { background: var(--surface-2); }
.ai-copy-item {
  flex: 1; display: flex; align-items: center; gap: 7px; text-align: left; font: inherit; font-size: 13px;
  color: var(--ink); background: none; border: 0; padding: 7px 10px; cursor: pointer; white-space: nowrap;
}
.ai-copy-label { flex: 1; }
/* Cached mode: a trailing history icon (SVG) after the label - "there's a saved version, click to
   restore" - muted so it reads as an affordance, not a success/selected state. Accent on hover. */
.ai-copy-check { flex: none; display: inline-flex; color: var(--ink-faint); }
.ai-copy-check svg { width: 12px; height: 12px; display: block; opacity: .8; }
.ai-copy-item:hover .ai-copy-check { color: var(--ink-soft); }
.ai-copy-regen {
  flex: none; width: 30px; display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-soft); background: none; border: 0; border-radius: 6px; cursor: pointer;
}
.ai-copy-regen svg { width: 15px; height: 15px; display: block; }
/* On direct hover the icon itself gets the accent (the row is already tinted), so it reads as its own
   action without a second competing box. */
.ai-copy-regen:hover { color: var(--accent); }

/* Themed tooltip for the AI menu icons (replaces the native title= bubble). Pure CSS, appears above
   the icon on hover/focus. The menu has no overflow clip, so an absolute bubble shows fine. */
.ai-copy-menu [data-tip] { position: relative; }
.ai-copy-menu [data-tip]::after {
  content: attr(data-tip); position: absolute; bottom: calc(100% + 6px); right: 0;
  background: var(--ink); color: var(--surface); font-size: 11.5px; font-weight: 500; line-height: 1.3;
  white-space: nowrap; padding: 5px 8px; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,.2);
  opacity: 0; transform: translateY(3px); pointer-events: none; transition: opacity .12s ease, transform .12s ease;
  z-index: 1;
}
.ai-copy-menu [data-tip]:hover::after,
.ai-copy-menu [data-tip]:focus-visible::after { opacity: 1; transform: translateY(0); }
.ai-copy-sep { height: 1px; background: var(--border); margin: 4px 2px; }
/* Undo link shown after an AI fill - revert to the value that was there before. */
.ai-copy-undo {
  font: inherit; font-size: 11.5px; font-weight: 500; color: var(--ink-soft);
  background: none; border: 0; padding: 3px 6px; margin-left: 4px; cursor: pointer; border-radius: 6px;
}
.ai-copy-undo:hover { color: var(--ink); background: var(--surface-2); text-decoration: underline; }
/* Inline "what should this step do?" panel inside the AI menu (bare-step Write-for-me). */
.ai-copy-ask { padding: 8px; display: flex; flex-direction: column; gap: 8px; min-width: 260px; }
.ai-copy-ask-q { font-size: 12.5px; font-weight: 600; color: var(--ink); }
/* stretch so the input + button are the SAME height regardless of their own padding */
.ai-copy-ask-row { display: flex; gap: 6px; align-items: stretch; }
.ai-copy-ask-input {
  flex: 1; min-width: 0; box-sizing: border-box; font: inherit; font-size: 13px; padding: 4px 9px; margin: 0;
  border: 1px solid var(--border-strong); border-radius: 7px; background: var(--surface); color: var(--ink);
}
.ai-copy-ask-input:focus { outline: none; border-color: var(--accent); }
.ai-copy-ask-go { flex: none; }
/* Says whether the intent input is optional (step has text to fall back on) or required (bare step). */
.ai-copy-ask-hint { font-size: 11.5px; line-height: 1.35; color: var(--muted); }
.ai-copy-ask-go[disabled] { opacity: .55; cursor: not-allowed; }

/* One-time "AI drafted your steps" reminder in the flow editor (shown after Generate-with-AI). A soft
   accent-tinted callout with a dismiss X; theme-aware via tokens. */
.flow-ai-callout {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0 0 16px; padding: 12px 14px;
  background: var(--accent-soft); border: 1px solid var(--border); border-radius: 10px;
}
.flow-ai-callout-ico { flex: none; font-size: 16px; line-height: 1.4; }
.flow-ai-callout p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink-soft); }
.flow-ai-callout p strong { color: var(--ink); }
.flow-ai-callout a { color: var(--accent); font-weight: 600; }
.flow-ai-callout-close {
  flex: none; margin-left: auto; font: inherit; font-size: 18px; line-height: 1;
  background: none; border: 0; color: var(--ink-faint); cursor: pointer; padding: 0 2px; border-radius: 6px;
}
.flow-ai-callout-close:hover { color: var(--ink); }

/* Generate-with-AI create panel: the Type + Product fields sit side by side on wider screens. */
.flow-ai-row { display: flex; gap: 14px; flex-wrap: wrap; }
.flow-ai-row .field { flex: 1; min-width: 180px; }

/* "Describe your audience" AI targeting box - sits above the rule builder. Accent-tinted panel so it
   reads as an AI helper, distinct from the plain rule rows below it. */
.ai-target {
  margin: 0 0 16px; padding: 12px 14px;
  background: var(--accent-soft); border: 1px solid var(--border); border-radius: 10px;
}
.ai-target-field { margin: 0; }
.ai-target-field > span { display: block; font-weight: 600; font-size: 13px; margin-bottom: 6px; color: var(--ink); }
.ai-target-badge {
  font-size: 10px; font-weight: 700; letter-spacing: .04em; color: var(--accent);
  border: 1px solid var(--accent); border-radius: 5px; padding: 1px 5px; margin-left: 6px; vertical-align: middle;
}
.ai-target-row { display: flex; gap: 8px; align-items: stretch; }
.ai-target-row input {
  flex: 1; min-width: 0; box-sizing: border-box; font: inherit; font-size: 14px; padding: 8px 11px; margin: 0;
  border: 1px solid var(--border-strong); border-radius: 8px; background: var(--surface); color: var(--ink);
}
.ai-target-row input:focus { outline: none; border-color: var(--accent); }
.ai-target-row .btn { flex: none; white-space: nowrap; }
.ai-target-hint { margin: 8px 0 0; font-size: 12px; }

.rte-body {
  position: relative;
  min-height: 92px; max-height: 260px; overflow-y: auto; padding: 10px 12px;
  font-size: 14px; line-height: 1.5; color: var(--ink); outline: none;
}
.rte-body a { color: var(--accent); }
.rte-body ul, .rte-body ol { margin: 4px 0; padding-left: 22px; }
.rte-body li { margin: 2px 0; }
.rte-body p { margin: 0 0 6px; }
/* alignment via classes (CSP-safe; inline style attrs are blocked). Squire emits
   these on aligned blocks; the runtime uses the same names when it renders. */
.align-center { text-align: center; }
.align-right { text-align: right; }
.align-left { text-align: left; }
.align-justify { text-align: justify; }
/* Placeholder is an absolutely-positioned overlay so it does not take a line in the
   content flow - otherwise the empty block the editor keeps sits BELOW the placeholder
   text and the caret drops to the next line. Anchored to the .rte-body padding box. */
.rte-body.is-empty::before {
  content: attr(data-placeholder); color: var(--ink-faint); pointer-events: none;
  position: absolute; top: 10px; left: 12px; right: 12px;
}
.rte-sep { width: 1px; align-self: stretch; margin: 3px 3px; background: var(--border); }
/* the link modal reuses .check-row (defined with the auth checkbox rows above). */
/* "unpublished changes" indicator: a small labelled pill next to the publish button */
.editor-changes-pill {
  display: none; font-size: 11px; font-weight: 600; color: var(--warn);
  background: color-mix(in srgb, var(--warn) 15%, transparent); border-radius: 999px;
  padding: 2px 9px; white-space: nowrap;
}
.editor-changes-pill.is-visible { display: inline-flex; align-items: center; }

/* ---- admin plan catalog editor (plan management) ---- */
.plan-slug { font-size: 13px; color: var(--ink-faint); font-weight: 400; }
/* Plans list (table + per-plan View/Edit dialog). */
.plan-row-actions { display: flex; gap: 6px; align-items: center; justify-content: flex-end; flex-wrap: nowrap; }
.plan-detail-h { margin: 18px 0 8px; font-size: 14px; font-weight: 700; }
.plan-detail-features { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
.plan-detail-features li { font-size: 13px; color: var(--ink-soft); line-height: 1.4; }
.plan-dialog .modal { max-width: 720px; }
.plan-dialog .plan-prices { margin-top: 20px; }
/* Flex-column dialog: fixed title + scrollable body + fixed Save/Cancel footer. */
.plan-dialog-modal { display: flex; flex-direction: column; max-height: min(88vh, 860px); overflow: hidden; }
.plan-dialog-title { flex: none; margin-right: 32px; /* clear the close button */ }
.plan-dialog-body { flex: 1 1 auto; overflow-y: auto; min-height: 0; margin: 0 -4px; padding: 0 4px; }
.plan-dialog-foot {
  flex: none; display: flex; justify-content: flex-end; gap: 10px;
  padding-top: 14px; margin-top: 6px; border-top: 1px solid var(--border);
}
.plan-form .field-label { display: block; font-size: 11.5px; font-weight: 600; color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .03em; margin-bottom: 5px; }
.plan-form .field-label .muted { text-transform: none; letter-spacing: 0; font-weight: 400; }
.plan-form .field { margin-bottom: 14px; }
.plan-form-note { font-size: 12px; color: var(--ink-faint); margin: 0 0 12px; }
.req-mark { color: var(--accent); font-weight: 700; }
/* Three inputs per row on desktop, collapsing to 2 then 1 on narrower widths. */
.plan-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px 18px; }
.plan-grid .field { margin-bottom: 0; min-width: 0; }
@media (max-width: 780px) { .plan-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .plan-grid { grid-template-columns: 1fr; } }
.plan-form input[type="text"], .plan-form input[type="number"], .plan-features {
  font: inherit; font-size: 13px; padding: 7px 10px; border-radius: 8px; width: 100%;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
}
.plan-toggles { display: flex; flex-direction: column; gap: 8px; margin: 14px 0; }
.plan-prices { margin-top: 20px; border-top: 1px solid var(--border); padding-top: 16px; }
.plan-prices h3 { margin: 0 0 4px; font-size: 15px; }
.price-id-table .price-id-form { display: flex; gap: 8px; align-items: center; flex-wrap: nowrap; }
.price-id-input { display: flex; gap: 4px; align-items: center; flex: 1; min-width: 0; }
.price-id-input input { font: inherit; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface);
  color: var(--ink); flex: 1; min-width: 0; }
.price-id-copy[disabled] { opacity: .45; cursor: default; }
.price-id-actions { display: flex; gap: 6px; justify-content: flex-end; }
.price-id-actions .inline-form { display: inline; }

/* ---- targeting rule editor (Phase 4d full targeting engine) ---- */
.targeting { margin-top: 20px; }
/* Free-tier "basic targeting only" notice above the rule builder (Phase 5 plan gating). */
.plan-notice {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
  border-radius: 10px; padding: 12px 14px; margin-bottom: 16px; font-size: 13px; color: var(--ink);
}
.plan-notice > span { flex: 1; min-width: 220px; }
.plan-notice > .btn { flex: none; }
/* space between the audience name and the rule builder below it */
.audience-name-field { margin-bottom: 24px; }
.targeting-save-state { font-size: 12px; margin-left: 6px; }
.targeting-save-state.is-saving { color: var(--ink-faint); }
.targeting-save-state.is-saved  { color: var(--good); }
.targeting-save-state.is-unsaved { color: var(--warn); }
.targeting-save-state.is-error  { color: var(--crit); }
.targeting-grid {
  display: grid; grid-template-columns: 1fr 300px; gap: 20px; align-items: start;
}
@media (max-width: 900px) {
.targeting-grid { grid-template-columns: 1fr; }
}
.rule-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.rule-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2);
}
.rule-pre { font-size: 12.5px; color: var(--ink-soft); min-width: 44px; }
.rule-row select, .rule-row input {
  height: 34px; padding: 0 8px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink); font-size: 13.5px;
}
.rule-row .rule-key { flex: 0 1 130px; min-width: 90px; }
/* The themed combobox replaces these selects at runtime, so it must inherit the ROW's compact
   metrics rather than the taller default .ss-btn (which is sized for full-width form fields).
   Without this the rule rows grow noticeably and stop lining up with the inputs beside them. */
.rule-row .ss-wrap { display: inline-flex; }
.rule-row .ss-btn {
  /* min-height must be reset too, not just height: a min-height of 41px from the base rule would
     win over a 34px height and the compact rows would grow back. */
  height: 34px; min-height: 0; padding: 0 26px 0 8px; border-radius: 6px;
  border-color: var(--border); font-size: 13.5px;
}
.rule-row .ss-caret { position: absolute; right: 8px; top: 50%; margin-top: -4px; }
.rule-row .ss-wrap { position: relative; }
/* Keep the popover readable even though the control itself is narrow. */
.rule-row .ss-pop { min-width: max(100%, 180px); max-width: min(280px, 80vw); }
/* Mirror the native selects' flex sizing onto the wrappers the island now inserts. */
.rule-row .rule-key-wrap { flex: 0 1 130px; min-width: 90px; }
.rule-row .rule-type-wrap { flex: 0 1 150px; min-width: 110px; }
.rule-row .rule-op-wrap { flex: 0 1 150px; min-width: 110px; }
.rule-row .rule-value-wrap { flex: 1; min-width: 120px; }
.rule-row .rule-value-compact-wrap { flex: 0 0 auto; min-width: 130px; width: auto; }
/* The group ALL/ANY selector is smaller again - match its 30px pill. */
.ss-wrap.group-match-wrap .ss-btn {
  height: 30px; min-height: 0; padding: 0 24px 0 8px; border-radius: 6px;
  font-size: 12.5px; font-weight: 600;
}
.ss-wrap.group-match-wrap { position: relative; display: inline-flex; }
.ss-wrap.group-match-wrap .ss-caret { position: absolute; right: 7px; top: 50%; margin-top: -4px; }
.ss-wrap.group-match-wrap .ss-pop { min-width: 120px; left: 0; right: auto; }
.rule-row .rule-suffix { font-size: 12.5px; color: var(--ink-soft); }
.rule-leaf { display: flex; flex-direction: column; gap: 3px; }
.rule-hint { font-size: 12px; color: var(--ink-faint); padding-left: 2px; }

/* searchable picker (flow / audience dropdowns; event name suggestions) */
.sp-wrap { position: relative; flex: 1; min-width: 120px; display: inline-flex; }
.sp-wrap.rule-key { flex: 0 1 150px; }
.sp-input { width: 100%; }
.sp-menu {
  position: absolute; top: calc(100% + 2px); left: 0; right: 0; z-index: 20;
  max-height: 220px; overflow-y: auto; padding: 4px;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 8px;
  box-shadow: var(--shadow);
}
.sp-item {
  padding: 6px 8px; border-radius: 6px; font-size: 13px; color: var(--ink); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sp-item:hover, .sp-item.is-active { background: var(--accent-soft); color: var(--accent); }
/* Footer note in a picker menu when results are capped: "N more - type to narrow". */
.sp-more { padding: 6px 8px; font-size: 11.5px; color: var(--ink-faint); font-style: italic; cursor: default; }
/* Multi-select chip picker (country "is one of"): chips + a search input wrap inside one field box. */
.cp-wrap {
  flex-wrap: wrap; align-items: center; gap: 4px; min-height: 38px; padding: 4px 6px;
  border: 1px solid var(--border-strong); border-radius: 8px; background: var(--surface);
}
.cp-wrap .cp-input {
  flex: 1; min-width: 90px; border: 0; background: none; padding: 3px 2px; outline: none;
  font: inherit; font-size: 13px; color: var(--ink);
}
.cp-chips { display: contents; }
.cp-chip {
  display: inline-flex; align-items: center; gap: 4px; font-size: 12px; line-height: 1;
  padding: 4px 4px 4px 8px; border-radius: 6px; background: var(--accent-soft); color: var(--accent); white-space: nowrap;
}
.cp-chip-x {
  border: 0; background: none; color: inherit; cursor: pointer; font-size: 15px; line-height: 1;
  padding: 0 3px; border-radius: 4px;
}
.cp-chip-x:hover { background: rgba(0,0,0,.08); }
.rule-row .rule-value { flex: 1; min-width: 120px; }
/* Compact value controls (a small number, a date, a New/Returning select) should size to their
   content instead of stretching across the whole row like a free-text / search value does. */
.rule-row .rule-value-compact { flex: 0 0 auto; min-width: 0; width: auto; }
.rule-row input.rule-value-compact[type="number"] { width: 110px; }
.rule-row input.rule-value-compact[type="date"] { width: 160px; }
.rule-row select.rule-value-compact { width: auto; min-width: 130px; padding-right: 28px; }
.rule-row .rule-remove { margin-left: auto; color: var(--crit); }
/* compact "x" remove button (conditions only): a plain cross, no button chrome,
   dark grey. Overrides the .btn/.rule-btn base it also carries. */
.rule-x {
  margin-left: auto; flex: none; width: 24px; min-width: 24px; height: 24px; padding: 0;
  border: 0; background: none; box-shadow: none;
  font-size: 16px; line-height: 1; color: var(--ink); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.rule-x:hover { background: none; color: var(--crit); }

/* flow editor tabs (Content / Targeting / Frequency): a segmented pill control -
   a soft grey track with the active tab as a raised surface pill. */
.flow-tabs { margin-top: 4px; }
.tab-bar {
  display: inline-flex; gap: 4px; margin-bottom: 20px;
  padding: 4px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px;
}
.tab-bar .tab {
  appearance: none; background: none; border: 0; border-radius: 999px;
  padding: 7px 18px; font-size: 13.5px; font-weight: 500; line-height: 1; white-space: nowrap;
  color: var(--ink-soft); cursor: pointer; transition: color .12s, background .12s;
  /* inline-flex so a trailing icon (the plan lock) centres against the label instead of
     sitting on the text baseline, which left it riding visibly high. */
  display: inline-flex; align-items: center; gap: 5px;
}
.tab-bar .tab:hover { color: var(--ink); }
.tab-bar .tab.is-active {
  color: var(--accent); font-weight: 600;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(16,32,46,.06), 0 1px 3px rgba(16,32,46,.10);
}
.tab-panel[hidden] { display: none; }
/* appearance tab: theme + intro modal panels */
.appearance .panel + .panel { margin-top: 16px; }
.appearance input[type="color"] {
  width: 48px; height: 34px; padding: 2px; border: 1px solid var(--border);
  border-radius: 6px; background: var(--surface); cursor: pointer;
}
.intro-fields { display: flex; flex-direction: column; gap: 12px; margin-top: 6px; }
.intro-fields[hidden] { display: none; }
/* appearance controls + live preview, side by side */
/* per-mode theming header: opt-in checkbox + a Light/Dark segmented switch */
.theme-mode-head { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.theme-mode-switch { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.theme-mode-switch[hidden] { display: none; }
.theme-mode-btn {
  border: 0; background: var(--surface); color: var(--ink-soft); font: inherit; font-size: 12px; font-weight: 600;
  padding: 6px 14px; cursor: pointer;
}
.theme-mode-btn + .theme-mode-btn { border-left: 1px solid var(--border); }
.theme-mode-btn.is-active { background: var(--accent); color: #fff; }
/* generic segmented pill control (e.g. Text size). inline-flex + align-self:start so
   it only takes its content width (not the full grid cell). */
/* 3x3 position picker: nine cells; the active one fills with the accent. A dot in each
   cell hints at where the modal would sit. */
.pos-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; width: 108px; align-self: start; }
.pos-cell {
  aspect-ratio: 1 / 1; border: 1px solid var(--border-strong); border-radius: 5px; background: var(--surface);
  padding: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: border-color .12s, background .12s;
}
.pos-cell:hover { border-color: var(--accent); }
.pos-cell .pos-dot { width: 7px; height: 7px; border-radius: 999px; background: var(--border-strong); transition: background .12s; }
.pos-cell.is-active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
.pos-cell.is-active .pos-dot { background: var(--accent); }
.pos-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* the compact grid + its offset inputs on one row (offsets to the right of the grid,
   top-aligned with it). */
.pos-row { display: flex; align-items: flex-start; gap: 14px; }
.pos-offsets { display: flex; flex-direction: column; gap: 8px; flex: 1; min-width: 0; }
.pos-offset { display: flex; align-items: center; gap: 8px; }
.pos-offset > span { flex: none; width: 108px; font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.pos-offset[hidden] { display: none; }
/* a small px number needs only a compact input, not the full column width */
.pos-offset .radius-num { flex: none; width: 72px; text-align: center; }
/* the Animation select sits beside the compact position grid, so cap its width */
.field select[data-appearance-field="intro_animation"] { max-width: 200px; }
/* pinned to 41px (matches the inputs / font trigger) so the button line box does not land
   on a fractional height; box-sizing:border-box keeps the border inside the 41px. */
.seg-control { display: inline-flex; align-self: start; height: 41px; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.seg-btn {
  display: inline-flex; align-items: center;
  border: 0; background: var(--surface); color: var(--ink-soft); font: inherit; font-size: 13px; font-weight: 600;
  padding: 0 14px; cursor: pointer; white-space: nowrap;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--border); }
.seg-btn.is-active { background: var(--accent); color: #fff; }
/* predefined theme presets: a custom dropdown (trigger + popover). The trigger shows
   the current preset's mini-preview + name; the popover lists every preset (grouped
   Light / Dark) as a preview row. Native <select> can't render the swatch markup. */
.field-preset { position: relative; max-width: 280px; }
/* A preset control that does not apply to the current layout (e.g. Step indicator when
   the layout is Progress hero or Inline bar) is dimmed and non-interactive. */
.field-preset.is-disabled { opacity: .5; }
.field-preset.is-disabled .preset-select { pointer-events: none; }
.preset-select { position: relative; }
.preset-trigger {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 10px;
  border: 1px solid var(--border-strong); border-radius: 8px; background: var(--surface);
  color: var(--ink); font: inherit; cursor: pointer; text-align: left;
}
.preset-trigger:hover { border-color: var(--accent); }
.preset-select.is-open .preset-trigger { border-color: var(--accent); }
.preset-trigger-name { flex: 1; min-width: 0; font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.preset-trigger-caret { flex: none; color: var(--ink-faint); transition: transform .15s ease; }
.preset-select.is-open .preset-trigger-caret { transform: rotate(180deg); }
.preset-trigger:hover .preset-trigger-caret { color: var(--ink-soft); }
.preset-trigger-swatch { width: 40px; height: 26px; flex: none; }
/* the popover list */
.preset-menu {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 30;
  max-height: 320px; overflow-y: auto; padding: 6px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface);
  box-shadow: var(--shadow, 0 12px 32px rgba(15, 23, 42, .18));
}
.preset-menu-group-label { font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); padding: 8px 8px 4px; }
.preset-menu-group-label[hidden] { display: none; }
.preset-option {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 6px 8px;
  border: 0; border-radius: 7px; background: transparent; color: var(--ink-soft);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; text-align: left;
}
.preset-option:hover, .preset-option:focus-visible { background: var(--surface-2, rgba(0, 0, 0, .04)); outline: none; color: var(--ink); }
.preset-option-name { flex: 1; min-width: 0; }
.preset-option-check { flex: none; color: var(--accent); opacity: 0; }
.preset-option.is-selected .preset-option-check { opacity: 1; }
/* shared mini live preview: bg surface + an "Aa" + an accent bar (trigger + options) */
.preset-card-swatch {
  position: relative; width: 44px; height: 30px; flex: none; border-radius: 6px; overflow: hidden;
  border: 1px solid var(--border); display: flex; align-items: center; justify-content: center;
}
.preset-card-aa { font-size: 13px; font-weight: 700; line-height: 1; }
.preset-card-bar { position: absolute; left: 6px; right: 6px; bottom: 4px; height: 4px; border-radius: 999px; }
/* color fields: a Coloris-enhanced hex input (swatch button + hex-first popover).
   Coloris wraps the input in .clr-field and injects its own swatch button; we only
   size the hex text box and leave Coloris's swatch to its own stylesheet. */
/* Generic two-column grid of fields (was ".color-grid" - it started as the color-swatch
   row but is reused for every paired row in the appearance editor: radius+shadow,
   font+text-size, layout+width, launcher options, buttons, etc). */
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }
@media (max-width: 520px) {
.field-grid { grid-template-columns: 1fr; }
}
.field-grid .field { margin-bottom: 8px; }
.color-field { max-width: 125px; }
/* pin to 41px so the mono-font line box does not land on a fractional height (matches the
   other inputs / font trigger); box-sizing:border-box keeps the border inside the 41px. */
.color-field .color-hex { width: 100%; height: 41px; padding-top: 0; padding-bottom: 0; font-family: var(--font-mono, monospace); text-transform: lowercase; }
/* Coloris wraps the input and injects an absolutely-positioned swatch button. By
   default that button is 30px wide x full input height, which reads as a tall
   rounded rectangle. Constrain it to a small centered square and pad the input so
   the hex text never runs under it. */
.color-field .clr-field { display: block; }
.color-field .clr-field input { padding-right: 40px; }
.color-field .clr-field button {
  width: 22px; height: 22px; right: 8px; top: 50%; transform: translateY(-50%);
  border-radius: 5px; overflow: hidden;
}
.color-field .clr-field button::after { border-radius: 5px; }
/* corner radius: a slider that grows + a small number box, both editing one value.
   Custom track + thumb (not native accent-color) so the thumb sits FLUSH at the
   extreme start when value is 0. Native sliders inset the thumb by half its width
   at each end; styling it ourselves lets the thumb hug both ends. */
.radius-control { display: flex; align-items: center; gap: 10px; max-width: 280px; }
/* display:flex overrides the [hidden] attribute's default display:none - restore it so a
   hidden radius-control (e.g. the announcement custom-height slider) actually hides. */
.radius-control[hidden] { display: none; }
.radius-control.is-disabled { opacity: .5; }
.check-row-tight { margin-bottom: 6px; }
.radius-control .radius-slider {
  flex: 1; min-width: 0; cursor: pointer; -webkit-appearance: none; appearance: none;
  height: 16px; background: transparent; /* box holds the thumb; track drawn below */
  border: 0; padding: 0; border-radius: 0; /* undo the generic .field input box */
}
.radius-control .radius-slider:focus { outline: none; }
.radius-control .radius-slider:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
/* WebKit/Blink: track + thumb */
.radius-control .radius-slider::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px; background: var(--border);
}
.radius-control .radius-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 16px; height: 16px; margin-top: -5px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25); cursor: pointer;
}
/* Firefox: track + thumb */
.radius-control .radius-slider::-moz-range-track { height: 6px; border-radius: 999px; background: var(--border); }
.radius-control .radius-slider::-moz-range-thumb {
  width: 16px; height: 16px; border-radius: 50%; background: var(--accent);
  border: 2px solid var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .25); cursor: pointer;
}
.radius-control .radius-num { width: 76px; flex: none; text-align: center; }
/* keep the font select from stretching across the whole column */
.field-select-narrow select,
.field-select-narrow > input[type="text"] { max-width: 220px; }
/* Focus select: a touch wider than the narrow cap so its longer option text fits. */
.field-focus select { max-width: 280px; }
/* Merged tooltip trigger select: its options are long ("When another element is clicked"),
   so it gets a wider cap than the narrow default. */
.field-tt-trigger select { max-width: 320px; }
/* font preview dropdown: same shell as the preset dropdown; each option renders in
   its own typeface (set via CSSOM in the island). */
.field-font { position: relative; max-width: 220px; }
.font-select { position: relative; }
.font-trigger {
  display: flex; align-items: center; gap: 8px; width: 100%; min-height: 41px; padding: 0 12px;
  border: 1px solid var(--border-strong); border-radius: 8px; background: var(--surface);
  color: var(--ink); font: inherit; cursor: pointer; text-align: left;
}
.font-trigger:hover, .font-select.is-open .font-trigger { border-color: var(--accent); }
.font-trigger-name { flex: 1; min-width: 0; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.font-menu {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 30;
  max-height: 320px; overflow-y: auto; padding: 6px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface);
  box-shadow: var(--shadow, 0 12px 32px rgba(15, 23, 42, .18));
}
/* Search box pinned to the top of the font menu, stays put while options scroll. */
.font-search {
  position: sticky; top: -6px; z-index: 1;
  margin: -6px -6px 6px; padding: 6px;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.font-search-input {
  width: 100%; box-sizing: border-box; font: inherit; font-size: 13px;
  padding: 7px 10px; border: 1px solid var(--border); border-radius: 7px;
  background: var(--surface-2, rgba(0,0,0,.03)); color: var(--ink);
}
.font-search-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.font-option[hidden] { display: none; }
.font-search-none { padding: 10px 8px; font-size: 13px; color: var(--ink-faint); text-align: center; }
.font-search-none[hidden] { display: none; }
.font-option {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 8px;
  border: 0; border-radius: 7px; background: transparent; color: var(--ink);
  font: inherit; cursor: pointer; text-align: left;
}
.font-option:hover, .font-option:focus-visible { background: var(--surface-2, rgba(0, 0, 0, .04)); outline: none; }
.font-option-name { flex: 1; min-width: 0; font-size: 15px; }
.font-option .preset-option-check { flex: none; color: var(--accent); opacity: 0; }
.font-option.is-selected .preset-option-check { opacity: 1; }
/* overlay controls: color + opacity, dimmed when the dim is toggled off */
/* a distinct sub-section within the theme controls (e.g. Overlay): a top divider +
   a larger, stronger heading so it reads as its own group, not just another field. */
.field-section { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
/* The first section in a column has nothing above it to be separated FROM, so its rule would
   float under the preview as a stray line. Which section is first varies by flow type (Layout
   for a checklist, Colors & style for a survey), so this keys off position, not name. */
.appearance-controls > .field-section:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.field-section > .field-section-title { font-size: 15px; font-weight: 700; color: var(--ink); }
.field-section .control-hint { margin: 4px 0 10px; font-size: 12.5px; }
/* a lighter sub-grouping inside a section (e.g. Colors / Buttons / Card in the modal
   style panel) - a small uppercase label, no divider, so it sits under the parent. */
/* small uppercase sub-heading for a nested group inside a section (e.g. Card / Buttons
   in the Modal style panel). Sits inside a .field, so bump specificity over `.field span`
   (which would otherwise force 13px). No wrapper class needed - just .field + this title. */
.field > .field-subsection-title { font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
/* a separator line above a sub-group (e.g. Buttons follows Card in the Modal style panel). */
.field-subsection-sep { margin-top: 16px; padding-top: 32px; border-top: 1px solid var(--border); }
/* overlay: reuses .field-grid (2 columns) for Color | Opacity; dimmed when off */
.overlay-controls.is-disabled { opacity: .5; }
.overlay-opacity { max-width: 200px; }
/* layout dropdown: each option shows a SCALED-DOWN MINI TOOLTIP (fake title/body
   lines + the real footer chrome) so it reads as a thumbnail of the actual tooltip. */
.layout-select { max-width: 320px; }
/* card-style dropdown: each option shows a tiny wireframe of that card layout */
.card-select { max-width: 360px; }
/* The Layout preview is the full mini tooltip, so give the option row a fixed-width
   preview column and let the name+hint take the rest (keeps rows aligned). */
.card-option .layout-preview { flex: none; }
.card-option { align-items: center; gap: 12px; }
.card-option .preset-option-name { flex: 1; min-width: 0; }
.card-option .layout-option-check { opacity: 0; color: var(--accent); flex: none; }
.card-option.is-selected .layout-option-check { opacity: 1; }

/* checklist launcher Layout dropdown: a tiny launcher-panel mockup per option. */
.llm-select { max-width: 260px; }
.llm-option { align-items: center; gap: 12px; }
.llm-option .layout-preview { flex: none; }
.llm-option .preset-option-name { flex: 1; min-width: 0; }
.llm-option .layout-option-check { opacity: 0; color: var(--accent); flex: none; }
.llm-option.is-selected .layout-option-check { opacity: 1; }
/* fixed height so every layout's mini is the same size in the dropdown (shorter ones get
   white space above/below via centering); the launcher pill is pinned to the bottom. */
.llm { display: flex; flex-direction: column; align-items: flex-end; justify-content: center; gap: 4px; width: 74px; height: 74px; }
.llm-panel { width: 100%; background: var(--surface); border: 1px solid var(--border); border-radius: 5px; padding: 5px 6px; box-shadow: 0 2px 6px rgba(16,32,46,.1); }
.llm-header { display: flex; flex-wrap: wrap; align-items: center; gap: 3px 4px; margin-bottom: 4px; }
.llm-title-line { height: 4px; border-radius: 2px; background: var(--ink-soft); flex: 1 1 auto; min-width: 0; }
.llm-bar { height: 3px; flex-basis: 100%; border-radius: 999px; background: var(--accent); }
/* the ring DOT (distinct from the .llm-ring layout wrapper - avoid the name collision). */
.llm-ring-dot { flex: 0 0 auto; width: 12px; height: 12px; border-radius: 999px; position: relative; display: grid; place-items: center; background: conic-gradient(var(--accent) 40%, var(--border) 0); }
.llm-ring-dot::before { content: ""; position: absolute; width: 7px; height: 7px; border-radius: 999px; background: var(--surface); }
/* the ring LAYOUT has no bar, so its header is a single non-wrapping row (dot + title). */
.llm.llm-ring .llm-header { flex-wrap: nowrap; }
.llm-body { display: flex; flex-direction: column; gap: 4px; }
.llm-item { display: flex; align-items: flex-start; gap: 4px; }
.llm-check { flex: none; width: 7px; height: 7px; border-radius: 999px; border: 1.5px solid var(--border-strong); box-sizing: border-box; margin-top: 1px; }
.llm-check.on { background: var(--accent); border-color: var(--accent); }
.llm-item-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.llm-line { height: 3px; border-radius: 2px; background: var(--ink-faint); width: 100%; }
.llm-line.short { width: 60%; }
/* subtext line under a task (shown for the roomy layouts; hidden for compact). */
.llm-sub { height: 2px; width: 72%; border-radius: 2px; background: var(--border-strong); }
.llm-launcher { width: 40px; height: 11px; border-radius: 999px; background: var(--accent); }
/* variant tweaks in the mini */
/* COMPACT: no subtext + tighter rows, so it reads visibly denser than Default. */
.llm-compact .llm-sub { display: none; }
.llm-compact .llm-body { gap: 2px; }
.llm-compact .llm-bar { margin: 0; }
.llm-banner .llm-header { background: var(--accent); margin: -5px -6px 4px; padding: 5px 6px; border-radius: 5px 5px 0 0; }
.llm-banner .llm-title-line { background: color-mix(in srgb, #fff 82%, transparent); }
.llm-banner .llm-bar { background: color-mix(in srgb, #fff 60%, transparent); }
.llm-tiles .llm-body { gap: 4px; }
.llm-tiles .llm-item { background: var(--surface-2); border: 1px solid var(--border); border-radius: 3px; padding: 3px 4px; }
/* every two-column field row gets a small top gap (was scoped to the launcher section). */
.field-grid { margin-top: 5px; }
/* Welcome-modal Layout dropdown: a tiny modal thumbnail per option (standard = image
   banner on top + text, split_media = image column beside the text). */
.mm-wrap { flex: none; width: 54px; height: 44px; border: 1px solid var(--line); border-radius: 6px; background: var(--paper, #fff); overflow: hidden; padding: 5px; display: flex; }
.preset-trigger .mm-wrap { width: 46px; height: 38px; }
.mm { flex: 1; display: flex; flex-direction: column; gap: 3px; justify-content: center; align-items: center; }
.mm-l { height: 3px; border-radius: 999px; background: var(--border-strong); }
.mm-l1 { width: 70%; height: 4px; background: var(--ink-faint); }
.mm-l2 { width: 85%; }
.mm-btn { width: 40%; height: 5px; border-radius: 2px; background: var(--accent); margin-top: 2px; }
/* the banner spans full width even though the mini centers its text lines + button */
.mm-img { width: 100%; align-self: stretch; height: 13px; border-radius: 3px; background: linear-gradient(120deg, var(--accent), #0ea5b7); margin-bottom: 2px; }
/* banner: banner on top, text lines left-aligned, button pushed to the right edge */
.mm-banner { align-items: stretch; }
.mm-banner .mm-l { align-self: flex-start; }
.mm-banner .mm-btn { align-self: flex-end; }
/* split variants: an image column beside the text. split_media insets the image (gap +
   rounded); split_full bleeds it to the edges; split_right puts the image on the right. */
.mm-split_media, .mm-split_full, .mm-split_right { flex-direction: row; align-items: stretch; gap: 4px; }
.mm-side { width: 14px; border-radius: 3px; background: linear-gradient(150deg, #f2683c, var(--accent)); flex: none; }
.mm-col { flex: 1; display: flex; flex-direction: column; gap: 3px; justify-content: center; align-items: flex-start; }
.mm-split_media .mm-btn, .mm-split_full .mm-btn, .mm-split_right .mm-btn { width: 60%; }
/* full-bleed variants: the wrap has 5px padding, so pull the image out to the edges. */
.mm-split_full, .mm-split_right { gap: 6px; }
.mm-split_full .mm-side { margin: -5px 0 -5px -5px; border-radius: 3px 0 0 3px; width: 16px; }
.mm-split_right .mm-side { margin: -5px -5px -5px 0; border-radius: 0 3px 3px 0; width: 16px; }
.layout-option { align-items: center; gap: 12px; }
.layout-option .preset-option-name { flex: 1; min-width: 0; }
.layout-option .layout-option-check { opacity: 0; color: var(--accent); flex: none; }
.layout-option.is-selected .layout-option-check { opacity: 1; }
/* The layout preview shows the WHOLE mini tooltip (a light-bordered dialog), fully
   visible. Built small directly (no transform/clip) so it never overflows and needs
   no fixed-height container. */
.layout-preview { flex: none; display: flex; align-items: center; justify-content: center; }
.lp-tip {
  flex: none; width: 124px; box-sizing: border-box; padding: 8px 9px;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface);
  box-shadow: 0 3px 10px rgba(15, 23, 42, .08);
  display: flex; flex-direction: column; gap: 4px;
}
/* Card-style variants of the mini tooltip (the Layout dropdown). Each shows the
   whole dialog laid out the way that card style renders a step. Fake image/media
   uses the same accent gradients as the runtime card slots. */
/* Every Layout preview is the SAME fixed box (width from .lp-tip, height here) so the
   trigger + option rows never shift as the card style changes. The footer is pinned to
   the bottom; content fills the space above it. */
[data-card-select] .lp-tip-card { height: 78px; overflow: hidden; }
[data-card-select] .lp-tip-card .lp-tip-foot { margin-top: auto; }
.lp-tip-card.lp-tip-media_top { padding-top: 0; padding-left: 0; padding-right: 0; }
.lp-tip-card.lp-tip-media_top > .lp-line,
.lp-tip-card.lp-tip-media_top > .lp-tip-foot { margin-left: 9px; margin-right: 9px; }
.lp-media { height: 26px; border-radius: 6px 6px 0 0; background: linear-gradient(120deg, var(--accent), #0ea5b7); margin-bottom: 2px; }
/* split_media mini: the image is a FULL-HEIGHT column flush to the left edge (top to
   bottom), and the text + footer stack in a column to its right. So the card is a ROW
   (image | right-column) with no padding, the image stretches the whole height, and the
   right column carries its own padding + the footer at the bottom. */
.lp-tip-card.lp-tip-split_media { padding: 0; flex-direction: row; align-items: stretch; gap: 0; overflow: hidden; }
.lp-tip-split_media .lp-split-media { width: 34px; flex: none; align-self: stretch; background: linear-gradient(150deg, #f2683c, var(--accent)); }
.lp-tip-split_media .lp-split-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; padding: 8px 9px; }
/* the footer sits at the bottom of the right column, pushed down by the text above it. */
.lp-tip-split_media .lp-split-text .lp-tip-foot { margin-top: auto; }
.lp-persona { display: flex; align-items: center; gap: 5px; margin-bottom: 1px; }
.lp-av { width: 14px; height: 14px; border-radius: 999px; background: var(--accent); flex: none; }
.lp-pname { width: 40%; margin: 0; }
/* chat: the body lines sit inside a speech bubble (asymmetric corners), mirroring the
   runtime's .onbixo-card-chat .onbixo-tip-text, so it reads clearly different from persona. The
   bubble takes most of the tooltip width and its lines fill it, so it reads as a solid
   message block (not a thin sliver sized to the shortest line). */
.lp-chat-bubble { display: flex; flex-direction: column; gap: 4px; align-self: stretch; width: 100%; box-sizing: border-box; padding: 6px 8px; border-radius: 3px 9px 9px 9px; background: color-mix(in srgb, var(--ink) 7%, var(--surface-2)); }
.lp-chat-bubble .lp-line { width: 100%; }
.lp-chat-bubble .lp-line-short { width: 62%; }
.lp-num { font-family: var(--font-mono, monospace); font-size: 20px; font-weight: 700; line-height: .9; color: color-mix(in srgb, var(--accent) 45%, var(--border-strong)); margin-bottom: 1px; }
.lp-hero { height: 6px; border-radius: 999px; background: var(--border-strong); overflow: hidden; margin-bottom: 2px; }
.lp-hero-fill { display: block; width: 66%; height: 100%; background: var(--accent); border-radius: 999px; }
/* inline: a compact single-row bar - title fills the left, action on the right, framed
   so it reads as one self-contained strip (mirrors the runtime's onbixo-card-inline row). */
.lp-inline { display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box; padding: 7px 9px; border: 1px solid var(--border); border-radius: 7px; background: var(--surface-2); }
.lp-inline .lp-line-title { flex: 1; width: auto; margin: 0; }
/* inline has no footer, so center its single row in the fixed-height box */
[data-card-select] .lp-tip-inline { justify-content: center; }
/* accent bar (barcard): the same framed single row as inline (it extends .lp-inline),
   plus a round leading icon. The accent CTA comes from .lp-nav-accent. NB: the class is
   .lp-barcard, NOT .lp-bar - .lp-bar is already a step-indicator segment (see below). */
.lp-barcard .lp-barcard-ic { width: 11px; height: 11px; border-radius: 50%; flex: none; background: var(--accent); }
/* bar has no footer, so center its single row in the fixed-height box (like inline) */
[data-card-select] .lp-tip-bar { justify-content: center; }
/* same size in the trigger as in the options, so the footer never re-wraps */
/* the Button-style and Step-indicator dropdowns show JUST their element (no tooltip
   frame), centered in an isolated pill of the same height/width. Only the Layout
   dropdown renders the whole mini tooltip. */
.layout-preview-in { display: flex; align-items: center; justify-content: center; padding: 0 8px; }
.lp-none { font-size: 12px; font-weight: 600; color: var(--ink-faint); }
[data-prog-select] .layout-preview,
[data-nav-select] .layout-preview { min-width: 84px; height: 30px; border-radius: 6px; background: var(--surface-2); }
/* Fixed width for the nav pill so Buttons (text Back/Next) and Arrows (chevrons)
   render at exactly the same size in both the trigger and the options. */
[data-nav-select] .layout-preview { width: 84px; }
[data-nav-select] .layout-preview-in { padding: 0 6px; width: 100%; box-sizing: border-box; }
/* Scale the nav buttons down so two of them sit comfortably in the isolated pill. */
[data-nav-select] .lp-nav { gap: 4px; }
[data-nav-select] .lp-nav-btn { font-size: 9px; font-weight: 600; padding: 3px 5px; border-radius: 4px; }
[data-nav-select] .lp-nav-chev { padding: 1px 2px; }
[data-nav-select] .lp-nav-chev svg { width: 11px; height: 11px; }
/* fake placeholder text lines (title = darker + wider, body lines lighter) */
/* fake placeholder lines in the mini tooltip */
.lp-line { height: 4px; border-radius: 999px; background: var(--border-strong); }
.lp-line-title { height: 5px; width: 62%; background: var(--ink-faint); }
.lp-line-short { width: 45%; }
.lp-tip-foot { display: flex; align-items: center; gap: 5px; margin-top: 3px; white-space: nowrap; }
.lp-prog { display: flex; align-items: center; flex: none; }
.lp-nav { display: flex; align-items: center; gap: 3px; flex: none; }
.lp-spacer { flex: 1; }
.lp-tip .lp-count { white-space: nowrap; }
/* footer elements INSIDE the mini tooltip are extra small so the whole thing fits */
.lp-tip .lp-count { font-size: 6.5px; }
.lp-tip .lp-dot { width: 3px; height: 3px; }
.lp-tip .lp-seg-dots, .lp-tip .lp-seg-bars { gap: 3px; }
.lp-tip .lp-nav-btn { font-size: 6.5px; padding: 2.5px 4px; border-radius: 3px; }
.lp-tip .lp-nav-chev { padding: 1px 1px; }
.lp-tip .lp-nav-chev svg { width: 8px; height: 8px; }
.lp-seg-dots, .lp-seg-bars { display: flex; align-items: center; gap: 5px; }
.lp-dot { width: 6px; height: 6px; border-radius: 999px; background: var(--border-strong); flex: none; }
.lp-dot.is-on { background: var(--accent); }
.lp-bar { width: 14px; height: 4px; border-radius: 999px; background: var(--border-strong); flex: none; }
.lp-bar.is-on { background: var(--accent); }
.lp-progbar { width: 60px; height: 5px; border-radius: 999px; background: var(--border-strong); overflow: hidden; }
.lp-progbar-fill { display: block; width: 66%; height: 100%; background: var(--accent); border-radius: 999px; }
.lp-count { font-size: 12px; font-weight: 600; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.lp-nav-btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 600; padding: 5px 10px; border-radius: 6px;
  border: 1px solid var(--border-strong); color: var(--ink-soft); line-height: 1;
}
.lp-nav-btn.lp-nav-accent { background: var(--accent); border-color: var(--accent); color: #fff; }
.lp-nav-chev { display: inline-flex; align-items: center; justify-content: center; padding: 3px 4px; color: var(--ink-faint); }
.lp-nav-chev.lp-nav-chev-accent { color: var(--accent); }
.lp-nav-chev svg { display: block; }
/* modal style panel (revealed by the "style separately" toggle) */
.modal-style-panel { display: flex; flex-direction: column; margin-top: 10px; }
.modal-style-panel[hidden] { display: none; }
/* image field: url input + upload button + hidden file picker */
.image-field { display: flex; align-items: center; gap: 8px; }
.image-field input[type="url"] { flex: 1; min-width: 0; }
.image-field .btn { flex: none; }
/* image picker field: a thumbnail on the left, and a stacked column (image name on top,
   the Select/Change button below). No visible URL input (it is a hidden field the dialog
   manages). */
.image-picker { display: flex; align-items: center; gap: 12px; }
.image-picker-thumb {
  flex: none; width: 52px; height: 52px; border-radius: 9px; border: 1px solid var(--border-strong);
  background: var(--surface-2) center / cover no-repeat; cursor: pointer; position: relative;
}
.image-picker-thumb-round { border-radius: 999px; }
/* a faint image glyph when empty; hidden once an image is set */
.image-picker-thumb::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; background: no-repeat center / 22px 22px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%237a8296' stroke-width='2'%3E%3Crect x='3' y='3' width='18' height='18' rx='2'/%3E%3Ccircle cx='9' cy='9' r='1.5'/%3E%3Cpath d='M21 15l-5-5L5 21'/%3E%3C/svg%3E");
  opacity: .55;
}
.image-picker.has-img .image-picker-thumb::after { display: none; }
.image-picker-body { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; }
.image-picker-name {
  font-size: 13px; color: var(--ink-soft); cursor: pointer; max-width: 260px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.image-picker.has-img .image-picker-name { color: var(--ink); font-weight: 500; }
.image-picker-btn { flex: none; align-self: flex-start; }
.image-picker-actions { display: flex; gap: 10px; align-items: center; }
/* Clear is a quiet, muted secondary action - no border/background, faint text. */
.image-picker-clear {
  border: none; background: none; padding: 0;
  color: var(--ink-faint); font-size: 12.5px; cursor: pointer;
}
.image-picker-clear:hover { color: var(--crit); text-decoration: underline; }
.image-picker-clear[hidden] { display: none; }
/* the currently-in-use library image, highlighted in the dialog grid */
.media-card.is-selected { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent) inset; }
/* Both appearance panels stack the preview FIRST (full-width, on top) so it reads better -
   the tour tooltip and the welcome modal both show above their controls. */
.appearance-grid { display: grid; grid-template-columns: 1fr; gap: 20px; align-items: start; }
.appearance-grid .appearance-preview { order: -1; align-items: center; padding: 26px 20px; }
.appearance-grid .appearance-controls { order: 0; }
.appearance-grid .preview-label { align-self: flex-start; }
.appearance-grid .preview-head { align-self: stretch; }
/* The welcome modal is a centered overlay, so its preview reads best STACKED and shown
   FIRST: the full-width preview on top, then the settings below it. `order` flips the
   two grid items without reordering the DOM (keeps the island's element lookups intact). */
.appearance-grid-modal { grid-template-columns: 1fr; }
.appearance-grid-modal .appearance-preview { order: -1; }
.appearance-grid-modal .appearance-controls { order: 0; }
@media (max-width: 900px) {
.appearance-grid { grid-template-columns: 1fr; }
}
/* No flex gap between sections: each .field-section carries its own top margin + separator
   border, so a gap would double-space them. Non-section children keep their own margins. */
.appearance-controls { display: flex; flex-direction: column; }
.appearance-preview {
  border: 1px solid var(--border); border-radius: 10px; padding: 16px; background: var(--surface-2);
  display: flex; flex-direction: column; gap: 10px;
}
/* Previewing the DARK theme: darken the preview pane so the flow shows on a dark backdrop.
   The island also sets the exact bg from the theme (inline) - this is the CSS fallback. */
[data-editing-mode="dark"] .appearance-preview { background: #0f1420; border-color: #232d3d; }
[data-editing-mode="dark"] .appearance-preview .preview-label { color: #6b788c; }
.preview-label { font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
/* TEMPORARY comparison box (refactor step 1 of 2): hosts the REAL runtime rendered in
   `contained` mode, next to the look-alike preview, so the two can be compared before the
   look-alike is deleted. position:relative anchors the runtime's absolutely-positioned host. */
.runtime-preview-compare { margin-top: 18px; display: flex; flex-direction: column; gap: 8px; width: 100%; align-self: stretch; }
.runtime-preview-box {
  /* min-height fits the TALLEST card (media_top: ~140px banner + content + footer). The
     runtime host inside is position:absolute, so the box cannot size to its content - too
     small a floor clipped the card, since the host also has overflow:hidden. */
  position: relative; width: 100%; align-self: stretch; min-height: 340px;
  border-radius: var(--radius); background: transparent; overflow: hidden;
}
/* Checklist variant: the widget anchors its launcher to bottom:20px and its panel to
   bottom:74px, so the box needs room for the panel to open upward above the launcher.
   Tall enough for a typical 5-item panel at full height - the panel is NOT capped, so it
   renders naturally like the static preview instead of scrolling inside itself. */
.runtime-preview-box-cl { min-height: 620px; }
/* preview header: the "Preview" label on the left, the step navigator on the right. */
.preview-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; }
.preview-stepper { display: inline-flex; align-items: center; gap: 6px; }
.preview-stepper[hidden] { display: none; }
.preview-step-count { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--ink-soft); min-width: 34px; text-align: center; }
.preview-step-btn {
  width: 24px; height: 24px; flex: none; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface); color: var(--ink-soft);
  font-size: 15px; line-height: 1; cursor: pointer;
}
.preview-step-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--ink); }
.preview-step-btn:disabled { opacity: .4; cursor: default; }
/* sample welcome modal (intro preview) - now a full-width stage below the controls, with
   the modal itself centered at a realistic width (it renders as a centered overlay). */
.appearance-grid-modal .appearance-preview { align-items: center; padding: 28px 20px; }
.appearance-grid-modal .preview-label { align-self: flex-start; }
/* Position preview: align the modal left / center / right within the stage via the modal's
   OWN align-self (a width:100% flex item ignores the container's align-items, so it must be
   set on the item). Vertical top/bottom is a runtime viewport concept that does not map to
   this short stage, so the preview shows the horizontal placement only. Default = center. */
.appearance-grid-modal .modal-preview { width: 100%; max-width: 480px; align-self: center; }
.modal-preview {
  position: relative; background: var(--surface); border: 1px solid var(--border);
  padding: 20px; box-shadow: var(--shadow); text-align: center; width: 100%;
  overflow: hidden;
}
.modal-preview-x {
  position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; padding: 0;
  border: none; background: none; color: var(--ink-faint); font-size: 18px; line-height: 1; cursor: default;
}
.modal-preview-img { width: 100%; max-width: 100%; object-fit: cover; border-radius: 8px; margin-bottom: 12px; }
.modal-preview-title { font-weight: 700; font-size: 16px; color: var(--ink); margin-bottom: 8px; }
.modal-preview-body { font-size: 13px; color: var(--ink-soft); line-height: 1.5; margin-bottom: 14px; }
.modal-preview-cta { border: none; color: #fff; font-size: 13.5px; font-weight: 600; padding: 8px 20px; cursor: default; }
/* actions row: CTA + optional secondary "Not now" dismiss. With a dismiss button the
   two sit side by side (CTA auto-width); without it the CTA fills the row. */
/* Standard: the actions (optional "Not now" + CTA) are auto-width and centered under the
   text, matching the centered modal. Banner/Split override the alignment below. */
.modal-preview-actions { display: flex; align-items: center; gap: 10px; justify-content: center; }
.modal-preview-actions .modal-preview-cta { width: auto; }
.modal-preview-dismiss { border: none; background: none; font-size: 13.5px; font-weight: 600; padding: 8px 6px; cursor: default; color: var(--ink-soft); }
.modal-preview-dismiss[hidden] { display: none; }
/* Welcome-modal layouts in the preview (mirror the runtime's showModal):
   banner = image banner + left-aligned text with the CTA pinned bottom-right;
   split_media = image column beside the text via a 2-col grid (image spans the text
   rows). standard keeps the image as a top banner - the centered default. */
.modal-preview.ip-modal-banner { text-align: left; }
/* full-bleed banner: break the image out of the modal's 20px padding (top + sides),
   square its top corners, and drop the extra gap so it reads as an edge-to-edge banner. */
.modal-preview.ip-modal-banner .modal-preview-img { width: calc(100% + 40px); max-width: none; margin: -20px -20px 16px; border-radius: 0; display: block; }
/* banner: the actions (Not now + Get started) group together at the right, with the
   dismiss just before the CTA, instead of the CTA filling the row. */
.modal-preview.ip-modal-banner .modal-preview-actions { justify-content: flex-end; }
.modal-preview.ip-modal-banner .modal-preview-actions .modal-preview-cta { width: auto; margin-left: 0; }
.modal-preview.ip-modal-split { display: grid; grid-template-columns: 120px 1fr; column-gap: 16px; align-items: center; text-align: left; }
.modal-preview.ip-modal-split .modal-preview-img { grid-column: 1; grid-row: 1 / span 3; height: 100%; object-fit: cover; margin-bottom: 0; align-self: stretch; border-radius: 8px; }
.modal-preview.ip-modal-split .modal-preview-title { grid-column: 2; }
.modal-preview.ip-modal-split .modal-preview-body { grid-column: 2; }
.modal-preview.ip-modal-split .modal-preview-actions { grid-column: 2; justify-content: flex-start; }
.modal-preview.ip-modal-split .modal-preview-actions .modal-preview-cta { width: auto; }
/* split_full: the image column bleeds to the top/left/bottom edges (out of the 20px pad),
   square inner corner, and takes the column gap so the text starts flush. The image fills
   its own grid cell exactly (width:100%; max-width:none) - only the OUTER edge bleeds via a
   negative margin, so it never overflows the (overflow:hidden) container. */
.modal-preview.ip-modal-split-full { column-gap: 20px; }
/* Bleed the image PAST the padding on its outer edge: width = column + 20px pad, pulled
   out with a negative margin. overflow:hidden clips the overshoot, guaranteeing a flush
   edge with no leftover gap regardless of exact grid rounding. */
.modal-preview.ip-modal-split-full .modal-preview-img { width: calc(100% + 20px); max-width: none; margin: -20px 0 -20px -20px; height: calc(100% + 40px); border-radius: 0; }
/* split_right: text on the LEFT, image full-bleed on the RIGHT (columns swapped). */
.modal-preview.ip-modal-split-right { grid-template-columns: 1fr 120px; column-gap: 20px; }
.modal-preview.ip-modal-split-right .modal-preview-img { grid-column: 2; width: calc(100% + 20px); max-width: none; margin: -20px -20px -20px 0; height: calc(100% + 40px); border-radius: 0; }
.modal-preview.ip-modal-split-right .modal-preview-title,
.modal-preview.ip-modal-split-right .modal-preview-body,
.modal-preview.ip-modal-split-right .modal-preview-actions { grid-column: 1; }
/* ---- Live preview overlay (the "Live preview" button): plays the modal full-screen on
   the editor page with its real position + animation. The card is a clone of the styled
   .modal-preview, so it needs only backdrop + placement + motion here. ---- */
.modal-live-btn { align-self: center; margin-top: 8px; display: inline-flex; align-items: center; gap: 7px; }
.modal-live-btn svg { flex: none; }
.modal-live-back {
  position: fixed; inset: 0; z-index: 1000; display: flex; padding: 40px; box-sizing: border-box;
  background: rgba(15, 23, 42, .55); opacity: 0; transition: opacity .2s ease;
  align-items: center; justify-content: center;
}
.modal-live-back.is-shown { opacity: 1; }
.modal-live-card { width: 100%; max-width: 460px; box-shadow: 0 24px 64px rgba(15, 23, 42, .32); }
.modal-live-card .modal-preview-x, .modal-live-card .modal-preview-cta,
.modal-live-card .modal-preview-dismiss { cursor: pointer; }
/* position within the viewport (mirrors the runtime's onbixo-pos-*) */
.modal-live-back.mlp-pos-top { align-items: flex-start; }
.modal-live-back.mlp-pos-bottom { align-items: flex-end; }
.modal-live-back.mlp-pos-left { justify-content: flex-start; }
.modal-live-back.mlp-pos-right { justify-content: flex-end; }
.modal-live-back.mlp-pos-top_left { align-items: flex-start; justify-content: flex-start; }
.modal-live-back.mlp-pos-top_right { align-items: flex-start; justify-content: flex-end; }
.modal-live-back.mlp-pos-bottom_left { align-items: flex-end; justify-content: flex-start; }
.modal-live-back.mlp-pos-bottom_right { align-items: flex-end; justify-content: flex-end; }
/* entry/exit animation (mirrors the runtime's onbixo-anim-*): the card rests at
   transform:none/opacity:1 on .is-shown; each animation sets a different START. */
.modal-live-card { transition: transform .22s ease, opacity .22s ease; }
.modal-live-back.mlp-anim-fade .modal-live-card { transform: translateY(8px) scale(.98); opacity: 0; }
.modal-live-back.mlp-anim-slide_up .modal-live-card { transform: translateY(28px); opacity: 0; }
.modal-live-back.mlp-anim-slide_down .modal-live-card { transform: translateY(-28px); opacity: 0; }
.modal-live-back.mlp-anim-scale .modal-live-card { transform: scale(.9); opacity: 0; }
.modal-live-back.mlp-anim-none .modal-live-card { transform: none; opacity: 1; transition: none; }
.modal-live-back.is-shown .modal-live-card { transform: none; opacity: 1; }

/* ---- Theme "Live preview": a simulated app (faux chrome + dim + spotlight) with the
   themed tooltip attached to a mock target, so the full tour experience is visible. ---- */
.tip-live-btn { align-self: center; margin-top: 8px; display: inline-flex; align-items: center; gap: 7px; }
.tip-live-btn svg { flex: none; }
/* the stage is a true full-screen overlay: the faux app fills the whole viewport so the
   spotlight dim covers everything and the real editor page never shows through. */
.tps-back { position: fixed; inset: 0; z-index: 3000; opacity: 0; transition: opacity .2s ease; }
.tps-back.is-shown { opacity: 1; }
/* A corner-placed widget (a survey) lands under the stage's own close button. Move the close
   button left so both stay reachable rather than stacked on each other. */
.tps-back.tps-has-corner-widget .tps-close { right: auto; left: 16px; }
.tps-app { position: absolute; inset: 0; display: flex; overflow: hidden;
  background: var(--surface); font-family: system-ui, sans-serif; }
.tps-side { flex: none; width: 220px; background: color-mix(in srgb, var(--ink) 5%, var(--surface)); border-right: 1px solid var(--border);
  padding: 22px 16px; display: flex; flex-direction: column; gap: 6px; }
.tps-logo { width: 36px; height: 36px; border-radius: 8px; background: linear-gradient(135deg, #6366f1, #0ea5b7); margin-bottom: 14px; }
.tps-nav-item { padding: 9px 12px; border-radius: 7px; font-size: 14px; font-weight: 600; color: var(--ink-soft); }
.tps-nav-item.is-2 { background: color-mix(in srgb, var(--ink) 8%, var(--surface)); color: var(--ink); }
.tps-main { flex: 1; min-width: 0; padding: 26px 32px; }
.tps-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 26px; }
.tps-title { font-size: 24px; font-weight: 750; color: var(--ink); }
.tps-new { border: none; background: #4f46e5; color: #fff; font-size: 14px; font-weight: 650; padding: 10px 18px; border-radius: 8px; cursor: default; }
.tps-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.tps-card { height: 180px; border-radius: 12px; border: 1px solid var(--border); background: color-mix(in srgb, var(--ink) 3%, var(--surface)); }
/* Dark mode: when previewing the flow's dark theme, render the mock app dark too so the
   flow is shown in a realistic dark-app context (matches the selected theme). */
.tps-dark .tps-app { background: #0f1420; }
.tps-dark .tps-side { background: #141b29; border-right-color: #232d3d; }
.tps-dark .tps-nav-item { color: #94a3b8; }
.tps-dark .tps-nav-item.is-2 { background: #1e2635; color: #e6edf6; }
.tps-dark .tps-title { color: #e6edf6; }
.tps-dark .tps-card { background: #141b29; border-color: #232d3d; }
/* fixed, toast-style close button in the top-right of the viewport */
.tps-close { position: fixed; top: 16px; right: 18px; z-index: 5; width: 34px; height: 34px; border: none;
  border-radius: 999px; background: rgba(15, 23, 42, .55); color: #fff; font-size: 20px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 14px rgba(15, 23, 42, .3); }
.tps-close:hover { background: rgba(15, 23, 42, .78); }
/* a small fixed hint chip so it reads as a preview, with the close */
.tps-badge { position: fixed; top: 18px; left: 18px; z-index: 5; font-size: 12px; font-weight: 600; letter-spacing: .03em;
  text-transform: uppercase; color: #fff; background: rgba(15, 23, 42, .55); padding: 6px 12px; border-radius: 999px; box-shadow: 0 4px 14px rgba(15, 23, 42, .3); }
/* "not simulated" note for delay/scroll/click/manual triggers: bottom-center, subtle. */
.tps-trigger-note { position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%); z-index: 5;
  max-width: min(640px, calc(100vw - 40px)); text-align: center; font-size: 12.5px; color: #fff;
  background: rgba(15, 23, 42, .62); padding: 8px 16px; border-radius: 10px; box-shadow: 0 4px 14px rgba(15, 23, 42, .3); }
.tps-trigger-note[hidden] { display: none; }
/* Dashboard-only caption naming the mock target for a hover/click TRIGGER step (positioned by
   the stage JS just below the target, inside .tps-app). The runtime shows the pulsing hotspot;
   this says what to do with it. */
.tps-trigger-hint {
  position: absolute; z-index: 4; pointer-events: none; white-space: nowrap;
  padding: 6px 12px; border-radius: 8px; font-size: 12px; font-weight: 600;
  background: var(--ink); color: var(--surface); box-shadow: 0 4px 14px rgba(0,0,0,.22);
  animation: tps-hint-bob 1.6s ease-in-out infinite;
}
.tps-trigger-hint[hidden] { display: none; }
/* a small pointer on top of the pill, aimed up at the target/hotspot above it */
.tps-trigger-hint::before {
  content: ""; position: absolute; top: -5px; left: 50%; transform: translateX(-50%);
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-bottom: 5px solid var(--ink);
}
@keyframes tps-hint-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }
@media (prefers-reduced-motion: reduce) { .tps-trigger-hint { animation: none; } }
@keyframes tps-pulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent); } 70% { box-shadow: 0 0 0 12px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
@keyframes tps-hint-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }
/* checklist item editor */
.checklist-editor .panel + .panel { margin-top: 16px; }
.item-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.item-row {
  display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2);
}
.item-row.is-dragging { opacity: .5; }
.item-row.is-drop-target { border-color: var(--accent); }
.item-handle { cursor: grab; color: var(--ink-faint); line-height: 1.8; user-select: none; }
.item-main { flex: 1; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.item-main .item-title {
  height: 34px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink); font-size: 14px;
}
/* optional per-item subtext - smaller + muted, sits just under the title input. */
.item-main .item-subtext {
  height: 30px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink-soft); font-size: 12.5px;
}
.item-done-row { display: flex; align-items: center; gap: 8px; }
.item-done-label { font-size: 12.5px; color: var(--ink-soft); }
.item-done {
  height: 30px; padding: 0 8px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink); font-size: 13px;
}
/* The themed combobox replaces this select at runtime and must keep its compact 30px pill, not the
   base .ss-btn metrics (41px, sized for a full-width .field input). min-height needs resetting too:
   a min-height of 41px would win over a 30px height. */
.ss-wrap.item-done-wrap { display: inline-flex; position: relative; }
.ss-wrap.item-done-wrap .ss-btn {
  height: 30px; min-height: 0; width: auto; padding: 0 24px 0 8px;
  border-radius: 6px; border-color: var(--border); font-size: 13px;
}
.ss-wrap.item-done-wrap .ss-caret { position: absolute; right: 7px; top: 50%; margin-top: -4px; }
.ss-wrap.item-done-wrap .ss-pop { min-width: max(100%, 200px); max-width: min(300px, 85vw); }
/* Per-item "done when" contextual controls (click selector / api snippet). */
.item-done-details { display: flex; flex-direction: column; gap: 6px; margin-top: 2px; }
.item-done-details:empty { display: none; }
/* The captured-target readout sits INSIDE the item card, which already uses --surface-2,
   so tint it a touch darker than the card (a soft inset grey, not a bright white box) with
   a slightly stronger border so it reads as a distinct panel. (The same .editor-target
   block sits on a plain form elsewhere and keeps its default surface.) */
.item-done-details .editor-target {
  background: color-mix(in srgb, var(--ink) 4%, var(--surface-2));
  border-color: color-mix(in srgb, var(--ink) 12%, var(--border));
  gap: 5px; padding: 11px 13px; margin: 2px 0 0;
}
.item-selector-field { display: flex; flex-direction: column; gap: 5px; }
.item-selector-label { font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.item-selector {
  height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink); font-size: 13px; font-family: var(--mono); width: 100%;
}
.item-empty { margin: 8px 0 0; }
/* dark mode: the surface/track contrast carries the raised look; soften the shadow
   and add a hairline so the active pill stays crisp on a dark track. */
:root[data-theme="dark"] .tab-bar .tab.is-active {
  box-shadow: 0 1px 2px rgba(0,0,0,.4);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .tab-bar .tab.is-active { box-shadow: 0 1px 2px rgba(0,0,0,.4); }
}
@media (prefers-reduced-motion: reduce) {
.tab-bar .tab { transition: none; }
}

/* frequency / re-show controls */
/* "When it appears" (trigger) + "Conversion goal" sit side by side on the Targeting tab.
   Two equal columns that stack on narrow screens. Each child panel keeps its own styling. */
.trigger-goal-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: stretch; }
.trigger-goal-row > .panel { margin: 0; height: 100%; }
@media (max-width: 900px) { .trigger-goal-row { grid-template-columns: 1fr; } }

/* Conversion goal editor. Reuses .field / .field-grid; just needs vertical rhythm between
   the type select and the conditional fields. */
.goal-editor { display: flex; flex-direction: column; gap: 16px; }
/* [hidden] must beat .field-grid's display:grid / .field's block (documented codebase trap),
   so a hidden goal sub-field actually disappears. */
.goal-editor .goal-field[hidden] { display: none; }
.goal-editor .field-hint code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 4px; padding: 1px 4px;
}

.frequency { /* lives in its own tab panel now */ }
.frequency-title { font-size: 14px; font-weight: 600; margin: 0 0 10px; color: var(--ink); }
/* clear separation between the "Show again" and "Stop showing after" sections */
.frequency-title + .freq-modes { margin-bottom: 28px; }
.freq-modes, .freq-stops { display: flex; flex-direction: column; gap: 4px; }
/* each option is a full-width row: comfortable padding, rounded, hover highlight,
   and a faint accent tint when selected - same radio/checklist format, polished. */
.radio-row, .freq-stops .check-row {
  display: inline-flex; align-self: flex-start; width: auto;
  align-items: center; gap: 10px; margin: 0; min-height: 40px;
  padding: 6px 12px; border-radius: 8px; border: 1px solid transparent;
  font-size: 13.5px; color: var(--ink); cursor: pointer;
  transition: background .12s, border-color .12s;
}
.radio-row:hover, .freq-stops .check-row:hover { background: var(--surface-2); }
/* selected state: the row containing a checked control gets an accent tint (the
   row hugs its content, so the tint does not span the full width). */
.radio-row:has(input:checked), .freq-stops .check-row:has(input:checked) {
  background: var(--accent-soft); border-color: var(--accent);
}
/* brand-blue native controls, a touch larger than default; no focus outline. */
.radio-row input[type="radio"], .freq-stops .check-row input[type="checkbox"] {
  width: 16px; height: 16px; margin: 0; flex: none; accent-color: var(--accent); cursor: pointer;
  outline: none; box-shadow: none;
}
/* keep a keyboard-focus cue on the row (the raw control outline is removed above). */
.radio-row:focus-within, .freq-stops .check-row:focus-within { border-color: var(--accent); }
.freq-interval-row { flex-wrap: wrap; }
/* scoped under the rows so these beat the base `.check-row input { width:auto }`
   (which is more specific than a bare `.freq-stop-n`). */
.freq-interval-row .freq-interval-n,
.freq-stops .freq-stop-n {
  width: 56px; flex: none; height: 30px; padding: 0 8px; border: 1px solid var(--border);
  border-radius: 6px; background: var(--surface); color: var(--ink); font-size: 13px;
}
.freq-interval-unit {
  height: 30px; padding: 0 8px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink); font-size: 13px;
}
.rule-empty { margin: 6px 0 0; }

/* group / OR builder: every group is a self-describing box; nested ones indent
   with a dashed border so the "condition vs group of conditions" nesting reads. */
.rule-list-inner { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
/* more breathing room between top-level groups than between conditions in a group */
.rule-group.is-root > .rule-list-inner { gap: 16px; }
.rule-item { margin: 0; }
.rule-group.is-nested {
  border: 1px dashed var(--border); border-radius: 8px; padding: 10px; background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.group-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 8px; }
.group-head .rule-pre { min-width: 0; }
.group-match {
  height: 30px; padding: 0 8px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink); font-size: 12.5px; font-weight: 600;
}
.group-remove { margin-left: auto; color: var(--crit); }
.group-actions { display: flex; gap: 6px; margin-top: 8px; }
/* the root "+ Add condition" bar sits below the last group, so give it the same
   breathing room as the gap between groups (not the tight in-group gap). */
.rule-group.is-root > .group-actions { margin-top: 16px; }
.rule-btn { font-size: 12.5px; }
.group-actions .rule-btn { color: var(--accent); }

.targeting-preview {
  border: 1px solid var(--border); border-radius: 10px; padding: 14px; background: var(--surface-2);
}
.targeting-preview h3 { margin: 0 0 4px; font-size: 14px; }
.targeting-preview .field { margin-top: 10px; }
.preview-result { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.preview-verdict {
  font-size: 13.5px; font-weight: 600; padding: 8px 10px; border-radius: 8px; text-align: center;
}
.preview-verdict.is-match { color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent); }
.preview-verdict.is-nomatch { color: var(--crit); background: color-mix(in srgb, var(--crit) 14%, transparent); }
.preview-rules { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.preview-rules li { display: flex; gap: 8px; align-items: baseline; font-size: 12.5px; }
.preview-rules .preview-mark { font-weight: 700; width: 14px; flex: none; text-align: center; }
.preview-none { margin: 8px 0 0; font-size: 12.5px; }
/* nested preview tree: groups + leaves mirror the rule tree */
.preview-group { margin-top: 8px; }
.preview-group-label { font-size: 11px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; color: var(--ink-soft); }
.preview-group.is-fail > .preview-group-label { color: var(--crit); }
.preview-group.is-pass > .preview-group-label { color: var(--good); }
.preview-group .preview-rules { border-left: 2px solid var(--border); padding-left: 10px; margin-left: 3px; }
.preview-leaf { display: flex; gap: 8px; align-items: baseline; font-size: 12.5px; }
.preview-leaf .preview-mark { font-weight: 700; width: 14px; flex: none; text-align: center; }
.preview-leaf.is-pass .preview-mark { color: var(--good); }
.preview-leaf.is-fail { color: var(--ink-soft); }
.preview-leaf.is-fail .preview-mark { color: var(--crit); }
.targeting-preview textarea {
  width: 100%; min-height: 44px; padding: 8px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--ink); font-size: 12.5px; font-family: var(--mono, monospace); resize: vertical;
}

@media (prefers-reduced-motion: reduce) {
.onbixo-toast { transition: none; }
  .skeleton::after { animation: none; }
  .pw-bar-fill, .pw-strength { transition: none; }
}

/* ---- Media library dialog ------------------------------------------------------- */
.media-dlg-lock { overflow: hidden; }
.media-dlg-back {
  position: fixed; inset: 0; z-index: 4000; display: none; align-items: center; justify-content: center;
  padding: 4vh 4vw; box-sizing: border-box; background: rgba(16, 32, 46, .5); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .18s ease;
}
.media-dlg-back.is-shown { display: flex; opacity: 1; }
.media-dlg {
  width: 100%; max-width: 860px; max-height: 92vh; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 30px 80px rgba(15, 23, 42, .4); overflow: hidden;
}
.media-dlg-head { display: flex; align-items: center; justify-content: space-between; padding: 18px 20px 14px; }
.media-dlg-title { font-size: 18px; font-weight: 750; color: var(--ink); margin: 0; }
.media-dlg-close {
  width: 32px; height: 32px; border: none; border-radius: 999px; background: var(--surface-2); color: var(--ink-soft);
  font-size: 20px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.media-dlg-close:hover { background: var(--border); color: var(--ink); }
/* tabs */
.media-tabs { display: flex; gap: 4px; padding: 0 20px; border-bottom: 1px solid var(--border); }
.media-tab {
  border: none; background: none; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--ink-soft);
  padding: 10px 12px; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.media-tab:hover { color: var(--ink); }
.media-tab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
/* add row: upload dropzone + url */
.media-add { display: flex; flex-direction: column; gap: 10px; padding: 16px 20px; border-bottom: 1px solid var(--border); }
.media-drop {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left; cursor: pointer;
  border: 1.5px dashed var(--border-strong); border-radius: 10px; background: var(--surface-2);
  padding: 16px 18px; color: var(--ink-soft); font: inherit; transition: border-color .12s, background .12s;
}
.media-drop:hover, .media-drop.is-drag { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 7%, var(--surface-2)); }
.media-drop.is-busy { opacity: .6; pointer-events: none; }
.media-drop-icon {
  flex: none; width: 38px; height: 38px; border-radius: 9px; background: var(--surface); border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center; font-size: 18px; color: var(--accent); font-weight: 700;
}
.media-drop-main { display: flex; flex-direction: column; gap: 2px; font-weight: 650; color: var(--ink); font-size: 14px; }
.media-drop-sub { font-size: 12px; font-weight: 500; color: var(--ink-faint); }
.media-url-row { display: flex; gap: 8px; }
.media-url-input {
  flex: 1; min-width: 0; font: inherit; font-size: 13.5px; padding: 9px 12px; border-radius: 8px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
}
.media-url-input:focus { outline: none; border-color: var(--accent); }
.media-url-btn { flex: none; }
/* body + states */
.media-body { flex: 1; overflow-y: auto; padding: 18px 20px 22px; min-height: 200px; }
.media-loading, .media-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  min-height: 200px; color: var(--ink-faint); font-size: 14px; text-align: center;
}
.media-loading[hidden], .media-empty[hidden], .media-grid[hidden] { display: none; }
.media-empty-icon { font-size: 34px; opacity: .7; }
/* grid of image cards */
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; }
.media-card { position: relative; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface-2); }
.media-card-pic {
  display: block; width: 100%; aspect-ratio: 16 / 10; padding: 0; border: none; cursor: pointer; background: none;
  border-bottom: 1px solid var(--border);
}
.media-card-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-card:hover { border-color: var(--accent); box-shadow: 0 6px 18px rgba(15, 23, 42, .12); }
.media-card-meta { display: flex; flex-direction: column; gap: 1px; padding: 8px 10px; }
.media-card-name { font-size: 12.5px; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.media-card-sub { font-size: 11px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.media-card-del {
  position: absolute; top: 6px; right: 6px; width: 24px; height: 24px; border: none; border-radius: 999px;
  background: rgba(15, 23, 42, .55); color: #fff; font-size: 15px; line-height: 1; cursor: pointer; opacity: 0;
  display: flex; align-items: center; justify-content: center; transition: opacity .12s;
}
.media-card:hover .media-card-del { opacity: 1; }
.media-card-del:hover { background: #dc2626; }

/* ---- Launch-audit checklist (/admin/audit-checklist) ------------------------------------------ */
/* Severity accent tokens (semantic, distinct from the indigo brand accent). */
.ac-critical, .ac-sev.ac-critical { --acsev: #d92d33; }
.ac-high,     .ac-sev.ac-high     { --acsev: #d1740a; }
.ac-medium,   .ac-sev.ac-medium   { --acsev: #b8890a; }
.ac-low,      .ac-sev.ac-low      { --acsev: #5b6472; }
:root[data-theme="dark"] .ac-critical { --acsev: #ff6b70; }
:root[data-theme="dark"] .ac-high     { --acsev: #f0a24a; }
:root[data-theme="dark"] .ac-medium   { --acsev: #e6c76a; }
:root[data-theme="dark"] .ac-low      { --acsev: #99a1b3; }

/* Summary + progress row */
.ac-summary { display: flex; flex-wrap: wrap; gap: 16px 28px; align-items: center; justify-content: space-between; }
.ac-progress-wrap { flex: 1; min-width: 240px; }
.ac-progress-head { font-size: 14px; color: var(--ink-soft); margin-bottom: 8px; }
.ac-progress-head strong { color: var(--ink); font-size: 16px; }
.ac-counts { display: flex; flex-wrap: wrap; gap: 8px; }
.ac-count { font-size: 12.5px; font-weight: 600; color: var(--acsev); border: 1px solid color-mix(in srgb, var(--acsev) 30%, var(--border)); border-radius: 999px; padding: 5px 11px; }
.ac-count b { font-weight: 800; }

/* Filters */
.ac-filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: space-between; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.ac-filter-group { display: inline-flex; gap: 4px; background: var(--surface-2); border-radius: 9px; padding: 3px; }
.ac-fbtn { font: inherit; font-size: 12.5px; font-weight: 600; color: var(--ink-soft); background: none; border: 0; border-radius: 7px; padding: 6px 11px; cursor: pointer; }
.ac-fbtn:hover { color: var(--ink); }
.ac-fbtn.is-active { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.08); }

/* Finding cards */
.ac-list { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }
.ac-item { display: flex; gap: 16px; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--acsev); border-radius: 12px; padding: 16px 18px; box-shadow: var(--shadow); }
.ac-item[data-ac-status="done"] { opacity: .62; }
.ac-item[data-ac-status="wont-fix"] { opacity: .55; }
.ac-item[data-ac-status="done"] .ac-title, .ac-item[data-ac-status="wont-fix"] .ac-title { text-decoration: line-through; text-decoration-color: var(--ink-faint); }
.ac-item-main { flex: 1; min-width: 0; }
.ac-item-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 6px; }
.ac-sev { font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; font-weight: 800; color: var(--acsev); background: color-mix(in srgb, var(--acsev) 12%, transparent); border-radius: 5px; padding: 3px 8px; }
.ac-area { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
.ac-verified { font-size: 10.5px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--good, #1f9d63); background: color-mix(in srgb, var(--good, #1f9d63) 14%, transparent); border-radius: 5px; padding: 3px 8px; }
.ac-status-tag { font-size: 10.5px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; border-radius: 5px; padding: 3px 8px; margin-left: auto; }
.ac-st-in-progress { color: var(--accent); background: var(--accent-soft, color-mix(in srgb, var(--accent) 14%, transparent)); }
.ac-st-done { color: var(--good, #1f9d63); background: color-mix(in srgb, var(--good, #1f9d63) 14%, transparent); }
.ac-st-wont-fix { color: var(--ink-faint); background: var(--surface-2); }
.ac-title { font-size: 16px; line-height: 1.35; margin: 0 0 6px; font-weight: 700; }
.ac-loc { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; color: var(--ink-faint); margin-bottom: 9px; word-break: break-word; }
.ac-detail { font-size: 14px; color: var(--ink-soft); margin: 0 0 10px; line-height: 1.55; }
.ac-fix { font-size: 13.5px; background: var(--surface-2); border-radius: 8px; padding: 9px 12px; color: var(--ink-soft); }
.ac-fix b { color: var(--good, #1f9d63); text-transform: uppercase; font-size: 10.5px; letter-spacing: .06em; margin-right: 6px; }
.ac-note-wrap { margin-top: 10px; }
.ac-note-wrap summary { font-size: 12.5px; color: var(--ink-faint); cursor: pointer; user-select: none; }
.ac-note-wrap summary:hover { color: var(--accent); }
.ac-note { width: 100%; box-sizing: border-box; margin-top: 8px; font: inherit; font-size: 13px; border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; background: var(--surface-2); color: var(--ink); resize: vertical; }
.ac-note:focus { outline: none; border-color: var(--accent); }

/* Status action buttons (right column) */
.ac-item-actions { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; }
.ac-sbtn { font: inherit; font-size: 12px; font-weight: 600; color: var(--ink-soft); background: var(--surface); border: 1px solid var(--border); border-radius: 7px; padding: 6px 12px; cursor: pointer; white-space: nowrap; min-width: 86px; text-align: center; }
.ac-sbtn:hover { border-color: var(--accent); color: var(--accent); }
.ac-sbtn.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
.ac-empty { padding: 24px; text-align: center; }
@media (max-width: 620px) {
  .ac-item { flex-direction: column; gap: 12px; }
  .ac-item-actions { flex-direction: row; flex-wrap: wrap; }
  .ac-sbtn { min-width: 0; flex: 1; }
}

/* Mail-templates list: give the Updated + Actions columns fixed room and keep them on one line, so
   the action buttons never wrap and "8 hours ago" doesn't break awkwardly. Subject flexes. */
.mail-tpl-table .tpl-updated { width: 120px; white-space: nowrap; }
.mail-tpl-table .tpl-actions { width: 1%; white-space: nowrap; }
.mail-tpl-table .tpl-actions .row-actions { justify-content: flex-end; flex-wrap: nowrap; }
.mail-tpl-table td[data-label="Slug"], .mail-tpl-table th:nth-child(3) { width: 150px; white-space: nowrap; }
.mail-tpl-table td[data-label="Subject"] { max-width: 360px; }
.mail-tpl-table td[data-label="Subject"] .err-msg { max-width: 360px; }

/* Admin env badge (topnav): unmistakable DEV (amber) vs PROD (green) indicator so staff always know
   which environment they're acting in. Dev is styled as a caution; prod as a confident success. */
.env-badge { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700;
  letter-spacing: .02em; padding: 4px 11px; border-radius: 999px; white-space: nowrap; border: 1px solid transparent; }
.env-badge .env-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.env-badge.env-dev { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--warn) 40%, var(--border)); }
.env-badge.env-dev .env-dot { background: var(--warn); }
.env-badge.env-prod { color: var(--good); background: color-mix(in srgb, var(--good) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--good) 40%, var(--border)); }
.env-badge.env-prod .env-dot { background: var(--good); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 20%, transparent); }
@media (max-width: 640px) { .env-badge { display: none; } }

/* ===== Marketing plan (/admin/marketing) ===== */
.mkt-progress { height: 8px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border);
  overflow: hidden; margin: 4px 0 20px; }
.mkt-progress-fill { height: 100%; background: var(--accent); border-radius: 999px; transition: width .25s ease; }
.mkt-phase { margin-bottom: 16px; }
.mkt-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.mkt-task { border: 1px solid var(--border); border-radius: 10px; background: var(--surface); padding: 12px 14px; }
.mkt-task.is-done { background: var(--surface-2); }
.mkt-task.is-done .mkt-task-title { text-decoration: line-through; color: var(--ink-faint); }
.mkt-task.is-done .mkt-task-detail { color: var(--ink-faint); }
.mkt-task-main { display: flex; gap: 12px; align-items: flex-start; }
/* Custom checkbox: hide the native box, draw a themed one that fills on :checked. */
.mkt-check { position: relative; flex: none; width: 20px; height: 20px; cursor: pointer; margin-top: 1px; }
.mkt-check input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.mkt-check-box { display: block; width: 20px; height: 20px; border: 2px solid var(--border-strong); border-radius: 6px;
  background: var(--surface); transition: background .12s ease, border-color .12s ease; }
.mkt-check input:hover + .mkt-check-box { border-color: var(--accent); }
.mkt-check input:focus-visible + .mkt-check-box { outline: 2px solid var(--accent); outline-offset: 2px; }
.mkt-check input:checked + .mkt-check-box { background: var(--accent); border-color: var(--accent); }
.mkt-check input:checked + .mkt-check-box::after { content: ""; position: absolute; left: 6px; top: 2px;
  width: 5px; height: 10px; border: solid var(--accent-ink); border-width: 0 2px 2px 0; transform: rotate(45deg); }
.mkt-task-body { flex: 1 1 auto; min-width: 0; }
.mkt-task-title { font-weight: 600; font-size: 14.5px; color: var(--ink); }
/* pre-line preserves the newlines authored in a task's detail (e.g. the paste-ready ad copy
   blocks) while still wrapping long lines - so multi-line copy reads as a list, not one blob. */
.mkt-task-detail { margin: 4px 0 0; font-size: 13px; line-height: 1.5; color: var(--ink-soft); white-space: pre-line; }
/* Logo-download strip on the social-profiles task: the 512x512 avatars as thumbnails, each with a
   Download PNG link. A checkered-ish neutral tile behind each so the transparent variant is visible. */
.mkt-logos { margin-top: 12px; padding: 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2); }
.mkt-logos-head { font-size: 12px; font-weight: 600; color: var(--ink-soft); margin-bottom: 10px; }
.mkt-logos-grid { display: flex; flex-wrap: wrap; gap: 16px; }
.mkt-logo { display: flex; flex-direction: column; align-items: center; gap: 4px; margin: 0; }
.mkt-logo-img { width: 72px; height: 72px; border-radius: 12px; border: 1px solid var(--border);
  background: repeating-conic-gradient(var(--surface) 0% 25%, var(--surface-2) 0% 50%) 0 0 / 16px 16px; }
.mkt-logo-cap { font-size: 11px; color: var(--ink-faint); }
.mkt-task-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 9px; }
.mkt-tag { font-size: 11.5px; color: var(--ink-faint); }
.mkt-linkbtn { appearance: none; border: 0; background: none; padding: 0; cursor: pointer; font: inherit;
  font-size: 12px; color: var(--accent); }
.mkt-linkbtn:hover { text-decoration: underline; }
.mkt-linkbtn-danger { color: var(--crit); }
.mkt-note-row { display: flex; gap: 8px; align-items: flex-start; margin-top: 10px; }
.mkt-note { flex: 1 1 auto; min-width: 0; resize: vertical; font: inherit; font-size: 13px; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--ink); }
.mkt-note:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.mkt-add-grid { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr 1fr; gap: 12px; margin-bottom: 12px; }
.mkt-add-actions { margin-top: 4px; }
@media (max-width: 820px) { .mkt-add-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .mkt-add-grid { grid-template-columns: 1fr; } }

/* ===== Trial banner (settings/plan) ===== */
.trial-banner { display: flex; align-items: center; gap: 12px; padding: 14px 18px; border-radius: 12px;
  margin-bottom: 20px; border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  background: var(--accent-soft); }
.trial-banner-body { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.trial-banner-body strong { font-size: 14px; color: var(--ink); }
.trial-banner-body span { font-size: 13px; color: var(--ink-soft); line-height: 1.45; }
.trial-banner-muted { background: var(--surface-2); border-color: var(--border); }
.trial-banner-cta { flex: none; white-space: nowrap; }
/* Ending-soon (<=3 days): a red warning banner that visibly demands attention. */
.trial-banner-warn { background: color-mix(in srgb, var(--crit) 12%, var(--surface));
  border-color: color-mix(in srgb, var(--crit) 45%, var(--border)); }
.trial-banner-warn .trial-banner-body strong { color: var(--crit); }
/* On a red warning banner, the CTA reads best as a white button with red text (an indigo primary
   clashes with the red ground). Applies to the plan-page warn banner + the global ending banner. */
.trial-banner-warn .btn-primary,
.trial-global-banner .btn-primary {
  background: var(--surface); color: var(--crit); border: 1px solid color-mix(in srgb, var(--crit) 40%, var(--border));
}
.trial-banner-warn .btn-primary:hover,
.trial-global-banner .btn-primary:hover {
  background: var(--crit); color: #fff; border-color: var(--crit);
}

/* ===== Rich signup (/signup) - namespaced under .signup-page so nothing leaks/collides ===== */
/* Local token aliases: app.css uses --accent; the signup design was authored with --ind/--ind-bright.
   --border-strong exists in app.css. Fraunces is declared globally already (light-DOM @font-face). */
.signup-page { --ind: var(--accent); --ind-bright: #6d5efc; min-height: 100vh; }
:root[data-theme="dark"] .signup-page, .signup-page { }
.auth-wide-body { margin: 0; background: var(--bg); color: var(--ink); }
.auth-wide-flash { position: fixed; top: 16px; right: 16px; z-index: 60; max-width: 360px; padding: 11px 15px;
  border-radius: 10px; font-size: 13.5px; box-shadow: var(--shadow); background: var(--surface); border: 1px solid var(--border); }
.auth-wide-flash.error { border-color: color-mix(in srgb, var(--crit) 45%, var(--border)); color: var(--crit); }
.auth-wide-flash.success { border-color: color-mix(in srgb, var(--good) 45%, var(--border)); }

.signup-page .split { display: grid; grid-template-columns: 1.05fr 1fr; grid-template-rows: 1fr auto; min-height: 100vh; }
.signup-page .value { grid-row: 1 / 3; grid-column: 1; position: relative; overflow: hidden; color: #eef0f7;
  padding: 64px; display: flex; flex-direction: column; gap: 40px; justify-content: center;
  background: radial-gradient(1100px 800px at 82% 12%, rgba(109,94,252,.34), transparent 55%),
              radial-gradient(760px 620px at 0% 108%, rgba(45,212,191,.10), transparent 52%), #0b0c17; }
.signup-page .formside { grid-row: 1; grid-column: 2; display: flex; flex-direction: column; padding: 40px 40px 24px; }
.signup-page .form-foot { grid-row: 2; grid-column: 2; display: flex; align-items: center; justify-content: space-between;
  gap: 12px; font-size: 12px; color: var(--ink-faint); padding: 20px 40px; border-top: 1px solid var(--border); background: var(--bg); }
.signup-page .form-foot a { color: var(--ink-faint); text-decoration: none; }
.signup-page .form-foot a:hover { color: var(--ink-soft); }
.signup-page .form-foot .links { display: flex; gap: 16px; }

.signup-page .v-brand { display: inline-flex; align-items: center; gap: 8px; font-weight: 700; font-size: 22px; letter-spacing: -.02em; color: #fff; text-decoration: none; }
.signup-page .v-brand svg { width: 36px; height: 36px; }
.signup-page .v-head { font-family: "Fraunces", Georgia, serif; font-weight: 400; font-size: 44px; line-height: 1.06; letter-spacing: -.02em; max-width: 14ch; }
.signup-page .v-head .em { font-style: italic; color: var(--ind-bright); }
.signup-page .v-ticks { display: flex; flex-direction: column; gap: 16px; }
.signup-page .v-tick { display: flex; align-items: flex-start; gap: 11px; font-size: 15.5px; color: #cfd6e6; line-height: 1.4; }
.signup-page .v-tick svg { flex: none; margin-top: 2px; color: #34d399; }
.signup-page .v-tick b { color: #fff; font-weight: 650; }
.signup-page .v-free { display: inline-flex; align-self: flex-start; width: auto; max-width: 100%; align-items: center;
  gap: 16px; padding: 16px 18px; border-radius: 14px; background: rgba(109,94,252,.12); border: 1px solid rgba(109,94,252,.4); }
.signup-page .v-free .price { font-size: 34px; font-weight: 800; letter-spacing: -.03em; color: #fff; line-height: 1; }
.signup-page .v-free .fx { font-size: 13.5px; color: #c7cde0; line-height: 1.4; }
.signup-page .v-free .fx b { color: #fff; }

.signup-page .v-mock { position: absolute; right: -80px; bottom: -10px; width: 500px; z-index: 0; pointer-events: none;
  transform: rotate(-9deg); transform-origin: bottom right; opacity: .6;
  -webkit-mask-image: linear-gradient(155deg, transparent 8%, rgba(0,0,0,.85) 55%, #000 85%);
          mask-image: linear-gradient(155deg, transparent 8%, rgba(0,0,0,.85) 55%, #000 85%); }
.signup-page .v-app { position: relative; border-radius: 16px; overflow: hidden; border: 1px solid rgba(255,255,255,.09);
  background: linear-gradient(180deg,#1a2032,#141a29); box-shadow: 0 30px 70px rgba(0,0,0,.5); }
.signup-page .v-app-bar { height: 38px; display: flex; align-items: center; gap: 7px; padding: 0 14px; border-bottom: 1px solid rgba(255,255,255,.08); }
.signup-page .v-app-bar i { width: 9px; height: 9px; border-radius: 50%; }
.signup-page .v-app-bar .r { background: #ff5f57; } .signup-page .v-app-bar .y { background: #febc2e; } .signup-page .v-app-bar .g { background: #28c840; }
.signup-page .v-app-body { padding: 16px; display: grid; grid-template-columns: 1fr 1fr; gap: 12px; position: relative; }
.signup-page .v-card { height: 60px; border-radius: 10px; background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.08); }
.signup-page .v-card.hl { border-color: var(--ind-bright); box-shadow: 0 0 0 3px rgba(109,94,252,.4), 0 0 34px rgba(109,94,252,.3); background: rgba(109,94,252,.10); }
.signup-page .coach { position: absolute; right: 16px; bottom: 16px; width: 56%; max-width: 230px;
  background: linear-gradient(180deg,#232b40,#1b2233); color: #e6ebf5; border: 1px solid rgba(255,255,255,.10);
  border-radius: 12px; padding: 12px 14px; box-shadow: 0 18px 44px rgba(0,0,0,.5); z-index: 2; }
.signup-page .coach .step { font-size: 9.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: #8b7ff0; }
.signup-page .coach .title { font-size: 13.5px; font-weight: 700; margin: 5px 0 4px; line-height: 1.15; color: #8b93a8; }
.signup-page .coach .body { font-size: 11px; color: #828da4; line-height: 1.4; }
.signup-page .coach .foot { display: flex; align-items: center; justify-content: space-between; margin-top: 11px; }
.signup-page .coach .dots { display: inline-flex; gap: 5px; } .signup-page .coach .dots i { width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,.2); } .signup-page .coach .dots i.on { background: var(--ind-bright); }
.signup-page .coach .btn { font-size: 11px; font-weight: 700; color: #fff; background: var(--ind); padding: 5px 12px; border-radius: 8px; }

.signup-page .form-center { flex: 1; display: flex; align-items: center; justify-content: center; }
.signup-page .formcard { width: 100%; max-width: 420px; }
.signup-page .form-brand { display: none; align-items: center; gap: 8px; font-weight: 700; font-size: 21px; letter-spacing: -.02em; color: var(--ink); text-decoration: none; margin-bottom: 22px; }
.signup-page .form-brand svg { width: 34px; height: 34px; }
.signup-page .formcard h1 { font-size: 27px; letter-spacing: -.02em; margin-bottom: 7px; }
.signup-page .formcard .sub { color: var(--ink-soft); font-size: 15px; line-height: 1.5; margin-bottom: 30px; }
.signup-page .sfield { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
.signup-page .sfield span { font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.signup-page .sfield input { font: inherit; font-size: 15px; padding: 12px 14px; border: 1px solid var(--border-strong);
  border-radius: 10px; background: var(--surface); color: var(--ink); width: 100%; }
.signup-page .sfield input:focus { outline: none; border-color: var(--ind); box-shadow: 0 0 0 3px var(--accent-soft); }
.signup-page .sfield .hint { font-size: 12px; color: var(--ink-faint); }
.signup-page .btn-signup { display: block; width: 100%; font: inherit; font-size: 15.5px; font-weight: 700; color: #fff;
  background: var(--ind); border: 0; padding: 14px; border-radius: 11px; cursor: pointer; margin-top: 8px; }
.signup-page .btn-signup:hover { filter: brightness(1.06); }
.signup-page .strust { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 20px; font-size: 12.5px; color: var(--ink-faint); }
.signup-page .strust span { display: inline-flex; align-items: center; gap: 5px; }
.signup-page .strust svg { color: var(--good); }
.signup-page .altline { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); font-size: 14px; color: var(--ink-soft); text-align: center; }
.signup-page .altline a { color: var(--ind); font-weight: 600; text-decoration: none; }
.signup-page .legal { margin-top: 14px; font-size: 11.5px; color: var(--ink-faint); line-height: 1.5; text-align: center; }
.signup-page .legal a { color: var(--ink-soft); }

@media (max-width: 900px) {
  .signup-page .split { grid-template-columns: 1fr; grid-template-rows: none; }
  .signup-page .value { grid-row: auto; grid-column: auto; order: 2; padding: 36px 28px 40px; gap: 20px; justify-content: flex-start; }
  .signup-page .formside { grid-row: auto; grid-column: auto; order: 1; padding: 34px 24px 32px; }
  .signup-page .form-foot { grid-row: auto; grid-column: auto; order: 3; }
  .signup-page .form-brand { display: inline-flex; }
  .signup-page .v-brand { display: none; }
  .signup-page .v-head { font-size: 32px; }
  .signup-page .v-mock { display: none; }
  .signup-page .v-ticks { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .signup-page .v-tick { font-size: 13.5px; }
  .signup-page .v-free { align-self: stretch; width: 100%; }
  .signup-page .form-center { flex: 0 0 auto; }
  .signup-page .formcard { margin: 0 auto; }
}
@media (max-width: 560px) { .signup-page .v-ticks { grid-template-columns: 1fr; } .signup-page .value { padding: 28px 22px; } }

/* Price summary at the top of the pre-checkout billing dialog. */
.billing-checkout-summary { font-size: 13.5px; color: var(--ink-soft); margin: 0 0 14px;
  padding: 10px 12px; border-radius: 9px; background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); }
.billing-checkout-summary strong { color: var(--ink); }

/* ===== Trial-lapse decide gate (blocking modal when a trial ended unpaid) ===== */
.lapse-gate-overlay { z-index: 4600; }  /* above normal modals; a true blocking decision */
.lapse-gate { max-width: 720px; }
.lapse-gate-lede { color: var(--ink-soft); font-size: 14px; line-height: 1.5; margin: 6px 0 20px; }
.lapse-gate-lede strong { color: var(--crit); }
/* Two side-by-side option cards (Keep <plan> | Continue on Free). */
.lapse-gate-options { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.lapse-gate-options.is-single { grid-template-columns: 1fr; }
.lapse-gate-opt { display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: 12px;
  padding: 18px; background: var(--surface); }
.lapse-gate-opt.is-primary { border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: var(--accent-soft); }
.lapse-gate-opt-head { font-size: 15px; font-weight: 700; color: var(--ink); display: flex;
  align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 12px; }
.lapse-gate-price { font-size: 18px; font-weight: 800; letter-spacing: -.02em; }
.lapse-gate-price span { font-size: 12px; font-weight: 500; color: var(--ink-faint); }
.lapse-gate-pros { list-style: none; margin: 0 0 16px; padding: 0; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.lapse-gate-pros li { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; color: var(--ink-soft); line-height: 1.4; }
.lapse-gate-pros li svg { flex: none; margin-top: 2px; color: var(--good); }
.lapse-gate-pros.is-muted li svg { color: var(--ink-faint); }
.lapse-gate-note { font-size: 11.5px; color: var(--ink-faint); text-align: center; margin: 10px 0 0; }
@media (max-width: 560px) { .lapse-gate-options { grid-template-columns: 1fr; } }

/* Lapsed sidebar: 'flows paused' status line replaces the (misleading) MAU meter. */
.sidebar-usage-paused { color: var(--crit); }

/* Lapse-gate: dismissible close button + the persistent "trial expired" reminder strip. */
.lapse-gate { position: relative; }
.lapse-gate-close { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; border: 0;
  background: none; color: var(--ink-faint); font-size: 22px; line-height: 1; cursor: pointer; border-radius: 8px; }
.lapse-gate-close:hover { color: var(--ink); background: var(--surface-2); }
.lapse-strip { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 11px 14px;
  margin-bottom: 18px; border-radius: 10px; font-size: 13.5px;
  border: 1px solid color-mix(in srgb, var(--crit) 40%, var(--border)); border-left: 3px solid var(--crit);
  background: color-mix(in srgb, var(--crit) 9%, var(--surface)); }
.lapse-strip-ico { font-size: 16px; line-height: 1; color: var(--crit); }
.lapse-strip-text { flex: 1 1 260px; color: var(--ink-soft); }
.lapse-strip-text strong { color: var(--ink); }

/* ============================================================================
   FLOW VERSION HISTORY (the History panel in the flow editor)
   A two-pane dialog: the timeline of checkpoints on the left, what the selected
   one contains on the right. Reuses .modal-overlay/.modal for the shell.
   ========================================================================== */
/* The History trigger, on the flow's meta line rather than in the action group. It is information
   about the flow (where it has been) more than an action on it, so it reads as part of the muted
   "Type . Status" line and leaves Save / Analytics / Publish uninterrupted. Icon + small label,
   quiet by default, accent on hover. */
/* Icon-only: the label lives in aria-label + the tooltip. Padding is even and the gap is gone -
   both were sized for a text label that used to sit beside the icon. */
.flow-meta-btn {
  display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
  margin-left: 10px; padding: 5px; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: 6px;
  font: inherit; font-size: 12.5px; line-height: 1.6; color: var(--ink-soft);
}
.flow-meta-btn svg { flex: none; opacity: .85; }
.flow-meta-btn:hover { color: var(--accent); background: var(--surface-2); border-color: var(--border); }
.flow-meta-btn:hover svg { opacity: 1; }
.flow-meta-btn[aria-expanded="true"] { color: var(--accent); background: var(--surface-2); border-color: var(--border); }

.fh-modal { max-width: 720px; width: 100%; }
.fh-body { display: grid; grid-template-columns: 260px 1fr; gap: 18px; margin: 4px 0 18px;
  border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }

/* Left: the timeline. Scrolls independently so a long history never grows the dialog. */
.fh-list { max-height: 340px; overflow-y: auto; background: var(--surface-2); border-right: 1px solid var(--border); }
.fh-item { display: flex; flex-direction: column; gap: 3px; width: 100%; text-align: left; cursor: pointer;
  padding: 10px 13px; background: none; border: 0; border-bottom: 1px solid var(--border);
  font: inherit; color: var(--ink); }
.fh-item:last-child { border-bottom: 0; }
.fh-item:hover { background: var(--surface); }
.fh-item.is-active { background: var(--surface); box-shadow: inset 3px 0 0 var(--accent); }
.fh-item-top { display: flex; align-items: center; gap: 7px; }
.fh-item-label { font-size: 13.5px; font-weight: 620; }
/* A publish is a state real users actually saw, and the one kept forever while ordinary checkpoints
   roll off at 20 - so it is marked structurally rather than with a pill that just repeated the word.
   Hollow dot for an edit, filled accent dot + accent label for a publish. */
.fh-item-dot { width: 7px; height: 7px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--ink-soft); opacity: .55; }
.fh-item.is-publish .fh-item-dot { background: var(--accent); border-color: var(--accent); opacity: 1; }
.fh-item.is-publish .fh-item-label { color: var(--accent); }
.fh-item-meta { font-size: 12px; color: var(--ink-soft); }

/* Right: what the selected version contains. */
.fh-detail { padding: 14px 16px; min-height: 160px; }
.fh-detail-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; font-size: 14px; }
.fh-steps { margin: 0 0 8px; padding-left: 20px; font-size: 13px; line-height: 1.7; color: var(--ink-soft); }
.fh-steps li { padding-left: 2px; }
.fh-more { margin: 0 0 12px; font-size: 12.5px; color: var(--ink-soft); }
.fh-restore { margin-top: 4px; }
.fh-empty { margin: 0; padding: 14px; font-size: 13px; color: var(--ink-soft); }

@media (max-width: 620px) {
  /* Stack the panes; the timeline shortens so the detail + restore button stay reachable. */
  .fh-body { grid-template-columns: 1fr; }
  .fh-list { max-height: 190px; border-right: 0; border-bottom: 1px solid var(--border); }
}

/* ============================================================================
   A/B EXPERIMENTS (flow editor)
   The A/B test tab, and the A/B switcher above the step list.
   ========================================================================== */
/* Tab lock glyph, shown below Growth. */
/* The lock beside a plan-gated tab. Sized in em so it tracks the tab's own type; the tab
   itself is a flex row (above), which is what actually centres it against the label. */
/* Muted so the label leads and the lock reads as a quiet marker beside it. Inherited color
   rather than a fixed one, so it still follows the tab through hover and its active state. */
.tab-lock { display: inline-flex; align-items: center; opacity: .55; }
/* Sized from the cropped viewBox's own ratio so the padlock is not squashed. Deliberately
   under 1em: it is a status marker beside the label, not a second glyph competing with it. */
.tab-lock svg { width: .78em; height: .93em; display: block; }

/* --- the A/B test tab --- */
.exp-head h2 { margin: 0 0 6px; }
.exp-sub { margin: 0 0 18px; font-size: 13.5px; line-height: 1.7; color: var(--ink-soft); }
/* The toggle gets its own full-width row rather than floating in the header corner: the whole row is
   the click target, and the label sits BEFORE the track so it reads as a setting being turned on. */
.exp-toggle { display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 14px 16px; border: 1px solid var(--border); border-radius: 11px;
  background: var(--surface-2); }
.exp-toggle-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.exp-toggle-txt b { font-size: 14px; font-weight: 640; color: var(--ink); }
.exp-toggle-txt small { font-size: 12.5px; color: var(--ink-soft); }
/* No version B yet: the row is not a toggle at all, it is a prompt to go and write one. Not
   greyed out - a disabled control invites clicking to find out why. */
.exp-toggle-actions { display: flex; align-items: center; gap: 14px; flex: none; }
.exp-toggle.is-blocked { cursor: default; }
.exp-toggle.is-blocked:hover { border-color: var(--border); }
/* .is-blocked marks "version B does not exist yet". The toggle stays fully usable in that state -
   turning it on seeds B from a copy of the control - so it must NOT look or behave disabled. */
/* Shown while version B is still a byte-identical copy of A - the test runs but measures nothing.
   Warn-toned rather than error: it is a valid state, just not a useful one yet. */
.exp-identical { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 11px 14px; border-radius: 10px; font-size: 13px; line-height: 1.5;
  background: color-mix(in srgb, var(--warn) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--border)); }
/* A / B / Both filter, in the analytics controls row beside the date range - both narrow what the
   page counts. Same visual language as the editor's variant switcher, so the two read as one
   feature across the two pages. The label matters: a bare "Both / A / B" does not say what it
   filters, and this control silently changes every number on the page. */
/* A column: the label + pills on one row, the scoping caption tucked beneath them. The caption
   used to be a full-width row of its own below the whole controls block, which cost a line of
   vertical space; here it occupies the gap beside the date picker that was empty anyway, so the
   controls block grows by only a few pixels and the first metric moves up. */
.variant-filter-wrap { display: inline-flex; flex-direction: column; align-items: flex-start;
  gap: 5px; margin-right: auto; }
.variant-filter-row { display: inline-flex; align-items: center; gap: 8px; }
/* Compare mode has no caption - its label reads "Comparing" and it carries the test-window line
   and the exit button as direct children, so it stays the single centred row it always was. */
.variant-filter-wrap:not(:has(> .variant-filter-row)) { flex-direction: row; align-items: center;
  gap: 8px; }

/* Compare mode states which two versions are on screen rather than offering a choice between
   them - both render side by side, so there is nothing to pick. Same pill-group shell as the
   switcher it replaced, so the controls row keeps its shape, but flat: no button affordance on
   something that is not clickable. */
.variant-filter-static { display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 9px; background: var(--surface-2);
  font-size: 13px; color: var(--ink); white-space: nowrap; }
.variant-filter-static b { font-weight: 650; }
.variant-filter-static .muted { font-size: 12px; }
.variant-filter-vs { padding: 0 2px; font-size: 11.5px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-faint); }
.variant-filter-label { font-size: 12px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-faint); }
.variant-filter { display: inline-flex; gap: 3px; padding: 3px; background: var(--surface-2);
  border-radius: 9px; }
.variant-filter-btn { display: inline-flex; align-items: center; padding: 5px 11px; border-radius: 7px;
  font-size: 13px; font-weight: 650; color: var(--ink-soft); text-decoration: none;
  border: 1px solid transparent; white-space: nowrap; }
.variant-filter-btn:hover { color: var(--ink); }
.variant-filter-btn.is-active { background: var(--surface); border-color: var(--border);
  color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.04); }

/* Shown while one arm is selected: every metric below counts that arm only, which the numbers
   alone do not reveal. A caption, not a banner - it sits directly under the switcher that caused
   the scoping, so it reads as that control's footnote. It was a full-width accent-tinted band,
   which gave a one-sentence footnote the same weight as the panels holding the actual results
   and pushed the first metric off the fold. */
.variant-scope-caption { margin: 0; font-size: 12.5px; line-height: 1.45;
  color: var(--ink-faint); }
.variant-scope-caption b { font-weight: 650; color: var(--ink-soft); }

/* On narrow screens the labelled filter needs its own line rather than squeezing the date range. */
@media (max-width: 720px) {
  .variant-filter-wrap { margin-right: 0; width: 100%; }
}

/* Per-arm diagnostics under each arm's headline rate. */
.exp-arm-metrics { display: grid; gap: 6px; margin: 12px 0 0; padding: 12px 0 0;
  border-top: 1px solid var(--border); }
.exp-arm-metrics > div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.exp-arm-metrics dt { font-size: 12.5px; color: var(--ink-faint); margin: 0; }
.exp-arm-metrics dd { margin: 0; font-size: 13px; font-weight: 650; color: var(--ink);
  font-variant-numeric: tabular-nums; }
.exp-arm-metrics dd .muted { font-weight: 500; }

/* The record of a finished test, shown after promotion. Deliberately quieter than the live
   results panel - it is a reference, not something to act on. */
.exp-past-when { font-size: 12.5px; }
.exp-past-arms { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.exp-past-arm { display: grid; gap: 3px; padding: 14px 16px; border-radius: 12px;
  background: var(--surface-2); border: 1px solid transparent; }
.exp-past-arm.is-winner { background: color-mix(in srgb, var(--good) 8%, var(--surface));
  border-color: color-mix(in srgb, var(--good) 35%, var(--border)); }
.exp-past-name { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink); }
.exp-past-arm b { font-size: 24px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.exp-past-arm .muted { font-size: 12.5px; }
.exp-past-note { margin: 12px 0 0; font-size: 13px; color: var(--ink-soft); }

/* The past-test panel folds: the verdict is one line and always visible, the arms and dates
   open on demand. A <details> rather than a scripted toggle - it is CSP-safe with no island,
   and keyboard/screen-reader behaviour comes free. */
.exp-past summary::-webkit-details-marker { display: none; }
.exp-past-summary { display: flex; align-items: center; gap: 10px; cursor: pointer;
  list-style: none; padding: 2px 0; }
.exp-past-summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px;
  border-radius: 6px; }
.exp-past-sum-text { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 14.5px; }
.exp-past-sum-text b { font-weight: 650; color: var(--ink); }
.exp-past-sum-text .muted { font-size: 13px; }
.exp-past-summary .exp-past-when { margin-left: auto; }
/* Rotates to point down when open - the only state the summary itself shows. */
.exp-past-chev { flex: none; display: inline-flex; color: var(--ink-faint);
  transition: transform .15s; }
.exp-past-chev svg { width: 15px; height: 15px; display: block; }
.exp-past[open] .exp-past-chev { transform: rotate(90deg); }
/* The open body needs separating from the summary; closed, the panel is just the one row. */
.exp-past[open] .panel-head { margin-top: 12px; }
.exp-past-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--border); }
.exp-past-actions .muted { font-size: 12.5px; }
/* One chart per version, stacked full-width so the x-axes line up and the starts-to-completions
   gap can be compared by looking down the page rather than across two half-width plots. */
.ab-stack + .ab-stack { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
.ab-stack-head { margin: 0 0 6px; font-size: 13px; font-weight: 650; }
.ab-stack-head .muted { font-weight: 500; }

/* A vs B KPI cards. Same grid and card as the flow's own row directly below, so the two read
   as one page rather than a comparison bolted on - only the contents differ. */
.kpi-row-ab { margin-bottom: 14px; }
.kpi-card-ab .kpi-ab-pair { display: flex; align-items: baseline; gap: 18px; margin: 6px 0 2px;
  flex-wrap: wrap; }
.kpi-ab-side { display: flex; align-items: baseline; gap: 7px; }
/* The letter, not a word: it repeats four times per row and the label carries the meaning. */
.kpi-ab-tag { display: inline-grid; place-items: center; width: 18px; height: 18px;
  border-radius: 5px; font-size: 10.5px; font-weight: 700; background: var(--surface-2);
  color: var(--ink-faint); border: 1px solid var(--border); }
.kpi-ab-tag-b { background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }
.kpi-ab-val { font-size: 21px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums; }

/* Two funnels side by side, for a test whose arms have different lengths - the only honest
   way to show per-step numbers while both versions are live. Stacks on a narrow screen. */
.arm-funnels { display: grid; gap: 18px; }
/* 1100px, not 780: each table now carries seven columns, and two of those side by side on a
   narrower screen squeezes every column to unreadable. Below that they stack, which is still a
   direct comparison - just vertical. */
@media (min-width: 1100px) { .arm-funnels { grid-template-columns: 1fr 1fr; } }
.arm-funnel-head { margin: 0 0 8px; font-size: 13.5px; font-weight: 650; }
.arm-funnel-head .muted { font-weight: 500; font-size: 12.5px; }
.arm-funnels-foot { margin-top: 14px; }

.exp-past-kept { margin: 10px 0 0; font-size: 12.5px; }
.exp-past-kept a { color: var(--accent); font-weight: 600; text-decoration: none; }
.exp-past-kept a:hover { text-decoration: underline; }

/* Marks the promoted version in the switcher - it is the one still running, so its numbers
   keep growing while the other version's stopped at the test. */
.variant-live { font-size: 9.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: 1px 5px; border-radius: 999px; margin-left: 5px; vertical-align: 1px;
  background: color-mix(in srgb, var(--good) 16%, var(--surface));
  color: var(--good); border: 1px solid color-mix(in srgb, var(--good) 32%, var(--border)); }

/* Compare mode: the window is stated rather than picked, and a way out sits beside it. */
.variant-window { font-size: 12.5px; white-space: nowrap; }
.variant-exit { margin-left: auto; }

/* The same record on the EDITOR's A/B tab, above the editor. Only the head and footer are new:
   the arm cards reuse .exp-past-* so the two places cannot drift apart. */
.exp-history { margin-bottom: 16px; }
.exp-history-head { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap;
  margin-bottom: 14px; }
.exp-history-head > div { flex: 1 1 320px; min-width: 0; }
.exp-history-head h2 { margin: 0 0 3px; font-size: 15.5px; font-weight: 650; }
.exp-history-head p { margin: 0; font-size: 13.5px; }
.exp-history-head .exp-past-when { flex: none; margin-left: auto; }
.exp-history-foot { margin: 12px 0 0; font-size: 12.5px; }
.exp-history-foot a { color: var(--accent); font-weight: 600; text-decoration: none; }
.exp-history-foot a:hover { text-decoration: underline; }

/* CSP-safe proportional widths. style-src 'self' drops inline style attributes, so anything sized
   from data needs a class or an attribute selector. 5%% steps keep this to 21 rules and are ample
   for a progress bar. */
.exp-progress span[data-pct="0"], .sv-pctbar[data-pct="0"] { width: 0%; }
.exp-progress span[data-pct="5"], .sv-pctbar[data-pct="5"] { width: 5%; }
.exp-progress span[data-pct="10"], .sv-pctbar[data-pct="10"] { width: 10%; }
.exp-progress span[data-pct="15"], .sv-pctbar[data-pct="15"] { width: 15%; }
.exp-progress span[data-pct="20"], .sv-pctbar[data-pct="20"] { width: 20%; }
.exp-progress span[data-pct="25"], .sv-pctbar[data-pct="25"] { width: 25%; }
.exp-progress span[data-pct="30"], .sv-pctbar[data-pct="30"] { width: 30%; }
.exp-progress span[data-pct="35"], .sv-pctbar[data-pct="35"] { width: 35%; }
.exp-progress span[data-pct="40"], .sv-pctbar[data-pct="40"] { width: 40%; }
.exp-progress span[data-pct="45"], .sv-pctbar[data-pct="45"] { width: 45%; }
.exp-progress span[data-pct="50"], .sv-pctbar[data-pct="50"] { width: 50%; }
.exp-progress span[data-pct="55"], .sv-pctbar[data-pct="55"] { width: 55%; }
.exp-progress span[data-pct="60"], .sv-pctbar[data-pct="60"] { width: 60%; }
.exp-progress span[data-pct="65"], .sv-pctbar[data-pct="65"] { width: 65%; }
.exp-progress span[data-pct="70"], .sv-pctbar[data-pct="70"] { width: 70%; }
.exp-progress span[data-pct="75"], .sv-pctbar[data-pct="75"] { width: 75%; }
.exp-progress span[data-pct="80"], .sv-pctbar[data-pct="80"] { width: 80%; }
.exp-progress span[data-pct="85"], .sv-pctbar[data-pct="85"] { width: 85%; }
.exp-progress span[data-pct="90"], .sv-pctbar[data-pct="90"] { width: 90%; }
.exp-progress span[data-pct="95"], .sv-pctbar[data-pct="95"] { width: 95%; }
.exp-progress span[data-pct="100"], .sv-pctbar[data-pct="100"] { width: 100%; }

/* ---- survey results ------------------------------------------------------
   The answers ARE the report, so they lead the page. NPS gets its score, its split and the 0-10
   spread - the score alone hides whether a +40 came from a wall of 9s or a war between 10s and 3s. */
.survey-results { display: grid; gap: 0; }
/* The export sits opposite the heading. panel-head-split centres its children, which would float
   the button against the middle of a two-line description - align it to the top instead, and stop
   it shrinking when the description wraps. */
.survey-results .panel-head-split { align-items: flex-start; gap: 16px; }
.sv-export { flex: none; }
/* Each question is its own block, not a row in a list: a hairline and 20px of padding left
   a chart, a spread and a stack of written answers running straight into the next question.
   The extra room plus a heavier question heading make the boundary readable while scrolling. */
/* Two questions per row from a comfortable width. One per row gave each question the full panel
   for what is usually a short bar chart, so a four-question survey scrolled for no reason.
   Auto rows mean a row sizes to its tallest cell, which matters because a text question carries a
   scrolling answer list and an NPS carries a score + spread, while a choice is three bars. */
/* Each question is its own CARD rather than a slab separated by a hairline. A rule only works
   when every cell is the same height; here they are not - a text question carries a scrolling
   answer list, an NPS a score plus a spread, a choice three bars - so in two columns the rules
   started level and the content under them did not. A bordered card holds whatever height its
   question needs and still reads as one unit. */
.survey-results .sv-grid { display: grid; gap: 18px; margin-top: 4px; }
/* 1180px, not 900: a card is half the panel minus the gap, and the panel is already inset by the
   dashboard sidebar. At 900 each card lands near 350px, which is too tight for an NPS score plus
   its three-bar split side by side (that inner split fires at 620px on the VIEWPORT, so it cannot
   see how narrow its card has become). */
@media (min-width: 1180px) { .survey-results .sv-grid { grid-template-columns: 1fr 1fr; gap: 20px; } }
/* White card on the panel: the border and the gap do the separating, so the question stays a
   clean surface for the charts inside it. */
.sv-res { padding: 20px 22px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface); align-content: start; }
.sv-res-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 14px;
  padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.sv-res-q { margin: 0; font-size: 16.5px; font-weight: 650; letter-spacing: -0.01em; }
/* The question's position in the survey. Quiet by design - an orientation marker while
   scrolling a long results page, not a label competing with the question itself. */
.sv-res-num { flex: none; align-self: center; min-width: 22px; height: 22px; border-radius: 6px;
  display: grid; place-items: center; font-size: 11.5px; font-weight: 700;
  background: var(--surface-2); color: var(--ink-faint); border: 1px solid var(--border);
  font-variant-numeric: tabular-nums; }
.sv-res-count { margin-left: auto; font-size: 13px; color: var(--ink-faint);
  font-variant-numeric: tabular-nums; }
.sv-res-none { margin: 0; font-size: 13.5px; }
.sv-res-avg { margin: 0 0 12px; font-size: 14px; color: var(--ink-soft); }
.sv-res-avg b { font-size: 20px; color: var(--ink); }

/* The score reads as a heading for the split beside it, so it aligns to the TOP of the chart,
   not to its centre - centred, it sagged against the three bars and looked unrelated to them.
   Narrower now the questions are two-up, so the score column is sized rather than auto. */
.sv-nps { display: grid; gap: 16px; align-items: start; margin-bottom: 16px; }
@media (min-width: 620px) { .sv-nps { grid-template-columns: 92px 1fr; gap: 20px; } }
.sv-nps-score { text-align: center; }
.sv-nps-score b { display: block; font-size: 44px; font-weight: 700; line-height: 1;
  color: var(--ink); font-variant-numeric: tabular-nums; }
.sv-nps-score span { font-size: 11px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-faint); }
.sv-nps-chart { margin-top: 0; }
.sv-spread { margin-top: 10px; }
.sv-choice-chart { margin-top: 2px; }

.sv-texts { margin-top: 16px; }
.sv-texts-head { display: flex; gap: 10px; align-items: baseline; font-size: 12px;
  font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint);
  margin-bottom: 8px; }
.sv-texts-head .muted { text-transform: none; letter-spacing: 0; font-weight: 500; }
.sv-text-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px;
  max-height: 340px; overflow-y: auto; }
.sv-text-list li { display: flex; align-items: flex-start; gap: 10px; padding: 10px 13px;
  background: var(--surface-2); border-radius: 9px; font-size: 13.5px; line-height: 1.5; }
.sv-text-score { flex: none; min-width: 24px; height: 24px; border-radius: 6px; display: grid;
  place-items: center; font-size: 12px; font-weight: 700; background: var(--surface);
  border: 1px solid var(--border); font-variant-numeric: tabular-nums; }
.sv-text-body { flex: 1; }
.sv-text-when { flex: none; font-size: 12px; color: var(--ink-faint); }
/* Which arm an answer came from, when the survey is under an A/B test. */
.sv-text-variant { flex: none; font-size: 11px; font-weight: 600; letter-spacing: .03em;
  color: var(--ink-faint); border: 1px solid var(--border); border-radius: 999px;
  padding: 1px 7px; }
/* "View all" sits at the end of the answers header, opposite the count. */
.sv-texts-all { margin-left: auto; }
/* Inside the dialog the list is the whole content, so it uses the modal's own scroll rather
   than the inline preview's 340px cap. */
/* The answers dialog is a header + a scrolling list + a pager that must stay reachable.
   A plain max-height with the whole modal scrolling pushed the pager below the fold, so
   Next could not be clicked. Column layout: only the list scrolls, header and pager stay put.
   Width comes from .modal-lg; only the column layout is set here. */
.modal-answers { display: flex; flex-direction: column; max-height: 84vh; overflow: hidden; }
.modal-answers .modal-head { flex: none; }
.sa-list { min-height: 120px; display: flex; flex-direction: column; min-height: 0; flex: 1; }
.sa-list.is-loading { opacity: .55; }
/* The list itself takes the scroll; the pager after it does not move. */
.sv-text-list-full { max-height: none; overflow-y: auto; gap: 9px; flex: 1; min-height: 0;
  padding-right: 2px; }
.sa-list .pager { margin-top: 14px; flex: none; }

/* ---- survey question editor ---------------------------------------------
   A question is a small form, not a step card: the kind picker sits in the head so switching it
   visibly swaps the controls below, which is the clearest way to show that kind, scale and
   placement are separate things. */
.survey-presets { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 0 0 16px; border-bottom: 1px solid var(--rule, var(--border)); margin-bottom: 16px; }
.survey-presets-label { font-size: 12px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-faint); }
.survey-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
/* The card is a header bar plus a body. The header carries the ordinal, the kind and the row
   actions on its own tinted ground, so a long list of questions reads as a stack of distinct
   things rather than one continuous sheet of fields. */
.survey-q { border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
  overflow: hidden; }
.survey-q:focus-within { border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 9%, transparent); }
.survey-q-head { display: flex; align-items: center; gap: 10px;
  padding: 11px 14px 11px 12px; background: var(--surface-2);
  border-bottom: 1px solid var(--border); }
/* Solid accent chip: the ordinal is the one thing that says WHERE in the sequence you are, and
   the runtime shows the same "N of M" to the person answering. */
.survey-q-n { flex: none; width: 24px; height: 24px; border-radius: 7px; display: grid;
  place-items: center; font-size: 12px; font-weight: 700; background: var(--accent);
  color: #fff; font-variant-numeric: tabular-nums; }
.survey-q-kind { font: inherit; font-size: 13px; font-weight: 600; padding: 6px 10px;
  border-radius: 8px; border: 1px solid var(--border); background: var(--surface);
  color: var(--ink); }
/* The themed combobox replaces this select at runtime. It must keep the header row's compact
   metrics rather than the base .ss-btn (41px, sized for a full-width .field input), or the type
   picker stands taller than the Remove/reorder buttons beside it. min-height is reset because a
   larger min-height would win over the padding-derived height. */
.ss-wrap.survey-q-kind-wrap { flex: none; display: inline-flex; position: relative; }
.ss-wrap.survey-q-kind-wrap .ss-btn {
  /* 7px vertical padding, NOT the 6px the native .survey-q-kind used: this control's real
     neighbours in the header row are the .btn-sm actions (7px 13px @ 13px -> 35.5px), and the old
     native select was 2px shorter than them. Match the buttons, not the control being replaced. */
  min-height: 0; width: auto; padding: 7px 26px 7px 10px; border-radius: 8px;
  border-color: var(--border); font-size: 13px; font-weight: 600;
}
.ss-wrap.survey-q-kind-wrap .ss-caret { position: absolute; right: 9px; top: 50%; margin-top: -4px; }
.ss-wrap.survey-q-kind-wrap .ss-pop { min-width: max(100%, 180px); }
/* The question's own wording, echoed in the header so a long list is scannable. Truncates
   rather than wrapping: the header is a one-line identifier, not a second copy of the field. */
.survey-q-echo { flex: 1; min-width: 0; font-size: 13.5px; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.survey-q-echo.is-empty { color: var(--ink-faint); font-style: italic; }
.survey-q-acts { margin-left: auto; display: flex; gap: 6px; flex: none; }
/* Reorder arrows read as icon buttons, not as full buttons competing with Remove. */
.survey-move { min-width: 30px; padding: 5px 8px; line-height: 1; color: var(--ink-faint); }
.survey-move:hover { color: var(--ink); }
/* The body: everything below the header shares one padded block. */
.survey-q > .field, .survey-q-opts { padding: 0 16px; }
.survey-q > .field:first-of-type { padding-top: 14px; }
.survey-q-opts:last-child { padding-bottom: 16px; }
.survey-q-title { width: 100%; font: inherit; font-size: 14px; padding: 8px 11px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--ink); }
.survey-q-opts { display: grid; gap: 12px; margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--rule-soft, var(--border)); }
.survey-q-note { margin: 0; font-size: 12.5px; }
/* Scale + Points share the SAME two-column grid as Low label / High label below them, so
   the question card has one column edge running down it rather than a content-sized row that
   starts its own. The selects still stop short of the full card width they used to fill. */
.survey-q-scale { display: grid; gap: 10px; }
@media (min-width: 640px) { .survey-q-scale { grid-template-columns: 1fr 1fr; } }
/* Points is a single digit, so it stops short of the shared narrow cap. */
.survey-q-points select { max-width: 110px; }
.survey-q-labels { display: grid; gap: 10px; }
@media (min-width: 640px) { .survey-q-labels { grid-template-columns: 1fr 1fr; } }
.survey-q-choices { display: grid; gap: 8px; justify-items: start; }
/* The option rows fill the width; the Add button and the toggles below must not - a grid item
   stretches by default, which is what made "Add option" render as a full-width bar. */
.survey-q-choices > .survey-choice-line { justify-self: stretch; }
.survey-choice-line { display: flex; gap: 8px; align-items: center; width: 100%; }
.survey-choice-line .survey-q-title { flex: 1; min-width: 0; }
/* Add sits under the last option, visually attached to the list rather than floating. */
.survey-q-choices > .btn { margin-top: 2px; }
/* Adding is constructive; removing is not. They were identical, which read backwards. */
.survey-add-opt { color: var(--accent); font-weight: 600;
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  background: color-mix(in srgb, var(--accent) 6%, var(--surface)); }
.survey-add-opt:hover { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
/* Quiet until hovered: a column of red Remove buttons shouts louder than the options do. */
.survey-rm-opt { color: var(--ink-faint); }
.survey-rm-opt:hover { color: var(--crit, #d5442f);
  border-color: color-mix(in srgb, var(--crit) 45%, var(--border));
  background: color-mix(in srgb, var(--crit) 7%, var(--surface)); }
/* .check-row carries its own bottom margin for use in normal forms; inside these grids the gap
   already does that job, so the margin only makes the spacing uneven. */
.survey-q-opts .check-row, .survey-q-choices .check-row { margin: 0; }
/* A little air before the first toggle, so the checkboxes read as a group after the fields. */
.survey-q-choices > .check-row:first-of-type { margin-top: 6px; }
/* The kind-specific toggles sit in .survey-q-choices and "Allow skipping" in .survey-q-opts, so
   the two grids' gaps have to agree or the last checkbox sits further from the others than they
   are from each other. 10px in both. */
.survey-q-opts { gap: 10px; }
.survey-q-choices { gap: 10px; }
.survey-empty { padding: 18px 0 4px; }
.survey-settings { display: grid; gap: 16px; max-width: 520px; }

.exp-identical[hidden] { display: none; }
.exp-identical-txt { margin: 0; flex: 1 1 260px; }
.exp-identical-acts { display: flex; gap: 8px; margin-left: auto; flex: none; }

/* AI variant picker. A radio list of hypotheses, each one grounded in this flow's own funnel.
   Scoped to this dialog: as `.modal.modal-lg` it narrowed EVERY large modal (flow activity,
   install, location, survey answers) to 560px as a side effect of styling this one. */
.modal.modal-ai-variant { max-width: 560px; }
.exp-ai-state { margin: 4px 0 10px; }
.exp-ai-note { margin: 0 0 10px; font-size: 13px; }
.exp-ai-list { list-style: none; margin: 0 0 4px; padding: 0; display: grid; gap: 8px;
  max-height: 46vh; overflow-y: auto; }
.exp-ai-item { display: flex; gap: 10px; align-items: flex-start; padding: 11px 13px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface); }
.exp-ai-item:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.exp-ai-item input { margin-top: 3px; flex: none; }
.exp-ai-item label { display: grid; gap: 3px; cursor: pointer; font-size: 13.5px; line-height: 1.45; }
.exp-ai-item small { font-size: 12.5px; line-height: 1.5; }
/* Selected state, so the choice is visible before hitting Write. */
.exp-ai-item:has(input:checked) { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, var(--surface)); }
/* Body appears under the toggle once a test is on, separated by a rule rather than sitting flush. */
.exp-editor .exp-body { margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--border); }

.exp-hint { margin: 0 0 16px; font-size: 13.5px; color: var(--ink-soft); }
/* A/B badge on a flows-list row. Accent-toned so it reads as a capability in use rather than a
   status - the status pill beside it already owns draft/published. The pending variant (switched
   on, but not published so not actually splitting traffic) is outlined instead of filled: same
   word, visibly not the same state. */
.pill-ab { color: var(--accent); background: color-mix(in srgb, var(--accent) 13%, transparent);
  border-color: transparent; font-variant-numeric: tabular-nums; letter-spacing: .02em; }
.pill-ab.is-pending { color: var(--ink-faint); background: transparent;
  border: 1px dashed color-mix(in srgb, var(--ink-faint) 45%, transparent); }

/* "Not running yet" - the test is switched on in the draft but the runtime serves the PUBLISHED
   config, so nothing is being split until the flow is published. Warn-toned rather than danger:
   nothing is broken, an action is simply outstanding. */
.exp-notlive { display: flex; align-items: flex-start; gap: 10px; margin: 14px 0 0;
  padding: 12px 14px; border-radius: 11px; font-size: 13px; line-height: 1.55;
  color: var(--ink-soft);
  background: color-mix(in srgb, var(--warn, #b5751a) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn, #b5751a) 30%, var(--border)); }
.exp-notlive b { color: var(--ink); font-weight: 640; }
.exp-notlive svg { width: 17px; height: 17px; flex: none; margin-top: 1px;
  color: var(--warn, #b5751a); }

/* "End this test" - the named way to stop a run without promoting. Same row shape as .exp-toggle
   so it reads as part of the same panel, tinted with the danger hue because it ends something the
   customer has been spending traffic on. Sits last, after the settings, since it is the action you
   reach for once rather than a control you adjust. */
.exp-danger { display: flex; align-items: center; justify-content: space-between; gap: 20px;
  margin-top: 18px; padding: 14px 16px; border-radius: 11px;
  background: color-mix(in srgb, var(--danger, #d64545) 6%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--danger, #d64545) 28%, var(--border)); }
.exp-danger-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.exp-danger-txt b { font-size: 14px; font-weight: 640; color: var(--ink); }
.exp-danger-txt small { font-size: 12.5px; color: var(--ink-soft); line-height: 1.55; }
.exp-danger .btn { flex: none; }

.exp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; margin-bottom: 6px; }
.exp-split-row { display: flex; align-items: center; gap: 12px; }
.exp-split-row input[type="range"] { flex: 1; }
.exp-split-row output { min-width: 44px; text-align: right; font-variant-numeric: tabular-nums;
  font-weight: 650; color: var(--ink); }
.exp-editor .check-row { margin-top: 14px; }
.exp-editor .check-row small { display: block; margin-top: 2px; }
/* A bare button that reads as a link - used to jump to the Content tab from the hint. */
.link-btn { background: none; border: 0; padding: 0; font: inherit; color: var(--accent);
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }

/* --- the A/B switcher above the step list --- */
.variant-switch { display: flex; gap: 4px; margin: 0 0 12px; padding: 3px;
  background: var(--surface-2); border-radius: 9px; }
.variant-btn { flex: 1; display: flex; align-items: baseline; justify-content: center; gap: 5px;
  padding: 6px 8px; cursor: pointer; background: none; border: 1px solid transparent;
  border-radius: 7px; font: inherit; font-size: 13px; font-weight: 650; color: var(--ink-soft); }
/* A tooltip flow has no step-list sidebar, so its switcher sits above the full-width editor form.
   Cap the width there - two stretched buttons across the whole column read as a form control
   rather than the A/B toggle they are. */
.variant-switch-solo { max-width: 280px; }
.variant-btn:hover { color: var(--ink); }
.variant-btn.is-active { background: var(--surface); border-color: var(--border); color: var(--ink);
  box-shadow: 0 1px 2px rgba(0,0,0,.04); }
.variant-sub { font-size: 11px; font-weight: 500; opacity: .75; }
/* While editing version B, tint the editor so it is never ambiguous which version is on screen -
   the two look identical otherwise, and saving the wrong one is silent. */
body.editing-variant-b .editor-steps { box-shadow: inset 3px 0 0 var(--accent); }

/* --- A/B results (analytics page) --- */
.exp-verdict { margin: 0 0 14px; font-size: 14px; line-height: 1.6; color: var(--ink-soft); }
.exp-verdict strong { color: var(--ink); }
.exp-verdict.is-winner strong { color: var(--accent); }
.exp-invalid { margin-bottom: 14px; }
/* Progress toward the planned sample - the honest answer to "is it done yet". */
.exp-progress { height: 6px; border-radius: 3px; background: var(--surface-2); overflow: hidden; margin: 0 0 18px; }
.exp-progress span { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.exp-arms { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }
.exp-arm { padding: 16px 18px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); }
/* The winning arm is marked structurally, not only by the pill - the pill is easy to miss. */
.exp-arm.is-winner { border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: inset 3px 0 0 var(--accent); }
.exp-arm-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.exp-arm-name { font-size: 13px; font-weight: 650; color: var(--ink); }
.exp-arm-rate { font-size: 30px; font-weight: 740; line-height: 1.1; letter-spacing: -.02em; color: var(--ink); }
.exp-arm.is-winner .exp-arm-rate { color: var(--accent); }
.exp-arm-sub { margin-top: 3px; font-size: 12.5px; color: var(--ink-soft); }
.exp-arm-steps { list-style: none; margin: 12px 0 0; padding: 10px 0 0; border-top: 1px solid var(--border); }
.exp-arm-steps li { display: flex; align-items: center; gap: 8px; padding: 3px 0; font-size: 12.5px; color: var(--ink-soft); }
.exp-step-n { display: grid; place-items: center; width: 18px; height: 18px; flex: none;
  border-radius: 50%; background: var(--surface-2); font-size: 11px; font-weight: 650; }
