/* the brain — Terminal's clothes.

   Lifted from Desktop/Terminal/src/frontend/styles/Main.css: a soft blue-grey
   canvas, white chrome, one indigo accent, and cards that are white rectangles
   with a 16px radius and a shadow so soft it reads as a lift rather than a
   drop. Navigation is a 70px column of icons on the left.

   Three of Terminal's habits are the whole look and are worth naming, because
   getting them wrong is what makes a copy look like a copy:

     1. The shadow is tiny. 0 4px 6px -1px at 5% black. Anything heavier and
        the page turns into floating slabs.
     2. Hairlines are almost invisible - #f1f5f9 between rows, #e2e8f0 on an
        input. Structure comes from whitespace, not from lines.
     3. Colour is rationed. Indigo for where you are, teal for the button you
        press, and pastel pills for state. Everything else is slate.

   Terminal has no dark mode. This app keeps its toggle, so there is a dark
   restatement below - but light is home ground now, and the app opens light. */

/* ── Theme tokens ─────────────────────────────────────────────
   :root is light, which is Terminal's only ground. Nothing below either block
   names a colour, so these two blocks are the whole theme. */
:root {
  color-scheme: light;
  --bg:         #f0f4f8;   /* the canvas */
  --bg-raise:   #f8fafc;   /* sunk fields and code, a step *under* white */
  --surface:    #ffffff;   /* every card */
  --bar:        #ffffff;   /* sidebar and top bar */
  --border:     #e2e8f0;
  --hairline:   #f1f5f9;   /* the line between two table rows */
  --text:       #334155;
  --text-muted: #64748b;
  --accent:     #4f46e5;   /* indigo — where you are */
  --accent-wash:#eef2ff;   /* the active nav tile */
  --action:     #0d9488;   /* teal — the button you press */
  --action-hi:  #0f766e;
  --value:      #1e293b;   /* headings */
  --on-accent:  #ffffff;
  --pos:        #166534;
  --neg:        #b91c1c;
  --warn:       #854d0e;
  --info:       #3730a3;

  /* Pill grounds. Terminal's status pills are a pastel field with dark ink on
     it, never a coloured border round transparent. */
  --pos-wash:   #dcfce7;
  --neg-wash:   #fee2e2;
  --warn-wash:  #fef9c3;
  --info-wash:  #e0e7ff;
  --idle-wash:  #f1f5f9;

  --border-hover: #cbd5e1;
  --raise-hover:  #f8fafc;
  --idle:         #64748b;
  --dim:          #cbd5e1;   /* a zero count */
  --text-dim:     #94a3b8;   /* timestamps, idle icons */
  --neg-line:     #fca5a5;
  --scrim:        rgba(15, 23, 42, .6);

  /* The soft shadow, verbatim. */
  --shadow-soft: 0 4px 6px -1px rgba(0, 0, 0, .05), 0 2px 4px -1px rgba(0, 0, 0, .03);
  --shadow-pop:  0 10px 24px -6px rgba(15, 23, 42, .12);
  --ring:        0 0 0 4px rgba(79, 70, 229, .1);

  --mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
  --body: "Assistant", -apple-system, "Segoe UI", system-ui, sans-serif;

  --radius: 16px;
  --gap: 16px;
  --rail-w: 70px;
  /* No cap. A 1500px ceiling left a quarter of a wide monitor empty while the
     board below it was scrolling — on this app the extra width is always worth
     something (another column of project cards, a wider board), so every view
     runs the full width of the window and the padding does the framing. */
  --maxw: none;
}

/* ── Dark restatement ─────────────────────────────────────────
   Terminal does not have one; this app does, and the toggle should not lead
   anywhere broken. Same structure, same indigo, slate inverted. */
[data-theme="dark"] {
  color-scheme: dark;
  --bg:         #0f172a;
  --bg-raise:   #0b1220;
  --surface:    #1e293b;
  --bar:        #1e293b;
  --border:     #334155;
  --hairline:   #263449;
  --text:       #cbd5e1;
  --text-muted: #94a3b8;
  --accent:     #a5b4fc;
  --accent-wash:#312e81;
  --action:     #14b8a6;
  --action-hi:  #2dd4bf;
  --value:      #f1f5f9;
  --on-accent:  #0f172a;
  --pos:        #86efac;
  --neg:        #fca5a5;
  --warn:       #fde047;
  --info:       #c7d2fe;

  --pos-wash:   #14532d;
  --neg-wash:   #7f1d1d;
  --warn-wash:  #713f12;
  --info-wash:  #312e81;
  --idle-wash:  #334155;

  --border-hover: #475569;
  --raise-hover:  #263449;
  --idle:         #94a3b8;
  --dim:          #475569;
  --text-dim:     #94a3b8;
  --neg-line:     #7f1d1d;
  --scrim:        rgba(2, 6, 23, .7);

  --shadow-soft: 0 4px 6px -1px rgba(0, 0, 0, .3), 0 2px 4px -1px rgba(0, 0, 0, .2);
  --shadow-pop:  0 10px 24px -6px rgba(0, 0, 0, .5);
  --ring:        0 0 0 4px rgba(165, 180, 252, .15);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

/* Terminal's body is the layout: a flex row, rail on the left, everything else
   in a column on the right that scrolls on its own. */
body {
  margin: 0;
  display: flex;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* ── Cards ──────────────────────────────────────────────────────
   One rule for every surface that sits on the canvas. White, 16px, and a
   shadow you have to look for. No borders: the lift does that job. */
.pcard, .phead, .col, .info-panel, .feed-item, .prop, .prop-strip,
.agent-card, .run-row, .app-state, .empty, .skel-card, .agent-row, .app-tile {
  background: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius);
  box-shadow: var(--shadow-soft);
}

h1, h2, h3 { margin: 0; font-weight: 600; }
a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 8px;
}

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

/* ── Mono micro-label (used everywhere a field needs naming) ───────── */
/* Terminal's field label: small, semibold, slate. Not mono, not uppercase, no
   tracking — it names a field, it does not announce one. */
.label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-muted);
}

.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ── The rail ──────────────────────────────────────────────────────
   70px, white, fixed for the height of the page. Terminal's navigation is
   nothing but icons, and it works because there are only a handful of screens
   and each one has an obvious shape. Six here, near enough. */
#sidebar {
  width: var(--rail-w);
  min-width: var(--rail-w);
  flex-shrink: 0;
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
  background: var(--bar);
  border-right: 1px solid var(--hairline);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 18px 0;
  z-index: 100;
}

/* The wordmark does not fit in 70px, so the rail carries initials and the
   title attribute carries the name. */
.rail-brand {
  width: 40px;
  height: 40px;
  margin-bottom: 26px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.03em;
}

.tabs { display: flex; flex-direction: column; align-items: center; gap: 6px; }

.tab {
  position: relative;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  color: var(--text-dim);
  text-decoration: none;
  transition: background .2s ease, color .2s ease;
}
.tab svg { width: 20px; height: 20px; display: block; }
.tab:hover { background: var(--raise-hover); color: var(--accent); }
.tab.is-active { background: var(--accent-wash); color: var(--accent); }

/* ── The account, at the foot of the rail ─────────────────────────
   margin-top:auto is the whole of the layout: the tabs stay under the brand and
   this falls to the bottom whatever the rail's height. The hairline above it is
   what stops the initials reading as one more tab. */
.rail-account {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline);
  width: 40px;
}
.rail-account[hidden] { display: none; }

/* Initials, not a name — 70px of rail has no room for one, and the title
   attribute carries the address for when you actually need to know. */
.rail-who {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .02em;
  cursor: default;
  user-select: none;
}

/* Shaped like a tab because it sits in a column of them, coloured like a
   warning only on hover — signing out is easy to hit by accident here. */
.rail-signout {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  padding: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  transition: background .2s ease, color .2s ease;
}
.rail-signout svg { width: 17px; height: 17px; display: block; }
.rail-signout:hover { background: var(--neg-wash); color: var(--neg); }

/* ── Right side: top bar, then the scrolling board ───────────────── */
#right-side {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

#top-bar {
  flex-shrink: 0;
  position: sticky;
  top: 0;
  z-index: 90;
  height: 64px;
  padding: 0 34px;
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--bar);
  border-bottom: 1px solid var(--hairline);
}
.bar-spacer { margin-left: auto; }

.sync {
  font-size: 13px;
  color: var(--text-dim);
  white-space: nowrap;
}
.sync[data-busy="1"] { color: var(--warn); }

/* ── Buttons ───────────────────────────────────────────────────────── */
/* Teal is the press colour in Terminal, and indigo is reserved for where you
   are — so the primary button is teal and the rail is indigo, exactly as
   there. The lift on hover is Terminal's too. */
.btn {
  background: var(--action);
  color: #ffffff;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 9px 16px;
  font-family: var(--body);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background .2s ease, transform .12s ease, box-shadow .2s ease;
}
.btn:hover  { background: var(--action-hi); transform: translateY(-1px); box-shadow: var(--shadow-soft); }
.btn:active { transform: translateY(0); }
.btn[disabled] { opacity: .5; cursor: not-allowed; transform: none; box-shadow: none; }

/* The quiet one: a slate tile, no border. Terminal's .btn-action. */
.btn--ghost {
  background: var(--idle-wash);
  color: var(--text-muted);
  border-color: transparent;
}
.btn--ghost:hover {
  background: var(--surface);
  color: var(--value);
  box-shadow: 0 0 0 1.5px var(--border-hover);
  transform: none;
}
#btn-theme { min-width: 86px; }

.btn--danger { background: transparent; color: var(--neg); border-color: var(--neg-line); }
.btn--danger:hover { background: var(--neg-wash); color: var(--neg); transform: none; box-shadow: none; }

/* For a control that lives inside a card rather than under one — a full-size
   button there reads as the card's purpose instead of as one thing you can do
   to it. */
.btn--tiny { padding: 6px 11px; font-size: 12.5px; border-radius: 6px; }

/* Icon-only, for the board card. A task card is read in a column of eight, so
   the control has to be small enough to ignore — the title is what you are
   scanning for, not the button. It grows a word only while a job is in flight,
   because "queued" and "waiting for the runner" cannot be drawn. */
.btn--icon {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 9px;
  border-radius: 8px;
  min-width: 0;
  line-height: 1;
}
.btn--icon svg { display: block; }
.btn-icon-word { font-size: 12px; }

.iconbtn {
  background: none; border: none; cursor: pointer;
  color: var(--text-muted); font-size: 22px; line-height: 1;
  padding: 2px 6px; border-radius: 6px;
}
.iconbtn:hover { color: var(--text); background: var(--raise-hover); }

.linkbtn {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent); font-size: 13.5px; font-weight: 600;
}
.linkbtn:hover { text-decoration: underline; }

/* ── Layout ────────────────────────────────────────────────────────── */
#main { flex: 1; min-width: 0; max-width: var(--maxw); width: 100%; padding: 28px 34px 60px; }

.viewhead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
/* Terminal's header h1: 1.8rem, 600, slate-800. Nothing louder anywhere. */
.viewtitle {
  font-size: 1.8rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--value);
}
.summary { display: flex; gap: 18px; flex-wrap: wrap; }
.summary-item { display: flex; align-items: baseline; gap: 6px; }
.summary-num { font-weight: 600; font-size: 1.15rem; font-variant-numeric: tabular-nums; }

.backlink {
  display: inline-block;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: none;
  margin-bottom: 14px;
}
.backlink:hover { color: var(--text); }

/* ── State colour ────────────────────────────────────────────────────
   The 3px accent rail is gone. Terminal carries state in a pastel pill and
   nowhere else, and a card with both a rail and a pill says the same thing
   twice. --rail survives only as the variable the pills and the few remaining
   coloured edges read from. */
[data-accent] { --rail: var(--idle); --wash: var(--idle-wash); }
[data-accent="pos"]  { --rail: var(--pos);  --wash: var(--pos-wash); }
[data-accent="neg"]  { --rail: var(--neg);  --wash: var(--neg-wash); }
[data-accent="warn"] { --rail: var(--warn); --wash: var(--warn-wash); }
[data-accent="info"] { --rail: var(--info); --wash: var(--info-wash); }
[data-accent="idle"] { --rail: var(--idle); --wash: var(--idle-wash); }

/* ── Chips ─────────────────────────────────────────────────────────── */
/* Terminal's status pill: a pastel field, dark ink, fully rounded, no border
   and no dot. Sentence case — a pill is a word, not a shout. */
.chip {
  display: inline-flex;
  align-items: center;
  padding: 4px 11px;
  border-radius: 20px;
  font-size: 12.5px;
  font-weight: 600;
  border: none;
  color: var(--rail, var(--text-muted));
  background: var(--wash, var(--idle-wash));
  white-space: nowrap;
}
.chip--plain { background: var(--idle-wash); color: var(--text-muted); }

.chipbar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.filter {
  font-size: 13px;
  font-weight: 600;
  padding: 7px 15px;
  border-radius: 20px;
  border: 1px solid transparent;
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  box-shadow: var(--shadow-soft);
}
.filter:hover { color: var(--value); }
.filter.is-active { color: var(--accent); background: var(--accent-wash); box-shadow: none; }
.filter-count { opacity: .55; margin-left: 6px; font-variant-numeric: tabular-nums; }
.filter.is-active .filter-count { opacity: .65; }

/* ── Project cards ─────────────────────────────────────────────────── */
.card-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--gap);
  align-items: start;
}

.pcard {
  display: block;
  width: 100%;
  text-align: left;
  padding: 18px 20px;
  cursor: pointer;
  transition: box-shadow .2s ease, transform .12s ease;
}
.pcard:hover { box-shadow: var(--shadow-pop); transform: translateY(-2px); }
.pcard:active { transform: translateY(0); }

/* Card baseline rule: fixed-height header zone, so the count row below it sits
   on the same line across every card no matter how long the name runs. */
.pcard-header {
  min-height: 7rem;   /* fits chips + 2-line name + 2-line description */
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.pcard-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.pcard-name {
  font-size: 17px;
  font-weight: 600;
  color: var(--value);
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pcard-desc {
  font-size: 13px;
  color: var(--text-muted);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.pcard-counts {
  display: flex;
  gap: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--hairline);
}
.count { display: flex; flex-direction: column; gap: 2px; }
.count-num {
  font-variant-numeric: tabular-nums;
  font-size: 1.35rem;
  font-weight: 600;
  line-height: 1;
  color: var(--value);
}
.count-num[data-tone="live"]    { color: var(--info); }
.count-num[data-tone="blocked"] { color: var(--neg); }
.count-num[data-tone="done"]    { color: var(--pos); }
.count-num[data-zero="1"]       { color: var(--dim); }

.pcard-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 12px;
}
.pcard-activity { font-size: 12.5px; color: var(--text-dim); }

/* ── App tiles ──────────────────────────────────────────────────────────
   Narrower than a project card, because a tile carries three things and a
   project card carries nine. Same white rectangle and same lift on hover, so
   the two grids read as the same app looked at two ways. */
.app-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--gap);
  align-items: start;
}

.app-tile {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 15px 16px;
  text-decoration: none;
  color: var(--text-dim);
  transition: box-shadow .2s ease, transform .12s ease;
}
.app-tile:hover { box-shadow: var(--shadow-pop); transform: translateY(-2px); color: var(--accent); }
.app-tile:active { transform: translateY(0); }

/* The rail-brand trick, one size up: initials on the accent wash, standing in
   for the logo no project on this board has. */
.app-mark {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -.03em;
}

.app-tile-body { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.app-tile-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--value);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-tile-host {
  font-size: 12.5px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-tile svg { flex-shrink: 0; margin-left: auto; }

/* ── Project detail head ───────────────────────────────────────────── */
.phead {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 22px 24px;
  margin-bottom: 18px;
}
.phead-top { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.phead-title {
  font-size: 1.6rem;
  font-weight: 600;
  color: var(--value);
  line-height: 1.25;
}
.phead-desc { color: var(--text-muted); font-size: 14px; margin-top: 6px; max-width: 70ch; }

/* The way out of the app, at the top of the page about it. Indigo, because it
   is a link and this palette has exactly one link colour. */
.phead-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  max-width: 100%;
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  overflow: hidden;
}
.phead-link svg { flex-shrink: 0; }
.phead-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.phead-link:hover span { text-decoration: underline; }
.phead-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

.statuspick { display: flex; align-items: center; gap: 8px; }
select, input[type="text"], input[type="url"], input[type="date"], textarea {
  min-width: 0;
  background: var(--bg-raise);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  color: var(--text);
  font-family: var(--body);
  font-size: 14px;
  width: 100%;
  transition: border-color .2s ease, background .2s ease, box-shadow .2s ease;
}
select { cursor: pointer; }
input[type="date"] { padding-left: 10px; padding-right: 8px; }
select:hover, input:hover, textarea:hover { border-color: var(--border-hover); }
select:focus, input:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface);
  box-shadow: var(--ring);
}
textarea { resize: vertical; min-height: 76px; line-height: 1.5; }
.statuspick select { width: auto; font-family: var(--mono); font-size: 12px; }

/* ── Board ─────────────────────────────────────────────────────────── */
.project-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--gap);
  align-items: start;
}

.board {
  display: grid;
  grid-template-columns: repeat(4, minmax(190px, 1fr));
  gap: 12px;
  min-width: 0;
}

.col {
  background: var(--surface);
  padding: 14px 13px;
  min-height: 180px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: background .15s ease, border-color .15s ease;
}
.col.is-over { background: var(--accent-wash); }

.col-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
/* The column header is where a board's state colour lives now. */
.col-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--rail);
}
.col-count { font-size: 12.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.col-empty {
  font-size: 12.5px;
  color: var(--text-dim);
  text-align: center;
  padding: 16px 4px;
  border: 1px dashed var(--border);
  border-radius: 10px;
}

/* The cards of one column, capped at four and scrolled past that. The cap is a
   pixel height set in JS rather than here, because four cards is a different
   height in every column — see capColumns(). */
.col-tasks {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
  /* Every column keeps room for a scrollbar whether or not it has one, so the
     busy column's cards are the same width as the quiet column's beside it.
     Without the reserved gutter a column narrowed by 10px the moment it filled
     up, and the board stopped lining through. */
  overflow-y: auto;
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  /* Sideways room for the card's hover shadow, which the scroll container would
     otherwise clip flush against its edge. Horizontal only: vertical padding
     here would eat into the four cards the measured height allows for. */
  padding: 0 4px;
  margin: 0 -4px;
}

.tcard {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 12px 13px;
  /* Logical, to match the rail below: both have to flip together or an RTL
     board would draw the edge on one side and clear room on the other. */
  padding-inline-start: 16px;
  cursor: grab;
  display: flex;
  flex-direction: column;
  gap: 8px;
  transition: border-color .15s ease, background .15s ease;
}
/* Priority, as the card's own edge.
   It was a pill, which cost a slot on a row that had no room for one and put a
   fourth colour beside three others at the same weight. The card already
   carried data-accent from the priority - that is where the pill got its pink -
   so the colour was here all along and nothing was drawing it. An edge costs no
   width, reads down a whole column at a glance, and leaves the row to the facts
   that need words. */
.tcard::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 3px;
  border-start-start-radius: 12px;
  border-end-start-radius: 12px;
  background: var(--rail);
}
.tcard:hover { border-color: var(--border); box-shadow: var(--shadow-soft); }
.tcard.is-dragging { opacity: .45; cursor: grabbing; }
.tcard-title { font-size: 14px; line-height: 1.35; color: var(--text); }

/* Facts on the left, controls in a fixed slot on the right. Deliberately not
   wrapping: a row that wraps puts the run button in a different place on every
   card, and the eye never learns where to look for it. */
.tcard-meta { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }
.tcard-facts {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  /* The backstop. Nothing in here wraps, so without this a row too long for a
     190px column does not push the controls along - it runs underneath them,
     and a date reading through a play button is worse than a clipped one. */
  overflow: hidden;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
}
/* One separator between whatever happens to be present. An absent fact renders
   no element at all, so the adjacent-sibling rule gets this right without the
   markup having to know which facts a card has. */
.tcard-facts > * + *::before {
  content: "·";
  margin-inline-end: 6px;
  color: var(--dim);
  font-weight: 400;
}
/* Every fact can be cut, and cuts with an ellipsis rather than mid-glyph: a
   date sliced through the "e" of "Overdue" reads as a broken card, the same
   words with a "…" read as a card too narrow to say everything. */
.tcard-facts > * {
  flex: 0 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* But not in any order, and not in proportion to length either - flex shrinks
   items by their own width, so a long agent name beside a short "Low" took a
   little from each and left "L...". These weights say it plainly: the priority
   word and the picture count are never cut, the date only as a last resort, and
   the agent name absorbs everything before either of them is touched. */
.tcard-prio  { flex-shrink: 0; }
.tcard-shots { flex-shrink: 0; }
.tcard-due   { flex-shrink: 1; }
.tcard-who   { flex-shrink: 20; }
.tcard-shots { display: inline-flex; align-items: center; gap: 3px; }
.tcard-prio { color: var(--rail); }
.tcard-due { color: var(--text-dim); }
.tcard-due[data-overdue="1"] { color: var(--neg); }
.tcard-acts {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-inline-start: auto;
  /* Never squeezed. The controls holding one position down the column is the
     whole point of splitting this row in two. */
  flex: 0 0 auto;
}

/* The card a run was pointed at, on the run's own row. Wider than the other
   chips because a task title is a sentence, not a status. */
.chip--task {
  text-decoration: none;
  max-width: 46ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border-style: dashed;
}

/* What an agent is on right now, on the agent's own row. */
.agent-busy { font-size: 13px; line-height: 1.5; color: var(--text-muted); }
.agent-busy strong { color: var(--text); font-weight: 600; }
.agent-busy[data-tone="info"] { color: var(--info); }
.agent-busy[data-tone="warn"] { color: var(--warn); }

/* The task a history line is about, when the line itself does not name it. */
.feed-task {
  display: block;
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 2px;
}

/* The run control rides the meta row, hard right, instead of getting a line to
   itself. Three rounds of this: first a full-width bar with a border above it,
   which read as the card's purpose; then a small button alone on the last row,
   which still spent a whole row of an eight-card column on a control nobody
   scans for; then two of them, filled teal, wrapping onto lines of their own.
   Now they share one fixed slot at the end of a row that already exists.

   Quiet at rest, and that is the point. This theme rations colour - teal is
   "the button you press" - and a board of eight cards was spending sixteen
   filled teal blocks on controls at rest, which left nothing louder for a job
   that was actually running. So resting is grey, and the pastel state grounds
   below are what a card in flight lights up with. */
.tcard-meta .btn--icon {
  padding: 4px 6px;
  border-radius: 6px;
  background: transparent;
  /* Muted, not dim. Dim is the weight for a timestamp you are not meant to
     read; this is a control, and one nobody can find is worse than one that is
     slightly louder than the row it sits on. */
  color: var(--text-muted);
  box-shadow: none;
}
.tcard-meta .btn--icon:hover {
  background: var(--idle-wash);
  color: var(--action);
  transform: none;
  box-shadow: none;
}
/* In flight. The same pastel-pill vocabulary the status chips use, so one
   working card stands out of a quiet column without a new colour being
   invented for it. */
.tcard-meta .btn--icon[data-tone="info"] { background: var(--info-wash); color: var(--info); }
.tcard-meta .btn--icon[data-tone="warn"] { background: var(--warn-wash); color: var(--warn); }
.tcard-meta .btn--icon[data-tone="neg"]  { background: var(--neg-wash);  color: var(--neg); }
.tcard-meta .btn--icon[disabled] { opacity: 1; }
/* Room for the word a running job grows, without letting it shove the chips
   off the row. */
.tcard-meta .btn--icon .btn-icon-word { font-size: 11px; }
.tcard .run-hint { font-size: 11px; }

/* ── Info panel ────────────────────────────────────────────────────── */
.info-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  position: sticky;
  top: 80px;   /* clears the 64px top bar */
}
.info-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--value);
  padding-bottom: 12px;
  border-bottom: 1px solid var(--hairline);
}
.field { display: flex; flex-direction: column; gap: 5px; }
.field--inline { flex-direction: row; align-items: center; gap: 8px; }
.field--inline select { width: auto; min-width: 180px; }
/* Two fields on a line. minmax(0,1fr) rather than 1fr, and min-width:0 on the
   items: a grid item's automatic minimum is its content, and a native date
   input's content is 134px at its very narrowest — wider than half the info
   panel. That minimum is what pushed this row out through the side of the card
   instead of letting the inputs get narrower.

   Not repeat(auto-fit,…), which is the obvious way to make this responsive and
   does not work here: the row is a stretched flex item, so its available width
   is indefinite, and an indefinite width makes the repetition count 1 at every
   size. The two places this row is too narrow for are both known, so they say
   so below rather than asking the grid to work it out. */
.field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

/* 262px of content, and two date inputs need 278px. They never fit here. */
.info-panel .field-row { grid-template-columns: minmax(0, 1fr); }
/* Grid items default to min-width:auto, which is the date input's intrinsic
   width — the one thing pushing this row out of the info panel. */
.field-row > * { min-width: 0; }
.info-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.info-recent { display: flex; flex-direction: column; gap: 8px; }
.info-recent-item { font-size: 12px; color: var(--text-muted); line-height: 1.4; }
.info-recent-item time { font-size: 11.5px; color: var(--text-dim); display: block; }

/* ── History feed ──────────────────────────────────────────────────── */
.feed { display: flex; flex-direction: column; gap: 8px; }
.feed-item {
  display: grid;
  grid-template-columns: 152px 128px minmax(0, 1fr);
  gap: 14px;
  align-items: baseline;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 13px 16px;
}
.feed-when { font-size: 12.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.feed-project {
  font-size: 12.5px; font-weight: 600; color: var(--rail);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.feed-text { font-size: 14px; line-height: 1.45; }
.feed-text .arrow { color: var(--text-muted); padding: 0 2px; }

/* ── Ideas (the first gate) ────────────────────────────────────────────
   Proposals are deliberately not task cards: they are a decision, not work in
   progress, so they get the warn rail, the reasoning in full, and three
   unambiguous verbs. */
/* On a rail there is no room beside the icon, so the count sits on its
   shoulder. Indigo, because it is telling you where to go. */
.tab-badge {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--accent);
  color: #ffffff;
  font-size: 11px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--bar);
}
.tab-badge:empty { display: none; }
#tab-review[data-waiting="1"], #tab-work[data-waiting="1"] { color: var(--accent); }

.muted-line { margin: 0; color: var(--text-muted); font-size: 13px; }

.review-feed { display: flex; flex-direction: column; gap: 12px; }

.prop {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px;
}
.prop-head { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.prop-chips { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.prop-when { font-size: 12.5px; color: var(--text-dim); }
.prop-title { font-size: 15px; font-weight: 600; color: var(--value); line-height: 1.35; }
.prop-why { font-size: 14px; color: var(--text); line-height: 1.55; max-width: 82ch; }
.prop-key { font-size: 10px; color: var(--text-dim); word-break: break-all; }

/* The plain line — the one sentence the verdict is actually made from, so it
   gets the panel the evidence used to have. Its rail is the card's own accent:
   the highlighted block on a proposal card should read as part of that card,
   not as a second kind of thing. */
.prop-plain {
  font-size: 15px;
  line-height: 1.5;
  color: var(--text);
  max-width: 82ch;
  padding: 12px 15px;
  background: var(--bg-raise);
  border-radius: 10px;
}

/* The evidence, kept out of the way until asked for. The lead line above has to
   carry the decision on its own; this is for when it does not. */
.prop-detail {
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-muted);
  max-width: 82ch;
  padding: 12px 14px;
  background: var(--bg-raise);
  border-radius: 10px;
  white-space: pre-wrap;
}
.prop-more { align-self: flex-start; }

/* The discussion on a proposal — the answer that is neither yes nor no.
   Indented under the idea it is about, because a thread with no visible parent
   is just a second feed. The two voices are told apart by the rail colour and
   the indent, not only by the name on top. */
.thread {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 3px;
  max-width: 82ch;
}
.thread-line {
  padding: 10px 13px;
  background: var(--accent-wash);
  border-radius: 10px;
}
.thread-line[data-who="agent"] { background: var(--bg-raise); margin-left: 22px; }
.thread-who {
  display: flex; gap: 8px; align-items: baseline;
  font-size: 12px; color: var(--text-dim); margin-bottom: 4px;
}
.thread-when { color: var(--text-dim); opacity: .75; }
.thread-text { font-size: 13px; line-height: 1.55; color: var(--text); white-space: pre-wrap; }
.thread-pending {
  font-size: 12px; color: var(--text-dim); margin-left: 22px;
}
.prop-actions { display: flex; flex-direction: column; gap: 7px; flex: 0 0 auto; }

.prop-strip {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 16px;
  padding: 16px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--surface);
}
.prop-strip .prop { background: var(--bg-raise); }
.prop-strip-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }

/* ── Agents ─────────────────────────────────────────────────────────── */
.agent-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
  margin-bottom: 14px;
}
.agent-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 12px; margin-bottom: 12px; border-bottom: 1px solid var(--border);
}
.agent-name { font-size: 1.05rem; font-weight: 600; color: var(--value); }
.agent-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  background: var(--bg-raise);
  box-shadow: none;
  border-radius: 12px; padding: 13px 15px; margin-bottom: 8px;
}
.agent-row:last-child { margin-bottom: 0; }
.agent-row-main { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.agent-project { font-size: 15px; font-weight: 600; color: var(--value); text-decoration: none; }
.agent-project:hover { text-decoration: underline; }
.agent-row-meta { display: flex; gap: 6px; flex-wrap: wrap; }
.agent-row-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.agent-mode-pick { width: auto; min-width: 210px; font-size: 13px; }

/* The one warning in the app. It only ever appears on the rung with no human. */
.danger-note {
  border: 1px solid var(--neg-line);
  background: var(--neg-wash);
  border-radius: 12px;
  padding: 14px 16px;
  margin-top: 4px;
}
.danger-note-title {
  display: block;
  font-size: 13px; font-weight: 600; color: var(--neg);
  margin-bottom: 6px;
}
.danger-note p { margin: 0 0 6px; font-size: 13px; line-height: 1.5; color: var(--text-muted); }
.danger-note p:last-child { margin-bottom: 0; }

@media (max-width: 760px) {
  .agent-row { flex-direction: column; align-items: stretch; }
  .agent-row-actions { width: 100%; }
  .agent-mode-pick { flex: 1; min-width: 0; }
}

/* ── Finished work (the second gate) ────────────────────────────────── */
.work-group { display: flex; flex-direction: column; gap: 10px; margin-bottom: 22px; }
/* The agent's own words for what it did, once the plain line has taken the
   lead. Kept visible rather than hidden behind the toggle: it is the sentence
   that says *what* changed, and skipping it to read the reasoning is backwards. */
.change-summary {
  font-size: 13px;
  color: var(--text-muted);
  line-height: 1.5;
  max-width: 82ch;
}
.change-task { font-size: 12px; color: var(--text-dim); }
.change-facts { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 2px; }
.change-fact { display: flex; flex-direction: column; gap: 2px; }
.change-fact-v { font-size: 13px; color: var(--text); font-variant-numeric: tabular-nums; }
.change-fact-v[data-tone="pos"] { color: var(--pos); }
.change-fact-v[data-tone="neg"] { color: var(--neg); }
.change-block { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
.change-block:last-child { margin-bottom: 0; }
.change-files { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 3px; }
.change-files li { font-size: 13px; }
.count-num[data-tone="review"] { color: var(--warn); }

.rejected-block { margin-bottom: 16px; }
.rejected-list { display: flex; flex-direction: column; gap: 5px; margin-top: 8px; }
.rejected-item {
  display: flex; gap: 10px; align-items: baseline;
  font-size: 13px; color: var(--text-muted);
  text-decoration: line-through;
  text-decoration-color: var(--text-dim);
}
.rejected-item .rejected-title { flex: 1; }
.rejected-item .linkbtn { text-decoration: none; flex: 0 0 auto; }
.rejected-item .mono { font-family: var(--body); font-size: 12px; color: var(--text-dim); text-decoration: none; }

@media (max-width: 760px) {
  .prop { flex-direction: column; gap: 12px; }
  /* Wrap, do not squeeze. The buttons say whole phrases now ("Send back for
     changes"), and .btn is nowrap — four of them on one row would push the
     card off the screen rather than getting narrower. */
  .prop-actions { flex-direction: row; flex-wrap: wrap; width: 100%; }
  .prop-actions .btn { flex: 1 1 auto; }
}

/* ── States: loading / empty / error ───────────────────────────────── */
.app-state, .empty {
  padding: 52px 30px;
  text-align: center;
}
.state-title {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--value);
}
.state-body { color: var(--text-muted); margin: 10px auto 0; max-width: 62ch; font-size: 14px; }
.state-body code {
  font-family: var(--mono); font-size: 12px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
  padding: 1px 5px; color: var(--text);
}
.state-actions { margin-top: 18px; display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

/* Google renders its own button into here and owns how it looks — the sign-in
   mark has to be Google's to be trusted, and their terms say so too. All this
   does is give it the same air as the buttons it stands in for. */
.signin-actions { margin-top: 24px; min-height: 44px; }

/* The icon over an empty screen. Same accent wash as the active rail tile and
   the app tiles' initials, so an empty screen still looks like this app rather
   than like a page that failed to load. */
.state-mark {
  width: 54px;
  height: 54px;
  margin: 0 auto 18px;
  border-radius: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-wash);
  color: var(--accent);
}
.state-mark svg { width: 25px; height: 25px; }

/* An empty screen here has to explain a gate, and three centred paragraphs of
   equal weight is a wall rather than an explanation — nothing tells you where
   one point ends. Same three points as tiles: each gets a heading you can scan,
   they sit left-aligned so the lines start in the same place, and they run
   across the width instead of down the middle. */
.state-notes {
  list-style: none;
  margin: 28px auto 0;
  padding: 0;
  max-width: 1020px;
  text-align: left;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: 12px;
}
.state-notes li {
  background: var(--bg-raise);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 15px 16px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-muted);
}
.state-notes strong {
  display: block;
  margin-bottom: 4px;
  font-weight: 600;
  color: var(--value);
}
.state-error .state-title { color: var(--neg); }
.state-steps { text-align: left; max-width: 62ch; margin: 14px auto 0; color: var(--text-muted); font-size: 14px; line-height: 1.7; padding-left: 20px; }

.skeleton { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: var(--gap); }
.skel-card {
  height: 178px;
  background: linear-gradient(100deg, var(--surface) 30%, var(--bg-raise) 50%, var(--surface) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.5s linear infinite;
}
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -20% 0; } }

/* ── Modal ─────────────────────────────────────────────────────────── */
.modal { position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center; padding: 20px; }
.modal-backdrop { position: absolute; inset: 0; background: var(--scrim); backdrop-filter: blur(4px); }
.modal-card {
  position: relative;
  width: min(560px, 100%);
  max-height: 88vh;
  overflow-y: auto;
  background: var(--surface);
  border: none;
  border-radius: 16px;
  padding: 26px 28px 22px;
  box-shadow: 0 24px 60px rgba(15, 23, 42, .25);
}
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-bottom: 15px; border-bottom: 1px solid var(--hairline); margin-bottom: 18px; }
.modal-title { font-size: 1.3rem; font-weight: 600; line-height: 1.25; color: var(--value); }
.modal-form { display: flex; flex-direction: column; gap: 13px; }

/* Ask dialogs: the app asking, not the browser interrupting. */
.ask-body {
  margin: 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text);
  max-width: 60ch;
}
.btn--solid-danger {
  background: var(--neg);
  border-color: var(--neg);
  color: var(--on-accent);
}
.modal-error {
  margin: 14px 0 0;
  color: var(--neg);
  font-size: 13px;
  border: 1px solid var(--neg-line);
  background: var(--neg-wash);
  border-radius: 10px;
  padding: 9px 12px;
}
.modal-actions { display: flex; gap: 8px; align-items: center; margin-top: 18px; }
.modal-actions .spacer { margin-left: auto; }
.modal-meta { font-size: 12px; color: var(--text-dim); }

/* ── Task pictures ─────────────────────────────────────────────────────
   Thumbnails in the dialog, full size in the viewer. A picture worth pinning
   to a card is usually a screenshot, and a screenshot at 84px is unreadable —
   the tile is there to say which one it is, not to be read. */
/* ── Choosing a picture ─────────────────────────────────────────────
   The native file input is the one control in the app that answers to none of
   these tokens - a system-grey button and "No file chosen" in the browser's own
   font. So it is clipped out of the layout and a label dressed as one of our
   quiet buttons is what you actually press. See imageField() for why it is
   clipped rather than hidden, and why the label wraps only its own text. */
.filepick-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.filepick {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: var(--idle-wash);
  color: var(--text-muted);
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background .2s ease, color .2s ease, box-shadow .2s ease;
}
.filepick:hover {
  background: var(--surface);
  color: var(--value);
  box-shadow: 0 0 0 1.5px var(--border-hover);
}
/* The input is what the keyboard lands on, and it is not on screen - so the
   ring has to be drawn on the thing standing in for it, or tabbing through the
   modal would pass through an invisible stop. */
.filepick-input:focus-visible + .filepick {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Six already on the card. draw() disables the input at the cap; without this
   the button carried on looking pressable and simply did nothing. */
.filepick-input:disabled + .filepick {
  opacity: .5;
  cursor: not-allowed;
  background: var(--idle-wash);
  color: var(--text-muted);
  box-shadow: none;
}

.imgstrip { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 4px; }
.imgtile { position: relative; margin: 0; width: 84px; display: flex; flex-direction: column; gap: 4px; }
.imgtile img,
.imgtile-wait {
  width: 84px;
  height: 84px;
  border-radius: 8px;
  border: 1px solid var(--hairline);
  background: var(--idle-wash);
  display: block;
}
.imgtile img { object-fit: cover; cursor: zoom-in; }
.imgtile-wait {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: var(--text-dim);
}
.imgtile figcaption {
  font-size: 11px;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Always visible rather than shown on hover: there is no hover on a phone, and
   the one thing you need on a tile you picked by mistake is the way off it. */
.imgtile-x {
  position: absolute;
  top: 4px;
  inset-inline-end: 4px;
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 50%;
  background: var(--scrim);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: .8;
}
.imgtile-x:hover { opacity: 1; }
#m-image-file { font-size: 13px; color: var(--text-muted); }
#m-image-file:disabled { opacity: .5; }

/* The badge on a board card. Icon and count, never a thumbnail — the pictures
   are fetched when a card opens, and a board of them would undo that. */

/* Above the dialog (50) and below a toast (60), so a save confirmation still
   reads while a picture is open. */
.lightbox { position: fixed; inset: 0; z-index: 55; display: flex; align-items: center; justify-content: center; padding: 28px; }
.lightbox-img { position: relative; max-width: 100%; max-height: 100%; border-radius: 10px; box-shadow: var(--shadow-pop); }
.lightbox-x { position: absolute; top: 16px; inset-inline-end: 18px; color: #fff; }

/* ── Toast ─────────────────────────────────────────────────────────── */
/* toast() stamps data-accent, so the rail follows the tone. This is only the
   colour before the first toast has ever fired. */
.toast:not([data-accent]) { --rail: var(--pos); }
.toast {
  position: fixed;
  bottom: 22px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-left: 4px solid var(--rail, var(--action));
  border-radius: 12px;
  padding: 12px 17px;
  font-size: 13px;
  box-shadow: var(--shadow-pop);
  max-width: min(520px, calc(100vw - 40px));
}

/* ── Narrow screens (desktop-first, but nothing may overflow) ──────── */
@media (max-width: 1040px) {
  .project-body { grid-template-columns: minmax(0, 1fr); }
  .info-panel { position: static; }
  .board { grid-template-columns: repeat(4, minmax(210px, 1fr)); overflow-x: auto; padding-bottom: 6px; }
  /* The priority word goes, the rail keeps saying it. At this width the four
     columns are at their floor and the row has to choose between the word and
     the due date - and the word is the one already being said in colour down
     the card's own edge. */
  .tcard-prio { display: none; }
  /* And so does its separator. `display:none` leaves the element in the tree,
     so the sibling rule still fires and the row opened with a stray dot. It has
     to be keyed on what follows the hidden element, not on :first-child, which
     the hidden element is still the one matching. */
  .tcard-prio + *::before { content: none; }
}

@media (max-width: 760px) {
  #main { padding: 18px 16px 48px; }
  /* A modal is the width of the phone here, so its paired fields are as
     cramped as the info panel's and stack for the same reason. */
  .field-row { grid-template-columns: minmax(0, 1fr); }
  /* The rail lies down across the bottom of the screen, thumb-height. */
  body { flex-direction: column; }
  #sidebar {
    position: fixed; bottom: 0; left: 0; top: auto;
    width: 100%; min-width: 0; height: 58px;
    flex-direction: row; justify-content: space-around;
    padding: 0 8px;
    border-right: none; border-top: 1px solid var(--border);
  }
  .rail-brand { display: none; }
  .tabs { flex-direction: row; gap: 2px; }
  /* No column to fall to the bottom of — it just joins the row of tabs. */
  .rail-account {
    margin-top: 0; flex-direction: row; gap: 4px; width: auto;
    padding-top: 0; border-top: 0;
  }
  #right-side { padding-bottom: 58px; }
  #top-bar { height: auto; padding: 10px 16px; flex-wrap: wrap; gap: 8px; }
  .bar-spacer { margin-left: 0; }
  .card-row, .skeleton, .app-grid { grid-template-columns: minmax(0, 1fr); }
  .viewtitle { font-size: 24px; }
  .feed-item { grid-template-columns: minmax(0, 1fr); gap: 5px; }
  .field-row { grid-template-columns: minmax(0, 1fr); }
  .phead-actions { margin-left: 0; width: 100%; }
}

/* ── Runs: asking for something, and seeing whether anything answered ───
   Every control here is a request. The visual job is to keep "I asked" and
   "it is happening" from looking the same, because a button that looks busy
   while nothing is listening is worse than one that looks idle. */

.run-pill {
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  padding: 5px 12px;
  border-radius: 20px;
  border: none;
  color: var(--text-muted);
  background: var(--idle-wash);
  white-space: nowrap;
}
.run-pill[data-tone="info"] { color: var(--info); background: var(--info-wash); }
.run-pill[data-tone="warn"] { color: var(--warn); background: var(--warn-wash); }
.run-pill[data-tone="neg"]  { color: var(--neg);  background: var(--neg-wash); }
.run-pill:hover { box-shadow: 0 0 0 1.5px var(--border-hover); }

/* A queued button is not a disabled button — it is a button reporting a state,
   so it keeps its colour instead of fading out of the card. */
.btn[data-tone="info"][disabled],
.btn[data-tone="warn"][disabled],
.btn[data-tone="neg"][disabled] { opacity: .95; }
.btn[data-tone="info"] { border-color: var(--info); color: var(--info); background: transparent; }
.btn[data-tone="warn"] { border-color: var(--warn); color: var(--warn); background: transparent; }
.btn[data-tone="neg"]  { border-color: var(--neg);  color: var(--neg);  background: var(--neg-wash); }

.run-hint {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
  margin-top: 8px;
  max-width: 74ch;
}
.run-hint[data-tone="neg"] { color: var(--neg); }
.run-hint code {
  font-family: var(--mono); font-size: 11px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
  padding: 1px 5px; color: var(--text);
}

.runs-block {
  display: flex; flex-direction: column; gap: 10px;
  margin-bottom: 22px;
}
.runs-idle { font-size: 13px; color: var(--text-muted); line-height: 1.55; max-width: 74ch; }

/* Whether anything is listening on the other end of the queue.

   Sits above the rows rather than inside the header line on purpose: on the day
   this was needed the header said "idle", which was true of the queue and
   silent about the runner, and a full sentence is what was missing. The rail
   carries the tone, and the chip says it in words as well - nothing here
   depends on colour alone. */
.runner-bar {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px;
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--rail, var(--idle));
  border-radius: 10px;
  background: var(--bg-raise);
}
.runner-bar-what {
  font-size: 13px; color: var(--text-muted); line-height: 1.55;
  flex: 1; min-width: 0; max-width: 78ch;
}

/* Show-all and Clear-finished share a line. Space-between rather than a gap so
   the destructive-sounding one is never the thing your thumb lands on first. */
.runs-foot { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.runs-foot:empty { display: none; }
.runs-foot .linkbtn:only-child { margin-right: auto; }

.run-row {
  display: flex; align-items: flex-start; gap: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 16px 18px;
}
.run-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.run-row-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.run-row-top .chip { text-decoration: none; }
.run-row-what { font-size: 14px; color: var(--text); line-height: 1.45; }

/* The tail of a build log. Scrolls inside itself so a long line never widens
   the page — the one thing a log must not be allowed to do to a layout. */
.run-log {
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--text-muted);
  background: var(--bg-raise);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  padding: 8px 10px;
  margin: 2px 0 0;
  max-height: 190px;
  overflow: auto;
  white-space: pre;
}

/* Opened in full it is no longer a log being glanced at but a report being
   read, so the lines wrap and the box is given the height of a page. The
   sideways scroll is what made a prose report unreadable in the first place. */
.run-log[data-full] {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 60vh;
  max-width: 96ch;
}

@media (max-width: 760px) {
  .run-row { flex-direction: column; }
  .run-row .btn { width: 100%; }
  .run-pill { display: none; }   /* the Agents screen carries the same news */
}

/* Said out loud on a row that is history rather than an assignment. Without it
   the only signal was the absence of a chip, which nobody reads as a fact. */
.agent-row-note {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
  max-width: 70ch;
}
.agent-row-note strong { color: var(--text); font-family: var(--mono); font-size: 11px; }

/* ── A roster, not a single agent ──────────────────────────────────────
   A project carries several agents now, each with its own permission. The
   layout has to make "these are separate grants" obvious at a glance, or the
   list reads as one setting with several rows. */
.pi-roster { display: flex; flex-direction: column; gap: 6px; }
.pi-agent-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name remove" "mode mode";
  gap: 5px 8px;
  align-items: center;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-raise);
}
.pi-agent-name { grid-area: name; font-size: 12px; color: var(--value); min-width: 0; overflow-wrap: anywhere; }
.pi-agent-mode { grid-area: mode; width: 100%; font-size: 12px; }
.pi-agent-row .iconbtn { grid-area: remove; }
.pi-empty { font-size: 12px; color: var(--text-dim); }

/* Ticking several agents at once. The whole row is the target rather than the
   box alone — this is a list you run down, and a 15px square you have to aim at
   makes "add all four" feel like four decisions. */
.checklist {
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 4px;
  background: var(--bg-raise);
}
.checkrow {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 9px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 13px;
  color: var(--text);
}
/* A lift off the sunk list, not --raise-hover: in light that token *is*
   --bg-raise, so the hover on this ground would be no hover at all. */
.checkrow:hover { background: var(--surface); }
.checkrow input[type="checkbox"] { accent-color: var(--accent); width: 15px; height: 15px; flex: none; }
.checkrow .mono { font-size: 12px; color: var(--value); overflow-wrap: anywhere; }
/* Above the list, not in it: it names the list rather than being a member. The
   extra 4px is the list's own padding, so its box still lines up with theirs. */
.checkrow--all { padding-left: 13px; color: var(--text-muted); font-weight: 600; }

.agent-head-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.agent-shift-note {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
  margin-bottom: 10px;
  max-width: 78ch;
}

/* The one line under a page title that says what you do on that page. */
.viewsub { margin: -6px 0 16px; max-width: 76ch; }
