/*
 * Simple QA experience — deliberately NOT styles.css.
 *
 * styles.css is a dense operator cockpit; this screen is for someone non-technical who wants one
 * answer. Separate stylesheet so neither has to compromise: calm surface, one accent, generous
 * whitespace, no tables of counts. Light-first, with a dark-scheme pass since it is nearly free.
 */

:root {
  --bg: #fbfbfa;
  --surface: #ffffff;
  --line: #e7e5e1;
  --line-soft: #f0eeea;
  --ink: #1c1b19;
  --ink-2: #55524d;
  --ink-3: #8b8781;
  --accent: #4e008e;
  --accent-strong: #3b006c;
  --accent-soft: #f4ecfc;
  --accent-contrast: #ffffff;
  --brand: #4e008e;
  --crit: #c8372d;
  --high: #d9722c;
  --med: #b8901c;
  --low: #7c7a75;
  --ok: #1f7a4d;
  --radius: 14px;
  /* One shell width for the header, every screen and the footer — the results screen used to break
     out to 1440px and visibly overhang the header rules. 1280 is the middle ground: the issue cards
     keep ~800px beside the 420px shot panel, and nothing is wider than the header. */
  --shell: 1280px;
  --shadow: 0 1px 2px rgba(28, 27, 25, 0.05), 0 6px 20px rgba(28, 27, 25, 0.05);
  /*
   * Two sticky panels sit side by side on the results screen, and their heads must land on ONE
   * line — at page top AND while pinned. That only holds if both pin at the SAME offset (a
   * static top padding can align one state or the other, never both). So both use --shot-pin,
   * and the filter block masks the strip between the header and that line while it is stuck.
   */
  --header-h: 58px; /* bottom of the sticky site header */
  --shot-pin: 96px; /* the line both sticky column heads pin to */
  --shot-tail: 152px; /* page bottom padding + footer: the run-out the shot panel must clear */
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16171a;
    --surface: #1e2024;
    --line: #2d3036;
    --line-soft: #26282d;
    --ink: #ecebe8;
    --ink-2: #b3b0aa;
    --ink-3: #857f78;
    --accent: #b489e8;
    --accent-strong: #c9a6f0;
    --accent-soft: #2d1d44;
    --accent-contrast: #1c1029;
    --brand: #cbb2e8;
    --crit: #f0776c;
    --high: #eda06a;
    --med: #dcbc5a;
    --low: #9a968f;
    --ok: #5cbd8b;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 20px rgba(0, 0, 0, 0.25);
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Inter, sans-serif;
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent);
}

/* An ambient accent wash behind the top of the page — depth without decoration.
   Fixed and non-interactive, so it never affects layout or hit-testing. */
body::before {
  content: "";
  position: fixed;
  inset: 0 0 auto;
  height: 420px;
  pointer-events: none;
  z-index: -1;
  background:
    radial-gradient(120% 100% at 12% -20%, color-mix(in srgb, var(--accent) 11%, transparent), transparent 62%),
    radial-gradient(90% 90% at 92% -30%, color-mix(in srgb, var(--accent) 7%, transparent), transparent 60%);
}

/* ── Frame ───────────────────────────────────────────────────────────────── */
.top {
  position: sticky;
  top: 0;
  /*
   * Above ALL page content — including the entry head, which raises itself to 30 so its
   * quick-audit popover out-stacks the sidebar. Scrolling used to slide that head over the
   * header. Still below every overlay (modal 50, drawer 60, lightbox 70), which are meant to
   * cover the header.
   */
  z-index: 40;
  /* 24px matches .main / .foot so the brand, the page content and the footer share one left edge
     at every width — not just above the shell width. */
  padding: 18px 24px;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: saturate(140%) blur(12px);
  border-bottom: 1px solid var(--line-soft);
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  text-decoration: none;
  color: var(--brand);
}
.brand-logo {
  height: 22px;
  width: auto;
  display: block;
}
.brand-divider {
  width: 1px;
  height: 20px;
  background: var(--line);
}
.brand-name {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
/* The reading column for every screen; the entry screen alone widens to hold its sidebar. */
/* +48px so the CONTENT box is exactly --shell wide (box-sizing is border-box), which puts page
   content on the same left/right edge as the header's brand and the footer. */
.main {
  max-width: calc(var(--shell) + 48px);
  margin: 0 auto;
  padding: 40px 24px 40px;
}
/*
 * Two tiers, one frame. Screens that are a single column of prose + inputs (project run, project
 * configuration) keep a form measure — 1280px-wide text fields and 70ch paragraphs would be worse,
 * not better. Screens with a real side-by-side layout (entry, results, agents) take the full shell.
 *
 * The measure is LEFT-ALIGNED to the shell rather than centred, which is what stops the width
 * change from reading as a jump: the brand, the back link and every H1 sit on the same vertical
 * line on every screen, and only the right edge of a form column comes in.
 */
.screen {
  max-width: 880px;
  margin: 0;
}
.screen.entry,
.screen.agents-screen,
.screen.matrix-screen,
.screen.progress,
.screen.results,
.screen.project-screen {
  max-width: none;
}
.foot {
  max-width: calc(var(--shell) + 48px);
  margin: 0 auto;
  padding: 28px 24px 56px;
  display: flex;
  justify-content: space-between;
  gap: 16px;
  font-size: 12.5px;
  color: var(--ink-3);
  border-top: 1px solid var(--line-soft);
}
.foot a {
  color: var(--ink-3);
  text-decoration: none;
}
.foot-brand {
  letter-spacing: 0.06em;
}
/* Two things on the left, one flex child — so the brand stays in the middle of the footer. */
.foot-left {
  display: flex;
  gap: 12px;
  min-width: 0;
}
/* The build line is reference material: legible when looked for, never competing with the page. */
.foot-build {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.foot a:hover {
  color: var(--accent);
}
[hidden] {
  display: none !important;
}

/* ── Screen 1: entry ──────────────────────────────────────────────────────
   Two columns: the things you DO on the left (quick audit first, then the
   projects you own), the things that already happened parked in a quiet
   sidebar on the right. History is reference material, not a call to action. */
.entry {
  padding-top: 4vh;
}
/* Headline + the highlighted "New project" button, side by side at the very top. */
.entry-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  padding-bottom: 30px;
  border-bottom: 1px solid var(--line-soft);
  /*
   * The head must out-stack the grid below it, or the quick-audit popover opens UNDERNEATH the
   * Recent-audits panel. Both carry the `rise-in` entrance, and an animated transform creates a
   * stacking context — so the popover's own z-index is scoped inside this element and DOM order
   * would otherwise let the later sidebar win. Deliberately BELOW the sticky header (40): this
   * lifts the whole head, not just the popover, and at 30-over-20 the h1 and buttons slid over
   * the header on scroll. A popover clipped by the header while scrolling is the lesser evil —
   * and it closes on scroll anyway.
   */
  position: relative;
  z-index: 30;
}
.entry-head-text {
  max-width: 58ch;
}
.entry-head-text .lede {
  margin-bottom: 0;
}
.eyebrow {
  margin: 0 0 10px;
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
/* The head's action pair: quick audit (secondary) beside New project (primary). */
.entry-actions {
  flex: 0 0 auto;
  margin-top: 26px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.entry-actions .go {
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.quick-wrap {
  position: relative;
}
.quick-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 14px 18px;
  font-size: 15px;
  border-radius: var(--radius);
  white-space: nowrap;
}
.quick-toggle .bolt {
  font-size: 14px;
  line-height: 1;
}
.quick-toggle[aria-expanded="true"] {
  border-color: var(--accent);
  color: var(--accent);
}
/* Anchored under the toggle; right-aligned so it never runs off the viewport edge. */
.quick-pop {
  position: absolute;
  z-index: 30;
  top: calc(100% + 10px);
  right: 0;
  width: min(420px, calc(100vw - 48px));
  padding: 20px 22px 22px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: var(--shadow);
  text-align: left;
}
.quick-pop .entry-form {
  margin-top: 4px;
}
.quick-pop .url-input {
  flex: 1 1 100%;
  font-size: 15px;
  padding: 12px 14px;
  box-shadow: none;
}
.quick-pop .go {
  width: 100%;
  padding: 12px 18px;
  font-size: 15px;
}
@media (prefers-reduced-motion: no-preference) {
  .quick-pop {
    animation: rise-in 0.16s ease both;
  }
}
.go-plus {
  font-size: 18px;
  line-height: 1;
  font-weight: 500;
  opacity: 0.85;
}
.entry-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 44px;
  align-items: start;
  margin-top: 36px;
}
.entry-main {
  min-width: 0;
}
.entry-side {
  min-width: 0;
  position: sticky;
  top: 96px;
}
@media (max-width: 900px) {
  .entry-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 40px;
  }
  .entry-side {
    position: static;
  }
}

/* Quick audit — the one action that needs no setup, so it gets the raised card. */
.panel-raised {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 24px 26px 26px;
  box-shadow: var(--shadow);
}
/* Hairline accent along the top edge: presence without a coloured block. */
.panel-raised::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  border-radius: 18px 18px 0 0;
  background: linear-gradient(90deg, var(--accent), transparent 70%);
  opacity: 0.7;
}
.quick-head {
  display: flex;
  align-items: center;
  gap: 12px;
}
.quick-pop h2 {
  font-size: 12.5px;
  font-weight: 650;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin: 0;
}
.quick-tag {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 99px;
  background: var(--accent-soft);
  color: var(--accent);
}
.quick-pop .projects-lede {
  margin: 8px 0 16px;
}
h1 {
  font-size: 38px;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 12px;
  font-weight: 640;
}
.lede {
  margin: 0 0 28px;
  color: var(--ink-2);
  font-size: 17px;
  max-width: 46ch;
}
.entry-form {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.url-input {
  flex: 1 1 340px;
  min-width: 0;
  padding: 15px 18px;
  font-size: 17px;
  font-family: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.url-input::placeholder {
  color: var(--ink-3);
}
.url-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.go {
  padding: 15px 24px;
  font-size: 16px;
  font-weight: 600;
  font-family: inherit;
  color: var(--accent-contrast);
  background: var(--accent);
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
}
.go:hover {
  filter: brightness(1.06);
}
.go[disabled] {
  opacity: 0.5;
  cursor: default;
}
.entry-error {
  margin: 14px 0 0;
  color: var(--crit);
  font-size: 14.5px;
}

.recent {
  margin-top: 56px;
}
.recent h2,
.page-view-head h2 {
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 12px;
}
.recent-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Two lines: what was audited, then how it went and when. Reads at sidebar width. */
.recent-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  text-align: left;
  padding: 11px 10px 11px 8px;
  background: none;
  border: none;
  border-radius: 10px;
  font: inherit;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease;
}
.recent-item:hover {
  background: var(--line-soft);
}
.recent-item:hover .recent-page {
  color: var(--accent);
}
.recent-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  margin-top: 7px;
  border-radius: 99px;
  background: var(--low);
}
.recent-dot[data-tone="ok"] {
  background: var(--ok);
}
.recent-dot[data-tone="warn"] {
  background: var(--med);
}
.recent-dot[data-tone="bad"] {
  background: var(--crit);
}
.recent-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.recent-page {
  font-size: 14.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.recent-meta {
  font-size: 12.5px;
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Sidebar variant of the same list: a quiet panel, not a headline surface. */
.side-panel {
  margin-top: 0;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  padding: 18px 12px 14px;
}
.side-panel h2 {
  margin: 0 0 8px;
  padding: 0 8px;
}
.side-panel .recent-list {
  max-height: min(60vh, 520px);
  overflow-y: auto;
}

/* ── Screen 2: progress ───────────────────────────────────────────────────
   Mission-control layout: header with a live clock, a horizontal journey rail, then the work
   split into a main column (detection + agent bench) and a live activity feed on the right. */
.prog-head {
  margin-bottom: 26px;
}
.prog-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
}
.progress h1 {
  font-size: 26px;
  margin: 6px 0 0;
  word-break: break-word;
}
.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 99px;
  background: var(--accent);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent);
  animation: live-ping 2s ease-out infinite;
}
@keyframes live-ping {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  70% { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.prog-elapsed {
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: none;
}
.prog-elapsed:not(:empty)::before {
  content: "· ";
}
/* The journey rail: five nodes joined by a line; the line up to a node fills as the run reaches
   it, so the whole thing reads as one pipeline flowing left to right. */
.stages {
  list-style: none;
  margin: 0 0 34px;
  padding: 22px 18px 16px;
  display: flex;
  align-items: flex-start;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.stage {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 7px;
  padding: 0 8px;
  color: var(--ink-3);
}
/* Connector from the previous node: sits at the dot row, spans the gap between node centres. */
.stage + .stage::before {
  content: "";
  position: absolute;
  top: 10px;
  left: calc(-50% + 19px);
  right: calc(50% + 19px);
  height: 2px;
  border-radius: 2px;
  background: var(--line);
}
.stage.active::before,
.stage.done::before {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 45%, var(--line)), var(--accent));
}
.stage-dot {
  position: relative;
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
}
.stage-dot-core {
  position: absolute;
  inset: 4px;
  border-radius: 99px;
  background: var(--line);
  color: var(--accent-contrast);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  transition: background 0.3s;
}
.stage.active .stage-dot-core {
  inset: 3px;
  background: linear-gradient(135deg, var(--accent), var(--accent-strong));
}
.stage.active .stage-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 99px;
  border: 2px solid var(--accent);
  animation: agent-pulse 1.5s ease-out infinite;
}
.stage.done .stage-dot-core {
  background: var(--accent);
}
.stage-label {
  font-size: 13px;
  line-height: 1.35;
  font-weight: 500;
}
.stage.active .stage-label {
  color: var(--ink);
  font-weight: 600;
}
.stage.done .stage-label {
  color: var(--ink-2);
}
@keyframes pulse {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}
.stage-note {
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--ink-3);
  min-height: 0;
}
.stage-note:empty {
  display: none;
}
/* Main work on the left, the live feed on the right; one column under 1100px. */
.prog-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: 28px;
  align-items: start;
}
@media (max-width: 1100px) {
  .prog-grid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 740px) {
  .stages {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
    padding: 18px 16px;
  }
  .stage {
    flex-direction: row;
    text-align: left;
    align-items: flex-start;
    padding: 0;
    gap: 12px;
  }
  .stage + .stage::before {
    top: -14px;
    left: 10px;
    right: auto;
    width: 2px;
    height: 14px;
  }
  .stage-label {
    flex: 0 0 auto;
  }
  .stage-note {
    flex: 1;
    text-align: right;
  }
}
/* ── The live activity feed ──────────────────────────────────────────────
   Every line is a real event off the run's SSE stream; the typing row at the tail is the only
   element that isn't an event, and it only says which stage the backend last reported. */
.prog-feed {
  position: sticky;
  top: 86px;
}
.feed-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.live-badge {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 99px;
  color: var(--accent);
  background: var(--accent-soft);
}
.live-badge.live::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 99px;
  background: currentColor;
  margin-right: 6px;
  vertical-align: 1px;
  animation: pulse 1.4s ease-in-out infinite;
}
.live-badge.done {
  color: var(--ok);
  background: color-mix(in srgb, var(--ok) 12%, transparent);
}
.live-badge.err {
  color: var(--crit);
  background: color-mix(in srgb, var(--crit) 12%, transparent);
}
.feed {
  list-style: none;
  margin: 0;
  padding: 6px 2px;
  max-height: min(520px, 62vh);
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  /* New lines arrive at the bottom; the top fades out instead of cutting off. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 12px);
  mask-image: linear-gradient(180deg, transparent 0, #000 12px);
}
.feed-item {
  display: flex;
  align-items: baseline;
  gap: 9px;
  padding: 6px 14px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-2);
  animation: feed-in 0.35s ease both;
}
@keyframes feed-in {
  from {
    opacity: 0;
    transform: translateY(5px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.feed-dot {
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-radius: 99px;
  background: var(--line);
  align-self: center;
}
.feed-item.agent .feed-dot {
  background: var(--accent);
}
.feed-item.agent .feed-text {
  color: var(--ink);
}
.feed-item.flag .feed-dot {
  background: var(--med);
}
.feed-item.err .feed-dot {
  background: var(--crit);
}
.feed-item.err .feed-text {
  color: var(--crit);
}
.feed-time {
  flex: 0 0 auto;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 10.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.feed-text {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The typing row: three dots breathing while the backend works between events. */
.feed-item.typing {
  color: var(--ink-3);
  font-style: italic;
}
.feed-dots {
  flex: 0 0 auto;
  display: inline-flex;
  gap: 3px;
  align-self: center;
}
.feed-dots i {
  width: 5px;
  height: 5px;
  border-radius: 99px;
  background: var(--accent);
  animation: feed-dot-bounce 1.2s ease-in-out infinite;
}
.feed-dots i:nth-child(2) {
  animation-delay: 0.15s;
}
.feed-dots i:nth-child(3) {
  animation-delay: 0.3s;
}
@keyframes feed-dot-bounce {
  0%,
  60%,
  100% {
    transform: none;
    opacity: 0.45;
  }
  30% {
    transform: translateY(-3px);
    opacity: 1;
  }
}
.log-details {
  margin-top: 12px;
  font-size: 13px;
  color: var(--ink-3);
}
.log-details summary {
  cursor: pointer;
}
.log {
  max-height: 240px;
  overflow: auto;
  margin: 12px 0 0;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 11.5px;
  line-height: 1.6;
  white-space: pre-wrap;
  color: var(--ink-2);
}
.link-btn {
  margin-top: 20px;
  padding: 0;
  background: none;
  border: none;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
}

/* ── Screen 3: results ────────────────────────────────────────────────────
   Same wide container as the landing screen, split into the page on the left
   (sticky, so the numbered pins stay in view) and its issues on the right. The
   prose above the split keeps a reading measure — only the panels go wide. */
/*
 * This screen uses the FULL shell rather than the 780px reading column — the screenshot keeps its
 * fixed 420px panel and every remaining pixel goes to the ISSUE CARDS, which are the work surface.
 * It deliberately stops at the shell width: it used to break out to 1440px and overhang the header
 * and footer rules, which read as a broken layout on a wide monitor.
 */
.screen.results {
  max-width: none;
}
/* The cards get the room (they are the work surface); the screenshot keeps a compact fixed
   panel that houses desktop AND phone captures comfortably — the phone one centers on the
   panel's letterbox backdrop instead of shrinking the panel around itself. */
.results-grid {
  display: grid;
  /* shot panel first in the DOM = first column, fixed and compact; the cards take the rest */
  grid-template-columns: 420px minmax(0, 1fr);
  gap: 36px;
  align-items: start;
}
/*
 * The panel is pinned inside the GRID's area, so its pin breaks against the grid's bottom edge in
 * the last stretch of scroll — and every card expansion lengthens the grid, which re-pins it. The
 * screenshot then jumped up and down as cards opened and closed. It never breaks if the panel
 * fits between its pin and the page's run-out below the grid, so the panel is capped at exactly
 * that, and the frame inside takes whatever the head and hint leave it.
 */
/*
 * …and it is also capped to the CARD COLUMN beside it (--shot-fit, measured in JS). Without that
 * cap a run with one or two cards made this column — up to a whole viewport tall — the height of
 * the grid row, leaving a screen-high empty stretch under the last card before anything that
 * follows the grid. CSS cannot ask how tall the other column is, so a ResizeObserver reports it;
 * the floor keeps a short card list from shrinking the screenshot to a stamp.
 */
.results-shot {
  position: sticky;
  top: var(--shot-pin);
  min-width: 0;
  display: flex;
  flex-direction: column;
  max-height: min(calc(100vh - var(--shot-pin) - var(--shot-tail)), max(360px, var(--shot-fit, 100vh)));
}
.results-shot .page-view {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  margin-bottom: 0;
}
/* Sticky: the frame takes the height it can actually keep on screen. */
.results-shot .page-shot {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
}
.results-issues {
  min-width: 0;
}
/* The persistent screenshot carries the locating job, so each card's own crop can be smaller. */
@media (min-width: 1041px) {
  .results-issues .card-main {
    grid-template-columns: 150px 1fr;
    gap: 16px;
  }
}
@media (max-width: 1040px) {
  .results-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 32px;
  }
  /* Stacked, the panel is not pinned — nothing to clear, so it takes its natural height. */
  .results-shot {
    position: static;
    display: block;
    max-height: none;
  }
  .results-shot .page-view {
    display: block;
  }
  .results-shot .page-shot {
    flex: none;
    max-height: 520px;
  }
}
/* Breadcrumb above the run header — quiet until hovered, so it never competes with the verdict. */
.crumb-back {
  display: inline-block;
  margin: 0 0 14px;
  padding: 0;
  background: none;
  border: none;
  font: inherit;
  font-size: 13.5px;
  color: var(--ink-3);
  cursor: pointer;
}
.crumb-back:hover {
  color: var(--accent);
}
.results-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 26px;
}
.result-target {
  display: block;
  font-size: 14px;
  font-weight: 600;
  word-break: break-all;
}
.result-when {
  font-size: 13px;
  color: var(--ink-3);
}
.results-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
/* "Run again" plus an optional caret for the suites this run skipped. Quiet by design: the pair
   reads as ONE ghost control (shared outline, the seam a single hairline), and with no extra option
   the caret is absent and the button looks exactly as it always did. */
.rerun {
  position: relative;
  display: inline-flex;
}
.rerun.split #rerun-btn {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
/* `.rerun .rerun-more`, not `.rerun-more`: `.ghost` sets the pill radius further down the file and
   would win a specificity tie on source order, leaving the caret a separate lozenge. */
.rerun .rerun-more {
  margin-left: -1px;
  padding: 8px 9px;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  line-height: 1;
}
.rerun .rerun-more[aria-expanded="true"] {
  border-color: var(--accent);
  color: var(--accent);
}
.rerun-menu {
  position: absolute;
  z-index: 30;
  top: calc(100% + 6px);
  left: 0;
  min-width: max(100%, 240px);
  padding: 6px;
  display: grid;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow);
}
.rerun-item {
  padding: 8px 10px;
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  color: var(--ink-2);
  background: none;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
}
.rerun-item:hover {
  color: var(--accent);
  background: var(--accent-soft);
}
/* The download menu hangs off the LAST control in the row, so it is anchored to its right edge at
   every width — left-anchored it runs off the screen the moment the shell is not very wide. */
#download-menu {
  left: auto;
  right: 0;
  min-width: min(260px, calc(100vw - 40px));
  white-space: normal;
}
/* A download choice is two lines: what it is, and what it will cost to fetch. */
a.rerun-item {
  display: block;
  text-decoration: none;
}
.rerun-sub {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  color: var(--ink-3);
  white-space: normal;
}
.rerun-item.muted {
  display: block;
  color: var(--ink-2);
  cursor: default;
}
/* Below the results head's own wrap point the menu can overhang the viewport edge — anchor it to
   the right instead, where there is always room. */
@media (max-width: 560px) {
  .rerun-menu {
    left: auto;
    right: 0;
    min-width: min(280px, calc(100vw - 40px));
    white-space: normal;
  }
  .rerun-item {
    white-space: normal;
  }
}
.ghost {
  padding: 8px 14px;
  font: inherit;
  font-size: 13.5px;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  text-decoration: none;
  cursor: pointer;
}
.ghost:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
}
.ghost.danger {
  color: var(--crit);
  border-color: var(--crit);
}
.ghost.danger:hover:not(:disabled),
.ghost.danger[data-armed="1"] {
  color: #fff;
  background: var(--crit);
  border-color: var(--crit);
}
/* A locked action has to LOOK locked. The component audit's "Run audit" stays disabled until its
   plan has been previewed, and a button that reads as clickable would make that gate feel broken
   rather than deliberate — the button's own title says what unlocks it. */
.ghost:disabled {
  color: var(--ink-3);
  background: var(--line-soft);
  border-color: var(--line-soft);
  cursor: not-allowed;
}
.headline-list {
  margin: 0 0 16px;
  padding-left: 20px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 62ch;
}
.headline-list li {
  margin: 0 0 6px;
}
/* Quiet credit line under the summary — real agent counts only, set server-side. */
.ai-byline {
  margin: 0 0 16px;
  font-size: 13px;
  color: var(--ink-3);
}
/* ── Run scorecard: the numbers first, prose second ─────────────────────── */
.scorecard {
  margin: 0 0 18px;
}
.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 14px;
}
.stat {
  min-width: 104px;
  padding: 10px 16px 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.stat b {
  display: block;
  font-size: 26px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.stat span {
  display: block;
  font-size: 12.5px;
  color: var(--ink-3);
}
/* Status tones on the NUMBER only — the tile stays quiet. */
.stat.bad b {
  color: var(--crit);
}
.stat.good b {
  color: var(--ok);
}
/* Severity strip: one thin stacked bar, 2px surface gaps between segments, counts labeled
   beside it — never color alone. */
.sev-strip {
  display: flex;
  gap: 2px;
  height: 8px;
  max-width: 560px;
}
.sev-strip span {
  display: block;
  border-radius: 2px;
  min-width: 8px;
}
.sev-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin-top: 8px;
  font-size: 12.5px;
  color: var(--ink-2);
}
.sev-legend i {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 6px;
}
/* The lead analyst's full prose, folded away for whoever wants the reasoning. */
.summary-details {
  margin: 0 0 30px;
  max-width: 68ch;
}
.summary-details summary {
  cursor: pointer;
  font-size: 13.5px;
  color: var(--ink-3);
}
.summary-details summary:hover {
  color: var(--accent);
}
.summary-details[open] summary {
  margin-bottom: 8px;
}
.summary-details .headline-list {
  margin-bottom: 0;
}

/* ── The sticky filter block above the cards ─────────────────────────────────
   Lens tabs on top, category pills beneath — both live where their effect is felt (the card
   list) and stay reachable while it scrolls. Frosted backdrop so cards slide under it. */
/* Zero-height probe just above the block; the IntersectionObserver in simple.js watches it to
   know when the block is stuck (CSS alone cannot express that state). */
.filters-sentinel {
  height: 0;
}
.filters-sticky {
  position: sticky;
  /* Same line the screenshot panel pins to, so the two column heads agree in BOTH states. */
  top: var(--shot-pin);
  z-index: 15; /* above cards; far below the drawer (60) and lightbox (70) */
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(10px);
  padding: 0 0 4px;
  margin-bottom: 8px;
}
/*
 * The strip between the header and the pin line. Cards scroll through it, so while the block is
 * stuck this masks them with the block's own frosted surface — which is also what reads as air
 * above the tabs. Absolutely positioned, so it never changes the block's height: sticking cannot
 * nudge the cards below by a single pixel.
 */
.filters-sticky::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: calc(var(--shot-pin) - var(--header-h));
  background: inherit;
  backdrop-filter: blur(10px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
}
.filters-sticky.stuck::before {
  opacity: 1;
  visibility: visible;
}
/* ── Lens tabs: what the page was judged against ─────────────────────────────
   Underline tabs, deliberately a DIFFERENT visual language from the small category pills
   beneath: the lens is the primary question, the pills only refine within it. */
.lens-tabs {
  display: flex;
  align-items: baseline;
  gap: 2px;
  margin: 0 0 12px;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.lens-tabs::-webkit-scrollbar {
  display: none;
}
.lens-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-right: 14px;
  white-space: nowrap;
  align-self: center;
}
.lens-tab {
  padding: 8px 14px 11px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink-3);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px; /* the active underline sits ON the bar's hairline */
  cursor: pointer;
  white-space: nowrap;
}
.lens-tab:hover {
  color: var(--accent);
}
.lens-tab .n {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  margin-left: 3px;
}
.lens-tab.on {
  color: var(--ink);
  border-bottom-color: var(--accent);
}
.lens-tab.on .n {
  color: var(--accent);
}
/* Why the tab counts can overlap — shown only when at least one card genuinely spans two
   comparisons. Pushed to the row's far end, out of the tabs' reading line. */
.lens-note {
  margin-left: auto;
  padding-left: 16px;
  align-self: center;
  font-size: 11.5px;
  color: var(--ink-3);
  white-space: nowrap;
  cursor: help;
}
@media (max-width: 740px) {
  .lens-note {
    display: none; /* the scrolling tab row is no place for a footnote on a phone */
  }
}
/* The lens chip on a card: which comparison produced this — outlined accent, so it reads as a
   provenance tag rather than a category. */
.chip.lens {
  color: var(--accent);
  background: none;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  letter-spacing: 0.04em;
}

/* ── Agent filter above the cards ───────────────────────────────────────── */
.agent-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 16px;
}
.agent-btn {
  padding: 6px 12px;
  font: inherit;
  font-size: 13px;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 99px;
  cursor: pointer;
}
.agent-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.agent-btn .n {
  font-size: 11.5px;
  color: var(--ink-3);
}
.agent-btn.on {
  /* --bg, not #fff: in dark mode --ink is near-white and white-on-white would vanish. */
  color: var(--bg);
  background: var(--ink);
  border-color: var(--ink);
}
.agent-btn.on .n {
  color: var(--bg);
  opacity: 0.7;
}

/* Annotated full-page screenshot */
.page-view {
  margin-bottom: 40px;
}
.page-view-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
}
/*
 * A multi-page run adds a page switcher beside the viewport chips, and title + both controls on one
 * line collapsed the h2 into a vertical word-stack in a 420px column. With more than one page the
 * head becomes two rows — title, then controls — and the sticky panel's frame absorbs the extra
 * height (see .results-shot .page-shot: flex 1 / min-height 0). A single-page run never gets the
 * class, so its head is byte-for-byte the one-line head it always was.
 */
.page-view-head.has-pages {
  display: block;
}
.page-view-head.has-pages h2 {
  margin-bottom: 8px;
}
.page-view-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.page-view-head.has-pages .page-view-controls {
  margin-bottom: 12px;
}
/* Takes the row's spare width and shrinks below its content (min-width:0) so a long route is
   ellipsized by the control instead of widening the panel column. */
.page-select {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 100%;
  padding: 4px 8px;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}
.page-select:hover {
  color: var(--ink);
  border-color: var(--ink-3);
}
.page-select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.viewport-tabs {
  display: flex;
  /* Shrinkable and wrappable: a run with many renders must stay inside the panel column rather
     than running under the cards (it becomes a select past a handful, but never overflow). */
  flex: 0 1 auto;
  min-width: 0;
  flex-wrap: wrap;
  gap: 6px;
}
.viewport-tabs .page-select {
  width: 100%;
}
.vp-tab {
  padding: 5px 11px;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink-2);
  background: none;
  border: 1px solid var(--line);
  border-radius: 99px;
  cursor: pointer;
}
.vp-tab.on {
  /* --bg, not #fff: in dark mode --ink is near-white and white-on-white would vanish. */
  color: var(--bg);
  background: var(--ink);
  border-color: var(--ink);
}
.page-shot {
  position: relative;
  overflow: auto; /* long pages scroll inside the frame; the marks scroll with the image */
  max-height: 520px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.page-hint {
  margin: 10px 0 0;
  font-size: 13px;
  color: var(--ink-3);
}

/* The panel keeps its full column width for every viewport; a capture narrower than the panel
   (the phone shot) centers on this quiet striped backdrop. The desktop frame covers the panel
   completely, so the stripes only ever show beside a letterboxed phone capture. */
#page-shot {
  background:
    repeating-linear-gradient(-45deg, transparent 0 10px, rgba(127, 127, 127, 0.06) 10px 20px),
    var(--surface);
}
#page-shot .shot-frame {
  box-shadow: 0 0 0 1px var(--line);
}

/* The overlay primitives: a relatively-positioned frame, percentage-positioned marks. */
.shot-frame {
  position: relative;
  display: block;
  line-height: 0;
  margin: 0 auto; /* centers a frame narrower than its panel (phone letterbox) */
}
.shot-frame img {
  display: block;
  width: 100%;
  height: auto;
}
/*
 * A capture wider than its breakpoint (fullPage spans the document's scroll width, so a page with
 * horizontal overflow brings an empty canvas strip with it): the frame is a window on the page.
 * `aspect-ratio` (inline, from the visible page's dimensions) gives it the right shape, and the
 * scaled-up image is taken out of flow so its height matches the frame exactly — cropped, not
 * squashed. Applies to the OLD stored runs too; new captures are already clipped at the source.
 */
.shot-frame.cropped {
  overflow: hidden;
}
.shot-frame.cropped img {
  position: absolute;
  top: 0;
  left: 0;
}
.mark-box {
  position: absolute;
  border: 2px solid var(--mark, var(--accent));
  border-radius: 3px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0);
  pointer-events: none;
}
/* On the full screenshot a pin is a LINK to its card — clickable boundary, badge included.
   Card thumbnails keep pointer-events off (the whole thumb is already a button). */
#page-shot .mark-box {
  pointer-events: auto;
  cursor: pointer;
}
#page-shot .mark-box:hover {
  border-width: 3px;
}
#page-shot .mark-box:hover::after {
  transform: scale(1.15);
}
/*
 * The number hangs just outside the box so it never covers the thing it points at — except for
 * elements flush against the top/left of the page (headers, skip links), where it would be clipped;
 * those get `.inset-y` / `.inset-x` and the badge tucks inside instead.
 */
.mark-box::after {
  content: attr(data-n);
  position: absolute;
  top: -11px;
  left: -11px;
  min-width: 21px;
  height: 21px;
  padding: 0 5px;
  border-radius: 99px;
  background: var(--mark, var(--accent));
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 21px;
  text-align: center;
}
/* Full-page-shot marks carry NO ::after badge — their badges are standalone .mark-badge
   elements, so findings sharing one element get a row of side-by-side numbers instead of a
   stack where only the top one is visible. Crops (thumbnails) keep the pseudo-element badge. */
.mark-box.no-badge::after {
  display: none;
}
.mark-badge {
  position: absolute;
  min-width: 21px;
  height: 21px;
  padding: 0 5px;
  border-radius: 99px;
  background: var(--mark, var(--accent));
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 21px;
  text-align: center;
  z-index: 999; /* above every mark box — badges are the primary click targets */
  pointer-events: auto;
  cursor: pointer;
  transition: transform 0.12s ease;
}
.mark-badge:hover {
  transform: scale(1.15);
}
.mark-box.inset-y::after {
  top: 2px;
}
.mark-box.inset-x::after {
  left: 2px;
}
.mark-box.plain::after {
  content: none;
}

/* CSS crop: the pan is sized/offset in PERCENTAGES of the crop box, so it scales with the layout. */
.shot-crop {
  position: relative;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--line-soft);
  border-radius: 10px;
}
.shot-crop .shot-pan {
  position: absolute;
  top: 0;
  left: 0;
}
/* Page-level issue (or an old run with no stored pixel size): show the page from the top, to scale. */
.shot-crop.no-box .shot-pan {
  width: 100%;
}
.shot-crop img {
  display: block;
  width: 100%;
  height: auto;
}

/* Issue cards */
.cards {
  display: grid;
  gap: 18px;
}
/* Root-cause cards are accordions: a compact clickable head (one-line title, two lines of
   prose), a meta row, and the member findings inline when open. A left edge + real gap between
   cards keeps them clearly separate even when several are open. The edge is never severity-
   coloured (a row of red/orange/yellow spines read as a second taxonomy competing with the
   category chips) — neutral at rest, the accent when the card is open. */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.card.open {
  border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
  border-left-color: var(--accent);
}
.card.reviewed-rejected,
.card.reviewed-muted {
  opacity: 0.55;
}
/* The whole head is the toggle. */
.card-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  padding: 16px 18px 4px;
  font: inherit;
  text-align: left;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}
.card-head .pin {
  margin-top: 1px;
}
.card-head-main {
  flex: 1;
  min-width: 0;
}
.card-caret {
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--ink-3);
  font-size: 13px;
  transition: transform 0.18s ease;
}
.card.open .card-caret {
  transform: rotate(180deg);
}
/* Collapsed: title on one line — the details live inside, one click away. */
.card:not(.open) .card-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
  -webkit-line-clamp: 1;
}
/*
 * The prose is secondary: it stays clamped to two lines in BOTH card states and unrolls only via
 * its own "See more" link — opening the card reveals the FINDINGS, not a wall of explanation. The
 * text itself is not a control (a stray click while reading should do nothing). It sits outside
 * the head button (nesting a control inside a button is invalid) and aligns to the title column.
 * A <p> rather than a <button>: Chrome refuses -webkit-box on button elements, so the clamp
 * silently did nothing there.
 */
.why {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
  -webkit-line-clamp: 2;
  padding: 0 18px 0 52px;
}
.why.why-open {
  display: block;
  overflow: visible;
}
/* Quiet, in the prose's own colour — it is a disclosure, not an action. */
.why-more {
  display: none;
  margin: 2px 0 0;
  padding: 0 18px 0 52px;
  font: inherit;
  font-size: 14px;
  color: var(--ink-2);
  background: none;
  border: none;
  text-decoration: underline;
  cursor: pointer;
}
.why-more.clampable {
  display: block;
}
.why-more:hover {
  color: var(--ink);
}
.card-findings {
  border-top: 1px solid var(--line-soft);
  margin-top: 12px;
}
.card-top {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 7px;
  flex-wrap: wrap;
}
.pin {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 99px;
  background: var(--sev, var(--accent));
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 22px;
  text-align: center;
}
/* The number is the same number as the pin on the screenshot, so it LOCATES (switching the panel to
   the finding's own page when needed) instead of toggling the card it sits in. */
.pin.locates {
  cursor: pointer;
}
.pin.locates:hover {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--sev, var(--accent)) 28%, transparent);
}
.chip {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 99px;
  color: #fff;
  background: var(--sev, var(--low));
}
.chip.soft {
  color: var(--ink-2);
  background: none;
  border: 1px solid var(--line);
  letter-spacing: 0.04em;
}
/* WHERE the finding lives, on a multi-page run only. A route is not a label — it keeps its own
   casing and reads as the path it is, so it never competes with the uppercase taxonomy chips. */
.chip.soft.page {
  text-transform: none;
  letter-spacing: 0.01em;
  font-weight: 600;
  font-size: 11px;
  color: var(--ink-3);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Category chip: what KIND of bug (Content / CMS / SEO / …) — never colored by severity. */
.chip.cat {
  color: var(--accent);
  background: var(--accent-soft);
  border: none;
}
.card h3 {
  font-size: 16.5px;
  font-weight: 620;
  line-height: 1.35;
  margin: 0 0 6px;
}
.card .why {
  margin: 0;
  color: var(--ink-2);
  font-size: 14px;
  line-height: 1.55;
}
/* The element ticket's symptoms, one per line, under the title (PLAN §12 A). */
.symptoms {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-2);
  font-weight: 400;
}
.symptoms li {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.symptoms .sym-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--ink-3);
}
.card-meta {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--ink-3);
  /* Aligns under the head's text column: 18px pad + 22px pin + 12px gap. */
  padding: 8px 18px 14px 52px;
}
.expand {
  padding: 0;
  font: inherit;
  font-size: 13px;
  color: var(--accent);
  background: none;
  border: none;
  cursor: pointer;
}
.card-actions {
  display: flex;
  gap: 6px;
  padding: 10px 18px 14px;
  flex-wrap: wrap;
}
.act {
  padding: 6px 12px;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink-3);
  background: none;
  border: 1px solid var(--line-soft);
  border-radius: 99px;
  cursor: pointer;
}
.act:hover {
  color: var(--ink);
  border-color: var(--line);
}
.act.on {
  color: var(--bg);
  background: var(--ink);
  border-color: var(--ink);
}
.act-note {
  align-self: center;
  font-size: 12.5px;
  color: var(--ink-3);
}
/* "All N findings:" — says out loud that the card's buttons act on every member. */
.act-hint {
  align-self: center;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-2);
}
/* Per-finding decisions inside the drawer (CEO feedback: judge the item, not the cluster). */
.member-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 12px;
}
/* A clicked pin flashes its card so the eye lands on the right one after the scroll. */
.card.flash {
  animation: card-flash 1.6s ease-out;
}
@keyframes card-flash {
  0%,
  45% {
    box-shadow: 0 0 0 3px var(--accent);
  }
  100% {
    box-shadow: 0 0 0 3px transparent;
  }
}

/* Expanded members */
.members {
  border-top: 1px solid var(--line-soft);
  padding: 4px 18px 8px;
}
.member {
  padding: 16px 0;
  border-bottom: 1px solid var(--line-soft);
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 18px;
}
.member:last-child {
  border-bottom: none;
}
@media (max-width: 640px) {
  .member {
    grid-template-columns: 1fr;
  }
}
.member h4 {
  margin: 0 0 8px;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.4;
}
.kv {
  display: grid;
  grid-template-columns: 78px 1fr;
  gap: 3px 12px;
  font-size: 13px;
  color: var(--ink-2);
  margin: 0 0 10px;
}
.kv dt {
  color: var(--ink-3);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding-top: 2px;
}
.kv dd {
  margin: 0;
  overflow-wrap: anywhere;
}
/* A finding row carries up to six tags (category, comparison, viewport, AI verdict, seen-before,
   places) — at the card's chip size that row shouts louder than the finding's own title, so the
   member tags run one notch smaller than the card-level chips. */
.tags {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
  margin-bottom: 7px;
}
.tags .chip {
  font-size: 9.5px;
  letter-spacing: 0.05em;
  padding: 2px 7px;
}
.tags .chip-link {
  font-size: 11.5px;
}
.tech {
  font-size: 12.5px;
  color: var(--ink-3);
}
.tech summary {
  cursor: pointer;
}
.tech code {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 11.5px;
  overflow-wrap: anywhere;
}
.tech .kv {
  margin-top: 8px;
  font-size: 12.5px;
}

.empty {
  padding: 40px 18px;
  text-align: center;
  color: var(--ink-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.ai-off {
  margin: 34px 0 0;
  font-size: 13px;
  color: var(--ink-3);
}

/* ── Projects ──────────────────────────────────────────────────────────────
   The saved-target list and its configuration screen. Same calm surface as the
   rest of the simple experience: layer state is shown as quiet chips, never as
   tier numbers or check ids. */
.projects {
  margin-top: 40px;
}
.projects-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.projects h2,
.recent h2 {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 6px;
}
.projects-lede {
  margin: 4px 0 0;
  font-size: 14.5px;
  color: var(--ink-2);
  max-width: 60ch;
}
.project-list,
.layers {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
}
.project-list {
  display: grid;
  gap: 10px;
  margin-top: 14px;
}
/* Each project is a card you press: hover lifts it and reveals its accent edge. */
.project-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 16px 4px 18px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}
.project-row::before {
  content: "";
  position: absolute;
  left: 0;
  top: 14px;
  bottom: 14px;
  width: 2px;
  border-radius: 0 2px 2px 0;
  background: var(--accent);
  opacity: 0;
  transition: opacity 0.16s ease;
}
.project-row:hover {
  border-color: var(--line);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
.project-row:hover::before {
  opacity: 1;
}
.project-open {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 0;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
.project-text {
  display: grid;
  gap: 3px;
  min-width: 0;
}
/* Identity mark — the same monogram language as the agent roster, so each project is
   recognisable at a glance instead of the list reading as undifferentiated rows. */
.project-mark {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 13px;
  font-weight: 650;
  letter-spacing: 0.04em;
  transition: background 0.16s ease, color 0.16s ease;
}
.project-row:hover .project-mark {
  background: var(--accent);
  color: var(--accent-contrast);
}
.project-row:hover .project-name {
  color: var(--accent);
}
.project-name {
  font-size: 16px;
  font-weight: 500;
}
.project-host {
  font-size: 13px;
  color: var(--ink-3);
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
}
.project-layers {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}
.layer-chip {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.03em;
  padding: 3px 9px;
  border-radius: 99px;
  background: var(--accent-soft);
  color: var(--accent);
}
.layer-chip.muted {
  background: var(--line-soft);
  color: var(--ink-3);
}
.layer-chip.lock {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--ink-3);
}
.go.small {
  padding: 9px 16px;
  font-size: 14px;
  border-radius: 10px;
}

/* ── Project configuration ─────────────────────────────────────────────── */
.project-screen .back {
  margin-bottom: 12px;
}
.ghost.small {
  padding: 8px 14px;
  font-size: 13.5px;
}
.project-run-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
/* Keep Configure on the head's right edge — without this the long meta line pushes it onto its
   own row and it reads as a stray button under the title. */
.project-run-head > div:first-child {
  /* basis 0, not auto: with auto the meta line's max-content size wraps the button onto its own row */
  flex: 1 1 0;
  min-width: 12ch;
}
.project-run-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.project-run-meta {
  margin: 8px 0 0;
  color: var(--ink-2);
  font-size: 15px;
}
/* ── Issue detail drawer ─────────────────────────────────────────────────── */
.drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 16, 30, 0.4);
  z-index: 60;
  animation: drawer-fade 0.18s ease-out;
}
.drawer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(720px, 94vw);
  background: var(--surface);
  border-left: 1px solid var(--line);
  box-shadow: -24px 0 60px rgba(0, 0, 0, 0.22);
  display: flex;
  flex-direction: column;
  animation: drawer-slide 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes drawer-fade {
  from { opacity: 0; }
}
@keyframes drawer-slide {
  from { transform: translateX(40px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .drawer-backdrop,
  .drawer {
    animation: none;
  }
}
.drawer-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 24px 0;
}
.drawer-head h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 620;
  line-height: 1.35;
}
.drawer-chips {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.drawer-lede {
  margin: 8px 0 0;
  padding: 0 24px;
  color: var(--ink-2);
  font-size: 14.5px;
}
.drawer-body {
  margin-top: 14px;
  padding: 0 24px 24px;
  overflow-y: auto;
  flex: 1;
}
.drawer-body .members {
  border-top: 1px solid var(--line-soft);
  padding: 0;
}
.drawer-count {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ── Jira draft modal ────────────────────────────────────────────────────── */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 16, 30, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 50;
}
.modal {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
  width: min(680px, 100%);
  max-height: 88vh;
  overflow-y: auto;
  padding: 22px 24px;
  display: grid;
  gap: 14px;
}
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.modal-head h2 {
  margin: 0;
  font-size: 19px;
}
.modal-close {
  background: none;
  border: none;
  font-size: 18px;
  color: var(--ink-3);
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 8px;
}
.modal-close:hover {
  background: var(--line);
}
.modal-lede {
  margin: 0;
  color: var(--ink-2);
  font-size: 14.5px;
}
.modal textarea,
.modal input {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  width: 100%;
  box-sizing: border-box;
}
.modal-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
#delete-project-modal .modal-actions,
#clone-project-modal .modal-actions {
  justify-content: flex-end;
}
.delete-project-field {
  display: grid;
  gap: 6px;
  font-size: 14px;
  margin-bottom: 16px;
}
.delete-project-field input {
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--line, #ccc);
  border-radius: 6px;
}
.go.danger-go {
  background: var(--crit);
  border-color: var(--crit);
}
.go.danger-go:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.modal-note {
  font-size: 13px;
  color: var(--ink-3);
}
.go[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}
.chip-link {
  font-size: 12.5px;
  font-family: inherit;
  color: var(--accent);
  background: none;
  border: none;
  padding: 2px 0;
  cursor: pointer;
  text-decoration: none;
}
.chip-link:hover {
  text-decoration: underline;
}
a.expand {
  text-decoration: none;
}
.access-test {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.access-test-result {
  font-size: 14px;
  color: var(--ink-2);
  max-width: 60ch;
}
.access-test-result.good {
  color: var(--ok, #1a7f37);
}
.access-test-result.bad {
  color: var(--crit);
}
/* A secret field and its two controls: show/hide, and copy. The input keeps the whole row's width
   so nothing about the form's rhythm changes when a field gains buttons. */
/* The "what this test did" download, inline after the verdict it belongs to. */
.diag-link {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}
.secret-row {
  display: flex;
  gap: 6px;
  align-items: stretch;
}
.secret-row input {
  flex: 1;
  min-width: 0;
}
.secret-btn {
  flex: none;
  padding: 0 10px;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
}
.secret-btn:hover:not(:disabled) {
  color: var(--accent);
  border-color: var(--accent);
}
.secret-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
.secret-btn.flashed {
  color: var(--accent);
  border-color: var(--accent);
}
/* The CMS test answers in three separate verdicts — the token that reads the schema, the token that
   reads published content, and whether this workspace may be written to at all. */
.cms-test-steps {
  display: grid;
  gap: 6px;
  margin-top: 10px;
  max-width: 70ch;
}
.cms-test-step {
  display: flex;
  gap: 8px;
  margin: 0;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--ink-2);
}
.cms-test-mark {
  flex: none;
  width: 1em;
  font-weight: 700;
}
.cms-test-step.good .cms-test-mark {
  color: var(--ok, #1a7f37);
}
.cms-test-step.bad .cms-test-mark {
  color: var(--crit);
}
.cms-test-step.skip .cms-test-mark {
  color: var(--ink-3);
}
/* An entry the test could not delete changed the CMS and nobody removed it: its own line, and it
   stays readable rather than sitting at the end of a step's sentence. */
.cms-test-leftover {
  margin: 4px 0 0;
  padding: 8px 10px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--crit);
  background: color-mix(in srgb, var(--crit) 10%, transparent);
  border-radius: 8px;
}
.project-form {
  display: grid;
  gap: 22px;
  margin-top: 26px;
}
/*
 * Project screens go two-column on a wide monitor instead of leaving half the shell empty beside a
 * stretched form. Configuration: identity + access on the left, what we compare against on the
 * right. Run screen: what you DO on the left, what already happened on the right — the same split
 * as the entry screen. Below 1100px both collapse to one column at a form measure.
 */
.config-grid,
.run-grid {
  max-width: 880px;
}
/*
 * The left column is its OWN block, not two grid items. With the panels as siblings of the tall
 * Reference-layers panel, expanding a layer grew the shared grid rows and pushed Access down the
 * page; a wrapper makes the two columns independent.
 */
.config-col {
  display: grid;
  gap: 22px;
  align-content: start;
}
.run-grid {
  display: grid;
  gap: 22px;
  align-items: start;
}
@media (min-width: 1100px) {
  .config-grid,
  .run-grid {
    max-width: none;
  }
  .config-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 24px 34px;
    align-items: start;
  }
  .config-grid .form-actions,
  .config-grid .entry-error {
    grid-column: 1 / -1;
  }
  .run-grid {
    grid-template-columns: minmax(0, 1fr) 380px;
    gap: 44px;
  }
  /* The history panel is reference material — it sits at the top of its column, not 56px down. */
  .run-side .recent {
    margin-top: 0;
  }
}
.field {
  display: grid;
  gap: 6px;
}
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
.field-label {
  font-size: 13.5px;
  font-weight: 500;
}
.field-hint {
  font-size: 12.5px;
  color: var(--ink-3);
}
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 11px 13px;
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
}
.field textarea {
  resize: vertical;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 13.5px;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}
/* ── Screenshot lightbox: the capture at full resolution, scrolled to the bug ── */
/* Above the drawer (z 60) — a member crop opens the lightbox from INSIDE the drawer. */
#shot-lightbox {
  z-index: 70;
}
/* Sized to leave a real margin of backdrop on every side — that gap is the click target that
   dismisses it, so reaching the ✕ is never the only way out. */
.lightbox {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  width: min(1240px, 84vw);
  height: min(820px, 82vh);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.lightbox-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-soft);
}
.lightbox-title {
  flex: 1;
  font-size: 14.5px;
  font-weight: 550;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lightbox-body {
  flex: 1;
  overflow: auto;
  background: var(--surface);
  padding: 16px;
}
/* The frame keeps the page's own pixel width (that IS the enlargement), never past the panel. */
.lightbox-body .shot-frame {
  max-width: 100%;
  box-shadow: 0 0 0 1px var(--line);
}
/*
 * Two moves on one crop: clicking the picture LOCATES the finding on the full-page screenshot,
 * the corner button ENLARGES it. The button sits above the crop and stops the click, so the
 * bigger, more useful target keeps the whole picture.
 */
.card-thumb[data-focus="1"],
.member-thumb[data-focus="1"] {
  cursor: pointer;
}
.member-thumb {
  position: relative;
}
.zoom-btn {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  font: inherit;
  font-size: 13px;
  line-height: 1;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 7px;
  opacity: 0.82;
  cursor: zoom-in;
  z-index: 2;
}
.zoom-btn:hover {
  opacity: 1;
  color: var(--accent);
  border-color: var(--accent);
}
.zoom-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  opacity: 1;
}

/*
 * "on the live site" — the caption that keeps a missing-element crop honest. It sits ON the
 * picture (bottom-left, opposite the zoom button) because it is a statement about THIS picture,
 * not about the finding: the crop shows the live site, the finding says the page under test has
 * nothing there. Loud enough to be read before the picture is.
 */
.live-crop-badge {
  /* A caption UNDER the crop, not a floating pill on it: the thumb column is taller than the
     picture, so an absolutely-positioned badge drifts to the bottom of the row, away from the
     thing it is captioning. */
  display: block;
  width: fit-content;
  margin: 6px auto 0;
  padding: 2px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  opacity: 0.94;
  pointer-events: auto;
}
/* The crop is not a locator when it is the live site — there is no target spot to scroll to. */
.member-thumb.live .shot-crop {
  outline: 1px dashed var(--line);
  outline-offset: -1px;
}

/* Lightbox tabs: the page under test vs the live site it was compared against. */
.lightbox-tabs {
  display: flex;
  gap: 4px;
  flex: none;
}
.lb-tab {
  font: inherit;
  font-size: 12.5px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}
.lb-tab:hover {
  border-color: var(--accent);
}
.lb-tab.on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}
.lb-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Priority rules: one row per rule — which checks → what priority. */
.sev-rules {
  display: grid;
  gap: 8px;
}
.sev-rule {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.sev-rule select {
  width: auto;
  padding: 7px 11px;
  font: inherit;
  font-size: 13.5px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.sev-rule-arrow {
  color: var(--ink-3);
}

/* One-line field (Jira priority): label · control · quiet note, on a single row. */
.field-inline {
  grid-template-columns: max-content max-content 1fr;
  align-items: center;
  gap: 10px;
}
.field-inline select {
  width: auto;
  padding: 7px 11px;
  font-size: 14px;
}
.field-note {
  font-size: 12.5px;
  color: var(--ink-3);
}
.panel {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 20px 20px;
  margin: 0;
  display: grid;
  gap: 14px;
  background: var(--surface);
}
.panel legend {
  padding: 0 8px;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.panel-lede {
  margin: 0;
  font-size: 14px;
  color: var(--ink-2);
  max-width: 68ch;
}
.panel-note {
  margin: 0;
  font-size: 13px;
  color: var(--ink-3);
}
.auth-fields {
  display: grid;
  gap: 14px;
}
.layers {
  display: grid;
  gap: 12px;
}
/* Each layer is a collapsed row: the fieldset reads as a menu of what can be configured, and only
   the layer being edited is expanded. */
.layer-card {
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--bg);
}
.layer-card.on {
  border-left-color: var(--accent);
  background: var(--surface);
}
.layer-card[open] {
  border-color: var(--line);
  border-left-color: var(--accent);
}
.layer-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.layer-head::-webkit-details-marker {
  display: none;
}
.layer-head:hover .layer-title {
  color: var(--accent);
}
.layer-chevron {
  flex: none;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid var(--ink-3);
  border-bottom: 1.5px solid var(--ink-3);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.layer-card[open] .layer-chevron {
  transform: rotate(45deg);
}
.layer-id {
  flex: 1 1 auto;
  display: grid;
  gap: 2px;
  min-width: 0;
}
.layer-title {
  font-size: 15px;
  font-weight: 500;
}
/* What this layer currently points at — shown only when the layer is active. */
.layer-gist {
  font-size: 12.5px;
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.layer-state {
  flex: none;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.layer-card.on .layer-state {
  color: var(--accent);
}
.layer-body {
  display: grid;
  gap: 12px;
  padding: 4px 16px 16px 16px;
}
.layer-unlocks {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-2);
  max-width: 70ch;
}
.layer-fields {
  display: grid;
  gap: 12px;
}

/* ── The live site's own way in (inside its layer card) ───────────────────────
   Same controls as the project's Access panel, one level in: separated by a rule so the address
   above reads as "where" and this as "how to get in". */
.live-access {
  display: grid;
  gap: 12px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.live-access .access-test {
  margin-top: 0;
}

/* ── Choosing a run's input (project run screen) ──────────────────────────────
   A page list, or a URL-mapping sheet. A segmented control rather than two panels: the choice is
   exclusive, and only one of them describes any given audit. */
.seg {
  display: inline-flex;
  /* The form is a grid, and a grid item stretches to its column however inline it declares itself —
     so `inline-flex` alone still left this pill spanning the whole measure with dead space after the
     second button. Pin it to the start of its column and it hugs its two labels. Wraps to a second
     row on a phone rather than overflowing the page. */
  justify-self: start;
  align-self: start;
  max-width: 100%;
  flex-wrap: wrap;
  gap: 2px;
  padding: 3px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 99px;
}
.seg-btn {
  padding: 7px 15px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 550;
  color: var(--ink-2);
  background: none;
  border: none;
  border-radius: 99px;
  cursor: pointer;
  white-space: nowrap;
}
.seg-btn:hover {
  color: var(--accent);
}
.seg-btn.on {
  /* --bg, not #fff: in dark mode --ink is near-white and white-on-white would vanish. */
  color: var(--bg);
  background: var(--ink);
}
/* ── The two audit kinds: cards, not a second pill ────────────────────────────
   Content vs Components is a fork between two mechanisms, not a display toggle — it deserves a
   line of explanation each and enough weight to read as the first decision on the screen. Stacked
   pills made it look like one of four equal tabs. */
.mode-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 10px;
  max-width: 620px;
}
.mode-card {
  display: grid;
  gap: 3px;
  padding: 13px 15px;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.mode-card:hover {
  border-color: var(--ink-3);
}
.mode-card.on {
  border-color: var(--accent);
  background: var(--accent-soft);
  /* A second ring rather than a 2px border: no 1px reflow when the selection moves. */
  box-shadow: inset 0 0 0 1px var(--accent);
}
.mode-card-title {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
}
.mode-card.on .mode-card-title {
  color: var(--accent);
}
.mode-card-hint {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--ink-3);
}
.mode-card.on .mode-card-hint {
  color: var(--ink-2);
}

/* The content audit's page-source picker: a quiet pill under its own label, so the two levels
   read as kind → source rather than as four peers. Selected = a raised chip on a recessed track,
   not the solid ink fill the cards' selection already carries. */
.run-source {
  display: grid;
  gap: 8px;
}
.seg-quiet {
  background: var(--line-soft);
  border-color: transparent;
}
.seg-quiet .seg-btn.on {
  color: var(--accent);
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(28, 27, 25, 0.08);
}
.seg-sub .seg-btn {
  padding: 5px 12px;
  font-size: 12.5px;
}
.run-pane {
  display: grid;
  gap: 14px;
}
/* A checkbox and its explanation on one row, the label's text block aligned to the box's line. */
.check-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
}
.check-row input {
  margin: 2px 0 0;
  accent-color: var(--accent);
  width: 15px;
  height: 15px;
  flex: none;
}
.check-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.check-label {
  font-size: 14px;
  color: var(--ink);
}
.check-row .field-hint {
  margin: 0;
}
/* A suite that cannot be picked (no live site configured) stays readable but visibly inert — the
   hint beside it says why, which is more use than a row that silently disappears. */
.check-row.off {
  cursor: default;
}
.check-row.off .check-label {
  color: var(--ink-3);
}

/* ── Typed page pairs (the pairs pane of the run screen) ──────────────────────
   The syntax lives in the hint, so the two example pairings have to read as code rather than as
   prose that happens to contain slashes. The textarea itself is monospaced for the same reason:
   the pairs line up column-wise, which is how a typo becomes visible. */
#pane-pairs .field-hint code {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12px;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0 4px;
  white-space: nowrap;
}
#run-pairs {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12.5px;
}

/* ── What to check: the two suites, outside the input panes (both modes show them) ── */
.suites {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.suites legend {
  padding: 0 6px;
  color: var(--ink-3);
}
.suite-warn {
  margin: 0;
  font-size: 12.5px;
  color: var(--crit);
}
/* ── Picking components to audit (the components pane of the run screen) ──────
   A stack can carry hundreds of component types, so this list is built to stay usable at that
   size: one filter over both lists, a select-all that only ever means "what is on screen", and a
   count that always says where the selection stands against what is on offer. */
.comp-status {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-2);
}
.comp-status.bad {
  color: var(--crit);
}
/* The per-type reasons under a failed catalog read: quiet until opened, then a plain list. */
.comp-status-details {
  margin: 6px 0 0;
  max-width: 72ch;
}
.comp-status-details summary {
  cursor: pointer;
  font-size: 13px;
  color: var(--ink-3);
}
.comp-status-details summary:hover {
  color: var(--accent);
}
.comp-status-reasons {
  margin: 8px 0 0;
  padding-left: 18px;
  font-size: 13px;
  color: var(--ink-2);
}
.comp-status-reasons li {
  margin: 0 0 4px;
}
.comp-status-reasons code {
  font-size: 12px;
}
.comp-status-hint {
  margin: 8px 0 0;
  font-size: 13px;
  color: var(--ink-2);
}
/* The pane's header: what this pane is on the left, the catalog-wide action on the right. */
.comp-pane-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 10px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line-soft);
}
/* When the catalog on screen was last read from the CMS — sits with the label, not the button, so
   the refresh action stays the rightmost thing in the row. */
.comp-cache-note {
  margin-right: auto;
  font-size: 12px;
  color: var(--ink-3);
}
/* A floor under the pane, so the first CMS read cannot collapse the form and bounce the rest of
   the run screen up. The skeleton below fills the same space the picker will take. */
.comp-body {
  min-height: 480px;
}
.comp-picker {
  display: grid;
  gap: 12px;
}
/* Loading state: the shape of the picker, greyed. Same rhythm as the real thing — page-type
   select, filter, list — so nothing moves when the catalog lands. */
.comp-skeleton {
  display: grid;
  gap: 12px;
}
.comp-skeleton .sk-line,
.comp-skeleton .sk-box,
.comp-skeleton .sk-row {
  border-radius: 8px;
  background: linear-gradient(
    90deg,
    var(--line-soft) 25%,
    color-mix(in srgb, var(--line) 60%, transparent) 37%,
    var(--line-soft) 63%
  );
  background-size: 400% 100%;
  animation: comp-sk 1.4s ease-in-out infinite;
}
.comp-skeleton .sk-label {
  height: 11px;
  width: 130px;
}
.comp-skeleton .sk-select {
  height: 38px;
}
.comp-skeleton .sk-filter {
  height: 36px;
  max-width: 320px;
}
.comp-skeleton .sk-list {
  background: none;
  animation: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 12px;
  display: grid;
  gap: 14px;
}
.comp-skeleton .sk-row {
  height: 12px;
}
.comp-skeleton .sk-row:nth-child(2) {
  width: 78%;
}
.comp-skeleton .sk-row:nth-child(3) {
  width: 64%;
}
.comp-skeleton .sk-row:nth-child(5) {
  width: 71%;
}
.comp-skeleton .sk-row:nth-child(6) {
  width: 55%;
}
.comp-skeleton-note {
  margin: 0;
  font-size: 13px;
  color: var(--ink-3);
}
@keyframes comp-sk {
  0% {
    background-position: 100% 0;
  }
  100% {
    background-position: 0 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .comp-skeleton .sk-line,
  .comp-skeleton .sk-box,
  .comp-skeleton .sk-row {
    animation: none;
  }
}
.comp-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
/* The selection's own readout, as a pill: with chips the count is the only place the total lives,
   so it earns more than 12px of grey. */
.comp-count {
  font-size: 12.5px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  padding: 3px 10px;
  border-radius: 99px;
  background: var(--line-soft);
}
.comp-count.some {
  color: var(--accent);
  background: var(--accent-soft);
  font-weight: 550;
}
.comp-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
/* Select all / Clear read as actions on the list, not as prose links dropped beside a search box. */
.comp-toolbar .link-btn {
  margin-top: 0;
  font-size: 12.5px;
  font-weight: 550;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 99px;
  background: var(--surface);
}
.comp-toolbar .link-btn:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.comp-filter {
  flex: 1 1 220px;
  min-width: 0;
  padding: 8px 11px;
  font: inherit;
  font-size: 13.5px;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.comp-filter:focus {
  outline: none;
  border-color: var(--accent);
}
/* ── The component picker: chips, not a stack of checkbox rows ────────────────
   A one-per-row list showed five of twenty-two components through a 300px window, so choosing a
   handful meant scrolling a list nobody could see the shape of. Chips wrap, so the same box shows
   the whole catalog at a glance and the selection is legible as a pattern rather than as ticks.
   Still capped and scrolled: a 300-component stack must not push "Run audit" off the screen. */
.comp-list {
  list-style: none;
  margin: 0;
  padding: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-content: start;
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
}
.comp-item {
  display: flex;
}
.comp-chip {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 12px 7px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.comp-chip:hover {
  border-color: var(--ink-3);
}
/* The checkbox stays a real, visible, clickable checkbox — the chip is a bigger hit area around it,
   not a replacement for it. */
.comp-chip .comp-check {
  margin: 0;
  flex: none;
  width: 15px;
  height: 15px;
  accent-color: var(--accent);
}
.comp-chip-text {
  display: grid;
  gap: 1px;
  min-width: 0;
}
.comp-chip-name {
  font-size: 13px;
  font-weight: 550;
  color: var(--ink);
  line-height: 1.25;
}
/* The uid is what the CMS and the findings call it, so it stays on screen — just demoted under the
   human title instead of being the row's headline. */
.comp-chip-uid {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  color: var(--ink-3);
  line-height: 1.25;
}
.comp-item:has(.comp-check:checked) .comp-chip,
.comp-chip.on {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.comp-item:has(.comp-check:checked) .comp-chip-name,
.comp-chip.on .comp-chip-name {
  color: var(--accent);
}
.comp-item:has(.comp-check:checked) .comp-chip-uid,
.comp-chip.on .comp-chip-uid {
  color: var(--ink-2);
}
.comp-empty {
  flex: 1 1 100%;
  padding: 6px 4px;
  font-size: 13px;
  color: var(--ink-3);
}
/* ── The picker's group tabs ──────────────────────────────────────────────────
   An underlined strip, not the pill row the rest of the console uses for filters: these tabs sit
   ON the list box and switch what it contains, and two pills hanging under the box (which is what
   this replaced) read as leftovers rather than as the other two thirds of the same control. */
.comp-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin: 10px 0 0;
  border-bottom: 1px solid var(--line);
}
.comp-tab {
  padding: 8px 12px 7px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 550;
  color: var(--ink-3);
  background: none;
  border: 0;
  /* The 2px sits ON the strip's own 1px rule, so switching tabs never moves the list. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
}
.comp-tab:hover {
  color: var(--ink);
}
.comp-tab.on {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
/* The count is the point of the strip — it says what is in the tabs you are NOT looking at. */
.comp-tab-n {
  margin-left: 5px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-3);
}
.comp-tab.on .comp-tab-n {
  color: var(--accent);
}
/* A tick that is not on the open tab still has to be visible from here. */
.comp-tab-dot {
  margin-left: 5px;
  color: var(--accent);
}
.comp-tabs + .comp-group-note {
  margin: 10px 0;
}
/* The list keeps its own rounded box under the strip — square top corners would claim the two are
   one surface, and the strip's rule already does that job without the seam. */
.comp-tabs + .comp-list,
.comp-group-note + .comp-list {
  margin-top: 10px;
}
/* Why a content type was sorted out of the default list — argued with, not just obeyed. Only the
   building-block and other-component chips carry one, so only those chips grow this third line. */
.comp-why {
  margin-top: 2px;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--ink-3);
  font-style: italic;
  max-width: 34ch;
}
/* The 409's questions. A question is not an error, so this reads as a form section rather than as
   the red refusal line the run form uses for genuine failures. */
.comp-asks {
  display: grid;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--accent);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}
.comp-ask-head {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink);
  font-weight: 550;
}
.comp-asks .field-hint code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.comp-plan summary {
  font-size: 13px;
}

/* ── URL-mapping upload (the sheet pane of the run screen) ────────────────────
   A spreadsheet of live-site → new-site addresses: the new ones become this run's pages, the live
   ones the routes each is compared against. */
.url-map {
  display: grid;
  gap: 12px;
}
.url-map-lede {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-2);
  max-width: 70ch;
}
/* The primary action of this pane, sized like it: a generous target you can drop onto without aiming,
   its glyph and two lines stacked and centred rather than a thin strip of a row. */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  width: 100%;
  min-height: 200px;
  padding: 30px 20px;
  text-align: center;
  font: inherit;
  color: var(--ink);
  background: var(--bg);
  border: 1.5px dashed var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.dropzone:hover,
.dropzone.over {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.dropzone:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.dz-glyph {
  flex: none;
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  color: var(--ink-3);
  background: var(--surface);
  border: 1px solid var(--line);
}
.dz-glyph svg {
  width: 26px;
  height: 26px;
}
.dropzone:hover .dz-glyph,
.dropzone.over .dz-glyph {
  color: var(--accent);
  border-color: var(--accent);
}
.dz-text {
  display: grid;
  justify-items: center;
  gap: 4px;
  min-width: 0;
}
.dz-title {
  font-size: 15.5px;
  font-weight: 550;
}
.dz-hint {
  font-size: 13px;
  color: var(--ink-3);
}
/* The real input is the accessible control the label-less button delegates to; kept reachable by
   assistive tech rather than display:none'd out of existence. */
.dz-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.map-status {
  margin: 0;
  font-size: 13px;
  color: var(--ink-2);
}
.map-status.good {
  color: var(--ok);
}
.map-status.bad {
  color: var(--crit);
}
.map-config {
  display: grid;
  gap: 12px;
}
.map-file-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.map-file-name {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12.5px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* "Remove file" is a quiet inline link, not the 20px-offset link-btn the form actions use. */
.map-file-row .link-btn {
  margin-top: 0;
  flex: none;
  font-size: 13px;
}
.map-selects {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
}
.map-selects .field select {
  padding: 8px 11px;
  font-size: 14px;
}
.map-origins {
  margin: 0;
  font-size: 13px;
  color: var(--ink-2);
}
.map-origins b {
  font-weight: 550;
  color: var(--ink);
}
.map-counts {
  margin: 0;
  font-size: 13px;
  color: var(--ink-2);
}
.map-counts b {
  font-weight: 600;
  color: var(--ink);
}
/* The preview scrolls inside its own frame: a 500-row sheet must never stretch the form, and the
   head stays put while the rows move under it. */
.map-table-wrap {
  max-height: 320px;
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
}
table.map-table {
  width: 100%;
  /* Below this the two path columns squeeze to one character per line. The frame scrolls in both
     directions instead — same bargain the check matrix makes. */
  min-width: 520px;
  border-collapse: collapse;
  font-size: 12.5px;
}
.map-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 8px 10px;
  text-align: left;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.map-table td {
  padding: 7px 10px;
  vertical-align: top;
  border-top: 1px solid var(--line-soft);
}
.map-table .map-tick {
  width: 34px;
  padding-right: 0;
}
.map-tick input {
  accent-color: var(--accent);
  cursor: pointer;
}
.map-table tr.off .map-route,
.map-table tr.off .map-label {
  color: var(--ink-3);
  text-decoration: line-through;
}
.map-table tr.unusable .map-tick input {
  cursor: not-allowed;
}
.map-label {
  color: var(--ink-2);
  max-width: 18ch;
  overflow-wrap: anywhere;
}
.map-route {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 12px;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.map-empty {
  font-family: inherit;
  color: var(--ink-3);
}
.map-flags {
  white-space: nowrap;
}
.map-badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 99px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  border: 1px solid var(--line);
}
.map-badge.warn {
  color: var(--high);
  border-color: var(--high);
}
.map-badge.bad {
  color: var(--crit);
  border-color: var(--crit);
}
/* What the sheet means for the live-site comparison, plus the one-click adopt when it names a site
   the project does not have configured. Boxed rather than a bare line: it is the one place on this
   screen where a decision may still be needed before running. */
.map-live {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 11px 14px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.map-note {
  font-size: 12.5px;
  color: var(--ink-3);
}
.map-note.good {
  color: var(--ok);
}
.map-note.warn {
  color: var(--high);
}
.map-note.bad {
  color: var(--crit);
}
/* The sentence takes the room it needs and the action stays on its line beside it, dropping under
   only when there is genuinely no space. */
.map-live .map-note {
  flex: 1 1 260px;
}
.map-live .ghost.small {
  margin-left: auto;
  flex: none;
  /* The label carries a host name, and a long one on a narrow phone would otherwise widen the whole
     page rather than the button. Let it wrap inside its own box instead. */
  max-width: 100%;
  white-space: normal;
  text-align: left;
}

.form-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.link-btn.danger {
  color: var(--crit);
}

/* ── Inspector lanes (progress screen) ─────────────────────────────────── */
.inspectors-wrap {
  margin-top: 34px;
}
.inspectors-head {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 10px;
  display: flex;
  gap: 10px;
  align-items: baseline;
}
.inspector-lede {
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}
.inspectors {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(175px, 1fr));
  gap: 4px 18px;
}
.lane {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px 0;
  font-size: 13px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink-3);
}
.lane.done {
  color: var(--ink);
}
.lane-dot {
  width: 7px;
  height: 7px;
  border-radius: 99px;
  background: var(--line);
  flex: 0 0 auto;
}
.lane.done .lane-dot {
  background: var(--accent);
}
/* An agent lane mid-flight: same pulse as the active stage mark, so "working" reads consistently. */
.lane.working {
  color: var(--ink);
}
.lane.working .lane-dot {
  background: var(--accent);
  animation: pulse 1.4s ease-in-out infinite;
}
/* A toolkit mid-scan: pulsing dot plus a light sweep across the name — checks are running. */
.lane.scanning {
  color: var(--ink-2);
}
.lane.scanning .lane-dot {
  background: var(--accent);
  animation: pulse 1.4s ease-in-out infinite;
}
.lane.scanning .lane-name {
  background: linear-gradient(90deg, var(--ink-2) 30%, var(--accent) 50%, var(--ink-2) 70%) 0 0 / 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: text-sheen 1.8s linear infinite;
}
@keyframes text-sheen {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}
.lane.failed .lane-dot {
  background: var(--line);
}
.lane-name {
  flex: 1;
}
.lane-count {
  font-size: 12.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* ── AI triage team: the agent bench ─────────────────────────────────────────
   Cards wired into the lead. Grayed cards are agents standing by / with nothing in their
   domain; the halo spin + flowing line only run while the BACKEND says the agent is working.
   The lead card sits centred beneath the bench with every specialist's line converging on it. */
.agent-net {
  position: relative;
  /* Room under the lead card for the bypass arc's label, which sits beside the card's lower half. */
  padding-bottom: 10px;
}
.agent-links {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}
.net-line {
  fill: none;
  stroke-width: 1.5;
  stroke: var(--line);
  transition: stroke 0.4s;
}
.net-line.dim {
  stroke-dasharray: 3 5;
}
.net-line.flow {
  stroke: var(--accent);
  stroke-width: 1.8;
  stroke-dasharray: 5 7;
  animation: net-flow 0.9s linear infinite;
}
.net-line.lit {
  stroke: color-mix(in srgb, var(--accent) 55%, var(--line));
}
.net-line.dead {
  stroke: var(--line);
  stroke-dasharray: 2 6;
}
/* The bypass: findings the gate's cap could not take, reaching the lead unverified. Deliberately
   the thinnest, faintest line on the board — it is a leak in the story, not a hand-off. */
.net-line.bypass {
  stroke: var(--ink-3);
  stroke-width: 1;
  stroke-dasharray: 1.5 4;
  opacity: 0.85;
}
.net-node {
  fill: var(--accent);
  stroke: var(--surface);
  stroke-width: 2;
}
.net-arrow {
  fill: var(--line);
}
.net-arrow.flow {
  fill: var(--accent);
}
.net-arrow.lit {
  fill: color-mix(in srgb, var(--accent) 55%, var(--line));
}
.net-arrow.soft {
  fill: var(--ink-3);
}
/* Labels live in the SVG so they sit exactly where the geometry puts them. They are aria-hidden;
   the .agent-rail paragraphs carry the same words to a screen reader. */
.agent-links text {
  font-family: inherit;
  font-size: 11.5px;
  fill: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.agent-links text.lede {
  font-weight: 600;
}
@keyframes net-flow {
  to {
    stroke-dashoffset: -12;
  }
}
.agent-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
  gap: 10px;
  position: relative;
  z-index: 1;
}
/* The verification gate's own tier: the whole bench converges here before anything reaches the
   lead, so it is centred like the lead and given room to say what it is doing. */
.agent-tier {
  position: relative;
  z-index: 1;
  margin-top: 44px;
  display: flex;
  justify-content: center;
}
.agent-tier:empty {
  display: none;
}
.agent-card.verifier {
  min-width: min(380px, 100%);
}
/* The rails restate the two connector labels in words. Above 740px the SVG shows them and these
   exist for screen readers only; below 740px the SVG is gone and they become the visible link. */
.agent-rail {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.agent-rail:empty {
  display: none;
}
/* One column on a phone: the converging-wires metaphor has no geometry to draw — so the tiers
   stack and the rails carry the story instead of the lines. */
@media (max-width: 740px) {
  .agent-links {
    display: none;
  }
  .agent-tier,
  #agent-lead {
    margin-top: 14px;
  }
  .agent-rail {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
    white-space: normal;
    margin: 14px 0 0;
    padding-left: 18px;
    border-left: 2px solid var(--accent);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ink-3);
  }
  .agent-rail.bypass {
    border-left-color: transparent;
    border-left-style: dotted;
    border-image: repeating-linear-gradient(var(--ink-3) 0 3px, transparent 3px 7px) 2;
  }
}
.agent-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  transition: border-color 0.3s, opacity 0.3s, box-shadow 0.3s;
}
/* Asleep cards stay OPAQUE (the wiring runs behind the bench and must not shine through);
   the dimming comes from grayscale + muted text, not transparency. */
.agent-card.standby,
.agent-card.idle {
  filter: grayscale(1);
  border-color: var(--line-soft);
}
.agent-card.standby .agent-name,
.agent-card.idle .agent-name {
  color: var(--ink-3);
}
.agent-card.standby .agent-note,
.agent-card.idle .agent-note {
  opacity: 0.75;
}
.agent-card.working {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-soft), 0 6px 22px color-mix(in srgb, var(--accent) 13%, transparent);
}
/* The lead: centred under the bench, at the point the specialists' lines converge on. */
#agent-lead {
  position: relative;
  z-index: 1;
  margin-top: 44px;
  display: flex;
  justify-content: center;
}
.agent-card.lead {
  min-width: min(340px, 100%);
  border-left: 3px solid var(--accent);
}
.agent-orb {
  position: relative;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
}
.agent-core {
  position: absolute;
  inset: 4px;
  border-radius: 99px;
  background: linear-gradient(135deg, var(--accent), var(--accent-strong));
  color: var(--accent-contrast);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
}
.agent-core svg {
  width: 17px;
  height: 17px;
  display: block;
}
.agent-card.standby .agent-core,
.agent-card.idle .agent-core {
  background: var(--line);
  color: var(--ink-3);
}
.agent-ring {
  position: absolute;
  inset: 0;
  border-radius: 99px;
  border: 2px solid var(--accent);
  opacity: 0;
}
/* The thinking halo: a conic sliver orbiting the core, masked to a thin ring. */
.agent-halo {
  position: absolute;
  inset: -1px;
  border-radius: 99px;
  opacity: 0;
  background: conic-gradient(from 0deg, var(--accent) 0 14%, transparent 22% 100%);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 3px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 3px));
}
.agent-card.working .agent-halo {
  opacity: 1;
  animation: halo-spin 1.1s linear infinite;
}
@keyframes halo-spin {
  to {
    transform: rotate(360deg);
  }
}
.agent-card.working .agent-ring {
  animation: agent-pulse 1.5s ease-out infinite;
}
@keyframes agent-pulse {
  0% { transform: scale(0.72); opacity: 0.9; }
  100% { transform: scale(1.25); opacity: 0; }
}
.agent-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.agent-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.agent-note {
  font-size: 12px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
/* While the agent thinks, its status line shimmers — and each new finding title fades in. */
.agent-note.thinking {
  background: linear-gradient(90deg, var(--ink-3) 30%, var(--accent) 50%, var(--ink-3) 70%) 0 0 / 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: text-sheen 2.2s linear infinite;
}
.agent-note.tick {
  animation: feed-in 0.4s ease both, text-sheen 2.2s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .agent-card.working .agent-ring,
  .agent-card.working .agent-core,
  .agent-card.working .agent-halo,
  .stage.active .stage-dot::after,
  .lane.scanning .lane-name,
  .agent-note.thinking,
  .agent-note.tick,
  .net-line.flow,
  .live-dot,
  .feed-item,
  .feed-dots i {
    animation: none;
  }
  .agent-card.working .agent-ring {
    opacity: 0.6;
    transform: none;
  }
  .agent-card.working .agent-halo {
    opacity: 0.5;
  }
  .lane.scanning .lane-name,
  .agent-note.thinking,
  .agent-note.tick {
    -webkit-text-fill-color: currentColor;
    background: none;
  }
}

@media (max-width: 640px) {
  .field-row {
    grid-template-columns: 1fr;
  }
}

/* ── Motion & focus ────────────────────────────────────────────────────────
   A single entrance on the landing screen, staggered so the eye lands on the
   projects list first. Everything here is opt-out under reduced motion. */
@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.entry .entry-head,
.entry .projects,
.entry .entry-side {
  animation: rise-in 0.42s cubic-bezier(0.22, 0.68, 0.28, 1) both;
}
.entry .projects {
  animation-delay: 0.06s;
}
.entry .entry-side {
  animation-delay: 0.12s;
}
.project-name,
.recent-page {
  transition: color 0.16s ease;
}
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}
.url-input:focus-visible {
  outline: none;
}
@media (prefers-reduced-motion: reduce) {
  .entry .entry-head,
  .entry .projects,
  .entry .entry-side,
  .quick-pop,
  .lane.working .lane-dot {
    animation: none;
  }
  .project-row:hover {
    transform: none;
  }
  * {
    transition-duration: 0.01ms !important;
  }
}

/* ── Agents screen ───────────────────────────────────────────────────────────
   The AI analyst team, laid out master-detail: the roster on the left is exactly the set of
   agents the triage graph can deploy (served, never hardcoded), the editor on the right holds
   the selected agent's skill file. Same design language as the bench on the progress screen —
   the orbs ARE the agents the user watched work during an audit. */
/* Master-detail is a workspace, so it takes the full shell like the results screen: the roster keeps
   its fixed 350px column and the skill editor gets the rest. */
.agents-head .lede {
  max-width: 62ch;
  margin-bottom: 0;
}
.agents-grid {
  display: grid;
  grid-template-columns: 350px minmax(0, 1fr);
  gap: 26px;
  align-items: start;
  margin-top: 32px;
}
.agent-roster {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.roster-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
  transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}
.roster-item:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  transform: translateX(2px);
}
.roster-item.active {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), var(--shadow);
}
.roster-item.lead {
  border-left: 3px solid var(--accent);
}
/* Agent codes ("L10N", "LEAD") in the same orb the progress bench uses. */
.roster-item .agent-core {
  font-size: 9.5px;
  letter-spacing: 0.04em;
}
.agent-orb.unwired .agent-core {
  background: var(--line);
  color: var(--ink-2);
}
.roster-tail {
  margin-left: auto;
  flex: 0 0 auto;
  font-size: 11.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.roster-divider {
  height: 1px;
  margin: 6px 4px;
  background: var(--line-soft);
}

.skill-editor {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 22px 24px 24px;
  box-shadow: var(--shadow);
}
/* Same hairline accent as the raised quick-audit card — this is the screen's working surface. */
.skill-editor::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  border-radius: 18px 18px 0 0;
  background: linear-gradient(90deg, var(--accent), transparent 70%);
  opacity: 0.7;
}
.skill-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.skill-id h2 {
  margin: 0;
  font-size: 20px;
  letter-spacing: -0.01em;
}
.skill-domain {
  margin: 5px 0 0;
  color: var(--ink-2);
  font-size: 14px;
  max-width: 54ch;
}
.skill-file,
.agents-foot code {
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  color: var(--ink-3);
  background: var(--bg);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  padding: 4px 8px;
}
.agents-foot code {
  padding: 1px 6px;
}
.skill-unwired {
  margin: 14px 0 0;
  font-size: 13.5px;
  color: var(--med);
}
#skill-text {
  width: 100%;
  margin-top: 16px;
  min-height: 340px;
  resize: vertical;
  padding: 14px 16px;
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.7;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
}
#skill-text:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.skill-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 14px;
  flex-wrap: wrap;
}
.skill-note {
  font-size: 13px;
  color: var(--ink-3);
}
.agents-foot {
  margin-top: 30px;
  font-size: 13.5px;
  color: var(--ink-3);
  max-width: 76ch;
}
.agents-screen .agents-head,
.agents-screen .agents-grid,
.agents-screen .agents-foot {
  animation: rise-in 0.42s cubic-bezier(0.22, 0.68, 0.28, 1) both;
}
.agents-screen .agents-grid {
  animation-delay: 0.05s;
}
.agents-screen .agents-foot {
  animation-delay: 0.1s;
}
@media (max-width: 900px) {
  .agents-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .agents-screen .agents-head,
  .agents-screen .agents-grid,
  .agents-screen .agents-foot {
    animation: none;
  }
  .roster-item:hover {
    transform: none;
  }
}

/* ── Header actions: tool-level configuration lives in the header, not the page ── */
.top-inner {
  max-width: var(--shell);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
/* The header's action row — a flex row so the pills sit on one centre line with a real gap
   (as inline-flex siblings in a block they aligned on differing text baselines). */
.top-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.top-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 99px;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.16s ease, color 0.16s ease;
}
.top-link:hover {
  border-color: var(--accent);
  color: var(--accent);
}
/* Both header glyphs occupy the same 16×12 box, so the two pills' labels line up exactly. */
.team-glyph,
.grid-glyph {
  flex: 0 0 auto;
  width: 16px;
  height: 12px;
  justify-content: center;
}
/* Three overlapping orbs — the team, in miniature. */
.team-glyph {
  display: inline-flex;
  align-items: center;
}
.team-glyph i {
  width: 10px;
  height: 10px;
  border-radius: 99px;
  background: linear-gradient(135deg, var(--accent), var(--accent-strong));
  border: 1.5px solid var(--surface);
  opacity: 0.85;
}
.team-glyph i + i {
  margin-left: -4px;
}
/* The check-matrix glyph: a 2×2 grid of squares, same accent language as the team orbs. */
.grid-glyph {
  display: inline-grid;
  grid-template-columns: repeat(2, 5px);
  grid-template-rows: repeat(2, 5px);
  gap: 2px;
  align-content: center;
}
.grid-glyph i {
  width: 5px;
  height: 5px;
  border-radius: 1.5px;
  background: linear-gradient(135deg, var(--accent), var(--accent-strong));
  opacity: 0.85;
}

/* ── The check matrix screen ─────────────────────────────────────────────────
   Rows = quality domains (agent identity reused), columns = what the page is judged against.
   The layer toggles are a pretend project configuration: switching one on lights its column. */
.matrix-layers {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 26px 0 10px;
}
.layer-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 99px;
}
button.layer-toggle {
  cursor: pointer;
}
button.layer-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.layer-toggle.on {
  border-color: var(--accent);
  color: var(--ink);
  box-shadow: 0 0 0 1px var(--accent-soft);
}
.layer-toggle.locked {
  color: var(--ink-3);
}
.lt-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 99px;
  background: var(--line);
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 700;
}
.layer-toggle.on .lt-mark,
.layer-toggle.locked .lt-mark {
  background: linear-gradient(135deg, var(--accent), var(--accent-strong));
  color: var(--accent-contrast);
}
.lt-n {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.matrix-total {
  font-size: 14px;
  color: var(--ink-2);
  margin: 0 0 18px;
}
.matrix-total strong {
  color: var(--accent);
  font-size: 16px;
}
.matrix-scroll {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}
table.matrix {
  width: 100%;
  min-width: 760px;
  border-collapse: collapse;
  font-size: 13px;
}
.matrix th,
.matrix td {
  padding: 12px 14px;
  text-align: center;
  border-bottom: 1px solid var(--line-soft);
}
.matrix thead th {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
/* Axis labels. The spanning "Compared against →" claims the reference columns; the corner cell
   below it carries "Quality domain ↓" for the rows, so neither can be mistaken for the other. */
.mx-axis-row td,
.mx-axis-row th {
  padding: 12px 14px 0;
  border-bottom: none;
}
.mx-axis {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.matrix .mx-domain {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
/* …but the row heads themselves are content, not an axis label. */
.matrix tbody .mx-domain {
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  font-weight: 400;
}
.matrix .mx-note {
  display: block;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-top: 2px;
}
.matrix tbody tr:last-child th,
.matrix tbody tr:last-child td {
  border-bottom: none;
}
.matrix th.off,
.matrix td.off {
  opacity: 0.35;
  filter: grayscale(1);
}
.mx-domain {
  text-align: left !important;
  min-width: 190px;
}
.mx-fam {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.mx-fam .agent-orb {
  width: 28px;
  height: 28px;
}
.mx-fam .agent-core {
  inset: 3px;
  font-size: 8.5px;
  letter-spacing: 0.03em;
}
.mx-fam-text {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}
.mx-fam-name {
  font-weight: 600;
  color: var(--ink);
}
.mx-fam-agent {
  font-size: 11.5px;
  font-weight: 400;
  color: var(--ink-3);
}
.mx-cell {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 52px;
  padding: 7px 12px;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid transparent;
  border-radius: 10px;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.mx-cell:hover {
  border-color: var(--accent);
}
/* Built checks, cell open. */
.mx-cell.open {
  background: var(--accent);
  color: var(--accent-contrast);
}
/* Cataloged but not built yet: dashed outline, and the sub-label says so in words rather than
   relying on the outline alone. */
.mx-cell.planned {
  background: none;
  border: 1px dashed color-mix(in srgb, var(--accent) 40%, var(--line));
  color: var(--ink-3);
}
/* A planned cell, open. Needed explicitly: `.planned` and `.open` have equal specificity, so
   without this the later rule stripped the fill while the label kept its light color and
   vanished. Soft accent fill keeps both readable, dashed border still says "not built". */
.mx-cell.planned.open {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
/* Inherit — never its own color. This is what makes the state above impossible to get wrong:
   the label always tracks whatever color the cell itself resolved to. */
.mx-cell-word {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: inherit;
  opacity: 0.72;
}
.mx-none {
  color: var(--ink-3);
  opacity: 0.6;
}
/* Legend: the cell states, named. */
.matrix-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  margin: 12px 2px 0;
  font-size: 12px;
  color: var(--ink-3);
}
.mx-key {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.mx-key-box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  padding: 2px 6px;
  border-radius: 7px;
  font-size: 11px;
  font-weight: 700;
  border: 1px solid transparent;
  font-variant-numeric: tabular-nums;
}
.mx-key-box.built {
  color: var(--accent);
  background: var(--accent-soft);
}
.mx-key-box.dim {
  color: var(--accent);
  background: var(--accent-soft);
  opacity: 0.35;
  filter: grayscale(1);
}
.mx-key-box.planned {
  color: var(--ink-3);
  border: 1px dashed color-mix(in srgb, var(--accent) 40%, var(--line));
}
.mx-key-none {
  min-width: 26px;
  text-align: center;
  opacity: 0.6;
}
.matrix-detail {
  margin-top: 18px;
  padding: 18px 20px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
}
.matrix-detail h2 {
  font-size: 15px;
  margin: 0 0 12px;
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.mx-detail-note {
  font-size: 12px;
  font-weight: 400;
  color: var(--ink-3);
}
.mx-checks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}
.mx-checks li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13px;
}
.mx-checks li:last-child {
  border-bottom: none;
}
.mx-checks code {
  flex: 0 0 auto;
  font-size: 11.5px;
  color: var(--accent);
}
.mx-check-text {
  flex: 1;
  min-width: 0;
}
.mx-check-desc {
  display: block;
  font-size: 12px;
  color: var(--ink-3);
  margin-top: 1px;
}
.mx-check-meta {
  flex: 0 0 auto;
  font-size: 11.5px;
  color: var(--ink-3);
  white-space: nowrap;
}
@media (max-width: 640px) {
  .mx-check-meta {
    display: none;
  }
}

/* ── Agent pipeline: how the selected agent works, as stages ────────────────── */
.agent-pipeline {
  display: flex;
  align-items: stretch;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 16px;
  padding: 12px 14px;
  background: var(--bg);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
}
.pipe-step {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.pipe-step b {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}
.pipe-step span {
  font-size: 12.5px;
  color: var(--ink-2);
}
.pipe-arrow {
  align-self: center;
  color: var(--ink-3);
  font-size: 14px;
}

/* ── Reasoning-model picker: which Claude model the agent thinks with ────────
   Radio-cards, one per model. The active card carries the accent ring the rest
   of the console uses for "selected"; the shipped recommendation is labeled so
   a custom choice is always a visible, deliberate state. */
.model-section {
  margin-top: 16px;
}
.model-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 8px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.model-note {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-3);
}
.model-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px;
}
.model-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 14px;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}
.model-card:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}
.model-card.active {
  background: var(--surface);
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.model-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.model-card-top b {
  font-size: 13.5px;
  font-weight: 650;
}
.model-rec {
  flex: 0 0 auto;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 99px;
  background: var(--accent-soft);
  color: var(--accent);
}
.model-card-blurb {
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-2);
}
.model-card-price {
  font-size: 11px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* Tiny model tag beside an agent's name — roster cards and the live bench alike. */
.model-badge {
  display: inline-block;
  margin-left: 7px;
  padding: 1.5px 7px;
  vertical-align: 1px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 99px;
  background: var(--accent-soft);
  color: var(--accent);
}

/* ── Vetting verdicts: the agent's judgment on a deterministic finding ──────── */
.chip.verdict {
  border: 1px solid transparent;
}
.chip.verdict.ok {
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  color: var(--ok);
}
.chip.verdict.fp {
  background: color-mix(in srgb, var(--med) 14%, transparent);
  color: var(--med);
}
.chip.verdict.unsure {
  background: var(--line-soft);
  color: var(--ink-2);
}
.verdict-reason {
  margin: 6px 0 10px;
  font-size: 13px;
  color: var(--med);
}
.vet-flag {
  color: var(--med);
  font-weight: 600;
}

/* ── Grounded verification: seen in the evidence, not merely reasoned about ─── */
.chip.verified {
  background: color-mix(in srgb, var(--ok) 16%, transparent);
  color: var(--ok);
  border: 1px solid color-mix(in srgb, var(--ok) 32%, transparent);
  font-weight: 700;
}
/* Looked at and not proven — amber, because it stands on the check's word alone. */
.chip.unproven {
  background: color-mix(in srgb, var(--med) 14%, transparent);
  color: var(--med);
  border: 1px solid color-mix(in srgb, var(--med) 30%, transparent);
  font-weight: 600;
}
/* Never looked at — quiet, but never mistaken for either verdict. */
.chip.unchecked {
  background: transparent;
  color: var(--ink-2);
  border: 1px dashed color-mix(in srgb, var(--ink-2) 45%, transparent);
}
.verified-note {
  margin: 6px 0 8px;
  font-size: 13px;
  color: var(--ink-2);
  font-style: italic;
}
/* Refuted claims: present, quiet, and clearly out of the count. */
#suppressed {
  margin-top: 22px;
}
details.suppressed {
  border: 1px dashed var(--line);
  border-radius: 12px;
  padding: 12px 16px;
  background: var(--surface-2, transparent);
}
details.suppressed > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  color: var(--ink-2);
}
.suppressed-lede {
  margin: 10px 0 12px;
  font-size: 13px;
  color: var(--ink-2);
  max-width: 68ch;
}
.suppressed-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.suppressed-list li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 10px;
  align-items: start;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line-soft);
}
.suppressed-list li:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}
.suppressed-title {
  font-size: 14px;
  color: var(--ink-2);
  text-decoration: line-through;
  text-decoration-color: var(--line);
}
.suppressed-reason,
.suppressed-note {
  grid-column: 2;
  font-size: 13px;
  color: var(--ink-2);
}
.suppressed-note {
  font-style: italic;
}

/* Phone: the landing screen leads with the action, not with the headline block. */
@media (max-width: 640px) {
  .entry {
    padding-top: 0;
  }
  .entry h1 {
    font-size: 30px;
  }
  .entry-head {
    gap: 16px;
    padding-bottom: 22px;
  }
  /* Both actions go full width and stack; the popover then spans the screen under them. */
  .entry-actions {
    margin-top: 0;
    width: 100%;
    flex-direction: column-reverse;
    align-items: stretch;
    gap: 8px;
  }
  .entry-actions .go,
  .quick-toggle {
    width: 100%;
    justify-content: center;
  }
  .quick-pop {
    right: auto;
    left: 0;
    width: 100%;
  }
  /* The header keeps only the glyph, tappable-comfortable; the label would crowd the brand. */
  .top-link {
    padding: 8px 11px;
  }
  .top-link-label {
    display: none;
  }
  .entry-grid {
    margin-top: 26px;
  }
  .panel-raised {
    padding: 20px 18px 22px;
  }
  .side-panel .recent-list {
    max-height: none;
  }
}

/* ── QA feedback: the 💬 on a finding, and the drawer it opens ─────────────── */
/* The icon joins the Confirm / Not an issue / Ignore pills on a member row, so it borrows their
   outline: without one it read as a stray glyph floating at the end of a row of buttons. */
.fb-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  align-self: center;
  flex: 0 0 auto;
  padding: 5px 10px;
  font: inherit;
  background: none; /* it is a real <button> — strip the UA chrome */
  border: 1px solid var(--line-soft);
  border-radius: 99px;
  color: var(--ink-3);
  cursor: pointer;
}
/*
 * The card head's counterpart is NOT a control. A collapsed card hides its members and therefore
 * their badges, so this chip is the only way to see that a card has been worked on while
 * scanning — but rating happens on the finding, and anything button-shaped up here would invite a
 * click that has nowhere to go. So: exactly the review chip's idiom, no icon and no hover state,
 * borrowing only the feature's accent so the eye can find it. It deliberately does NOT override
 * the head's pointer cursor — clicking it toggles the card, like clicking any other chip up here.
 */
.fb-rated {
  color: var(--accent);
  border-color: var(--accent-soft);
}
/* The Jira chip IS a control, unlike its neighbours: a card that has been raised carries its ticket
   key up here, and clicking it opens the ticket rather than toggling the card (see wireJiraChip). */
.jira-chip {
  text-decoration: none;
  letter-spacing: 0.04em;
  color: var(--accent);
  border-color: var(--accent-soft);
  cursor: pointer;
}
.jira-chip:hover {
  background: var(--surface);
  border-color: var(--accent);
}
.jira-filed {
  margin: 0 0 14px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
}
.jira-filed a {
  color: var(--accent);
}
.fb-btn:hover {
  color: var(--accent);
  border-color: var(--line);
  background: var(--surface);
}
.fb-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
/* Once somebody has said something, the icon stops being decoration and starts being a count. */
.fb-btn.has-fb {
  color: var(--accent);
}
.fb-count {
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}
/* The toolbar entry point's bubble — same idea, sitting on a ghost button. */
.fb-bubble {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--accent-contrast);
  background: var(--accent);
  border-radius: 99px;
}

/* Above the sticky filter block (15) and the drawer-era backdrop (60); the lightbox (70) still
   wins, so a crop opened from a card keeps working while this panel is up. */
.fb-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 16, 30, 0.4);
  opacity: 0;
  transition: opacity 0.2s ease-out;
  z-index: 60;
}
.fb-backdrop.open {
  opacity: 1;
}
.fb-drawer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(380px, 100vw);
  background: var(--surface);
  border-left: 1px solid var(--line);
  box-shadow: -24px 0 60px rgba(0, 0, 0, 0.22);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.fb-backdrop.open .fb-drawer {
  transform: translateX(0);
}
@media (prefers-reduced-motion: reduce) {
  .fb-backdrop,
  .fb-drawer {
    transition: none;
  }
}
.fb-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 20px 12px;
  border-bottom: 1px solid var(--line-soft);
}
.fb-head-main {
  min-width: 0;
}
.fb-head h2 {
  margin: 0;
  font-size: 17px;
  font-weight: 620;
}
/* Which finding is being rated — the reader must never lose that while typing. */
.fb-subject {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 0;
  font-size: 13.5px;
  color: var(--ink-2);
}
.fb-subject-text {
  min-width: 0;
}
/* Which root cause a member finding was clustered under — context, not the subject, so quieter
   and on its own line. */
.fb-parent {
  flex-basis: 100%;
  font-size: 12.5px;
  color: var(--ink-3);
}
.fb-body {
  flex: 1;
  overflow-y: auto;
  padding: 16px 20px 20px;
  display: grid;
  gap: 14px;
  align-content: start;
}
.fb-rate {
  display: flex;
  gap: 8px;
}
.fb-rate-btn {
  font-size: 13px;
  padding: 8px 12px;
}
/* `.field textarea` is monospaced because everywhere else it holds URLs and Jira markup. This one
   holds a sentence of English, so it takes the page's own face back. */
.fb-body textarea {
  resize: vertical;
  min-height: 84px;
  font-family: inherit;
  font-size: 14px;
}
/* The signature: a name we minted, shown as a fact the reviewer can overrule — never as a
   question. The ↻ sits inside the field's outline so the pair reads as one control. */
.fb-signed {
  display: flex;
  align-items: stretch;
  gap: 6px;
}
.fb-signed input {
  flex: 1 1 auto;
  min-width: 0;
}
.fb-reroll {
  flex: 0 0 auto;
  width: 40px;
  font: inherit;
  font-size: 16px;
  line-height: 1;
  color: var(--ink-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
}
.fb-reroll:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.fb-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
/* Withdrawing is deliberately the quietest control on the panel. */
.fb-remove {
  justify-self: start;
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink-3);
  background: none;
  border: none;
  text-decoration: underline;
  cursor: pointer;
}
.fb-remove:hover {
  color: var(--crit);
}
.fb-others {
  border-top: 1px solid var(--line-soft);
  padding-top: 12px;
}
.fb-others-head,
.fb-group-title {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.fb-entry {
  padding: 8px 0;
  border-bottom: 1px solid var(--line-soft);
}
.fb-entry:last-child {
  border-bottom: none;
}
.fb-entry-head {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 12.5px;
}
.fb-entry-author {
  font-weight: 620;
  color: var(--ink);
}
.fb-entry-when {
  color: var(--ink-3);
}
.fb-entry-comment {
  margin: 4px 0 0;
  font-size: 13.5px;
  color: var(--ink-2);
  white-space: pre-wrap;
}
.fb-group {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px 14px;
  background: var(--bg);
}
.fb-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.fb-group-title {
  text-transform: none;
  letter-spacing: 0;
  font-size: 14px;
  font-weight: 620;
  color: var(--ink);
  min-width: 0;
}
/* Per-finding verdicts inside a card's block, indented under the finding they are about. */
.fb-member-block {
  margin-top: 8px;
  padding-left: 10px;
  border-left: 2px solid var(--line);
}
.fb-member-label {
  margin: 0;
  font-size: 12.5px;
  font-weight: 620;
  color: var(--ink-2);
}
/* Verdicts on the whole root cause, which this screen can no longer produce. Kept visible because
   they are real captured data — quieter than the live per-finding entries, and clearly labelled
   so nobody mistakes them for something they could add to. */
.fb-legacy-block {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
  opacity: 0.85;
}
.fb-legacy-label {
  margin: 0 0 2px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.fb-locate {
  flex: 0 0 auto;
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--accent);
  background: none;
  border: none;
  cursor: pointer;
}
.fb-locate:hover {
  text-decoration: underline;
}
.fb-empty {
  margin: 0;
  color: var(--ink-2);
  font-size: 14px;
}
.fb-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 20px;
  border-top: 1px solid var(--line-soft);
}
.fb-download {
  font-size: 13px;
  color: var(--accent);
}
.fb-download.quiet {
  font-size: 12.5px;
  color: var(--ink-3);
}

/* Phone: the drawer IS the screen — 380px of panel beside 375px of viewport is a sliver. */
@media (max-width: 480px) {
  .fb-drawer {
    width: 100vw;
    border-left: none;
  }
}

/* ── Component audit: the "what will be tested?" disclosure and the Scenarios panel ──
 *
 * One vocabulary for both surfaces, because they show the same thing at two moments: the plan
 * BEFORE anything is written (now an optional disclosure inside the run screen's components pane,
 * so it borrows the surrounding details' chrome rather than carrying a card of its own), and the
 * record afterwards. Status colours are deliberately muted — a "findings" scenario is the audit
 * working as intended, and a CMS rejection is a POSITIVE result (the schema held), so neither earns
 * the alarm red the findings cards use. The one loud thing on this panel is a failed cleanup, which
 * means entries may still be in the CMS.
 */
/* Its own full-width area under the results grid, separated from the cards by real space and its
   own surface rather than reading as their continuation. */
.audit-panel {
  margin: 34px 0 18px;
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  /* The picture grids inside size themselves against THIS panel, not the viewport: the panel is a
     column now, and a viewport-width media query would keep three frames side by side in 300px. */
  container-type: inline-size;
}
/* Three readings of one audit. The tab strip is the panel's own head, so it sits on the panel's
   background with a rule under it rather than floating in a pill of its own. */
.ap-tabs {
  display: flex;
  width: 100%;
  justify-content: flex-start;
  margin: 0 0 14px;
  border-radius: 0;
  border: none;
  border-bottom: 1px solid var(--line);
  background: none;
  padding: 0 0 6px;
}
.ap-tabs .seg-btn {
  padding: 7px 14px;
  font-size: 13.5px;
}
/* A tab hides what it is not showing, so its label carries its own alarm — otherwise tabbing this
   panel would be a way of not reporting things. */
.ap-alarm {
  margin-left: 7px;
  padding: 1px 7px;
  border-radius: 99px;
  font-size: 11px;
  font-weight: 700;
  color: var(--high, #c2410c);
  background: color-mix(in srgb, var(--high, #c2410c) 12%, transparent);
}
.seg-btn.on .ap-alarm {
  background: color-mix(in srgb, var(--high, #c2410c) 18%, transparent);
}
.ap-body > .dod-group:last-of-type {
  margin-bottom: 10px;
}
/* The scenario ledger folds: summary row stays clickable and calm, chevron from the details
   marker, cursor signals it. The h2 inside a <summary> must not add block margins. */
.audit-details > summary { cursor: pointer; list-style: none; }
.audit-details > summary::-webkit-details-marker { display: none; }
.audit-details > summary .audit-head h2::before,
.audit-details > summary h2::before { content: "\25B8\00a0"; font-size: 0.8em; color: var(--muted, #888); }
.audit-details[open] > summary h2::before { content: "\25BE\00a0"; }
.audit-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
}
.audit-head h2 {
  margin: 0;
  font-size: 17px;
}
.sc-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.audit-facts,
.audit-plan-head {
  margin: 6px 0 12px;
  font-size: 13px;
  color: var(--ink-2);
  line-height: 1.7;
}
.audit-facts code,
.audit-plan-head code,
.sc-what code,
.sc-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
}
.audit-wait {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-2);
}
.sc-warn {
  color: var(--high);
}

/* A failed cleanup is a HARNESS failure: qa-tmp entries may still be sitting in the sandbox, so
   this is the one thing on the panel allowed to shout, and it names the command that clears them. */
.audit-banner {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--crit);
  border-left-width: 4px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--crit) 8%, transparent);
  color: var(--crit);
  font-size: 13.5px;
  font-weight: 600;
}
.audit-banner code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
}
.audit-abort {
  margin: 0 0 12px;
  font-size: 13.5px;
  color: var(--high);
}

/* ── One run, many components ─────────────────────────────────────────────────
   A multi-component audit's questions are per component ("did the hero survive?", "was anything
   left behind?"), so each gets its own sub-head with its own counts, resolution and cleanup state.
   A single-component (or pre-run-time) report renders the flat list and never sees these rules. */
.sc-group {
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.sc-group:first-of-type {
  margin-top: 0;
  border-top: 0;
  padding-top: 0;
}
.sc-grouphead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 4px;
}
.sc-groupname {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13.5px;
  font-weight: 650;
  color: var(--ink);
}
.sc-group-failed .sc-groupname {
  color: var(--high);
}
.sc-groupmeta {
  margin: 0 0 8px;
  font-size: 12.5px;
  color: var(--ink-3);
  line-height: 1.7;
}
.sc-groupmeta code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}

.sc-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line-soft);
}
.sc-row {
  padding: 8px 0 8px 10px;
  border-bottom: 1px solid var(--line-soft);
  border-left: 3px solid transparent;
}
.sc-row:last-child {
  border-bottom: 0;
}
.sc-row.sc-pass {
  border-left-color: var(--ok);
}
.sc-row.sc-findings {
  border-left-color: var(--high);
}
.sc-row.sc-cms-rejected {
  border-left-color: var(--ok);
}
.sc-row.sc-inconclusive {
  border-left-color: var(--med);
}
.sc-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
.sc-id {
  font-weight: 650;
  color: var(--ink);
}
.sc-what {
  font-size: 13px;
  color: var(--ink-2);
  min-width: 0;
}
.sc-origin {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0 5px;
  white-space: nowrap;
}
.sc-origin-ai {
  color: var(--accent);
  border-color: var(--accent);
}
.sc-status {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  white-space: nowrap;
  border: 1px solid transparent;
}
.scs-pass {
  color: var(--ok);
  border-color: color-mix(in srgb, var(--ok) 45%, transparent);
  background: color-mix(in srgb, var(--ok) 10%, transparent);
}
/* A rejection is the schema doing its job, so it belongs to the same family as pass — but it is a
   different ANSWER ("we asked, the CMS said no") and has to be tellable apart at a glance, hence
   its own shade rather than pass's with a lighter border. */
.scs-rejected {
  color: #2f7f66;
  border-color: color-mix(in srgb, #2f7f66 40%, transparent);
  background: transparent;
}
@media (prefers-color-scheme: dark) {
  .scs-rejected {
    color: #6fc4a6;
    border-color: color-mix(in srgb, #6fc4a6 40%, transparent);
  }
}
.scs-findings {
  color: var(--high);
  border-color: color-mix(in srgb, var(--high) 45%, transparent);
  background: color-mix(in srgb, var(--high) 10%, transparent);
}
.scs-inconclusive {
  color: var(--med);
  border-color: color-mix(in srgb, var(--med) 45%, transparent);
  background: color-mix(in srgb, var(--med) 10%, transparent);
}
.scs-planned {
  color: var(--ink-3);
  border-color: var(--line);
  background: transparent;
}
.sc-flags {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}
.sc-time {
  font-size: 11.5px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.sc-flag {
  font-size: 10.5px;
  color: var(--ink-3);
  border: 1px dashed var(--line);
  border-radius: 3px;
  padding: 0 5px;
}
.sc-flag-bad {
  color: var(--crit);
  border-color: var(--crit);
}
.sc-note {
  margin: 4px 0 0;
  font-size: 12.5px;
  color: var(--ink-2);
}
/* A component that FAILED explains itself in a note; the note's own colour would otherwise win on
   source order and paint the reason as ordinary prose. */
.sc-note.sc-warn {
  color: var(--high);
}

.audit-notes {
  margin-top: 12px;
  font-size: 13px;
  color: var(--ink-2);
}
.audit-notes summary {
  cursor: pointer;
  color: var(--ink-2);
}
.audit-notes ul {
  margin: 8px 0 0;
  padding-left: 20px;
  line-height: 1.7;
}

/* ── A primary button that is waiting on the server ────────────────────────────
 * Starting a component audit is one POST that reads schemas and reference pools
 * before it answers, which takes seconds. The button says so and spins, so the
 * screen is never silent while it waits. */
.go.busy {
  position: relative;
  padding-left: 34px;
  cursor: progress;
}
.go.busy::before {
  content: "";
  position: absolute;
  left: 14px;
  top: 50%;
  width: 12px;
  height: 12px;
  margin-top: -6px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  opacity: 0.85;
  animation: go-spin 0.7s linear infinite;
}
@keyframes go-spin {
  to {
    transform: rotate(360deg);
  }
}

/* A component result row's presentation (its `frontend_component` value) — the real variant this row audited. */
.sc-presentation { display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: 999px; font-size: 12px; background: var(--accent-soft, #eef); color: var(--accent, #225); vertical-align: middle; }

/* ── Coverage matrix (run screen, "Where to check") ─────────────────────────────
   One row per axis, chips as toggles. Reads as a statement of what the audit will look at, so the
   breakpoints, browsers and devices the tool covers are in plain sight on every run. */
.coverage {
  gap: 8px;
}
.cov-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 8px;
}
.cov-key {
  flex: 0 0 96px;
  font-size: 12.5px;
  color: var(--ink-3);
}
.cov-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.cov-chip {
  padding: 5px 11px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 550;
  color: var(--ink-2);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 99px;
  cursor: pointer;
  white-space: nowrap;
}
.cov-chip:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.cov-chip.on {
  color: var(--bg);
  background: var(--ink);
  border-color: var(--ink);
}
.cov-chip.locked {
  cursor: default;
  opacity: 0.85;
}
.cov-chip small {
  font-weight: 500;
  opacity: 0.75;
}
.cov-link {
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--accent);
  background: none;
  border: none;
  cursor: pointer;
}
.cov-link:hover {
  text-decoration: underline;
}
#cov-summary {
  margin: 2px 0 0 104px;
}
@media (max-width: 640px) {
  .cov-key {
    flex-basis: 100%;
  }
  #cov-summary {
    margin-left: 0;
  }
}
.coverage-line {
  margin-bottom: 6px;
}

/* ── Results view switch + DOD checklist ─────────────────────────────────────
   The DOD view reads the same cards back as the QA team's own checklist: one row per DOD line,
   status first (issues / checked / not run / manual), the cards folded underneath. */
.view-switch {
  margin: 0 0 10px;
}
.dod-group {
  margin: 0 0 22px;
}
.dod-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 8px;
}
.dod-head h3 {
  margin: 0;
  font-size: 15px;
  font-weight: 650;
}
.dod-head .dod-tally {
  font-size: 12.5px;
  color: var(--ink-3);
}
.dod-line {
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  margin: 0 0 6px;
}
.dod-line > summary {
  display: grid;
  /* `auto` on the status column let a long verdict push the title into a one-word-per-line
     ribbon; the status is short now and may wrap before the title does. */
  grid-template-columns: 22px minmax(0, 1fr) minmax(0, auto);
  gap: 10px;
  align-items: start;
  padding: 10px 14px;
  cursor: pointer;
  list-style: none;
}
.dod-line > summary::-webkit-details-marker {
  display: none;
}
.dod-line.inert > summary {
  cursor: default;
}
.dod-mark {
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border-radius: 50%;
  display: inline-grid;
  place-items: center;
  font-size: 11px;
  font-weight: 800;
  color: #fff;
  background: var(--ink-3);
}
.dod-line[data-status="issues"] .dod-mark {
  background: var(--high, #c2410c);
}
.dod-line[data-status="clear"] .dod-mark {
  background: var(--ok, #15803d);
}
.dod-line[data-status="notrun"] .dod-mark,
.dod-line[data-status="manual"] .dod-mark {
  background: none;
  color: var(--ink-3);
  border: 1.5px dashed var(--ink-3);
}
/* PARTIAL (§1m): covered in part. Deliberately NOT the green tick — a reader takes the reading from
   the mark and the word long before they reach the note that says 8 of 9. */
.dod-line[data-status="partial"] .dod-mark {
  background: none;
  color: var(--warn, #c86a1a);
  border: 1.5px solid var(--warn, #c86a1a);
}
.dod-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
}
.dod-line[data-status="notrun"] .dod-title,
.dod-line[data-status="manual"] .dod-title {
  color: var(--ink-2);
  font-weight: 500;
}
.dod-note {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  color: var(--ink-3);
}
.dod-status {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  font-size: 12.5px;
  color: var(--ink-2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* The capability ladder, where the run recorded one. Quiet by default — it qualifies the verdict
   beside it, it is not a second verdict — with only the two ends coloured: L0 says the tick you are
   looking at covers nothing, L3 that a human's second opinion also went over it. */
.dod-level {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: 99px;
  border: 1px solid var(--line);
  color: var(--ink-3);
  background: var(--line-soft);
}
.dod-level[data-level="0"] {
  border-style: dashed;
}
.dod-level[data-level="3"] {
  color: var(--accent);
  border-color: var(--accent);
  background: none;
}
/* The check ids that actually ran, and — when nothing did — the reason, on the line itself rather
   than behind a hover: "not run" is only useful with its why attached. */
.dod-ids {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  color: var(--ink-3);
}
.dod-ids code {
  font-size: 10.5px;
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--line-soft);
}
.dod-why {
  display: block;
  margin-top: 3px;
  font-size: 12px;
  font-style: italic;
  color: var(--ink-3);
}
.dod-legend {
  margin: 0;
  font-size: 11.5px;
  color: var(--ink-3);
}
/* Pins to the cards above, for the checklist that does not carry copies of them. */
.dod-pins {
  display: inline-flex;
  gap: 4px;
  margin-top: 5px;
}
.dod-pin {
  min-width: 20px;
  padding: 1px 6px;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  color: var(--bg);
  background: var(--high, #c2410c);
  border: none;
  border-radius: 99px;
  cursor: pointer;
}
.dod-pin:hover {
  background: var(--ink);
}
.dod-line[data-status="issues"] .dod-status {
  color: var(--high, #c2410c);
  font-weight: 600;
}
.dod-line[data-status="clear"] .dod-status {
  color: var(--ok, #15803d);
}
.dod-partial {
  margin-left: 6px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.dod-cards {
  padding: 4px 12px 12px;
}
.dod-cards .card {
  margin-top: 8px;
}
.sc-coverage { margin: 4px 0 8px; font-size: 12px; }
.sc-coverage summary { cursor: pointer; color: var(--muted, #555); }
.sc-coverage-list { margin: 6px 0 0; padding-left: 18px; columns: 2; column-gap: 24px; }
.sc-coverage-list li { break-inside: avoid; margin: 2px 0; }
.sc-cov-base { color: var(--muted, #555); }
.sc-cov-opp.sc-warn { color: var(--danger, #b00); }

/* ── Header glyphs for Memory and Settings ───────────────────────────────────
   Same 16×12 box as the team orbs and the matrix grid, so all four header pills' labels sit on
   one baseline. */
.stack-glyph,
.key-glyph {
  flex: 0 0 auto;
  width: 16px;
  height: 12px;
  display: inline-flex;
  justify-content: center;
  align-items: center;
}
/* Three stacked layers — memory as sediment. */
.stack-glyph {
  flex-direction: column;
  gap: 2px;
}
.stack-glyph i {
  width: 13px;
  height: 2.5px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent), var(--accent-strong));
  opacity: 0.85;
}
.stack-glyph i:nth-child(2) {
  width: 10px;
  opacity: 0.65;
}
.stack-glyph i:nth-child(3) {
  width: 7px;
  opacity: 0.45;
}
/* A key: ring plus bit. */
.key-glyph i {
  position: relative;
  width: 8px;
  height: 8px;
  border: 2px solid var(--accent);
  border-radius: 99px;
  opacity: 0.9;
}
.key-glyph i::after {
  content: "";
  position: absolute;
  top: 1px;
  left: 7px;
  width: 7px;
  height: 2px;
  background: var(--accent);
  border-radius: 2px;
  box-shadow: -2px 3px 0 -0.5px var(--accent);
}

/* ── Settings ────────────────────────────────────────────────────────────────
   One card per account, and every card carries its OWN Save / Test / Remove. The console this
   screen replaces kept one row of buttons in a modal footer under all three sections, so the Save
   below the Figma field posted to the Claude endpoint. Keeping the actions inside the card is the
   fix, and the reason the layout must never be collapsed into a shared footer again. */
/* Settings is tool-level, one tab per area: Connectors · Test management · Execution agents (the
   last is #screen-agents, which repeats the tab row). Connectors keeps its 860px card measure;
   Test management needs the full width for its list + document. */
.settings-screen {
  max-width: none;
}
.settings-tabs {
  display: flex;
  gap: 4px;
  margin: 18px 0 22px;
  border-bottom: 1px solid var(--line);
}
.settings-tab {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 10px 14px;
  margin-bottom: -1px;
  border: none;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--ink-2);
  cursor: pointer;
}
.settings-tab:hover {
  color: var(--accent);
}
.settings-tab.on {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
#settings-panel-connectors {
  max-width: 860px;
}
.settings-lede {
  margin: 0 0 18px;
  max-width: 860px;
}
.settings-sub {
  margin: 0 0 6px;
  font-size: 22px;
}
.settings-cards {
  display: flex;
  flex-direction: column;
  gap: 18px;
  margin-top: 28px;
}
.set-card {
  padding: 22px 24px 20px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.set-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
}
.set-id h2 {
  margin: 0;
  font-size: 17px;
  letter-spacing: -0.01em;
}
.set-what {
  margin: 6px 0 0;
  max-width: 56ch;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-2);
}
/* What is stored right now — never the secret, only its last four and when it landed. */
.set-status {
  flex: 0 0 auto;
  padding: 5px 11px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-3);
  background: var(--line-soft);
  border-radius: 99px;
  white-space: nowrap;
}
.set-status.is-set {
  color: var(--ok);
  background: color-mix(in srgb, var(--ok) 12%, transparent);
}
.set-field {
  display: block;
  margin-top: 16px;
}
.set-field > span {
  display: block;
  margin-bottom: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
}
.set-field > span em {
  margin-left: 8px;
  font-style: normal;
  font-weight: 450;
  color: var(--ink-3);
}
.set-field input,
.set-field select,
.set-field textarea {
  width: 100%;
  padding: 10px 12px;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
}
.set-field textarea {
  resize: vertical;
  line-height: 1.5;
}
.set-field input:focus,
.set-field select:focus,
.set-field textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}
.set-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0 18px;
}
.set-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 18px;
}
.set-actions button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
/* One line of feedback per card, beside that card's own buttons. */
.set-result {
  font-size: 13px;
  font-weight: 550;
  color: var(--ink-3);
}
.set-result.set-ok {
  color: var(--ok);
}
.set-result.set-ok::before {
  content: "✓ ";
}
.set-result.set-err {
  color: var(--crit);
}
.set-result.set-err::before {
  content: "✗ ";
}
.set-result.set-pending {
  color: var(--ink-3);
}
.set-help {
  margin: 14px 0 0;
  max-width: 66ch;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-3);
}
.set-help code {
  padding: 1px 5px;
  font-size: 12px;
  background: var(--line-soft);
  border-radius: 5px;
}

/* ── Experience memory ───────────────────────────────────────────────────────
   The lessons queue: candidate → approved → retired, promoted by a human. */
.memory-screen {
  max-width: 1000px;
}
.mem-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 14px;
  margin: 26px 0 12px;
}
.seg-n {
  opacity: 0.6;
  font-variant-numeric: tabular-nums;
}
.mem-scopes {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 18px;
}
.mem-chip {
  padding: 5px 11px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 550;
  color: var(--ink-2);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 99px;
  cursor: pointer;
}
.mem-chip span {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.mem-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.mem-chip.on {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent);
}
.mem-bar-end {
  display: flex;
  align-items: center;
  gap: 8px;
}
.mem-search {
  width: 260px;
  max-width: 100%;
  padding: 8px 12px;
  font: inherit;
  font-size: 13.5px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 99px;
}
.mem-search:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}
.mem-count,
.mem-empty {
  margin: 0 0 10px;
  font-size: 12.5px;
  color: var(--ink-3);
}
.mem-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.mem-list .mem-count:last-child {
  margin: 4px 0 0;
}
.mem-row {
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* A retired lesson is history: present, legible, visibly out of service. */
.mem-row[data-status="retired"] {
  opacity: 0.62;
}
.mem-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
}
.mem-badge {
  padding: 3px 9px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  border-radius: 99px;
  color: var(--ink-2);
  background: var(--line-soft);
}
.mem-badge.ms-approved {
  color: var(--ok);
  background: color-mix(in srgb, var(--ok) 13%, transparent);
}
.mem-badge.ms-candidate {
  color: var(--accent);
  background: var(--accent-soft);
}
.mem-scope,
.mem-src,
.mem-act {
  padding: 3px 9px;
  font-weight: 600;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 99px;
}
.mem-scope.is-global {
  color: var(--ink-2);
  border-style: dashed;
}
.mem-jira,
.mem-id {
  padding: 3px 7px;
  font-size: 11px;
  color: var(--ink-3);
  background: var(--line-soft);
  border-radius: 5px;
}
.mem-id {
  margin-left: auto;
}
.mem-lesson {
  margin: 12px 0 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
}
.mem-trigger {
  margin: 10px 0 0;
  font-size: 12.5px;
  color: var(--ink-2);
}
.mem-trigger-label {
  margin-right: 8px;
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
/* No trigger means the lesson can never match a finding — say so, don't hide it. */
.mem-trigger.is-empty {
  color: var(--high);
}
.mem-meta {
  margin: 8px 0 0;
  font-size: 11.5px;
  color: var(--ink-3);
}
.mem-notes {
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--ink-2);
}
.mem-notes summary {
  cursor: pointer;
  color: var(--ink-3);
}
.mem-notes p {
  margin: 8px 0 0;
  line-height: 1.55;
}
.mem-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.mem-trail {
  max-height: 320px;
  margin: 12px 0 0;
  padding: 12px 14px;
  overflow: auto;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-2);
  background: var(--line-soft);
  border-radius: 10px;
}
.mem-form {
  margin-bottom: 18px;
  padding: 18px 20px;
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.mem-form-title {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent);
}

@media (max-width: 720px) {
  .set-head {
    flex-direction: column;
    gap: 10px;
  }
  .mem-bar-end {
    margin-left: 0;
    width: 100%;
  }
  .mem-search {
    flex: 1;
    width: auto;
  }
}

/* A job in flight on the project screen: the dot breathes; the row reads as "attach", not "open". */
.recent-dot[data-tone="run"] {
  background: var(--accent, #2563eb);
  animation: recent-pulse 1.4s ease-in-out infinite;
}
@keyframes recent-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.7); }
}

/* A component type a page schema references but this branch does not contain: shown (nothing
   silently vanishes from a list someone is hunting through) and not tickable, because the audit
   could only fail at its first schema read. */
.comp-chip.unreadable {
  opacity: 0.62;
  cursor: not-allowed;
}
.comp-chip.unreadable .comp-check {
  cursor: not-allowed;
}

/* Check set (run screen): two radios, the chosen one remembered per browser. */
/* The presentation block: same fieldset skin as the check set; two selects side by side. */
.comp-presentation {
  margin: 14px 0 0;
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
}
.comp-pres-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
}
.comp-pres-grid select {
  width: 100%;
}
@media (max-width: 720px) {
  .comp-pres-grid {
    grid-template-columns: 1fr;
  }
}
/* Audit areas — which check families a component audit runs (2026-09-18). */
.comp-areas {
  border: 1px solid var(--border, #ddd);
  border-radius: 8px;
  padding: 0.6rem 0.8rem 0.5rem;
  margin: 0.8rem 0 0.4rem;
  display: grid;
  gap: 0.35rem;
}
.comp-areas legend {
  padding: 0 0.3rem;
}
.comp-areas-actions {
  font-weight: 400;
  font-size: 0.85em;
  opacity: 0.85;
  margin-left: 0.4rem;
}
.comp-areas .linkish {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent, #2b4fd8);
  cursor: pointer;
  text-decoration: underline;
}
.comp-areas-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.35rem 1rem;
  margin-top: 0.2rem;
}
@media (max-width: 720px) {
  .comp-areas-grid {
    grid-template-columns: 1fr;
  }
}
.comp-area-opt {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.5rem;
  align-items: start;
  font-size: 0.9em;
  line-height: 1.3;
  cursor: pointer;
}
.comp-area-opt input {
  margin-top: 0.25rem;
}
.comp-area-body {
  display: grid;
  gap: 0.05rem;
}
.comp-area-label {
  font-weight: 600;
}
.comp-area-count {
  font-weight: 400;
  opacity: 0.75;
}
.comp-area-hint {
  font-size: 0.86em;
  opacity: 0.75;
}
.comp-areas-note {
  margin: 0.2rem 0 0;
}
.comp-areas-empty {
  color: var(--bad, #b3261e);
}
/* Clone-fixture lanes (docs §1j): one small number beside the areas, on one line. */
.comp-lanes {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0.55rem 0 0;
  padding-top: 0.5rem;
  border-top: 1px solid var(--line, #e3e6ee);
}
.comp-lanes-label {
  font-weight: 600;
}
.comp-lanes input {
  width: 4.2rem;
  padding: 0.2rem 0.35rem;
}
.comp-lanes-hint {
  margin: 0;
}
.comp-lanes input#comp-root-attr-input {
  width: 11rem;
  max-width: 100%;
  font-family: var(--mono, ui-monospace, monospace);
}
.sc-checkset {
  display: inline-block;
  padding: 0 0.45em;
  border-radius: 999px;
  background: var(--accent-soft, #eef3ff);
  color: var(--accent, #2b4fd8);
  font-weight: 600;
}

/* Real page vs QA baseline (component audits) */
.sbs .field-hint { margin: 0.2rem 0 0.8rem; }
.sbs-block { margin: 0.6rem 0 1.2rem; }
.sbs-row { margin: 0.6rem 0 0; }
.sbs-viewport { margin: 0 0 0.3rem; font-size: 0.85em; font-weight: 600; opacity: 0.8; }
/* One viewport at a time: the pills choose which .sbs-row is shown. */
.sbs-vps { margin: 0.5rem 0 0.2rem; }
.sbs-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 0.8rem; align-items: start; }
@container (max-width: 620px) { .sbs-pair { grid-template-columns: 1fr; } }
.sbs-pic { margin: 0; border: 1px solid var(--border, #ddd); border-radius: 8px; padding: 0.5rem; background: var(--bg-soft, #fafafa); }
.sbs-pic figcaption { font-size: 0.82em; font-weight: 600; margin-bottom: 0.4rem; }
.sbs-pic img { display: block; max-width: 100%; height: auto; max-height: 520px; object-fit: contain; object-position: top left; border: 1px solid var(--border, #e3e3e3); background: #fff; }
.sbs-links { margin: 0.4rem 0 0; font-size: 0.78em; opacity: 0.85; word-break: break-all; }
.sbs-missing .audit-wait { margin: 0; }
.shot-full-link { margin: 0.4rem 0 0; font-size: 0.82em; opacity: 0.85; }

/* Figma frames per component (run screen) */
.comp-figma { border: 1px solid var(--border, #ddd); border-radius: 8px; padding: 0.6rem 0.8rem 0.6rem; margin: 0.8rem 0 0; display: grid; gap: 0.5rem; }
.comp-figma legend { padding: 0 0.3rem; }
.comp-figma-opt { font-weight: 400; opacity: 0.7; }
.comp-figma .field-hint { margin: 0; }
.comp-figma-row { display: grid; grid-template-columns: minmax(120px, 0.6fr) 1fr 1fr; gap: 0.5rem; align-items: center; }
@media (max-width: 720px) { .comp-figma-row { grid-template-columns: 1fr; } }
.comp-figma-row code { font-size: 0.85em; word-break: break-all; }
.comp-figma-row input { width: 100%; box-sizing: border-box; font-size: 0.85em; }
.comp-figma-row input.bad { border-color: var(--danger, #c0392b); }
.sbs-pair.sbs-three { grid-template-columns: 1fr 1fr 1fr; }
@container (max-width: 860px) { .sbs-pair.sbs-three { grid-template-columns: 1fr 1fr; } }
@container (max-width: 560px) { .sbs-pair.sbs-three { grid-template-columns: 1fr; } }

/* Figma vs render, property table */
.fid { margin: 0.5rem 0 0; font-size: 0.85em; }
.fid summary { cursor: pointer; }
.fid-scroll { overflow-x: auto; }
.fid-areas { margin: 0.4rem 0; padding-left: 1.2rem; }
.fid-areas li { margin: 0.1rem 0; }
.fid-table { border-collapse: collapse; width: 100%; margin-top: 0.4rem; }
.fid-table th, .fid-table td { text-align: left; padding: 0.25rem 0.5rem; border-bottom: 1px solid var(--border, #e5e5e5); vertical-align: top; white-space: nowrap; }
.fid-table th { font-weight: 600; opacity: 0.8; }
.fid-ok { color: var(--ok, #1e7e34); }
.fid-bad { color: var(--danger, #c0392b); font-weight: 600; }
.fid-na { opacity: 0.6; }
.fid-q { cursor: help; }
.fid-layer { opacity: 0.7; font-size: 0.9em; }

/* Figma layer → CMS field: the recorded pairing decision (2026-09-15). The "why" column wraps —
   it is a sentence, not a value — while the rest of the table stays on one line. */
.fid-assign summary { font-weight: 600; }
.fid-assign .fid-table td:first-child { max-width: 26rem; white-space: normal; overflow-wrap: anywhere; }
.fid-assign .fid-table td:last-child { white-space: normal; min-width: 14rem; }
.fid-why { opacity: 0.7; font-size: 0.9em; }
.fid-how { font-size: 0.85em; padding: 0 0.35rem; border-radius: 3px; border: 1px solid var(--line, #ddd); }
.fid-how-ai { border-color: var(--accent, #7b27ff); color: var(--accent, #7b27ff); }
/* "copy": a rich-text block paired to a layer by exact copy equality (2026-09-23) — decided by the words, not by a name or a model. */
.fid-how-copy { border-color: var(--ok, #1a7f37); color: var(--ok, #1a7f37); }
.fid-how-unassigned { opacity: 0.6; }
.fid-doubt { margin: 0.4rem 0 0; color: var(--warn, #9a6700); }

/* A running audit's row carries a Cancel beside it. The row itself stays the click target for
   following the run, so the two live side by side rather than nesting — a button inside a button
   is invalid markup and the inner one swallows the outer's clicks. */
#project-running-list li {
  display: flex;
  align-items: center;
  gap: 6px;
}
#project-running-list .recent-item {
  flex: 1;
  min-width: 0;
}
.run-cancel {
  flex: none;
  align-self: center;
  padding: 6px 12px;
  background: none;
  border: 1px solid var(--line);
  border-radius: 8px;
  font: inherit;
  font-size: 0.82rem;
  color: var(--ink-soft, var(--ink));
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}
.run-cancel:hover:not(:disabled) {
  background: var(--bad-soft, var(--line-soft));
  border-color: var(--bad, var(--line));
  color: var(--bad, var(--ink));
}
.run-cancel:disabled {
  opacity: 0.6;
  cursor: default;
}

/* The Section/Page DOD pills — one checklist group at a time, like the Desktop/Mobile viewport
   pills in "Real page vs QA fixture". */
.dod-tabs {
  margin: 0 0 12px;
}

/* ── REQUIREMENTS AND THE MANUAL CHECKLIST (docs §1k, 2026-09-22) ────────────
   The Jira task a run was judged against: a field on the picker with a preview under it, a byline
   on the results header, and a tab of checks a person ticks. Nothing here is decorative — every
   rule exists to keep the ticket's own words readable where they are quoted. */
.comp-jira {
  border: 1px solid var(--border, var(--line));
  border-radius: 8px;
  padding: 0.6rem 0.8rem;
  margin: 0.8rem 0 0;
  display: grid;
  gap: 0.5rem;
}
.comp-jira legend { padding: 0 0.3rem; }
.comp-jira .field-hint { margin: 0; }
.comp-jira input {
  width: 100%;
  box-sizing: border-box;
  max-width: 18rem;
  text-transform: uppercase;
}
.comp-jira-preview {
  border-left: 3px solid var(--accent);
  background: var(--line-soft);
  border-radius: 0 8px 8px 0;
  padding: 0.5rem 0.7rem;
  font-size: 0.9em;
}
.comp-jira-head { margin: 0 0 0.35rem; }
.comp-jira-status { color: var(--ink-3); }
.comp-jira-bad { margin: 0; color: var(--crit); }
/* The acceptance criteria are shown as they are WRITTEN — a table row that reads
   "Logo Cards | Min 2 … Max 12" only reads that way if nothing reflows it. */
.comp-jira-ac {
  margin: 0;
  max-height: 11rem;
  overflow: auto;
  white-space: pre-wrap;
  font-size: 0.85em;
  line-height: 1.45;
  color: var(--ink-2);
}
.requirements-line a { color: var(--accent); }

/* ── The interaction inventory (docs §1m, 2026-09-22) ──────────────────────────
 * A gap row has to look like a gap. The unexercised items carry the warning rule down their left
 * edge; the exercised ones are quiet, because a list where everything shouts is a list nobody reads.
 */
.interactions-line .sc-warn { font-weight: 600; }
.iv-head { margin: 18px 0 4px; font-size: 1em; }
.iv-group { margin: 6px 0 10px; }
.iv-group > summary { cursor: pointer; color: var(--ink-2); }
.iv-list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px; }
.iv-item {
  display: grid;
  gap: 2px;
  padding: 6px 10px;
  border-left: 3px solid var(--line);
  background: var(--surface);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.iv-gap { border-left-color: var(--warn, #c86a1a); }
.iv-what code { font-size: 0.85em; }
.iv-href { color: var(--ink-2); font-size: 0.85em; }
.iv-by { color: var(--ink-2); font-size: 0.85em; }
.iv-why { font-size: 0.88em; }
.iv-attrs { color: var(--ink-3, var(--ink-2)); font-size: 0.78em; }

.mc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.mc-case {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 12px 14px;
}
.mc-title { margin: 0 0 6px; font-weight: 600; }
.mc-comp { font-weight: 400; color: var(--ink-3); }
/* The criterion, verbatim. Quoted rather than paraphrased, so a tester can check the claim against
   the ticket without leaving the row. */
.mc-quote {
  margin: 0 0 8px;
  padding: 6px 10px;
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  color: var(--ink-2);
  font-size: 0.92em;
}
.mc-src a { color: var(--accent); white-space: nowrap; }
.mc-steps { margin: 0 0 8px; padding-left: 1.2rem; color: var(--ink-2); }
.mc-steps li { margin: 2px 0; }
.mc-expected { margin: 0 0 4px; }
.mc-why { margin: 0 0 8px; color: var(--ink-3); font-size: 0.9em; }
.mc-verdicts { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.mc-verdict {
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-2);
  border-radius: 999px;
  padding: 3px 12px;
  font: inherit;
  font-size: 0.9em;
  cursor: pointer;
}
.mc-verdict:hover { border-color: var(--ink-3); }
.mc-verdict.on { color: var(--accent-contrast); border-color: transparent; }
.mc-pass.on { background: var(--ok); }
.mc-fail.on { background: var(--crit); }
.mc-na.on { background: var(--low); }
.mc-note { flex: 1 1 12rem; min-width: 8rem; font-size: 0.9em; }
.mc-saved { color: var(--ink-3); font-size: 0.82em; }
@media (max-width: 640px) {
  .mc-verdicts { gap: 4px; }
  .mc-note { flex-basis: 100%; }
}

/* THE TWO ROOTS a component was measured in (§1n) — quiet, but never hidden: the narrow-root
   defect survived because this screen showed the pictures and not the boxes they were measured in. */
.sbs-roots { list-style: none; margin: 2px 0 8px; padding: 0; display: grid; gap: 4px; }
.sbs-roots li { font-size: 12px; line-height: 1.45; }

/* ── Folded audit areas + Figma-from-Jira (owner ask 2026-09-23) ──────────────
   The areas control is folded by default because "all of them" is the answer on
   almost every run; the summary line carries what a closed fold would otherwise
   hide, and JS forces it open the moment the set is narrowed. */
.comp-areas-fold > summary {
  cursor: pointer;
  list-style: revert;
  font-size: 0.9em;
  padding: 0.1rem 0;
}
.comp-areas-fold > summary::marker { color: var(--ink-3, #888); }
.comp-areas-fold[open] > summary { margin-bottom: 0.3rem; }
#comp-areas-summary { color: var(--ink-2, #555); }
#comp-areas-summary.narrowed { color: var(--accent, #2b4fd8); font-weight: 600; }
.comp-areas-fold > .comp-areas-actions { display: inline-block; margin: 0 0 0.2rem 0; }

/* The green "came from the ticket" badge beside an auto-filled frame link. */
.comp-figma-from {
  display: inline-block;
  font-size: 0.78em;
  color: var(--ok, #1e7a3c);
  background: color-mix(in srgb, var(--ok, #1e7a3c) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--ok, #1e7a3c) 35%, transparent);
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
  margin-top: 0.15rem;
  white-space: nowrap;
  justify-self: start;
}
.comp-figma-cell { display: grid; gap: 0.1rem; }
.comp-figma-fetched { margin: 0; color: var(--ok, #1e7a3c); }
.comp-figma-fetched.bad { color: var(--crit, #b3261e); }
.comp-figma .linkish {
  background: none; border: 0; padding: 0; font: inherit; font-size: 0.85em;
  color: var(--accent, #2b4fd8); cursor: pointer; text-decoration: underline;
}

/* "Run details" — the run screen's own form, read back off the report (2026-09-23), shown in a
   drawer that borrows the feedback panel's furniture. */
.run-setup-grid {
  display: grid;
  grid-template-columns: minmax(140px, max-content) 1fr;
  gap: 0.3rem 1rem;
  margin: 0;
  font-size: 0.88em;
}
.run-setup-grid dt { color: var(--ink-3, #777); }
/* Wider than the feedback drawer: this one holds URLs, frame links and an eight-item area list,
   and at 380px every other value wrapped onto three lines. */
#run-setup-drawer .fb-drawer { width: min(560px, 100vw); }
#run-setup-body .run-setup-grid { grid-template-columns: minmax(150px, max-content) 1fr; gap: 0.45rem 1.2rem; }
.run-setup-grid dd { margin: 0; word-break: break-word; }
.run-setup-grid code { font-size: 0.95em; }
.run-setup-grid .rs-dim { color: var(--ink-3, #777); }
@media (max-width: 720px) {
  .run-setup-grid { grid-template-columns: 1fr; gap: 0.1rem; }
  .run-setup-grid dd { margin-bottom: 0.35rem; }
}

/* TEST ENTRIES (2026-09-24): the Components pane's second action. In that mode the audit-only
   controls are hidden — never changed — and the panel below the presentation takes their place. */
.comp-action {
  margin: 14px 0 0;
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
}
body.entries-mode #comp-areas,
body.entries-mode #comp-planner-row,
body.entries-mode #comp-plan,
body.entries-mode #comp-gaps,
body.entries-mode #run-coverage,
body.entries-mode #run-go {
  display: none !important;
}
/* The frame field stays in demo mode — the demo is built from it. */
body.entries-mode:not(.demo-mode) #comp-figma,
body.entries-mode:not(.demo-mode) #comp-jira,
body.demo-mode .te-only,
body:not(.demo-mode) .demo-only {
  display: none !important;
}
.comp-demo-where {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: 12px;
}
@media (max-width: 720px) {
  .comp-demo-where {
    grid-template-columns: 1fr;
  }
}
.comp-demo-where select,
.comp-demo-where input {
  width: 100%;
  box-sizing: border-box;
}
.demo-ready {
  display: inline-block;
  font-size: 12px;
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid var(--line-soft);
}
.demo-ready.ok {
  border-color: #6fbf8e;
}
.demo-ready.no {
  border-color: #e0a36b;
}
.demo-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
  margin: 6px 0;
}
.demo-table td,
.demo-table th {
  text-align: left;
  padding: 3px 6px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}
.demo-table .unassigned {
  opacity: 0.65;
}
.demo-frame {
  max-width: 100%;
  max-height: 260px;
  border: 1px solid var(--line-soft);
  border-radius: 6px;
  margin: 6px 0;
}
.comp-entries {
  margin: 14px 0 0;
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  display: grid;
  gap: 10px;
}
.comp-entries textarea {
  width: 100%;
  box-sizing: border-box;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
}
.te-cases {
  margin: 6px 0 4px;
  padding-left: 26px;
  display: grid;
  gap: 2px;
}
.te-cases li {
  font-size: 13px;
  line-height: 1.45;
}
.te-src {
  display: inline-block;
  font-size: 11px;
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid var(--line-soft);
  margin-right: 4px;
}
.te-src-every-value {
  border-color: #9fb4ec;
}
.te-src-extra {
  border-color: #e2c27a;
}
.te-log {
  margin: 4px 0 0;
  padding-left: 20px;
  font-size: 12.5px;
}
.te-head {
  margin: 8px 0 2px;
  font-size: 14px;
}
.te-build {
  border-top: 1px solid var(--line-soft);
  padding: 8px 0 4px;
}
.te-build-head {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
}
.te-gone {
  opacity: 0.6;
}
.te-page {
  margin: 6px 0;
}
.te-notes {
  font-size: 12.5px;
}
.te-anchor {
  margin-left: 6px;
  font-size: 12px;
}

/* Configure → CMS connection: the two connections are headed apart (2026-09-28). */
.field-section {
  grid-column: 1 / -1;
  display: grid;
  gap: 2px;
  margin: 10px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--line-soft);
}
.field-section-title {
  font-weight: 600;
  font-size: 13.5px;
}
.demo-thumb {
  max-width: 120px;
  max-height: 72px;
  border: 1px solid var(--line-soft);
  border-radius: 4px;
  display: block;
}

/* ── Ticket chat drawer (PLAN §12 B) ─────────────────────────────────────── */
.discuss-btn { font-weight: 560; }
.rule-note {
  margin: 6px 0 0;
  font-size: 12.5px;
  color: var(--ink-3);
}
#rc-drawer .rc-drawer { width: min(560px, 100vw); }
.rc-thread {
  flex: 1;
  overflow-y: auto;
  padding: 14px 20px;
  display: grid;
  gap: 12px;
  align-content: start;
}
.rc-empty { margin: 0; font-size: 13.5px; color: var(--ink-2); line-height: 1.5; }
.rc-turn {
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 10px 12px;
  font-size: 14px;
  line-height: 1.45;
  background: var(--surface);
  min-width: 0;
}
.rc-turn p { margin: 4px 0 0; overflow-wrap: anywhere; }
.rc-me { background: var(--accent-soft); border-color: transparent; margin-left: 32px; }
.rc-agent { margin-right: 16px; }
.rc-error { border-color: var(--crit); color: var(--crit); }
.rc-thinking { color: var(--ink-3); font-style: italic; }
.rc-who { font-size: 12px; color: var(--ink-3); }
.rc-understood { font-size: 12.5px; color: var(--ink-2); }
.rc-refused { font-size: 12.5px; color: var(--high); }
.rc-system { font-size: 12.5px; color: var(--ink-3); text-align: center; padding: 0 12px; }
.rc-atts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.rc-att-img img { display: block; max-width: 160px; max-height: 100px; border-radius: 6px; border: 1px solid var(--line); }
.rc-att-link { font-size: 12.5px; overflow-wrap: anywhere; }
.rc-rule {
  margin-top: 8px;
  padding: 10px;
  border-radius: 10px;
  background: var(--bg);
  border: 1px solid var(--line-soft);
  display: grid;
  gap: 6px;
}
.rc-rule-head { font-size: 12px; font-weight: 620; color: var(--ink-2); text-transform: uppercase; letter-spacing: 0.04em; }
.rc-rule-what code { font-size: 12.5px; overflow-wrap: anywhere; white-space: normal; }
.rc-rule-why { font-size: 13px; color: var(--ink-2); }
.rc-find { justify-self: start; }
.rc-matches { display: grid; gap: 8px; margin-top: 4px; }
.rc-count, .rc-none { margin: 0; font-size: 13px; color: var(--ink-2); }
.rc-match {
  display: grid;
  grid-template-columns: auto 112px minmax(0, 1fr);
  gap: 10px;
  align-items: start;
  padding: 6px;
  border-radius: 8px;
  border: 1px solid var(--line-soft);
  background: var(--surface);
  cursor: pointer;
}
.rc-match.off { opacity: 0.55; }
.rc-match-thumb { display: block; width: 112px; cursor: zoom-in; }
.rc-match-thumb .shot-crop { width: 112px; }
.rc-match-text { display: grid; gap: 2px; min-width: 0; font-size: 13px; }
.rc-match-where { font-size: 12px; color: var(--ink-3); overflow-wrap: anywhere; }
.rc-match-where code { font-size: 11.5px; }
.rc-act { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.rc-confirm { display: grid; gap: 8px; }
.rc-confirm p { margin: 0; font-size: 13px; }
.rc-confirm .go, .rc-confirm .ghost { justify-self: start; }
.rc-compose {
  border-top: 1px solid var(--line-soft);
  padding: 12px 20px 16px;
  display: grid;
  gap: 8px;
}
.rc-compose textarea { width: 100%; resize: vertical; min-height: 64px; font-family: inherit; font-size: 14px; box-sizing: border-box; }
.rc-compose-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.rc-compose-row input[type="url"] { flex: 1 1 180px; min-width: 0; }
.rc-pendings { display: flex; flex-wrap: wrap; gap: 6px; }
.rc-pendings:empty { display: none; }
.rc-pending {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  padding: 3px 6px;
  border: 1px solid var(--line);
  border-radius: 99px;
}
.rc-pending img { width: 22px; height: 22px; object-fit: cover; border-radius: 4px; }
.rc-pending button { border: 0; background: none; cursor: pointer; color: var(--ink-3); padding: 0 2px; }
.rc-compose textarea,
.rc-compose input[type="url"] {
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}

/* ── The TEST-CASE RUN SCREEN (PLAN §14.5, §15.4) that replaces the root-cause cards on a run started
   from a test suite (class names kept from the TMT phase). Finding rows inside are the ordinary .member. */
.comp-suite-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 0 0 4px; }
.comp-suite-sec { margin: 0 0 4px; font-size: 13px; }
.comp-suite-secs { display: grid; gap: 4px; margin: 4px 0; }
.comp-suite-pick { display: flex; align-items: center; gap: 6px; font-size: 13px; }
/* Where the suite's cases ask the audit to look (phase C): chips the person trims before starting. */
.comp-suite-plan { display: grid; gap: 4px; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line, #ddd); }
.comp-suite-plan:empty { display: none; }
.comp-suite-plan-line { margin: 0; font-size: 13px; }
.comp-suite-plan-ids code { font-size: 11.5px; padding: 0 4px; border-radius: 4px; background: var(--chip-bg, rgba(127,127,127,.12)); white-space: nowrap; }
.comp-suite-cov { display: grid; gap: 6px; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line, #ddd); }
.comp-suite-cov-line { margin: 0; font-size: 13px; }
.panel-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.tmt-screen { display: grid; gap: 12px; }
.tmt-head { display: grid; gap: 6px; }
.tmt-run-name { font-size: 17px; margin: 0; }
.tmt-sub, .tmt-post { margin: 0; font-size: 12.5px; color: var(--ink-2); }
.tmt-filters { display: flex; flex-wrap: wrap; gap: 6px; }
.tmt-filter { font: inherit; font-size: 12.5px; border: 1px solid var(--line); background: transparent; color: var(--ink); border-radius: 999px; padding: 3px 11px; cursor: pointer; }
.tmt-filter.on { border-color: currentColor; font-weight: 600; }
.tmt-cases { display: grid; gap: 10px; }
.tmt-case { border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; display: grid; gap: 8px; background: var(--card, transparent); }
.tmt-case.tmt-focus { outline: 2px solid var(--ok); outline-offset: 1px; }
.tmt-case-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; }
.tmt-id { font: 600 12px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--ink-2); }
.tmt-title { flex: 1 1 220px; margin: 0; font-size: 14.5px; }
.tmt-status { border-radius: 999px; padding: 1px 10px; font-size: 12px; font-weight: 600; white-space: nowrap; border: 1px solid currentColor; }
.tmt-ok { color: var(--ok); }
.tmt-bad { color: var(--crit); }
.tmt-warn { color: #a86400; }
.tmt-muted { color: var(--ink-2); border-color: var(--line); }
.tmt-stale { font-size: 11.5px; color: var(--ink-2); border: 1px dashed var(--line); border-radius: 6px; padding: 0 6px; }
.tmt-meta { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12.5px; color: var(--ink-2); }
.tmt-meta code { font-size: 11.5px; }
.tmt-reason { margin: 0; font-size: 13px; color: var(--ink-2); }
.tmt-box h4 { margin: 0 0 4px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-2); }
.tmt-obs { list-style: none; margin: 0; padding: 0; font-size: 13px; }
.tmt-obs li { padding: 3px 0; border-top: 1px dashed var(--line); }
.tmt-obs li:first-child { border-top: 0; }
.tmt-vp { display: inline-block; min-width: 64px; font-weight: 600; }
.tmt-thumbs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.tmt-thumb { width: 132px; padding: 0; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; background: none; cursor: zoom-in; }
.tmt-thumb .shot-crop { max-height: 84px; }
.tmt-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.tmt-other { margin-top: 10px; }
.tmt-other h3 { margin: 0 0 2px; font-size: 15px; }

/* Execution head = Test management head (.tm-head): actions + module switcher on one line, bottom-aligned. */
.project-run-head.tm-head {
  align-items: flex-end;
}
.project-run-head.tm-head .project-run-actions {
  align-items: center;
}
.project-run-head.tm-head .project-run-meta {
  margin-top: 4px;
  font-size: 14px;
}
/* Project name on the Execution and Configure heads — same size as Test management's .tm-project-name. */
.head-name {
  margin: 0;
  font-size: 28px;
  line-height: 1.2;
}
#project-eyebrow {
  margin: 0 0 2px;
}

/* ── Header fit ───────────────────────────────────────────────────────────
 * With Test management's search + user chip the header holds seven items. Never wrap the brand or
 * a chip; below 1180px the pills go icon-only (title= gives the name on hover), below 900px the
 * "QA Console" wordmark goes too. The search box gives way first. */
.brand,
.brand-name,
.tm-authoff {
  white-space: nowrap;
  flex-shrink: 0;
}
.top-actions {
  min-width: 0;
}
.tm-header,
.tm-search {
  min-width: 0;
  flex-shrink: 1;
}
@media (max-width: 1180px) {
  .top-link {
    padding: 8px 11px;
  }
  .top-link-label {
    display: none;
  }
}
@media (max-width: 900px) {
  .brand-divider,
  .brand-name {
    display: none;
  }
}

/* ── Footer at the bottom ─────────────────────────────────────────────────
 * Short screens (an empty suite list, milestones) left the footer floating mid-window. */
body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
body > .main {
  flex: 1 0 auto;
  width: 100%;
}
body > .foot {
  width: 100%;
}
/* Phone: the action row takes its own line under the brand; search fills it. */
@media (max-width: 640px) {
  .top-inner {
    flex-wrap: wrap;
    row-gap: 10px;
  }
  .top-actions {
    width: 100%;
  }
  .tm-header,
  .tm-search {
    flex: 1 1 auto;
  }
  .tm-authoff {
    display: none;
  }
}

/* PLAN §14.6: the check matrix's Generated family */
.matrix-generated { margin-top: 18px; display: grid; gap: 8px; }
.matrix-gen-title { font-size: 17px; margin: 0; display: flex; gap: 8px; align-items: baseline; }
.matrix-gen-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.matrix-gen-table th, .matrix-gen-table td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line, #e5e5e5); vertical-align: top; }

/* ── Modules (owner ask 2026-10-08): the header's module switch, beside the brand ── */
.top-left {
  display: flex;
  align-items: center;
  gap: 28px;
  min-width: 0;
}
.mod-switch {
  display: flex;
  align-items: center;
  gap: 4px;
}
.mod-tab {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 7px 12px;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--ink-2);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 0.16s ease, color 0.16s ease;
}
.mod-tab:hover {
  color: var(--accent);
}
.mod-tab.on {
  color: var(--accent);
  background: var(--accent-soft);
}
/* The landing is one screen dressed per module: Test management has no quick audit, no recent
   audits and no execution tabs. */
#screen-entry.is-tm #quick-wrap,
#screen-entry.is-tm #recent,
#screen-entry.is-tm .exec-tabs {
  display: none;
}
#screen-entry .exec-tabs {
  margin-top: 4px;
}
@media (max-width: 900px) {
  .top-left {
    gap: 14px;
  }
  .mod-tab {
    padding: 6px 8px;
    font-size: 13px;
  }
}

/* ── Suite runs (2026-10-08): the audit form, progress and results opened from a test suite ── */
.suite-back {
  display: block;
  margin: 0 0 4px;
}
#screen-project-run.suite-mode .tm-crumbs {
  display: none;
}
#screen-project-run.suite-mode #run-mode,
#screen-project-run.suite-mode #comp-action-wrap,
#screen-project-run.suite-mode #comp-areas,
#screen-project-run.suite-mode #comp-planner-row,
#screen-project-run.suite-mode #comp-plan,
#screen-project-run.suite-mode #comp-gaps {
  display: none !important;
}
#screen-project-run.suite-mode.suite-cov #run-coverage {
  display: none !important;
}
#comp-jira-key[readonly] {
  background: var(--line-soft);
  color: var(--ink-2);
}
.comp-suite-setup {
  margin: 10px 0 0;
  padding: 8px 12px;
  border-radius: 8px;
  background: var(--accent-soft);
  font-size: 13px;
}
.prog-suite {
  margin: 6px 0 0;
  color: var(--ink-2);
  font-size: 14px;
}
.suite-banner {
  margin: 0 0 18px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.suite-banner-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.suite-banner-eyebrow {
  margin: 0 0 2px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.suite-banner h2 {
  margin: 0;
  font-size: 20px;
}
.suite-banner-sub {
  margin: 4px 0 0;
  color: var(--ink-2);
  font-size: 13px;
}
.suite-banner-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
}
.suite-banner-counts li {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 99px;
  font-size: 13px;
}
.suite-banner-counts b {
  font-size: 16px;
}
.suite-banner-counts .bad b {
  color: var(--crit);
}
.suite-banner-counts .warn b {
  color: var(--high);
}
.suite-banner-counts .ok b {
  color: var(--ok);
}
.suite-only {
  display: none;
}
#screen-project-run.suite-mode .suite-only {
  display: block;
}
#screen-project-run.suite-mode #comp-jira .not-suite,
#screen-project-run.suite-mode #comp-jira .comp-figma-opt {
  display: none;
}

/* The module switch sits in the MIDDLE of the header (owner, 2026-10-08): brand left, which app you are
   in centre, tool-level settings and the user right. Three columns with equal outer tracks keep it
   truly centred whatever the brand and the user chip measure. */
.top-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
}
.top-inner > .top-actions {
  justify-self: end;
}
.mod-switch {
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--surface);
}
.mod-tab {
  padding: 6px 16px;
}
.mod-tab.on {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent);
}
