/* Everything that is not the shell: type, tables, forms, cards, badges, dialogs,
 * and the states around the data.
 *
 * Ported from the old app.css, with two systematic changes.
 *
 * Colour. Red no longer labels a delete, fills an error toast, outlines the
 * selected plan or paints a coverage bar. Each of those has a token of its own
 * now, so the accent is free to mean "act here" everywhere it appears.
 *
 * Density. Rows are ~34px rather than ~44px. This is an operations console -
 * runs, grants, jobs - where rows-per-screen is the thing being optimised.
 * Prose surfaces keep their air.
 */

* { box-sizing: border-box; }

/* --- type ---------------------------------------------------------------- */

h1 { font-size: 1.3rem; margin: 0 0 .25rem; letter-spacing: -.022em; font-weight: 700; }
h2 { font-size: 1rem; margin: 0 0 .7rem; font-weight: 700; }
h3 { font-size: .9rem; margin: 0 0 .5rem; font-weight: 700; }

.lede { color: var(--muted); font-size: .875rem; margin: 0; max-width: 66ch; }
.muted { color: var(--muted); }
.small { font-size: .8rem; }
a { color: var(--ink); text-decoration-color: var(--accent); text-underline-offset: 2px; }

/* The page head. Pages that sit inside the shell no longer need to restate where
   they are - the crumb and title do that - so this is now only for the pages
   that carry an action button beside their heading. */
.page-head {
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
  margin-bottom: var(--s4);
}
.page-head .grow { flex: 1; min-width: 0; }

/* Kept for the signed-out pages, which have no crumb to go back through. */
.back {
  display: inline-block;
  color: var(--muted);
  text-decoration: none;
  font-size: .8rem;
  margin-bottom: .5rem;
}
.back:hover { color: var(--accent); }

code, .mono { font-family: var(--mono); font-size: .88em; }
code.hash { font-family: var(--mono); font-size: .78rem; color: var(--muted); }

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

table { width: 100%; border-collapse: collapse; }

th, td {
  text-align: left;
  padding: .42rem .6rem .42rem 0;
  border-bottom: 1px solid var(--rule-2);
  vertical-align: top;
}

th {
  font-size: .64rem;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--muted);
  font-weight: 700;
  border-bottom: 1px solid var(--rule);
  padding-bottom: .35rem;
}

tbody tr:last-child td { border-bottom: 0; }

td.id, th.id { width: 3.5rem; color: var(--muted); font-family: var(--mono); font-size: .78rem; font-variant-numeric: tabular-nums; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.actions, th.actions { width: 1%; white-space: nowrap; text-align: right; padding-right: 0; }
td.avatar, th.avatar { width: 2.5rem; }
td.chips { color: var(--muted); }

.chip {
  display: inline-block;
  margin: .08rem .22rem .08rem 0;
  padding: .05rem .38rem;
  border: 1px solid var(--rule);
  font-size: .76rem;
  white-space: nowrap;
  color: var(--muted);
}

tr.clickable { cursor: pointer; }
tr.clickable:hover td { background: var(--sunk); }

/* A framed table: hairline box, tinted header. Was scoped to #agents; it is a
   general treatment now, because the roster was never the only list that
   deserved to read as one object. */
table.framed {
  border-collapse: separate;
  border-spacing: 0;
  border: 1px solid var(--rule);
  background: var(--surface);
}
table.framed th, table.framed td { padding: .45rem .7rem; }
table.framed thead th { background: var(--sunk); border-bottom: 1px solid var(--rule); }
table.framed tbody tr:last-child td { border-bottom: none; }
table.framed tbody tr.clickable:hover td { background: var(--sunk); }

/* Row actions appear on hover. A row that is already clickable does not need a
   permanent Open button sitting on it, and a permanent Delete beside one is how
   a list ends up looking like a control panel. Focus-within keeps them reachable
   from the keyboard, and touch has no hover so they stay put there. */
/* visibility, not opacity. An opacity:0 button is still hit-testable, so the
   invisible Delete would swallow the click that was meant to open the row -
   which is the worst possible outcome for the one control that destroys
   something. visibility:hidden keeps the column from reflowing and takes the
   button out of hit testing at the same time. */
@media (hover: hover) {
  td.actions .rowact { visibility: hidden; opacity: 0; transition: opacity .12s; }
  tr:hover td.actions .rowact,
  td.actions .rowact:focus-within { visibility: visible; opacity: 1; }
}

/* Keep the desktop structure on phones; scroll the table, never the page. */
@media (max-width: 760px) {
  table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* --- buttons ------------------------------------------------------------- */

button {
  font: inherit;
  font-size: .82rem;
  padding: .32rem .7rem;
  border: 1px solid var(--rule);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  border-radius: 0;
}
button:hover { background: var(--sunk); }
button:disabled { color: var(--faint); cursor: default; background: var(--surface); }

button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 700; }
button.primary:hover { background: var(--accent-d); border-color: var(--accent-d); }
button.primary:disabled { background: var(--sunk); border-color: var(--rule); color: var(--faint); }

button.ghost { border-color: transparent; color: var(--muted); background: none; }
button.ghost:hover { color: var(--ink); background: var(--sunk); }

/* Destructive, before the point of no return. Neutral here on purpose: this
   button only opens a question. Red arrives in the confirm dialog, where the
   action becomes real. */
button.danger { color: var(--muted); }
button.danger:hover { color: var(--danger); border-color: var(--danger); background: var(--surface); }

button.tiny { font-size: .74rem; padding: .12rem .42rem; }

/* --- cards --------------------------------------------------------------- */

section.card {
  border: 1px solid var(--rule);
  padding: var(--s4) 1.1rem;
  margin-bottom: var(--s4);
  background: var(--surface);
}
section.card > header {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  margin-bottom: var(--s3);
}
section.card > header .grow { flex: 1; min-width: 0; }
section.card > header h2 { margin: 0; }
section.card > header .note { color: var(--muted); font-size: .8rem; }

/* A grouped block inside a dialog. */
.card-inset {
  border: 1px solid var(--rule);
  padding: var(--s3) .8rem .1rem;
  margin: .85rem 0;
  background: var(--page);
}
.card-inset h3 { margin: 0 0 .5rem; }
.card-inset > .hint { margin: 0 0 .5rem; }
.card-inset ul { margin: 0 0 var(--s3); padding-left: 1.1rem; font-size: .85rem; }

/* --- the states around the data ------------------------------------------ */

/* Loading. A skeleton at the real row height, so nothing jumps when the data
   lands - the old "Loading…" in a table cell was one line tall and every table
   snapped downwards the moment it resolved. */
/* The margin is load-bearing: .8rem of bar plus .28rem either side comes to the
   1.36rem line box a real row of text occupies, so the table does not jump when
   the data lands. A bar sized to itself would be shorter than the rows it stands
   in for and defeat the point. */
.skeleton {
  display: block;
  height: .8rem;
  margin: .28rem 0;
  background: linear-gradient(90deg, var(--sunk) 25%, var(--rule-2) 37%, var(--sunk) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}
.skeleton-card { display: flex; flex-direction: column; gap: .55rem; }
.skeleton.w40 { width: 40%; }
.skeleton.w60 { width: 60%; }
.skeleton.w80 { width: 80%; }
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none; } }

/* Empty. Says what would fill it and offers the thing that does - one grey
   sentence told you a list was empty but never what to do about it. */
.emptystate {
  border: 1px solid var(--rule);
  background: var(--surface);
  padding: var(--s6) var(--s5);
  text-align: center;
}
.emptystate h3 { margin: 0 0 .3rem; font-size: 1rem; }
.emptystate p { margin: 0 auto var(--s4); color: var(--muted); font-size: .875rem; max-width: 48ch; }
.emptystate p:last-child { margin-bottom: 0; }

.empty { color: var(--muted); font-size: .875rem; padding: var(--s3) 0; }
.placeholder td { color: var(--muted); }
.placeholder.error, .placeholder.error td { color: var(--danger); }

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

.field { display: block; margin-bottom: .85rem; }
.field-label {
  display: block;
  font-size: .64rem;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--muted);
  font-weight: 700;
  margin-bottom: .25rem;
}
.hint { display: block; font-size: .78rem; color: var(--muted); margin-top: .25rem; }

input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input:not([type]), textarea, select {
  width: 100%;
  font: inherit;
  font-size: .875rem;
  padding: .35rem .5rem;
  border: 1px solid var(--rule);
  border-radius: 0;
  background: var(--surface);
  color: var(--ink);
}
textarea { resize: vertical; min-height: 4.5rem; line-height: 1.55; }
input:focus, textarea:focus, select:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}

/* Wraps, and each field claims a readable width before it does. Three selects
   in a row is right at full width and unusable at 30rem - which is exactly the
   width of the side panel these forms now open in. */
.row { display: flex; gap: var(--s3); flex-wrap: wrap; }
.row > * { flex: 1 1 11rem; min-width: 0; }

.checklist {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem .6rem;
  border: 1px solid var(--rule);
  padding: .5rem .6rem;
  max-height: 11rem;
  overflow-y: auto;
  background: var(--surface);
}
.check { display: flex; align-items: center; gap: .35rem; font-size: .85rem; }
.check input { margin: 0; width: auto; }

/* --- badges and status --------------------------------------------------- */

.badge {
  display: inline-block;
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  padding: .1rem .36rem;
  border: 1px solid var(--rule);
  color: var(--muted);
  white-space: nowrap;
}
.badge.ok   { color: var(--ok);   border-color: currentColor; }
.badge.warn { color: var(--warn); border-color: currentColor; }

/* Risk keeps the accent. It is the one badge whose job is to stop you, and the
   only place outside brand and action where alarm red is the honest colour. */
.badge.risk-high   { color: var(--accent);   border-color: currentColor; }
.badge.risk-medium { color: var(--warn);  border-color: currentColor; }
.badge.risk-low    { color: var(--muted); }

/* A proposed grant must not look like an approved one at a glance - the whole
   safety model rests on that difference being visible. Amber, not red: it is
   waiting on someone, which is not the same as dangerous. */
tr.proposed td { background: var(--warn-wash); }

/* --- dialogs ------------------------------------------------------------- */

dialog.editor {
  border: 1px solid var(--rule);
  border-radius: 0;
  background: var(--surface);
  color: var(--ink);
  padding: 0;
  width: min(34rem, calc(100vw - 2rem));
  box-shadow: var(--shadow-lg);
}
dialog.editor::backdrop { background: rgba(22, 17, 15, .45); }
dialog.editor form { padding: 1.1rem 1.25rem var(--s4); }
dialog.editor h2 { margin: 0 0 var(--s4); }
dialog.editor menu {
  display: flex;
  justify-content: flex-end;
  gap: .5rem;
  margin: var(--s4) 0 0;
  padding: 0;
}
.dialog-error { color: var(--danger); font-size: .85rem; margin: .5rem 0 0; }

/* The confirm dialog, replacing window.confirm(). Narrower than an editor, and
   the one place a filled red button appears on a destructive action - by the
   time you are reading this, the thing being deleted has been named. */
dialog.confirm { width: min(26rem, calc(100vw - 2rem)); }
dialog.confirm .what { font-weight: 700; }
dialog.confirm .consequence { color: var(--muted); font-size: .875rem; margin: .4rem 0 0; }
dialog.confirm button.destroy {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
  font-weight: 700;
}
dialog.confirm button.destroy:hover { filter: brightness(1.1); }

/* --- toasts -------------------------------------------------------------- */

.toasts {
  position: fixed;
  bottom: var(--s4); right: var(--s4);
  display: flex;
  flex-direction: column;
  gap: .4rem;
  z-index: 90;
}
.toast {
  background: var(--ink);
  color: var(--page);
  font-size: .85rem;
  padding: .5rem .8rem;
  box-shadow: var(--shadow-lg);
  transition: opacity .3s, transform .3s;
  max-width: 30rem;
}
/* An error is read, not glanced at - often it is a whole sentence from the
   server about a credential or an account. A solid red fill made it compete
   with the Save button for the same weight and made the text harder to read
   than the thing it was reporting on. */
.toast.error {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--danger);
}
.toast.out { opacity: 0; transform: translateY(.3rem); }

/* --- the overview ---------------------------------------------------------
   A strip of counts over two lists. Every tile is a link: a count of two
   approvals is only useful beside the way to go and make it zero. */

.statstrip {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: var(--s5);
}
.stat {
  flex: 1 1 140px;
  min-width: 120px;
  background: var(--surface);
  border: 1px solid var(--rule);
  padding: .55rem .8rem .6rem;
  display: flex;
  flex-direction: column;
  gap: .15rem;
  text-decoration: none;
  color: var(--ink);
}
a.stat:hover { border-color: var(--ink); }
.stat .n {
  font-size: 1.5rem; font-weight: 700; line-height: 1.15;
  letter-spacing: -.025em; font-variant-numeric: tabular-nums;
}
.stat .l { font-size: .72rem; color: var(--muted); }
/* A non-zero queue is not neutral information. Zero of the same thing stays
   plain - a red 0 is an alarm about nothing. */
.stat.alert { border-color: var(--accent); }
.stat.alert .n { color: var(--accent); }

/* min(330px, 100%), not a bare 330px: auto-fit will happily lay out a track
   wider than its container, and on a 360px phone with the view's padding there
   are only 328px to play with - the page would scroll sideways to show a column
   that could have just been narrower. */
.overviewgrid {
  display: grid;
  gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(330px, 100%), 1fr));
  align-items: start;
}

/* One row in either overview list. */
.lineitem {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: .45rem 0;
  border-bottom: 1px solid var(--rule-2);
  text-decoration: none;
  color: var(--ink);
}
.lineitem:last-child { border-bottom: 0; }
.lineitem:hover { background: var(--sunk); }
.lineitem .li-tag {
  font-size: .62rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); flex: none; min-width: 4.6rem;
}
.lineitem .li-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.lineitem .li-what { font-size: .875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lineitem .li-why { font-size: .76rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lineitem .li-when {
  font-family: var(--mono); font-size: .72rem; color: var(--faint); flex: none;
  font-variant-numeric: tabular-nums;
}

/* --- the ask panel --------------------------------------------------------
   A transcript over a composer. Scoped to one agent, so there is never a
   question about who is answering. */

.asklog { max-height: 26rem; overflow-y: auto; }
.asklog:empty { display: none; }

.askturn { border-top: 1px solid var(--rule-2); padding: .6rem 0; }
.asklog .askturn:first-child { border-top: 0; padding-top: 0; }
.askturn .who {
  font-size: .68rem; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .25rem;
}
/* pre-wrap, because a model answers in paragraphs and collapsing them turns an
   explanation into a wall. */
.askturn .body { font-size: .9rem; line-height: 1.55; white-space: pre-wrap; word-wrap: break-word; }
.askturn.pending .body { color: var(--faint); font-style: italic; }
.askturn.err .body { color: var(--danger); }

.askform {
  display: flex;
  align-items: flex-end;
  gap: .5rem;
  border: 1px solid var(--rule);
  padding: .45rem .5rem;
  margin-top: var(--s3);
}
.askform:focus-within { border-color: var(--accent); }
.askinput {
  flex: 1;
  border: 0;
  background: transparent;
  resize: none;
  min-height: 2.4rem;
  max-height: 9rem;
  padding: .25rem;
  font: inherit;
  font-size: .9rem;
}
.askinput:focus { outline: none; border: 0; }
.askform button { flex: none; }

/* --- charts -------------------------------------------------------------- */

.stats-filter { min-width: 12rem; }
.stats-filter .field-label { margin-bottom: .2rem; }

svg.chart { width: 100%; height: auto; display: block; }
svg.chart .grid { stroke: var(--rule-2); stroke-width: 1; }
svg.chart .axis { fill: var(--muted); font-size: 11px; font-family: var(--mono); }
svg.chart .series { fill: none; stroke-width: 2; }
/* The folded remainder. Thinner and dashed as well as grey, so it reads as an
   aggregate rather than as a quiet fifth agent. */
svg.chart .series.muted-series { stroke-width: 1.5; stroke-dasharray: 4 3; }
/* Direct labels wear text ink. A name set in its own series colour is a legend
   pretending to be a label, and it fails exactly when the colour is what is in
   doubt - the coloured dot beside it carries the identity instead. */
svg.chart .direct { fill: var(--ink); font-size: 11px; font-family: var(--grot); }

.chartnote {
  margin: var(--s3) 0 0;
  font-size: .78rem;
  color: var(--muted);
  border-top: 1px solid var(--rule-2);
  padding-top: .6rem;
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem var(--s4);
  margin-top: var(--s3);
  font-size: .8rem;
  color: var(--muted);
}
.legend-item { display: inline-flex; align-items: center; gap: .35rem; }
.legend .swatch { display: inline-block; width: .7rem; height: .7rem; }

/* --- coverage bars (roles) ----------------------------------------------- */

.role-name { display: flex; align-items: flex-start; gap: .5rem; }
.caret {
  border: none; background: none; padding: 0; margin: 0;
  width: 1rem; color: var(--muted); font-size: .8rem; line-height: 1.5;
  cursor: pointer; flex: none;
}
.caret:hover { background: none; color: var(--ink); }
.caret.empty { cursor: default; color: var(--rule); }

.cov { display: flex; align-items: center; gap: .5rem; min-width: 9rem; }
.cov-track { flex: 1; height: 5px; background: var(--rule); overflow: hidden; }
.cov-fill { height: 100%; background: var(--muted); }
/* Slate, not red. How much of a role is still done by a person is a fact about
   the work, not a failure - painting the low end in alarm red told you the
   opposite of what the number means. */
.cov-fill.lo  { background: var(--scale-lo); }
.cov-fill.mid { background: var(--scale-mid); }
.cov-fill.hi  { background: var(--scale-hi); }
.cov-fill.none { width: 0; }
.cov-label { font-size: .8rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cov-note { color: var(--muted); font-size: .72rem; margin-top: .15rem; }

table.roles tr.subrow > td { background: var(--page); }
table.roles tr.subrow .cov { min-width: 9rem; }
td.subrow-name { padding-left: 2.25rem; color: var(--muted); }

/* --- plans --------------------------------------------------------------- */

.plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--s4);
  margin-top: .5rem;
}
.plan { display: flex; flex-direction: column; }
.plan h3 { margin: 0 0 .5rem; }
.plan ul { margin: 0 0 var(--s4); padding-left: 1.1rem; color: var(--muted); flex: 1; }
.plan button, .plan .muted { margin-top: auto; }
/* Ink, not accent. Which plan you are on is a statement of fact; it was drawn in
   the same colour as the button that changes it. */
.plan-current { outline: 2px solid var(--ink); outline-offset: -1px; }

/* --- closing the account ------------------------------------------------- */

section.card.card-danger { border-color: var(--danger); }
.card-danger .note { color: var(--muted); font-size: .8rem; margin: 0 0 .5rem; }
.card-danger details { font-size: .8rem; color: var(--muted); margin: .5rem 0 .9rem; }
.card-danger details summary { cursor: pointer; color: var(--ink); }
.card-danger details ul { margin: .5rem 0 0; padding-left: 1.15rem; }
.card-danger details li { margin: .2rem 0; }
.card-danger form { display: flex; align-items: flex-end; gap: .6rem; flex-wrap: wrap; }
.card-danger .field { margin: 0; }
.card-danger .field b { font-family: var(--mono); }
.card-danger button[type="submit"] {
  background: var(--danger); border-color: var(--danger); color: #fff; font-weight: 700;
}
.card-danger button[type="submit"]:disabled {
  background: var(--sunk); border-color: var(--rule); color: var(--faint);
}

/* --- read-only pair lists ------------------------------------------------ */

dl.kv {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: .25rem var(--s3);
  margin: 0 0 var(--s4);
  font-size: .8rem;
}
dl.kv dt { color: var(--muted); }
dl.kv dd { margin: 0; font-family: var(--mono); word-break: break-all; }

/* --- one-time secrets ---------------------------------------------------- */

textarea.secret { font-family: var(--mono); font-size: .8rem; word-break: break-all; }
pre.snippet {
  background: var(--page);
  border: 1px solid var(--rule);
  padding: .6rem .7rem;
  font-family: var(--mono);
  font-size: .75rem;
  overflow-x: auto;
  margin: .25rem 0 0;
}

/* --- signed-out pages ---------------------------------------------------- */

.signin { max-width: 22rem; margin: var(--s6) auto; }
.signin h1 { margin-bottom: .25rem; }
.signin .lede { margin-bottom: var(--s5); }
.signin button { width: 100%; margin-top: .5rem; }
.signin .hint { margin-top: var(--s5); }

.or-divider {
  display: flex; align-items: center; gap: var(--s3);
  margin: 1.25rem 0; color: var(--muted); font-size: .8rem;
}
.or-divider::before, .or-divider::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

.google-btn {
  display: block; width: 100%; text-align: center;
  padding: .5rem .7rem; border: 1px solid var(--rule);
  text-decoration: none; color: var(--ink); font-weight: 700; font-size: .875rem;
}
.google-btn:hover { background: var(--sunk); }

/* --- focus --------------------------------------------------------------- */
/* One rule, everywhere. Only inputs had a focus style before, which left every
   button and link in the app keyboard-invisible. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The CV teaser's entrance on the signed-out pages. `.card-inset` already draws
   the box; this only reins in the heading, which is sized for a settings panel
   and shouts next to a sign-in form. */
.teaser-hook { margin-top: var(--s5); }
.teaser-hook h3 { font-size: .95rem; }
.teaser-hook > a { display: inline-block; margin-bottom: var(--s3); font-size: .875rem; }

/* --- competence: a skill beside what its work needs ------------------------
   Two columns per skill rather than a tree with a detail pane. The question the
   tab answers is asked of the whole agent - can it do the job it was hired for -
   and an answer you have to click nine skills to assemble is not one. A skill's
   needs are two or three lines, so showing them all costs a page you scroll
   rather than a page you interrogate. */

.cmprole + .cmprole { margin-top: var(--s5); }
.cmprolename {
  font-size: .78rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); padding-bottom: .3rem; border-bottom: 1px solid var(--rule);
}

.cmprow {
  display: grid;
  grid-template-columns: minmax(11rem, 20rem) 1fr;
  gap: var(--s4);
  padding: .55rem 0;
  border-bottom: 1px solid var(--rule-2);
  align-items: start;
}
@media (max-width: 760px) {
  /* One column under the breakpoint the rest of this file uses. The skill keeps
     its own row above what it needs, which is the reading order anyway. */
  .cmprow { grid-template-columns: 1fr; gap: var(--s2); }
}

.cmpskillname { display: flex; align-items: baseline; gap: .5rem; font-size: .9rem; }
/* Three markers because there are three answers, and the third earns the extra
   glyph: a skill nobody has assessed is not a skill that passed. */
.cmpmark { font-size: .82rem; }
.cmpmark.ok   { color: var(--ok); }
.cmpmark.warn { color: var(--warn); }
.cmpmark.none { color: var(--muted); }

.cmpneeds > * + * { margin-top: .4rem; }
.cmpneed { padding: .1rem 0; }
.cmpneedtop { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.cmpneedtop code { font-size: .82rem; }
/* Tinted rather than boxed: these rows already sit inside a bordered card, and a
   second outline round the one needing attention reads as a nested table. */
.cmpneed.short {
  background: var(--warn-wash); padding: .35rem .5rem;
  border-left: 2px solid var(--warn);
}
.cmpfix { margin: .25rem 0 0; color: var(--warn); }

.cmpnone { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }

.chip.ok   { color: var(--ok);   border-color: var(--ok); }
.chip.warn { color: var(--warn); border-color: var(--warn); }
/* A tool handle the executor no longer implements. Struck through rather than
 * hidden: a capability bound only to retired handles must not end up looking
 * like one that binds nothing, because those are different findings with
 * different fixes. The dashed border reads as degraded at a glance, before the
 * strike-through is legible at this size. */
.chip.retired {
  color: var(--faint);
  border-style: dashed;
  text-decoration: line-through;
}

.cmptally { margin-top: var(--s4); padding-top: var(--s3); border-top: 1px solid var(--rule); }

/* A picker in a card header. `select` is width:100% globally, and a full-width
   control in that flex header collapses the `.grow` div beside it to its
   `min-width: 0` - which is what wrapped a card note one word per line. */
section.card > header .headsel { width: auto; min-width: 8rem; }

/* --- the skill card ------------------------------------------------------ */
/* A generated document rather than a form or a table, so it gets prose measure
   and prose spacing. Everything else on this screen is an operations console at
   34px rows; this is the one surface somebody reads rather than scans. */

.cardsheet {
  border: 1px solid var(--rule);
  background: var(--surface);
  padding: var(--s4) var(--s5) var(--s5);
  max-width: 68ch;
}
.cardsheet h3 {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted); font-weight: 700;
  margin: var(--s5) 0 .3rem;
}
/* The sub-headings NVIDIA's shape nests inside a section - License/Terms sits
   under Publisher, Deployment Geography under Use Case. Same treatment as the
   level above, indented rather than resized: the difference between them is
   where they belong, not how loud they are. */
.cardsheet h4 {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .09em;
  color: var(--faint); font-weight: 700;
  margin: var(--s4) 0 .3rem; padding-left: .6rem;
  border-left: 2px solid var(--rule-2);
}
.cardsheet > :first-child { margin-top: 0; }
.cardsheet p { margin: 0 0 .55rem; font-size: .875rem; }
.cardsheet p:last-child { margin-bottom: 0; }

/* A banner above the thing it is about, never in place of it. A stale card is
   still the card somebody wants to read - the warning says what it no longer
   describes, and hiding the document behind it would answer a different
   question than the one they asked. */
.notice {
  border: 1px solid var(--rule);
  background: var(--sunk);
  padding: var(--s3) var(--s4);
  font-size: .85rem;
}
.notice.warn { border-color: var(--warn); background: var(--warn-wash); }
.notice.warn strong { color: var(--warn); }
.notice strong { display: block; }
.notice .small { color: var(--ink); opacity: .8; }

/* A stale measurement is worth saying in the row rather than only in a panel:
   it is the one line that stops a coverage figure being read as current. */
.warnnote { color: var(--warn); }

/* --- jobs, by role and by agent ------------------------------------------
   The Jobs page was one flat table with an Agent column, ordered by row id, and
   the column was where its structure went to hide: the same agent's name printed
   four times, and no way to see that one role carried eight of the twenty jobs.
   These classes are that column turned into a hierarchy - a card per role, an
   agent group inside it, the jobs inside that.

   Not built on `section.card`: a card pads its own edges, and the whole effect
   here depends on the job tables running flush to the border so the group headers
   read as bands across the card rather than as boxes floating inside one. */

.rolecard {
  border: 1px solid var(--rule);
  background: var(--surface);
  margin-bottom: var(--s4);
}
.rolecard > header {
  display: flex; align-items: flex-start; gap: var(--s4);
  padding: var(--s4) 1.1rem var(--s3);
  border-bottom: 1px solid var(--rule);
}
.rolecard > header .grow { flex: 1; min-width: 0; }
.rolecard > header h2 { margin: 0; font-size: 1rem; }

/* The role's own title line. The slug rides beside the name because a rename is
   allowed and the slug is what STAFFING actually keys on - reading them together
   is how somebody confirms this group is the role they think it is. */
.roletitle { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.roleslug {
  font-family: var(--mono); font-size: .68rem; color: var(--faint);
  background: var(--sunk); padding: .05rem .3rem;
}
.rolemeta { margin: .2rem 0 0; font-size: .78rem; color: var(--muted); }
.rolemeta b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }

.rolecount { flex: none; text-align: right; }
.rolecount .n {
  display: block; font-size: 1.15rem; font-weight: 700; line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.rolecount .l { font-size: .66rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }

/* One agent's jobs within the role, as a full-width band inside the same table
   rather than a table of its own. One table per role is what keeps the five
   columns aligned down the whole card - three sibling tables would each size
   their own columns, and "Files into" would step left and right between agents.
   The band is tinted rather than ruled: at four groups in a card, four more
   hairlines read as a grid of boxes, which is the thing --rule-2 exists to
   prevent. */
.jobtable tr.agentrow td { background: var(--sunk); padding: .3rem 1.1rem; }
.jobtable tr.agentrow + tr td { border-top: 1px solid var(--rule-2); }
.agenthead {
  display: flex; align-items: center; gap: var(--s3);
}
.agenthead .nm { font-size: .85rem; font-weight: 700; }
.agenthead .sub { font-size: .74rem; color: var(--muted); }
.agenthead .grow { flex: 1; min-width: 0; }

/* An agent running work its role does not staff. Nothing checks a Job row's
   agent against STAFFING, so this is the only place it is ever said. */
.rolefit {
  display: inline-flex; align-items: center; gap: .3rem; flex: none;
  font-size: .72rem; color: var(--warn); font-weight: 600;
}
.rolefit::before { content: "\25B2"; font-size: .6rem; }

/* `fixed`, and it is load-bearing rather than tidiness. The outcome below is one
   nowrap line with an ellipsis, and under the default `auto` layout a nowrap cell
   cannot shrink - so the browser widens the column to fit the whole sentence, the
   `width: 40%` is treated as a suggestion and ignored, and every column after it is
   pushed off the right edge. On a page where four of Pamela's rows carry
   paragraph-long outcomes, that meant Action, When, Files into and Status simply
   were not on screen. With `fixed`, the widths below are the widths. */
.jobtable { table-layout: fixed; }
.jobtable th, .jobtable td { padding: .4rem .7rem; overflow: hidden; }
.jobtable th:first-child, .jobtable td:first-child { padding-left: 1.1rem; width: 38%; }
.jobtable th:nth-child(2), .jobtable td:nth-child(2) { width: 10%; }
.jobtable th:nth-child(3), .jobtable td:nth-child(3) { width: 14%; }
/* The widest of the four, because it holds a chip per space and an agent seated in
   five wraps to two rows rather than clipping one. */
.jobtable th:nth-child(4), .jobtable td:nth-child(4) { width: 26%; }
.jobtable th:last-child, .jobtable td:last-child { padding-right: 1.1rem; width: 12%; }
.jobtable thead th { border-bottom: 1px solid var(--rule-2); }
.jobtable .t {
  font-weight: 600; font-size: .85rem;
  /* A long title clips rather than wrapping to three lines and setting the height
     of every cell beside it. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.jobtable .o {
  font-size: .76rem; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The agent header spans every column, so it must not inherit the clip above -
   without this its name and job count are cut at 38% of the table. */
.jobtable tr.agentrow td { overflow: visible; }
.jobtable .mono { font-family: var(--mono); font-size: .76rem; }
.jobtable .tz { font-size: .72rem; color: var(--faint); }
.jobtable .next { font-size: .72rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* The trailing card: a cron, no action, and therefore a row the tick never
   claims. It used to say so in grey in the last column, under a green Enabled
   badge - the one broken row on the page drawn quieter than the working ones. */
.rolecard.unfired { border-color: var(--accent); }
.rolecard.unfired > header { background: var(--accent-wash); border-bottom-color: var(--accent); }
.rolecard.unfired > header h2 { color: var(--accent-d); }
.badge.dead { color: var(--accent); border-color: currentColor; }

/* A count that is a piece of prose, not a number. Sits under the strip's tiles. */
.stat .sub { font-size: .72rem; color: var(--faint); }
.stat .n.words { font-size: 1.05rem; }

/* --- where a job files ----------------------------------------------------
 *
 * This was a word in a column - the job's space, or "fleet" - because a job had
 * at most one, and a job that wanted three had to be three rows to say so. It is
 * a chip per space now, and the chips are read-only: a job files wherever its
 * agent is a member, so the list is herdwork's roster and the place to change it
 * is there. A control here would be a second way to say the same thing. */
.jchips { margin-top: .2rem; }

/* The outcome is a prompt, not a description. Read once when it is written and
   never again by somebody scanning the table, so it is clamped to two lines with
   the whole of it on the row's title attribute. */
.jout {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

