/* ===================================================================
   Adment-aligned visual layer.
   Loaded AFTER style.css and deliberately overrides it. The reference
   captures in adment-implementation-reference-20260911/live-20260911
   are 1349x722 CSS px; every fixed number below was measured off those
   pixels (rail edge x=72, panel 113..1308, row pitch 48px, case layer
   left edge x=307, ad layer x=377) rather than estimated by eye.

   What this replaces, per the user's correction:
     - 240px dark teal sidebar  -> 72px white icon rail
     - teal accent              -> blue accent on light grey
     - big stat cards           -> compact underline tabs with counts
     - centred report modal     -> right-hand overlay layer
   =================================================================== */

:root {
  --bg: #f6f8fb;
  --panel: #ffffff;
  --panel-alt: #fbfcfe;
  --line: #e3e8f0;
  --line-soft: #edf2f8;
  --text: #0b1730;
  --text-2: #33455c;
  --muted: #5f6d82;
  --accent: #1d54d1;
  --accent-2: #3b82f6;
  --accent-soft: #e8effd;
  --accent-line: #1d54d1;
  --teal: #5eead4;
  --teal-soft: #e3fbf5;
  --ink: #070f22;
  --ink-2: #0c1a36;
  --ink-3: #132a52;
  --success: #0f9d58;
  --success-soft: #e6f7ed;
  --warn: #a95c00;
  --warn-soft: #fff4e5;
  --danger: #b42318;
  --danger-soft: #fdeaea;
  --chip: #f2f4f9;
  --chip-line: #e4e8f2;
  --row-hover: #f4f7fd;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-xl: 18px;
  --shadow-xs: 0 1px 2px rgba(11, 23, 48, .05);
  --shadow-sm: 0 1px 2px rgba(11, 23, 48, .04), 0 6px 20px -12px rgba(11, 23, 48, .18);
  --shadow-md: 0 2px 4px rgba(11, 23, 48, .05), 0 18px 40px -18px rgba(11, 23, 48, .28);
  --shadow-lg: 0 24px 60px -20px rgba(7, 15, 34, .35);
  --ease: cubic-bezier(.2, .7, .2, 1);
  --dur: .18s;
  --rail: 232px;
}

html, body { background: var(--bg); color: var(--text); }
body { font-size: 14px; line-height: 1.5; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }

/* ---- Buttons -------------------------------------------------------
   Bare <button> is the primary action everywhere (login submit, wizard
   Next/Finish, dialog primary actions). Every other button-like class
   (.secondary, .ghostbtn, .iconbtn, .linkbtn, .sortbtn, ...) already sets
   its own background, so it never inherits the primary fill. */
button, .button {
  background: linear-gradient(180deg, var(--accent-2) 0%, var(--accent) 100%);
  color: #fff;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 9px 15px;
  font-size: 13.5px;
  cursor: pointer;
  transition: filter var(--dur) var(--ease), transform var(--dur) var(--ease),
    background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
button:hover, .button:hover { filter: brightness(1.06); }
button:active, .button:active { transform: translateY(.5px); }
button:disabled { opacity: .45; cursor: default; filter: none; transform: none; }
button:focus-visible, .button:focus-visible, a:focus-visible, summary:focus-visible,
input:focus-visible, select:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
.secondary, .ghostbtn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--panel);
  color: var(--text-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 9px 14px;
  font-size: 13.5px;
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.secondary:hover, .ghostbtn:hover { border-color: #c7d0e0; background: var(--panel-alt); filter: none; box-shadow: var(--shadow-sm); }

/* ---- Icon rail ---------------------------------------------------- */
aside {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--rail);
  background: linear-gradient(190deg, var(--ink-2) 0%, var(--ink) 100%);
  border-right: 1px solid rgba(255, 255, 255, .06);
  color: rgba(255, 255, 255, .7);
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  z-index: 30;
}
.rail-logo {
  height: 58px;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 18px;
  margin-bottom: 10px;
  color: #fff;
  font-weight: 800;
  font-size: 14.5px;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.rail-logo svg { width: 21px; height: 21px; color: #fff; flex: none; }
/* The rail's own mark: a standalone crop of the shield graphic out of the
   canonical BrandPatrol.net logo (logo-mark.svg, dark-background variant --
   the rail sits on the same dark ink as the sign-in screen), shown small
   here because the wordmark beside it already carries the name. Sized to
   the tile `.mark` used to fill so the header row does not reflow. The
   crop's own viewBox is a tight square around the shield's outer path, so
   the rendered mark already fills its box edge-to-edge -- confirmed by a
   zoomed render, where the shield's rounded body and inner arcs sit fully
   inside the frame with no border-radius needed and nothing clipped. */
.rail-logo .rail-mark { width: 26px; height: 26px; flex: none; }
/* The workspace control sits directly under the logo. For a Superadmin or a
   Brand owner it is a labelled indicator (one accessible brand, nothing to
   switch to). For an Agency -- which can be granted more than one brand and
   is the only role that can register a new one -- it is a real dropdown:
   the brands it can reach, plus "+ Onboard new brand". */
.rail-workspace {
  display: flex;
  align-items: center;
  gap: 8px;
  width: calc(100% - 20px);
  margin: 2px auto 12px;
  padding: 9px 10px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: var(--r-md);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.rail-workspace:not(.static):hover { border-color: rgba(255, 255, 255, .22); background: rgba(255, 255, 255, .09); }
/* The indicator form: one brand, nothing to switch to. It reads as a label,
   so it takes no pointer affordance and no hover response -- the resting
   colours above are left to stand rather than restated here. */
.rail-workspace.static { cursor: default; }
.rail-workspace svg:first-child { width: 16px; height: 16px; flex: none; color: var(--teal); }
.rail-workspace-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-workspace-chevron { width: 14px; height: 14px; flex: none; color: rgba(255, 255, 255, .5); transition: transform var(--dur) var(--ease); }
.rail-workspace[aria-expanded="true"] .rail-workspace-chevron { transform: rotate(180deg); }
.workspacemenu {
  position: fixed;
  z-index: 60;
  width: 248px;
  background: rgba(255, 255, 255, .98);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  padding: 6px;
}
.workspacemenu-list { display: flex; flex-direction: column; gap: 1px; margin-bottom: 2px; }
.workspacemenu-item {
  border: 0; background: transparent; font-family: inherit; text-align: left; cursor: pointer;
  display: flex; align-items: center; gap: 8px;
  padding: 8px 9px; border-radius: var(--r-md);
  font-size: 13px; color: var(--text-2);
}
.workspacemenu-item.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.workspacemenu-item .wchip {
  font-size: 10.5px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--muted); background: var(--chip); border-radius: 4px; padding: 1px 5px;
}
.workspacemenu-empty { padding: 8px 9px; font-size: 12.5px; color: var(--muted); }
.workspacemenu-add {
  display: block; width: 100%; text-align: left; background: transparent;
  border: 0; border-top: 1px solid var(--line-soft); border-radius: 0;
  padding: 9px 9px 8px; margin-top: 2px; font-size: 13px; font-weight: 600;
  color: var(--accent); cursor: pointer; transition: background var(--dur) var(--ease);
}
.workspacemenu-add:hover { background: var(--accent-soft); filter: none; }
/* ---- Brand onboarding wizard: segmented step indicator -------------- */
#onboarddialog { padding: 0; width: min(560px, 94vw); max-width: 560px; border: 0; border-radius: var(--r-xl); box-shadow: var(--shadow-lg); }
/* ---- Keyword auto-detect drawer (Keywords settings page) ------------ */
#keywordsuggest { padding: 0; width: min(480px, 94vw); max-width: 480px; border: 0; border-radius: var(--r-xl); box-shadow: var(--shadow-lg); }
#keywordsuggest .formactions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 16px; }
#keywordsuggest .checklist { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; max-height: 340px; overflow-y: auto; }
#keywordsuggest .checklist .checkline { font-size: 13.5px; padding: 6px 0; margin: 0; }
.onboardbody { padding: 4px 28px 28px; }
.onboardsteps {
  display: flex;
  padding: 24px 28px 20px;
  margin: 0 -28px 20px;
  border-bottom: 1px solid var(--line-soft);
}
.onboardstep {
  flex: 1 1 0;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  text-align: center;
}
.onboardstep::before {
  content: attr(data-step);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--chip);
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.onboardstep::after {
  content: "";
  position: absolute;
  top: 12px;
  left: calc(50% + 20px);
  right: calc(-50% + 20px);
  height: 2px;
  background: var(--line);
  z-index: -1;
  transition: background var(--dur) var(--ease);
}
.onboardstep:last-child::after { display: none; }
.onboardstep.active { color: var(--accent); }
.onboardstep.active::before { background: var(--accent); color: #fff; box-shadow: 0 0 0 4px var(--accent-soft); }
.onboardstep:has(~ .onboardstep.active) { color: var(--text-2); }
.onboardstep:has(~ .onboardstep.active)::before { background: var(--accent); color: #fff; }
.onboardstep:has(~ .onboardstep.active)::after { background: var(--accent); }
.onboardpane label { display: block; font-size: 12.5px; font-weight: 600; color: var(--muted); margin-bottom: 14px; }
.onboardpane input {
  width: 100%; margin-top: 6px; padding: 9px 11px; font-size: 14px;
  border: 1px solid var(--line); border-radius: var(--r-md);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.onboardpane input:focus { border-color: var(--accent-2); outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }
.onboardpane .formactions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 16px; }
#obcandidates { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
#obcandidates .checkline { font-size: 13.5px; padding: 6px 0; }
.obkeywords { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; min-height: 32px; }
.obaddkw { display: flex; gap: 8px; margin-bottom: 6px; }
.obaddkw input { flex: 1 1 auto; margin-top: 0; padding: 8px 10px; font-size: 13px; border: 1px solid var(--line); border-radius: var(--r-md); }
.obaddkw button { flex: none; }
/* Step 4 (monitoring): the pane's input rule does not reach textareas or
   selects, and the checkbox rows must not take the full-width bordered look
   the text inputs get. */
.onboardpane textarea,
.onboardpane select {
  width: 100%; margin-top: 6px; padding: 9px 11px; font-size: 14px; font-family: inherit;
  border: 1px solid var(--line); border-radius: var(--r-md);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.onboardpane textarea { resize: vertical; box-sizing: border-box; }
.onboardpane textarea:focus,
.onboardpane select:focus { border-color: var(--accent-2); outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }
.obgroup { margin-bottom: 14px; }
.obgroup .obgrouplabel { display: block; font-size: 12.5px; font-weight: 600; color: var(--muted); }
#onboardstep4 .checkgrid { margin: 6px 0 0; }
#onboardstep4 .checkgrid .checkline { display: flex; font-weight: 400; color: var(--text-2); }
#onboardstep4 .checkgrid .checkline input { width: auto; margin: 0; padding: 0; border: 0; box-shadow: none; }
.obreadiness { display: flex; flex-direction: column; gap: 6px; margin: 2px 0 4px; }
.obreadiness .obcheck { display: flex; gap: 8px; align-items: baseline; margin: 0; font-size: 13px; color: var(--text-2); }
.obreadiness .obcheckmark { flex: none; font-weight: 700; }
.obreadiness .obcheckmark.ok { color: var(--success); }
.obreadiness .obcheckmark.bad { color: var(--danger); }
aside nav { display: flex; flex-direction: column; gap: 2px; padding: 4px 10px; }
aside nav :is(button, a[data-view]) {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 11px;
  background: transparent;
  border: 0;
  border-radius: var(--r-md);
  padding: 9px 11px;
  color: rgba(255, 255, 255, .62);
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.3;
  text-align: left;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
aside nav :is(button, a[data-view]) svg { width: 18px; height: 18px; flex: none; }
aside nav :is(button, a[data-view]) span { display: block; overflow: hidden; text-overflow: ellipsis; }
aside nav :is(button, a[data-view]):hover { background: rgba(255, 255, 255, .07); color: #fff; }
aside nav :is(button, a[data-view]).active {
  background: rgba(59, 130, 246, .2);
  color: #fff;
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--accent-2);
}
aside nav :is(button, a[data-view]).active svg { color: var(--teal); }
/* The admin console link (index.html `[data-admin-console-link]`) is a
 * real full-page `<a>`, not a `<button>`, so it never matches the rail's
 * `nav button` active-state selector (LegacySection.tsx) -- it just needs
 * to look like the other rail items, plus a small external-navigation
 * hint and a hairline separating it from the SPA views above it. */
aside nav a[data-admin-console-link] {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 11px;
  background: transparent;
  border: 0;
  border-top: 1px solid rgba(255, 255, 255, .08);
  margin-top: 6px;
  padding: 13px 11px 9px;
  color: rgba(255, 255, 255, .62);
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.3;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
aside nav a[data-admin-console-link] svg { width: 18px; height: 18px; flex: none; }
aside nav a[data-admin-console-link] span { display: block; overflow: hidden; text-overflow: ellipsis; }
aside nav a[data-admin-console-link]:hover { background: rgba(255, 255, 255, .07); color: #fff; }

.rail-bottom { margin-top: auto; padding: 10px 12px 14px; border-top: 1px solid rgba(255, 255, 255, .08); }
.rail-user {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 8px 0 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent-2), var(--accent));
  color: #fff;
  border: 0;
  padding: 0;
  cursor: pointer;
  box-shadow: 0 4px 14px -4px rgba(29, 84, 209, .65);
  transition: transform var(--dur) var(--ease), filter var(--dur) var(--ease);
}
.rail-user:hover { transform: translateY(-1px); filter: brightness(1.08); }
.rail-user svg { width: 18px; height: 18px; }
aside .buildstamp {
  display: block;
  font-size: 10px;
  text-align: left;
  color: rgba(255, 255, 255, .32);
  padding: 0 2px 8px;
  word-break: break-all;
  line-height: 1.3;
}

/* The account menu is a floating light card whose DOM parent is the dark
   rail; `.accountmenu button{color:inherit}` (style.css) would otherwise
   inherit the rail's light text colour and render invisible white-on-white
   labels. Re-asserted here, plus the same blur/shadow treatment as the
   workspace menu. */
.accountmenu {
  color: var(--text-2);
  background: rgba(255, 255, 255, .98);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
.accountmenu :is(button, a):hover { background: var(--chip); }
/* Menu rows lead with a glyph: the icon is decorative, the label speaks.
   `justify-content` is reset because style.css spaces the row's children
   apart, which pushed every label to the far edge, away from its icon. */
.accountmenu button { display: flex; align-items: center; justify-content: flex-start; gap: 9px; width: 100%; text-align: left; }
.accountmenu a { display: flex; align-items: center; gap: 9px; width: 100%; color: inherit; text-decoration: none; text-align: left; padding: 10px 12px; border-radius: 7px; font-size: 14px; }
.accountmenu :is(button, a) svg { width: 15px; height: 15px; flex: none; color: var(--muted); }
.accountmenu :is(button, a):hover svg { color: var(--text); }
/* The signed-in identity line at the top of the account menu (`#railrole`,
 * app.js `applySession`): "username · role label", muted and unclickable,
 * so the dropdown states who is signed in before its two actions. */
.railrole {
  padding: 8px 12px 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--line-soft, rgba(16, 42, 61, .1));
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted, #6b7785);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* ---- Page frame --------------------------------------------------- */
main {
  margin-left: var(--rail);
  padding: 28px 40px 40px;
  max-width: none;
}
header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin: 0 0 20px;
}
header h1 {
  font-size: 25px;
  font-weight: 700;
  letter-spacing: -.5px;
  margin: 0;
  color: var(--text);
}
.headeractions { display: flex; align-items: center; gap: 10px; }
.ghostbtn svg { width: 15px; height: 15px; }

/* ---- Icons in buttons and headings ---------------------------------
   frontend/src/ui/Icons.tsx draws every glyph in a 24x24 currentColor
   frame; the size lives here so the stroke metrics have one home. A
   primary <button> that carries an icon becomes a flex row so the label
   and glyph sit on one baseline. Zero specificity (`:where`) on purpose:
   the base `button` rule sets no display, so a primary still gets the row,
   while every variant that lays itself out (.iconbtn is a grid, tabs and
   segments are their own) wins outright. No `justify-content`: a button
   sized to its content looks the same either way, and stretched rows (rail
   links, account-menu rows) set no justify of their own — a `center` here
   was the only value they had, and it centred every rail label. More
   specific rules (aside nav button svg 18px, .iconbtn svg 16px) keep their sizes. */
:where(button:has(> svg), .button:has(> svg)) {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
button > svg, .button > svg { width: 15px; height: 15px; flex: none; }
/* Inline glyph inside a heading, summary, table header or notice: text-sized
   and baseline-aligned, so it reads as part of the sentence. Colour inherits
   so a warning notice's icon warns in the notice's own colour. */
svg.hicon { width: 16px; height: 16px; vertical-align: -3px; margin-right: 7px; color: inherit; flex: none; }
summary svg.hicon, th svg.hicon { width: 14px; height: 14px; vertical-align: -2px; }
/* Status chips that lead with a state glyph: the glyph takes the chip's own
   colour and sits tight against the label, so no per-use inline style is
   needed. */
.chips svg, .chip svg { width: 13px; height: 13px; flex: none; color: inherit; margin-right: 4px; vertical-align: -2px; }
.mschip svg.hicon { color: inherit; margin-right: 0; }
/* An empty state dropped into the evidence thumbnail grid spans the row. */
.evidencegrid > .emptystate { grid-column: 1 / -1; }

/* The environment caveat is a single quiet strip, not a yellow block
   that owns the first screen. */
.notice, #hijdemo, #biddemo, #setdemo {
  background: var(--panel-alt);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent-2);
  border-radius: var(--r-md);
  padding: 10px 14px;
  margin-bottom: 14px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-2);
}
#alert:not([hidden]) { border-left-color: var(--warn); background: var(--warn-soft); }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 0;
  margin-bottom: 16px;
  box-shadow: var(--shadow-xs);
}
.panel > h2 { padding: 18px 20px 0; }

/* ---- List panel: tabs -> filters -> table -> pager ----------------- */
.casepanel { display: flex; flex-direction: column; }
.tabrow {
  display: flex;
  align-items: stretch;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  padding: 0 14px;
  overflow-x: auto;
}
.tabrow button {
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--muted);
  font-size: 13.5px;
  font-weight: 500;
  padding: 13px 12px 11px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.tabrow button:hover { color: var(--text-2); filter: none; background: transparent; }
.tabrow button[aria-pressed="true"] { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.tabrow .tabcount {
  background: var(--chip);
  color: var(--muted);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  padding: 1px 7px;
  font-variant-numeric: tabular-nums;
}
.tabrow button[aria-pressed="true"] .tabcount { background: var(--accent-soft); color: var(--accent); }

/* `.filterbar` is now only a vertical stack: the primary row, then the
   optional secondary panel. It holds no horizontal layout of its own.

   `align-items` is pinned here because the legacy sheet still carries
   `#hijfilters { align-items: end }` - an ID, so it outranks this class. On
   the old row that was inert; on a column it stops the rows stretching, and
   the primary row then sized to its content (1361px inside a 1195px panel)
   and hung off the left edge, cutting the Date pill in half. */
.filterbar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 12px 16px;
}
/* Two groups share the primary row: `.fpills` (the pills + More filters)
   and `.fsearchgroup` (search + export). The pills stay on one line —
   that keeps their labels readable and the table high on the page — and
   absorb pressure by shrinking to their floor. Search + export move under
   the pills *together* when the line is too short for both.

   This row used to be one `nowrap` line holding everything. The comment
   promised "seven pills at the floor still leave the search box its
   minimum"; at laptop widths they did not, so `.fsearch` (min-width 0)
   collapsed to 0px, its 210px input spilled out, and the export button
   was drawn on top of the search field.

   `.filtermore` used to sit *inside* this row, so opening it competed for
   width and shrank every pill below its own label ("Country" -> "C...",
   "Advertiser" -> "Advert...") exactly when the user had opened the panel to
   filter hardest. It is now a sibling in the column above, so the row keeps
   the full width whether the panel is open or closed. */
.filterrow {
  display: flex;
  align-items: center;
  gap: 10px 8px;
  /* Wraps rather than overflowing: at a mid-width window the seven pills,
     More filters, search and export do not fit one line, and nowrap pushed
     search past the card edge and the export button out of sight. */
  flex-wrap: wrap;
  min-width: 0;
}
.fpills {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
  /* Never grows: free space on the line belongs to the search group. */
  flex: 0 1 auto;
  /* A <select>'s min-content is its widest option; without an explicit 0
     the group could not shrink and the pills' own floor would never apply. */
  min-width: 0;
}
.fsearchgroup {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  /* Basis 0 with the default `min-width: auto`: the group's size for line
     breaking is its min-content (search floor + export), so it drops to a
     second line only when the search box could not get its floor beside the
     pills — a second line pushes the pager below the fold at 1349x722
     (test_list_viewport). Then it grows into whatever the pills leave. */
  flex: 1 1 0%;
}
/* Pills never go below the width of their own label: the row is only
   useful if a reader can still tell the controls apart. Where the browser
   can size a <select> to its selected text (`field-sizing: content`), each
   pill is exactly as wide as its label — "Tag" stops costing as much as
   "Advertiser", the seven pills need ~785px instead of a fixed 7×floor, and
   no label can be cut. Elsewhere a shared floor applies: 108px clears the
   longest label ("Advertiser") plus the padding and the chevron in Inter;
   the old 104px read "Advertis...".

   Without a floor, adding the Domain filter silently truncated "Country"
   to "C..." - the same class of defect the row was restructured to fix. */
.fpills > .fpill { flex: 0 1 auto; min-width: 108px; }
@supports (field-sizing: content) {
  .fpills > .fpill { field-sizing: content; flex: 0 0 auto; min-width: 0; }
}
/* "More filters" is a fixed label with no value to abbreviate: shrinking it
   produced "More filt...", which reads as a defect rather than a control. */
.fpills > .morechip { flex: 0 0 auto; }
/* Below the width where the pills still fit on one line, they wrap rather
   than run past the panel edge (fallback floor: 7×108 + More + gaps, plus
   the rail and gutters, is ~1280px). */
@media (max-width: 1280px) {
  .fpills { flex-wrap: wrap; }
}
.filterbar select,
.filterbar input[type="date"] {
  appearance: none;
  background: var(--panel) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23667085' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>") no-repeat right 8px center;
  background-size: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 8px 28px 8px 11px;
  font-size: 13px;
  color: var(--text-2);
  min-height: 36px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.filterbar input[type="date"] { background-image: none; padding-right: 10px; }
.filterbar select:hover { border-color: #cfd4dc; }
.filterbar select:focus, .filterbar input[type="date"]:focus {
  outline: none;
  border-color: var(--accent-2);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
/* A filter pill reads as its own name until a value is chosen, then shows
   "Name: value" and takes the accent, so an active filter is visible without
   opening the control. */
.fpill { max-width: 220px; text-overflow: ellipsis; }
.fpill.on {
  border-color: var(--accent);
  color: var(--accent);
  background-color: var(--accent-soft);
}
/* Export is a labelled button so it reads as an action, not a stray icon. */
.filterbar .exportbtn { width: auto; border-radius: var(--r-md); padding: 0 12px; display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }

/* Inside `.fsearchgroup`: the group's gap spaces search from export, and the
   search box has a real floor — at `min-width: 0` it collapsed to nothing
   whenever the row ran short. */
.filterbar .fsearch { position: relative; flex: 0 1 240px; min-width: 160px; }
.filterbar .fsearch input {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 8px 11px 8px 32px;
  font-size: 13px;
  min-height: 36px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.filterbar .fsearch input:focus {
  outline: none;
  border-color: var(--accent-2);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.filterbar .fsearch svg {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  color: var(--muted);
  pointer-events: none;
}
.filterbar .checkline { font-size: 12.5px; color: var(--muted); gap: 5px; }
.filterbar .linkbtn { font-size: 12.5px; font-family: inherit; }
/* Secondary filters. Real capability that does not fit the reference's one
   line: parked behind a toggle instead of being deleted. The dot marks that
   something inside is active, so a hidden filter can never silently narrow
   the list without a visible trace. */
.filtermore {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  /* Parent is a column with its own 8px gap; no extra top margin. */
  padding: 12px 0 2px;
  border-top: 1px solid var(--line-soft);
}
.filtermore[hidden] { display: none; }
.filtermore label { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--muted); }
.filtermore .muted.small { flex-basis: 100%; margin: 0; font-size: 11.5px; line-height: 1.5; }
.morechip.dot::after {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 6px;
  border-radius: 50%;
  background: var(--accent);
  vertical-align: middle;
}
/* In the filter bar the toggle sits in a row of 36px controls, so it matches
   them there. The compact `.morechip` base is still what the case layer uses. */
.filterbar .morechip {
  min-height: 36px;
  padding: 8px 12px;
  font-size: 13px;
  border-radius: var(--r-md);
  background: #fff;
  border-color: var(--line);
}
.filterbar .morechip.on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.filtercount {
  padding: 0 16px 10px;
  margin: -2px 0 0;
  font-size: 12px;
  color: var(--muted);
}

/* Table: 48px pitch, column separators, no inner card. */
.casetable { font-size: 13px; width: 100%; border-collapse: collapse; }
.casetable thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--panel);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  border-right: 1px solid var(--line-soft);
  padding: 0;
  color: var(--muted);
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .02em;
  white-space: nowrap;
}
.casetable thead th:last-child { border-right: 0; }
.casetable .sortbtn {
  width: 100%;
  text-align: left;
  font-weight: 500;
  font-size: 12.5px;
  color: var(--muted);
  padding: 11px 12px;
  display: flex;
  align-items: center;
  gap: 5px;
}
.casetable .sortbtn:hover { color: var(--text-2); }
.casetable .sortbtn.on { color: var(--text-2); font-weight: 600; }
/* Every sortable column shows the affordance; the active one resolves to a
   single arrow. Hiding it on inactive columns hid that they sort at all. */
.casetable .sortmark {
  width: 13px;
  height: 13px;
  margin-left: 4px;
  color: #c3c9d4;
  flex: none;
}
.casetable .sortmark.on { color: var(--accent); }
.casetable .sortbtn { display: inline-flex; align-items: center; }
/* Long values truncate on one line and keep the full text in the tooltip,
   which is what holds the 48px row pitch. */
.ellip {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

.casetable tbody td {
  border-top: 0;
  border-bottom: 1px solid var(--line-soft);
  border-right: 1px solid var(--line-soft);
  padding: 0 12px;
  height: 48px;
  vertical-align: middle;
  color: var(--text-2);
  max-width: 190px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.casetable tbody td:last-child { border-right: 0; }
.casetable tbody tr:hover { background: var(--row-hover); }
.casetable tbody tr:focus { outline: 2px solid var(--accent-2); outline-offset: -2px; }
.casetable .dimrow { background: var(--panel-alt); color: var(--muted); }
.casetable .numcol { text-align: left; }

/* Identifier chip, status chip, tag chip - the three chip shapes the
   reference list uses, and nothing else. */
.idchip {
  display: inline-block;
  background: var(--chip);
  border: 1px solid var(--chip-line);
  border-radius: var(--r-sm);
  padding: 3px 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--text-2);
  white-space: nowrap;
}
.statuschip {
  display: inline-block;
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  background: var(--accent-soft);
  color: var(--accent);
}
.statuschip.s-investigating { background: var(--warn-soft); color: var(--warn); }
.statuschip.s-ready_for_action { background: #ecf3ff; color: #1d4ed8; }
.statuschip.s-resolved { background: var(--success-soft); color: var(--success); }
.statuschip.s-monitoring { background: var(--chip); color: var(--muted); }
.tagchip {
  display: inline-block;
  background: var(--accent-soft);
  border: 1px solid #d6e4fd;
  border-radius: var(--r-sm);
  padding: 3px 8px;
  font-size: 12px;
  color: #1d4ed8;
  white-space: nowrap;
}
.cellmuted { color: #98a2b3; }
.srcicon { width: 17px; height: 17px; vertical-align: -3px; }
.notedot { color: #98a2b3; display: inline-block; }

.pagerbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 16px;
  flex-wrap: wrap;
}
.pagerbar .perpage { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted); }
.pagerbar .perpage select {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 5px 9px;
  font-size: 12.5px;
}
.pagenums { display: flex; align-items: center; gap: 5px; }
.pagenums button {
  min-width: 30px;
  height: 30px;
  padding: 0 7px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-2);
  font-size: 12.5px;
  display: grid;
  place-items: center;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.pagenums button:hover:not(:disabled) { background: var(--chip); filter: none; }
.pagenums button[aria-current="true"] { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.pagenums button:disabled { color: #cfd4dc; cursor: default; }
.pagenums svg { width: 15px; height: 15px; }
.pagegap { color: var(--muted); font-size: 12.5px; padding: 0 2px; }

/* ---- Dialog foundations: rounded, layered shadow, blurred backdrop --- */
dialog::backdrop {
  background: rgba(7, 15, 34, .5);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
#detail, #invdetail {
  border: 0;
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
}

/* ---- Case layer: right overlay, not a centred modal ---------------- */
#casedetail, #addetail {
  position: fixed;
  margin: 0;
  border: 0;
  border-radius: var(--r-xl) 0 0 var(--r-xl);
  padding: 0;
  top: 0;
  bottom: 0;
  right: 0;
  left: auto;
  height: 100%;
  max-height: none;
  max-width: none;
  background: var(--panel);
  box-shadow: -24px 0 60px -16px rgba(7, 15, 34, .32), -1px 0 0 rgba(7, 15, 34, .05);
}
/* Measured: case layer starts at x=307 of 1349 (77.2% wide); the ad
   layer sits on top of it, inset a further 70px (x=377). */
#casedetail { width: 77.2vw; }
#addetail { width: 72vw; }
#casedetail[open], #addetail[open] { display: flex; flex-direction: column; }
#casedetail::backdrop, #addetail::backdrop { background: rgba(7, 15, 34, .45); }

.layerhead {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  /* Reference layer header measures 101px tall; the padding is what sets it. */
  padding: 33px 28px;
  border-bottom: 1px solid var(--line);
}
.layerhead h2 { font-size: 21px; font-weight: 650; margin: 0; color: var(--text); }
.layerhead .lh-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.iconbtn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text-2);
  display: grid;
  place-items: center;
  padding: 0;
  flex: none;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.iconbtn:hover { background: var(--chip); color: var(--text); filter: none; }
.iconbtn:active { transform: scale(.94); }
.iconbtn svg { width: 16px; height: 16px; }
#casebody, #adbody { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 8px 28px 28px; }

/* Label-value rows with thin separators - the reference case layout. */
.fieldrow {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  padding: 14px 0;
  border-bottom: 1px solid var(--line-soft);
  min-height: 56px;
}
.fieldrow > .flabel {
  flex: 0 0 200px;
  color: var(--muted);
  font-size: 13.5px;
  padding-top: 4px;
}
.fieldrow > .fvalue { flex: 1 1 auto; min-width: 0; font-size: 13.5px; color: var(--text-2); }
.fvalue .chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.addlink {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: none;
  border: 0;
  color: var(--accent);
  font-size: 13.5px;
  padding: 2px 0;
  cursor: pointer;
}
.addlink svg { width: 16px; height: 16px; }
.addlink:hover { filter: none; text-decoration: underline; }
.inlineinput {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 7px 10px;
  font-size: 13.5px;
  min-width: 220px;
}
.inlineselect {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 6px 9px;
  font-size: 13px;
}
.morechip {
  background: var(--chip);
  border: 1px solid var(--chip-line);
  border-radius: var(--r-sm);
  padding: 3px 8px;
  font-size: 12px;
  color: var(--text-2);
  cursor: pointer;
}
.switch {
  width: 40px;
  height: 22px;
  border-radius: 999px;
  background: #d0d5dd;
  border: 0;
  padding: 0;
  position: relative;
  cursor: pointer;
  transition: background .15s;
}
.switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  transition: transform .15s;
  box-shadow: 0 1px 3px rgba(16, 24, 40, .2);
}
.switch[aria-checked="true"] { background: var(--accent); }
.switch[aria-checked="true"]::after { transform: translateX(18px); }
.savehint { font-size: 12px; color: var(--muted); margin-left: 10px; }
.savehint.err { color: #b42318; }

.sectionhead {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 22px 0 10px;
}
.sectionhead h3 { font-size: 15px; font-weight: 600; margin: 0; color: var(--text); }
.sectionhead small { color: var(--muted); font-size: 12px; }

/* Qualifications stay available but folded: they are not the subject of
   the screen. */
.notes {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 0 14px;
  margin: 16px 0 0;
  background: #fcfcfd;
}
.notes > summary { padding: 11px 0; font-size: 13px; color: var(--text-2); font-weight: 500; }
.notes .facts { font-size: 13px; }
.notes p { font-size: 12.5px; line-height: 1.6; color: var(--muted); }

/* Ad layer: redirect chain and the two screenshot columns. */
.chain { list-style: none; margin: 0; padding: 0; }
.chain li {
  position: relative;
  padding: 0 0 14px 26px;
  border-left: 1px solid var(--line);
  margin-left: 7px;
}
.chain li:last-child { border-left-color: transparent; padding-bottom: 0; }
.chain li::before {
  content: "";
  position: absolute;
  left: -5px;
  top: 4px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--accent);
}
.chain .hop { font-size: 12.5px; color: var(--muted); }
.chain .hopurl {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  word-break: break-all;
  color: var(--text-2);
}
.shotcols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.shotcol { border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; background: #fff; }
.shotcol h4 { margin: 0; padding: 10px 12px; font-size: 12.5px; font-weight: 600; border-bottom: 1px solid var(--line-soft); color: var(--text-2); }
.shotcol .shotbox { background: #f8f9fb; cursor: zoom-in; display: block; width: 100%; border: 0; padding: 0; }
.shotcol img { display: block; width: 100%; height: 210px; object-fit: cover; object-position: top; margin: 0; border: 0; }
.shotcol .missing { padding: 26px 14px; font-size: 12.5px; color: var(--muted); text-align: center; line-height: 1.6; }

/* ---- Image viewer: light, centred, titled ------------------------- */
#shotviewer {
  background: var(--panel);
  border: 0;
  border-radius: var(--r-xl);
  color: var(--text);
  width: 74vw;
  max-width: 1000px;
  height: auto;
  max-height: 88vh;
  padding: 0;
  box-shadow: var(--shadow-lg);
}
#shotviewer::backdrop { background: rgba(16, 24, 40, .5); }
#shotviewer .viewerbar {
  background: #fff;
  border-bottom: 1px solid var(--line);
  height: auto;
  padding: 14px 18px;
  color: var(--text);
}
#shotviewer .viewerbar span { font-size: 14px; font-weight: 600; color: var(--text); }
#shotviewer .viewerbar > div { gap: 10px; }
#shotviewer .viewerbar a, #shotviewer .viewerbar .linkbtn { color: var(--accent); font-size: 13px; }
#shotviewer .viewerimage { background: #f8f9fb; height: auto; max-height: 68vh; padding: 14px; }
#shotviewer #shotimage { border: 1px solid var(--line); border-radius: var(--r-sm); }
#shotviewer #shotmeta { color: var(--muted); }
#shotviewer .galleryarrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 10px;
  background: #fff;
  border: 1px solid var(--line);
  color: var(--text-2);
  box-shadow: 0 2px 10px rgba(16, 24, 40, .12);
}

/* ---- Settings pages: shared group-card vocabulary -------------------- */
.setmain { display: flex; flex-direction: column; gap: 14px; }
.groupcard { background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg); padding: 16px 18px; }
.groupcard h3 { font-size: 14.5px; margin: 0 0 12px; font-weight: 600; }
.field { margin-bottom: 14px; }
.field > label { display: block; font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
/* `.mschip` is the live chip, used by the settings chip fields and by the
   case layer's keyword/country strips. The `.multiselect`/`.msoptions`/
   `.msrow` wrappers it once sat inside were replaced by `chipField` and
   matched nothing in the markup, so they are gone rather than left to look
   like the settings layout is defined in two places. */
.mschip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--accent-soft);
  border: 1px solid #c7d7fe;
  color: var(--accent);
  border-radius: 999px;
  padding: 4px 6px 4px 10px;
  font-size: 12.5px;
  font-weight: 500;
}
.mschip button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  background: none;
  border: 0;
  border-radius: 50%;
  color: inherit;
  padding: 0;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.mschip button:hover { background: rgba(29, 84, 209, .16); filter: none; }

.copybtn {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--text-2);
  font-size: 12.5px;
  padding: 7px 11px;
}
.addgroup {
  width: 100%;
  border: 1px dashed #cfd4dc;
  border-radius: var(--r-lg);
  background: #fff;
  color: var(--accent);
  padding: 13px;
  font-size: 13.5px;
  cursor: pointer;
}
.addgroup:hover { border-color: var(--accent); background: #fcfdff; filter: none; }
.setfooter {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 14px 0 0;
  border-top: 1px solid var(--line);
  margin-top: 4px;
}
.primarybtn {
  background: linear-gradient(180deg, var(--accent-2) 0%, var(--accent) 100%);
  color: #fff;
  border: 0;
  border-radius: var(--r-md);
  padding: 9px 18px;
  font-size: 13.5px;
  transition: filter var(--dur) var(--ease);
}
.primarybtn:hover { filter: brightness(1.06); }

/* ---- Mobile ------------------------------------------------------- */
@media (max-width: 900px) {
  /* The narrow rail is ONE horizontal strip: fixed logo, horizontally
     scrollable nav, fixed account button. It previously rendered ~310px tall
     with the buttons scattered across pseudo-columns and the avatar clipped
     off the right edge, because two global rules fight this layout:
       - style.css `nav { flex-wrap: wrap }` applies to every nav, so the
         row wrapped instead of scrolling;
       - style.css `.navgroup { flex: 1 1 100% }` at this same breakpoint
         forces each group label onto its own full-width line, which breaks
         the row into columns.
     Overridden here rather than edited there: those rules serve the auth
     screen and the desktop rail, which are not in this task's scope.
     `aside nav` (0,0,2) and `aside .navgroup` (0,1,1) win on specificity;
     theme.css also loads after style.css. */
  aside {
    position: static;
    width: auto;
    flex-direction: row;
    align-items: center;
    border-right: 0;
    border-bottom: 1px solid rgba(255, 255, 255, .08);
    /* The strip itself no longer scrolls - the nav inside it does, so the
       account button stays reachable without scrolling the whole rail. */
    overflow-x: hidden;
  }
  .rail-logo { height: 48px; padding: 0 10px; flex: 0 0 auto; border-bottom: 0; margin-bottom: 0; }
  aside nav {
    flex-direction: row;
    flex-wrap: nowrap;
    /* Takes the space between logo and account button, and scrolls within it. */
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  aside nav::-webkit-scrollbar { display: none; }
  /* 68x44 keeps every destination at a usable touch target while scrolling. */
  aside nav :is(button, a[data-view]) { min-width: 68px; min-height: 44px; flex: 0 0 auto; }
  /* Group headings are vertical-rail furniture. In a single scrolling line
     they can only act as row breaks, which is exactly what broke the layout;
     each button still carries its own label. */
  aside .navgroup { display: none; }
  /* `.rail-bottom` is a vertical-rail footer: in a horizontal strip its two
     navs plus the account button claimed 274px of 390px and squeezed the
     scrolling nav down to a 31px slot. It scrolls with everything else, and
     only the account button is pinned (below), so Operations, Users & Access
     and Settings sit on the same line as the rest instead of competing with
     it for width. */
  .rail-bottom { margin-top: 0; display: flex; align-items: center;
                 padding-bottom: 0; flex: 0 1 auto; min-width: 0;
                 overflow-x: auto; scrollbar-width: none; }
  .rail-bottom::-webkit-scrollbar { display: none; }
  .rail-bottom nav { flex-wrap: nowrap; }
  /* Pinned: the account menu must never scroll out of reach. */
  .rail-user { flex: 0 0 auto; margin: 0 8px 0 4px; }
  .rail-workspace, aside .buildstamp { display: none; }
  main { margin-left: 0; padding: 14px 12px 26px; }
  header h1 { font-size: 21px; }
  #casedetail, #addetail { width: 100vw; border-radius: 0; }
  .fieldrow { flex-direction: column; gap: 6px; min-height: 0; }
  .fieldrow > .flabel { flex: none; padding-top: 0; }
  .shotcols { grid-template-columns: 1fr; }
  /* Left to size themselves, the pills pack one or two per row at random and
     the filter block eats the whole first screen before a single case is
     visible. A shared basis packs them two per row, so the list starts higher
     up. Nothing is hidden: every control the desktop bar has is still here.
     (The pill group already wraps below 1240px.) */
  .fpills > .fpill { flex: 1 1 calc(50% - 4px); }
  .fpills > .morechip { flex: 1 1 calc(50% - 4px); justify-content: center; }
  /* Search + export take a full line of their own, search filling it. */
  .fsearchgroup { flex: 1 1 100%; }
  .filterbar .fsearch { flex: 1 1 auto; }
  /* Billing's filter form has its search box directly in the row. */
  .filterrow > .fsearch { margin-left: 0; flex: 1 1 100%; order: 1; }
  .filterrow .fsearch input { width: 100%; }
  #casebody, #adbody { padding: 8px 16px 22px; }
  .layerhead { padding: 14px 16px; }
}

/* Slim disclosure. One line by default, full text on expand. The point is that
   nothing is removed: a reader can always get the whole statement, but it stops
   costing two lines of every screen. */
.slimnotice { padding: 0; }
.slimnotice details { margin: 0; }
.slimnotice summary {
  cursor: pointer;
  list-style: none;
  padding: 8px 14px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text-2);
}
.slimnotice summary::-webkit-details-marker { display: none; }
.slimnotice summary .banner-details-label,
.slimnotice summary .banner-hide-label { margin-left: 6px; color: var(--accent); font-size: 11.5px; }
.slimnotice summary .banner-hide-label,
.slimnotice details[open] summary .banner-details-label { display: none; }
.slimnotice details[open] summary .banner-hide-label { display: inline; }
.slimnotice p { margin: 0; padding: 0 14px 10px; font-size: 12px; color: var(--muted); line-height: 1.55; }

/* style.css caps every .tablescroll at 340px, which is right for the small
   embedded tables it was written for but clips the case list mid-row. The case
   list is paged, so its height is bounded by pageSize, not by a scrollbox.

   Only the vertical cap is removed. An earlier `overflow: visible` here also
   disabled horizontal scrolling, which is invisible on a desktop width where
   the table fits, but on a phone the 1100px table escaped its 364px container
   and dragged the layout viewport out to 1112px - so the whole page rendered
   zoomed out and every other element was unreadable. overflow-y resolves to
   auto once overflow-x is not visible; with max-height:none there is nothing
   to scroll vertically, so no vertical scrollbar appears. */
.casepanel .tablescroll { max-height: none; overflow-x: auto; overflow-y: visible; }
.casepanel .tablescroll table { min-width: 0; }

/* Keep the pager on screen (B7 "pin the pager to the bottom").

   Measured at the 1349x722 acceptance viewport: the fixed chrome above the
   table is 275px (main padding, header, the demo-database notice, tabs,
   filters, the "N of M cases shown" line) and the pager is 52px, so an
   unbounded table put the pager at y=804 - off screen, leaving page 2 neither
   reachable nor discoverable without scrolling first.

   The reference bounds its table the same way: its own capture shows 8 of its
   10 rows with the pager resting at y~663. It simply has 94px more to spend,
   because it carries neither the demo notice nor the exclusion count - both
   of which are true statements about this data and are not dropped to win a
   pixel comparison.

   Desktop only. The rule above exists because a previous `overflow: visible`
   let a 1100px table drag the phone layout viewport out to 1112px; that fix
   is left exactly as it stands. */
@media (min-width: 901px) {
  .casepanel .tablescroll {
    max-height: calc(100vh - 347px);
    min-height: 180px;
    overflow-y: auto;
  }
  /* The header stays put while rows scroll under it; otherwise the column
     meanings are lost the moment the list moves. */
  .casepanel .tablescroll thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--panel);
  }
}

/* ---- Case layer: inline editing on the label-value rows ---------------
   Row geometry is defined once, at .fieldrow above. These are the controls
   that sit in the value column. */
.fieldlist { margin: 4px 0 0; }
.fieldrow:last-child { border-bottom: 0; }
.idchip {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
  background: var(--chip);
  border: 1px solid var(--chip-line);
  border-radius: var(--r-sm);
  padding: 3px 8px;
}
.notetext { white-space: pre-wrap; }
.linkvalue { background: transparent; border: 0; padding: 0; color: var(--text); font-size: 13px; cursor: pointer; border-bottom: 1px dashed var(--line); }
.statuschip {
  border-radius: 999px;
  padding: 3px 11px;
  font-size: 12px;
  font-weight: 600;
  border: 1px solid transparent;
  cursor: pointer;
  transition: filter var(--dur) var(--ease);
}
.s-new { background: #eef2ff; color: #3538cd; border-color: #c7d7fe; }
.s-in_progress { background: var(--warn-soft); color: var(--warn); border-color: #fbd9a8; }
.s-resolved { background: var(--success-soft); color: var(--success); border-color: #b9e8cc; }
/* Requested-only countries carry a star and a muted border: the difference
   between "we aimed here" and "the browser was verified here" must survive
   being reduced to a chip. */
.mschip.unverified { border-style: dashed; color: var(--muted); }
.savehint { margin: 8px 0 0; font-size: 12px; color: var(--muted); }
.savehint.bad { color: #b42318; }
.sectionhead {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 22px 0 8px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.sectionhead h3 { margin: 0; font-size: 14px; }
.detfilters { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
.detfilters .inlineinput { width: 260px; }
/* Our epistemic notes are real content, but they are not what a reviewer
   opens a case to read, so they sit below the evidence behind a summary. */
.notes { margin-top: 20px; border-top: 1px solid var(--line); padding-top: 10px; }
.notes summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text-2); }
.notesbody { font-size: 12.5px; color: var(--text-2); line-height: 1.6; }
.notesbody p { margin: 8px 0; }

/* Chips placed straight into a value column (no .chips wrapper) still need
   the same rhythm as the wrapped case. */
.fvalue > .mschip, .fvalue > .tagchip, .fvalue > .morechip { margin: 0 6px 0 0; }
.fvalue > .chiprest > .mschip { margin: 0 6px 0 0; }

/* A manually linked affiliate ID must not be mistakable for an observed one.
   The observed chip is the plain `.idchip`; a linked chip is dashed and
   carries the word "linked" inline, so the distinction survives a
   screenshot, a greyscale print and a colour-blind reader. */
.codeline { margin: 0 0 4px; }
.codeline:last-child { margin-bottom: 0; }
.linkchip {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
  background: #fffaf0;
  border: 1px dashed #f0c88a;
  color: #8a5a10;
  border-radius: var(--r-sm);
  padding: 3px 8px;
  margin: 0 6px 0 0;
}
.linkchip b {
  font-family: var(--font, inherit);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  margin-left: 6px;
  opacity: .85;
}

/* Detected-ads table: the reference keeps rows on one line and gives the
   title the slack, so the eye scans titles rather than wrapped columns.
   There is no Details button column -- the row itself opens the ad. */
.dettable tbody tr { cursor: pointer; }
.dettable tbody tr:hover { background: var(--row-hover); }
.dettable .titlecell { max-width: 340px; }
.dettable .titlecell .chip { margin-left: 7px; vertical-align: middle; }
.dettable .srccell, .dettable .devcell { width: 56px; text-align: center; color: var(--muted); }
.dettable .srccell svg, .dettable .devcell svg { vertical-align: middle; }
.dettable .datecell { white-space: nowrap; color: var(--text-2); }
/* Device is a glyph; the word stays in the accessibility tree. */
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ---- Redirect path -----------------------------------------------------
   A vertical chain: one dot per hop joined by a line, the URL on one line,
   and a caret that reveals what was actually recorded for that hop (status,
   method, resolved IPs). The detail is real captured data, so it is offered
   rather than summarised away. */
/* The reference frames the ad's destination URL in its own bordered box above
   the redirect path. The URL is long and must stay readable rather than being
   truncated into something that looks like a different address. */
.linkbox { display: flex; align-items: center; gap: 8px; margin: 4px 0 2px;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px;
  background: #f8fafc; }
.linkbox a { flex: 1; color: var(--accent); font-size: 12.5px; line-height: 1.5;
  word-break: break-all; text-decoration: none; }
.linkbox a:hover { text-decoration: underline; }

.hoplist { list-style: none; margin: 4px 0 0; padding: 0; }
.hop { position: relative; padding: 0 0 18px 30px; }
.hop:last-child { padding-bottom: 4px; }
.hopdot {
  position: absolute;
  left: 0;
  top: 4px;
  width: 15px;
  height: 15px;
  border: 2px solid var(--accent);
  border-radius: 50%;
  background: #fff;
}
/* The connector stops at the last hop, so the chain reads as terminated. */
.hop:not(.lasthop)::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 20px;
  bottom: 2px;
  width: 1px;
  /* --accent-soft is a fill tint (#e8f0fe) and vanishes as a 1px line. */
  background: #c7d7fe;
}
.hoprow { display: flex; align-items: center; gap: 6px; min-width: 0; }
.hopcaret {
  background: none;
  border: 0;
  padding: 0 2px;
  color: var(--muted);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  transition: transform .12s ease;
}
.hopcaret.open { transform: rotate(180deg); }
.hopcaret.ghost { width: 14px; cursor: default; }
.hopurl {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  color: var(--text-2);
}
.hopicon {
  flex: none;
  background: none;
  border: 0;
  padding: 2px;
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
}
.hopicon:hover { color: var(--accent); filter: none; }
.hopicon.copied { color: #067647; }
.hopdetail {
  margin: 7px 0 0;
  padding: 9px 11px;
  background: var(--chip);
  border: 1px solid var(--chip-line);
  border-radius: var(--r-sm);
  font-size: 12.5px;
  color: var(--text-2);
}
.hopdetail > div { padding: 2px 0; }
.hopdetail .hk { display: inline-block; min-width: 108px; color: var(--muted); }
.hopfull {
  margin-top: 5px;
  padding-top: 6px;
  border-top: 1px solid var(--chip-line);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px;
  word-break: break-all;
  color: var(--muted);
}

/* ---- Screenshots -------------------------------------------------------
   Two columns as in the reference (landing page and SERP), each with a dark
   title bar carrying the capture time. The provenance caveats stay under the
   image: an image without its scope is the easiest thing to misread. */
.shotcols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.shotcard {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  background: #fff;
}
.shotbar {
  background: #1d2939;
  color: #f9fafb;
  font-size: 11px;
  padding: 5px 9px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.shotcard img { display: block; width: 100%; height: 210px; object-fit: cover; object-position: top; }
.shotfoot { padding: 9px 11px; font-size: 12px; color: var(--text-2); display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.shotfoot b { flex-basis: 100%; }
.shotfoot small { flex-basis: 100%; color: var(--muted); line-height: 1.5; }

/* ---- Settings pages: repeatable group cards ---------------------------- */
.groupcardhead { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.groupcardhead .gname {
  flex: 1 1 auto;
  /* Without min-width:0 a flex input refuses to shrink below its intrinsic
     size, so on a phone the group name ran under the Remove group button. */
  min-width: 0;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 6px 9px;
  font-size: 15px;
  font-weight: 600;
  background: transparent;
}
.groupcardhead .gname:hover { border-color: var(--line); }
.groupcardhead .gname:focus { border-color: var(--accent); background: #fff; }
.chipfield { margin-bottom: 16px; }
/* A refused country. Amber, not red: nothing is broken and nothing was lost,
   the value simply was not something the collector can request. */
.chiphint {
  margin: 6px 0 0;
  font-size: 12.5px;
  color: #92400e;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: var(--r-sm);
  padding: 6px 9px;
}
.chipfieldhead { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.chipfieldhead h4 { margin: 0 0 2px; font-size: 13.5px; font-weight: 600; }
.chipfieldhead p { margin: 0 0 8px; font-size: 12.5px; color: var(--muted); }
.copybtn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: #fff;
  color: var(--text-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 5px 10px;
  font-size: 12.5px;
  cursor: pointer;
  flex: none;
}
.copybtn:hover { background: #f4f6f9; filter: none; }
.copybtn.copied { color: #067647; border-color: #abefc6; }
/* The box is the control: chips and the entry field share one bordered
   surface, so adding a value reads as extending the list. */
.chipbox {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 8px 10px;
  background: #fff;
  min-height: 42px;
}
.chipbox:focus-within { border-color: var(--accent); }
/* The 160px floor keeps the placeholder readable when the input shares a row
   with chips. On a phone that floor is wider than the space left beside a
   chip, so the placeholder was cut off mid-word; there it takes its own row
   instead. The floor is kept above that width, where it does no harm. */
.chipinput { flex: 1 1 160px; border: 0; padding: 3px 2px; font-size: 13px; min-width: 160px; background: transparent; }
@media (max-width: 900px) {
  .chipinput { flex-basis: 100%; min-width: 0; }
}
.chipinput:focus { outline: none; }
.chipx {
  background: none;
  border: 0;
  color: var(--muted);
  cursor: pointer;
  padding: 0 0 0 5px;
  font-size: 14px;
  line-height: 1;
}
.chipx:hover { color: #b42318; filter: none; }
.groupfoot {
  display: flex;
  justify-content: flex-end;
  gap: 26px;
  padding-top: 12px;
  margin-top: 4px;
  border-top: 1px solid var(--line-soft);
  font-size: 13px;
  color: var(--muted);
}
.groupfoot b { color: var(--text); margin-left: 4px; }
.cardbasis { margin: 8px 0 0; font-size: 12px; color: var(--muted); }
.addgroup {
  width: 100%;
  background: #fff;
  color: var(--accent);
  border: 1px dashed #b6c9f4;
  border-radius: var(--r-lg);
  padding: 14px;
  font-size: 13.5px;
  cursor: pointer;
}
.addgroup:hover { background: var(--accent-soft); filter: none; }
/* The reference shows a monthly price here. This workspace has no pricing
   model, so the same slot carries the honest equivalent: how many
   keyword-country combinations the settings ask for. */
.totalcard {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 16px 18px;
  font-size: 15px;
}
.setfoot { display: flex; align-items: center; justify-content: flex-end; gap: 14px; }

/* A two-word action must not wrap into the card title beside it. */
.nowrapbtn { white-space: nowrap; flex: none; }

.groupcardhead h3 { margin: 0; font-size: 15px; font-weight: 600; flex: 1 1 auto; }
.cardlead { margin: -6px 0 14px; font-size: 12.5px; color: var(--muted); }
.settingsgrid { margin-bottom: 14px; }
.settingsgrid input { border: 1px solid var(--line); border-radius: var(--r-md); padding: 7px 9px; font-size: 13px; width: 100%; }
.settingsgrid input:focus { border-color: var(--accent); outline: none; }
.checkline { display: flex; align-items: center; gap: 8px; font-size: 13px; margin: 12px 0 0; }
.checkline input { width: auto; }
/* Confirmation after a save: the write already happened, so this states the
   consequence (lists rebuilt) rather than just "OK". */
.savedflash {
  margin: 0 0 14px;
  padding: 10px 14px;
  border-radius: var(--r-md);
  background: #ecfdf3;
  border: 1px solid #abefc6;
  color: #067647;
  font-size: 13px;
}

/* ---- Legacy palette mapping -------------------------------------------
   style.css predates the theme and paints links, cards, focus rings and
   counters in a teal family (#087d79 / #0f5f5c / #149b89). Those rules are
   still live on the Evidence view, so the same product showed two accent
   colours depending on which view you opened. Rather than edit twenty
   scattered declarations in a minified stylesheet, the surviving classes
   are mapped onto the theme accent here. */
.linkbtn:hover { color: var(--accent); border-bottom-color: var(--accent); }
.evidencezoom { color: var(--accent); }
.evidencezoom:hover { border-color: var(--accent); }
.sortbtn:hover, .sortbtn.on { color: var(--accent); }
.casetable tbody tr:focus { outline-color: var(--accent); }
.modeswitch button[aria-pressed="true"] { background: var(--accent); color: #fff; }
button:focus-visible, a:focus-visible, summary:focus-visible { outline-color: var(--accent); }

/* The base pill is green, and `.pill.state-new` never had a rule of its own,
   so a NEW case rendered green in the legacy views while the case list paints
   the same state blue. One state must not have two colours. Neutral is the
   default; `new` matches the case-list status chip. */
.pill { background: var(--chip); color: var(--text-2); }
.pill.state-new { background: #eef4ff; border-color: #c7d7fe; color: #1d4ed8; }

/* The collection progress bar is the last teal fill. */
/* `.reason` and `.tinytag` are slate-blue chips from the old palette; they
   read as a third accent next to the theme's neutral chip. */
.reason, .tinytag { background: var(--chip); border-color: var(--chip-line); color: var(--text-2); }

/* ---- Loading and failure states ---------------------------------------
   A slow request used to show nothing, so the view looked empty - which is
   the same thing the UI shows for "no cases". The two must never look alike:
   one is a gap in knowledge, the other is a finding. */
.skeleton { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
.skelrow {
  height: 46px;
  border-radius: var(--r-md);
  background: linear-gradient(90deg, #f1f3f6 25%, #e8ebf0 37%, #f1f3f6 63%);
  background-size: 400% 100%;
  animation: skelshine 1.3s ease-in-out infinite;
}
@keyframes skelshine { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }
@media (prefers-reduced-motion: reduce) { .skelrow { animation: none; } }
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
.loaderror { border-color: #f3c6c6; background: #fffafa; }
.retrybtn { margin-top: 12px; align-self: flex-start; }

/* ---- Concept pages (Ad Radar / AI Radar) ------------------------------
   Built on the reference's template: title chip, dark hero, Explore grid.
   The reference's hero CTA is a sales action ("Contact Us For Access");
   there is nothing to sell here, so the hero states what is missing
   instead of offering access that does not exist. */
.titlerow { display: flex; align-items: center; gap: 10px; }
.titlechip {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  background: var(--chip);
  color: var(--text-2);
  border-radius: var(--r-sm);
  padding: 3px 9px;
  font-size: 12px;
  font-weight: 500;
}
.conceptwrap { display: flex; flex-direction: column; gap: 18px; }
.concepthero {
  background: linear-gradient(120deg, #0f1b2d 0%, #16283f 55%, #1d3b5c 100%);
  background-color: #0f1b2d;
  border-radius: var(--r-lg);
  padding: 34px 38px;
  color: #fff;
}
.conceptheroinner { max-width: 640px; }
.concepthero h2 { margin: 0 0 10px; font-size: 26px; font-weight: 600; color: #fff; }
.concepthero p { margin: 0; font-size: 14px; line-height: 1.65; color: #c9d4e3; }
.concepthero .heronote {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, .14);
  font-size: 12.5px;
  color: #9fb0c6;
}
.exploreh { margin: 0 0 18px; font-size: 17px; font-weight: 600; }
.exploregrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; }
.explorecard {
  display: flex;
  gap: 12px;
  padding: 14px 22px 14px 0;
  border-right: 1px solid var(--line-soft);
}
.explorecard:nth-child(3n) { border-right: 0; }
.explorecard:not(:nth-child(-n+3)) { border-top: 1px solid var(--line-soft); padding-top: 20px; }
.explorecard b { display: block; font-size: 13.5px; margin-bottom: 3px; }
.explorecard small { display: block; font-size: 12.5px; color: var(--muted); line-height: 1.55; }
.exploreicon {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.tint-rose { background: #fff1f3; color: #c11574; }
.tint-blue { background: #eff8ff; color: #175cd3; }
.tint-amber { background: #fffaeb; color: #b54708; }
.tint-green { background: #ecfdf3; color: #067647; }
.tint-cyan { background: #ecfeff; color: #0e7090; }
.tint-violet { background: #f4f3ff; color: #5925dc; }
@media (max-width: 900px) {
  .exploregrid { grid-template-columns: 1fr; }
  .explorecard { border-right: 0; padding-right: 0; }
  .explorecard:not(:first-child) { border-top: 1px solid var(--line-soft); padding-top: 18px; }
}

/* Transparency captures that attach to no observation row. Shown as cards in
   their own panel rather than as table rows: they are not part of any case
   list, and rendering them in the advertiser table would imply they are. */
.transcap small.warn { color: #8a5a12; }

/* ---- Platform screens (Operations, Users & Access) --------------------
   Only the classes these two views introduce. Everything else reuses the
   existing panel/metric/table/pill vocabulary so the platform surface does
   not drift into a second visual language. */
.small { font-size: 12.5px; line-height: 1.5; }
.grouprow { display: flex; justify-content: space-between; align-items: flex-start;
  gap: 16px; padding: 14px 0; border-bottom: 1px solid #e6edf0; }
.grouprow:last-of-type { border-bottom: 0; }
.groupactions { display: flex; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
.groupactions button { padding: 8px 13px; font-size: 13px; }
.groupactions button.secondary { background: white; color: #33505f; border: 1px solid #cbd6dc; }
.groupactions button.secondary:hover { border-color: var(--accent); color: var(--accent); filter: none; }
.groupactions button[disabled] { opacity: .45; cursor: not-allowed; filter: none; }
.formgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; }
.formgrid form { display: flex; flex-direction: column; gap: 10px; }
.formgrid h3 { margin: 0; font-size: 15px; }
.formgrid label { display: flex; flex-direction: column; gap: 5px; font-size: 13.5px; }
.formgrid select[multiple] { padding: 6px; }
/* The collector log is machine output: preserve its line structure, and keep
   a long line from widening the whole page. */
.logbox { background: #10222c; color: #d7e6ec; padding: 14px; border-radius: 8px;
  font-size: 12px; line-height: 1.55; max-height: 340px; overflow: auto;
  white-space: pre-wrap; word-break: break-word; }
@media (max-width: 760px) {
  .formgrid { grid-template-columns: 1fr; }
  .grouprow { flex-direction: column; }
}

/* ---- Roster ---------------------------------------------------------
   `.panel` is padding:0 by design so list panels can run tables edge to
   edge, and padding is restored only on `.panel > h2`. Every other direct
   child therefore sits against the border. The roster's panels are prose
   and forms, not full-bleed tables, so they opt back in here rather than
   by changing `.panel` and reflowing every table panel in the product. */
#roster .panel { padding: 20px; }
#roster .panel > h2 { padding: 0 0 4px; }
#roster .panelhead { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; margin-bottom: 8px; }
#roster .panelhead h2 { margin: 0; }
#roster .panel > p.muted { margin-top: 0; }
#roster table { width: 100%; }

/* The declare-complete control and the consequence of using it, on one
   row: the sentence explains the button beside it, not a setting on some
   other screen. */
.rosdeclare { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; border: 1px solid var(--line); border-radius: 10px;
  padding: 14px 16px; background: #f8fafb; margin-top: 14px; }
.rosdeclare p { margin: 4px 0 0; }
.rosdeclare button { white-space: nowrap; flex: 0 0 auto; }

/* File input and its action share a baseline; the input is allowed to
   shrink so the button never leaves the card on a narrow screen. */
.rosfilerow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.rosfilerow input[type=file] { flex: 1 1 260px; min-width: 0; }
.rosfilerow button { flex: 0 0 auto; }

.rosformat { border: 1px solid var(--line); border-radius: 10px; padding: 0 14px;
  margin-top: 14px; background: #fff; }
.rosformat summary { cursor: pointer; padding: 12px 0; font-size: 14px; font-weight: 600; }
.rosformat > div { padding-bottom: 14px; }
.rosformat code { background: #eef3f5; border-radius: 4px; padding: 1px 5px; font-size: 12px; }
.rosformat .button { display: inline-block; margin-top: 6px; }

@media (max-width: 760px) {
  #roster .panel { padding: 16px; }
  .rosdeclare { flex-direction: column; align-items: stretch; }
  .rosdeclare button { width: 100%; }
  .rosfilerow { flex-direction: column; align-items: stretch; }
  /* Column direction makes `flex: 1 1 260px` a 260px MINIMUM HEIGHT, which
     rendered the file input as a tall empty box. Reset both axes here. */
  .rosfilerow input[type=file] { flex: 0 0 auto; width: 100%; }
  .rosfilerow button { width: 100%; }
}

/* One row per observed platform account in a hijacking case. */
.caseaccounts { display: grid; gap: 12px; }
.caseaccount { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.caseaccount .accountid { overflow-wrap: anywhere; white-space: normal; }
.accountdisclosures { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---- Platform screens: Operations, Users & Access --------------------
   style.css sets `td { padding: 18px 16px }` and `th { padding: 16px }` for
   the evidence tables, where a row is a screenshot and a paragraph. An
   operations row is a region and a status, and at that padding six sources
   filled a 5250px-tall page. These rules are scoped to the two platform
   sections so the evidence tables keep their own spacing. */
#operations .panel, #users .panel { padding: 18px 20px; }
#operations h2, #users h2 { font-size: 16px; margin: 0; }
#operations .panelhead, #users .panelhead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 6px;
}
#operations .panel > .muted.small { margin: 0 0 12px; }
#opsitems [data-queue-counts] { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; }
#opsitems .opstools { display: flex; align-items: end; flex-wrap: wrap; gap: 8px 12px; margin-bottom: 10px; }
#opsitems .opstools label { display: grid; gap: 4px; width: min(100%, 180px); }
#opsitems .opstools input, #opsitems .opstools select { width: 100%; min-width: 0; }

/* ---- My account, Brands, Platform settings ---------------------------
   `.panel` is padding:0 so an evidence table can bleed to its edges, and
   each screen that is NOT such a table adds the gutter back (see #roster
   and the two platform screens above). These three never did, so their
   headings, copy and fields sat flush against the panel border. Same
   gutter as the platform screens, for one rhythm across the admin area. */
#account .panel, #brands .panel, #platform-settings .panel { padding: 18px 20px; }
#account .panel > h2, #brands .panel > h2, #platform-settings .panel > h2 { padding: 0; }
#account .panelhead, #brands .panelhead, #platform-settings .panelhead { margin-bottom: 10px; }
#brands .panelhead { display: flex; align-items: center; justify-content: flex-end; }
#platform-settings .panel > .muted { margin-top: 0; }
/* The notification and brand tables stay full-bleed inside that gutter, so a
   row separator still spans the panel the way the evidence tables do. */
#account .notiftable { width: calc(100% + 40px); margin-left: -20px; }
#account .notiftable th:first-child, #account .notiftable td:first-child { padding-left: 20px; }
#account .notiftable th:last-child, #account .notiftable td:last-child { padding-right: 20px; }

.opstable { width: 100%; border-collapse: collapse; font-size: 13px; }
.opstable th {
  background: #f8fafb; color: var(--muted); padding: 8px 10px;
  font-size: 11.5px; letter-spacing: .3px; text-transform: uppercase;
  white-space: nowrap; border-bottom: 1px solid var(--line);
}
.opstable td {
  padding: 9px 10px; border-top: 1px solid #eef2f5; vertical-align: top;
  max-width: 280px; overflow-wrap: anywhere; line-height: 1.45;
}
.opstable td .small, .opstable td .muted { font-size: 12px; }
.opstable tbody tr.rowwarn { background: #fffaf3; }
/* A three-column directory stretched across a 1300px panel put 600px of
   nothing between "IM8" and "im8". These tables size to their content and
   stop, with a cap so a long brand name still wraps rather than pushing the
   panel wide. */
.opstable.narrow { width: auto; min-width: min(100%, 560px); max-width: 100%; }
.opstable.narrow th, .opstable.narrow td { padding-right: 34px; }
.opstable.narrow th:last-child, .opstable.narrow td:last-child { padding-right: 10px; }
.opstable.narrow td:first-child { white-space: nowrap; }
/* `overflow-wrap: anywhere` is right for an advertiser URL and wrong for a
   role name: a squeezed column rendered "Platform operator" as "Platfo / rm /
   opera / tor". The directory columns hold prose, so they break on spaces. */
.opstable.narrow td { overflow-wrap: break-word; }
.opstable code { font-size: 12px; }

/* Summary cards. `.metrics`/`.metric` are the evidence dashboard's 30px
   figure tiles; a status word is not a figure and does not need that size. */
.opscards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px; margin-bottom: 14px;
}
.opscard {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 13px 15px;
  display: flex; flex-direction: column; gap: 3px;
}
.opscard small { font-size: 11.5px; letter-spacing: .3px; text-transform: uppercase; color: var(--muted); }
.opscard strong { font-size: 19px; font-weight: 620; line-height: 1.25; }
.opscard .small { font-size: 12px; line-height: 1.45; }
.tone-warn { color: #b45309; }
.tone-good { color: #15803d; }

.opsline { font-size: 12.5px; color: var(--text-2); margin: 0 0 10px; line-height: 1.6; }
.opsline code { font-size: 12px; }
.opsline .pill { margin-left: 4px; }

/* One alert style: a box that is only drawn when something is actually
   wrong, so its presence carries meaning. */
.opsalert {
  background: #fff7ed; border: 1px solid #fed7aa; border-radius: var(--r-md);
  padding: 10px 13px; margin: 0 0 12px; font-size: 13px; line-height: 1.55;
}
.opsalert b { display: block; color: #9a3412; }
.opsalert span { color: var(--text-2); }

/* Slim disclosure: one line by default, the full technical record on
   expand. Nothing true is removed, it just stops costing a screen. */
.opsnote { font-size: 12.5px; margin: 0 0 14px; }
.opsnote summary { cursor: pointer; color: var(--text-2); font-weight: 500; font-size: 12.5px; }
.opsnote > div { padding: 6px 0 0; color: var(--muted); line-height: 1.6; }
.opsnote p { margin: 0 0 6px; }
.opsrefresh { font-size: 12px; color: var(--muted); margin: 0 0 10px; }

details.opsdetails { padding: 14px 20px; }
details.opsdetails > summary { font-size: 14px; font-weight: 600; }
details.opsdetails[open] > summary { margin-bottom: 12px; }
details.opsdetails summary .muted { font-weight: 400; font-size: 12.5px; }

/* Diagnostics: newest-first tables cap at 20 rows with an explicit
   "Show all" toggle instead of one long page. */
.showall { margin: 8px 0 10px; }
.showall .linkbtn { margin-left: 4px; }

/* Action feedback sits next to the controls and is coloured by outcome:
   a refusal must not read like a confirmation. */
.opsmsg { font-size: 12.5px; margin: 10px 0 0; min-height: 1.2em; color: var(--muted); }
.opsmsg[data-tone="ok"] { color: #15803d; }
.opsmsg[data-tone="error"] { color: #b42318; }
#operations .grouprow { padding: 11px 0; }
#operations .grouprow .pill { margin-left: 6px; }
.groupactions button[aria-busy="true"] { opacity: .6; }

/* Users & Access */
.rolecards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 10px; margin-top: 14px;
}
.rolecard {
  border: 1px solid var(--line); border-radius: var(--r-md); padding: 10px 12px;
  display: flex; flex-direction: column; gap: 3px; font-size: 13px;
}
.rolecard .small { line-height: 1.5; }

.usform {
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 14px 16px; margin-bottom: 14px; background: #fbfdfe;
}
/* `align-items: start` is stated because the generic `.formrow` rule centres
   its items: with Password one helper line taller than its neighbours, the
   Username and Role labels were pushed 20px below it. */
.usform .formrow {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px;
  align-items: start;
}
/* Top-aligned: the three fields in a row are different heights (Password
   carries a helper line), and a stretched flex column would otherwise show
   the shorter labels lower than the taller one. */
.usform label { display: flex; flex-direction: column; justify-content: flex-start; gap: 4px; font-size: 13px; }
.usform input, .usform select { padding: 8px 10px; font-size: 13.5px; }
.usform .brandpick { border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 10px 12px; margin: 14px 0 0; }
.usform .brandpick legend { font-size: 12px; color: var(--muted); padding: 0 4px; }
/* `auto-fit` with one brand stretched the single cell across the fieldset and
   the checkbox sat centred in it, so `auto-fill` keeps the cell at column
   width and the boxes start at the left edge. */
.checkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 6px; }
/* `.usform label` above is a column; a checkbox and its caption are one row.
   Same specificity as `label.checkline` in style.css, but this sheet loads
   later, so the row direction has to be restated here. */
.usform .checkline, .checkgrid .checkline {
  flex-direction: row; align-items: center; justify-content: flex-start;
  gap: 8px; margin: 0; font-size: 13px;
}
.usform .checkline input { width: auto; margin: 0; }
/* A form's submit is a button, not a banner: full-width primary buttons made
   every creation form look like the page's main action. */
.usform .formactions { display: flex; align-items: center; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
.usform .formactions button { flex: 0 0 auto; }
/* The account maintenance panel opens as a row under the account it edits,
   so the operator's eye does not leave the row they were reading. */
.opstable tbody tr.rowoff td { color: var(--muted); }
.opstable tbody tr.rowoff td b { text-decoration: line-through; text-decoration-color: var(--muted); }
.opstable td.rowact { text-align: right; white-space: nowrap; }
.opstable tbody tr.editrow > td { padding: 0 0 12px; background: #fbfdfe; }
.usedit { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px;
  padding: 12px 12px 0; border-left: 3px solid var(--accent-soft); }
.usedit .usform { margin: 0; }
.usedit h4 { margin: 0 0 8px; font-size: 13.5px; font-weight: 600; }
.usedit .formactions { margin-top: 10px; }
.usedit button[disabled] { opacity: .45; cursor: not-allowed; }

@media (max-width: 760px) {
  #operations .panel, #users .panel { padding: 14px; }
  .opscards { grid-template-columns: 1fr 1fr; gap: 10px; }
  .opscard strong { font-size: 17px; }
  #operations .panelhead, #users .panelhead { align-items: flex-start; }
  .opstable { font-size: 12.5px; }
  .opstable td { padding: 8px; }
  /* On a phone the 560px floor and the 34px desktop gutters pushed the
     directory past the viewport and scrolled the whole document sideways.
     Content width wins over column rhythm at this size. */
  .opstable.narrow { min-width: 0; }
  .opstable.narrow th, .opstable.narrow td { padding-right: 10px; }
  .opstable.narrow td:first-child { white-space: normal; }
  /* A fourth column for Edit pushed the account table 83px past a 390px
     screen. The table is display:block here so the action cell can drop
     under the row instead of beside it. */
  #ustable, #ustable tbody, #ustable tr:not(.editrow), #ustable td { display: block; width: 100%; }
  #ustable thead { display: none; }
  #ustable tr:not(.editrow) { border-bottom: 1px solid var(--line); padding: 6px 0; }
  #ustable td { padding: 3px 8px; border: 0; }
  #ustable td.rowact { text-align: left; white-space: normal; }
  #ustable tr.editrow > td { display: block; }
  .usedit { grid-template-columns: 1fr; }
  /* The six-column source table inherits a 560px floor, which is ~93px per
     column and wraps "configuration" as "configurati / on". It already sits
     in a horizontal scroller, so let it be as wide as it needs and scroll. */
  .tablescroll .opstable { min-width: 700px; }
  .tablescroll .opstable.narrow { min-width: 0; }
}

/* ---- Role workspaces (2026-09) ---------------------------------------- */

/* The no-brand shell: a quiet panel, not an error. The account works; it
  just has nothing granted yet, and the page says exactly that. */
.noaccess {
  max-width: 620px;
  margin: 10vh auto 0;
  padding: 34px 32px 30px;
  text-align: center;
}
.noaccess h2 { margin: 0 0 10px; }
.noaccess .muted { max-width: 46ch; margin: 0 auto 6px; }
.noaccess code { font-size: 13px; }
.noaccess button { margin-top: 14px; }

/* Slim note used for the read-only banners in Settings and the case drawer:
  a notice's weight with one line's height. */
.notice.slimnotice {
  padding: 8px 12px;
  font-size: 13px;
}


/* ---- Dark mode ---------------------------------------------------------
   The interface vocabulary is variable-driven, so dark mode redefines the
   palette once on the root; components keep their semantics. A few legacy
   literals in style.css are patched below where variables never reached. */
html[data-theme="dark"] {
  --bg: #0f1720;
  --panel: #16222c;
  --line: #2a3a47;
  --line-soft: #22303c;
  --text: #dbe7ee;
  --text-2: #b9cad4;
  --muted: #8aa0ae;
  --accent: #4d8dff;
  --accent-soft: #1d3a5f;
  --chip: #1d2b36;
  --chip-line: #31424f;
  --panel-alt: #131f28;
  --row-hover: #182631;
}
html[data-theme="dark"] body { background: var(--bg); color: var(--text); }
html[data-theme="dark"] .panel,
html[data-theme="dark"] .card,
html[data-theme="dark"] .editcard,
html[data-theme="dark"] #detail,
html[data-theme="dark"] .dialogbar,
html[data-theme="dark"] .emptystate,
html[data-theme="dark"] .savedflash,
html[data-theme="dark"] input,
html[data-theme="dark"] select,
html[data-theme="dark"] .tablewrap table { background: var(--panel); color: var(--text); }
html[data-theme="dark"] input,
html[data-theme="dark"] select { border-color: var(--line); }
html[data-theme="dark"] th, html[data-theme="dark"] td { border-color: var(--line); }
html[data-theme="dark"] .notice { background: #1d2b36; border-color: #31424f; color: var(--text-2); }
html[data-theme="dark"] .accountmenu { background: var(--panel); border-color: var(--line); box-shadow: 0 18px 48px #00000066; }

/* ---- Empty states: centred, muted, no shouting ---------------------- */
.emptystate, .empty {
  padding: 32px 20px;
  text-align: center;
  background: var(--panel-alt);
  border: 1px dashed var(--line);
  border-radius: var(--r-lg);
  color: var(--muted);
  font-size: 13.5px;
}
.emptystate small { color: var(--muted); }
/* The friendly anatomy: a large quiet glyph, a real sentence title, one
   line of guidance, then the action. The icon is decorative -- the words
   carry the meaning. `.esicon` is also used on its own inside plain
   panels (noaccess), so the base rule is not scoped to `.emptystate`. */
.esicon { display: block; width: 34px; height: 34px; margin: 0 auto 10px; color: var(--muted); opacity: .7; }
.emptystate .esicon { margin-bottom: 10px; }
.emptystate b { display: block; font-size: 14.5px; font-weight: 650; color: var(--text-2); margin: 0 0 5px; }
.emptystate small + small { display: block; margin-top: 4px; }
.emptystate .esactions { margin-top: 14px; display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; }

/* ---- Sign-in: dark aurora backdrop, light card --------------------- */
#auth {
  position: relative;
  isolation: isolate;
  min-height: 100vh;
  width: 100%;
  margin: 0;
  max-width: none;
  padding: 48px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--ink);
  overflow: hidden;
}
#auth::before {
  content: "";
  position: absolute;
  inset: -20%;
  z-index: -1;
  background:
    radial-gradient(38% 45% at 18% 18%, rgba(29, 84, 209, .55), transparent 60%),
    radial-gradient(42% 50% at 85% 78%, rgba(94, 234, 212, .28), transparent 60%),
    radial-gradient(60% 60% at 50% 0%, rgba(59, 130, 246, .28), transparent 70%);
  filter: blur(10px);
}
#auth::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: .35;
  background-image:
    linear-gradient(rgba(255, 255, 255, .05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .05) 1px, transparent 1px);
  background-size: 34px 34px;
  -webkit-mask-image: radial-gradient(60% 60% at 50% 30%, #000 10%, transparent 100%);
  mask-image: radial-gradient(60% 60% at 50% 30%, #000 10%, transparent 100%);
}
#auth > .themetoggle {
  width: auto;
  max-width: max-content;
}
/* Two columns: the sign-in card, and a brand panel showing what the person
   is about to sign into -- a real product screenshot (shot-cases.webp, an
   actual case list render, not stock photography) plus three lines of why
   it exists. Collapses to the form alone under 900px: on a phone there is
   no room to do both without the form losing the fold. */
.authsplit {
  position: relative;
  display: flex;
  align-items: stretch;
  gap: 44px;
  width: 100%;
  max-width: 960px;
}
.authpanel {
  flex: 0 1 380px;
  min-width: 300px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.authmark { display: block; height: 34px; width: auto; }
#auth p#authdesc {
  margin: 10px 0 0;
  font-size: 13.5px;
  color: #b9c6dd;
}
#auth form {
  width: 100%;
  margin: 24px 0 0;
  background: var(--panel);
  border: 0;
  border-radius: var(--r-xl);
  padding: 28px 26px 26px;
  box-shadow: var(--shadow-lg);
}
#auth form label {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  margin: 0 0 16px;
}
#auth form input {
  display: block;
  width: 100%;
  margin-top: 7px;
  padding: 10px 12px;
  font-size: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--panel);
  color: var(--text);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
#auth form input:focus {
  outline: none;
  border-color: var(--accent-2);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
#auth form button {
  width: 100%;
  margin-top: 6px;
  padding: 11px 14px;
  font-size: 14px;
}
/* "Continue with Google", rendered above the password form -- hidden by
   default (see static/index.html) and only unhidden by app.js when
   window.BW_GOOGLE_ENABLED is true and the plain sign-in view is showing.
   White background and the official multi-colour "G" mark, per Google's
   branding guidelines; the dark #auth backdrop already gives it contrast
   without a separate dark-mode variant. */
#auth .googlewrap { width: min(360px, 100%); margin: 28px 0 0; }
#auth .googlebtn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 10px 14px;
  background: #fff;
  color: #3c4043;
  border: 1px solid #dadce0;
  border-radius: var(--r-md);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  box-shadow: var(--shadow-lg);
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
#auth .googlebtn:hover { background: #f8f9fa; text-decoration: none; }
#auth .googleicon { width: 18px; height: 18px; flex: none; }
#auth .authdivider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 20px 0 0;
  color: #8695b5;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
}
#auth .authdivider::before, #auth .authdivider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, .16);
}
/* Login's "Forgot password?" and the other two forms' "Back to sign in":
   one small link, right where the credentials were, so leaving one auth
   form for another reads as a continuation, not a new page. */
#auth .authlinks {
  margin: 14px 0 0;
  text-align: center;
  font-size: 12.5px;
}
#auth .authlinks a {
  color: var(--accent-2);
  text-decoration: none;
}
#auth .authlinks a:hover { text-decoration: underline; }
#auth .authmsg {
  margin: 12px 0 0;
  font-size: 13px;
  color: var(--danger);
  overflow-wrap: break-word;
}
/* Forgot-password's own success line is not an error: nothing failed, so
   red is the wrong signal for "check your inbox". */
#auth .authmsg.info { color: var(--muted); }
#auth .authmsg:empty { display: none; margin: 0; }
/* The brand panel: the same quiet glass tile `.rail-workspace` already
   uses for light-on-dark chrome in this file (rgba(255,255,255,.05) fill,
   .1 border), holding the pitch and the screenshot. `aria-hidden` is
   deliberately NOT set on the whole panel: the tagline and points are
   real content, not decoration. Only the screenshot's alt is empty, since
   the tagline beside it already says what it shows. */
.authbrand {
  flex: 1 1 460px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 22px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: var(--r-xl);
  padding: 32px;
}
.authtag {
  margin: 0;
  font-size: 19px;
  font-weight: 650;
  line-height: 1.5;
  color: #fff;
  letter-spacing: -.2px;
}
.authpoints { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.authpoints li { display: flex; align-items: center; gap: 10px; font-size: 13px; color: #c9d5ea; }
.authpoints svg { width: 17px; height: 17px; flex: none; color: var(--teal); }
.authshot {
  border-radius: var(--r-lg);
  overflow: hidden;
  background: #fff;
  box-shadow: 0 24px 50px -20px rgba(0, 0, 0, .55);
}
.authshotbar { display: flex; align-items: center; gap: 6px; padding: 8px 12px; background: #f1f4f9; border-bottom: 1px solid var(--line); }
.authshotbar i { width: 8px; height: 8px; border-radius: 50%; background: #d5dbe5; display: block; }
.authshotbar span { margin-left: 6px; font-size: 10.5px; color: var(--muted); }
.authshot img { display: block; width: 100%; height: auto; }
@media (max-width: 900px) {
  .authbrand { display: none; }
  .authsplit { max-width: 400px; }
  .authpanel { flex-basis: auto; }
}
@media (max-width: 480px) {
  #auth { padding: 32px 16px; }
  #auth form { padding: 24px 20px 22px; }
}

/* ---- Motion: respected everywhere, not just here -------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}


/* ---- Carried from Brandwatch: screens BrandPatrol's theme never styled ----
   BrandPatrol forked before these shipped, so its stylesheet has no rule for
   them and they would render unstyled. The settings menu card and the
   sign-in theme switch are still rendered by this codebase; the rules are
   reproduced verbatim rather than restyled, so the only visual change on
   this branch is the one BrandPatrol actually made. */
.setwrap { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 22px; align-items: start; }
.setwrap #setbody { min-width: 0; }
.settingsactions { display: flex; justify-content: flex-end; }
.groupcardhead > button { flex: none; }
.assetfield { display: flex; flex-direction: column; gap: 7px; margin: 0 0 18px; font-weight: 600; }
.assetfield small { color: var(--muted); font-weight: 400; }
.assetfield textarea, .assetadvertiser input, .assetadvertiser textarea {
  box-sizing: border-box; width: 100%; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: var(--r-md); background: #fff; color: var(--text); font: inherit;
}
.assetfield textarea:focus, .assetadvertiser input:focus, .assetadvertiser textarea:focus { border-color: var(--accent); outline: none; }
.assetadvertisers { display: flex; flex-direction: column; gap: 12px; margin-bottom: 14px; }
.assetadvertiser { display: grid; grid-template-columns: minmax(150px, 1fr) minmax(250px, 2fr) auto; gap: 10px; align-items: end; }
.assetadvertiser label { display: flex; flex-direction: column; gap: 6px; min-width: 0; font-weight: 600; }
.assetadvertiser button { align-self: end; }
.assetimpact, .assetreview, .asseterror { padding: 10px 12px; border-radius: var(--r-md); margin: 14px 0; }
.assetimpact { background: #f3f6fb; color: var(--text-2); }
.assetreview { background: #fffbeb; border: 1px solid #fde68a; }
.asseterror { background: #fef2f2; color: #991b1b; }
.assetactions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
@media (max-width: 760px) {
  .setwrap { grid-template-columns: minmax(0, 1fr); }
  .setwrap .groupcardhead { flex-wrap: wrap; }
  .setwrap .groupcardhead h3 { min-width: 0; overflow-wrap: anywhere; }
  .setwrap .groupfoot { flex-wrap: wrap; gap: 8px 18px; }
  .setwrap .mschip { max-width: 100%; overflow-wrap: anywhere; }
  .assetadvertiser { grid-template-columns: minmax(0, 1fr); }
}
.setmenu {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  padding-bottom: 8px;
}
.setmenuhead {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line);
  font-size: 15px;
}
.setmenugroup {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 13px 18px 9px;
  color: var(--accent);
  font-size: 13.5px;
}
.setmenu button {
  display: block;
  width: 100%;
  text-align: left;
  background: #fff;
  color: var(--text-2);
  border: 0;
  border-left: 3px solid transparent;
  border-radius: 0;
  padding: 10px 18px 10px 42px;
  font-size: 13.5px;
  cursor: pointer;
}
.setmenu button:hover { background: #f4f6f9; filter: none; }
.setmenu button[aria-pressed="true"] {
  background: var(--accent-soft);
  border-left-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}
.ghostbtn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: #fff;
  color: var(--text-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 9px 15px;
  font-size: 13.5px;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}
.ghostbtn:hover { border-color: #cfd4dc; filter: none; }
.themetoggle {
  display: block;
  margin: 10px auto 0;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  padding: 6px 12px;
}
.themetoggle:hover { filter: none; border-color: var(--accent); color: var(--accent); }
.themetoggle[aria-checked="true"] { border-color: var(--accent); color: var(--accent); }

/* ---- Dark mode ---------------------------------------------------------
   The interface vocabulary is variable-driven, so dark mode redefines the
   palette once on the root; components keep their semantics. A few legacy
   literals in style.css are patched below where variables never reached. */
html[data-theme="dark"] {
  --bg: #0f1720;
  --panel: #16222c;
  --line: #2a3a47;
  --line-soft: #22303c;
  --text: #dbe7ee;
  --text-2: #b9cad4;
  --muted: #8aa0ae;
  --accent: #f4711c;
  --accent-soft: #3a2118;
  --chip: #1d2b36;
  --chip-line: #31424f;
}
html[data-theme="dark"] body { background: var(--bg); color: var(--text); }
html[data-theme="dark"] .panel,
html[data-theme="dark"] .card,
html[data-theme="dark"] .editcard,
html[data-theme="dark"] #detail,
html[data-theme="dark"] .dialogbar,
html[data-theme="dark"] .emptystate,
html[data-theme="dark"] .savedflash,
html[data-theme="dark"] input,
html[data-theme="dark"] select,
html[data-theme="dark"] .tablewrap table { background: var(--panel); color: var(--text); }
html[data-theme="dark"] input,
html[data-theme="dark"] select { border-color: var(--line); }
html[data-theme="dark"] th, html[data-theme="dark"] td { border-color: var(--line); }
html[data-theme="dark"] .notice { background: #1d2b36; border-color: #31424f; color: var(--text-2); }
html[data-theme="dark"] .accountmenu { background: var(--panel); border-color: var(--line); box-shadow: 0 18px 48px #00000066; }

/* The eight rules above covered the panel shells only. Every surface below
   states a light literal -- a white card, a pale grey header, a tinted
   status fill -- which the token redefinition cannot reach. In dark mode
   those kept their light paint while the text they carry switched to the
   light-on-dark palette, so the text and its background converged: the
   flagged rows of the verification table (`.rowwarn`, a cream fill) were
   the loudest case, but the same defect is structural and every instance
   of it is answered here rather than one at a time.

   Tinted states stay tinted. A warning row must still read as a warning,
   so each keeps its hue and trades the pale fill for a dark one of the
   same family, with the foreground lifted to meet it. */

/* Surfaces painted plain white. */
html[data-theme="dark"] aside,
html[data-theme="dark"] .groupcard,
html[data-theme="dark"] .shotcol,
html[data-theme="dark"] .setmenu,
html[data-theme="dark"] .rosformat,
html[data-theme="dark"] .grouprowcard,
html[data-theme="dark"] .modeswitch,
html[data-theme="dark"] #shotviewer,
html[data-theme="dark"] #shotviewer .viewerbar,
html[data-theme="dark"] #shotviewer .galleryarrow,
html[data-theme="dark"] .chipbox,
html[data-theme="dark"] .copybtn,
html[data-theme="dark"] .iconbtn,
html[data-theme="dark"] .addgroup,
html[data-theme="dark"] .addchip,
html[data-theme="dark"] .filterbar .morechip,
html[data-theme="dark"] .casetable thead th,
html[data-theme="dark"] .caselayer,
html[data-theme="dark"] .setmenu button,
html[data-theme="dark"] .notes,
html[data-theme="dark"] .usform,
html[data-theme="dark"] .rosdeclare,
html[data-theme="dark"] .linkbox,
html[data-theme="dark"] .colhead #colrefresh,
html[data-theme="dark"] .coldetail,
html[data-theme="dark"] .opstable tbody tr.editrow > td { background: var(--panel); color: var(--text-2); }
html[data-theme="dark"] .colhead #colrefresh,
html[data-theme="dark"] .shotcol,
html[data-theme="dark"] .setmenu,
html[data-theme="dark"] .rosformat { border-color: var(--line); }

/* Hover and zebra tints: light greys that turned into flashes of white. */
html[data-theme="dark"] aside nav :is(button, a[data-view]):hover,
html[data-theme="dark"] aside .rail-switch:hover,
html[data-theme="dark"] .setmenu button:hover,
html[data-theme="dark"] .copybtn:hover,
html[data-theme="dark"] .iconbtn:hover,
html[data-theme="dark"] .pagenums button:hover:not(:disabled),
html[data-theme="dark"] .casetable tbody tr:hover { background: #1d2b36; color: var(--text); }
html[data-theme="dark"] .addgroup:hover { background: #1d2b36; }
html[data-theme="dark"] .shotcol .shotbox,
html[data-theme="dark"] #shotviewer .viewerimage,
html[data-theme="dark"] .rosformat code { background: #14202a; }

/* Table chrome. The verification and directory tables are the screens an
   operator reads for minutes at a time, so their header and separators are
   stated rather than left to the generic th/td border patch. */
html[data-theme="dark"] .opstable th { background: #14202a; color: var(--muted); }
html[data-theme="dark"] .opstable td { border-top-color: var(--line-soft); }
html[data-theme="dark"] .casetable .dimrow { background: #14202a; color: var(--muted); }

/* Flagged rows. Amber is kept; only its depth changes. */
html[data-theme="dark"] .opstable tbody tr.rowwarn { background: #2a2016; }
html[data-theme="dark"] .opstable tbody tr.rowwarn td { color: var(--text); }

/* Alert and warning boxes. */
html[data-theme="dark"] .opsalert,
html[data-theme="dark"] .warnbox,
html[data-theme="dark"] .refusedchip,
html[data-theme="dark"] .requestedonly { background: #2a2016; border-color: #5a4426; color: var(--text); }
html[data-theme="dark"] .opsalert b { color: #fdba74; }
html[data-theme="dark"] .loaderror { background: #2a1a1a; border-color: #5f3232; }
html[data-theme="dark"] .savedflash { background: #12291d; border-color: #2c5a3e; color: #86efac; }

/* Status fills: chips, pills and badges. Each keeps its family hue. */
html[data-theme="dark"] .statuschip.s-investigating,
html[data-theme="dark"] .s-in_progress,
html[data-theme="dark"] .chip.warn,
html[data-theme="dark"] .pill.auth-restricted,
html[data-theme="dark"] .badge-candidate,
html[data-theme="dark"] .tint-amber,
html[data-theme="dark"] #colroot .tone-warn > .colglyph,
html[data-theme="dark"] #colroot .colcellbtn.tone-warn .colglyph,
html[data-theme="dark"] #colroot .pill.warn { background: #33270f; border-color: #5a4426; color: #fcd34d; }
html[data-theme="dark"] .statuschip.s-resolved,
html[data-theme="dark"] .s-resolved,
html[data-theme="dark"] .chip.ok,
html[data-theme="dark"] .pill.auth-authorized,
html[data-theme="dark"] .badge-confirmed,
html[data-theme="dark"] .tint-green,
html[data-theme="dark"] #colroot .tone-good > .colglyph,
html[data-theme="dark"] #colroot .colcellbtn.tone-good .colglyph,
html[data-theme="dark"] #colroot .pill.good { background: #12291d; border-color: #2c5a3e; color: #86efac; }
/* The intent grid's not-in-rotation placeholder and the gaps list keep the
   dark palette's line and hover tokens; the warn/good fills above do not
   apply to a cell nothing was meant to run in. */
html[data-theme="dark"] #colroot .colnotsel .colglyph,
html[data-theme="dark"] #colroot .colcellbtn.colnotsel .colglyph { background: transparent; border-color: #31424f; color: #8aa0ae; }
html[data-theme="dark"] #colroot .colgaps li { border-top-color: #22303c; }
html[data-theme="dark"] #colroot .colgapbtn:hover,
html[data-theme="dark"] #colroot .colgapbtn:focus-visible { background: #182631; }
html[data-theme="dark"] .statuschip.s-ready_for_action,
html[data-theme="dark"] .pill.state-new,
html[data-theme="dark"] .pill.auth-not_authorized { background: #2f1a1c; border-color: #5f3232; color: #fca5a5; }
html[data-theme="dark"] .pill.state-ready_for_action,
html[data-theme="dark"] .s-new,
html[data-theme="dark"] .tint-blue { background: #16273a; border-color: #2c4a66; color: #93c5fd; }
html[data-theme="dark"] .statuschip.s-monitoring,
html[data-theme="dark"] .pill.state-resolved,
html[data-theme="dark"] .empty { background: var(--chip); color: var(--text-2); }
html[data-theme="dark"] .badge-demo,
html[data-theme="dark"] .tint-violet { background: #241a33; color: #c4b5fd; }
html[data-theme="dark"] .tint-rose { background: #331723; color: #f9a8d4; }
html[data-theme="dark"] .tint-cyan { background: #10272e; color: #67e8f9; }
html[data-theme="dark"] .prov { background: #33270f; border-color: #5a4426; color: #fcd34d; }

/* Coloured prose that was chosen against a white page. */
html[data-theme="dark"] .warn-text,
html[data-theme="dark"] .opsmsg[data-tone="error"] { color: #fca5a5; }
html[data-theme="dark"] .ok-text,
html[data-theme="dark"] .opsmsg[data-tone="ok"] { color: #86efac; }
html[data-theme="dark"] .transcap small.warn { color: #fcd34d; }
html[data-theme="dark"] .facts.unknown { color: #d6c79a; }

/* Separators stated as literals rather than through --line. */
html[data-theme="dark"] .grouprow,
html[data-theme="dark"] .colcause,
html[data-theme="dark"] .colaction,
html[data-theme="dark"] .grid th,
html[data-theme="dark"] .grid td { border-color: var(--line-soft); }

/* ---- Second pass: what the first one still missed --------------------- */

/* The page below the content. `style.css` paints its light background on
   `:root` -- the <html> element -- and the first pass recoloured only
   `body`. `body` is as tall as its content, so every short screen showed
   the light page continuing past it as a white band across the bottom.
   The element that owns the background has to be the element that is
   repainted. */
html[data-theme="dark"],
html[data-theme="dark"] body { background: var(--bg); color: var(--text); }

/* The subview tabs. The first pass recoloured `.modeswitch` (the bordered
   container) but not the buttons inside it, which state `background:#fff`
   of their own and so stayed a white bar. The selected tab keeps the
   accent fill it already had. */
html[data-theme="dark"] .modeswitch { border-color: var(--line); }
html[data-theme="dark"] .modeswitch button { background: var(--panel); color: var(--text-2); }
html[data-theme="dark"] .modeswitch button:hover { background: #1d2b36; color: var(--text); }
html[data-theme="dark"] .modeswitch button[aria-pressed="true"] { background: var(--accent); color: #fff; }

/* Table headers, stated once for any table rather than per class: the
   notification table's header row was white because only `.opstable` and
   `.casetable` had been named. A sticky header keeps its own fill, so it
   must be opaque or rows scroll visibly underneath it. */
html[data-theme="dark"] thead th,
html[data-theme="dark"] .notiftable th,
html[data-theme="dark"] .grid th,
html[data-theme="dark"] .casetable th { background: #14202a; color: var(--muted); }
html[data-theme="dark"] .notiftable th,
html[data-theme="dark"] .notiftable td { border-bottom-color: var(--line-soft); }

/* Form controls. Checkboxes and radios are painted by the browser, not by
   the `input` background rule the first pass patched, so they stayed white
   squares. `accent-color` is what recolours the control itself. */
html[data-theme="dark"] input[type="checkbox"],
html[data-theme="dark"] input[type="radio"] { accent-color: var(--accent); background: transparent; }
html[data-theme="dark"] input::placeholder { color: var(--muted); }
/* The select arrow is a data-URI SVG with a light stroke baked into the
   background shorthand, which also re-set the white fill. */
html[data-theme="dark"] select {
  background: var(--panel) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238aa0ae' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>") no-repeat right 8px center;
  background-size: 14px;
}

/* Cards and the sign-in panel: `.card` states its own white, and the
   pre-authentication screen is reached before any workspace class. */
html[data-theme="dark"] .card { background: var(--panel); border-color: var(--line); color: var(--text); }
html[data-theme="dark"] .card:hover { border-color: var(--accent); }
html[data-theme="dark"] .card small,
html[data-theme="dark"] small,
html[data-theme="dark"] .muted { color: var(--muted); }
html[data-theme="dark"] .auth { background: var(--bg); color: var(--text); }
html[data-theme="dark"] .dialogbar { border-bottom-color: var(--line); }
html[data-theme="dark"] .editcard { border-color: var(--line); }
html[data-theme="dark"] .empty { background: var(--chip); color: var(--text-2); }

/* ---- Sign-in screen ---------------------------------------------------- */

/* Chrome paints autofilled fields with its own background and ignores the
   `background` property entirely, which is why the username and password
   boxes stayed pale lavender on the dark sign-in card while every other
   input followed the theme. The documented way to override it is an inset
   shadow the size of the field, with the text colour forced alongside. */
html[data-theme="dark"] input:-webkit-autofill,
html[data-theme="dark"] input:-webkit-autofill:hover,
html[data-theme="dark"] input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--panel) inset;
  box-shadow: 0 0 0 1000px var(--panel) inset;
  -webkit-text-fill-color: var(--text);
  caret-color: var(--text);
  border-color: var(--line);
}

/* The sign-in card's own theme switch, sitting with the language picker.
   A quiet control: the screen's job is to sign someone in, and this is a
   preference, so it must not compete with the sign-in button. */
.themetoggle {
  display: block;
  margin: 10px auto 0;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  padding: 6px 12px;
}
.themetoggle:hover { filter: none; border-color: var(--accent); color: var(--accent); }
.themetoggle[aria-checked="true"] { border-color: var(--accent); color: var(--accent); }

/* ---- Third pass -------------------------------------------------------- */

/* The drawers are <dialog> elements. A dialog's background comes from the
   user agent, not from any rule in this sheet, so the case and ad layers
   opened as a white sheet no matter what the palette said. The header band
   was the visible part because the panels inside painted over the rest. */
html[data-theme="dark"] dialog,
html[data-theme="dark"] #casedetail,
html[data-theme="dark"] #addetail,
html[data-theme="dark"] #detail { background: var(--panel); color: var(--text); border-color: var(--line); }
html[data-theme="dark"] .layerhead { border-bottom-color: var(--line); }
html[data-theme="dark"] .layerhead h2 { color: var(--text); }
html[data-theme="dark"] .dialogbar { background: var(--panel); color: var(--text-2); }
html[data-theme="dark"] .detailfacts { background: #14202a; }
html[data-theme="dark"] .detailfacts dt { color: var(--muted); }

/* The workspace banner. `.notice` was already repainted, but the same
   declaration is shared with `#hijdemo, #biddemo, #setdemo` -- an id
   selector, which outranks a class-based override. On exactly those three
   views the white won. Matching on the ids is what actually settles it. */
html[data-theme="dark"] #hijdemo,
html[data-theme="dark"] #biddemo,
html[data-theme="dark"] #setdemo,
html[data-theme="dark"] .notice.slimnotice { background: #1d2b36; border-color: #31424f; color: var(--text-2); }
html[data-theme="dark"] .slimnotice summary { color: var(--text-2); }
html[data-theme="dark"] .slimnotice p { color: var(--muted); }

/* Loading rows. The shimmer is a gradient of two light greys, so a view
   waiting for data flashed near-white bars across the dark page -- the
   loudest thing on screen, for the state that means "nothing yet". */
html[data-theme="dark"] .skelrow {
  background: linear-gradient(90deg, #1a2731 25%, #22303c 37%, #1a2731 63%);
  background-size: 400% 100%;
}

/* Remaining white cards. */
html[data-theme="dark"] .totalcard,
html[data-theme="dark"] .unknownbox,
html[data-theme="dark"] .popcard { background: var(--panel); border-color: var(--line); color: var(--text); }
html[data-theme="dark"] .demoblock { background: #241a33; border-color: #5b3a80; }
html[data-theme="dark"] .demohead { border-bottom-color: #5b3a80; }
html[data-theme="dark"] .demohead b { color: #c4b5fd; }
html[data-theme="dark"] .demohead small { color: #a78bfa; }
html[data-theme="dark"] .caveat { border-top-color: var(--line); }
html[data-theme="dark"] .caveat summary { color: var(--text-2); }
html[data-theme="dark"] .next { background: #16273a; border-color: #2c4a66; color: var(--text); }
html[data-theme="dark"] .warnbox { border-left-color: #d99100; }

/* ops-items-table
   Why the queue table mangled identifiers: `.opstable td` sets
   `overflow-wrap: anywhere`, which drops every column's min-content width to
   a single character. Once the thirteen nowrap uppercase headers no longer
   fit the panel (the admin rail takes ~240px), the auto table algorithm
   shrinks every column toward that 1-character minimum and "base",
   "grp_snapshot", "google" and "2 小时前" break mid-token. Stopping the
   short columns from offering break points makes the algorithm keep them at
   their content width and take the deficit out of the two prose columns;
   when even those are spent the table overflows and scrolls horizontally
   inside `.tablescroll` instead of mangling codes. */
#opsitems .opstable td { overflow-wrap: break-word; }
/* Column map: 1 due, 2 status, 3 queue, 4 plan, 5 exit, 6 engine, 7 country,
   8 device, 9 keyword, 10 started, 11 finished, 12 result, 13 skip reason. */
#opsitems .opstable td:nth-child(1),
#opsitems .opstable td:nth-child(2),
#opsitems .opstable td:nth-child(3),
#opsitems .opstable td:nth-child(4),
#opsitems .opstable td:nth-child(5),
#opsitems .opstable td:nth-child(6),
#opsitems .opstable td:nth-child(7),
#opsitems .opstable td:nth-child(8),
#opsitems .opstable td:nth-child(10),
#opsitems .opstable td:nth-child(11),
#opsitems .opstable td:nth-child(12) { white-space: nowrap; }
/* Keyword and skip reason are prose: they absorb the squeeze and wrap, with
   `anywhere` kept so a pathological unbroken token still cannot push the
   table wide; the width hint keeps them from dictating the table's preferred
   width on a wide screen. */
#opsitems .opstable th:nth-child(9), #opsitems .opstable td:nth-child(9),
#opsitems .opstable th:nth-child(13), #opsitems .opstable td:nth-child(13) {
  overflow-wrap: anywhere; width: 220px;
}

/* The filter row inherited the global `input, select { padding: 12px }`, so
   the three stacked label-over-control grids rendered as tall hollow boxes.
   Match the other operations filters (`.usform` / `.colwindow`): single-line
   controls on one line with their label, wrapping on narrow screens. */
#opsitems .opstools { align-items: center; }
#opsitems .opstools label { display: flex; align-items: center; gap: 6px; width: auto; white-space: nowrap; }
#opsitems .opstools input, #opsitems .opstools select { width: auto; padding: 8px 10px; font-size: 13px; }
#opsitems .opstools input[data-items-filter] { width: 150px; }
#opsitems .queueitemtable { min-width: 760px; }
#opsitems .queueitemtable td:nth-child(4), #opsitems .queueitemtable td:nth-child(6) { white-space: normal; overflow-wrap: anywhere; min-width: 130px; }
.queueitemdetails summary { cursor: pointer; color: var(--accent); }
.queueitemdetails dl { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; margin: 8px 0 0; font-size: 12px; }
.queueitemdetails dt { color: var(--muted); }
.queueitemdetails dd { margin: 0; }
.queueadvanced { margin-top: 20px; border-top: 1px solid var(--line); padding-top: 14px; }
.queueadvanced > summary { cursor: pointer; font-weight: 600; }
.queueadvanced > .opscards { margin-top: 14px; }
.queuekpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.queueexecutor { margin: 0 0 16px; }
@media (max-width: 680px) { .queuekpis { grid-template-columns: 1fr; } }
/* end ops-items-table */

/* rail-subnav: Operations sections as rail items, grouped. */
aside nav .navgroup { color: rgba(255, 255, 255, .45); margin: 14px 0 4px; padding: 0 14px; }
aside nav .navgroup:first-child { margin-top: 4px; }
aside nav :is(button, a[data-view]) .navbadge {
  margin-left: auto; min-width: 20px; padding: 1px 6px; border-radius: 999px;
  background: #f59e0b; color: #1f1300; font-size: 11px; font-weight: 700; line-height: 16px; text-align: center;
}
aside nav :is(button, a[data-view]) .navbadge[hidden] { display: none; }
@media (max-width: 820px) { aside nav :is(button, a[data-view]) .navbadge { margin-left: 4px; } }
/* end rail-subnav */

/* ---- Job detail (`/operations/jobs/<id>`) -------------------------------
   Added for the jobs list/detail split (docs/handoff-ops-console/README.md):
   the description grid that replaces a facts paragraph, the header that
   carries the plan name plus its run/pause/edit actions, and the run
   history table's numeric/failed-sources columns. `.descgrid` deliberately
   is not CSS Grid: an un-wrapped `<dl>` alternates `dt`,`dd`,`dt`,`dd`, and
   a grid's row-major auto-placement would scatter each label away from its
   own value once the column count stops dividing the pair count evenly
   (3 columns, 2 items per fact). CSS multi-column layout instead flows the
   list top-to-bottom per column, so a label always precedes its value; the
   only failure mode is a pair split across a column break, guarded by
   `break-inside: avoid` on the label. */
.descgrid { columns: 1; column-gap: 28px; margin: 0 0 18px; }
.descgrid dt {
  font-size: 11.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .3px;
  margin: 0; break-inside: avoid; break-after: avoid; -webkit-column-break-inside: avoid;
}
.descgrid dd {
  font-size: 13.5px; color: var(--text); margin: 3px 0 14px; overflow-wrap: anywhere;
  break-inside: avoid; -webkit-column-break-inside: avoid;
}
.descgrid dd details summary { cursor: pointer; font-size: 13.5px; color: var(--text); font-weight: 500; }
.descgrid dd details p { margin: 4px 0 0; color: var(--text-2); font-size: 12.5px; line-height: 1.6; }
@media (min-width: 600px) { .descgrid { columns: 2; } }
@media (min-width: 1100px) { .descgrid { columns: 3; } }

/* The job header: plan name and status pills on the left, Run now/Pause/
   Edit on the right, wrapping under the title on a narrow rail instead of
   crowding it (the jobs table's own `.jobactions` cell reuses the same
   button gap without the flex header). */
.jobhead { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 14px; }
.jobhead h2 { margin: 0 0 6px; font-size: 19px; }
.jobactions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* The actions wrap inside their cell so they cannot cover the next-run time. */
td.jobactionscell { min-width: 190px; text-align: left; }
td.jobactionscell .jobactions { flex-wrap: wrap; justify-content: flex-start; }
.jobmore summary { list-style: none; cursor: pointer; border: 1px solid var(--line); border-radius: var(--r-sm); padding: 8px 11px; background: var(--panel); color: var(--text-2); white-space: nowrap; }
.jobmore summary::-webkit-details-marker { display: none; }
.jobmore summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.jobmoreitems { display: flex; flex-direction: column; align-items: flex-start; gap: 7px; padding: 8px 0 2px; }
.jobstable { min-width: 980px; }
.jobstitle { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.jobnexttime { display: flex; flex-direction: column; gap: 2px; min-width: 130px; }
.jobstable .linkbtn { font-family: inherit; font-weight: 600; text-align: left; }
.jobstable .jobname .pill { display: inline-block; margin-top: 4px; }
@media (max-width: 640px) {
  .jobhead { flex-direction: column; align-items: stretch; }
  .jobactions { justify-content: flex-start; }
}

/* Jobs table and run history table: priority/observations columns are
   figures, right-aligned and non-wrapping like every other `.opstable
   td.num`; the scope/failed-sources columns are prose and take the squeeze
   instead (same `.opstable` default `overflow-wrap: anywhere` the queue
   table already relies on). */
.jobstable td.jobname { min-width: 180px; }
.jobsearch { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0 16px; font-size: 13px; }
.jobsearch input { width: min(100%, 330px); padding: 9px 11px; }
.jobstable .jobresultcell { min-width: 170px; }
.jobresultcell .small { display: block; margin-top: 5px; }
.jobkeywords { display: block; max-width: 240px; margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.joboutcome { border: 1px solid var(--line); background: var(--panel-alt); border-radius: var(--r-md); padding: 14px; margin: 8px 0 22px; }
.joboutcome h3 { margin: 0 0 10px; }
.joboutcomeline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
.jobresulttable td:last-child { min-width: 220px; max-width: 420px; }
.joboutcome > [data-job-latest] > .secondary { margin-top: 8px; }
html[data-theme="dark"] .descgrid dt { color: var(--muted); }
html[data-theme="dark"] .descgrid dd details p { color: var(--text-2); }
/* end job detail */

/* ---- Runs list and run detail (ops console stage B) ---- */
/* RunDetail.tsx: back-link row above the h2/status/actions row. */
.rtdetailhead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rtdetailhead + .rtdetailhead { margin-top: 6px; }
.runoutcome { border-left: 3px solid var(--accent); background: var(--panel-alt); padding: 10px 14px; margin: 12px 0; line-height: 1.5; }
.runtechnical { margin: 12px 0; }
.rtsourcedetail { margin-top: 5px; }
.rtsourcedetail summary { cursor: pointer; color: var(--muted); font-size: 12px; }
.rtsourcedetail p { margin: 4px 0 0; overflow-wrap: anywhere; }
.runstable td { vertical-align: middle; }
.runstable td:nth-child(2), .runstable td:nth-child(4) { min-width: 140px; }
.runarchive { margin-top: 16px; }
.runarchive > summary { cursor: pointer; font-weight: 600; }
.runarchive > .opscards { margin-top: 16px; }

/* RunDetail.tsx: the requested -> started -> last activity -> ended
   timeline. A plain <ol>, laid out as a row on wide screens and a stack
   on narrow ones; each step's own label sits above its `<When>` value. */
.rttimeline {
  list-style: none; margin: 12px 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 18px 28px;
}
.rttimestep { display: flex; flex-direction: column; gap: 2px; min-width: 120px; }
.rttimelabel { font-size: 11.5px; letter-spacing: .3px; text-transform: uppercase; color: var(--muted); }
@media (max-width: 640px) {
  .rttimeline { flex-direction: column; gap: 10px; }
}

/* Runs.tsx: the All / Completed / With failures / Running filter above
   the recent list. Plain buttons, one pressed at a time via aria-pressed
   -- no radio semantics needed since it never leaves the page. */
.segmented { display: inline-flex; gap: 2px; margin: 4px 0 10px; padding: 2px; background: #f0f3f5; border-radius: 8px; }
.segbtn {
  border: none; background: transparent; color: var(--text-2);
  font-size: 12.5px; padding: 5px 12px; border-radius: 6px; cursor: pointer;
}
.segbtn:hover { color: var(--text); }
.segbtn[aria-pressed="true"] { background: #fff; color: var(--text); box-shadow: 0 1px 2px rgba(0,0,0,.12); font-weight: 600; }

/* RunDetail.tsx: the Copy button under the opened log box. */
.rtlogcopy { margin-top: 6px; }

/* Session resolution precedes both sign-in and workspace rendering. */
.sessionboot {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-content: center;
  padding: 24px;
  text-align: center;
  background: var(--bg, #f3f6f8);
  color: var(--muted, #687f8c);
}
.sessionboot-spinner {
  display: inline-block;
  width: 28px;
  height: 28px;
  border: 3px solid var(--line, #dce4e8);
  border-top-color: var(--accent, #087d79);
  border-radius: 50%;
  animation: sessionboot-spin 0.8s linear infinite;
}
@keyframes sessionboot-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .sessionboot-spinner { animation: none; }
}
