/* Z1X — Minimalism & Swiss, dark, data-dense.
   Palette: dark tech + status green. No webfonts (no-CDN constraint);
   system stacks chosen to echo the JetBrains Mono / IBM Plex Sans pairing. */

:root {
  /* Monochrome, black blended to charcoal - the brand mark is a black glyph
     on a pure white field, and the navy this replaced belonged to no part of
     it. Every pair that carries text was computed against WCAG rather than
     eyeballed: body text is 16-18:1 here and muted text 6.5-7.7:1, well past
     the 4.5:1 floor. Status hues are deliberately unchanged - they carry
     meaning, and retuning them to match a background is how a palette
     refresh quietly breaks a signal. */
  --bg: #0A0A0B;
  --bg-blend: #15161A;   /* the light end of the page wash */
  --card: #141417;
  --fg: #F5F5F7;
  --muted: #1D1D21;
  --muted-fg: #A1A1AA;
  /* TWO border tokens, because one could not serve both jobs. WCAG 1.4.11
     wants 3:1 for a control whose boundary identifies it (inputs, buttons,
     the focus ring's neighbours); a panel divider at that weight looks like
     a table grid. The old single --border met neither properly - it sat at
     2.15:1 on the navy and was used for both. */
  --border: #2E2E34;        /* decorative: panel edges, table rules */
  --border-strong: #6A6A73; /* controls: >=3.14:1 on every surface here */
  --ok: #22C55E;
  --warn: #D29922;
  /* Lightened from #EF4444, which measured 4.46:1 on --muted - just
     under the floor, and --muted is exactly where the "blocked" pill
     sits. The hue is unchanged; only the lightness moved, by the
     smallest step that clears 4.5:1 on every surface (worst case now
     4.97:1). Caught by tests/test_contrast.py, not by looking. */
  --bad: #F25555;
  --link: #7DD3FC;
  --ring: #7DD3FC;

  /* density 8/10 — dashboard scale */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px;

  --sans: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, "Cascadia Code", "Cascadia Mono", Consolas, "Liberation Mono", monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* The blend: black at the top-left, charcoal at the bottom-right, fixed so
     it does not slide under a scrolling page. `background-color` first as
     the fallback - a gradient that fails to paint must not leave white
     behind near-white text. */
  background-color: var(--bg);
  background-image: linear-gradient(145deg, var(--bg) 0%, var(--bg-blend) 55%, var(--bg) 100%);
  background-attachment: fixed;
  color: var(--fg);
  font: 16px/1.5 var(--sans);
  min-height: 100vh;
}

/* ---------- chrome ---------- */

/* A bare `header { display: flex }` rule used to live here, styling the
   hand-rolled header each of the six pages carried. base.html replaced
   those with one `.topbar`, and the orphaned rule then applied to the
   shell itself: it turned the whole header into a flex ROW, so the
   breadcrumb - a block-level second row - floated up beside the section
   nav and the top bar shrank to its content width. Deleted, not edited:
   `.topbar` below owns this now. */

/* h1 used to be the tiny mono wordmark in each page's hand-rolled header.
   base.html made it the page title, so it is styled as one - the old
   uppercase mono treatment now lives only on .stamp and table headers. */
h1 {
  margin: 0 0 var(--s5);
  font: 600 1.35rem/1.25 var(--sans);
  letter-spacing: -0.01em;
  text-transform: none;
}

#stamp, .stamp { color: var(--muted-fg); font: 400 12px/1.5 var(--mono); }

main { padding: var(--s5); }
main.center { max-width: 340px; margin: 12vh auto; }

a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- banners ---------- */

.banner, .err {
  margin: 0 0 var(--s4);
  padding: var(--s2) var(--s3);
  border-radius: 6px;
  border: 1px solid transparent;
  font-size: 14px;
}
.banner { background: #3B2A10; border-color: var(--warn); color: #F5D48A; }
.err    { background: #3B1A1A; border-color: var(--bad);  color: #FCA5A5; }

/* ---------- tables ---------- */

.tablewrap { overflow-x: auto; }        /* tables scroll, they do not break the layout */

table { border-collapse: collapse; width: 100%; min-width: 520px; }

th, td {
  text-align: left;
  padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--muted);
  vertical-align: top;
}

th {
  color: var(--muted-fg);
  font: 600 11px/1.4 var(--mono);
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}

tbody tr { transition: background 200ms ease; }
tbody tr:hover { background: var(--card); }

td.slug { font-family: var(--mono); font-weight: 600; white-space: nowrap; }
td.num  { font-family: var(--mono); text-align: right; width: 1%; white-space: nowrap; }
td.dim  { color: var(--muted-fg); }

/* ---------- status pills: colour AND label, never colour alone ---------- */

.status {
  display: inline-block;
  padding: 1px var(--s2);
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--muted);
  color: var(--muted-fg);
  font: 500 12px/1.6 var(--mono);
  white-space: nowrap;
}
.status.done, .status.succeeded              { color: var(--ok);  border-color: var(--ok); }
.status.running, .status.ready, .status.scheduled { color: var(--warn); border-color: var(--warn); }
.status.blocked, .status.failed              { color: var(--bad); border-color: var(--bad); }
.status.unknown { border-style: dashed; }

/* map health fragment: "fresh"/"stale" render identically without these -
   ok reuses --ok, stale reuses --warn, same as every other status pill. */
.status.ok    { color: var(--ok);   border-color: var(--ok); }
.status.stale { color: var(--warn); border-color: var(--warn); }

/* count chips on the overview */
.count   { font-family: var(--mono); font-size: 12px; color: var(--muted-fg); }
.count b { font-weight: 600; }
.count.running b { color: var(--warn); }
.count.blocked b { color: var(--bad); }
.count.done b    { color: var(--ok); }
.count.zero      { opacity: .45; }

/* ---------- panels ---------- */

.panel {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: var(--s3) var(--s4);
  margin-top: var(--s5);
}
.panel h2 {
  margin: 0 0 var(--s2);
  font: 600 11px/1.4 var(--mono);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted-fg);
}
.panel pre { margin: 0; overflow-x: auto; font: 13px/1.5 var(--mono); color: var(--fg); }
.empty { color: var(--muted-fg); font-size: 14px; }

/* ---------- forms ---------- */

label { display: block; font-size: 14px; color: var(--muted-fg); margin-top: var(--s3); }

input, button {
  font: inherit;
  width: 100%;
  margin-top: var(--s1);
  padding: var(--s2) var(--s3);
  min-height: 44px;                      /* 44px minimum target */
  border-radius: 6px;
  border: 1px solid var(--border-strong);
  background: var(--muted);
  color: var(--fg);
}

button {
  margin-top: var(--s4);
  background: var(--ok);
  border-color: var(--ok);
  color: #0B1220;
  font-weight: 600;
  cursor: pointer;
  transition: filter 200ms ease;
}
button:hover { filter: brightness(1.08); }

/* ---------- accessibility ---------- */

:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------- actions ---------- */

.actions { display: flex; gap: var(--s3); align-items: center; margin-bottom: var(--s4); flex-wrap: wrap; }
.actions button { width: auto; margin: 0; padding: var(--s2) var(--s4); }
/* The safe way out must never look like the irreversible action. Scoped to
   `button.ghost` rather than `.actions button.ghost` so it also reaches the
   Cancel controls inside the dialogs, which live outside .actions. */
button.ghost { background: var(--muted); border-color: var(--border-strong); color: var(--fg); }
/* Not scoped to `.actions` - a disabled button must look disabled wherever
   it lives (e.g. #decide-approve, the safety gate on the task detail page,
   which sits in a `.panel`, not `.actions`). */
button[disabled] { opacity: .5; cursor: default; }

/* Status is conveyed by the text of the message itself (never colour
   alone), so these classes only supply an accent; the wording in
   actions.js/compose.js/decide.js always says "unknown" / "done_late" /
   "failed" plainly. One grouped selector per accent, shared by every result
   element, so the four rules are stated once rather than duplicated per id. */
#act-result, #verify-result, #decide-result, #cb-result {
  font: 13px/1.5 var(--mono); color: var(--muted-fg);
}
#act-result.ok,      #verify-result.ok,      #decide-result.ok,      #cb-result.ok      { color: var(--ok); }
#act-result.warn,    #verify-result.warn,    #decide-result.warn,    #cb-result.warn    { color: var(--warn); }
#act-result.bad,     #verify-result.bad,     #decide-result.bad,     #cb-result.bad     { color: var(--bad); }
#act-result.unknown, #verify-result.unknown, #decide-result.unknown, #cb-result.unknown {
  color: var(--warn); border-bottom: 1px dashed var(--warn); padding-bottom: 1px;
}

/* A browser without <dialog> support renders it as a plain block, showing
   both forms inline in the page even though supportsDialog's JS fallback
   never opens them. */
dialog:not([open]) { display: none; }

dialog {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: var(--s5);
  max-width: 560px;
}
dialog::backdrop { background: rgba(0, 0, 0, .6); }
dialog h2 { margin: 0 0 var(--s3); font: 600 13px/1.4 var(--mono); text-transform: uppercase; letter-spacing: .06em; }
dialog p { margin: 0 0 var(--s2); font-size: 14px; }
dialog menu { display: flex; gap: var(--s3); justify-content: flex-end; margin: var(--s4) 0 0; padding: 0; }
dialog menu button { width: auto; margin: 0; }

/* ---------- checkboxes / radios: never stretched full-width by the
   generic `input { width: 100% }` rule above, and always a proper 44px
   target including the label text they sit beside ---------- */

input[type="checkbox"], input[type="radio"] {
  width: 20px;
  min-height: 20px;
  margin: 0 var(--s2) 0 0;
  vertical-align: middle;
}
/* .choice marks a label that wraps a checkbox/radio instead of a text
   input - a plain CSS class rather than :has(), which is not supported in
   every engine this page has to run in (same constraint as the <dialog>
   fallback above). */
label.choice {
  display: flex;
  align-items: center;
  /* A flex container makes each child its own item and collapses the
     whitespace between them, so `<code>edit_file</code> only` rendered as
     "edit_fileonly". gap restores the separation that the markup intends. */
  gap: var(--s2);
  min-height: 44px;
  margin-top: 0;
  font-size: 14px;
  color: var(--fg);
  cursor: pointer;
}

code {
  font: 13px/1.4 var(--mono);
  background: var(--muted);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px var(--s1);
  color: var(--fg);
}

textarea {
  font: inherit;
  width: 100%;
  margin-top: var(--s1);
  padding: var(--s2) var(--s3);
  border-radius: 6px;
  border: 1px solid var(--border-strong);
  background: var(--muted);
  color: var(--fg);
  resize: vertical;
}
textarea.mono { font: 13px/1.5 var(--mono); }

/* ---------- verdict pills: pass/fail/reported/refused, colour AND label ---------- */

.status.pass                    { color: var(--ok);  border-color: var(--ok); }
.status.fail, .status.refused   { color: var(--bad);  border-color: var(--bad); }
.status.reported                { color: var(--muted-fg); border-style: dashed; }

/* ---------- compose / task-detail panels ---------- */

.compose .panel button.ghost { background: var(--muted); }
#cb-sources label, #cb-rules label { min-height: 44px; }
#cb-result a { display: inline-block; margin-top: var(--s2); }

.checklabel {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  min-height: 44px;
}
.checklabel input { margin-top: var(--s2); }

/* ==========================================================================
   Shell: skip link, brand wash, top bar, sections nav, breadcrumbs.
   Added with base.html, which replaced six copies of a hand-rolled header.
   ========================================================================== */

.skip-link {
  position: absolute;
  left: var(--s4);
  top: -3rem;
  z-index: 100;
  padding: var(--s2) var(--s4);
  background: var(--card);
  border: 1px solid var(--ring);
  border-radius: 6px;
  color: var(--fg);
  text-decoration: none;
  /* Off-screen until focused, then visible. A skip link you cannot see
     once focused is not a skip link. */
  transition: top 120ms ease-out;
}
.skip-link:focus { top: var(--s4); }

/* --- the brand wash ------------------------------------------------------
   The supplied mark is pure black artwork on a light field. invert(1) makes
   it light on dark; mix-blend-mode: screen then drops the dark field
   entirely, because screen against a dark background contributes nothing.
   That pair works whether the source PNG's background is opaque or
   transparent, so it needs no image processing and no extra dependency.

   Opacity is deliberately tiny. Body text sits at #F8FAFC on #0F172A - a
   ratio far past 4.5:1 - and the wash must not erode it. Anything above
   about 0.05 starts to. */
.brand-wash {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;          /* decoration must never eat a click */
  background-image: url("/static/img/brand-mark.png");
  background-repeat: no-repeat;
  /* Bottom-right and partly off-canvas. Centred, it ran straight through
     the overview table and sat on top of the solar system, competing with
     the content it is meant to sit behind. A corner mark reads as a
     watermark; a centred one reads as a mistake. */
  background-position: right -12vw bottom -18vh;
  background-size: min(150vw, 2000px) auto;
  filter: invert(1);
  mix-blend-mode: screen;
  opacity: 0.05;
}

/* Everything real sits above the wash. */
.topbar, main { position: relative; z-index: 1; }

/* --- top bar ------------------------------------------------------------- */

.topbar {
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 88%, black);
  position: sticky;
  top: 0;
  z-index: 10;
}

.topbar-inner {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s3) var(--s5);
  max-width: 1600px;
  margin: 0 auto;
  flex-wrap: wrap;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  text-decoration: none;
  color: var(--fg);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.brand:hover { color: var(--fg); }

.brand-mark {
  width: 34px;
  height: 22px;
  background-image: url("/static/img/brand-mark.png");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 300% auto;    /* the source is a wide canvas; crop to the mark */
  filter: invert(1);
  mix-blend-mode: screen;
  opacity: 0.92;
  flex: none;
}

.brand-name { font-size: 0.95rem; }

/* --- sections nav -------------------------------------------------------- */

.sections {
  display: flex;
  gap: var(--s1);
  margin-right: auto;            /* push the rest of the bar to the right */
}

.sections a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;              /* touch target */
  padding: 0 var(--s3);
  color: var(--muted-fg);
  text-decoration: none;
  font-size: 0.92rem;
  border-bottom: 2px solid transparent;
  transition: color 160ms ease-out, border-color 160ms ease-out;
}
.sections a:hover { color: var(--fg); }

/* "You are here", shown three ways - colour, weight and a rule - so it does
   not depend on colour alone. aria-current carries the same fact to a
   screen reader. */
.sections a[aria-current] {
  color: var(--fg);
  font-weight: 600;
  border-bottom-color: var(--link);
}

/* --- project jump -------------------------------------------------------- */

.project-jump {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

.project-jump select {
  min-height: 36px;
  padding: 0 var(--s2);
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  font: inherit;
  font-size: 0.88rem;
  max-width: 15rem;
}

/* nav.js hides this once it can navigate on change; it stays for the
   JavaScript-off path, which is the whole reason this is a real form. */
.project-jump button[hidden] { display: none; }

.topbar-status {
  font-size: 0.82rem;
  color: var(--muted-fg);
  margin-left: auto;
}

/* --- breadcrumbs --------------------------------------------------------- */

.crumbs {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 0 var(--s5) var(--s3);
  max-width: 1600px;
  margin: 0 auto;
  font-size: 0.85rem;
  color: var(--muted-fg);
}
.crumbs a { color: var(--muted-fg); text-decoration: none; }
.crumbs a:hover { color: var(--fg); text-decoration: underline; }
.crumbs [aria-current="page"] { color: var(--fg); }

/* --- main ---------------------------------------------------------------- */

main {
  max-width: 1600px;
  margin: 0 auto;
  padding: var(--s5);
}
main:focus { outline: none; }    /* focused only as a skip-link target */

@media (max-width: 720px) {
  .topbar-inner { gap: var(--s3); }
  .sections { order: 3; width: 100%; margin-right: 0; }
  .topbar-status { order: 2; }
  .brand-wash { background-size: 260vw auto; opacity: 0.025; }
}

/* Motion is already minimal here by choice; this makes it none. */
@media (prefers-reduced-motion: reduce) {
  .skip-link, .sections a { transition: none; }
}

/* One heading treatment, set once in base.html rather than per page. */
.page-title {
  margin: 0 0 var(--s5);
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
}
.page-title .dim { font-weight: 400; color: var(--muted-fg); }

/* --- sign in ------------------------------------------------------------- */
/* The one page outside the shell. It had no styling of its own at all: a
   bare <main class="center">, no viewport meta, no mark - the first screen
   anyone sees, and the only one that looked unfinished. */

.signin {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--s5);
}
.signin-card {
  width: 100%;
  max-width: 360px;
  padding: 32px var(--s5) var(--s5);
  border: 1px solid var(--border);
  border-radius: 10px;
  background: color-mix(in srgb, var(--card) 92%, black);
  box-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.9);
  text-align: center;
}
.brand-mark-lg {
  display: block;
  width: 52px;                   /* same 34:22 crop, scaled up */
  height: 34px;
  margin: 0 auto var(--s3);
}
.signin-title {
  margin: 0;
  font: 600 1.6rem/1.2 var(--sans);
  letter-spacing: 0.02em;
}
.signin-sub {
  margin: var(--s1) 0 var(--s5);
  color: var(--muted-fg);
  font-size: 0.85rem;
}
/* Labels stay visible - a placeholder-only password field loses its label
   the moment anyone starts typing. Left-aligned inside a centred card so
   the field reads as a field rather than as more centred copy. */
.signin form { text-align: left; }
.signin label {
  display: block;
  margin-bottom: var(--s2);
  font-size: 0.85rem;
  color: var(--muted-fg);
}
.signin input[type="password"] {
  width: 100%;
  margin-bottom: var(--s4);
}
.signin button { width: 100%; }
.signin .err {
  margin: 0 0 var(--s4);
  padding: var(--s3);
  border: 1px solid var(--bad);
  border-radius: 6px;
  background: color-mix(in srgb, var(--bad) 12%, transparent);
  color: var(--fg);
  font-size: 0.85rem;
  text-align: left;
}

/* --- filter chips -------------------------------------------------------- */
/* Every chip is an <a> with a real href. The overview filters with scripting
   off, a filtered view is a bookmarkable URL, and the back button leaves a
   filter the way anyone would expect. aria-pressed carries the toggle state
   that the href alone does not express. */

/* One group per ROW, with the legends aligned in a column. Laid out as a
   single wrapping line, two groups shared a row and a legend no longer
   visibly owned the chips after it - "DIRECTION ... STAGE All idea" reads
   as one run of chips with two labels in it. */
.filters {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin-bottom: var(--s4);
  padding: var(--s3);
  border: 1px solid var(--border);
  border-radius: 8px;
  background: color-mix(in srgb, var(--card) 60%, transparent);
}
.chip-group {
  display: flex;
  flex-wrap: wrap;          /* never a clipped single row - the labels are
                               the content, and hiding them hides the filter */
  align-items: center;
  gap: var(--s1);
}
.chip-legend {
  /* Fixed width so every group's chips start on the same x - the column of
     legends is what makes the rows read as separate filters. */
  flex: none;
  width: 7.5rem;
  margin-right: var(--s1);
  color: var(--muted-fg);
  font: 600 10px/1 var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
}
@media (max-width: 720px) {
  /* Too narrow for a legend column: let it sit above its own chips. */
  .chip-group { align-items: flex-start; }
  .chip-legend { width: 100%; margin-bottom: 2px; }
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 44px is the touch target; the visible pill is smaller, so the padding
     does the work rather than a min-height that would inflate the row. */
  min-height: 32px;
  padding: 5px 10px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--muted-fg);
  font: 500 12px/1 var(--sans);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
}
.chip:hover {
  border-color: var(--link);
  color: var(--fg);
}
/* Selected is border + weight + background, never colour alone. */
.chip.is-on {
  border-color: var(--link);
  background: color-mix(in srgb, var(--link) 16%, transparent);
  color: var(--fg);
  font-weight: 700;
}
.chip-count {
  padding: 1px 5px;
  border-radius: 999px;
  background: var(--muted);
  color: var(--muted-fg);
  font: 600 10px/1.4 var(--mono);
}
.chip.is-on .chip-count { background: color-mix(in srgb, var(--link) 28%, transparent); }
.chip-clear { border-style: dashed; }

.result-count {
  margin: 0 0 var(--s3);
  color: var(--muted-fg);
  font-size: 0.85rem;
}

/* --- classification tags ------------------------------------------------- */
/* A tag always says its value in words. Colour groups the lines of work so a
   long table can be scanned, but it never carries meaning on its own. */

.tag {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--muted);
  color: var(--fg);
  font: 500 11px/1.6 var(--mono);
  white-space: nowrap;
}
.work-commerce       { border-color: #C084FC; color: #E9D5FF; }
.work-service        { border-color: #7DD3FC; color: #BAE6FD; }
.work-infrastructure { border-color: #5EEAD4; color: #99F6E4; }
.work-transport      { border-color: #FDBA74; color: #FED7AA; }
.work-media          { border-color: #F9A8D4; color: #FBCFE8; }
.work-internal       { border-color: var(--border); color: var(--muted-fg); }

.stage-live        { border-color: var(--ok); color: #BBF7D0; }
.stage-building    { border-color: var(--warn); color: #FDE68A; }
.stage-idea        { border-color: var(--border); color: var(--muted-fg); }
.stage-maintenance { border-color: var(--link); color: #BAE6FD; }
.stage-dormant     { border-color: var(--border); color: var(--muted-fg); opacity: .7; }

/* The purpose column is prose in a table of values: cap it so one long
   sentence cannot push the count columns off the side. */
.projects .purpose {
  max-width: 34ch;
  color: var(--muted-fg);
  font-size: 0.85rem;
}

/* --- prompt panel -------------------------------------------------------- */
/* A box you type into and press a button on is assumed to START something.
   This one does not: it writes a brief and creates a blocked task. The
   button label and the hint both say so, because the safest affordance is
   the one that reads correctly before it is pressed. */

.prompt-form textarea {
  width: 100%;
  min-height: 7em;
  margin-bottom: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font: 400 14px/1.6 var(--sans);
  resize: vertical;
}
.prompt-form textarea::placeholder { color: var(--muted-fg); opacity: .75; }
.prompt-row {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--s3);
}
.prompt-assignee { display: flex; flex-direction: column; gap: 4px; }
.prompt-assignee label { font-size: 0.8rem; color: var(--muted-fg); }
.prompt-row button { width: auto; margin: 0; min-height: 44px; }
.prompt-form .hint { margin: var(--s3) 0 0; }

/* Outcome messages. role=status / role=alert carry this to a screen reader;
   the border and the word carry it to everyone else - never colour alone. */
.notice {
  margin: 0 0 var(--s3);
  padding: var(--s3);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: 6px;
  font-size: 0.9rem;
}
.notice.ok  { border-left-color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, transparent); }
.notice.err { border-left-color: var(--bad); background: color-mix(in srgb, var(--bad) 10%, transparent); }

/* --- profile panel ------------------------------------------------------- */

.profile-purpose {
  margin: 0 0 var(--s4);
  color: var(--fg);
  font-size: 0.95rem;
  max-width: 68ch;          /* prose, not a table cell */
}
.profile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--s3) var(--s4);
  margin: 0;
}
.profile-grid > div { min-width: 0; }
.profile-wide { grid-column: 1 / -1; }
.profile-grid dt {
  margin-bottom: 4px;
  color: var(--muted-fg);
  font: 600 10px/1 var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
}
.profile-grid dd { margin: 0; }
.profile-problems {
  margin: 0;
  padding-left: 1.2em;
  color: var(--muted-fg);
  font-size: 0.85rem;
}

/* The prompt's submit is the primary action of ITS panel, but it must never
   look like "Run dispatch pass" - that button starts an agent and spends
   credits, this one writes a file. Same prominence, different colour, so the
   two are not read as the same kind of act. (The map's satellite controls
   already follow this rule: only the destructive one keeps the solid fill.) */
.prompt-row button {
  background: color-mix(in srgb, var(--link) 22%, transparent);
  border: 1px solid var(--link);
  color: var(--fg);
}
.prompt-row button:hover {
  background: color-mix(in srgb, var(--link) 34%, transparent);
}

/* ONE COLOUR PER POSTURE, DEFINED ONCE.
   Each posture class sets `--posture` and nothing else. The star, the
   label word and the legend swatch all read that variable, so they cannot
   drift - and the legend can be an HTML <span>, which has no `fill` and
   could never have shared a rule written against `.star`.
   `.posture-empty` is deliberately muted and unglowed: a configuration gap
   is not a state of the work, and it must read as "nothing here yet"
   rather than compete with the rungs that mean trouble. */
.posture-no-vcs,
.posture-missing,
.posture-blocked     { --posture: var(--bad); }
.posture-uncommitted,
.posture-stale       { --posture: var(--warn); }
.posture-ok          { --posture: var(--ok); }
.posture-empty       { --posture: var(--muted-fg); }

/* --- command view -------------------------------------------------------- */
/* Proposals, never actions. Nothing on this page dispatches, so nothing on
   it is styled like the buttons that do - no solid fills, no green. */

.panel-note {
  margin: 0 0 var(--s4);
  padding: var(--s3);
  border: 1px dashed var(--border-strong);
  border-radius: 6px;
  color: var(--muted-fg);
  font-size: 0.88rem;
}
.section-head {
  margin: var(--s5) 0 var(--s3);
  font: 600 1.05rem/1.3 var(--sans);
}

.impact {
  display: inline-block;
  flex: none;
  padding: 1px 7px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  font: 600 10px/1.6 var(--mono);
  letter-spacing: .06em;
  text-transform: uppercase;
}
/* Band is stated in the word; colour only restates it.
   Scoped to `.impact` deliberately: the same impact-* class is also on the
   list item (so a card can be styled by band), and an unscoped rule tinted
   the entire move - title, facts and action all red - which buries the one
   word that was supposed to carry the signal and makes a page of proposals
   read as a page of alarms. */
.impact.impact-high   { border-color: var(--bad);  color: var(--bad); }
.impact.impact-medium { border-color: var(--warn); color: var(--warn); }
.impact.impact-low    { border-color: var(--border-strong); color: var(--muted-fg); }

/* The item itself is marked by a left edge, not by colouring its text. */
.move.impact-high   { border-left: 2px solid var(--bad); padding-left: var(--s3); }
.move.impact-medium { border-left: 2px solid var(--warn); padding-left: var(--s3); }

.moves { margin: 0; padding: 0; list-style: none; }
.move {
  padding: var(--s3) 0;
  border-top: 1px solid var(--border);
}
.moves > .move:first-child { border-top: 0; }
.move-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin-bottom: 4px;
}
.move-title { font-weight: 600; }
.move-slug {
  font: 600 12px/1.4 var(--mono);
  color: var(--link);
  text-decoration: none;
}
.move-slug:hover { text-decoration: underline; }
/* The facts. Indented under the claim they support, and quieter than it -
   they are evidence, not the headline. */
.move-why {
  margin: 0 0 6px;
  padding-left: 1.1em;
  color: var(--muted-fg);
  font-size: 0.85rem;
}
.move-action {
  margin: 0;
  font-size: 0.88rem;
}
.move-not {
  margin: 4px 0 0;
  color: var(--muted-fg);
  font-size: 0.82rem;
  font-style: italic;
}

.systemic { margin: 0; padding-left: 1.1em; font-size: 0.9rem; }
.systemic li { margin-bottom: 6px; }
.systemic a { color: var(--link); }

/* One card per project. auto-fit rather than a fixed count: at nine
   projects a hard 3-up leaves a ragged last row on every width. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--s4);
  align-items: start;
}
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s2);
}
.card-head h3 { margin: 0; font: 600 1rem/1.3 var(--mono); }
.card-head a { color: var(--fg); text-decoration: none; }
.card-head a:hover { text-decoration: underline; }
.card-purpose {
  margin: 0 0 var(--s3);
  color: var(--muted-fg);
  font-size: 0.85rem;
}
.card-flow { margin: 0 0 var(--s3); font-size: 0.85rem; }
.flow-state {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 7px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  font: 600 10px/1.6 var(--mono);
  text-transform: uppercase;
}
.flow-moving  { border-color: var(--ok);   color: var(--ok); }
.flow-stuck   { border-color: var(--bad);  color: var(--bad); }
.flow-queued  { border-color: var(--warn); color: var(--warn); }
.flow-settled { border-color: var(--border-strong); color: var(--muted-fg); }
.flow-idle    { border-color: var(--border); color: var(--muted-fg); }

/* The posture tag reuses --posture, same as the map's star and legend. */
.posture-tag { border-color: var(--posture); color: var(--posture); }

/* --- synthesis: where the projects collide ------------------------------- */
/* The evidence is never hidden behind a disclosure. It is the reason a pair
   is on the page, and a bare score with the reasons folded away is a number
   asking to be trusted. */

.pairs { margin: 0; padding: 0; list-style: none; }
.pair {
  padding: var(--s3) 0;
  border-top: 1px solid var(--border);
}
.pair:first-child { border-top: 0; }
.pair.is-open {
  border-left: 2px solid var(--link);
  padding-left: var(--s4);
  background: color-mix(in srgb, var(--card) 55%, transparent);
}
.pair-head {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  margin-bottom: 6px;
}
/* The score is an ordering device, not a measurement anyone should quote -
   muted and monospaced so it reads as a rank rather than a result. */
.pair-score {
  flex: none;
  min-width: 3.2em;
  color: var(--muted-fg);
  font: 600 12px/1.4 var(--mono);
}
.pair-names { font: 600 0.95rem/1.4 var(--mono); }
.pair-names a { color: var(--fg); text-decoration: none; }
.pair-names a:hover { text-decoration: underline; }
.pair-x { color: var(--muted-fg); margin: 0 4px; }

.pair-evidence {
  display: flex;
  flex-wrap: wrap;          /* never a clipped row: the labels are the point */
  gap: 5px;
  margin: 0 0 8px;
  padding: 0;
  list-style: none;
}
.ev {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 2px 7px;
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 0.78rem;
}
.ev-kind {
  color: var(--muted-fg);
  font: 600 9px/1.6 var(--mono);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ev-value { font: 500 12px/1.5 var(--mono); }
/* A word shared by both PURPOSES says what the projects are; one shared only
   through a todo title says what is currently wrong with them, which rhymes
   across projects far more easily. Different weight, different mark. */
.ev-term { border-color: var(--link); }
.ev-mention { border-style: dashed; }

.pair-open { margin: 0; font-size: 0.85rem; }
.idea-form { margin: var(--s2) 0 0; max-width: 60ch; }
.idea-form label {
  display: block;
  margin-bottom: 4px;
  font-size: 0.85rem;
  color: var(--muted-fg);
}
.idea-form button {
  width: auto;
  margin-top: var(--s2);
  min-height: 44px;
  background: color-mix(in srgb, var(--link) 22%, transparent);
  border: 1px solid var(--link);
  color: var(--fg);
}

/* A deadline is a different fact from an impact band: one says how bad, the
   other says how soon. Shown beside the band, never instead of it, and the
   date itself is always on screen - "imminent" without a date is a mood. */
.due {
  display: inline-block;
  flex: none;
  padding: 1px 7px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  font: 600 10px/1.6 var(--mono);
  letter-spacing: .04em;
  color: var(--muted-fg);
}
.due-overdue  { border-color: var(--bad);  color: var(--bad); }
.due-imminent { border-color: var(--bad);  color: var(--bad); }
.due-soon     { border-color: var(--warn); color: var(--warn); }
.due-later    { border-color: var(--border-strong); color: var(--muted-fg); }

/* --- agent analysis ------------------------------------------------------ */
/* Kept visibly separate from the deterministic findings. advisor.py can show
   its working; an agent cannot. A reader must always be able to tell which
   of the two a claim came from, so the source is named on both and they are
   never merged into one list. */

.src {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 4px;
  font: 600 9px/1.7 var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
}
/* Dashed, because judgement is not measurement. The deterministic findings
   carry solid borders throughout. */
.src-agent {
  border: 1px dashed var(--link);
  color: var(--link);
}

.analysis-meta {
  margin: 0 0 var(--s3);
  color: var(--muted-fg);
  font-size: 0.82rem;
}
.analysis-h {
  margin: var(--s4) 0 var(--s2);
  font: 600 0.95rem/1.3 var(--sans);
}
.analysis-text {
  margin: 0;
  max-width: 72ch;          /* prose, not a table cell */
  white-space: pre-wrap;    /* the agent's own line breaks are meaningful */
  font-size: 0.92rem;
}
.analysis-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--border);
}
.analysis-form button { width: auto; margin: 0; min-height: 44px; }
.analysis-form .hint { margin: 0; }
.card-analysis { margin: 0 0 var(--s3); font-size: 0.8rem; }
