/* ============================================================================
   AgentQA design system
   Vibrant indigo→violet brand over a calm neutral canvas.

   Status colors (pass / fail / blocked / replayed) come from a fixed reserved
   palette and are NEVER reused as decoration — and every status is rendered as
   icon + text label, so state never depends on hue alone.
   ============================================================================ */

:root {
  /* brand ramp */
  --brand-50:  #eef2ff;
  --brand-100: #e0e7ff;
  --brand-200: #c7d2fe;
  --brand-400: #818cf8;
  --brand-500: #6366f1;
  --brand-600: #4f46e5;
  --brand-700: #4338ca;
  --violet-500: #8b5cf6;
  --fuchsia-500: #d946ef;
  --cyan-500: #06b6d4;

  --grad-brand: linear-gradient(135deg, var(--brand-600) 0%, var(--violet-500) 55%, var(--fuchsia-500) 130%);
  --grad-sidebar: linear-gradient(170deg, #1e1b4b 0%, #17163a 45%, #121230 100%);

  /* reserved status palette — icon + label always accompany these */
  --good: #0ca30c;
  --good-bg: #e8f7e8;
  --warning: #b8790a;
  --warning-bg: #fdf3e0;
  --critical: #d03b3b;
  --critical-bg: #fdecec;
  --info: #4f46e5;
  --info-bg: #eef2ff;

  /* surfaces & ink */
  --canvas: #ffffff;
  --surface: #f7f8fc;
  --surface-2: #eff1f8;
  --sunken: #0f172a;
  --hairline: #e4e7f0;
  --hairline-strong: #cdd3e3;

  --ink: #14162b;
  --body: #4a5069;
  --muted: #858cab;
  --on-dark: #ffffff;
  --on-dark-dim: #b9b8dd;

  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 18px;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgba(20, 22, 43, .06), 0 1px 3px rgba(20, 22, 43, .04);
  --shadow: 0 4px 16px rgba(20, 22, 43, .07), 0 1px 3px rgba(20, 22, 43, .05);
  --shadow-lg: 0 24px 60px rgba(20, 22, 43, .22);
  --ring: 0 0 0 3px var(--brand-100);

  --sans: "Segoe UI Variable Display", "Segoe UI", system-ui, -apple-system, "Inter", sans-serif;
  --mono: "Cascadia Code", "JetBrains Mono", ui-monospace, Consolas, monospace;

  --sidebar-w: 268px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --canvas: #16182b;
    --surface: #10121f;
    --surface-2: #1d2036;
    --hairline: #2a2e47;
    --hairline-strong: #3a3f5e;
    --ink: #f2f3fb;
    --body: #c2c6dc;
    --muted: #8b90ae;
    --good-bg: #122c17;
    --warning-bg: #2d2312;
    --critical-bg: #33191b;
    --info-bg: #1e1f45;
    --good: #4ade80;
    --warning: #fbbf24;
    --critical: #f87171;
    --info: #a5b4fc;
    --shadow: 0 4px 18px rgba(0, 0, 0, .4);
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, .35);
    --ring: 0 0 0 3px rgba(99, 102, 241, .35);
  }
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--body);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; color: var(--ink); font-weight: 650; letter-spacing: -.01em; }
h1 { font-size: 21px; }
h2 { font-size: 17px; }
h3 { font-size: 15px; }

svg.ic { width: 1em; height: 1em; flex: none; vertical-align: -.125em; }
.ic-lg { width: 20px; height: 20px; }

/* ---------------------------------------------------------------- shell */

.shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; height: 100vh; overflow: hidden; }

.sidebar {
  background: var(--grad-sidebar);
  color: var(--on-dark);
  display: flex;
  flex-direction: column;
  padding: 20px 16px;
  gap: 20px;
  overflow-y: auto;
}

.brand { display: flex; align-items: center; gap: 12px; }
.brand-mark {
  width: 40px; height: 40px; border-radius: var(--radius);
  background: var(--grad-brand);
  display: grid; place-items: center; color: #fff;
  box-shadow: 0 6px 18px rgba(139, 92, 246, .45);
}
.brand-mark svg { width: 22px; height: 22px; }
.brand h2 { color: #fff; font-size: 16px; line-height: 1.15; }
.brand small { color: var(--on-dark-dim); font-size: 11px; font-weight: 500; }

.side-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: #8d8bc4; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;
}

.side-select {
  width: 100%; height: 40px; padding: 0 12px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--radius-sm);
  color: #fff; font-size: 13px; font-weight: 550; font-family: inherit;
  cursor: pointer;
}
.side-select option { background: #1e1b4b; color: #fff; }
.side-select:focus { outline: none; border-color: var(--brand-400); }

.nav { display: flex; flex-direction: column; gap: 3px; }
.nav-item {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 12px; border: none; border-radius: var(--radius-sm);
  background: transparent; color: #c6c5e8;
  font: inherit; font-size: 13.5px; font-weight: 550; text-align: left;
  cursor: pointer; transition: background .13s, color .13s;
  text-decoration: none;   /* the guide link is an <a>, the tabs are <button> */
}
.nav-item:hover { background: rgba(255, 255, 255, .07); color: #fff; }
.nav-item.active { background: var(--grad-brand); color: #fff; box-shadow: 0 4px 14px rgba(99, 102, 241, .4); }
/*
   The base dot. It exists because the only rule used to be the nav one below, and the two places
   that reused the class outside the sidebar got nothing from it: the Live Runner's step banner
   rendered a 10px box with no background at all — invisible — and the Performance tab's rendered a
   square, because it set a background inline but there was no border-radius to make it round.
   Both had been sitting there looking like a missing element rather than a broken style.

   The nav rule keeps its own colour, size and pulse; it is more specific, so nothing about the
   sidebar changes.
*/
.badge-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--brand-500); flex: none;
}
.nav-item .badge-dot {
  margin-left: auto; width: 7px; height: 7px; border-radius: 50%;
  background: #34d399; box-shadow: 0 0 0 3px rgba(52, 211, 153, .25);
  animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: .35; } }

.side-foot { margin-top: auto; padding-top: 14px; border-top: 1px solid rgba(255, 255, 255, .1); }
.side-foot .k { font-size: 10.5px; color: #8d8bc4; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; }
.side-foot .v { font-size: 11.5px; color: #d9d8f2; word-break: break-all; font-family: var(--mono); margin-top: 3px; }

/* ---------------------------------------------------------------- main */

.main { display: flex; flex-direction: column; min-width: 0; overflow: hidden; background: var(--surface); }

.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 16px 26px; background: var(--canvas);
  border-bottom: 1px solid var(--hairline); flex: none;
}
.topbar .sub { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.topbar-actions { display: flex; align-items: center; gap: 12px; }

.content { flex: 1; overflow-y: auto; padding: 24px 26px 40px; }

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-flex; align-items: center; gap: 7px; justify-content: center;
  height: 38px; padding: 0 16px; border-radius: var(--radius-sm);
  font: inherit; font-size: 13.5px; font-weight: 600;
  border: 1px solid transparent; cursor: pointer; white-space: nowrap;
  transition: transform .1s, box-shadow .15s, background .15s, border-color .15s;
}
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn:focus-visible { outline: none; box-shadow: var(--ring); }

.btn-primary { background: var(--grad-brand); color: #fff; box-shadow: 0 4px 14px rgba(99, 102, 241, .35); }
.btn-primary:hover:not(:disabled) { box-shadow: 0 7px 22px rgba(99, 102, 241, .48); }

.btn-ghost { background: var(--canvas); color: var(--ink); border-color: var(--hairline-strong); }
.btn-ghost:hover:not(:disabled) { background: var(--surface-2); border-color: var(--brand-400); }

.btn-danger { background: var(--critical); color: #fff; }
.btn-danger:hover:not(:disabled) { filter: brightness(1.08); }

.btn-sm { height: 30px; padding: 0 11px; font-size: 12.5px; border-radius: 7px; }
.btn-icon { width: 30px; height: 30px; padding: 0; border-radius: 7px; }
.btn-icon.danger { color: var(--critical); }
.btn-icon.danger:hover:not(:disabled) { background: var(--critical-bg); border-color: var(--critical); }

/* segmented control */
.segment { display: inline-flex; background: var(--surface-2); border-radius: var(--radius-sm); padding: 3px; gap: 2px; }
.segment button {
  border: none; background: transparent; color: var(--muted);
  font: inherit; font-size: 12.5px; font-weight: 650;
  padding: 6px 14px; border-radius: 6px; cursor: pointer; transition: all .14s;
  display: inline-flex; align-items: center; gap: 6px;
}
.segment button:hover { color: var(--ink); }
.segment button.active { background: var(--canvas); color: var(--brand-600); box-shadow: var(--shadow-sm); }

/* ---------------------------------------------------------------- cards */

.card {
  background: var(--canvas); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); padding: 20px; box-shadow: var(--shadow-sm);
}
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
/* min-width:0 lets the title shrink and ellipsise instead of wrapping to two lines and
   shoving the status chips out of alignment in a narrow column. */
.card-head h3 {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.card-head h3 svg { color: var(--brand-500); flex: none; }

.split { display: grid; grid-template-columns: 320px 1fr; gap: 20px; align-items: start; }
.split-wide { display: grid; grid-template-columns: 1fr 1.25fr; gap: 20px; }

@media (max-width: 1100px) {
  .split, .split-wide { grid-template-columns: 1fr; }
}

/* list rows (pages, reports) */
.row {
  position: relative; display: block; width: 100%; text-align: left;
  padding: 12px 13px; margin-bottom: 6px;
  background: var(--canvas); border: 1px solid var(--hairline);
  border-radius: var(--radius); cursor: pointer;
  transition: border-color .14s, box-shadow .14s, transform .1s;
}
.row:hover { border-color: var(--brand-400); box-shadow: var(--shadow-sm); }
.row.active { border-color: var(--brand-500); background: var(--info-bg); box-shadow: var(--ring); }
.row-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.row-title strong { color: var(--ink); font-size: 13.5px; font-weight: 650; }
.row-meta { font-size: 11.5px; color: var(--muted); margin-top: 3px; display: flex; align-items: center; gap: 8px; }
.row-actions { position: absolute; top: 8px; right: 8px; display: none; gap: 4px; }
.row:hover .row-actions, .row.active .row-actions { display: flex; }

.tag {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  padding: 2px 7px; border-radius: 5px;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--hairline);
  max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The full URL under the page heading. The 190px cap exists for the narrow pages
   list; here there is room, and a truncated URL you cannot read or copy is worse
   than a long one. Wraps rather than ellipsising so nothing is ever hidden. */
/* An <a> styled as a button: .btn handles the box, but a link still arrives with the
   user-agent's underline and link colour, which would make it the only odd control in
   a toolbar of real buttons. */
a.btn { text-decoration: none; color: inherit; }
a.btn-primary { color: #ffffff !important; }
a.btn:hover { text-decoration: none; }

/* ---------------------------------------------------------------- status */

.pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  padding: 3px 9px; border-radius: var(--radius-pill);
  border: 1px solid transparent; white-space: nowrap;
}
.pill svg { width: 12px; height: 12px; }
.pill.passed  { background: var(--good-bg);     color: var(--good);     border-color: color-mix(in srgb, var(--good) 30%, transparent); }
.pill.failed  { background: var(--critical-bg); color: var(--critical); border-color: color-mix(in srgb, var(--critical) 30%, transparent); }
.pill.blocked { background: var(--warning-bg);  color: var(--warning);  border-color: color-mix(in srgb, var(--warning) 32%, transparent); }
.pill.running { background: var(--info-bg);     color: var(--info);     border-color: color-mix(in srgb, var(--info) 30%, transparent); }
.pill.idle    { background: var(--surface-2);   color: var(--muted);    border-color: var(--hairline); }
.pill.incomplete { background: var(--warning-bg); color: var(--warning); border-color: color-mix(in srgb, var(--warning) 32%, transparent); }
/* Not run at all — an upstream test on a shared-window page failed first. */
.pill.skipped { background: var(--warning-bg); color: var(--warning); border-color: color-mix(in srgb, var(--warning) 32%, transparent); }
.pill.neutral { background: var(--surface-2);   color: var(--body);     border-color: var(--hairline); }

.chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px; font-weight: 650; padding: 2px 8px;
  border-radius: var(--radius-pill); background: var(--surface-2);
  color: var(--muted); border: 1px solid var(--hairline);
  /* A chip is a single token — "2 parallel" breaking across two lines made the whole
     header row wrap and pushed the title out of alignment. */
  white-space: nowrap;
}
.chip svg { width: 11px; height: 11px; flex: none; }
.chip.on { background: var(--good-bg); color: var(--good); border-color: color-mix(in srgb, var(--good) 28%, transparent); }
.chip.rec { background: var(--info-bg); color: var(--info); border-color: color-mix(in srgb, var(--info) 28%, transparent); }
.chip.warn { background: var(--warning-bg); color: var(--warning); border-color: color-mix(in srgb, var(--warning) 28%, transparent); }

/* ---------------------------------------------------------------- stat tiles */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin-bottom: 22px; }
.stat {
  background: var(--canvas); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: 15px 16px; box-shadow: var(--shadow-sm);
}
.stat .label { font-size: 11.5px; color: var(--muted); font-weight: 600; margin-bottom: 5px; }
/* proportional figures: tabular-nums makes large standalone values look loose */
.stat .value { font-size: 27px; font-weight: 680; color: var(--ink); line-height: 1.1; letter-spacing: -.02em; }
.stat .value.good { color: var(--good); }
.stat .value.bad { color: var(--critical); }
.stat .foot { font-size: 11px; color: var(--muted); margin-top: 4px; }

/* hero: exactly one per view */
.hero-verdict {
  display: flex; align-items: center; gap: 16px;
  padding: 20px 22px; border-radius: var(--radius-lg); margin-bottom: 20px;
  border: 1px solid var(--hairline); background: var(--canvas); box-shadow: var(--shadow-sm);
}
.hero-verdict .mark { width: 54px; height: 54px; border-radius: 15px; display: grid; place-items: center; }
.hero-verdict .mark svg { width: 28px; height: 28px; }
.hero-verdict.passed .mark { background: var(--good-bg); color: var(--good); }
.hero-verdict.failed .mark { background: var(--critical-bg); color: var(--critical); }
.hero-verdict.incomplete .mark { background: var(--warning-bg); color: var(--warning); }
.hero-verdict .headline { font-size: 30px; font-weight: 680; letter-spacing: -.02em; color: var(--ink); line-height: 1.1; }
.hero-verdict .sub { font-size: 12.5px; color: var(--muted); margin-top: 3px; }

/* ---------------------------------------------------------------- scenarios & steps */

.scenario {
  border: 1px solid var(--hairline); border-radius: var(--radius);
  padding: 16px; margin-bottom: 14px; background: var(--canvas);
  transition: border-color .14s, box-shadow .14s;
}
.scenario:hover { border-color: var(--hairline-strong); }
.scenario-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.scenario-head .desc { font-size: 12px; color: var(--muted); margin-top: 2px; }
.scenario-tools { display: flex; align-items: center; gap: 6px; flex: none; }

.step {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 11px; margin-bottom: 5px;
  background: var(--surface); border-radius: var(--radius-sm);
  border-left: 3px solid var(--brand-400); font-size: 13px; color: var(--body);
}
.step.assertion { border-left-color: var(--fuchsia-500); background: color-mix(in srgb, var(--fuchsia-500) 5%, var(--surface)); }
.step .n {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  width: 18px; flex: none; text-align: center;
}
.step .txt { flex: 1; min-width: 0; }
.step code { font-family: var(--mono); font-size: 12px; background: var(--brand-50); color: var(--brand-700); padding: 1px 5px; border-radius: 4px; }

/* ---------------------------------------------------------------- runner */

.viewport {
  background: var(--sunken); border-radius: var(--radius);
  border: 1px solid var(--hairline); overflow: hidden;
  display: grid; place-items: center; min-height: 430px; position: relative;
}
.viewport img { width: 100%; height: auto; display: block; }
.viewport .placeholder { color: #6b7280; text-align: center; padding: 40px; }
.viewport .placeholder svg { width: 44px; height: 44px; opacity: .4; margin-bottom: 10px; }

.live-dot {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 700; color: var(--critical);
}
.live-dot::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--critical); animation: pulse 1.3s ease-in-out infinite;
}

/* The post-suite performance pass. Shaped like the step banner above the runner grid so the two
   read as the same kind of thing, and pulsing on the same keyframes as the nav dot so it reads as
   "still working" rather than as a result. */
.perf-live {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 12px 18px; margin-bottom: 12px;
  background: var(--surface); border: 1px solid var(--brand-500);
  border-left: 5px solid var(--brand-500); border-radius: var(--radius);
  box-shadow: 0 2px 8px rgba(79, 70, 229, .08);
}
.perf-live .dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none; margin-top: 4px;
  background: var(--brand-500);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 22%, transparent);
  animation: pulse 1.6s ease-in-out infinite;
}
.perf-live .body { min-width: 0; }
.perf-live .k {
  font-size: 11px; font-weight: 700; color: var(--brand-500);
  text-transform: uppercase; letter-spacing: .05em;
}
.perf-live .v {
  font-size: 14px; font-weight: 600; color: var(--ink); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.perf-live .why { font-size: 11.5px; color: var(--muted); margin-top: 4px; }

@media (prefers-reduced-motion: reduce) {
  .perf-live .dot { animation: none; }
}

.log { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; flex: 1; min-height: 0; }
.log-line {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 9px 11px; border-radius: var(--radius-sm);
  background: var(--surface); border-left: 3px solid var(--hairline-strong);
  font-size: 12.5px; color: var(--body); word-break: break-word;
}
.log-line svg { width: 14px; height: 14px; margin-top: 1px; flex: none; }
.log-line.passed  { border-left-color: var(--good);     background: var(--good-bg);     color: color-mix(in srgb, var(--good) 75%, var(--ink)); }
.log-line.failed  { border-left-color: var(--critical); background: var(--critical-bg); color: color-mix(in srgb, var(--critical) 75%, var(--ink)); }
.log-line.blocked { border-left-color: var(--warning);  background: var(--warning-bg);  color: color-mix(in srgb, var(--warning) 75%, var(--ink)); }
.log-line.skipped { border-left-color: var(--warning);  background: var(--warning-bg);  color: color-mix(in srgb, var(--warning) 75%, var(--ink)); }
.log-line.info    { border-left-color: var(--brand-400); }

/* Inline failure detail: the same evidence Reports shows, without leaving the runner. */
.log-detail {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--hairline);
  color: var(--body);
}
.log-detail .shot { max-height: 220px; }

/* ---------------------------------------------------------------- report detail */

.step-report {
  border: 1px solid var(--hairline); border-radius: var(--radius);
  padding: 13px 15px; margin-bottom: 10px; background: var(--canvas);
}
.step-report-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.step-report .detail { font-size: 12.5px; color: var(--muted); margin-top: 6px; }
.trace {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  padding: 3px 0 3px 11px; border-left: 2px solid var(--hairline); margin: 3px 0 0 3px;
}
.callout {
  margin-top: 10px; padding: 11px 13px; border-radius: var(--radius-sm);
  background: var(--surface); border-left: 3px solid var(--brand-500);
  font-size: 12px; color: var(--body); white-space: pre-wrap;
}
.callout.warn { background: var(--warning-bg); border-left-color: var(--warning); }
.shot { width: 100%; max-height: 300px; object-fit: contain; object-position: top left;
        border-radius: var(--radius-sm); border: 1px solid var(--hairline); margin-top: 10px; background: var(--surface); }

/* ---------------------------------------------------------------- forms */

.field { margin-bottom: 14px; }
.field label { display: block; font-size: 12px; font-weight: 650; color: var(--ink); margin-bottom: 5px; }
.field .hint { font-size: 11.5px; color: var(--muted); margin-top: 4px; }

.input, .select, textarea.input {
  width: 100%; height: 40px; padding: 0 12px;
  background: var(--canvas); color: var(--ink);
  border: 1px solid var(--hairline-strong); border-radius: var(--radius-sm);
  font: inherit; font-size: 13.5px;
}
textarea.input { height: auto; padding: 10px 12px; resize: vertical; }
.input:focus, .select:focus { outline: none; border-color: var(--brand-500); box-shadow: var(--ring); }
.input::placeholder { color: var(--muted); }

.check { display: flex; align-items: center; gap: 9px; font-size: 13px; color: var(--body); cursor: pointer; }
.check input { width: 17px; height: 17px; accent-color: var(--brand-600); cursor: pointer; }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 760px) { .grid-2 { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------- modal */

.overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(15, 17, 34, .55); backdrop-filter: blur(3px);
  display: grid; place-items: center; padding: 24px;
  animation: fade .14s ease;
}
@keyframes fade { from { opacity: 0; } }

/* A picker opened from inside a dialog — the "copy steps" and "run another test first" pickers,
   which are siblings of the dialog in the markup rather than children of it. Their z-index is
   therefore compared against the dialog's own 200 and has to beat it, or they open behind the
   thing that opened them. Stays under the toasts (300) so a save is still confirmed on top. */
.overlay.stacked { z-index: 210; }

.modal {
  background: var(--canvas); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg); width: 100%; max-width: 560px;
  max-height: 88vh; display: flex; flex-direction: column;
  animation: rise .18s cubic-bezier(.2, .8, .3, 1);
}
@keyframes rise { from { opacity: 0; transform: translateY(12px) scale(.98); } }
.modal-head { padding: 20px 22px 0; }
.modal-head p { font-size: 13px; color: var(--muted); margin: 6px 0 0; }
.modal-body { padding: 18px 22px; overflow-y: auto; }
.modal-foot {
  display: flex; justify-content: flex-end; gap: 9px;
  padding: 16px 22px; border-top: 1px solid var(--hairline);
}
.modal.danger .modal-head h2 { color: var(--critical); }

/* ---------------------------------------------------------------- misc */

.empty { text-align: center; padding: 56px 24px; color: var(--muted); }
.empty svg { width: 44px; height: 44px; opacity: .35; margin-bottom: 12px; color: var(--brand-500); }
.empty h3 { color: var(--body); margin-bottom: 6px; }
.empty p { font-size: 13px; margin: 0 auto; max-width: 380px; }

.banner {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 12px 15px; border-radius: var(--radius); margin-bottom: 18px;
  font-size: 12.5px; border: 1px solid transparent;
}
.banner svg { width: 16px; height: 16px; flex: none; margin-top: 1px; }
.banner.ok   { background: var(--good-bg);     color: var(--good);     border-color: color-mix(in srgb, var(--good) 25%, transparent); }
.banner.bad  { background: var(--critical-bg); color: var(--critical); border-color: color-mix(in srgb, var(--critical) 25%, transparent); }
.banner.tip  { background: var(--info-bg);     color: var(--info);     border-color: color-mix(in srgb, var(--info) 22%, transparent); }

/* Toasts. Bottom-right and fixed, above the modal overlay (z-index 200) so a save made from
   inside a dialog is still confirmed. Stacked newest-last so the list reads in the order the
   actions happened. */
.toast-wrap {
  position: fixed; bottom: 20px; right: 20px; z-index: 300;
  display: flex; flex-direction: column; gap: 9px;
  max-width: min(380px, calc(100vw - 40px));
  pointer-events: none;                 /* never blocks the UI behind it */
}
.toast {
  pointer-events: auto;                 /* but the dismiss button still works */
  display: flex; gap: 10px; align-items: flex-start;
  padding: 11px 13px; border-radius: var(--radius);
  font-size: 12.5px; border: 1px solid transparent;
  background: var(--panel); color: var(--ink);
  box-shadow: 0 6px 20px rgb(0 0 0 / 18%);
  animation: toast-in 140ms ease-out;
}
.toast svg { width: 16px; height: 16px; flex: none; margin-top: 1px; }
.toast-text { flex: 1; min-width: 0; }
.toast.ok   { background: var(--good-bg);     color: var(--good);     border-color: color-mix(in srgb, var(--good) 25%, transparent); }
.toast.bad  { background: var(--critical-bg); color: var(--critical); border-color: color-mix(in srgb, var(--critical) 25%, transparent); }
.toast.tip  { background: var(--info-bg);     color: var(--info);     border-color: color-mix(in srgb, var(--info) 22%, transparent); }
.toast-x {
  background: none; border: 0; cursor: pointer; padding: 0 2px;
  color: inherit; opacity: .55; font-size: 15px; line-height: 1;
}
.toast-x:hover { opacity: 1; }

@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* Honour a reduced-motion preference: the toast still appears, it just does not slide. */
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

.steps-editor-row { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; transition: background 0.15s, transform 0.15s; border-radius: var(--radius-sm); padding: 4px; margin-left: -4px; margin-right: -4px; }
.steps-editor-row .select { width: 116px; flex: none; }
.steps-editor-row .drag-handle { cursor: grab; color: var(--muted); flex: none; width: 18px; height: 18px; opacity: 0.5; transition: opacity 0.15s, color 0.15s; }
.steps-editor-row .drag-handle:active { cursor: grabbing; }
.steps-editor-row:hover .drag-handle { opacity: 1; color: var(--ink); }
.steps-editor-row.dragging { opacity: 0.4; background: var(--brand-50, rgba(79,70,229,0.06)); }
.steps-editor-row.drag-over { background: var(--brand-50, rgba(79,70,229,0.08)); box-shadow: 0 -2px 0 0 var(--brand-500) inset; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--hairline-strong); border-radius: 5px; border: 2px solid var(--surface); }
::-webkit-scrollbar-thumb:hover { background: var(--muted); }

/* ---- Live Runner scenario rail: run the next test without leaving the tab ---- */

/* The rail holds a three-level tree, so it needs more room than a flat list did —
   at 270px, grouped test names wrapped to two lines and then truncated. */
.runner-grid { display: grid; grid-template-columns: 330px 1fr 1.25fr; gap: 20px; }
@media (max-width: 1400px) { .runner-grid { grid-template-columns: 290px 1fr 1fr; } }
@media (max-width: 1100px) { .runner-grid { grid-template-columns: 1fr; height: auto !important; } }

/* .rail-page is defined in the runner-rail block further down — it became a clickable
   collapse header, so the old text-only rule was superseded rather than extended. */
.rail-row {
  display: flex; align-items: center; gap: 8px;
  position: relative; overflow: hidden;
  padding: 9px 10px; margin-bottom: 5px;
  border: 1px solid var(--hairline); border-radius: var(--radius-sm);
  background: var(--canvas); transition: border-color .13s, background .13s, box-shadow .13s;
  cursor: pointer;
}
.rail-row:hover { border-color: var(--brand-400); }

/* The inspected row — quieter than .live, which outranks it whenever a run is going. */
.rail-row.picked { border-color: var(--brand-400); background: var(--surface); }
.rail-row.picked .rail-name { color: var(--brand-600); font-weight: 700; }

/* The running test has to be findable at a glance in a long rail: accent bar, tinted
   ground, and a sweep along the top edge so it reads as active rather than merely selected. */
.rail-row.live {
  border-color: var(--brand-500);
  background: var(--info-bg);
  box-shadow: var(--ring);
  padding-left: 13px;
}
.rail-row.live::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: var(--brand-500);
}
.rail-row.live::after {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, transparent, var(--brand-500), transparent);
  animation: railsweep 1.6s ease-in-out infinite;
}
.rail-row.live .rail-name { color: var(--brand-600); font-weight: 700; }
@keyframes railsweep { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .rail-row.live::after { animation: none; opacity: .55; }
}

/* ---- project variables editor ---- */
.var-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) auto;
  gap: 8px; align-items: center;
}
.var-head {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
}
/* Rows are real elements so they can carry a Blazor @key, but they must not become grid
   items themselves — display:contents hoists their children into the parent grid. */
.var-row { display: contents; }
.var-grid .input { margin: 0; }
/* Block rules carry a fourth column the variables editor does not have. */
.rule-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) minmax(0, 1.1fr) auto; }
.rule-ww { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rule-ww .check { font-size: 12.5px; }
.rule-ww .check:has(input:disabled) { opacity: .5; cursor: default; }
.rule-ww .check input:disabled { cursor: default; }
.rule-ww-hint { font-size: 11px; line-height: 1.35; color: var(--muted); }
.input.mono { font-family: var(--mono, ui-monospace, monospace); font-size: 12.5px; }
/* Inline literal inside running text — a selector in a hint, not a whole field. Sized in em
   so it tracks whatever it sits in rather than jumping in the 11px hints. */
.mono { font-family: var(--mono, ui-monospace, monospace); font-size: .95em; }
.rail-name {
  font-size: 12.5px; font-weight: 600; color: var(--ink); line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* ---- report summary table ---- */
.scroll-x { overflow-x: auto; max-width: 100%; box-sizing: border-box; }
.rep-table { border-collapse: collapse; width: 100%; font-size: 13.5px; table-layout: auto; }
.rep-table th, .rep-table td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--hairline); vertical-align: top; word-break: break-word; overflow-wrap: anywhere; }
.rep-table thead th {
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
  background: var(--surface); white-space: nowrap;
}
.rep-table tbody tr:last-child td { border-bottom: none; }
.rep-table tbody tr:hover { background: var(--surface); }
.rep-table td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------------------------------------------------------------- app sign-in */

/* The one screen shown before the app shell exists, so it carries its own centring
   and ground rather than inheriting the sidebar layout. */
.login-shell {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px; background: var(--canvas);
}
.login-card {
  width: 100%; max-width: 380px;
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: 34px 32px 30px;
  box-shadow: 0 12px 40px rgba(15, 18, 45, .10);
}
.login-brand { text-align: center; margin-bottom: 26px; }
.login-mark {
  width: 46px; height: 46px; margin: 0 auto 13px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 12px; background: var(--grad-brand); color: #fff;
}
.login-mark .ic { width: 24px; height: 24px; }
.login-brand h1 { margin: 0; font-size: 22px; letter-spacing: -.02em; color: var(--ink); }
.login-brand p { margin: 4px 0 0; font-size: 12.5px; color: var(--muted); }

.login-field { margin-bottom: 15px; }
.login-field label {
  display: block; font-size: 12.5px; font-weight: 650; color: var(--ink); margin-bottom: 6px;
}
.login-submit { width: 100%; justify-content: center; margin-top: 6px; }

.login-error {
  display: flex; gap: 9px; align-items: flex-start;
  background: var(--critical-bg); color: color-mix(in srgb, var(--critical) 78%, var(--ink));
  border: 1px solid color-mix(in srgb, var(--critical) 26%, transparent);
  border-radius: var(--radius-sm); padding: 10px 12px; margin-bottom: 18px; font-size: 12.5px;
}
.login-error .ic { width: 15px; height: 15px; flex: none; margin-top: 1px; }

/* ---------------------------------------------------------------- test groups */

/* Section header inside a page's test list — the same tinted container header the
   runner rail uses, so the two views teach one visual language instead of two.
   Sticky, because with 40 tests on a page the header leaves the viewport fast. */
.group-head {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 12px; margin: 20px 0 0;
  /* --info-bg is theme-aware (the raw brand ramp is not) and fully opaque, so the
     header hides content scrolling beneath it while stuck. */
  background: var(--info-bg);
  border: 1px solid color-mix(in srgb, var(--info) 22%, transparent);
  border-radius: var(--radius-sm);
  cursor: pointer; user-select: none;
  position: sticky; top: 0; z-index: 2;
}
.group-head:hover { border-color: color-mix(in srgb, var(--info) 42%, transparent); }
.group-head strong {
  color: var(--ink); font-size: 12.5px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
}

/* Tests belonging to a group sit inside a containing rail, so the group has a
   visible extent — where it starts and, just as importantly, where it ends.
   Rail is brand-tinted and stronger than a test card's own hairline: the weight
   order (rail > card) is what keeps this reading as a tree rather than a stack. */
.group-kids {
  position: relative;
  margin: 0 0 22px 12px;
  padding: 12px 0 0 16px;
  border-left: 2px solid color-mix(in srgb, var(--info) 34%, transparent);
}
.group-kids::after {
  content: ""; position: absolute; left: -2px; bottom: 0;
  width: 10px; height: 2px;
  background: color-mix(in srgb, var(--info) 34%, transparent);
  border-radius: 0 2px 2px 0;
}
/* The rail supplies the indent, so the card must not add another. Cards inside a
   rail get the same subtle lift the runner's rows do, so both views agree. */
.scenario.grouped { margin-left: 0; box-shadow: var(--shadow-sm); }
.scenario.grouped:hover { border-color: var(--brand-400); box-shadow: var(--shadow); }

/* Bulk group assignment, shown only while something is ticked. */
.bulk-bar {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 10px 13px; margin-bottom: 12px;
  background: var(--info-bg); border: 1px solid color-mix(in srgb, var(--info) 26%, transparent);
  border-radius: var(--radius-sm); font-size: 13px; color: var(--ink);
  /* Outranks the sticky group headers, which would otherwise slide underneath and
     show through this bar's tinted (not opaque) ground. */
  position: sticky; top: 0; z-index: 3;
  box-shadow: var(--shadow-sm);
}
.scenario.picked { border-color: var(--brand-400); box-shadow: var(--ring); }

/* ---------------------------------------------------------------- runner rail

   A three-level tree in a 270px column: page › group › test. The levels are told
   apart by weight and indent rather than by boxes — three nested boxes in a narrow
   column read as clutter, and the earlier version made a group header look exactly
   like a test row.                                                                */

.rail-block { margin-bottom: 4px; }

/* Page-level rail — the OUTER container.
   Deliberately stronger than the group rail nested inside it: two rails of equal
   weight read as two siblings, which is exactly the ambiguity that made everything
   look like a flat stack of boxes. Outer = brand-tinted and solid; inner = grey and
   lighter. Depth is then legible at a glance rather than inferred from indent. */
.rail-page-kids {
  position: relative;
  margin: 2px 0 14px 6px;
  padding-left: 14px;
  border-left: 2px solid color-mix(in srgb, var(--info) 34%, transparent);
}
.rail-page-kids::after {
  content: ""; position: absolute; left: -2px; bottom: 0;
  width: 9px; height: 2px; background: color-mix(in srgb, var(--info) 34%, transparent);
  border-radius: 0 2px 2px 0;
}
/* Inner group rail sits just inside the page rail, not indented a second full step —
   in a 330px column every extra indent is taken straight out of the test name.
   Kept a touch stronger than a row's own border so the containment still reads
   rail-then-card rather than two competing edges. */
.rail-page-kids .rail-kids {
  margin-left: 2px;
  padding-left: 11px;
  border-left-color: var(--hairline-strong);
}

/* Shared twisty. Rotates rather than swapping glyphs, so there is one icon to
   recognise and the direction reads as state. */
.tw {
  display: inline-flex; flex: none; width: 13px; height: 13px;
  align-items: center; justify-content: center;
  color: var(--muted); transition: transform .15s;
  transform: rotate(90deg);          /* open: pointing down */
}
.tw.shut { transform: rotate(0deg); }  /* closed: pointing right */
.tw svg { width: 9px; height: 9px; }
@media (prefers-reduced-motion: reduce) { .tw { transition: none; } }

/* Level 1 — page. Sentence case: uppercase plus letter-spacing overflowed a long
   page name straight into its own count badge. */
/* The page header is the top of a container, so it is tinted and rounded into the
   rail below it rather than being another neutral row in the stack. */
.rail-page {
  display: flex; align-items: center; gap: 7px;
  padding: 8px 9px; border-radius: var(--radius-sm);
  cursor: pointer; user-select: none;
  font-size: 13px; font-weight: 680; color: var(--ink);
  /* --info-bg is theme-aware (the brand ramp is not: --brand-50 stays near-white in
     dark mode and would put dark ink on a light chip). */
  background: var(--info-bg);
  border: 1px solid color-mix(in srgb, var(--info) 22%, transparent);
}
.rail-page:hover { border-color: color-mix(in srgb, var(--info) 42%, transparent); }
.rail-page-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail-page.shut { color: var(--body); }

/* Level 2 — group. Same container treatment as the page header, one step quieter:
   a filled, bordered header that visibly caps the rail beneath it. Kept smaller and
   uppercase so it still reads as subordinate to the page rather than a peer. */
.rail-group {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 8px; margin-top: 8px;
  cursor: pointer; user-select: none;
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--body); border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--hairline);
}
.rail-group:hover { border-color: var(--hairline-strong); color: var(--ink); }
.rail-group-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Count badge. Fixed min-width and its own box so a long name can never collide
   with it — the bug that made "DISH MAINTANANCE" sit on top of its 40. */
.rail-count {
  flex: none; min-width: 20px; text-align: center;
  padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--surface-2); border: 1px solid var(--hairline);
  font-size: 10.5px; font-weight: 650; color: var(--muted);
  font-variant-numeric: tabular-nums; letter-spacing: 0;
}
.rail-page:hover .rail-count, .rail-group:hover .rail-count { border-color: var(--hairline-strong); }

/* The tests belonging to one group, wrapped so the group has a visible extent.
   A continuous rail down the left says "these belong to the header above" and,
   just as importantly, shows where the group STOPS — indentation alone did not. */
.rail-kids {
  position: relative;
  margin: 2px 0 10px 8px;
  padding-left: 12px;
  border-left: 2px solid var(--hairline-strong);
}
/* A short foot on the rail closes the group off, so two adjacent groups cannot
   read as one continuous run of tests. */
.rail-kids::after {
  content: ""; position: absolute; left: -2px; bottom: 0;
  width: 8px; height: 2px; background: var(--hairline-strong);
  border-radius: 0 2px 2px 0;
}

/* Level 3 — test. Title on its own line at full width; chips and actions beneath. */
.rail-row { flex-direction: column; align-items: stretch; gap: 5px; }
/* The rail provides the indent now, so the row does not add its own on top. */
.rail-kids .rail-row { margin-left: 0; }

/* Rows inside a rail are proper cards again, but deliberately quieter than the rail
   that contains them: a light hairline on a raised ground. Equal-weight borders were
   what made the tree read as a flat stack of boxes — the hierarchy survives because
   the rail is stronger than the card, not because the card has no edge at all. */
.rail-page-kids .rail-row {
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  margin-bottom: 6px;
}
.rail-page-kids .rail-row:last-child { margin-bottom: 0; }
.rail-page-kids .rail-row:hover {
  border-color: var(--brand-400);
  box-shadow: var(--shadow);
}
/* The two states worth interrupting the list for. `.live` keeps its accent bar,
   ring and sweep from the base rule — only the border/background are restated. */
.rail-page-kids .rail-row.picked {
  background: var(--canvas);
  border-color: var(--brand-400);
  box-shadow: var(--ring);
}
.rail-page-kids .rail-row.live {
  border-color: var(--brand-500);
  background: var(--info-bg);
  box-shadow: var(--ring);
  padding-left: 13px;
}
.rail-title { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.rail-title .rail-name { flex: 1; min-width: 0; }
.rail-meta { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; min-height: 24px; }

/* Actions appear on hover/focus. Identical icons repeated down every row was noise;
   revealing them keeps the list scannable while leaving them one move away.
   Always visible on touch, where there is no hover to reveal them with. */
.rail-acts {
  display: flex; align-items: center; gap: 3px; margin-left: auto; flex: none;
  opacity: 0; transition: opacity .12s;
}
.rail-row:hover .rail-acts,
.rail-row:focus-within .rail-acts,
.rail-page:hover .rail-acts,
.rail-page:focus-within .rail-acts,
.rail-group:hover .rail-acts,
.rail-group:focus-within .rail-acts,
.rail-row.live .rail-acts,
.rail-row.picked .rail-acts { opacity: 1; }
@media (hover: none) { .rail-acts { opacity: 1; } }
.rail-acts .btn { height: 26px; padding: 0 6px; }

/* ---------------------------------------------------------------- worker grid */

/* One tile per parallel browser. auto-fit rather than a fixed column count so 2
   workers get big tiles and 8 get small ones, without a media query per case. */
.worker-grid {
  display: grid; gap: 8px; flex: 1; min-height: 0; overflow-y: auto;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  align-content: start;
}
.worker-tile {
  border: 1px solid var(--hairline); border-radius: var(--radius-sm);
  background: var(--sunken); overflow: hidden;
  display: flex; flex-direction: column;
}
/* Only the working tiles are outlined, so a glance says how many are actually busy. */
.worker-tile.busy { border-color: var(--brand-500); }
.worker-head {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 7px; background: var(--surface-2);
  border-bottom: 1px solid var(--hairline); font-size: 11px;
}
.worker-n {
  flex: none; width: 16px; height: 16px; border-radius: 4px;
  background: var(--brand-500); color: #fff;
  display: grid; place-items: center; font-size: 10px; font-weight: 700;
}
.worker-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--body);
}
.worker-tile img { width: 100%; display: block; aspect-ratio: 16 / 10; object-fit: cover; object-position: top left; }

/* ---------------------------------------------------------------- design system editor */

/* Dimmed rather than hidden when the spec is switched off: what is declared is still worth
   reading, and a collapsed section makes the switch look like it deleted the values. */
.spec-body.off { opacity: .55; }
.spec-body.off .field label { color: var(--muted); }
/* Two columns — the group key is fixed vocabulary, only the word is editable. */
.verb-grid { grid-template-columns: minmax(0, 140px) minmax(0, 1fr); }
.verb-key {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 12.5px; color: var(--ink);
}

/* Six columns on one line, with the colours narrowest: a hex value is seven characters and
   a selector is not, so the space goes where the typing is. */
.role-grid {
  grid-template-columns:
    minmax(0, 1fr) minmax(0, 1.5fr)
    minmax(0, .9fr) minmax(0, .9fr) minmax(0, .9fr) auto;
}
/* The selector cell stacks so the warning sits under its own field instead of forcing a
   seventh column onto every row. */
.role-sel { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.role-warn { font-size: 11px; line-height: 1.35; color: var(--warning); }
/* Amber, not red: the row is incomplete rather than wrong, and it costs nothing but a check
   that never runs. */
.input.flagged {
  border-color: color-mix(in srgb, var(--warning) 55%, var(--hairline));
  background: var(--warning-bg);
}
/* The tone column is fixed vocabulary, so it only needs room for the longest of the four. */
.msg-grid { grid-template-columns: minmax(0, 100px) minmax(0, 1fr) minmax(0, 1fr); }

/* ---------------------------------------------------------------- audit page picker */

/* auto-fit rather than a fixed count: a six-page map and a forty-page map are the same
   markup, and the tiles have to stay large enough to recognise a page from. */
.audit-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
/* The whole tile is the label, so the thumbnail is a click target too — the checkbox
   alone is a 17px target repeated forty times. */
.audit-page {
  border: 1px solid var(--hairline); border-radius: var(--radius);
  background: var(--canvas); overflow: hidden; cursor: pointer;
  display: flex; flex-direction: column;
}
.audit-page:hover { border-color: var(--brand-400); }
.audit-page.picked { border-color: var(--brand-500); box-shadow: var(--ring); }
/* Unpicked tiles are dimmed rather than hidden: what is being left out is part of the
   decision, and a greyed tile says "considered and skipped". */
.audit-page:not(.picked) .audit-page-shot { opacity: .45; }
.audit-page-shot {
  aspect-ratio: 16 / 10; background: var(--sunken);
  border-bottom: 1px solid var(--hairline);
}
.audit-page-shot img { width: 100%; height: 100%; display: block; object-fit: cover; object-position: top left; }
.audit-page-blank { width: 100%; height: 100%; display: grid; place-items: center; color: var(--muted); }
.audit-page-blank svg { width: 22px; height: 22px; opacity: .5; }
.audit-page-body { display: flex; align-items: flex-start; gap: 9px; padding: 10px 11px; }
.audit-page-body input { width: 17px; height: 17px; flex: none; accent-color: var(--brand-600); cursor: pointer; }

/* ---------------------------------------------------------------- design document import */

/* Used in a few places for a parenthetical next to a label, and never defined until now. */
.muted { color: var(--muted); }

/* A preview, not a result: bordered and inset so it reads as something still pending a
   decision rather than as part of the form it is sitting in. */
.spec-import {
  border: 1px solid var(--hairline); border-radius: var(--radius);
  background: var(--surface-2); padding: 12px 14px;
}
.spec-import-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--ink); margin-bottom: 8px;
}
.spec-import-head svg { width: 14px; height: 14px; flex: none; color: var(--muted); }
.spec-import-sub {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  color: var(--muted); margin: 10px 0 4px;
}
.spec-import-list { margin: 0; padding-left: 18px; font-size: 12.5px; color: var(--ink); }
.spec-import-list li { margin-bottom: 3px; }

/* The small label above each prose block inside a security finding (Impact, Proof of concept,
   Remediation). Same visual weight as the import sub-heading so the two read as one family. */
.sec-section-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.03em; color: var(--muted); margin: 0 0 3px;
}
/* Scrolls rather than grows: a token file can produce dozens of lines, and a preview that
   pushes its own Apply button off the screen is not a preview. */
.spec-import-list { max-height: 220px; overflow-y: auto; }

/* ============================================================ admin screens
   Its own route rather than a tab in the main shell, so these styles stand alone rather than
   inheriting the sidebar layout. Everything below is built from the same tokens as the rest of
   the app, which is what makes it follow the theme into dark mode without a second palette. */

.admin-shell {
  max-width: 1240px;
  margin: 0 auto;
  padding: 28px 32px 64px;
}

.admin-top {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin-bottom: 26px;
}
.admin-top h1 { margin: 0; font-size: 26px; letter-spacing: -.02em; color: var(--ink); }
.admin-top .sub { color: var(--muted); font-size: 13.5px; margin-top: 4px; }
.admin-top-acts { display: flex; gap: 8px; align-items: center; }

.admin-loading { color: var(--muted); padding: 60px 0; text-align: center; }

/* ---- headline figures ---- */
.stat-row {
  display: grid; gap: 16px; margin-bottom: 20px;
  grid-template-columns: repeat(auto-fit, minmax(228px, 1fr));
}
.stat-card {
  display: flex; gap: 14px; align-items: flex-start;
  background: var(--canvas); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: 18px; box-shadow: var(--shadow-sm);
}
.stat-icon {
  width: 42px; height: 42px; flex: none; border-radius: var(--radius-sm);
  display: grid; place-items: center;
}
.stat-icon .ic { width: 20px; height: 20px; }
.stat-body { min-width: 0; }
.stat-value { font-size: 30px; font-weight: 650; line-height: 1; color: var(--ink); letter-spacing: -.03em; }
.stat-unit { font-size: 16px; font-weight: 500; color: var(--muted); margin-left: 2px; }
.stat-label { font-size: 13px; font-weight: 600; color: var(--body); margin-top: 6px; }
.stat-foot { font-size: 12px; color: var(--muted); margin-top: 3px; }

/* ---- the grid the panels sit in ---- */
/* Two equal columns rather than auto-fit. Auto-fit gave four columns on a wide screen and the
   two half-width panels then sat in the first two, leaving half the row empty. */
.admin-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.admin-chart { grid-column: 1 / -1; }
.admin-recent { grid-column: 1 / -1; }

@media (max-width: 900px) {
  .admin-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---- activity bars ----
   Divs rather than an SVG chart: fourteen values need no axes and no scale library, and this way
   the bars inherit the theme's colours in both schemes without being told about either. */
.bars {
  display: flex; align-items: flex-end; gap: 6px;
  height: 150px; padding: 8px 0 0;
}
.bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; }
.bar-stack {
  width: 100%; max-width: 34px; margin-top: auto;
  display: flex; flex-direction: column-reverse;
  border-radius: 5px; overflow: hidden; min-height: 2px;
  background: var(--hairline);
  transition: opacity .12s ease;
}
.bar-col:hover .bar-stack { opacity: .82; }
.bar-pass { background: var(--good); width: 100%; }
.bar-fail { background: var(--critical); width: 100%; }
.bar-label { font-size: 10.5px; color: var(--muted); white-space: nowrap; }

.bars-key { display: flex; gap: 16px; margin-top: 12px; font-size: 12px; color: var(--muted); }
.bars-key span { display: inline-flex; align-items: center; gap: 6px; }
.key-swatch { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.key-pass { background: var(--good); }
.key-fail { background: var(--critical); }

/* ---- label/value list ---- */
.kv-list { list-style: none; margin: 0; padding: 0; }
.kv-list li {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--hairline);
  font-size: 13.5px; color: var(--body);
}
.kv-list li:last-child { border-bottom: 0; }
.kv-list b { color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums; }

/* ---- tables ---- */
.admin-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.admin-table th {
  text-align: left; font-size: 11.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
  padding: 0 10px 9px; border-bottom: 1px solid var(--hairline); white-space: nowrap;
}
.admin-table td { padding: 11px 10px; border-bottom: 1px solid var(--hairline); color: var(--body); vertical-align: middle; }
.admin-table tr:last-child td { border-bottom: 0; }
.admin-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.admin-table th.num { text-align: right; }
.admin-table .strong { color: var(--ink); font-weight: 600; }
.admin-table tbody tr:hover td { background: var(--surface); }
.admin-table .acts { text-align: right; white-space: nowrap; }
.admin-table .acts .btn { margin-left: 2px; }

/* A deactivated account still has to be readable — it is dimmed, not hidden, because it is
   exactly the row an administrator goes looking for when someone cannot sign in. */
.users-table .row-off td { opacity: .58; }

.user-cell { display: flex; align-items: center; gap: 11px; min-width: 0; }
.avatar {
  width: 34px; height: 34px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--grad-brand); color: #fff;
  font-size: 12px; font-weight: 700; letter-spacing: .02em;
}

.pill-admin { background: var(--info-bg); color: var(--info); border-color: transparent; }
.pill-admin .ic { width: 12px; height: 12px; margin-right: 3px; vertical-align: -1px; }
.pill-good { background: var(--good-bg); color: var(--good); border-color: transparent; }
.pill-bad { background: var(--critical-bg); color: var(--critical); border-color: transparent; }
.pill-warn { background: var(--warning-bg); color: var(--warning); border-color: transparent; }

/* ---- callouts ---- */
.callout-good { border-color: color-mix(in srgb, var(--good) 34%, transparent); background: var(--good-bg); }
.callout-bad { border-color: color-mix(in srgb, var(--critical) 34%, transparent); background: var(--critical-bg); }

/* ---- the page shown to a signed-in non-administrator ---- */
.admin-denied { min-height: 70vh; display: grid; place-items: center; padding: 32px; }
.admin-denied-card {
  max-width: 440px; text-align: center;
  background: var(--canvas); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); padding: 40px 36px; box-shadow: var(--shadow);
}
.admin-denied-card .ic-lg { color: var(--muted); width: 40px; height: 40px; }
.admin-denied-card h1 { font-size: 20px; margin: 14px 0 8px; color: var(--ink); }
.admin-denied-card p { color: var(--body); font-size: 14px; line-height: 1.6; margin-bottom: 22px; }

@media (max-width: 720px) {
  .admin-shell { padding: 20px 16px 48px; }
  .bar-label { display: none; }
}

/* The counterpart to .login-error: the same shape and the same construction, said in the
   affirmative. Built from the same tokens rather than from fixed colours, so it follows the theme
   the way its sibling does - a first attempt here hard-coded pale green text for a dark card and
   was unreadable on the light one the sign-in pages actually use. */
.login-note {
  display: flex; gap: 9px; align-items: flex-start;
  background: var(--good-bg); color: color-mix(in srgb, var(--good) 74%, var(--ink));
  border: 1px solid color-mix(in srgb, var(--good) 26%, transparent);
  border-radius: var(--radius-sm); padding: 10px 12px; margin-bottom: 18px;
  font-size: 12.5px; line-height: 1.55;
}
.login-note .ic { width: 15px; height: 15px; flex: none; margin-top: 1px; }
.login-note b { color: var(--ink); }
