/* War Room — Studio Artifice design language.
   Fragment Mono + Helvetica Now Display, near-black ground, lime and cyan
   accents, matching the Idler / AFXHQ system. */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --ground: #0a0a0a;
  --panel: #111111;
  --ink: #fcfaf5;
  --lime: #d4f034;
  --cyan: #34e0f0;
  --amber: #f0b234;
  --red: #f0556b;
  --rule: #262626;
  --rule-hi: #3a3a3a;
  --muted: #8a8a8a;
  --dim: #5c5c5c;
  --mono: "Fragment Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --sans: "Helvetica Now Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}

html, body { height: 100%; background: var(--ground); color: var(--ink); }
body { font-family: var(--sans); line-height: 1.45; -webkit-font-smoothing: antialiased; overflow: hidden; }

/* ── shell ─────────────────────────────────────────────────────────────── */
.shell { display: flex; flex-direction: column; height: 100dvh; }

.topbar {
  display: flex; align-items: center; gap: 1rem;
  padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--rule); flex: 0 0 auto;
}
.topbar-title { font-family: var(--mono); font-size: 0.8125rem; letter-spacing: -0.01em; }
.topbar-client { color: var(--muted); font-size: 0.8125rem; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 0.75rem; }

.addr { font-family: var(--mono); font-size: 0.75rem; color: var(--lime); }
.btn-reset { padding: 0.25rem 0.6rem; font-size: 0.5625rem; color: var(--muted); }
.btn-reset:hover { color: var(--ink); }

/* ── needs-you ────────────────────────────────────────────────────────────
   A BANNER, not a container.

   Boxing the open items made two small cards stretch to a full column with a
   third of the screen empty under them, and split the page into two grids with
   different rhythms. The ask is one line; the items belong in the same grid as
   everything else, marked and placed first. Emphasis from colour and order,
   not from a second layout. */
/* Slimmer than it was: "Needs you" is now a filter chip with a live count, so
   the banner only has to say what to do about it, not carry the emphasis. */
.needs-bar {
  display: flex; align-items: center; gap: 0.75rem;
  border-left: 3px solid var(--amber); background: rgba(240, 178, 52, 0.06);
  padding: 0.5rem 0.8rem; margin-bottom: 0.9rem;
}
.needs-count {
  font-family: var(--mono); font-size: 0.6875rem; flex: 0 0 auto;
  width: 1.4rem; height: 1.4rem; display: grid; place-items: center;
  background: var(--amber); color: var(--ground); border-radius: 100px;
}
.needs-copy { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; min-width: 0; }
.needs-copy strong { font-weight: 500; font-size: 0.8125rem; white-space: nowrap; }
.needs-sub { font-size: 0.75rem; color: var(--muted); }
.needs-jump {
  margin-left: auto; flex: 0 0 auto; cursor: pointer;
  font-family: var(--mono); font-size: 0.625rem; text-transform: uppercase;
  letter-spacing: 0.06em; background: transparent; color: var(--amber);
  border: 1px solid var(--amber); border-radius: 2px; padding: 0.3rem 0.6rem;
}
.needs-jump:hover { background: var(--amber); color: var(--ground); }

/* An open card carries the emphasis itself, so it reads as urgent wherever it
   sits in the grid. */
.disp-open { border-left: 2px solid var(--amber); padding-left: 0.75rem; }
.disp-flag {
  font-family: var(--mono); font-size: 0.5625rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--amber); margin-bottom: 0.15rem;
}

/* ── change key + list ────────────────────────────────────────────────────
   The overlay's colours mean nothing without a legend, and "26 elements
   outlined" is a count rather than a summary. This says what each colour
   means and what actually changed, grouped by kind. */
/* The key stays put while the list under it scrolls - a legend you have to
   scroll back to find is not doing its job. */
.change-key { flex: 0 1 auto; padding: 0 1.5rem; min-height: 0; display: flex; flex-direction: column; max-height: 28vh; }
.change-key .key-row {
  position: sticky; top: 0; z-index: 1; background: var(--ground);
  padding: 0.6rem 0 0.5rem; border-bottom: 1px solid var(--rule);
}
.key-list { overflow-y: auto; min-height: 0; padding-bottom: 0.5rem; }
.key-row {
  display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap;
  font-family: var(--mono); font-size: 0.625rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.key-row .key-swatch:not(:first-child) { margin-left: 0.9rem; }
.key-swatch { width: 0.7rem; height: 0.7rem; border: 2px solid var(--cyan); display: inline-block; }
.key-swatch[data-k="insert"] { border-color: var(--lime); }
.key-swatch[data-k="remove"] { border-color: var(--red); }
.key-hint { margin-left: auto; color: var(--dim); text-transform: none; letter-spacing: 0; }

.key-list { margin-top: 0.5rem; display: grid; gap: 0.15rem; }
.key-item {
  display: grid; grid-template-columns: 0.7rem 11rem 1fr; gap: 0.5rem;
  align-items: baseline; font-size: 0.75rem; padding: 0.15rem 0;
}
.key-item .key-swatch { align-self: center; }
.key-sel { font-family: var(--mono); font-size: 0.625rem; color: var(--cyan); }
.key-what { color: var(--muted); }
.key-what b { color: var(--ink); font-weight: 400; }

/* ── expanded previews ────────────────────────────────────────────────────
   The comparison IS the review, so it gets the option of the whole screen.
   Everything else on the stage collapses rather than being hidden, so leaving
   the mode does not feel like navigating somewhere new. */
.previews-expanded .stages,
.previews-expanded .wb-tabs,
.previews-expanded .wb-panel,
.previews-expanded .panel-foot,
.previews-expanded .footer { display: none; }
.previews-expanded .panel-head p { display: none; }
/* The pages are ~900px tall at 1440, so a full-height pane leaves half of
   itself empty. Let the frames size to their content and sit at the top
   instead of stretching. */
.previews-expanded .wb-frames { min-height: 0; padding-bottom: 1rem; align-items: start; }
.previews-expanded .frame-wrap { flex: 0 0 auto; }
.previews-expanded .panel-head { padding-bottom: 0.5rem; }
.previews-expanded .stage-body { overflow-y: auto; }

.toggle-gap { width: 0.75rem; display: inline-block; }

/* The two controls that change what you can SEE were the same weight as the
   viewport numbers and got lost. They are the primary tools of this stage. */
#btn-highlight, #btn-expand {
  font-size: 0.6875rem; padding: 0.4rem 0.8rem;
  border-color: var(--rule-hi); color: var(--ink);
}
#btn-highlight::before { content: '◧ '; color: var(--cyan); }
#btn-expand::before { content: '⤢ '; color: var(--cyan); }
#btn-highlight:hover, #btn-expand:hover { border-color: var(--cyan); }
.wb-tab[aria-pressed="true"] {
  color: var(--ground); background: var(--cyan); border-color: var(--cyan);
}
.wb-tab[aria-pressed="true"]::before { color: var(--ground); }

/* Step 1 shows the PARSE, not a second copy of the pasted text. */
.parse-summary { font-size: 0.8125rem; color: var(--muted); margin-top: 1rem; }
.parse-summary strong { color: var(--ink); font-weight: 500; }
.parse-groups { display: grid; grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr)); gap: 1rem; margin-top: 1rem; align-content: start; }
.parse-group { border: 1px solid var(--rule); padding: 0.6rem 0.75rem; display: grid; gap: 0.4rem; align-content: start; }
.parse-channel {
  font-family: var(--mono); font-size: 0.625rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--cyan); display: flex; gap: 0.5rem;
}
.parse-channel span { margin-left: auto; color: var(--dim); }
.parse-item { display: grid; grid-template-columns: 3.25rem 1fr; gap: 0.5rem; align-items: baseline; }
.parse-id { font-family: var(--mono); font-size: 0.625rem; color: var(--dim); }
.parse-text { font-size: 0.75rem; color: var(--muted); }

.state-pill {
  font-family: var(--mono); font-size: 0.6875rem; text-transform: uppercase;
  letter-spacing: 0.06em; padding: 0.25rem 0.6rem; border: 1px solid var(--rule-hi);
  border-radius: 100px; color: var(--muted);
}
.state-pill[data-state="COMMITTED"] { color: var(--ground); background: var(--lime); border-color: var(--lime); }
.state-pill[data-state="BLOCKED"] { color: var(--red); border-color: var(--red); }
.state-pill[data-state="AWAITING_COMMIT"] { color: var(--lime); border-color: var(--lime); }
.state-pill[data-running] { color: var(--cyan); border-color: var(--cyan); }

/* ── stages ──────────────────────────────────────────────────────────────
   Four numbered steps, in the order the work happens. One owns the screen at a
   time: five things competing for one screen meant none had room to be read,
   and the previews in particular were unusable. */
.stages {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border-bottom: 1px solid var(--rule); flex: 0 0 auto;
}
.stage {
  display: flex; align-items: center; gap: 0.6rem; text-align: left;
  background: transparent; border: 0; border-right: 1px solid var(--rule);
  padding: 0.7rem 1rem; cursor: pointer; color: var(--muted);
}
.stage:last-child { border-right: 0; }
.stage:hover { background: var(--panel); }
.stage[aria-selected="true"] { color: var(--ink); background: var(--panel); }
.stage[aria-selected="true"] .stage-n { background: var(--lime); color: var(--ground); border-color: var(--lime); }
.stage-n {
  font-family: var(--mono); font-size: 0.625rem; flex: 0 0 auto;
  width: 1.35rem; height: 1.35rem; display: grid; place-items: center;
  border: 1px solid var(--rule-hi); border-radius: 100px; color: var(--muted);
}
.stage-text { display: grid; gap: 0.1rem; min-width: 0; }
.stage-label { font-size: 0.8125rem; letter-spacing: -0.01em; }
.stage-sub {
  font-family: var(--mono); font-size: 0.5625rem; color: var(--dim);
  text-transform: uppercase; letter-spacing: 0.06em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A stage with nothing in it yet reads as unavailable rather than broken. */
.stage[data-ready="no"] { opacity: 0.5; }

.stage-body { flex: 1 1 auto; min-height: 0; display: flex; }
.panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.panel[hidden] { display: none; }

.panel-head { flex: 0 0 auto; padding: 1.1rem 1.5rem 0.75rem; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 1rem; }
.panel-head h1 { font-size: 1.0625rem; font-weight: 500; letter-spacing: -0.02em; }
.panel-head p { flex: 1 1 22rem; font-size: 0.8125rem; color: var(--muted); max-width: 46rem; }
.head-count { font-family: var(--mono); font-size: 0.6875rem; color: var(--dim); }
.panel-head .viewport-toggle { margin-left: auto; }

.panel-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 1.5rem 1.5rem; }
.panel-foot {
  flex: 0 0 auto; border-top: 1px solid var(--rule); padding: 0.75rem 1.5rem;
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
}
.panel-foot .gate-reason { margin-top: 0; }

/* The raw feedback list on step 1 — provenance only, no decisions yet. */
.raw-list { display: grid; gap: 0.4rem; margin-top: 1rem; }
.raw-item { border-left: 2px solid var(--rule); padding: 0.35rem 0 0.35rem 0.7rem; }
.raw-text { font-size: 0.8125rem; }
.raw-src { font-family: var(--mono); font-size: 0.625rem; color: var(--dim); margin-top: 0.1rem; }

/* ── the changeset table ──────────────────────────────────────────────────
   16 fully-expanded cards meant no hierarchy and no density control - every
   item shouting at the same volume, 4000px of form fields. One row per item,
   expanding on demand, with filters carrying live counts.

   Scanning 16 rows is a glance. Scanning 16 cards was a scroll. */
.filters { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; padding: 0 0 0.75rem; }
.fgap { flex: 0 0 0.75rem; }
.fchip {
  font-family: var(--mono); font-size: 0.625rem; text-transform: uppercase;
  letter-spacing: 0.06em; padding: 0.3rem 0.55rem; cursor: pointer;
  background: transparent; border: 1px solid var(--rule); color: var(--muted);
  border-radius: 2px; display: inline-flex; gap: 0.4rem; align-items: center;
}
.fchip span { color: var(--dim); }
.fchip:hover { border-color: var(--rule-hi); color: var(--ink); }
.fchip[aria-selected="true"] { color: var(--ink); border-color: var(--ink); }
.fchip-open[aria-selected="true"] { color: var(--ground); background: var(--amber); border-color: var(--amber); }
.fchip-open[aria-selected="true"] span { color: var(--ground); }
.fchip-open { color: var(--amber); border-color: rgba(240,178,52,0.4); }
.fchip-warn { color: var(--muted); }

.rows { border-top: 1px solid var(--rule); }
.r { border-bottom: 1px solid var(--rule); }
.r-open { background: rgba(240, 178, 52, 0.045); }
.r-open .r-line { border-left: 2px solid var(--amber); }

.r-line {
  width: 100%; display: grid; grid-template-columns: 4.5rem minmax(0, 1.6fr) minmax(0, 1fr) auto;
  gap: 0.75rem; align-items: baseline; text-align: left; cursor: pointer;
  background: transparent; border: 0; padding: 0.55rem 0.75rem; color: var(--ink);
}
.r-line:hover { background: var(--panel); }
.r-expanded > .r-line { background: var(--panel); }

.r-text {
  font-size: 0.8125rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.r-expanded .r-text { white-space: normal; }
.r-effect {
  font-family: var(--mono); font-size: 0.625rem; color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.r-op { color: var(--cyan); }
.r-none { color: var(--dim); font-style: italic; }
.r-marks { display: flex; gap: 0.4rem; align-items: center; font-size: 0.75rem; }
.r-mark { color: var(--amber); }
.r-chev { color: var(--dim); font-size: 0.625rem; }

.r-body { padding: 0 0.75rem 0.85rem 5.25rem; display: grid; gap: 0.45rem; }
.r-meta { font-family: var(--mono); font-size: 0.625rem; color: var(--dim); }
.r-why { font-size: 0.8125rem; color: var(--muted); }

@media (max-width: 70rem) {
  .r-line { grid-template-columns: 4.5rem 1fr auto; }
  .r-effect { display: none; }
  .r-body { padding-left: 0.75rem; }
}

/* ── needs-you ────────────────────────────────────────────────────────────
   A BANNER, not a container.

   Boxing the open items made two small cards stretch to a full column with a
   third of the screen empty under them, and split the page into two grids with
   different rhythms. The ask is one line; the items belong in the same grid as
   everything else, marked and placed first. Emphasis from colour and order,
   not from a second layout. */
/* Slimmer than it was: "Needs you" is now a filter chip with a live count, so
   the banner only has to say what to do about it, not carry the emphasis. */
.needs-bar {
  display: flex; align-items: center; gap: 0.75rem;
  border-left: 3px solid var(--amber); background: rgba(240, 178, 52, 0.06);
  padding: 0.5rem 0.8rem; margin-bottom: 0.9rem;
}
.needs-count {
  font-family: var(--mono); font-size: 0.6875rem; flex: 0 0 auto;
  width: 1.4rem; height: 1.4rem; display: grid; place-items: center;
  background: var(--amber); color: var(--ground); border-radius: 100px;
}
.needs-copy { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; min-width: 0; }
.needs-copy strong { font-weight: 500; font-size: 0.8125rem; white-space: nowrap; }
.needs-sub { font-size: 0.75rem; color: var(--muted); }
.needs-jump {
  margin-left: auto; flex: 0 0 auto; cursor: pointer;
  font-family: var(--mono); font-size: 0.625rem; text-transform: uppercase;
  letter-spacing: 0.06em; background: transparent; color: var(--amber);
  border: 1px solid var(--amber); border-radius: 2px; padding: 0.3rem 0.6rem;
}
.needs-jump:hover { background: var(--amber); color: var(--ground); }

/* An open card carries the emphasis itself, so it reads as urgent wherever it
   sits in the grid. */
.disp-open { border-left: 2px solid var(--amber); padding-left: 0.75rem; }
.disp-flag {
  font-family: var(--mono); font-size: 0.5625rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--amber); margin-bottom: 0.15rem;
}

/* ── change key + list ────────────────────────────────────────────────────
   The overlay's colours mean nothing without a legend, and "26 elements
   outlined" is a count rather than a summary. This says what each colour
   means and what actually changed, grouped by kind. */
/* The key stays put while the list under it scrolls - a legend you have to
   scroll back to find is not doing its job. */
.change-key { flex: 0 1 auto; padding: 0 1.5rem; min-height: 0; display: flex; flex-direction: column; max-height: 28vh; }
.change-key .key-row {
  position: sticky; top: 0; z-index: 1; background: var(--ground);
  padding: 0.6rem 0 0.5rem; border-bottom: 1px solid var(--rule);
}
.key-list { overflow-y: auto; min-height: 0; padding-bottom: 0.5rem; }
.key-row {
  display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap;
  font-family: var(--mono); font-size: 0.625rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.key-row .key-swatch:not(:first-child) { margin-left: 0.9rem; }
.key-swatch { width: 0.7rem; height: 0.7rem; border: 2px solid var(--cyan); display: inline-block; }
.key-swatch[data-k="insert"] { border-color: var(--lime); }
.key-swatch[data-k="remove"] { border-color: var(--red); }
.key-hint { margin-left: auto; color: var(--dim); text-transform: none; letter-spacing: 0; }

.key-list { margin-top: 0.5rem; display: grid; gap: 0.15rem; }
.key-item {
  display: grid; grid-template-columns: 0.7rem 11rem 1fr; gap: 0.5rem;
  align-items: baseline; font-size: 0.75rem; padding: 0.15rem 0;
}
.key-item .key-swatch { align-self: center; }
.key-sel { font-family: var(--mono); font-size: 0.625rem; color: var(--cyan); }
.key-what { color: var(--muted); }
.key-what b { color: var(--ink); font-weight: 400; }

/* ── expanded previews ────────────────────────────────────────────────────
   The comparison IS the review, so it gets the option of the whole screen.
   Everything else on the stage collapses rather than being hidden, so leaving
   the mode does not feel like navigating somewhere new. */
.previews-expanded .stages,
.previews-expanded .wb-tabs,
.previews-expanded .wb-panel,
.previews-expanded .panel-foot,
.previews-expanded .footer { display: none; }
.previews-expanded .panel-head p { display: none; }
/* The pages are ~900px tall at 1440, so a full-height pane leaves half of
   itself empty. Let the frames size to their content and sit at the top
   instead of stretching. */
.previews-expanded .wb-frames { min-height: 0; padding-bottom: 1rem; align-items: start; }
.previews-expanded .frame-wrap { flex: 0 0 auto; }
.previews-expanded .panel-head { padding-bottom: 0.5rem; }
.previews-expanded .stage-body { overflow-y: auto; }

.toggle-gap { width: 0.75rem; display: inline-block; }

/* The two controls that change what you can SEE were the same weight as the
   viewport numbers and got lost. They are the primary tools of this stage. */
#btn-highlight, #btn-expand {
  font-size: 0.6875rem; padding: 0.4rem 0.8rem;
  border-color: var(--rule-hi); color: var(--ink);
}
#btn-highlight::before { content: '◧ '; color: var(--cyan); }
#btn-expand::before { content: '⤢ '; color: var(--cyan); }
#btn-highlight:hover, #btn-expand:hover { border-color: var(--cyan); }
.wb-tab[aria-pressed="true"] {
  color: var(--ground); background: var(--cyan); border-color: var(--cyan);
}
.wb-tab[aria-pressed="true"]::before { color: var(--ground); }

/* Step 1 shows the PARSE, not a second copy of the pasted text. */
.parse-summary { font-size: 0.8125rem; color: var(--muted); margin-top: 1rem; }
.parse-summary strong { color: var(--ink); font-weight: 500; }
.parse-groups { display: grid; grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr)); gap: 1rem; margin-top: 1rem; align-content: start; }
.parse-group { border: 1px solid var(--rule); padding: 0.6rem 0.75rem; display: grid; gap: 0.4rem; align-content: start; }
.parse-channel {
  font-family: var(--mono); font-size: 0.625rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--cyan); display: flex; gap: 0.5rem;
}
.parse-channel span { margin-left: auto; color: var(--dim); }
.parse-item { display: grid; grid-template-columns: 3.25rem 1fr; gap: 0.5rem; align-items: baseline; }
.parse-id { font-family: var(--mono); font-size: 0.625rem; color: var(--dim); }
.parse-text { font-size: 0.75rem; color: var(--muted); }

.state-pill {
  font-family: var(--mono); font-size: 0.6875rem; text-transform: uppercase;
  letter-spacing: 0.06em; padding: 0.25rem 0.6rem; border: 1px solid var(--rule-hi);
  border-radius: 100px; color: var(--muted);
}
.state-pill[data-state="COMMITTED"] { color: var(--ground); background: var(--lime); border-color: var(--lime); }
.state-pill[data-state="BLOCKED"] { color: var(--red); border-color: var(--red); }
.state-pill[data-state="AWAITING_COMMIT"] { color: var(--lime); border-color: var(--lime); }
.state-pill[data-running] { color: var(--cyan); border-color: var(--cyan); }

/* ── stages ──────────────────────────────────────────────────────────────
   Four numbered steps, in the order the work happens. One owns the screen at a
   time: five things competing for one screen meant none had room to be read,
   and the previews in particular were unusable. */
.stages {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border-bottom: 1px solid var(--rule); flex: 0 0 auto;
}
.stage {
  display: flex; align-items: center; gap: 0.6rem; text-align: left;
  background: transparent; border: 0; border-right: 1px solid var(--rule);
  padding: 0.7rem 1rem; cursor: pointer; color: var(--muted);
}
.stage:last-child { border-right: 0; }
.stage:hover { background: var(--panel); }
.stage[aria-selected="true"] { color: var(--ink); background: var(--panel); }
.stage[aria-selected="true"] .stage-n { background: var(--lime); color: var(--ground); border-color: var(--lime); }
.stage-n {
  font-family: var(--mono); font-size: 0.625rem; flex: 0 0 auto;
  width: 1.35rem; height: 1.35rem; display: grid; place-items: center;
  border: 1px solid var(--rule-hi); border-radius: 100px; color: var(--muted);
}
.stage-text { display: grid; gap: 0.1rem; min-width: 0; }
.stage-label { font-size: 0.8125rem; letter-spacing: -0.01em; }
.stage-sub {
  font-family: var(--mono); font-size: 0.5625rem; color: var(--dim);
  text-transform: uppercase; letter-spacing: 0.06em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A stage with nothing in it yet reads as unavailable rather than broken. */
.stage[data-ready="no"] { opacity: 0.5; }

.stage-body { flex: 1 1 auto; min-height: 0; display: flex; }
.panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.panel[hidden] { display: none; }

.panel-head { flex: 0 0 auto; padding: 1.1rem 1.5rem 0.75rem; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 1rem; }
.panel-head h1 { font-size: 1.0625rem; font-weight: 500; letter-spacing: -0.02em; }
.panel-head p { flex: 1 1 22rem; font-size: 0.8125rem; color: var(--muted); max-width: 46rem; }
.head-count { font-family: var(--mono); font-size: 0.6875rem; color: var(--dim); }
.panel-head .viewport-toggle { margin-left: auto; }

.panel-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 1.5rem 1.5rem; }
.panel-foot {
  flex: 0 0 auto; border-top: 1px solid var(--rule); padding: 0.75rem 1.5rem;
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
}
.panel-foot .gate-reason { margin-top: 0; }

/* The raw feedback list on step 1 — provenance only, no decisions yet. */
.raw-list { display: grid; gap: 0.4rem; margin-top: 1rem; }
.raw-item { border-left: 2px solid var(--rule); padding: 0.35rem 0 0.35rem 0.7rem; }
.raw-text { font-size: 0.8125rem; }
.raw-src { font-family: var(--mono); font-size: 0.625rem; color: var(--dim); margin-top: 0.1rem; }

/* Two columns of cards on step 2, so a 16-item changeset is scannable. */
/* The multi-column rule was for the old card wall. A TABLE must be one column
   or an expanded row breaks across the gutter mid-content, which is what it
   did the first time this rendered. */
.rows { columns: auto; }
@media (max-width: 60rem) { .disp-grid { grid-template-columns: 1fr; } }

/* ── intake ────────────────────────────────────────────────────────────── */
.intake textarea {
  width: 100%; min-height: 7rem; background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); padding: 0.6rem; font-family: var(--mono);
  font-size: 0.6875rem; line-height: 1.5; resize: vertical;
}
.intake textarea:focus { outline: 1px solid var(--rule-hi); border-color: var(--rule-hi); }

.btn {
  font-family: var(--mono); font-size: 0.6875rem; text-transform: uppercase;
  letter-spacing: 0.06em; padding: 0.5rem 0.9rem; border-radius: 2px;
  border: 1px solid var(--rule-hi); background: transparent; color: var(--ink);
  cursor: pointer;
}
.btn:hover:not(:disabled) { border-color: var(--ink); }
/* A disabled primary button must not read as actionable. Dimming a lime fill
   leaves an olive that still looks clickable, so a disabled .btn-go drops the
   fill entirely and becomes an outline. */
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-go { background: var(--lime); border-color: var(--lime); color: var(--ground); font-weight: 500; }
.btn-go:hover:not(:disabled) { filter: brightness(1.08); }
.btn-go:disabled { background: transparent; border-color: var(--rule-hi); color: var(--muted); opacity: 0.7; }
.btn-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; margin-top: 0.75rem; }

/* The gate reason. Central to the demo: a disabled button that does not say
   why proves nothing. */
.gate-reason {
  font-family: var(--mono); font-size: 0.6875rem; color: var(--amber);
  border-left: 2px solid var(--amber); padding: 0.35rem 0 0.35rem 0.6rem; margin-top: 0.6rem;
}
.gate-reason.ok { color: var(--lime); border-color: var(--lime); }
.gate-detail { color: var(--dim); display: block; margin-top: 0.2rem; }

/* ── disposition table ─────────────────────────────────────────────────── */
.disp { border-top: 1px solid var(--rule); padding: 0.6rem 0; display: grid; gap: 0.3rem; }
.disp-top { display: flex; align-items: flex-start; gap: 0.5rem; }
.disp-raw { font-size: 0.8125rem; flex: 1 1 auto; }
.disp-src { font-family: var(--mono); font-size: 0.625rem; color: var(--dim); }
.disp-why { font-size: 0.75rem; color: var(--muted); }

.tag {
  font-family: var(--mono); font-size: 0.5625rem; text-transform: uppercase;
  letter-spacing: 0.08em; padding: 0.15rem 0.4rem; border-radius: 2px;
  border: 1px solid currentColor; flex: 0 0 auto; white-space: nowrap;
}
.tag[data-d="KEEP"]   { color: var(--muted); }
.tag[data-d="CHANGE"] { color: var(--cyan); }
.tag[data-d="FIX"]    { color: var(--red); }
.tag[data-d="DEFER"]  { color: var(--amber); }
.tag[data-d="OPEN"]   { color: var(--ground); background: var(--amber); border-color: var(--amber); }

.conflict {
  font-family: var(--mono); font-size: 0.625rem; color: var(--amber);
}
/* ── the change itself, on the card ───────────────────────────────────────
   The disposition says what kind of answer this is; the directive says what
   actually happens to the page. Indented under the feedback so the relationship
   reads as "because of this, do that". */
.change-list { display: grid; gap: 0.4rem; margin-top: 0.15rem; }
.change {
  border-left: 2px solid var(--rule-hi); padding: 0.3rem 0 0.3rem 0.6rem;
  display: grid; gap: 0.3rem;
}
.change-head { display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap; }
.change-op {
  font-family: var(--mono); font-size: 0.5625rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--cyan);
}
.change-target { font-family: var(--mono); font-size: 0.625rem; color: var(--muted); }
.change-owner {
  font-family: var(--mono); font-size: 0.5625rem; color: var(--dim);
  margin-left: auto; text-transform: uppercase; letter-spacing: 0.06em;
}
.change-none {
  font-family: var(--mono); font-size: 0.625rem; color: var(--dim);
  padding: 0.2rem 0 0.2rem 0.6rem; border-left: 2px solid var(--rule);
}
/* A CHANGE or FIX with no directive fails the lock gate, so it warns rather
   than sitting quietly. */
[data-disposition="CHANGE"] .change-none[data-testid="change-none"],
[data-disposition="FIX"] .change-none[data-testid="change-none"] { color: var(--amber); border-color: var(--amber); }

.change-input {
  width: 100%; background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); padding: 0.25rem 0.35rem;
  font-family: var(--sans); font-size: 0.75rem; line-height: 1.4; resize: vertical;
}
.change-input.mono { font-family: var(--mono); font-size: 0.6875rem; }
.change-input:focus { outline: none; border-color: var(--cyan); }
.change-input[readonly] { opacity: 0.65; border-color: var(--rule); }

.btn-add-dir {
  justify-self: start; font-family: var(--mono); font-size: 0.625rem;
  text-transform: uppercase; letter-spacing: 0.06em; cursor: pointer;
  background: transparent; border: 1px dashed var(--cyan); color: var(--cyan);
  padding: 0.35rem 0.7rem; border-radius: 2px;
}
.btn-add-dir:hover { background: rgba(52,224,240,0.08); }
.mini-select, .mini-input {
  font-family: var(--mono); font-size: 0.625rem; background: var(--panel);
  color: var(--ink); border: 1px solid var(--rule-hi); padding: 0.1rem 0.25rem;
}
.mini-input { width: 11rem; }
.mini-select:focus, .mini-input:focus { outline: none; border-color: var(--cyan); }

.change-pairs { display: grid; gap: 0.25rem; }
.change-pair { display: grid; grid-template-columns: 8rem 1fr; align-items: center; gap: 0.4rem; }
.change-key { font-family: var(--mono); font-size: 0.625rem; color: var(--dim); }

@media (max-width: 90rem) {
  .change-pair { grid-template-columns: 1fr; gap: 0.15rem; }
}

.disp-edit { display: flex; align-items: center; gap: 0.5rem; }
.edited-flag {
  font-family: var(--mono); font-size: 0.5625rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--amber);
}
/* A hand-edited row is marked so the machine's output and the human's
   override are never visually indistinguishable. */
.disp[data-edited] { border-left: 2px solid var(--amber); padding-left: 0.6rem; }
.disp select {
  font-family: var(--mono); font-size: 0.625rem; background: var(--panel);
  color: var(--ink); border: 1px solid var(--rule-hi); padding: 0.15rem 0.3rem;
}

/* ── right pane: war room + workbench ────────────────────────────────────
   The workbench gets the larger share and a floor: side-by-side previews are
   the point of the pane, and squeezing them to nav-only defeats it. The event
   log scrolls instead, since it is a log. */
.right { display: grid; grid-template-rows: minmax(0, 0.85fr) minmax(26rem, 1.15fr); min-height: 0; }
.right > section { min-height: 0; display: flex; flex-direction: column; }
.right > section + section { border-top: 1px solid var(--rule); }

/* lanes */
.lanes { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; padding: 0 1.5rem 0.75rem; flex: 0 0 auto; }
.lane { border: 1px solid var(--rule); padding: 0.5rem 0.6rem; }
.lane[data-active] { border-color: var(--cyan); }
.lane-role { font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.08em; text-transform: uppercase; }
.lane[data-active] .lane-role { color: var(--cyan); }
.lane-status { font-size: 0.6875rem; color: var(--muted); margin-top: 0.15rem; }
.lane-mode {
  font-family: var(--mono); font-size: 0.5625rem; color: var(--dim);
  margin-top: 0.25rem; text-transform: uppercase; letter-spacing: 0.06em;
}
.lane-mode[data-mode="live"] { color: var(--lime); }

/* events */
.event { border-top: 1px solid var(--rule); padding: 0.6rem 0; }
.event-top { display: flex; align-items: baseline; gap: 0.5rem; }
.event-role { font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.08em; color: var(--cyan); }
.event-seq { font-family: var(--mono); font-size: 0.5625rem; color: var(--dim); margin-left: auto; }
.event-summary { font-size: 0.8125rem; margin-top: 0.1rem; }
.event-why { font-size: 0.75rem; color: var(--muted); margin-top: 0.2rem; }
.chips { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-top: 0.4rem; }
.chip-ev {
  font-family: var(--mono); font-size: 0.5625rem; padding: 0.15rem 0.4rem;
  border: 1px solid var(--rule-hi); border-radius: 100px; color: var(--muted);
  background: transparent; cursor: pointer;
}
.chip-ev:hover { border-color: var(--cyan); color: var(--cyan); }
.chip-ev[data-status="FAIL"], .chip-ev[data-status="ERROR"] { color: var(--red); border-color: var(--red); }
.chip-ev[data-status="PASS"] { color: var(--lime); border-color: var(--lime); }

/* workbench */
.wb-frames { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; padding: 0 1.5rem; flex: 1 1 auto; min-height: 16rem; }
.frame-wrap { display: flex; flex-direction: column; min-height: 12rem; border: 1px solid var(--rule); }
.frame-label {
  font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.06em;
  padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--rule); color: var(--muted);
  display: flex; align-items: center; gap: 0.4rem;
}
.frame-label .badge { margin-left: auto; }
.fbtn {
  font-family: var(--mono); font-size: 0.5625rem; text-transform: uppercase;
  letter-spacing: 0.06em; background: transparent; border: 1px solid var(--rule-hi);
  color: var(--muted); padding: 0.15rem 0.4rem; border-radius: 2px; cursor: pointer;
}
.fbtn:hover { color: var(--cyan); border-color: var(--cyan); }

/* ── device frame ─────────────────────────────────────────────────────────
   A page rendered edge-to-edge in a black pane has no visible boundary, so
   you cannot tell where the design stops and the tool begins - which matters
   most at 375, where the whole point is what fits. */
.device { position: relative; background: #000; border: 1px solid var(--rule-hi); border-radius: 2px; overflow: hidden; }
/* A phone at 375: rounded, thick bezel, speaker slot, and sized to the design
   rather than the pane - the whole point is seeing what fits. */
.device[data-vw="375"] {
  border-radius: 1.75rem; border-width: 10px; border-color: #191919;
  box-shadow: 0 0 0 1px var(--rule-hi), 0 12px 32px rgba(0,0,0,0.6);
  align-self: start;
}
.device[data-vw="375"]::before {
  content: ''; position: absolute; top: 7px; left: 50%; transform: translateX(-50%);
  width: 24%; height: 4px; background: #2a2a2a; border-radius: 100px; z-index: 3;
}
.device[data-vw="375"]::after {
  content: ''; position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%);
  width: 32%; height: 3px; background: #2a2a2a; border-radius: 100px; z-index: 3;
}
.wb-frames { align-items: stretch; }
/* A phone is tall: give the row the height it needs rather than the share a
   1440 preview wants, or the device shrinks to a thumbnail. */
.wb-frames:has(.device[data-vw="375"]) { min-height: 62vh; }

/* ── baseline source ──────────────────────────────────────────────────────
   Deliberately the same shape as the commit target on step 4: pull from here,
   push back to there. An asymmetric pair would suggest the two ends are
   different kinds of thing, and they are not. */
.source {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
  padding: 0 0 0.9rem; font-family: var(--mono); font-size: 0.6875rem;
}
.source-label {
  color: var(--cyan); text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 0.625rem; display: flex; align-items: center; gap: 0.35rem;
}
.source-label::before { content: '⎘'; font-size: 0.8125rem; }

.src-preview {
  border: 1px solid var(--rule); border-left: 2px solid var(--cyan);
  padding: 0.6rem 0.8rem; display: grid; gap: 0.3rem; margin-bottom: 1rem;
}

/* ── commit target ────────────────────────────────────────────────────────
   UI ONLY. Nothing here talks to GitHub; it names the destination and shows
   what a commit WOULD do, which is the part a demo needs to make legible. The
   state label says "not connected" rather than implying a live integration. */
.target {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
  padding: 0.7rem 1.5rem 0.1rem; font-family: var(--mono); font-size: 0.6875rem;
}
.target-label {
  color: var(--cyan); text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 0.625rem; display: flex; align-items: center; gap: 0.35rem;
}
.target-label::before { content: '⎇'; font-size: 0.8125rem; }
.target-input {
  flex: 1 1 22rem; max-width: 34rem; background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule-hi); padding: 0.4rem 0.55rem;
  font-family: var(--mono); font-size: 0.6875rem;
}
.target-input::placeholder { color: var(--dim); }
.target-input:focus { outline: none; border-color: var(--cyan); }
.target-state { color: var(--dim); font-size: 0.625rem; }
.target-state[data-ok] { color: var(--lime); }
.target-state[data-bad] { color: var(--amber); }

.pr-preview {
  margin: 0.5rem 1.5rem 0; border: 1px solid var(--rule); border-left: 2px solid var(--lime);
  padding: 0.6rem 0.8rem; display: grid; gap: 0.3rem;
}
.pr-title { font-size: 0.8125rem; }
.pr-meta { font-family: var(--mono); font-size: 0.625rem; color: var(--dim); }
.pr-files { font-family: var(--mono); font-size: 0.625rem; color: var(--muted); display: grid; gap: 0.15rem; }
.pr-files b { color: var(--cyan); font-weight: 400; }
.pr-note { font-family: var(--mono); font-size: 0.5625rem; color: var(--amber); text-transform: uppercase; letter-spacing: 0.06em; }

/* ── full-screen modal ───────────────────────────────────────────────────
   Expand shows BOTH revisions larger; this shows ONE at full size. Comparing
   and inspecting are different jobs and deserve different affordances. */
.modal { position: fixed; inset: 0; z-index: 50; background: var(--ground); display: flex; flex-direction: column; }
.modal[hidden] { display: none; }
.modal-bar {
  display: flex; align-items: center; gap: 0.75rem; flex: 0 0 auto;
  padding: 0.6rem 1rem; border-bottom: 1px solid var(--rule);
  font-family: var(--mono); font-size: 0.6875rem;
}
.modal-title { color: var(--ink); }
.modal-hint { color: var(--dim); font-size: 0.625rem; }
.modal-bar .fbtn:first-of-type { margin-left: auto; }
.modal-stage { flex: 1 1 auto; min-height: 0; overflow: hidden; display: grid; place-items: start center; padding: 1rem; }
.modal-stage .device { max-width: 100%; }
.modal-stage iframe { border: 0; display: block; transform-origin: top left; }
.frame-label .badge { margin-left: auto; }
/* SCALED PREVIEW.
   A 1440px page inside a ~700px pane showed a fragment of the nav and nothing
   else - the side-by-side comparison was unreadable, which defeats the pane.
   The iframe renders at its true viewport width and is transform-scaled to fit,
   so the whole page is visible and the two revisions are actually comparable.
   Scaling rather than shrinking the viewport matters: at width:100% the page
   would reflow to a narrow layout and you would be comparing a phone rendering
   to a desktop one. */
/* An iframe swallows wheel and pointer events - they land on ITS document, not
   this one - so the zoom/pan surface is a transparent layer above it. The
   previews are for looking at, not clicking into, so nothing is lost. */
.frame-scale {
  flex: 1 1 auto; position: relative; overflow: hidden; min-height: 0;
  background: #000; touch-action: none;
}
.frame-catch { position: absolute; inset: 0; z-index: 2; cursor: grab; background: transparent; }
.frame-catch:active { cursor: grabbing; }
.frame-wrap iframe {
  position: absolute; top: 0; left: 0; border: 0; background: #000;
  transform-origin: top left;
  /* width and scale are set from JS, which is the only place the pane's real
     pixel width is known. */
}

.wb-tabs { display: flex; gap: 0.4rem; padding: 0.75rem 1.5rem 0; flex: 0 0 auto; flex-wrap: wrap; }
.wb-tab {
  font-family: var(--mono); font-size: 0.625rem; text-transform: uppercase;
  letter-spacing: 0.06em; background: transparent; border: 1px solid var(--rule);
  color: var(--muted); padding: 0.25rem 0.6rem; cursor: pointer;
}
.wb-tab[aria-selected="true"] { color: var(--ink); border-color: var(--rule-hi); }
/* The scrolling QA list ran flush into the commit bar, which reads as an
   overlap even when the boxes do not touch. A rule and a fade give the scroll
   an edge. */
.wb-panel {
  overflow-y: auto; padding: 0.5rem 1.5rem 1rem; flex: 0 1 auto;
  min-height: 7rem; max-height: 34vh; border-bottom: 1px solid var(--rule);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.25rem), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 1.25rem), transparent);
}

.check { display: grid; gap: 0.15rem; padding: 0.45rem 0; border-top: 1px solid var(--rule); }
.check-top { display: flex; gap: 0.5rem; align-items: baseline; }
.check-name { font-size: 0.8125rem; }
.check-detail { font-family: var(--mono); font-size: 0.6875rem; color: var(--muted); }
.check-return { font-family: var(--mono); font-size: 0.625rem; color: var(--amber); }
.mark { font-family: var(--mono); font-size: 0.75rem; width: 1rem; flex: 0 0 auto; }
.mark[data-s="PASS"] { color: var(--lime); }
.mark[data-s="FAIL"], .mark[data-s="ERROR"] { color: var(--red); }
.mark[data-s="SKIP"] { color: var(--dim); }

.op { font-family: var(--mono); font-size: 0.6875rem; padding: 0.3rem 0; border-top: 1px solid var(--rule); color: var(--muted); }
.op-sel { color: var(--cyan); }
.key-note { color: var(--muted); font-family: var(--sans); font-size: 0.8125rem; border: 0; padding-bottom: 0.5rem; }

.viewport-toggle { margin-left: auto; display: flex; gap: 0.3rem; }

/* ── footer ────────────────────────────────────────────────────────────── */
.footer {
  flex: 0 0 auto; border-top: 1px solid var(--rule); padding: 0.5rem 1.5rem;
  font-family: var(--mono); font-size: 0.6875rem; color: var(--muted);
  display: flex; align-items: center; gap: 1rem;
}
.footer .right-side { margin-left: auto; display: flex; gap: 0.75rem; align-items: center; }

.mock-badge {
  font-family: var(--mono); font-size: 0.5625rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--dim); border: 1px solid var(--rule);
  padding: 0.1rem 0.35rem; border-radius: 2px;
}
.mock-badge[data-mode="live"] { color: var(--lime); border-color: var(--lime); }

.empty { color: var(--dim); font-size: 0.8125rem; padding: 1rem 0; }

@media (max-width: 60rem) {
  .panes { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .pane + .pane { border-left: 0; border-top: 1px solid var(--rule); }
  .wb-frames { grid-template-columns: 1fr; }
  body { overflow: auto; }
  .shell { height: auto; }
}
