/* ═══════════════════════════════════════════════════════════════════════
   Datacompany app — "paper instrument"

   The visual world is the one already committed on datacompany.nl
   (datacompany-website/app/globals.css + tailwind.config.ts): warm bone
   paper, near-black ink, one cobalt accent, hairline rules, Inter.

   Three rules hold the whole system together:

   1. Surfaces separate with a hairline, never with a shadow. Shadow means
      "this floats above the page" and is reserved for real overlays —
      menus, tooltips, multiselect panels. A dashboard where every tile
      floats is a dashboard with no depth at all.
   2. Chroma in the chrome is cobalt and nothing else. Green/red/amber are
      reserved for status and deltas; the rest of the colour budget belongs
      to the data itself (see channel-colors.js).
   3. Labels are sentence case. Size, weight and colour carry hierarchy —
      uppercase + letterspacing on every tile title flattens it instead.
   ═══════════════════════════════════════════════════════════════════════ */

/* Inter, self-hosted: the marketing site's face, and no third-party font
   request from a product whose privacy policy promises none. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/inter-latin-wght-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  color-scheme: light;

  /* ── Ground & ink ── */
  --bg: #fbfbf7;          /* warm bone paper */
  --card: #ffffff;        /* a sheet laid on the paper */
  --surface-2: #f4f2eb;   /* sand — table heads, inset panels */
  --fg: #0b0d12;
  --muted: #5f6470;
  --muted-soft: #8b909c;

  /* ── Accent (cobalt, from the brand) ── */
  --accent: #2d5bff;
  --accent-2: #7c8dff;
  --accent-soft: #ecf0ff;
  --accent-ink: #1f49e5;  /* pressed / hover fill */

  /* ── Rules. Ink at low alpha, so they sit *in* the paper rather than
       being a separate grey that drifts out of the palette. ── */
  --border: rgba(11, 13, 18, 0.09);
  --border-strong: rgba(11, 13, 18, 0.18);
  --row-hover: rgba(11, 13, 18, 0.035);

  /* ── Status. Printed-ink versions: dark enough to pass 4.5:1 as text on
       both white and their own tint, desaturated enough not to shout. ── */
  --error: #c0362c;
  --error-soft: #fbedeb;
  --error-line: rgba(192, 54, 44, 0.22);
  --success: #0f7a5a;
  --success-soft: #e9f4f0;
  --success-line: rgba(15, 122, 90, 0.22);
  --warn: #8f5d0f;
  --warn-soft: #f8f0df;
  --warn-line: rgba(143, 93, 15, 0.24);

  /* ── Radius: one scale, four steps. ── */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-pill: 999px;

  /* ── Elevation: only for things that genuinely float. ── */
  --shadow-sm: 0 1px 0 rgba(11, 13, 18, 0.03);
  --shadow-md: 0 1px 0 rgba(11, 13, 18, 0.03), 0 10px 30px -18px rgba(11, 13, 18, 0.18);
  --shadow-pop: 0 1px 0 rgba(11, 13, 18, 0.04), 0 18px 44px -22px rgba(11, 13, 18, 0.34);

  --focus-ring: 0 0 0 3px rgba(45, 91, 255, 0.18);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* Measured height of .topbar at its normal single-row height. Anything
     else that sticks (the overview's window bar, the metric-tree detail
     panel) offsets from this, so the two can't drift apart. Below the wrap
     breakpoint the bar grows a second row and the sticky children turn
     static instead of guessing a new number. */
  --topbar-h: 51px;
}

* { box-sizing: border-box; }
/* Chart.js sets explicit width/height attrs on <canvas>. Without these
   two rules, a canvas that grew when the viewport was wide prevents its
   CSS-grid / flex parent from ever shrinking back down. */
canvas { max-width: 100%; }
.kpi-grid > *, .charts-grid > *, .charts-grid--quad > *, .grid-2 > * { min-width: 0; }

body {
  margin: 0;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* ss01 = alternate digits/punctuation, cv11 = single-storey a. Same pair
     the marketing site sets, so the two properties read as one typeface. */
  font-feature-settings: "ss01", "cv11";
  background: var(--bg);
  color: var(--fg);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection {
  background: rgba(45, 91, 255, 0.22);
  color: var(--fg);
}

/* ── Top bar ─────────────────────────────────────────────────────────── */
.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1.5rem;
  padding: 0.7rem 1.75rem;
  background: rgba(251, 251, 247, 0.86);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
  min-height: var(--topbar-h);
  position: sticky;
  top: 0;
  z-index: 100;
}
body.impersonating .topbar { top: 36px; }

.brand {
  font-weight: 600;
  font-size: 1.02rem;
  letter-spacing: -0.03em;
  color: var(--fg);
  text-decoration: none;
  white-space: nowrap;
}
.brand:hover { text-decoration: none; color: var(--accent); }

.container {
  max-width: 720px;
  margin: 3rem auto;
  padding: 0 1.5rem;
}

/* The default sheet. Hairline only — see rule 1 at the top of this file. */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 1.75rem;
}

h1 {
  margin-top: 0;
  font-weight: 600;
  font-size: 1.75rem;
  line-height: 1.15;
  letter-spacing: -0.035em;
  text-wrap: balance;
}
h2 {
  font-weight: 600;
  font-size: 1.2rem;
  line-height: 1.25;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
h3 { font-weight: 600; letter-spacing: -0.015em; }
p { text-wrap: pretty; }
.muted { color: var(--muted); }
.error { color: var(--error); }
.ok { color: var(--success); }

.card + .card { margin-top: 1rem; }

/* ── Tables ──────────────────────────────────────────────────────────── */
table.data {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.5rem;
}
table.data th, table.data td {
  padding: 0.6rem 0.7rem;
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-size: 0.9rem;
}
table.data th {
  color: var(--muted);
  font-weight: 500;
  font-size: 0.82rem;
  background: var(--surface-2);
  border-bottom-color: var(--border-strong);
  white-space: nowrap;
}
/* Numbers line up column-wise only if every figure is the same width. */
table.data td { font-variant-numeric: tabular-nums; }
/* Below the phone breakpoint a nowrap header row is what pushes a six-column
   table — and with it the whole page — past the viewport. Let the labels wrap
   and tighten the cells instead of handing the user a sideways-scrolling page. */
@media (max-width: 760px) {
  table.data th { white-space: normal; }
  table.data th, table.data td { padding: 0.5rem 0.45rem; font-size: 0.85rem; }
  table.data td .bar-cell .bar-track { display: none; }
}

label {
  display: block;
  margin-top: 1rem;
  font-size: 0.88rem;
  color: var(--muted);
}

input[type="email"], input[type="text"], input[type="password"], input[type="date"] {
  width: 100%;
  padding: 0.55rem 0.75rem;
  margin-top: 0.25rem;
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  font: inherit;
  font-size: 0.95rem;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input::placeholder, textarea::placeholder { color: var(--muted-soft); }
input[type="email"]:focus, input[type="text"]:focus,
input[type="password"]:focus, input[type="date"]:focus,
textarea:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}
/* Keyboard users get the same ring on everything else that can take focus. */
a:focus-visible, button:focus-visible, summary:focus-visible,
[role="button"]:focus-visible, input[type="checkbox"]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-sm);
}

button {
  margin-top: 1.25rem;
  padding: 0.55rem 1.05rem;
  background: var(--accent);
  color: #ffffff;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font: inherit;
  font-weight: 500;
  font-size: 0.92rem;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.15s ease, box-shadow 0.2s var(--ease-out);
}
button:hover { background: var(--accent-ink); }
button:active { background: var(--accent-ink); box-shadow: inset 0 1px 2px rgba(11, 13, 18, 0.2); }

button:disabled {
  background: var(--surface-2);
  /* --muted, not --muted-soft: a disabled control is exempt from WCAG 1.4.3,
     but at 2.85:1 nobody could read which action was unavailable. */
  color: var(--muted);
  border-color: var(--border);
  cursor: not-allowed;
}

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

/* ── Dashboard layout ───────────────────────────────────────────── */
/* Expand the base <main class="container"> when it wraps a wide dashboard
   child, so the 720px cap doesn't bottle-neck the inner layout. Pages
   without .container.wide (login, onboarding) keep the narrow column. */
main.container:has(> .container.wide) {
  max-width: 100%;
  padding: 0;
  margin-top: 1.75rem;
}
/* Capped rather than full-bleed: on a 2560px monitor an uncapped dashboard
   stretches bar charts and trend lines so wide that the shape of the data
   becomes unreadable. 1600px keeps a 2x2 chart grid comfortable while still
   using the whole width of a laptop screen. */
.container.wide { max-width: 1600px; margin-inline: auto; padding: 0 1.5rem; }

/* ── Primary nav ─────────────────────────────────────────────────────
   The active marker is the site's highlighter stripe (globals.css
   .accent-text), reduced to a 2px cobalt rule and drawn from the left on
   load. It is the one authored motion in the app. */
.nav-links { display: flex; gap: 1.15rem; align-items: center; font-size: 0.9rem; flex-wrap: wrap; }
.nav-links a { text-decoration: none; color: var(--muted); }
.nav-links a:hover { color: var(--fg); text-decoration: none; }
.nav-links > a, .nav-links .nav-dd > summary {
  position: relative;
  padding-bottom: 5px;
}
.nav-links > a.active::after,
.nav-links .nav-dd.active > summary::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
  transform-origin: left center;
  animation: nav-underline 0.42s var(--ease-out) both;
}
@keyframes nav-underline { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
  .nav-links > a.active::after,
  .nav-links .nav-dd.active > summary::after { animation: none; }
}
.nav-links a.active { color: var(--fg); font-weight: 500; }
.nav-links a.nav-primary { color: var(--fg); font-weight: 500; }
.nav-links a.nav-primary.active { color: var(--fg); }
.nav-links .nav-spacer { flex: 1; }

/* ── Dropdown nav items (5 top-level groups + right-side utility) ──── */
.nav-links .nav-dd { position: relative; }
.nav-links .nav-dd > summary {
  list-style: none;
  cursor: pointer;
  color: var(--muted);
  padding-right: 0.85rem;
  user-select: none;
  white-space: nowrap;
}
.nav-links .nav-dd > summary::-webkit-details-marker { display: none; }
/* Drawn chevron, not a "▾" glyph: the text arrow renders at a different
   weight and baseline in every font on every OS. */
.nav-links .nav-dd > summary::before {
  content: "";
  position: absolute;
  right: 0;
  /* Centred on the text, not on the box: the box carries 5px of padding
     below for the active underline, which would drag a 50% chevron down. */
  top: calc(50% - 2.5px);
  width: 9px;
  height: 9px;
  margin-top: -4.5px;
  background: currentColor;
  -webkit-mask: var(--icon-chevron-down) center / 9px 9px no-repeat;
  mask: var(--icon-chevron-down) center / 9px 9px no-repeat;
  opacity: 0.55;
  transition: transform 0.18s var(--ease-out);
}
.nav-links .nav-dd[open] > summary::before { transform: rotate(180deg); }
.nav-links .nav-dd:hover > summary,
.nav-links .nav-dd[open] > summary { color: var(--fg); }
.nav-links .nav-dd.active > summary { color: var(--fg); font-weight: 500; }
.nav-links .nav-dd-menu {
  position: absolute;
  top: calc(100% + 0.55rem);
  left: -0.6rem;
  min-width: 208px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 0.35rem;
  box-shadow: var(--shadow-pop);
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 0;
}
.nav-links .nav-dd-menu.nav-dd-menu-right { left: auto; right: -0.6rem; }
.nav-links .nav-dd-menu a,
.nav-links .nav-dd-menu .nav-dd-static {
  display: block;
  padding: 0.42rem 0.65rem;
  border-radius: var(--r-sm);
  color: var(--fg);
  white-space: nowrap;
}
.nav-links .nav-dd-menu a:hover { background: var(--row-hover); text-decoration: none; }
.nav-links .nav-dd-menu a.active { font-weight: 500; color: var(--accent); background: var(--accent-soft); }
.nav-links .nav-dd-menu .nav-dd-static {
  font-size: 0.8rem;
  cursor: default;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  margin-bottom: 0.3rem;
  padding-bottom: 0.5rem;
}
/* Hover-open on devices that support it — keeps click toggle working as fallback. */
@media (hover: hover) {
  .nav-links .nav-dd:hover > .nav-dd-menu { display: flex; }
  .nav-links .nav-dd:not(:hover):not([open]) > .nav-dd-menu { display: none; }
}
.nav-links .nav-utility > summary { font-size: 0.85rem; }
/* Leading icon inside a nav summary (Data). */
.nav-ico {
  width: 14px;
  height: 14px;
  vertical-align: -2px;
  margin-right: 0.3rem;
  opacity: 0.75;
}

/* ── Monitor hub: shared tabs between /anomalies and /notifications ── */
/* Section tab bar. Started life on the Monitor hub; /data uses it too, hence
   the shared .section-tabs name with .monitor-tabs kept as-is. */
.monitor-tabs, .section-tabs {
  display: flex;
  gap: 0.25rem;
  margin: 0 0 1.25rem 0;
  border-bottom: 1px solid var(--border);
}
.monitor-tabs a, .section-tabs a {
  padding: 0.55rem 0.9rem;
  color: var(--muted);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-size: 0.92rem;
}
.monitor-tabs a:hover, .section-tabs a:hover { color: var(--fg); text-decoration: none; }
.monitor-tabs a.active, .section-tabs a.active {
  color: var(--fg);
  font-weight: 500;
  border-bottom-color: var(--accent);
}

/* ── Tiles ───────────────────────────────────────────────────────────── */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0.85rem;
  margin-bottom: 1.25rem;
}
/* Provenance line under the scorecard: quiet by default, because on a healthy
   account it is reassurance rather than news. It only earns attention once the
   model has gone stale. */
.model-freshness {
  margin: -0.75rem 0 1.25rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.model-freshness strong { color: var(--fg); font-weight: 600; }
.model-freshness a { margin-left: 0.5rem; }
.model-freshness.is-stale {
  padding: 0.5rem 0.7rem;
  border-left: 3px solid var(--warn);
  background: var(--warn-soft);
  border-radius: var(--r-sm);
  color: var(--fg);
}
.kpi {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 1rem 1.15rem 1.1rem;
}
/* Sentence case, not uppercase: see rule 3. */
.kpi .label {
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}
.kpi .value {
  font-size: 1.7rem;
  font-weight: 600;
  margin-top: 0.15rem;
  letter-spacing: -0.035em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.kpi .delta { font-size: 0.84rem; margin-top: 0.2rem; font-variant-numeric: tabular-nums; }
.kpi .delta.pos { color: var(--success); }
.kpi .delta.neg { color: var(--error); }

/* Scorecard row (overview) — big number + Δ + sparkline per tile.
   The canvas MUST live in a fixed-height, position:relative wrapper: Chart.js
   (responsive + maintainAspectRatio:false) sizes to its parent and overrides
   any height set on the canvas itself with an inline style. Inside a .kpi
   (a grid item with no bounded height) that creates a resize feedback loop
   that grows the canvas — and the whole tile — without limit. The wrapper
   caps it. See _scorecard.html. */
.kpi .spark-wrap { position: relative; width: 100%; height: 34px; margin-top: 0.6rem; overflow: hidden; }
.kpi .spark-wrap .spark { display: block; }
/* The hero tile earns its emphasis from the ground it sits on, not from a
   slab of accent border down its edge. */
.kpi-hero {
  background: var(--accent-soft);
  border-color: rgba(45, 91, 255, 0.22);
}
.kpi-hero .label { color: var(--accent-ink); }
.kpi-hero .value { color: var(--accent-ink); }
.kpi-hero .delta.up, .kpi-hero .delta.down, .kpi-hero .delta.flat { color: var(--accent-ink); }

/* Explanatory line under a KPI value (link-cards on /data → Settings). */
.kpi .kpi-note { margin-top: 0.35rem; font-size: 0.85rem; line-height: 1.4; }

/* Unit suffix ("conversions", "%", "×") so a bare number can't stand alone. */
.kpi .kpi-unit {
  font-size: 0.58em;
  font-weight: 500;
  color: var(--muted);
  letter-spacing: -0.01em;
}

/* ---- Info tooltips ("i") -- see _macros.html info() and glossary.py ---- */
/* The wrapper stays `display: inline` on purpose. As an inline-block/flex it
   built its own line box (34px tall against a 14px dot), and vertical-align
   then centred *that* box against the text -- dropping the dot ~10px below the
   text centre everywhere. Keeping it inline means the button itself is the
   aligned box, which is the plain, predictable case.
   position:relative still anchors the absolutely-positioned .info-tip. */
.info-wrap {
  position: relative;
  display: inline;
}
/* Sized in em so the dot scales with whatever it annotates -- a 12px KPI label
   and a 19px section heading need different dots, and a fixed px size can only
   be optically centred in one of them.

   vertical-align is 0.05em, NOT `middle`: measured against the text box,
   `middle` sat 11.5px too low in every context, while this lands within ~1px
   in both. Don't "simplify" it back to middle. */
.info-dot {
  position: relative;
  display: inline-block;
  vertical-align: 0.05em;
  font-size: 0.7em;
  width: 1.15em;
  height: 1.15em;
  line-height: 1.1em;
  margin-left: 0.45em;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-weight: 600;
  text-align: center;
  cursor: help;
  transition: color 0.15s ease, border-color 0.15s ease;
}
/* Invisible hit area: the visible circle follows the text size and would
   otherwise be a sub-20px tap target on small labels. Doesn't affect layout. */
.info-dot::after {
  content: "";
  position: absolute;
  inset: -0.6rem;
}
.info-dot:hover, .info-wrap.is-open > .info-dot {
  color: var(--accent);
  border-color: var(--accent);
}
.info-tip {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  /* Above .nav-dd-menu (z-index 50) so a tooltip near the top bar isn't
     swallowed by an open nav dropdown. */
  z-index: 60;
  display: none;
  width: max-content;
  max-width: 22rem;
  padding: 0.65rem 0.8rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-pop);
  color: var(--fg);
  font-size: 0.82rem;
  line-height: 1.45;
  /* Reset the label styling we're nested inside: .kpi .label and
     .chart-card__title are letterspaced, and the bubble inherits. */
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  text-align: left;
  white-space: normal;
  cursor: auto;
}
.info-tip > * { display: block; }
.info-tip strong { margin-bottom: 0.15rem; }
.info-how {
  margin-top: 0.3rem;
  color: var(--muted);
  font-size: 0.76rem;
}
.info-next { margin-top: 0.35rem; color: var(--accent); }
@media (hover: hover) {
  .info-wrap:hover > .info-tip { display: block; }
}
.info-wrap:focus-within > .info-tip,
.info-wrap.is-open > .info-tip { display: block; }
/* Flipped by tooltip.js when the bubble would run off the right edge — needed
   for the rightmost column of every table. */
.info-tip.flip-right { left: auto; right: 0; }

.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: 1rem;
}

.chart-box { position: relative; height: 320px; }
.chart-box.tall { height: 420px; }

/* ── Filters ─────────────────────────────────────────────────────────── */
.filter-bar {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  align-items: flex-end;
  margin-bottom: 1rem;
}
.filter-bar label { margin-top: 0; }
.filter-bar select, .filter-bar input[type="date"] {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: 0.42rem 0.6rem;
  font: inherit;
  font-size: 0.88rem;
}
.filter-bar button { margin-top: 0; padding: 0.45rem 1rem; }

/* Quick date-range preset pills (shared _date_presets.html) — same look as
   the overview's window toggle so ranges feel identical across pages. */
.date-presets {
  display: inline-flex;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 0.18rem;
  gap: 0.1rem;
  margin-bottom: 1rem;
}
.date-presets a {
  padding: 0.28rem 0.8rem;
  border-radius: var(--r-pill);
  color: var(--muted);
  text-decoration: none;
  font-size: 0.87rem;
  font-weight: 500;
  transition: color 0.15s ease, background 0.15s ease;
}
.date-presets a:hover { color: var(--fg); text-decoration: none; }
.date-presets a.is-active {
  background: var(--card);
  color: var(--fg);
  box-shadow: var(--shadow-sm);
}

/* Unified filter row (_filter_bar.html): presets and pickers on ONE baseline.
   They used to be two separate blocks stacked on top of each other, which is
   why the pills never lined up with the date inputs. */
.filter-bar--unified {
  align-items: center;
  gap: 0.75rem 1rem;
}
.filter-bar--unified .date-presets { margin-bottom: 0; }
/* The covered-span line is a footnote to the whole bar, not another control
   beside the date inputs, so it claims the full row instead of flowing in
   next to "Apply". */
.filter-bar--unified .filter-note {
  flex-basis: 100%;
  max-width: none;
  margin-top: 0;
}
.filter-bar__form {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.filter-bar__divider {
  width: 1px;
  align-self: stretch;
  min-height: 1.75rem;
  background: var(--border);
}
.filter-field { display: flex; align-items: center; gap: 0.4rem; }
.filter-field label {
  margin: 0;
  color: var(--muted);
  font-size: 0.82rem;
  white-space: nowrap;
}
.filter-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  color: var(--muted);
  font-size: 0.82rem;
  white-space: nowrap;
}
.filter-clear { font-size: 0.85rem; color: var(--muted); text-decoration: none; }
.filter-clear:hover { color: var(--accent); }
@media (max-width: 700px) {
  .filter-bar__divider { display: none; }
}

/* Campaign name → deep-dive link inside data tables. */
.campaign-link { color: var(--accent); text-decoration: none; }
.campaign-link:hover { text-decoration: underline; }

/* ── Banners ─────────────────────────────────────────────────────────── */
.banner {
  padding: 0.7rem 0.95rem;
  border-radius: var(--r-md);
  margin-bottom: 1rem;
  font-size: 0.9rem;
  border: 1px solid transparent;
}
.banner.warn { background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn); }
.banner.err  { background: var(--error-soft); border-color: var(--error-line); color: var(--error); }
.banner.ok   { background: var(--success-soft); border-color: var(--success-line); color: var(--success); }
/* Links inside a tinted banner inherit its hue instead of dropping to cobalt,
   which reads as a second, unrelated message. */
.banner a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.banner strong { font-weight: 600; }

.empty {
  text-align: center;
  color: var(--muted);
  padding: 2.5rem 1rem;
  font-size: 0.95rem;
}

.pill {
  display: inline-block;
  padding: 0.12rem 0.55rem;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 0.75rem;
  margin-left: 0.4rem;
}
.pill.ok { background: var(--success-soft); color: var(--success); }
.pill.err { background: var(--error-soft); color: var(--error); }

/* Genuinely clickable chip — distinct from the static .pill/.badge family so
   users can tell at rest (not just on hover) that this does something. */
.example-chip {
  display: inline-block;
  padding: 0.3rem 0.7rem;
  margin: 0;
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--accent);
  border: 1px dashed rgba(45, 91, 255, 0.5);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
}
.example-chip:hover { background: var(--accent-soft); border-style: solid; }

table.data tr:hover { background: var(--row-hover); }
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Reports */
.delta.up { color: var(--success); }
.delta.down { color: var(--error); }
.delta.flat { color: var(--muted); }
.insight-box {
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 1rem 1.25rem;
  border-radius: var(--r-md);
  font-size: 0.95rem;
  white-space: pre-wrap;
  line-height: 1.55;
}
.country-section { margin: 2rem 0; padding-top: 1.5rem; border-top: 1px solid var(--border); }
.country-title { display: flex; align-items: baseline; gap: 1rem; }
.country-title h2 { margin: 0; }
.muted-small { color: var(--muted); font-size: 0.85rem; }

/* Holdout-window caption above the detail charts. */
.chart-note {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: -0.25rem 0 0.75rem;
  font-size: 0.8rem;
  color: var(--muted);
}
.holdout-swatch {
  display: inline-block;
  width: 22px;
  height: 12px;
  border-radius: 3px;
  flex: 0 0 auto;
  background: rgba(45, 91, 255, 0.12);
  border-left: 2px dashed rgba(45, 91, 255, 0.75);
  border-right: 2px dashed rgba(45, 91, 255, 0.75);
}

/* Status badges — light tint with a stronger text colour so they read as
   informational on the light card surface. */
.badge { display: inline-block; padding: 0.1rem 0.55rem; border-radius: var(--r-pill); font-size: 0.75rem; line-height: 1.5; border: 1px solid transparent; font-weight: 500; }
.badge-confirmed { background: var(--accent-soft); color: var(--accent-ink); border-color: rgba(45, 91, 255, 0.22); }
.badge-analyzed  { background: var(--success-soft); color: var(--success); border-color: var(--success-line); }
.badge-archived  { background: var(--surface-2); color: var(--muted); border-color: var(--border); }
.badge-detected  { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.badge-rejected  { background: var(--error-soft); color: var(--error); border-color: var(--error-line); }
.badge-high      { background: var(--success-soft); color: var(--success); border-color: var(--success-line); }
.badge-medium    { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.badge-low       { background: var(--error-soft); color: var(--error); border-color: var(--error-line); }
.badge-critical  { background: var(--error-soft); color: var(--error); border-color: var(--error-line); }
.badge-warn      { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.badge-info      { background: var(--accent-soft); color: var(--accent-ink); border-color: rgba(45, 91, 255, 0.22); }
.nav-badge {
  display: inline-block;
  margin-left: 0.3rem;
  padding: 0 0.35rem;
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.25rem;
  border-radius: var(--r-pill);
  background: var(--error);
  color: #fff;
  vertical-align: middle;
  min-width: 1.25rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.85rem; }
.stat { padding: 0.85rem 0.95rem; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--card); }
.stat .label { font-size: 0.8rem; color: var(--muted); font-weight: 500; text-transform: none; letter-spacing: 0; }
.stat .value { font-size: 1.2rem; font-weight: 600; margin-top: 0.15rem; letter-spacing: -0.025em; font-variant-numeric: tabular-nums; }
.stat .hint  { font-size: 0.78rem; color: var(--muted); margin-top: 0.25rem; }

/* Model diagnostics */
.mape-green  { color: var(--success); }
.mape-amber  { color: var(--warn); }
.mape-red    { color: var(--error); }
.mape-unknown { color: var(--muted); }

/* ── Overview page ───────────────────────────────────────────────────── */
.page-head { margin-bottom: 1.5rem; }
.page-head h1 { margin-bottom: 0.2rem; }
.page-head p { margin: 0; }

.status-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0;
  margin-bottom: 1.5rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}
/* One instrument strip divided by hairlines, rather than five separate
   floating cards saying five one-word things. */
.status-cell {
  padding: 0.8rem 1.05rem;
  border-right: 1px solid var(--border);
}
.status-cell:last-child { border-right: 0; }
@media (max-width: 900px) {
  .status-cell { border-right: 0; border-bottom: 1px solid var(--border); }
  .status-cell:last-child { border-bottom: 0; }
}
.status-label {
  font-size: 0.78rem;
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
}
.status-value { font-size: 0.98rem; font-weight: 600; margin-top: 0.1rem; letter-spacing: -0.02em; }

/* ── Phase 5: evidence badges + CI ranges ────────────────────────── */
/* The evidence dot is a compact, screen-reader-tolerant indicator of
   which view produced a number. Pairs with the badge variant for
   table cells, or stands alone in tight KPI layouts. */
.evidence-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.1rem 0.55rem;
  border-radius: var(--r-pill);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  border: 1px solid transparent;
  line-height: 1.5;
}
.evidence-badge::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.evidence-badge.evidence-high {
  background: var(--success-soft);
  color: var(--success);
  border-color: var(--success-line);
}
.evidence-badge.evidence-medium {
  background: var(--warn-soft);
  color: var(--warn);
  border-color: var(--warn-line);
}
.evidence-badge.evidence-low {
  background: var(--surface-2);
  color: var(--muted);
  border-color: var(--border);
}

/* CI range rendered next to a point estimate. Keep the muted secondary
   small so the primary number stays scannable. */
.ci-range {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.ci-range .ci-bounds {
  color: var(--muted);
  font-size: 0.82em;
  margin-left: 0.35rem;
  font-weight: 400;
}
.status-hint  { font-size: 0.82rem; color: var(--muted); margin-top: 0.2rem; }

/* ── Overview performance charts (above "Dive in") ─────────────────── */
.overview-charts__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.overview-charts__head h2 { margin: 0; }

/* Segmented control reused for the week/month toggle. */
.seg {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 0.18rem;
  background: var(--surface-2);
}
.seg-btn {
  margin: 0;
  padding: 0.28rem 0.8rem;
  background: transparent;
  color: var(--muted);
  border: 0;
  border-radius: var(--r-pill);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.seg-btn:hover { color: var(--fg); background: transparent; }
.seg-btn.is-active {
  background: var(--card);
  color: var(--fg);
  box-shadow: var(--shadow-sm);
}

.charts-grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: 0.85rem;
}
.charts-grid .chart-card--wide { grid-column: 1 / -1; }
/* Two side-by-side charts only above ~1200px viewport. Below that the
   columns get too narrow for Chart.js' axis labels to render legibly, so we
   stack everything full-width — better than two cramped charts. */
@media (max-width: 1200px) {
  .charts-grid { grid-template-columns: 1fr; }
}
/* Equal-width 2x2 for a set of related small charts. Reading four comparable
   charts side by side beats scrolling past four full-width ones. */
.charts-grid--quad { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 1100px) {
  .charts-grid--quad { grid-template-columns: 1fr; }
}

.chart-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 0.95rem 1.05rem 1.15rem;
}
.chart-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.65rem;
  flex-wrap: wrap;
}
.chart-card__title {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--fg);
  text-transform: none;
  letter-spacing: -0.01em;
}
.chart-card__total {
  font-size: 1.3rem;
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.chart-card__metric {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex: 1;
}
.chart-card__metric label {
  margin: 0;
  font-size: 0.8rem;
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0;
}
.chart-card__metric select {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: 0.28rem 0.5rem;
  font: inherit;
  font-size: 0.85rem;
}
.chart-canvas {
  position: relative;
  height: 280px;
  overflow: hidden;
}
.chart-canvas--tall { height: 380px; }

/* Inline horizontal mini-bars inside the Channel Statistics table. The bar
   width is set via the --pct CSS variable, scaled per-column so the longest
   value in each column hits ~100% — gives an at-a-glance comparison without
   another chart canvas. */
.data-bars td .bar-cell {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.55rem;
}
.data-bars td .bar-cell .bar-track {
  flex: 1;
  height: 6px;
  background: rgba(11, 13, 18, 0.07);
  border-radius: var(--r-pill);
  overflow: hidden;
  max-width: 80px;
}
.data-bars td .bar-cell .bar-fill {
  display: block;
  height: 100%;
  width: calc(var(--pct, 0) * 1%);
  background: var(--accent);
  border-radius: var(--r-pill);
}
.data-bars td .bar-cell .bar-fill.pos { background: var(--success); }
.data-bars td .bar-cell .bar-fill.neg { background: var(--error); }
.data-bars td .bar-cell .bar-val { font-variant-numeric: tabular-nums; min-width: 4rem; text-align: right; }

/* Exposure funnel on the campaign deep-dive. Wider track than the inline
   table bars, because here the bar *is* the panel rather than a hint beside a
   number, plus a reference marker at the channel median for that step:
   "1.576 of 4.429 delivered opened" says nothing until you know the other
   mailings sit at 33%. Needs a positioning context, which .bar-track doesn't
   otherwise have. */
.funnel-bars td .bar-cell .bar-track { max-width: none; position: relative; }
.funnel-bars td .bar-cell .bar-ref {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--pct, 0) * 1%);
  width: 2px;
  margin-left: -1px;
  background: var(--muted);
  opacity: 0.6;
}

.link-card {
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s ease, background 0.15s ease;
  display: block;
}
.link-card:hover { border-color: var(--border-strong); text-decoration: none; }
.link-card:hover .value { color: var(--accent); }

.callout {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.btn-secondary {
  display: inline-block;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--fg);
  text-decoration: none;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.btn-secondary:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--fg); text-decoration: none; }

.link-btn {
  background: none;
  border: 0;
  color: var(--accent);
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
}
.link-btn:hover { text-decoration: underline; }

/* ── Impersonation banner ───────────────────────────────────────────────
   Sticky amber strip across the top of every page when an admin is
   viewing as another org. Loud on purpose — see manager spec §5. */
.impersonation-banner {
  position: sticky;
  top: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 1.25rem;
  min-height: 36px;
  background: var(--warn-soft);
  color: var(--warn);
  font-size: 0.92rem;
  border-bottom: 1px solid var(--warn-line);
}
.impersonation-banner strong { font-weight: 600; color: var(--warn); }
.impersonation-banner .button {
  background: var(--warn);
  color: var(--warn-soft);
  border: 0;
  padding: 0.3rem 0.7rem;
  border-radius: var(--r-sm);
  font: inherit;
  font-weight: 500;
  font-size: 0.85rem;
  cursor: pointer;
}
.impersonation-banner .button:hover { background: #6f480b; }

body.impersonating main.container {
  border: 1px solid var(--warn-line);
  border-radius: var(--r-md);
}

/* ── Multi-select dropdown — looks like a regular <select> but multi-pick ── */
details.multiselect {
  position: relative;
  display: inline-block;
}
details.multiselect > summary {
  list-style: none;
  cursor: pointer;
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: 0.42rem 1.6rem 0.42rem 0.6rem;
  font-size: 0.88rem;
  min-width: 160px;
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
details.multiselect > summary::-webkit-details-marker { display: none; }
details.multiselect > summary::after {
  content: "";
  position: absolute;
  right: 0.6rem;
  top: 50%;
  width: 10px;
  height: 10px;
  margin-top: -5px;
  background: var(--muted);
  -webkit-mask: var(--icon-chevron-down) center / 10px 10px no-repeat;
  mask: var(--icon-chevron-down) center / 10px 10px no-repeat;
  transition: transform 0.18s var(--ease-out);
}
details.multiselect[open] > summary::after { transform: rotate(180deg); }
details.multiselect .multiselect-label {
  color: var(--muted);
  font-size: 0.78rem;
}
details.multiselect .multiselect-summary {
  color: var(--fg);
  font-weight: 500;
}
details.multiselect .multiselect-panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-height: 280px;
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 0.35rem;
  box-shadow: var(--shadow-pop);
}
details.multiselect .multiselect-hint {
  padding: 0.2rem 0.45rem 0.4rem;
  font-size: 0.75rem;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
  margin-bottom: 0.35rem;
}
/* Sits under the Entity filter and says how many markets the model left out.
   The filter used to list every country with a spend row (159 for one account
   whose model kept 2); now that it lists only the modelled markets, a missing
   country has to read as a decision rather than as data going astray. */
.filter-bar .filter-note {
  margin-top: 0.3rem;
  max-width: 22rem;
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--muted);
}
details.multiselect .multiselect-actions {
  display: flex;
  gap: 0.5rem;
  padding: 0 0.45rem 0.4rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 0.35rem;
}
details.multiselect .multiselect-actions a {
  font-size: 0.78rem;
  color: var(--accent);
  text-decoration: none;
}
details.multiselect .multiselect-actions a:hover { text-decoration: underline; }
details.multiselect .multiselect-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.45rem;
  margin: 0;
  font-size: 0.85rem;
  color: var(--fg);
  border-radius: var(--r-sm);
  cursor: pointer;
}
details.multiselect .multiselect-option:hover {
  background: var(--row-hover);
}
details.multiselect .multiselect-option input[type=checkbox] {
  margin: 0;
  accent-color: var(--accent);
}

/* ── Conversion goal toggle ────────────────────────────────────────────── */
.goal-toggle {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  user-select: none;
}
.goal-toggle input[type=checkbox] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.goal-pill {
  font-size: .78rem;
  padding: .18rem .65rem;
  border-radius: var(--r-pill);
  font-weight: 500;
  letter-spacing: 0;
  border: 1px solid transparent;
  transition: background .15s, color .15s, border-color .15s;
}
.goal-pill-on {
  background: var(--success-soft);
  color: var(--success);
  border-color: var(--success-line);
}
.goal-pill-off {
  background: var(--surface-2);
  color: var(--muted);
  border-color: var(--border);
}
.goal-toggle:hover .goal-pill-on { background: #dcece6; }
.goal-toggle:hover .goal-pill-off { background: var(--row-hover); color: var(--fg); }
/* Live state: reflect the checkbox via :checked so clicking gives instant feedback
   even though the pill's class is rendered server-side. */
.goal-pill .goal-pill-on-text,
.goal-pill .goal-pill-off-text { display: none; }
.goal-toggle input[type=checkbox]:checked ~ .goal-pill .goal-pill-on-text { display: inline; }
.goal-toggle input[type=checkbox]:not(:checked) ~ .goal-pill .goal-pill-off-text { display: inline; }
.goal-toggle input[type=checkbox]:checked ~ .goal-pill {
  background: var(--success-soft);
  color: var(--success);
  border-color: var(--success-line);
}
.goal-toggle input[type=checkbox]:not(:checked) ~ .goal-pill {
  background: var(--surface-2);
  color: var(--muted);
  border-color: var(--border);
}
.goal-toggle:hover input[type=checkbox]:checked ~ .goal-pill { background: #dcece6; }
.goal-toggle:hover input[type=checkbox]:not(:checked) ~ .goal-pill { background: var(--row-hover); color: var(--fg); }
.goal-row-off td:not(:first-child) { opacity: .45; }
tr:has(.goal-toggle input[type=checkbox]:not(:checked)) td:not(:first-child) { opacity: .45; }
tr:has(.goal-toggle input[type=checkbox]:checked) td:not(:first-child) { opacity: 1; }

/* ── Sortable table headers ────────────────────────────────────────────── */
.sort-link {
  color: inherit;
  text-decoration: none;
  font-weight: inherit;
}
.sort-link:hover {
  color: var(--accent);
  text-decoration: none;
}
.sort-link.active {
  color: var(--fg);
  font-weight: 600;
}

/* ── Training progress banner ──────────────────────────────────────────── */
.training-banner {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .85rem 1.05rem;
  margin: .5rem 0 1rem;
  background: var(--accent-soft);
  border: 1px solid rgba(45, 91, 255, 0.22);
  border-radius: var(--r-md);
  color: var(--fg);
}
.training-banner strong { color: var(--accent-ink); }
.spinner {
  width: 16px;
  height: 16px;
  border: 2px solid rgba(45, 91, 255, 0.25);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spinner-rot .8s linear infinite;
  flex-shrink: 0;
}
@keyframes spinner-rot { to { transform: rotate(360deg); } }

/* ── Connector picker (Add a connection) ───────────────────────────────── */
.connector-group-label {
  font-size: .82rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin: 1.25rem 0 .5rem;
}
.connector-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: .5rem;
}
.connector-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: .5rem;
  padding: 1rem .5rem .75rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  text-decoration: none;
  color: var(--fg);
  background: var(--card);
  transition: border-color .15s, background .15s;
  min-height: 110px;
  justify-content: flex-start;
}
.connector-card:hover {
  border-color: var(--border-strong);
  background: var(--surface-2);
  text-decoration: none;
}
.connector-card.is-connected {
  border-color: var(--success-line);
  background: var(--success-soft);
}
.connector-card.is-connected:hover {
  border-color: var(--success);
  background: var(--success-soft);
}
.connector-card .connected-check {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--success);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 2px var(--card);
}

/* ── Live connection list ──────────────────────────────────────────────── */
.connection-list {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  display: flex;
  flex-direction: column;
  gap: .5rem;
}
.connection-row {
  display: flex;
  align-items: center;
  gap: .9rem;
  padding: .7rem .9rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--card);
}
.connection-row .connector-icon {
  width: 36px;
  height: 36px;
}
.connection-meta {
  flex: 1;
  min-width: 0;
}
.connection-name {
  font-weight: 600;
  color: var(--fg);
  text-decoration: none;
  letter-spacing: -0.01em;
}
.connection-name:hover {
  color: var(--accent);
}
.connection-sub {
  font-size: .8rem;
  color: var(--muted);
  margin-top: .15rem;
}
.connection-actions {
  display: flex;
  gap: .25rem;
  align-items: center;
}
.connection-actions form {
  display: contents;
}
.icon-btn {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  margin: 0;
  font: inherit;
  line-height: 1;
  vertical-align: middle;
  text-decoration: none;
  transition: color .15s, border-color .15s, background .15s;
}
.icon-btn svg {
  display: block;
}
.icon-btn:hover {
  color: var(--accent);
  border-color: rgba(45, 91, 255, 0.35);
  background: var(--accent-soft);
  text-decoration: none;
}
img.connector-icon {
  width: 40px;
  height: 40px;
  object-fit: contain;
  flex-shrink: 0;
}
.connector-name {
  font-size: .78rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--fg);
}
.connector-badge {
  font-size: .66rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
  background: var(--surface-2);
  padding: 1px 6px;
  border-radius: var(--r-pill);
  margin-top: auto;
}
.connector-card.is-coming-soon {
  cursor: default;
  background: transparent;
  border-style: dashed;
}
.connector-card.is-coming-soon:hover {
  border-color: var(--border);
  background: transparent;
}
.connector-card.is-coming-soon .connector-icon,
.connector-card.is-coming-soon .connector-name {
  opacity: .5;
  filter: grayscale(1);
}
.connector-badge-soon {
  color: var(--muted);
  background: var(--surface-2);
}
.connector-badge-connected {
  color: var(--success);
  background: var(--success-soft);
}

/* ── 3-step setup flow (Add a connection) ──────────────────────────────── */
.card-head-inline {
  display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap;
}
.card-head-inline h2 { margin: 0; }
.card-head-inline .muted { margin: 0; font-size: .875rem; }

.setup-steps {
  display: grid;
  /* An asymmetric rail, not three equal columns. Step 2 holds 38 channel
     tiles; steps 1 and 3 hold two and one. Equal thirds starved the picker,
     and the stacked full-width version that replaced them pushed the first
     channel tile to y=949 on a 900px viewport: the page's whole subject was
     the one thing off-screen. Steps 1 and 3 and the Windsor note go in the
     narrow rail (they are single-tile prerequisites and read fine at 260px);
     step 2 gets the wide column and starts at the top of the grid. */
  grid-template-columns: minmax(230px, 264px) minmax(0, 1fr);
  align-items: start;
  gap: 1rem;
  margin: 1rem 0;
}
/* Explicit columns, auto rows. Rail items keep DOM order (1, 3, note) down
   column 1 while step 2 spans beside them, so the reading order the numbers
   promise is also the DOM and focus order. */
.setup-step--rail,
.setup-steps > .step-note-info { grid-column: 1; }
.setup-step--main { grid-column: 2; grid-row: 1 / span 3; }
.setup-step {
  display: flex;
  flex-direction: column;
  gap: .55rem;
  padding: .85rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  min-width: 0;
}
.step-head {
  display: flex;
  align-items: center;
  gap: .6rem;
}
.step-head h3 {
  margin: 0;
  font-size: .95rem;
  font-weight: 600;
}
.step-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--fg);
  color: var(--bg);
  font-size: .74rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.step-note {
  margin: 0;
  font-size: .82rem;
  color: var(--muted);
  line-height: 1.5;
}
.step-note-info {
  margin: 0;
  padding: .75rem .85rem;
  background: var(--accent-soft);
  border-radius: var(--r-md);
  color: var(--fg);
  font-size: .85rem;
}
.step-note-info strong { color: var(--fg); font-weight: 600; }
.step-connection-list {
  margin-bottom: 0;
}
.step-connection-list .connection-row {
  padding: .5rem .6rem;
  background: var(--card);
}
.step-connection-list .connector-icon {
  width: 28px;
  height: 28px;
}

/* ── Generic collapsed disclosure (More connectors, manual CSV, advanced
   settings) — same pattern everywhere so it always looks familiar, and
   reads as a control (bordered row) rather than a stray line of text. ── */
.disclosure-group {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}
.disclosure summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  font-size: .85rem;
  font-weight: 500;
  color: var(--fg);
  padding: .5rem .8rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--card);
  transition: border-color .15s, background .15s, color .15s;
}
.disclosure summary::-webkit-details-marker { display: none; }
.disclosure summary:hover {
  color: var(--fg);
  border-color: var(--border-strong);
  background: var(--surface-2);
}
.disclosure summary:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}
.disclosure-chevron {
  flex-shrink: 0;
  transition: transform .18s var(--ease-out);
}
.disclosure[open] .disclosure-chevron { transform: rotate(90deg); }
.disclosure .muted-small { margin: .6rem 0 .75rem; }
.disclosure > summary + * { margin-top: .75rem; }

/* Below this the rail has no room to be a rail: 240px of column would leave
   the picker under 400px, which is three tiles wide at best. Stack in DOM
   order (1, 2, 3, note) and let the picker use the full width. */
@media (max-width: 1080px) {
  .setup-steps { grid-template-columns: 1fr; }
  .setup-step--rail,
  .setup-step--main,
  .setup-steps > .step-note-info { grid-column: 1; grid-row: auto; }
  .setup-step--rail .ch-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  }
}

/* ── Custom file input (hides the browser/OS-locale native label) ──────── */
.file-upload-form {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.file-input-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  overflow: hidden;
}
.file-input-label {
  display: inline-block;
  cursor: pointer;
  margin: 0;
}
.file-input-name {
  flex: 1;
  min-width: 0;
}

/* ── Sync progress bar (connection detail page) ───────────────────── */
.progress-track {
  position: relative;
  height: 8px;
  width: 100%;
  background: rgba(11, 13, 18, 0.07);
  border-radius: var(--r-pill);
  overflow: hidden;
}
/* Driven by --p (0–1) on transform, not by width/margin: both of those
   relayout the bar on every one of the 3-second poller's updates, and the
   indeterminate loop relaid it out on every frame. See sync-progress.js. */
.progress-fill {
  height: 100%;
  width: 100%;
  background: var(--accent);
  border-radius: var(--r-pill);
  transform: scaleX(var(--p, 0));
  transform-origin: left center;
  transition: transform 0.4s var(--ease-out);
}
.progress-fill.indeterminate {
  transition: none;
  animation: progress-slide 1.6s ease-in-out infinite;
}
@keyframes progress-slide {
  0%   { transform: translateX(-30%) scaleX(0.3); }
  100% { transform: translateX(100%) scaleX(0.3); }
}
@media (prefers-reduced-motion: reduce) {
  .progress-fill { transition: none; }
  .progress-fill.indeterminate { animation-duration: 3.2s; }
}

/* ── Metric Tree (/dashboards/metric-tree) ────────────────────────────────
   Pure-CSS nested-list "org chart": each <ul> is a row of children laid out
   in a flex row, with ::before/::after pseudo-elements drawing the
   connector lines up to the parent. Node boxes reuse .kpi/.label/.value/
   .delta so they look consistent with the rest of the app. */
.mt-layout {
  display: flex;
  gap: 1.5rem;
  align-items: flex-start;
  flex-wrap: wrap;
}
.mt-scroll {
  flex: 3 1 640px;
  overflow-x: auto;
  padding: 0.5rem 0 1.5rem;
}
.mt-detail {
  flex: 2 1 300px;
  min-width: 280px;
  position: sticky;
  top: calc(var(--topbar-h) + 1rem);
}
.mt-detail table.data { font-size: 0.9rem; }
.mt-breadcrumb {
  margin: 0 0 0.25rem;
  font-size: 0.8rem;
  color: var(--muted);
}
.mt-subtitle {
  margin: -0.25rem 0 0.75rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.mt-tree, .mt-tree ul {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}
.mt-tree { padding-top: 0.5rem; }
.mt-tree ul { padding-top: 1.75rem; }
.mt-item {
  position: relative;
  flex: 1;
  padding: 1.75rem 0.6rem 0;
  text-align: center;
}
.mt-item::before, .mt-item::after {
  content: "";
  position: absolute;
  top: 0;
  width: 50%;
  height: 1.75rem;
  border-top: 1px solid var(--border-strong);
}
.mt-item::before { right: 50%; }
.mt-item::after { left: 50%; border-left: 1px solid var(--border-strong); }
.mt-item:first-child::before { border: 0 none; }
/* Only drop the top (trunk) border here — border-left is this item's own
   down-stem into its button, not just a trunk extension, and must survive. */
.mt-item:last-child::after { border-top: none; }
/* only-child: vertical stem only — must come after first/last rules to override */
.mt-item:only-child { padding-top: 1.75rem; }
.mt-item:only-child::before { display: none; }
.mt-item:only-child::after {
  left: calc(50% - 0.5px);
  width: 1px;
  border-top: none;
  border-left: 1px solid var(--border-strong);
}
/* root node overrides only-child padding — must come after */
.mt-tree > .mt-item { padding-top: 0; }
.mt-tree > .mt-item::before, .mt-tree > .mt-item::after { display: none; }

/* Anchors the glossary dot to the node card. The dot has to live outside the
   node <button> (nested buttons are invalid, and the inner click would fire
   the node's hx-get), so this wrapper is what gives it something to sit on.

   Clipping: .mt-item and .mt-node-btn are both overflow:visible, but .mt-scroll
   above them is not -- it sets overflow-x:auto, and CSS then computes overflow-y
   to auto as well, so it clips on BOTH axes. The right-aligned .info-tip below
   is what keeps tooltips inside it. Measured in headless Chrome across every
   node of the (fixed, 11-node) tree: zero overflow on either axis. If you widen
   the tooltip or change the tree's shape, re-measure. */
.mt-node-wrap {
  position: relative;
  display: inline-block;
}
.mt-node-wrap > .info-wrap {
  position: absolute;
  top: 0.4rem;
  right: 0.5rem;
  /* The dot's inline margin is for sitting beside a label; here it is
     positioned outright and the offset would push it off the card. */
  margin-left: 0;
  font-size: 0.95rem;
}
/* Tree nodes sit in a horizontal row, so a right-hand node's tooltip would
   run off-screen. tooltip.js flips it, but the rightmost cards are the common
   case here rather than the exception. */
.mt-node-wrap > .info-wrap > .info-tip { left: auto; right: 0; }

.mt-node-btn {
  display: inline-block;
  min-width: 150px;
  max-width: 220px;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
/* Long unbroken values (e.g. "€602,345") have no natural wrap point at this
   card width. A smaller font first (so most values fit on one line at the
   wider max-width above); overflow-wrap is just the fallback for the rare
   value that still doesn't fit, rather than the primary mechanism. */
.mt-node-btn .value { font-size: 1.25rem; overflow-wrap: anywhere; }
.mt-node-btn:hover { border-color: var(--border-strong); }
.mt-node-btn.is-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent) inset;
}
.mt-status-no_data .value, .mt-status-not_tracked .value { color: var(--muted); font-size: 1.1rem; }
.mt-deltas { display: flex; flex-direction: column; gap: 2px; }


/* ── Site footer ──
   Present on every page so the public legal pages are discoverable (Google's
   OAuth reviewer checks that the Privacy Policy is linked, not just reachable). */
.sitefoot {
  max-width: 1600px;
  margin: 4rem auto 2rem;
  padding: 1.1rem 1.5rem 0;
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem 1.25rem;
  font-size: 0.82rem;
  color: var(--muted);
}
.sitefoot nav { display: flex; flex-wrap: wrap; gap: 1.25rem; }
.sitefoot a { color: var(--muted); text-decoration: none; }
.sitefoot a:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 2px; }

/* ── Sign-in / password pages ────────────────────────────────────────────
   Two fields in a 720px box read as an unfinished form. The column narrows
   to the width of its content and drops toward the optical centre, so the
   front door looks like a door rather than the top of an empty page. */
main.container:has(> .auth) { margin-top: 4.5rem; }
.auth {
  max-width: 400px;
  margin-inline: auto;
  padding: 1.9rem 1.9rem 1.6rem;
}
.auth h1 { font-size: 1.45rem; margin-bottom: 0.3rem; }
.auth > p:first-of-type { margin-top: 0; color: var(--muted); font-size: 0.92rem; }
.auth label { font-size: 0.85rem; }
.auth button { width: 100%; padding: 0.6rem 1rem; margin-top: 1.1rem; }
.auth .auth-foot {
  margin: 1.25rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  font-size: 0.88rem;
  color: var(--muted);
}

/* ── Legal pages (/privacy, /terms) ── */
.container-wide { max-width: 900px; }
/* 68ch measure — long-form legal text set to the full 900px column is a wall. */
.legal p, .legal li { font-size: 0.95rem; max-width: 68ch; }
.legal h2 {
  margin-top: 2.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  font-size: 1.15rem;
}
.legal h3 { margin-top: 1.75rem; font-size: 1rem; }
.legal ul { padding-left: 1.25rem; }
.legal li { margin-bottom: 0.4rem; }
.legal table.data { margin-bottom: 0.5rem; }
.legal table.data th { vertical-align: top; }
.legal table.data td { vertical-align: top; font-size: 0.88rem; }
.legal code { font-size: 0.85em; word-break: break-all; }
.legal .legal-foot { margin-top: 2.5rem; font-size: 0.88rem; }
.legal-draft {
  background: var(--warn-soft);
  border: 1px solid var(--warn-line);
  color: var(--warn);
  border-radius: var(--r-md);
  padding: 0.75rem 1rem;
  font-size: 0.9rem;
}

/* ── Icon masks ───────────────────────────────────────────────────────────
   Drawn once, used as CSS masks so a chevron is the same shape, stroke and
   weight everywhere instead of whatever "▾" happens to be in the user's
   font. Stroke width 1.75 matches the inline SVGs in the templates. */
:root {
  --icon-chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 6 8 10.5 12.5 6'/%3E%3C/svg%3E");
}

/* ── Windsor per-channel picker ─────────────────────────────────────────────
   Replaces the single "Connect ad and social accounts" tile in step 2. Wider
   min-track than .connector-grid because these tiles carry a status line
   ("8 accounts · 43,694 rows") that a 96px column would wrap to shreds. */
.ch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
  gap: .35rem;
}
.ch {
  position: relative;
  min-width: 0;
  display: flex; align-items: center; gap: .55rem;
  padding: .4rem .6rem;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--card); color: var(--fg);
  text-decoration: none;
  transition: border-color .15s, background .15s;
}
.ch:hover { border-color: var(--border-strong); background: var(--surface-2); text-decoration: none; }
.ch-ico { width: 24px; height: 24px; flex-shrink: 0; object-fit: contain; }
.ch-mono {
  width: 24px; height: 24px; flex-shrink: 0; border-radius: 6px;
  display: grid; place-items: center;
  color: #fff; font-size: .72rem; font-weight: 700; letter-spacing: -.02em;
}
.ch-body { flex: 1; min-width: 0; }
.ch-name { display: block; font-size: .875rem; font-weight: 500; line-height: 1.3; }
.ch-hint, .ch-stat { display: block; font-size: .72rem; margin-top: .05rem; }
/* One line per tile, but only inside the channel picker. A grid row is as
   tall as its tallest cell, so in a 38-tile grid one wrapped status stretches
   every tile beside it: measured at 111px against a 45px neighbour, four such
   rows cost more height than the entire "most popular" section. Anything
   longer than the column lives in the tile's title instead.

   Everywhere else — the rail, the More-connectors grid — tiles are few and
   wide, wrapping costs nothing, and truncating a connection's own name would
   be a real loss. */
.ch-group .ch-name,
.ch-group .ch-hint,
.ch-group .ch-stat {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ch-hint { color: var(--muted-soft); }
.ch-stat { color: var(--success); font-variant-numeric: tabular-nums; }
.ch.is-on { border-color: var(--success-line); background: var(--success-soft); }
.ch-check {
  width: 16px; height: 16px; border-radius: 50%; background: var(--success);
  display: grid; place-items: center; flex-shrink: 0;
}
.ch-group { margin-top: 1.4rem; }
.ch-ghead {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  margin: 0 0 .35rem;
}
.ch-ghead h4 { margin: 0; font-size: .85rem; font-weight: 600; flex-shrink: 0; }
.ch-gnote { margin: 0; font-size: .78rem; color: var(--muted); max-width: 104ch; }

/* Inside a setup step the picker only gets a third of the card, so the tiles
   go single-column — same as the connector tiles in steps 1 and 3. Without
   this the 184px min-track forces a horizontal squeeze and the status line
   ("8 accounts · 43,694 rows") wraps to three lines. */
.setup-step .ch-group { margin-top: .75rem; }
/* The rail is 260px wide and holds one or two sources. A shared multi-column
   track wrapped "synced 29 Aug 2026 · 4,565 rows" over five lines and left a
   165px-tall tile next to five empty columns. */
.setup-step--rail .ch-grid { grid-template-columns: 1fr; }
/* Step 2 opens straight onto the argument, so it carries the top margin the
   removed step-note used to. */
.setup-step--main .ch-why { margin-top: .15rem; }

/* Spinner while we confirm a fresh authorisation with Windsor, and a brief
   flash when it lands — the popup closes with no signal of its own, so the
   tile has to say something or the customer wonders whether it worked. */
.ch-spin {
  width: 14px; height: 14px; flex-shrink: 0; border-radius: 50%;
  border: 2px solid var(--border-strong); border-top-color: var(--accent);
  animation: ch-spin .7s linear infinite;
}
@keyframes ch-spin { to { transform: rotate(360deg); } }
.ch.just-connected { animation: ch-pop 1.1s ease-out; }
@keyframes ch-pop {
  0%   { background: var(--success-soft); box-shadow: 0 0 0 0 var(--success-line); }
  35%  { box-shadow: 0 0 0 5px transparent; }
  100% { background: var(--success-soft); box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .ch-spin { animation: none; }
  .ch.just-connected { animation: none; }
}

/* The tick, the status line and the hint are toggled with the `hidden`
   attribute — by the server on first paint, then by the picker's JS when a
   fresh authorisation lands. `hidden` only works if nothing outranks it, and
   every one of these carries an explicit `display` above, which does: an
   author-level `display: grid` beats the UA stylesheet's `[hidden]`. Without
   this rule every tile paints a green tick on page load regardless of state,
   which is exactly the "everything looks connected" bug. */
.ch-stat[hidden],
.ch-hint[hidden],
.ch-check[hidden],
.ch-spin[hidden] { display: none; }


/* The "why bother" line above the groups. Sits at body size rather than as a
   muted footnote: it is the argument that gets the second and third channel
   connected, and a completeness problem is the one thing the customer can fix
   here that we cannot fix later. */
.ch-why {
  margin: 0 0 1rem;
  font-size: .85rem;
  color: var(--fg);
  max-width: 78ch;
}
.ch-group:first-of-type { margin-top: 0; }

/* Steps 1 and 3 reuse the channel tile, so a connected source needs sync and
   settings inside the tile. Icon-only and low contrast until hovered: they sit
   next to the source name and must not read as the primary action, which is
   still "connect the thing". */
.ch-act {
  display: grid; place-items: center;
  width: 26px; height: 26px; flex-shrink: 0;
  border: 1px solid transparent; border-radius: var(--r-sm, 6px);
  background: none; color: var(--muted-soft); cursor: pointer; padding: 0;
}
@media (pointer: coarse) {
  .ch-act { width: 40px; height: 40px; }
}
.ch-act:hover {
  color: var(--fg); border-color: var(--border);
  background: var(--card); text-decoration: none;
}
/* Connected native connectors: name, sync state and two controls in one tile.
   At the channel grid's 180px floor the name alone was clipped. */
.disclosure .ch-grid {
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
}
.ch form { display: flex; }
/* The name is a link when the tile is a div rather than an anchor. */
.ch-name-link { color: var(--fg); text-decoration: none; }
.ch-name-link:hover { text-decoration: underline; }
.step-add-another { margin-top: .6rem; font-size: .78rem; }

/* A source we cannot connect yet. Muted rather than greyed to unreadable:
   it is information ("we know about Shopify"), not an error, and it must not
   look like something that failed. */
.ch.is-soon {
  background: var(--surface-2);
  border-style: dashed;
  cursor: default;
}
.ch.is-soon .ch-ico { opacity: .55; }
.ch.is-soon .ch-name { color: var(--muted); }

/* A source whose last sync failed. Same tile, different reading: red instead
   of green, and an exclamation instead of a tick, so "connected" never gets
   confused with "working". */
.ch.is-err { border-color: var(--error); background: var(--error-soft); }
.ch.is-err .ch-stat { color: var(--error); }
.ch-warn {
  width: 16px; height: 16px; border-radius: 50%; background: var(--error);
  display: grid; place-items: center; flex-shrink: 0;
  color: #fff; font-size: .68rem; font-weight: 700; line-height: 1;
}

/* ── AI connectors (/settings/connectors) ───────────────────────────────────
   The page used to be a URL in a <code> and a table. The three cards below
   are the whole connect flow: pick your assistant, press one button. Wider
   min-track than .connector-grid because each card carries a sentence of
   explanation and a call to action, not just a logo. */
.ai-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: .75rem;
  margin-top: 1rem;
}
.ai-card {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--card);
}
/* Claude is the path that actually works in one click, so it is the only
   card drawn as the recommendation. */
.ai-card-primary { border-color: var(--accent); background: var(--accent-soft); }
.ai-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.ai-mark {
  width: 26px; height: 26px; flex-shrink: 0; border-radius: 7px;
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--muted);
  font-size: .72rem; font-weight: 700; letter-spacing: -.03em;
}
.ai-mark-accent { background: var(--accent); color: #fff; }
.ai-name { font-weight: 600; font-size: .95rem; }
.ai-body { margin: 0; font-size: .875rem; color: var(--fg); }
.ai-foot { margin: 0; font-size: .78rem; color: var(--muted); }
/* margin-top:auto pins every call to action to the same baseline, so three
   cards with different amounts of text still read as one row of buttons. */
.ai-cta {
  margin-top: auto;
  align-self: flex-start;
  padding: .5rem .9rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: .9rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background .15s ease;
}
.ai-cta:hover { background: var(--accent-ink); color: #fff; text-decoration: none; }
.ai-cta-quiet {
  background: var(--card); color: var(--fg); border-color: var(--border-strong);
}
.ai-cta-quiet:hover { background: var(--surface-2); color: var(--fg); }
.ai-steps summary { font-size: .82rem; color: var(--muted); cursor: pointer; }
.ai-steps ol { margin: .5rem 0 .5rem 1.1rem; padding: 0; }
.ai-steps li { font-size: .82rem; margin-bottom: .3rem; }

/* A value plus its copy button. The value wraps rather than scrolling: a
   connector URL cut off mid-host looks like the wrong URL. */
.copy-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.copy-row .btn-secondary { flex-shrink: 0; padding: .35rem .7rem; font-size: .82rem; }
.copy-val {
  flex: 1; min-width: 0;
  padding: .35rem .55rem;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  font-size: .8rem;
  overflow-wrap: anywhere;
}

.prompt-list, .scope-list { list-style: none; margin: .75rem 0 0; padding: 0; }
.prompt-list li {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  padding: .55rem 0;
  border-top: 1px solid var(--border);
}
.prompt-list .prompt-text { flex: 1; min-width: 0; font-size: .9rem; }
.prompt-list .btn-secondary { flex-shrink: 0; padding: .35rem .7rem; font-size: .82rem; }

.scope-head { margin: 0 0 .25rem; font-size: .9rem; font-weight: 600; }
.scope-list li { padding: .4rem 0; border-top: 1px solid var(--border); }
.scope-list li strong { display: block; font-size: .9rem; font-weight: 600; }
.scope-list li span { display: block; }
/* The "cannot" list gets no tick and no colour: it is a boundary, not a
   feature checklist, and dressing it up in green reads as bragging. */
.scope-list-no li span { font-size: .875rem; color: var(--muted); }

.token-form { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.token-form input { margin: 0; }
.token-form button { margin-top: 0; }

/* The global `button { margin-top: 1.25rem }` is written for the button at the
   bottom of a form. Wherever a button sits in a row beside something else that
   inherited margin drops it out of line: every Disconnect on this page used to
   sit 20px below the text in its own table row. */
.copy-row .btn-secondary,
.prompt-list .btn-secondary,
.connector-table button,
.card-head-inline form button { margin-top: 0; }
.connector-table form { margin: 0; }
/* Pushes a card's one action to the right of its heading. .card-head-inline
   itself only sets flex + baseline, so without this the button lands against
   the h2 as if it were part of the title. */
.card-head-inline form { margin: 0 0 0 auto; }
