/* Data Explorer — "ledger instrument" system
   ink chrome, paper surface, viridian signal, mono figures */
:root {
  --ink: #171c22;
  --ink-2: #232a33;
  --paper: #fbfbfc;
  --line: #d9dde2;
  --line-soft: #eceef1;
  --text: #1f242b;
  --text-dim: #5b6572;
  --accent: #0e7c66;
  --accent-soft: #e2f0ec;
  --accent-on-dark: #2fae93;   /* brightened accent for use on the dark chrome */
  --warn: #b7791f;
  --neg: #a63a2b;
  --mono: 'Cascadia Mono', Consolas, 'SF Mono', monospace;
  --sans: 'Segoe UI', system-ui, -apple-system, sans-serif;

  /* ---- chrome tokens -------------------------------------------------
     `--ink` does double duty in this file: it is both the dark CHROME
     SURFACE (top bar, tab strip, status bar, admin nav) and the darkest
     TEXT colour on the light paper surface (headings, metric figures,
     `.dash-title`, …). Those two roles cannot share one token the moment
     a theme wants light chrome — flipping `--ink` to white would turn
     roughly two dozen headings white-on-white.

     So the chrome surface gets its own tokens. They default to `--ink` /
     `--ink-2`, which keeps every existing dark-chrome theme byte-for-byte
     identical, while a light-chrome theme (Vantagepoint) can override the
     chrome alone and leave `--ink` dark for text. Custom properties
     substitute at computed-value time, so `var(--ink)` here still picks up
     whichever theme block is active. */
  --chrome-bg: var(--ink);
  --chrome-bg-2: var(--ink-2);
  --chrome-text: #e8ebee;      /* primary text on chrome */
  --chrome-strong: #ffffff;    /* hover / active text on chrome */
  --chrome-dim: #9aa4af;       /* secondary text, inactive tabs */
  --chrome-tab-hover: #d7dce1;
  --chrome-btn-text: #c7cdd4;
  --chrome-line: #3a434e;      /* chrome button borders */
  --chrome-divider: #2c343e;   /* separators inside chrome */
  --chrome-status-text: #aeb6bf;
  --chrome-status-key: #6d7683;
  --chrome-warn: #e2b25f;
  --chrome-active-bg: var(--chrome-bg);     /* admin nav selected item */
  --chrome-active-text: var(--chrome-strong);
  /* Dark chrome needs no border against the light page — it separates by
     contrast alone. Light chrome does, or the bar melts into the paper, so
     these hooks are `none` by default and set by light-chrome themes. */
  --chrome-edge-bottom: none;
  --chrome-edge-top: none;

  /* Height of the bottom strip, shared by the reports status bar and the
     admin build/version footer. They look deliberately different — one is
     chrome, one is a quiet note on the paper surface — but they occupy the
     same row at the bottom of the window, so switching between the admin
     console and the reports view must not move everything above them. One
     token, because two hand-matched numbers drift. */
  --strip-height: 30px;

  /* ---- surfaces, state and depth -------------------------------------
     Everything below used to be a literal in the rules that consumed it —
     29 separate `#fff`s, a handful of greys, and four hard-coded
     good/warn/bad/info colour pairs. Literals are fine while there is only
     one colour scheme; they are the whole problem the moment there are two,
     because a dark block can only override tokens. These are the light
     values, unchanged; `html[data-mode="dark"]` re-points them. */
  --surface: #ffffff;        /* raised surfaces: cards, inputs, dialogs, editors */
  --surface-2: #f1f3f5;      /* quiet headers — grid header, table heads */
  /* Recessed panel (the advanced/JSON box). This token was REFERENCED by
     `.advbox` as `var(--paper-2, #f6f5f1)` but never defined anywhere, so the
     fallback always won and the panel was a fixed cream in all nine themes —
     including the cool Vantagepoint skin, where it visibly clashes. Defined
     here at its existing value so light mode is unchanged. */
  --paper-2: #f6f5f1;
  --row-alt: #f7f8f9;        /* zebra striping */
  --row-sub: #f5f7f6;        /* currency sub-rows under a group */
  /* NULL cells and other absent values. Deliberately quiet, but #b3bac2
     measured 1.96:1 on white — and only 2.04:1 on the grid header, i.e. not
     readable at all rather than merely understated. Found when the dark
     palettes were swept for contrast: the generated dark values gate at 3.0
     and land near 4.0, so light mode was the worse of the two. Raised to
     3.64:1 on white / 3.27:1 against the darkest row it sits on, which is
     still visibly secondary to real values. */
  --text-faint: #7d8794;
  --on-accent: #ffffff;      /* text/marks sitting ON --accent */
  --on-accent-soft: #103f34; /* text sitting on --accent-soft */
  --veil: rgba(251,251,252,.75);   /* loading veil over the grid */
  --backdrop: rgba(23,28,34,.5);   /* dialog backdrop */
  --star: #b8860b;           /* favourite/pin marker */
  --disabled-mix: #cfd6d3;   /* greyed toward, for disabled primary buttons */
  --hover-wash: rgba(0,0,0,.06);   /* subtle hover fill on quiet icon buttons */

  /* semantic state pairs, shared by conditional formatting, health checks,
     impact rows, the drift banner and the mixed-currency badge */
  --ok-bg: #e2f0ec;   --ok-fg: #0f6e56;   --ok-line: #cfe4dd;
  --warn-bg: #f7ecd6; --warn-fg: #8a5a12; --warn-fg-2: #6d4a10; --warn-line: #e5d3ab;
  --bad-bg: #fbe4e0;  --bad-fg: #a63a2b;  --bad-line: #f0c7bf;
  --info-bg: #e6eef7; --info-fg: #1f4b7a;

  /* Depth. In dark mode a black shadow is invisible against a dark surface,
     so these are tokens rather than literal rgba(0,0,0,…) — the dark block
     deepens them instead of leaving cards flat. */
  --shadow-xs: rgba(0,0,0,.06);
  --shadow-sm: rgba(0,0,0,.08);
  --shadow-md: rgba(0,0,0,.14);
  --shadow-lg: rgba(0,0,0,.22);
  --shadow-xl: rgba(0,0,0,.4);

  /* dark banding used INSIDE the data surface (group headers, grand total) */
  --band-bg: var(--ink);
  --band-text: #e8ebee;
  --band-text-soft: #dfe4e8;
  --band-text-strong: #ffffff;
  --band-dim: #aeb6bf;
  --band-line: #2b333b;
  --band-accent: #3fae93;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: var(--sans); font-size: 13.5px; color: var(--text);
  background: var(--paper); display: flex; flex-direction: column; overflow: hidden;
}

/* ---------- top bar ---------- */
.topbar {
  background: var(--chrome-bg); color: var(--chrome-text); display: flex; align-items: center;
  gap: 16px; padding: 0 14px; height: 40px; flex: none;
  border-bottom: var(--chrome-edge-bottom);
}
.topbar .brand { font-weight: 600; letter-spacing: .04em; font-size: 13px; }
.topbar .brand em { font-style: normal; color: var(--accent-on-dark); }
.topbar .spacer { flex: 1; }
.topbar .who { color: var(--chrome-dim); font-size: 12px; }
.topbar button, .topbar a {
  background: none; border: 1px solid var(--chrome-line); color: var(--chrome-btn-text); font: inherit;
  font-size: 12px; padding: 4px 12px; border-radius: 8px; cursor: pointer; text-decoration: none;
}
.topbar button:hover, .topbar a:hover { border-color: var(--accent); color: var(--chrome-strong); }
/* Safety net for a real bug (§enh): `button.quiet` sets `background:#fff`
   and sits LATER in this file than `.topbar button`, so on an equal
   specificity tie it won — a .quiet button placed in the topbar rendered
   white-on-dark, then turned its own text white on hover (the rule above),
   making the label vanish entirely. The instances were fixed at source (the
   class removed from the markup and from addPreviewButton), but this keeps
   the topbar correct even if `.quiet` is ever added to a button in here
   again by mistake. */
.topbar button.quiet {
  background: none; border: 1px solid var(--chrome-line); color: var(--chrome-btn-text);
  font-size: 12px; padding: 4px 12px; border-radius: 8px; box-shadow: none;
}
.topbar button.quiet:hover { border-color: var(--accent); color: var(--chrome-strong); background: none; box-shadow: none; }

/* ---------- tab strip ---------- */
.tabstrip {
  display: flex; background: var(--chrome-bg-2); flex: none; overflow-x: auto;
  scrollbar-width: thin;
}
.tabstrip .tab {
  padding: 8px 18px 6px; color: var(--chrome-dim); cursor: pointer; white-space: nowrap;
  border-bottom: 2px solid transparent; font-size: 13px; user-select: none;
}
.tabstrip .tab:hover { color: var(--chrome-tab-hover); }
.tabstrip .tab.active { color: var(--chrome-strong); border-bottom-color: var(--accent-on-dark); }

/* ---------- filter panel ---------- */
.filterpanel {
  background: var(--paper); border-bottom: 1px solid var(--line); flex: none;
  padding: 10px 14px;
}
.filterpanel.collapsed .filters, .filterpanel.collapsed .filteractions { display: none; }
.filterpanel .summary {
  display: none; color: var(--text-dim); font-size: 12.5px; cursor: pointer;
}
.filterpanel.collapsed .summary { display: flex; align-items: center; gap: 10px; }
.filterpanel .summary .mono { font-family: var(--mono); color: var(--text); }
.filters { display: flex; flex-wrap: wrap; gap: 12px 18px; margin-bottom: 10px; }
.filter { display: flex; flex-direction: column; gap: 5px; min-width: 170px; }
.filter label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); }
.filter label .req { color: var(--neg); }
.filter input, .filter select {
  font: inherit; padding: 7px 11px; border: 1.5px solid var(--line); border-radius: 9px;
  background: var(--surface); min-height: 34px; color: var(--text);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.filter select[multiple] { padding: 5px; border-radius: 11px; }
.filter select[multiple] option { padding: 5px 9px; border-radius: 6px; }
.filter select[multiple] option:checked { background: var(--accent-soft); color: var(--ink); }
.filter input:focus, .filter select:focus, .filter textarea.inlist:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
.filter .range { display: flex; gap: 6px; align-items: center; }
.filter .range input { width: 110px; }
.filter textarea.inlist {
  font-family: var(--mono); font-size: 12px; width: 210px; height: 34px; padding: 7px 11px;
  border: 1.5px solid var(--line); border-radius: 9px; resize: vertical; background: var(--surface);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.filteractions { display: flex; gap: 8px; align-items: center; }
button.primary {
  background: var(--accent); border: 1.5px solid var(--accent); color: var(--on-accent);
  font: inherit; font-weight: 600; padding: 8px 20px; border-radius: 10px; cursor: pointer;
  transition: filter .12s ease, transform .08s ease, box-shadow .12s ease;
}
button.primary:hover { filter: brightness(.94); box-shadow: 0 3px 10px var(--shadow-md); }
button.primary:active { transform: translateY(1px); }
button.primary:disabled { background: color-mix(in srgb, var(--accent) 40%, var(--disabled-mix)); border-color: transparent; cursor: not-allowed; box-shadow: none; }
button.quiet {
  background: var(--surface); border: 1.5px solid var(--line); color: var(--text); font: inherit;
  padding: 7px 14px; border-radius: 10px; cursor: pointer;
  transition: border-color .12s ease, box-shadow .12s ease, transform .08s ease;
}
button.quiet:hover { border-color: var(--accent); box-shadow: 0 2px 8px var(--shadow-sm); }
button.quiet:active { transform: translateY(1px); }
.filteractions .hint { color: var(--text-dim); font-size: 12px; }
.filteractions .hint.warnmsg { color: var(--warn); }

/* ---------- toolbar ---------- */
.toolbar {
  display: flex; gap: 8px; align-items: center; padding: 7px 14px;
  background: var(--paper); border-bottom: 1px solid var(--line-soft); flex: none;
}
.toolbar input.quicksearch {
  font: inherit; padding: 6px 12px; border: 1.5px solid var(--line); border-radius: 10px; width: 220px;
  background: var(--surface); transition: border-color .12s ease, box-shadow .12s ease;
}
.toolbar input.quicksearch:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.toolbar select {
  font: inherit; font-size: 12.5px; padding: 6px 10px; border: 1.5px solid var(--line);
  border-radius: 10px; background: var(--surface); color: var(--text); cursor: pointer;
}
.toolbar .spacer { flex: 1; }
.toolbar button { font-size: 12.5px; }

/* ---------- grid ---------- */
.gridwrap { flex: 1; min-height: 0; position: relative; }
#grid { height: 100%; }
.emptystate {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 8px; color: var(--text-dim);
  background: var(--paper); z-index: 5;
}
.emptystate .big { font-size: 15px; color: var(--text); }
.emptystate.hidden { display: none; }
.loadveil {
  position: absolute; inset: 0; background: var(--veil); z-index: 6;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
}
.loadveil.hidden { display: none; }
.loadveil .elapsed { font-family: var(--mono); font-size: 13px; color: var(--text-dim); }
.spinner {
  width: 26px; height: 26px; border: 3px solid var(--line);
  border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }

/* ---------- the instrument strip (signature) ---------- */
.statusbar {
  background: var(--chrome-bg); color: var(--chrome-status-text); height: var(--strip-height); flex: none;
  border-top: var(--chrome-edge-top);
  display: flex; align-items: center; gap: 0; font-family: var(--mono); font-size: 12px;
  padding: 0 14px; overflow: hidden;
  /* Safety net for a real bug. `.gridwrap` is position:relative, so it and its
     children paint in a later stacking phase than this static footer — anything
     that overflows the wrap lands ON TOP of the status bar rather than behind or
     clipped. That is exactly what the footer totals strip did. The strip was moved
     out of the wrap (the real fix), but positioning this bar and lifting it a layer
     means any future overflow from the grid area can never hide it again. */
  position: relative; z-index: 1;
}
.statusbar .cell {
  display: flex; align-items: baseline; gap: 6px; padding: 0 14px;
  border-right: 1px solid var(--chrome-divider); white-space: nowrap;
}
.statusbar .cell:first-child { padding-left: 0; }
.statusbar .cell .k { color: var(--chrome-status-key); font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em; }
.statusbar .cell .v { color: var(--chrome-text); }
.statusbar .cell .v.accent { color: var(--accent-on-dark); }
.statusbar .cell .v.warn { color: var(--chrome-warn); }
.statusbar .spacer { flex: 1; }
.statusbar button {
  background: none; border: 1px solid var(--chrome-line); color: var(--chrome-btn-text); font: inherit; font-size: 11px;
  padding: 3px 11px; border-radius: 8px; cursor: pointer;
}
.statusbar button:hover { border-color: var(--accent); }

/* ---------- tabulator restyle ---------- */
.tabulator { border: none; background: var(--paper); font-size: 12.8px; }
.tabulator .tabulator-header {
  background: var(--surface-2); border-bottom: 1px solid var(--line); font-weight: 600;
}
.tabulator .tabulator-header .tabulator-col { background: var(--surface-2); border-right: 1px solid var(--line-soft); }
.tabulator-row { border-bottom: 1px solid var(--line-soft); }
.tabulator-row.tabulator-row-even { background: var(--row-alt); }
.tabulator-row .tabulator-cell { border-right: 1px solid transparent; }
.tabulator-row.tabulator-selected { background: var(--accent-soft) !important; }
.cell-num { font-family: var(--mono); font-size: 12.2px; text-align: right; display: block; }
.cell-num.negative { color: var(--neg); }
.cell-null { color: var(--text-faint); }

/* ---------- login page ---------- */
.loginwrap {
  height: 100%; display: flex; align-items: center; justify-content: center;
  background: var(--chrome-bg); overflow-y: auto;   /* safety net (§enh fix) — same class of
    bug as the theme popover: on a short viewport, more style tiles wrapping to an
    extra row could make the card taller than the screen with nothing to scroll,
    stranding the Sign in button off-screen. Deliberately plain `center`, not
    `safe center`: the `safe` overflow-alignment keyword only gained real browser
    support in mid-2023 (Chrome/Edge 115+) — on an older managed corporate build it
    would be invalid and silently discarded, falling back to `stretch` and breaking
    the card's centering everywhere, which is a worse regression than the rare
    overflow edge case this guards against. Plain `overflow-y: auto` alone is a
    smaller, universally-supported improvement: it won't perfectly solve the
    known "centered overflow hides the top" flexbox quirk, but it's a real net
    improvement with no downside, which `safe center` is not, given the actual
    browser floor here is unconfirmed. */
}
.logincard {
  background: var(--surface); padding: 34px 36px 30px; width: 360px; border-radius: 3px;
  border-top: 3px solid var(--accent);
}
.logincard h1 { font-size: 17px; margin: 0 0 2px; letter-spacing: .02em; }
.logincard .sub { color: var(--text-dim); font-size: 12px; margin-bottom: 22px; }
.logincard label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); margin: 12px 0 4px; }
.logincard input { width: 100%; font: inherit; padding: 9px 12px; border: 1.5px solid var(--line); border-radius: 9px; }
.logincard input:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }
.logincard button { width: 100%; margin-top: 20px; padding: 9px; }
.logincard .err { color: var(--neg); font-size: 12.5px; min-height: 18px; margin-top: 10px; }

/* ---------- admin ---------- */
.adminmain { flex: 1; min-height: 0; display: flex; }
.adminnav { width: 200px; background: var(--chrome-bg-2); color: var(--chrome-btn-text); flex: none; padding-top: 8px; }
.adminnav .navitem { padding: 9px 16px; cursor: pointer; font-size: 13px; }
.adminnav .navitem:hover { background: var(--chrome-divider); }
.adminnav .navitem.active { background: var(--chrome-active-bg); color: var(--chrome-active-text); border-left: 2px solid var(--accent); }
.adminbody { flex: 1; overflow: auto; padding: 18px 22px; }
.adminbody h2 { font-size: 15px; margin: 0 0 14px; }
.adminbody table.list { border-collapse: collapse; width: 100%; background: var(--surface); }
.adminbody table.list th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-dim); border-bottom: 1px solid var(--line); padding: 7px 10px;
}
.adminbody table.list td { border-bottom: 1px solid var(--line-soft); padding: 7px 10px; }
.adminbody table.list tr:hover td { background: var(--accent-soft); cursor: pointer; }
.sqleditor {
  width: 100%; min-height: 260px; font-family: var(--mono); font-size: 13px;
  border: 1.5px solid var(--line); border-radius: 10px; padding: 12px; resize: vertical;
  background: var(--surface); tab-size: 4;
}
.formrow { display: flex; gap: 14px; flex-wrap: wrap; margin: 10px 0; align-items: flex-end; }
/* ===================== universal field styling (fix) =====================
   Every field-styling rule in this file used to be scoped to `.formrow` or
   `dialog.formdialog` — meaning any admin screen that builds a field directly
   on the page instead of inside one of those two wrappers (Email settings,
   the chart/dashboard/alert editors, the backup screen, …) fell back to
   completely unstyled browser defaults: cramped, no spacing, label glued to
   input. This base rule fixes that everywhere at once. It is deliberately
   LOWER specificity than the `.formrow .field …` / `dialog.formdialog .field
   …` rules below, so it only fills the gap — it cannot override or change
   anything in a context that already looked right. */
.field { display: flex; flex-direction: column; gap: 4px; margin: 0 0 12px; max-width: 420px; }
.field > label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); }
/* :not([type=checkbox]):not([type=radio]) matters: `.field input` on its own
   also matches checkbox inputs nested inside a checklist/checkbox sub-widget
   (e.g. checklistField's per-row checkboxes) — without this exclusion they'd
   get the same padded, rounded-box, full-width treatment as a text input,
   rendering as an oversized blank box instead of a normal checkbox. This is
   exactly what happened in an earlier version of this rule — see
   docs/QA-REPORT.md. */
.field input:not([type="checkbox"]):not([type="radio"]), .field select, .field textarea {
  font: inherit; width: 100%; box-sizing: border-box; color: var(--text);
  padding: 7px 11px; border: 1.5px solid var(--line); border-radius: 9px; background: var(--surface);
}
.field input:not([type="checkbox"]):not([type="radio"]):focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
.field textarea { resize: vertical; }
/* checkbox rows (Active, Use TLS, …) are a single wrapping <label>, not a
   caption above an input — override the stacked/uppercase caption styling */
.field.checkfield { flex-direction: row; align-items: center; max-width: none; margin: 4px 0 12px; }
.field.checkfield > label {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; text-transform: none; letter-spacing: normal; color: var(--text);
}
.field input[type="checkbox"], .field input[type="radio"] { width: auto; accent-color: var(--accent); }
/* .hint had the same scoping problem as .field (only styled inside specific
   wrappers) — give it a sensible universal default too, on the same
   reasoning: any admin screen using it outside those wrappers was getting
   unstyled browser-default text. */
.hint { color: var(--text-dim); font-size: 12px; margin: 4px 0; line-height: 1.5; }

.formrow .field { display: flex; flex-direction: column; gap: 3px; }
.formrow .field label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); }
.formrow .field input, .formrow .field select { font: inherit; padding: 7px 11px; border: 1.5px solid var(--line); border-radius: 9px; background: var(--surface); }
.msg { font-size: 12.5px; margin: 8px 0; min-height: 16px; }
.msg.ok { color: var(--accent); }
.msg.err { color: var(--neg); }
.msg.warn { color: var(--warn); }
.coltable input[type="text"], .coltable select { font-size: 12px; padding: 3px 5px; }
.coltable input[type="number"] { width: 60px; font-size: 12px; padding: 3px 5px; }
dialog { border: 1px solid var(--line); border-radius: 3px; padding: 20px; min-width: 380px; }
dialog::backdrop { background: var(--backdrop); }

.notice-desktop { display: none; }
@media (max-width: 1279px) {
  .notice-desktop {
    display: block; background: var(--warn); color: var(--on-accent); text-align: center;
    padding: 4px; font-size: 12px; flex: none;
  }
}
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ===================== grouped report (§9.4, §16.5) ===================== */
.groupedview {
  position: absolute; inset: 0;           /* fill .gridwrap (position:relative) */
  overflow: auto;                          /* vertical + horizontal scrolling within the app */
  background: var(--paper); padding: 0;
}
.groupedview.hidden, #grid.hidden { display: none; }
.grouploading, .groupreport .err { padding: 16px; color: var(--text-dim); font-size: 13px; }
.groupreport { padding: 0 0 24px; }
.grouptable { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.grouptable th {
  position: sticky; top: 0; z-index: 1; text-align: left; padding: 8px 12px;
  background: var(--band-bg); color: var(--band-text); font-weight: 500; white-space: nowrap;
}
.grouptable th.num { text-align: right; }
.grouptable th.ccyhead { color: var(--band-dim); }
.grouptable td { padding: 7px 12px; border-bottom: 1px solid var(--line-soft); white-space: nowrap; }
.grouptable td.num { text-align: right; font-family: var(--mono); }
.grouptable td.muted { color: var(--text-dim); }
.grouptable td.negative, .grouptable td.num.negative { color: var(--neg); }
.grouptable tr.g-head td { background: var(--accent-soft); font-weight: 600; color: var(--on-accent-soft); border-bottom: 1px solid var(--ok-line); }
.grouptable tr.g-ccy td { background: var(--row-sub); }
.grouptable td.ccy-indent { border-bottom: none; background: var(--row-sub); }
.grouptable td.ccy-code { font-family: var(--mono); font-size: 11px; color: var(--accent); }
.mixed-badge {
  display: inline-block; font-family: var(--sans); font-size: 10.5px; font-weight: 600;
  color: var(--warn-fg); background: var(--warn-bg); padding: 1px 7px; border-radius: 4px; margin-left: 6px;
}
.grandtotal { margin-top: 4px; }
.grandtotal .gt-title { background: var(--band-bg); color: var(--band-text); font-weight: 600; padding: 9px 12px; font-size: 12.5px; }
.grouptable.gt td { background: var(--band-bg); color: var(--band-text-soft); border-bottom: 1px solid var(--band-line); }
.grouptable.gt td.num { color: var(--band-text-strong); }
.grouptable.gt td.ccy-code { color: var(--band-accent); }
.gt-note { color: var(--text-dim); font-size: 11px; padding: 8px 12px; }

/* group dialog */
dialog.groupdialog { border: 1px solid var(--line); border-radius: 10px; padding: 18px 20px; max-width: 460px; font-family: var(--sans); }
dialog.groupdialog h2 { margin: 0 0 4px; font-size: 15px; }
dialog.groupdialog .hint { color: var(--text-dim); font-size: 12px; margin: 0 0 12px; }
.grouppick { display: flex; flex-direction: column; gap: 7px; max-height: 260px; overflow: auto; margin-bottom: 14px; }
.grouppick label { font-size: 13px; display: flex; align-items: center; gap: 7px; }
.dlgbtns { display: flex; gap: 8px; justify-content: flex-end; }

/* ===================== conditional formatting (§enh) ===================== */
.cf-good { background: var(--ok-bg); color: var(--ok-fg); }
.cf-warn { background: var(--warn-bg); color: var(--warn-fg); }
.cf-bad  { background: var(--bad-bg); color: var(--bad-fg); }
.cf-info { background: var(--info-bg); color: var(--info-fg); }
.cf-databar {
  /* background-image is set per-cell by format.js (databarPercent), scaled to
     the column's actual observed min/max — never a fixed value here. If it
     can't be computed for a given cell, no bar shows at all, which is more
     honest than the fixed 40% every cell used to get regardless of value. */
  background-repeat: no-repeat;
}

/* ===================== form dialogs (user editor, etc.) ================= */
dialog.formdialog {
  display: flex; flex-direction: column; gap: 12px;
  min-width: 460px; max-width: 560px; padding: 22px 24px;
  border: 1px solid var(--line); border-radius: 6px;
}
dialog.formdialog h2 { margin: 0 0 4px; font-size: 16px; }

/* every labelled field stacks its label above a full-width control */
dialog.formdialog .field { display: flex; flex-direction: column; gap: 4px; }
dialog.formdialog .field > label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim);
}
dialog.formdialog .field input,
dialog.formdialog .field select,
dialog.formdialog .field textarea {
  width: 100%; box-sizing: border-box; font: inherit;
  padding: 7px 9px; border: 1px solid var(--line); border-radius: 3px; background: var(--paper);
}
dialog.formdialog .field.jsonfield textarea {
  resize: vertical; min-height: 46px; font-family: var(--mono); font-size: 12px; line-height: 1.4;
}
dialog.formdialog .jsonerr { color: var(--neg); font-size: 11px; min-height: 13px; }

/* inline checkbox rows: Active, and the Roles: heading + checklist */
dialog.formdialog > label {                 /* e.g. the Active toggle */
  display: flex; align-items: center; gap: 8px; font-size: 13px;
}
dialog.formdialog > div:not(.field):not(.formrow):not([data-group]) {   /* the "Roles:" caption */
  font-size: 12px; color: var(--text-dim); margin-bottom: -4px;
}
dialog.formdialog [data-group] {            /* the role checklist */
  display: flex; flex-wrap: wrap; gap: 6px 18px; padding: 2px 0;
}
dialog.formdialog [data-group] label { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }

dialog.formdialog .msg.err { color: var(--neg); font-size: 12px; min-height: 15px; }
dialog.formdialog .formrow { margin-top: 6px; gap: 10px; }   /* Save / Cancel */
dialog.formdialog .formrow button { padding: 8px 18px; }

/* direct-child controls in form dialogs (bare textarea / hint / buttons) */
dialog.formdialog > textarea {
  width: 100%; box-sizing: border-box; font-family: var(--mono); font-size: 12px;
  padding: 7px 9px; border: 1px solid var(--line); border-radius: 3px; resize: vertical; min-height: 60px;
}
dialog.formdialog > p, dialog.formdialog .hint { font-size: 12px; color: var(--text-dim); margin: 0; }
dialog.formdialog > button { align-self: flex-start; padding: 8px 18px; }

/* ===================== tab editor: advanced (params / RLS) block ========= */
.advbox {
  display: flex; flex-direction: column; gap: 14px;
  margin: 14px 0; padding: 14px 16px;
  background: var(--paper-2); border: 1px solid var(--line); border-radius: 6px;
}
.advbox .advhint { font-size: 12px; color: var(--text-dim); line-height: 1.5; margin-bottom: 2px; }
/* JSON fields stack label above a full-width textarea (fixes the squashed/floating box) */
.advbox .field.jsonfield { display: flex; flex-direction: column; gap: 5px; }
.advbox .field.jsonfield > label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim);
}
.advbox .field.jsonfield textarea {
  width: 100%; box-sizing: border-box; min-height: 64px; resize: vertical;
  font-family: var(--mono); font-size: 12px; line-height: 1.4;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 3px; background: var(--paper);
}
.advbox .jsonerr { color: var(--neg); font-size: 11px; min-height: 13px; }

/* active theme icon echoed in the header wordmark */
.topbar .brand { display: inline-flex; align-items: center; gap: 8px; }
.topbar .brand .brand-icon { width: 18px; height: 18px; color: var(--accent-on-dark); flex: none; }

/* toolbar cache-age hint beside the Refresh button */
.toolbar .cache-age { font-size: 11.5px; color: var(--text-dim); font-family: var(--mono); }

/* ===================== admin metrics dashboard ========================== */
.statcards { display: flex; flex-wrap: wrap; gap: 12px; margin: 14px 0 8px; }
.statcard {
  flex: 1 1 150px; min-width: 150px; background: var(--paper); border: 1px solid var(--line);
  border-radius: 8px; padding: 14px 16px;
}
.statcard .stat-v { font-family: var(--mono); font-size: 26px; font-weight: 600; color: var(--ink); line-height: 1.1; }
.statcard .stat-k { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); margin-top: 4px; }
.statcard .stat-sub { font-size: 11.5px; color: var(--accent); margin-top: 3px; }
.adminbody h3.subhead { font-size: 14px; color: var(--ink); margin: 22px 0 8px; }

/* ===================== in-app theme switcher ============================ */
.topbar .themebtn { /* uses the shared .topbar button styling; just a hook */ }
.theme-pop {
  position: fixed; z-index: 9000; background: var(--chrome-bg-2); color: var(--chrome-strong);
  border: 1px solid var(--chrome-pop-border, rgba(255,255,255,.14)); border-radius: 12px; padding: 14px;
  box-shadow: var(--chrome-pop-shadow, 0 18px 50px rgba(0,0,0,.45));
  overflow-y: auto;   /* safety net (§enh fix) — see the note in theme-icons.js */
}
.theme-pop-grid { display: grid; grid-template-columns: repeat(5, 46px); gap: 10px; }
.theme-pop-name { margin-top: 10px; font-size: 12px; color: var(--chrome-btn-text); text-align: center; }

/* ---- light / dark / system control -------------------------------------
   Shared markup (theme-icons.js `modeSwitcherNode`) used in two very different
   surroundings: the in-app popover, which sits on the chrome, and the sign-in
   card, which is styled from its own --lt-* set. These base rules are the
   chrome version; login-themes.css restates the colours for the card. */
.mode-switch {
  display: flex; gap: 6px; margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--chrome-divider);
}
.mode-btn {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 8px 2px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--chrome-line); background: none;
  color: var(--chrome-btn-text); font: inherit; font-size: 10.5px;
  transition: border-color .12s ease, color .12s ease;
}
.mode-btn svg { width: 17px; height: 17px; flex: none; }
.mode-btn:hover { border-color: var(--accent-on-dark); color: var(--chrome-strong); }
.mode-btn[aria-pressed="true"] {
  border-color: var(--accent-on-dark); color: var(--chrome-strong);
  box-shadow: inset 0 0 0 1px var(--accent-on-dark);
}
.theme-pop-grid .lt-swatch {
  width: 46px; height: 46px; border-radius: 14px; margin: 0; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: var(--sw-bg); color: var(--sw-accent); border: 1px solid var(--sw-border);
  transition: transform .12s ease;
}
.theme-pop-grid .lt-swatch svg { width: 23px; height: 23px; }
.theme-pop-grid .lt-swatch:hover { transform: translateY(-2px); }
.theme-pop-grid .lt-swatch[aria-pressed="true"] { outline: 2px solid var(--sw-accent); outline-offset: 2px; }
/* per-theme preview colours (shared with the login picker) */
.lt-swatch[data-theme-id="ledger"]    { --sw-bg:#fbfaf6; --sw-accent:#0e7c66; --sw-border:#e4e1d8; }
.lt-swatch[data-theme-id="onyx"]      { --sw-bg:#1b1a15; --sw-accent:#c9a86b; --sw-border:#33302a; }
.lt-swatch[data-theme-id="porcelain"] { --sw-bg:#ffffff; --sw-accent:#3f7a78; --sw-border:#dfe4e8; }
.lt-swatch[data-theme-id="midnight"]  { --sw-bg:#142138; --sw-accent:#d8b45c; --sw-border:#26344f; }
.lt-swatch[data-theme-id="bordeaux"]  { --sw-bg:#201715; --sw-accent:#c85c68; --sw-border:#3a2a27; }
.lt-swatch[data-theme-id="sage"]      { --sw-bg:#f6f4ec; --sw-accent:#35604a; --sw-border:#d6d3c4; }
.lt-swatch[data-theme-id="copper"]    { --sw-bg:#241d18; --sw-accent:#c47e4f; --sw-border:#3c2f26; }
.lt-swatch[data-theme-id="harbor"]    { --sw-bg:#ffffff; --sw-accent:#4d7597; --sw-border:#dbe2e8; }
.lt-swatch[data-theme-id="vantagepoint"] { --sw-bg:#EDF0F6; --sw-accent:#4C92D9; --sw-border:#BFC6D4; }
/* Real bug found and fixed here: this block is a DUPLICATE of the swatch
   colours in login-themes.css, needed because login-themes.css is scoped to
   the login page only (see its own header comment) while the in-app "Style"
   switcher runs on admin.html/index.html, which never load that file — so
   this copy has to exist for the in-app popover to have any swatch colours
   at all. It was never updated when Harbor was added, meaning the in-app
   popover's Harbor tile had no --sw-bg/--sw-accent/--sw-border defined and
   would have rendered with no colour at all — a strong, direct explanation
   for "Harbor still doesn't work" reported specifically against the admin
   console's popover. This is a SIXTH place a theme has to be registered,
   on top of the five already documented in BUILD-SPEC.md §31 — worth
   remembering next time a theme changes, since this is exactly the one that
   was missed. */

/* ===================== grouped chart view =============================== */
.group-controls { display: flex; align-items: center; gap: 12px; margin: 4px 0 12px; flex-wrap: wrap; }
.group-controls .ctl-label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); }
.group-controls select { font: inherit; padding: 5px 8px; border: 1px solid var(--line); border-radius: 4px; background: var(--paper); color: var(--text); }
.segmented { display: inline-flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.segmented .seg { border: none; background: var(--paper); color: var(--text); padding: 6px 16px; cursor: pointer; font: inherit; }
.segmented .seg + .seg { border-left: 1px solid var(--line); }
.segmented .seg.active { background: var(--accent); color: var(--on-accent); }
.group-chartview { padding: 8px 4px 4px; overflow-x: auto; }
.de-chart-title { font: 600 14px var(--sans); fill: var(--ink); }
.de-chart-label { font: 12px var(--sans); fill: var(--text); }
.de-chart-val   { font: 12px var(--mono); fill: var(--text-dim); }
.de-chart-note  { font: 11px var(--sans); fill: var(--text-dim); }
.de-chart-axis  { stroke: var(--line); stroke-width: 1; }
.de-bar   { fill: var(--accent); }
.de-bar.neg { fill: var(--neg); }

/* categorical series palette (visual-analytics addendum) — fixed, theme-
   agnostic hues so a multi-series chart stays legible regardless of which of
   the ten app themes is active (a full per-theme categorical palette is out
   of scope here; these six work reasonably against every theme's light
   grid surface). */
:root {
  --chart-s0: #2f6fd6; --chart-s1: #d68a2f; --chart-s2: #3fa66a;
  --chart-s3: #b23f6b; --chart-s4: #7d5fd6; --chart-s5: #4fa8b8;
}
.de-series-s0 { --sc: var(--chart-s0); }
.de-series-s1 { --sc: var(--chart-s1); }
.de-series-s2 { --sc: var(--chart-s2); }
.de-series-s3 { --sc: var(--chart-s3); }
.de-series-s4 { --sc: var(--chart-s4); }
.de-series-s5 { --sc: var(--chart-s5); }
.de-line { stroke: var(--sc); stroke-width: 2.2; }
.de-area-fill { fill: var(--sc); opacity: .16; stroke: none; }
.de-point { fill: var(--paper); stroke: var(--sc); stroke-width: 2; }
.de-slice { fill: var(--sc); stroke: var(--paper); stroke-width: 1.5; }
.de-legend-swatch { fill: var(--sc); }
.de-selectable { cursor: pointer; transition: opacity .1s ease; }
.de-selectable:hover { opacity: .72; }

/* ===================== subscriptions dialog ============================= */
.formdialog .sub-caption { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); margin-top: 6px; }
.sub-list { display: flex; flex-direction: column; gap: 6px; }
.sub-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; background: var(--paper); border: 1px solid var(--line); border-radius: 6px; }
.sub-row .sub-name { font-weight: 600; color: var(--ink); }
.sub-row .sub-when { font-size: 12px; color: var(--text-dim); margin-left: auto; }

/* drill-through: hint that rows open a detail tab on double-click */
.tabulator.drillable .tabulator-row { cursor: pointer; }

/* ===================== footer totals strip ============================== */
.totalsbar {
  flex: none; display: flex; align-items: center; gap: 22px; flex-wrap: wrap;
  padding: 6px 14px; background: var(--paper); border-top: 2px solid var(--accent);
  font-size: 12.5px;
}
.totalsbar .tt-label { font-family: var(--mono); color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }
.totalsbar .tt-cell { display: inline-flex; align-items: baseline; gap: 8px; }
.totalsbar .tt-k { color: var(--text-dim); font-size: 11.5px; }
.totalsbar .tt-v { font-family: var(--mono); font-weight: 600; color: var(--ink); }

/* saved-view drift warning banner */
.driftbanner {
  flex: none; display: flex; align-items: center; gap: 14px;
  padding: 7px 14px; background: var(--warn-bg); color: var(--warn-fg-2);
  border-bottom: 1px solid var(--warn-line); font-size: 12.5px;
}
.driftbanner button { margin-left: auto; }

/* typed header filters */
.hf-range { display: flex; gap: 4px; }
.hf-range input { width: 50%; min-width: 0; font-size: 11.5px; padding: 3px 5px; border: 1px solid var(--line); border-radius: 3px; }
.hf-date { width: 100%; font-size: 11.5px; padding: 3px 4px; border: 1px solid var(--line); border-radius: 3px; background: var(--surface); }

/* command palette */
.palette { position: fixed; inset: 0; background: rgba(10,14,18,.45); z-index: 9500; display: flex; justify-content: center; }
.palette-box { margin-top: 12vh; width: 480px; max-width: 90vw; height: fit-content; background: var(--paper); border-radius: 10px; box-shadow: 0 24px 60px var(--shadow-xl); overflow: hidden; }
.palette-box input { width: 100%; box-sizing: border-box; border: none; border-bottom: 1px solid var(--line); padding: 13px 16px; font: inherit; font-size: 15px; outline: none; background: var(--paper); color: var(--text); }
.palette-list { max-height: 300px; overflow: auto; }
.palette-item { display: flex; align-items: center; gap: 10px; padding: 9px 16px; cursor: pointer; font-size: 13.5px; }
.palette-item:hover, .palette-item.sel { background: var(--accent-soft); }
.palette-kind { font-family: var(--mono); font-size: 9.5px; color: var(--text-dim); letter-spacing: .08em; width: 34px; flex: none; }

/* tab health check results */
.hc-out { margin: 10px 0; display: flex; flex-direction: column; gap: 6px; }
.hc-summary { font-family: var(--mono); font-size: 12.5px; padding: 6px 10px; border-radius: 5px; width: fit-content; }
.hc-summary.hc-good { background: var(--ok-bg); color: var(--ok-fg); }
.hc-summary.hc-warn, .hc-row.hc-warn { background: var(--warn-bg); color: var(--warn-fg-2); }
.hc-summary.hc-bad, .hc-row.hc-bad { background: var(--bad-bg); color: var(--bad-fg); }
.hc-row { padding: 7px 10px; border-radius: 5px; font-size: 12.5px; }

/* link columns */
.tabulator .cell-link { color: var(--accent); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); }
.tabulator .cell-link:hover { text-decoration: none; border-bottom-color: var(--accent); }

/* tab chooser */
.tabstrip .tab-choose { opacity: .55; font-size: 15px; padding-inline: 12px; }
.tabstrip .tab-choose:hover { opacity: 1; }
.tabchooser { display: flex; flex-direction: column; gap: 4px; max-height: 320px; overflow: auto; }
.tabchooser .tc-row { display: flex; align-items: center; justify-content: space-between; gap: 9px; padding: 6px 8px; border-radius: 7px; font-size: 13.5px; }
.tabchooser .tc-row:hover { background: var(--accent-soft); }
.tabchooser .tc-row label { display: flex; align-items: center; gap: 8px; flex: 1; cursor: pointer; }
.tc-star {
  background: none; border: none; cursor: pointer; font-size: 16px; line-height: 1;
  color: var(--text-dim); padding: 2px 4px; border-radius: 4px;
}
.tc-star[aria-pressed="true"] { color: var(--star); }
.tc-star:hover { background: var(--hover-wash); }

/* ===================== structured config editors ======================== */
.roweditor { display: flex; flex-direction: column; gap: 8px; }
.roweditor .re-help { font-size: 12px; color: var(--text-dim); line-height: 1.5; }
.roweditor .re-rows { display: flex; flex-direction: column; gap: 8px; }
.roweditor .re-empty { font-size: 12.5px; color: var(--text-dim); font-style: italic; padding: 6px 2px; }
.roweditor .re-row {
  display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap;
  padding: 10px 12px; background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
}
.roweditor .re-cell { display: flex; flex-direction: column; gap: 4px; flex: 1 1 130px; min-width: 0; }
.roweditor .re-cell.re-hidden { display: none; }
.roweditor .re-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); }
.roweditor .re-cell input[type="text"], .roweditor .re-cell input[type="number"], .roweditor .re-cell select {
  font: inherit; font-size: 13px; padding: 6px 10px; border: 1.5px solid var(--line);
  border-radius: 8px; background: var(--surface); color: var(--text); width: 100%; box-sizing: border-box;
}
.roweditor .re-cell input:focus, .roweditor .re-cell select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
.roweditor .re-cell input[type="checkbox"] { width: 18px; height: 18px; margin: 4px 0; }
.roweditor .re-del { flex: 0 0 auto; align-self: flex-end; }
.roweditor .re-add { align-self: flex-start; }
.editorwrap { display: flex; flex-direction: column; gap: 10px; }
.jsonfallback > summary { font-size: 12px; color: var(--text-dim); cursor: pointer; padding: 4px 0; }
.jsonfallback textarea {
  width: 100%; box-sizing: border-box; font-family: var(--mono); font-size: 12px;
  padding: 8px 10px; border: 1.5px solid var(--line); border-radius: 8px; resize: vertical; min-height: 70px;
}
.edsection { margin-bottom: 18px; }
.edtitle { font-size: 13px; font-weight: 600; color: var(--ink); margin-bottom: 8px; }
dialog.formdialog.wide { max-width: 820px; min-width: 620px; }

/* CodeMirror SQL editor */
.cm-host { border: 1.5px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--surface); }
.cm-host .cm-editor { max-height: 420px; font-family: var(--mono); font-size: 13px; }
.cm-host .cm-editor.cm-focused { outline: none; }
.cm-host .cm-scroller { overflow: auto; }
.cm-host .cm-gutters { background: var(--paper); border-right: 1px solid var(--line-soft); color: var(--text-dim); }

/* ===================== column config: search + bulk actions ============= */
.colbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 10px 0; }
.colbar .colsearch {
  font: inherit; padding: 7px 12px; border: 1.5px solid var(--line); border-radius: 10px;
  width: 260px; background: var(--surface);
}
.colbar .colsearch:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.colbar .colcount { font-size: 12px; color: var(--text-dim); font-family: var(--mono); }
.bulkbar {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-left: auto;
  padding: 6px 10px; border: 1.5px dashed var(--line); border-radius: 10px; opacity: .55;
  transition: opacity .12s ease, border-color .12s ease;
}
.bulkbar.active { opacity: 1; border-style: solid; border-color: var(--accent); }
.bulkbar .bulkinfo { font-size: 11.5px; color: var(--text-dim); font-family: var(--mono); margin-right: 4px; }
.bulkbar button { font-size: 12px; padding: 5px 11px; }
.coltable td:first-child, .coltable th:first-child { width: 28px; text-align: center; }

/* publish impact dialog */
.impact { display: flex; flex-direction: column; gap: 6px; margin: 10px 0; }
.impact-row { display: flex; gap: 12px; align-items: baseline; padding: 6px 10px; background: var(--paper); border: 1px solid var(--line); border-radius: 8px; }
.impact-row.warn { background: var(--warn-bg); border-color: var(--warn-line); }
.impact-row.bad { background: var(--bad-bg); border-color: var(--bad-line); }
.impact-k { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); flex: 0 0 190px; }
.impact-v { font-family: var(--mono); font-size: 12.5px; color: var(--ink); }
.impact-note { font-size: 12px; color: var(--text-dim); padding-left: 10px; }

/* preview-as-role banner */
.previewbar {
  flex: none; display: flex; align-items: center; gap: 12px;
  padding: 7px 16px; background: #7c3aed; color: #fff; font-size: 12.5px;
}
.previewbar .pv-dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; flex: none; }
.previewbar .pv-text { flex: 1; }
.previewbar button { background: rgba(255,255,255,.16); border-color: rgba(255,255,255,.4); color: #fff; }
.previewbar button:hover { background: rgba(255,255,255,.26); border-color: #fff; }
body.previewing .topbar { box-shadow: inset 0 -2px 0 #7c3aed; }

/* admin help + deleted tabs */
.helpsec { margin-bottom: 22px; max-width: 900px; }
.helprow { display: flex; gap: 18px; padding: 9px 12px; border-bottom: 1px solid var(--line-soft); }
.helprow:last-child { border-bottom: none; }
.help-term { flex: 0 0 200px; font-weight: 600; font-size: 13px; color: var(--ink); }
.help-text { flex: 1; font-size: 13px; color: var(--text); line-height: 1.55; }
.delwrap { margin-top: 20px; }
.deleted-tabs > summary { font-size: 12.5px; color: var(--text-dim); cursor: pointer; padding: 6px 0; }

/* ===================== visual-analytics addendum: dashboards ============= */
.navtoplevel { margin-left: 4px; }
body.dashboards-mode .reports-only { display: none !important; }
.dashboardswrap {
  flex: 1; min-height: 0; overflow: auto; padding: 18px 22px;
  background: var(--paper); color: var(--text);
}
.dash-listhead { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.dash-listhead h2 { margin: 0; font-size: 18px; color: var(--ink); }
.dash-list { display: flex; flex-wrap: wrap; gap: 14px; }
.dash-card {
  width: 260px; text-align: left; padding: 16px; border: 1.5px solid var(--line);
  border-radius: 12px; background: var(--surface); cursor: pointer; font: inherit;
  transition: border-color .12s ease, box-shadow .12s ease;
  position: relative;
}
.dash-card:hover { border-color: var(--accent); box-shadow: 0 3px 12px var(--shadow-sm); }
.dash-card-name { font-weight: 600; color: var(--ink); margin: 0 26px 4px 0; }
.dash-card-desc { font-size: 12.5px; color: var(--text-dim); }
.dash-card-star {
  position: absolute; top: 12px; right: 12px; font-size: 16px; line-height: 1;
  color: var(--text-dim); cursor: pointer; padding: 2px 4px; border-radius: 4px;
}
.dash-card-star[aria-pressed="true"] { color: var(--star); }
.dash-card-star:hover { background: var(--hover-wash); }
.dash-topbar { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; flex-wrap: wrap; }
.dash-title { margin: 0; font-size: 18px; color: var(--ink); flex: 1; }
.dash-grid { display: grid; grid-template-columns: repeat(12, 1fr); grid-auto-rows: 54px; gap: 14px; }
.dash-widget {
  border: 1.5px solid var(--line); border-radius: 12px; background: var(--surface);
  padding: 12px 14px; overflow: auto; display: flex; flex-direction: column; min-width: 0;
}
.dash-widget-title { font-weight: 600; font-size: 13px; color: var(--ink); margin-bottom: 8px; flex: none; }
.dash-widget-body { flex: 1; min-height: 0; overflow: auto; }
.dash-widget-tools { display: flex; gap: 6px; margin-top: 8px; flex: none; }
.dash-widget-tools button { font-size: 11.5px; padding: 4px 10px; }
.dash-text { white-space: pre-wrap; font-size: 13px; line-height: 1.55; color: var(--text); }
.dash-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.dash-table th, .dash-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--line-soft); }
.dash-table th { color: var(--text-dim); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.dash-metric { font-family: var(--mono); font-size: 40px; font-weight: 600; color: var(--ink); line-height: 1.1; }
.dash-metric-sub { font-size: 11.5px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .05em; margin-top: 6px; }

/* addendum §3.5: single-column stacked widgets on tablet/phone, replacing
   the flat desktop-only notice — the dense grid keeps that notice instead. */
@media (max-width: 900px) {
  .dash-grid { grid-template-columns: 1fr; }
  .dash-widget { grid-column: 1 / -1 !important; grid-row: auto !important; min-height: 220px; }
  .dashboardswrap { padding: 12px; }
  .dash-card { width: 100%; }
}

/* ===================== alerts notification panel ========================= */
.bellbtn { position: relative; }
.alertsbadge {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px; padding: 0 3px;
  border-radius: 8px; background: var(--neg); color: var(--on-accent); font-size: 10px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; line-height: 1;
}
.alertspanel {
  position: fixed; top: 46px; right: 16px; z-index: 8500; width: 340px; max-height: 420px;
  overflow: auto; background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 18px 44px var(--shadow-lg);
}
.alertspanel-title { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); padding: 10px 14px 4px; }
.alert-row { padding: 10px 14px; border-top: 1px solid var(--line-soft); cursor: pointer; }
.alert-row:hover { background: var(--accent-soft); }
.alert-row.unread { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.alert-row.unread .alert-msg { font-weight: 600; }
.alert-msg { font-size: 12.5px; color: var(--text); line-height: 1.4; }
.alert-when { font-size: 11px; color: var(--text-dim); margin-top: 3px; }

/* ===================== admin: checklist field + widget blocks ============ */
.checklist {
  display: flex; flex-direction: column; gap: 2px; max-height: 220px; overflow: auto;
  border: 1.5px solid var(--line); border-radius: 8px; padding: 6px 8px; background: var(--surface);
}
.checklist-row { display: flex; align-items: center; gap: 8px; padding: 4px 2px; font-size: 12.5px; }
.checklist-row:hover { background: var(--accent-soft); border-radius: 4px; }
.checklist-empty { font-size: 12.5px; color: var(--text-dim); font-style: italic; padding: 4px 2px; }

.widgetblock {
  border: 1.5px solid var(--line); border-radius: 10px; padding: 14px 16px; margin-bottom: 14px; background: var(--paper);
}
.widgetblock-title { font-weight: 600; font-size: 13px; color: var(--ink); margin-bottom: 10px; }

/* preview-as-role caveat within Dashboards (§enh limitation) */
.dash-preview-caveat {
  background: var(--warn-bg); border: 1px solid var(--warn-line); color: var(--warn-fg-2);
  padding: 8px 12px; border-radius: 8px; font-size: 12.5px; margin-bottom: 14px;
}

/* ===================== admin: design spec viewer ========================= */
.designspec-head { display: flex; align-items: baseline; gap: 14px; }
.designspec-head a { font-size: 12.5px; }
.designspec-body {
  max-width: 880px; margin-top: 10px; padding: 22px 28px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 10px; font-size: 13.5px; line-height: 1.6;
}
.designspec-body .md-h1 { font-size: 20px; color: var(--ink); margin: 26px 0 10px; padding-bottom: 6px; border-bottom: 2px solid var(--accent); }
.designspec-body .md-h2 { font-size: 17px; color: var(--ink); margin: 22px 0 8px; }
.designspec-body .md-h3 { font-size: 14.5px; color: var(--accent); margin: 18px 0 6px; text-transform: uppercase; letter-spacing: .03em; }
.designspec-body h5, .designspec-body h6 { font-size: 13.5px; color: var(--ink); margin: 14px 0 4px; }
.designspec-body .md-hr { border: none; border-top: 1px solid var(--line-soft); margin: 20px 0; }
.designspec-body .md-p { margin: 0 0 12px; color: var(--text); }
.designspec-body .md-quote {
  margin: 0 0 14px; padding: 8px 14px; border-left: 3px solid var(--accent);
  background: var(--paper); color: var(--text-dim); font-style: italic;
}
.designspec-body .md-quote p { margin: 0 0 4px; }
.designspec-body .md-code {
  margin: 0 0 14px; padding: 12px 14px; background: var(--ink); color: #d7dce1;
  border-radius: 8px; overflow-x: auto; font-family: var(--mono); font-size: 12px; line-height: 1.5;
}
.designspec-body .md-code code { background: none; color: inherit; padding: 0; }
.designspec-body code {
  font-family: var(--mono); font-size: 12px; background: var(--paper);
  border: 1px solid var(--line-soft); border-radius: 4px; padding: 1px 5px;
}
.designspec-body .md-table { width: 100%; border-collapse: collapse; margin: 0 0 16px; font-size: 12.5px; }
.designspec-body .md-table th, .designspec-body .md-table td { text-align: left; padding: 6px 10px; border: 1px solid var(--line-soft); vertical-align: top; }
.designspec-body .md-table th { background: var(--paper); color: var(--ink); font-weight: 600; }
.designspec-body .md-list { margin: 0 0 14px; padding-left: 22px; }
.designspec-body .md-list li { margin-bottom: 5px; }
.designspec-body .md-list li input[type="checkbox"] { margin-right: 6px; }
.designspec-raw { white-space: pre-wrap; font-family: var(--mono); font-size: 12px; color: var(--text); }

/* ===================== admin: dashboard layout canvas (drag/resize) ====== */
.gridcanvas-wrap {
  border: 1.5px solid var(--line); border-radius: 10px; padding: 10px;
  background:
    linear-gradient(90deg, var(--line-soft) 1px, transparent 1px) 0 0 / calc(100% / 12) 100%,
    var(--paper);
  overflow: auto;
}
.gridcanvas { position: relative; width: 100%; min-height: 220px; }
.gridcanvas-item {
  position: absolute; box-sizing: border-box; background: var(--surface); border: 1.5px solid var(--line);
  border-radius: 8px; padding: 8px 10px; cursor: grab; user-select: none;
  display: flex; flex-direction: column; justify-content: space-between;
  box-shadow: 0 1px 3px var(--shadow-xs); transition: box-shadow .1s ease;
}
.gridcanvas-item:hover { border-color: var(--accent); box-shadow: 0 3px 10px var(--shadow-sm); }
.gridcanvas-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gridcanvas-item.dragging, .gridcanvas-item.resizing {
  cursor: grabbing; z-index: 20; box-shadow: 0 10px 26px var(--shadow-lg); border-color: var(--accent);
}
.gridcanvas-item.overlap { border-color: var(--neg); box-shadow: 0 0 0 2px rgba(166,58,43,.25); }
.gridcanvas-item .gc-label {
  font-size: 12px; font-weight: 600; color: var(--ink); line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gridcanvas-item .gc-badge { font-family: var(--mono); font-size: 10.5px; color: var(--text-dim); }
.gridcanvas-item .gc-resize-handle {
  position: absolute; right: 0; bottom: 0; width: 16px; height: 16px; cursor: nwse-resize;
  border-radius: 0 0 6px 0;
  background: linear-gradient(135deg, transparent 50%, var(--line) 50%, var(--line) 60%, transparent 60%,
    transparent 70%, var(--line) 70%, var(--line) 80%, transparent 80%);
}
.gridcanvas-item:hover .gc-resize-handle, .gridcanvas-item.resizing .gc-resize-handle {
  background: linear-gradient(135deg, transparent 50%, var(--accent) 50%, var(--accent) 60%, transparent 60%,
    transparent 70%, var(--accent) 70%, var(--accent) 80%, transparent 80%);
}

/* ===================== admin: new-tab wizard ============================= */
.wiz-head { display: flex; align-items: center; gap: 14px; margin-bottom: 4px; }
.wiz-head h2 { margin: 0; flex: 1; }
.wiz-crumbs { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 18px; }
.wiz-crumb {
  font-size: 11.5px; padding: 5px 11px; border-radius: 999px; background: var(--paper);
  border: 1px solid var(--line); color: var(--text-dim);
}
.wiz-crumb.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.wiz-crumb.done { border-color: var(--accent); color: var(--accent); }
.wiz-body { max-width: 760px; }
.wiz-nav { margin-top: 20px; gap: 10px; }

/* ===================== admin: quiet build/version footer =================== */
.adminfooter {
  /* Was `padding: 6px 20px` with no height at all, so the row was whatever its
     text happened to measure: 25px once the build id arrived, and only 13px
     while it was still empty. That produced two separate jumps — everything
     above it shifted down as the footer populated on load, and shifted again
     by 5px on every switch to the reports view, whose strip is a fixed 30px.
     Now a fixed row of the shared height, centred rather than padded, so it is
     stable whether the text is present, absent or long. `flex: none` matches
     the status bar: both are flex children of `body`, and without it the strip
     is shrinkable and can be squeezed by a tall admin body. */
  height: var(--strip-height); flex: none;
  display: flex; align-items: center;
  padding: 0 20px; font-size: 10.5px; color: var(--text-dim); font-family: var(--mono);
  border-top: 1px solid var(--line-soft); background: var(--paper);
  white-space: nowrap; overflow: hidden;
}

/* ===================== whole-dashboard PDF export (print) ================ */
@media print {
  body * { visibility: hidden; }
  #dashboardswrap, #dashboardswrap * { visibility: visible; }
  #dashboardswrap {
    position: absolute; top: 0; left: 0; width: 100%; padding: 0; background: var(--surface);
  }
  .no-print { display: none !important; }
  /* same single-column collapse already used under 900px on screen — avoids
     a widget being awkwardly split across a page break in a multi-column grid */
  .dash-grid { display: block; }
  .dash-widget {
    break-inside: avoid; page-break-inside: avoid; margin-bottom: 16px;
    border-color: var(--line); box-shadow: none; min-height: 0;
  }
  .dash-title { color: var(--ink); }
}

/* ===================== admin: wide SQL fields ============================= */
/* .field caps at 420px, which is right for a label or a number but far too
   narrow to read a SQL query in. This opt-in variant is used by the global
   parameter editor's Options/Default SQL boxes. */
.field.field-wide { max-width: 100%; }
.field.field-wide textarea {
  min-height: 84px; resize: vertical; font-family: var(--mono); font-size: 12.5px;
  line-height: 1.5; white-space: pre; overflow-x: auto;
}
