/* Screen rules for projects, apps, deployments and their tabs.
   ===================================================================

   app.css owns the Console design system: tokens, the shell, and every shared
   component (sections, cards, tables, the rail, controls, status, steppers,
   banners, overlays, the log). This file is for rules that belong to one
   screen and nowhere else — a grid only the app Overview uses, a column width
   only the deployments table needs.

   Owned by the screens implementer for:
     projects.js  projectdetail.js  apps.js  appdetail.js  appsettings.js
     deployment.js  stack.js

   (settings.js — Activity and the platform forms — is screens-data.css.)

   Rules:
     - Every colour comes from a token in app.css. A literal hex here is a bug
       in one of the two themes.
     - Prefix a class with the screen it belongs to (.app-overview-grid,
       .deploy-row-tag) so nothing here collides with the kit.
     - If two screens need the same shape, it is not a screen rule: add it to
       view.js and app.css instead.
     - Wide content scrolls in its own container. The body never scrolls
       sideways at 390px.
*/

/* ---- the Projects list ---------------------------------------------------
   The home screen has one job: say what is running and what needs attention
   before anything is read. The chip in the Health column says it in words and
   the kit's tone stripe (tr.tone-warn / tr.tone-bad) says it in shape; this is
   the one thing left that is only this screen's. */

/* A project's description is a sentence somebody wrote, so it is set in the
   sans and allowed to wrap — .cell-sub is the machine line, and it would put
   this in the mono of a repository path and refuse to break it.

   .cell-note is the same shape for any other second line in a cell that is
   prose rather than a machine value: "push by github:e2e-1" under a
   deployment's app. The two are one rule because they are one reading; the
   names are two because the owners are. */
.projects-about,
.cell-note {
  margin-top: 3px;
  max-width: 52ch;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-2);
}

/* ---- the project page ----------------------------------------------------
   Apps and Databases are two sections of one wall, each with a search box in
   its head. Side by side, half the page each, a 320px box is wider than its
   own column: the label, the box, the count and the button come to more than
   the half holds and the strip wraps onto a second line. So while the halves
   are side by side the box gives way — and only then, because once they stack
   a growing search box would run the width of the page. */

@media (min-width: 1360px) {
  .split .search { flex: 1 1 150px; width: auto; }
}

/* ---- the app's Settings tab ----------------------------------------------
   A section has no box around it, so a lone control in one runs the width of
   the page unless it is told not to. A replica count is three characters and
   a domain is forty; neither is 1180px of anything. */

.app-narrow-field { max-width: 320px; }
.app-narrow-row { max-width: 560px; }

/* The Domains tab's Certificate cell carries cert-manager's own words when a
   DNS issuance is pending or failed, and the "register the zone" hint under a
   custom domain. Both wrap inside the cell: a status dot is one line
   everywhere else, but here the sentence is the information, and cut off at
   the card's edge it said nothing. */
.app-domain-cert { min-width: 16em; }
.app-domain-cert .status-dot {
  white-space: normal;
  align-items: flex-start;
  overflow-wrap: anywhere;
}
.app-domain-cert .status-dot .dot { margin-top: 0.45em; flex: none; }

@media (max-width: 639px) {
  /* A phone keeps key, value and menu: the type is on the badge in the
     editor, and a value needs every pixel it can get. */
  .env-table th:nth-child(3), .env-table td.env-type { display: none; }
  .env-table th:nth-child(1) { width: 36%; }
  .env-changes { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 639px) {
  /* On a phone the description would be squeezed into a 40px column and turn
     every row into a paragraph. It keeps a readable measure and the table
     scrolls inside its own box instead, which is the rule everywhere else. */
  .projects-about, .cell-note { max-width: none; min-width: 26ch; }
}

/* ---- projects / project map / add app (worker A) ------------------------- */

/* The Projects list is a two-column screen: the table selects, the rail on the
   right says what the selected project holds. Everything here is either the
   rail's own furniture or the map's, and nothing else uses it. */

.projects-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 15px;
  font-weight: 650;
}

.projects-deploy { display: inline-flex; align-items: center; gap: var(--sp-2); }

/* The columns are set here rather than left to the browser. With auto layout
   the widths are a reading of the rows, so the last non-matching character
   typed into the search — the one that leaves the empty row alone in the
   table — moves every head sideways. Fixed, they stay where the eye left
   them, whatever the filter has left in the tbody. */
.projects-table table { table-layout: fixed; }
.projects-table th:nth-child(1) { width: 46%; }
.projects-table th:nth-child(2) { width: 9%; }
.projects-table th:nth-child(3) { width: 12%; }
.projects-table th:nth-child(4) { width: 16%; }
.projects-table th:nth-child(5) { width: 17%; }

/* The typed-name box of the project delete repeats a DNS label of at most
   thirty characters; a field the width of the card would say the answer might
   be longer than that. */
.confirm-field { max-width: 320px; }

/* The project header's sub line is two readings, not one run-on sentence: the
   counts of what the project holds, and under them the sentence somebody
   wrote about it. .page-sub is a flex row, so this is one child of it. */
.project-sub { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* The first-project hero centres its one control under the form. */
.projects-hero-actions { justify-content: center; margin-top: var(--sp-2); }

/* The three-step strip under the hero is a block with a name, so the rule and
   the space above it belong to the block rather than to the row of steps. */
.getting-started {
  width: 100%;
  margin-top: 56px;
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  text-align: left;
}
.getting-started h3 { font-size: 15px; font-weight: 650; }
.getting-started > .hero-steps { margin-top: var(--sp-4); padding-top: 0; border-top: none; }

.projects-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.projects-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: var(--radius-tag);
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 12.5px;
}

.projects-rail-block { display: flex; flex-direction: column; gap: var(--sp-2); }
.projects-rail-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
}
.projects-rail-list { display: flex; flex-direction: column; }
.projects-rail-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 7px 0;
  min-width: 0;
  font-size: 13.5px;
}
.projects-rail-row + .projects-rail-row { border-top: 1px solid var(--line); }
.projects-rail-mark { display: inline-flex; color: var(--ink-3); flex: none; }
.projects-rail-name { font-weight: 500; }
.projects-rail-sub {
  margin-left: auto;
  font: 400 12px/1.5 var(--mono);
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 55%;
}

/* ---- the project map -----------------------------------------------------
   A layered graph, not a stack of flex rows. projectmap.js computes every
   position from fixed sizes and hands the cards absolute coordinates, so the
   only thing this sheet decides is what the pieces look like. The edges are
   one <svg> of the canvas's own size under the cards; the canvas scrolls
   sideways inside the card and is scaled down by "Fit". */

.map { display: flex; flex-direction: column; min-width: 0; }
.map-viewport {
  position: relative;
  display: flex;
  align-items: center;
  overflow: auto;
  overscroll-behavior-x: contain;
  cursor: grab;
  min-height: 240px;
  max-height: 620px;
}
.map-viewport.grabbing { cursor: grabbing; }
/* A project with one app is a card, not a blank rectangle with a card in the
   corner of it: while the canvas is narrower than the card it is centred in
   it, and the auto margins collapse to nothing the moment it is wider, so a
   six-app row still scrolls from its own left edge. */
.map-stage { position: relative; margin: auto; flex: none; }
.map-canvas {
  position: relative;
  transform-origin: 0 0;
}

/* One edge is one <path> with an arrowhead and a chip on its horizontal
   segment. The overlay never swallows a click meant for a card; only the
   chips take the pointer, for their tooltip. */
.map-wires { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.map-wire { fill: none; stroke: var(--line-2); stroke-width: 1.5; }
.map-arrowhead { fill: var(--line-2); stroke: none; }
.map-arrowhead.lit { fill: var(--accent); }
.map-label-box { fill: var(--surface-2); stroke: var(--line); pointer-events: auto; }
.map-label {
  fill: var(--ink-2);
  font: 400 11.5px var(--sans);
  pointer-events: auto;
}
/* De-emphasis is a step, not an erasure. The wire drops to just over half —
   still a line, in both themes — and the protocol chip does not drop at all:
   the labels are the whole point of the map, and hovering a card must not
   cost the reader the three words it is drawn to explain. Hover and selection
   dim by the same amount, because they are the same reading. */
.map-edge, .map-wire { transition: opacity var(--dur) var(--ease); }
.map-wires.dim .map-wire { opacity: 0.55; }
.map-wires.dim .map-edge.lit .map-wire { opacity: 1; }
.map-edge.lit .map-wire { stroke: var(--accent); stroke-width: 2; }
.map-edge.lit .map-label-box { fill: var(--accent-soft); stroke: var(--accent-line); }
.map-edge.lit .map-label { fill: var(--accent); }

.map-entry {
  position: absolute;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-tag);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 12.5px;
}

.map-node {
  position: absolute;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 13px var(--sp-4) 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  cursor: pointer;
  overflow: hidden;
  text-align: left;
  transition: border-color var(--dur) var(--ease);
}
.map-node:hover { border-color: var(--line-2); }
.map-node.on {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.map-node-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.map-node-head .icon { width: 22px; height: 22px; color: var(--ink-3); flex: none; }
.map-node-name {
  border: none;
  background: none;
  padding: 0;
  font: 650 15px/1.3 var(--sans);
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}
.map-node-name:hover { color: var(--accent); }
/* The line under a node's name is a repository or an image on an app card —
   the machine voice — and the engine or the mode on a database or a Redis
   card, which are words a person reads. Sans by default, mono where the value
   earns it. */
.map-node-sub {
  font: 400 12px/1.4 var(--sans);
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.map-node-sub.mono { font-family: var(--mono); }
.map-node-foot {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 12.5px;
  color: var(--ink-2);
  min-width: 0;
}
.map-node-foot a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.map-bar {
  flex: 1 1 auto;
  height: 6px;
  min-width: 40px;
  border-radius: 3px;
  background: var(--surface-3);
  overflow: hidden;
}
.map-bar-fill { display: block; height: 100%; background: var(--accent); }
.map-bar-fill.warn { background: var(--warn); }
.map-bar-fill.bad { background: var(--bad); }

.map-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 10px var(--sp-4);
  border-top: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--ink-2);
}
.map-zoom { display: inline-flex; align-items: center; gap: var(--sp-2); }
.map-zoom-level {
  min-width: 42px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}

@media (prefers-reduced-motion: reduce) {
  .map-node, .map-edge { transition: none; }
}


/* ---- the add-app wizard --------------------------------------------------
   One page, four blocks down the left and the summary rail on the right. The
   blocks are the kit's numbered step sections with a rule between them. */

.appnew-block + .appnew-block {
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
}
.appnew-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
/* The create form's own actions live at the foot of the summary rail, which
   sticks: Create app is on the screen from the first field on, not below a
   fold four blocks down. The rule above them separates them from the facts. */
.appnew-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}
.appnew-sub { font-size: 13px; color: var(--ink-2); margin-top: -6px; }
/* A segmented control in a form grid is one cell wide, so its buttons share
   that width rather than sizing to their labels: "2" is not a third of the
   control just because it is one character. */
.appnew-block .seg { width: 100%; }
.appnew-block .seg-btn { flex: 1 1 0; }

/* The GitHub App's repository picker (repopicker.js, ADR 0047): a search
   field whose list opens in the flow under it, not over the form, so the
   card's own clipping never cuts it; the branch select beside it. */
.repo-pick { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.repo-pick-field { position: relative; display: flex; align-items: center; }
.repo-pick-field .icon { position: absolute; left: 11px; color: var(--ink-3); pointer-events: none; }
.repo-pick-input { width: 100%; padding-left: 34px; text-overflow: ellipsis; }
.repo-pick-list {
  max-height: 264px;
  overflow-y: auto;
  padding: var(--sp-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}
.repo-pick-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  padding: 7px var(--sp-2);
  border-radius: var(--radius-sm);
  color: var(--ink);
  cursor: pointer;
}
.repo-pick-item.on { background: var(--accent-soft); }
.repo-pick-name { font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.repo-pick-account {
  margin-left: auto;
  padding-left: var(--sp-2);
  color: var(--ink-3);
  font-size: 12.5px;
  white-space: nowrap;
}
.repo-pick-other { color: var(--accent); font-size: 13px; font-weight: 500; }
.repo-pick-other .icon { color: var(--accent); }
.repo-pick-list .repo-pick-other:not(:first-child) {
  margin-top: var(--sp-1);
  border-top: 1px solid var(--line);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}
.repo-pick-note { padding: 8px var(--sp-2); color: var(--ink-2); font-size: 13px; }
.repo-pick-note.bad { color: var(--bad); }
/* The link-like switch between the list and the free text. */
.appnew-switch {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: 500 12.5px/1.4 var(--sans);
  cursor: pointer;
}
.appnew-switch:hover { text-decoration: underline; }
/* "Use a token instead": the exception, folded under its own line. */
.appnew-fold > summary {
  width: fit-content;
  color: var(--ink-2);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
}
.appnew-fold > summary:hover { color: var(--ink); }
.appnew-fold[open] > summary { margin-bottom: var(--sp-3); }

/* Settings › GitHub (githubapp.js, ADR 0047): the app's own line over its
   facts, and the owner choice of the create form. */
.gh-app-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.gh-app-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink);
}
.gh-app-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.gh-app-name strong { font-size: 15px; }
.gh-owner { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--sp-3); }

@media (max-width: 639px) {
  .projects-rail-sub { display: none; }
}

/* ---- import from repository (stack.js, ADR 0025) --------------------------
   The plan is a list of sentences, each led by the badge that says what the
   step does, grouped under the kind it touches. The results reuse the rows
   with the step's state in the badge instead. */

.project-sync:empty { display: none; }
.projects-import { margin-top: var(--sp-3); text-align: center; }

.stack-group + .stack-group {
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}
.stack-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
}
.stack-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.stack-step {
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr);
  align-items: baseline;
  gap: var(--sp-3);
}
.stack-step .pill { justify-self: start; }
.stack-step-text { overflow-wrap: anywhere; }
.stack-step-error { display: block; margin-top: var(--sp-1); font-size: 13px; }
.stack-list { margin: 0; padding-left: var(--sp-4); }
.stack-list li + li { margin-top: var(--sp-1); }
.stack-hook + .stack-hook {
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}
.stack-hook { display: flex; flex-direction: column; gap: var(--sp-2); }

/* The file as it was read: a disclosure the size of a card, folded by
   default, opening onto the log's paper. */
.stack-file > summary {
  padding: var(--sp-3) var(--sp-4);
  cursor: pointer;
  color: var(--ink-2);
  font-size: 13.5px;
}
.stack-file > summary:hover { color: var(--ink); }
.stack-file[open] > summary { border-bottom: 1px solid var(--line); }
.stack-file-code { border-radius: 0 0 var(--radius-card) var(--radius-card); }

/* ---- templates (templates.js, ADR 0027) ------------------------------------
   The gallery is a grid of paper cards, each one a link to its install
   screen. A card that cannot be installed here is the same card, not a link,
   greyed, with what it needs under it. The install screen's review is
   stack.js's, so the only shapes of its own are the notes and the line that
   names the secrets it will generate. */

.tpl-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.tpl-chip {
  padding: 4px 12px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.tpl-chip:hover { color: var(--ink); border-color: var(--accent-line); }
.tpl-chip.on { background: var(--accent-soft); border-color: var(--accent); color: var(--ink); font-weight: 600; }

.tpl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--sp-4);
}
.tpl-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  color: var(--ink);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
a.tpl-card:hover { border-color: var(--accent-line); background: var(--surface-2); }
.tpl-card.disabled { background: var(--surface-2); box-shadow: none; }
.tpl-card.disabled .tpl-icon { opacity: 0.6; }
.tpl-card > .pill { align-self: flex-start; }
.tpl-card-head { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.tpl-icon {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: var(--radius);
  object-fit: contain;
}
.tpl-monogram {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
  font-size: 18px;
}
.tpl-card-title { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tpl-title { font-weight: 600; font-size: 15px; }
.tpl-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 2px var(--sp-2); color: var(--ink-3); font-size: 12.5px; }
.tpl-meta .mono { white-space: nowrap; }
.tpl-desc { margin: 0; color: var(--ink-2); font-size: 13.5px; line-height: 1.5; }
.tpl-needs .note { margin: 0; }
.tpl-needs .note + .note { margin-top: var(--sp-1); }
/* On a card the blocker is the fine print: the badge is the verdict and the
   description stays what the card is about. */
.tpl-card .tpl-needs .note { color: var(--ink-3); font-size: 12.5px; line-height: 1.5; }

/* The install fields sit in a fieldset only so a template this platform
   cannot install can switch them all off at once; it draws nothing itself. */
.tpl-fields { margin-left: 0; margin-right: 0; padding: 0; border: 0; min-width: 0; }
.tpl-fields:disabled :is(input, select) { background: var(--surface-2); color: var(--ink-3); cursor: not-allowed; }
.tpl-fields:disabled :is(input, select):hover { border-color: var(--line-2); }
.tpl-generated { margin: 0; line-height: 1.6; }
/* The notes are the template's own text: its line breaks are its layout. */
.tpl-notes { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.55; }
.tpl-source { width: 100%; min-height: 240px; resize: vertical; }

/* ---- app / deployment / app settings / deployments list (worker B) --------
   Rules that belong to one of these screens and nowhere else. Everything with
   a colour in it takes a token from app.css. */

/* -- the Environment tab --------------------------------------------------
   The table is the document and the rail is the diff of what you have typed
   on top of it, so a pending row has to read as pending from across the
   page: a tinted left edge on the row, the old value struck through when it
   is on its way out. */

.env-filter { width: 260px; }

/* The table is laid out, not measured: fixed columns, so a long value wraps
   inside its own cell instead of widening the table, and the key and the row
   menu never scroll out of view. The key column is sized for the keys people
   write (DATABASE_URL, OIDC_CLIENT_SECRET) and wraps past that. */
.env-table table { table-layout: fixed; }
.env-table th:nth-child(1) { width: 28%; }
.env-table th:nth-child(3) { width: 96px; }
.env-table th:nth-child(4) { width: 56px; }
.env-table td { vertical-align: top; }
/* Every cell of a row centres on the first line of text: the badge and the
   32px menu button are taller than a line, so they give the difference back
   rather than pushing their own middles below the key's. */
.env-table td { line-height: 20px; }
.env-table td.env-type .badge { vertical-align: top; line-height: 16px; }
.env-table td.env-actions .icon-btn { margin: -6px 0; vertical-align: top; }
.env-key { font-weight: 500; overflow-wrap: anywhere; }
.env-value { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }
.env-text {
  flex: 1 1 auto;
  min-width: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  line-height: 1.5;
}
/* Folded: the first lines of a long value, the rest behind "Show all". */
.env-text.env-folded {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}
.env-fold {
  flex-basis: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: 500 12.5px/1.4 var(--sans);
  text-align: left;
  cursor: pointer;
}
.env-fold:hover { text-decoration: underline; }
.env-masked { white-space: nowrap; }
/* A reference to another app (ADR 0033): the token set apart as one name,
   and what it resolves to now on its own line under the value. */
.env-ref {
  padding: 0 3px;
  border-radius: 4px;
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--line);
}
.env-resolved {
  flex-basis: 100%;
  color: var(--ink-2);
  font-size: 12px;
  line-height: 1.4;
  overflow-wrap: anywhere;
}
/* The editor's value box with its Reference… picker beside it. */
.env-value-box { display: flex; gap: var(--sp-2); align-items: center; }
.env-value-box > input { flex: 1 1 auto; min-width: 0; }
.env-ref-host { flex: none; }
.ref-picker { min-width: 320px; max-height: 340px; overflow-y: auto; }
.ref-item { justify-content: space-between; gap: var(--sp-3); }
.ref-item-value { color: var(--ink-2); font-size: 12px; overflow-wrap: anywhere; text-align: right; }
.ref-picker-note { margin: 0; padding: 8px 10px; font-size: 13px; }
/* The whole row opens the editor. */
tbody tr.env-row { cursor: pointer; }
tbody tr.env-row:hover > td { background: var(--surface-2); }
tbody tr.env-deleted { cursor: default; }
/* The row menu sits at the right edge of every row, top-aligned with the
   key, so it is where the eye expects it however tall the value is. */
.env-actions { vertical-align: top; }
.env-menu { display: inline-flex; }
.env-review-sub { font-size: 12.5px; color: var(--ink-2); }
.env-lock { display: inline-flex; align-self: center; color: var(--ink-3); flex: none; }
.env-lock .icon { width: 13px; height: 13px; }
.env-masked { letter-spacing: 0.06em; color: var(--ink-2); }

tbody tr.env-pending > td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
tbody tr.env-pending > td { background: var(--accent-soft); }
tbody tr.env-deleted > td:first-child { box-shadow: inset 2px 0 0 var(--bad); }
tbody tr.env-deleted > td { background: var(--bad-soft); }
tbody tr.env-deleted .env-key,
tbody tr.env-deleted .env-value { text-decoration: line-through; opacity: 0.75; }

/* The review's diff: the two values side by side with an arrow between them,
   the pending one carrying the accent edge. Above the table, the full width,
   the changes sit side by side when there is room for it. */
.env-changes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
  gap: var(--sp-3);
}
.env-change {
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: var(--sp-3);
  background: var(--surface);
}
.env-change-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.env-change-key { font-size: 13.5px; font-weight: 600; }
.env-change-diff {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: end;
  gap: var(--sp-2);
}
.env-side { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.env-side-label {
  font-size: 11.5px;
  color: var(--ink-2);
  background: var(--surface-2);
  border-radius: var(--radius-tag);
  padding: 1px 8px;
  align-self: flex-start;
}
.env-side.pending .env-side-label { background: var(--accent-soft); color: var(--accent); }
.env-side-value {
  border-radius: var(--radius);
  padding: 7px 10px;
  font-size: 12.5px;
  background: var(--surface-2);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 9.5em;
  overflow-y: auto;
}
.env-side.pending .env-side-value {
  background: var(--staged-soft);
  box-shadow: inset 2px 0 0 var(--staged);
}
/* "not set", "(empty)", "removed" are the console talking, not a value. */
.env-side-value .muted { font-family: var(--sans); font-style: italic; }
.env-arrow { color: var(--ink-3); padding-bottom: 9px; }
.env-affected { margin-top: var(--sp-4); }
.env-affected-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
  font-size: 13px;
  color: var(--ink-2);
}
.env-affected-row .icon { color: var(--ink-3); }
.env-import { width: 100%; min-height: 220px; line-height: 1.6; }

/* -- the deployment page -------------------------------------------------- */

/* The rail carries more than one block on these two screens — Error, Release
   and Previous release; the selected domain's DNS record — so each after the
   first is set off by a rule rather than by space alone. */
.deploy-rail, .set-rail {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
}
.deploy-rail > * + * { margin-top: 0; }
.deploy-rail > .rail-head + .rail-body { margin-top: 0; }
.deploy-rail > .rail-head:not(:first-child),
.set-rail > .rail-head:not(:first-child) {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}

/* The log card carries its tabs in its head instead of a title, so the tab
   row loses the page-level rule under it. The tabs and the stream's state are
   the title, left; the search and the buttons fill the rest of the row, right,
   and drop under the tabs as one row when the panel is narrow. */
.deploy-logs .tabs { border-bottom: 0; margin: 0; gap: var(--sp-4); }
.deploy-logs .tabs a, .deploy-logs .tabs button { padding: 6px 0; font-size: 13.5px; }
.deploy-logs .card-head { flex-wrap: wrap; }
.deploy-logs-title { display: flex; align-items: center; gap: var(--sp-4); flex: none; }
.deploy-logs .card-action { flex: 1 1 320px; justify-content: flex-end; min-width: 0; }
.deploy-logs .card-action > .toolbar { flex: 1 1 auto; justify-content: flex-end; flex-wrap: nowrap; min-width: 0; }
.deploy-logs .search { flex: 0 1 240px; min-width: 120px; }
.deploy-logs .log-tools { display: inline-flex; gap: var(--sp-2); flex: none; }

/* A log's title with the stream's state beside it, as a dot and a word. */
.log-title { display: inline-flex; align-items: center; gap: var(--sp-3); }
.log-state { display: inline-flex; font-size: 12.5px; font-weight: 500; }
.log-state:empty { display: none; }
.svc-log .card-head > .eyebrow { flex: none; }
.svc-log .card-action { flex: 1 1 360px; justify-content: flex-end; min-width: 0; }
.svc-log .card-action > .toolbar { flex: 1 1 auto; justify-content: flex-end; min-width: 0; }
@container svc (min-width: 760px) {
  .svc-log .card-action > .toolbar:not(.console-form) { flex-wrap: nowrap; }
}
.svc-log .search { flex: 1 1 200px; max-width: 320px; min-width: 140px; }
.svc-log .card-action select { flex: none; width: auto; }

/* The header Deploy's popover, when changes are staged for the app: those
   first, with the apply that ships them, then the plain version form. */
.deploy-pop { width: 320px; max-width: 100%; }
.deploy-pop-staged { padding: var(--sp-4); border-bottom: 1px solid var(--line); }
.deploy-pop-staged .btn { width: 100%; }
.deploy-pop-title { display: flex; align-items: center; gap: var(--sp-2); font-weight: 600; font-size: 13.5px; }
.deploy-pop-list { margin: 0; padding-left: 1.2em; font-size: 13px; color: var(--ink-2); }
.deploy-pop-list li + li { margin-top: 2px; }
.deploy-pop-staged .note { margin: 0; }
.deploy-pop-or { font-size: 12.5px; padding: var(--sp-3) var(--sp-4) 0; }
.deploy-pop-or + .pop-form { padding-top: var(--sp-2); }

/* Settings › About: the summary and the Runtime card under it share one
   label column, so their values start on the same line. */
.about-cards .facts { grid-template-columns: 136px minmax(0, 1fr); }
.about-cards .card-action .muted { font-size: 13px; }
/* A value made of several parts — a version, its status, its age — spaced
   evenly rather than by the width of a "·". */
.fact-inline { display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2); }

/* The five steps of a rollout, under the Run section of Settings. */
.set-behavior { list-style: none; margin: 0; padding: 0; }
.set-behavior li {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: var(--sp-3);
  padding-bottom: var(--sp-4);
  position: relative;
}
.set-behavior li:last-child { padding-bottom: 0; }
.set-behavior li:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 26px;
  bottom: 6px;
  width: 1px;
  background: var(--line);
}
.set-behavior-text { display: flex; flex-direction: column; gap: 2px; }
.set-behavior-title { font-size: 13.5px; font-weight: 600; }
.set-behavior-sub { font-size: 12.5px; color: var(--ink-2); line-height: 1.5; }

/* -- the deployments list -------------------------------------------------
   Eight columns is a wide table. It scrolls inside .table-wrap like every
   other, but the two states that are read first must not wrap mid-word. */

.deploy-list th, .deploy-list td:first-child { white-space: nowrap; }

/* -- staged values (ADR 0030 §4) -------------------------------------------
   A value that is staged and not deployed reads in amber where it sits: a tag
   with the dot and its undo beside the key, a tinted row, a marked section. */

.staged-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: var(--sp-2);
  padding: 1px 4px 1px 8px;
  border-radius: var(--radius-tag);
  background: var(--staged-soft);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 500;
  white-space: nowrap;
  vertical-align: 1px;
}
.staged-tag .staged-dot { width: 6px; height: 6px; box-shadow: none; }
.staged-tag:not(:has(.staged-undo)) { padding-right: 8px; }
.staged-undo.icon-btn { width: 22px; height: 22px; color: var(--ink-2); }
.staged-undo.icon-btn .icon { width: 13px; height: 13px; }
.staged-undo.icon-btn:hover { color: var(--ink); background: var(--surface); }
tbody tr.env-pending > td, tbody tr.staged-row > td { background: var(--staged-soft); }
tbody tr.env-pending > td:first-child, tbody tr.staged-row > td:first-child { box-shadow: inset 2px 0 0 var(--staged); }
.staged-slot:empty { display: none; }
.staged-slot { display: inline-flex; align-items: center; }
.env-side.pending .env-side-label { background: var(--staged-soft); color: var(--ink); }

.staged-strip {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
  border: 1px solid var(--staged);
  border-radius: var(--radius-card);
  background: var(--staged-soft);
  font-size: 13.5px;
}
.set-staged:empty { display: none; }
.set-staged { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-5); }

/* -- the Deployments tab: one card per run --------------------------------- */

.dep-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.dep-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  transition: border-color var(--dur) var(--ease);
}
.dep-card:hover { border-color: var(--line-2); }
.dep-card.active { border-color: var(--ok); box-shadow: inset 3px 0 0 var(--ok), var(--shadow-card); }
.dep-card.failed { box-shadow: inset 3px 0 0 var(--bad), var(--shadow-card); }
.dep-card.running { border-color: var(--accent-line); }
/* Put away (docs/adr/0042-dismissing-history.md): muted and untinted — a
   failure somebody has dealt with is not an alarm any more — and whole again
   under the pointer or the keyboard, where its menu is. */
.dep-card.dismissed { opacity: .6; box-shadow: var(--shadow-card); }
.dep-card.dismissed:hover, .dep-card.dismissed:focus-within { opacity: 1; }
.dep-list-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); min-height: 32px; }
.dep-list-head .muted { font-size: 12.5px; }
/* The toggle over what was put away: a line of small text, not a button. */
.dep-dismissed-toggle, .insight-dismissed button {
  align-self: flex-start;
  padding: var(--sp-1) 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  font: 500 12.5px/1.4 var(--sans);
  cursor: pointer;
}
.dep-dismissed-toggle:hover, .insight-dismissed button:hover { color: var(--ink); text-decoration: underline; }
.dep-card-row { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4); }
.dep-card-open {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.dep-card-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--radius-sm); }
.dep-card-title { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }
.dep-version { font-size: 14px; font-weight: 600; color: var(--ink); }
.dep-card-meta { display: flex; flex-wrap: wrap; gap: 2px var(--sp-3); font-size: 12.5px; color: var(--ink-2); }
.dep-card-meta > span + span::before { content: '·'; margin-right: var(--sp-3); color: var(--ink-3); }
.dep-card-actions { flex: none; display: flex; align-items: center; gap: var(--sp-2); }
.dep-card-error {
  margin: 0 var(--sp-4) var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius);
  background: var(--bad-soft);
  color: var(--ink);
  font-size: 12.5px;
  overflow-wrap: anywhere;
}
.dep-card-steps { padding: var(--sp-3) var(--sp-4) var(--sp-4); border-top: 1px solid var(--line); }
.dep-card-steps .progress-note { margin: var(--sp-2) 0 0; font-size: 12.5px; }

/* One deployment opened inside the tab: the way back, its title, and the
   run, logs and facts stacked. */
.dep-detail { display: flex; flex-direction: column; gap: var(--sp-3); }
.dep-back { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--ink-2); align-self: flex-start; }
.dep-back:hover { color: var(--ink); text-decoration: none; }
.dep-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-2); }
.dep-title { margin: 0; font-size: 18px; font-weight: 650; letter-spacing: -0.01em; display: inline-flex; align-items: center; gap: var(--sp-2); }
.dep-head-meta { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4); font-size: 12.5px; }
.dep-head-meta > span { display: inline-flex; align-items: center; gap: 4px; }
.dep-head-meta .icon { width: 13px; height: 13px; }
.dep-facts > .rail { position: static; }
.svc-pane .deploy-logs .logpanel, .svc-pane .deploy-logs pre.log { max-height: 420px; }
.svc-pane .tiles .tile-value { font-size: 20px; }
/* The Logs tab is the log: it takes the height the panel has. */
.pane-logs .svc-log pre.log { height: calc(100dvh - 330px); min-height: 280px; max-height: none; }

/* -- the Settings tab: sections with a sticky index ------------------------ */

.set-layout {
  display: grid;
  grid-template-columns: 148px minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: start;
}
.set-index {
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 2px;
}
.set-index-item {
  display: block;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--ink-2);
  border-left: 2px solid transparent;
}
.set-index-item:hover { color: var(--ink); background: var(--surface-2); text-decoration: none; }
.set-index-item.on { color: var(--accent); background: var(--accent-soft); }
.set-main { min-width: 0; }
.set-sections { display: flex; flex-direction: column; gap: var(--sp-6); min-width: 0; }
.set-section { scroll-margin-top: var(--sp-4); min-width: 0; }
.set-section-title {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-3);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.set-section-title .staged-host:empty { display: none; }
.set-section-title .staged-tag { margin-left: 0; }
.set-section > .card-body { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); }
.set-section.staged > .card-body,
.set-section.staged > .cards > .card-body:first-child { border-color: var(--staged); box-shadow: inset 3px 0 0 var(--staged); }
.set-section > .cards > .card-body:first-child { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); }
.set-domains, .set-metrics { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
.set-domains > .rail, .set-metrics > .rail { position: static; }
.metrics-actions { align-items: center; }

@container svc (max-width: 720px) {
  .set-layout { grid-template-columns: minmax(0, 1fr); }
  /* The index wraps rather than scrolling sideways: a strip cut off at the
     edge hid half the sections. */
  .set-index {
    flex-direction: row;
    flex-wrap: wrap;
    z-index: 4;
    margin: calc(var(--sp-5) * -1) calc(var(--sp-5) * -1) 0;
    padding: var(--sp-2) var(--sp-5);
    background: var(--ground);
    border-bottom: 1px solid var(--line);
  }
  .set-index-item { white-space: nowrap; padding: 4px 8px; }
  .set-section { scroll-margin-top: 96px; }
  .dep-card-row { flex-wrap: wrap; }
}
@container svc (min-width: 1100px) {
  .set-domains, .set-metrics { grid-template-columns: minmax(0, 1fr) var(--rail); }
}

@media (max-width: 999px) {
  .env-filter { width: 160px; }
  .env-change-diff { grid-template-columns: minmax(0, 1fr); }
  .env-arrow { display: none; }
}

@media (max-width: 639px) {
  .deploy-logs .search { width: 100%; }
}

/* The Console tab of an app (docs/adr/0016-pod-exec.md). The command line sits
   in the log panel's own head, on the same warm ink as the output under it, so
   the whole thing reads as one window rather than a form over a box. */
.card-head:has(> .card-action > .console-form) > .card-action { flex: 4 1 auto; }
.console-form { flex: 1 1 auto; flex-wrap: nowrap; }
.console-cmd {
  flex: 1 1 260px;
  min-width: 0;
}
@media (max-width: 720px) {
  .console-form { flex-wrap: wrap; }
}

/* -- the Assistant (docs/adr/0024-assistant.md) ----------------------------
   One card is the whole screen: the transcript scrolls in it and the composer
   sits on a rule at its foot, so the thing being read and the thing being
   typed are one object rather than two stacked cards.

   The conversation is prose, not a table, so almost nothing is drawn: the
   person's own message is a tinted block on the right and the assistant's
   answer is text on the page. The only colour is the one blue the kit already
   uses for a selected thing, and a chip's tone, which is the platform's four
   statuses and no fifth. */

.chat { display: flex; flex-direction: column; min-height: 520px; }
.chat-log {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  min-height: 0;
}

.chat-turn { display: flex; flex-direction: column; }
.chat-turn-user { align-items: flex-end; }

/* The person's message. Tinted, not bordered: it is the same page, said by
   somebody else. */
.chat-user {
  max-width: min(46em, 80%);
  padding: 10px 14px;
  border-radius: var(--radius-card);
  background: var(--accent-soft);
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.chat-answer { max-width: 72ch; line-height: 1.6; color: var(--ink); }
.chat-answer p { margin: 0 0 var(--sp-3); }
.chat-answer p:last-child { margin-bottom: 0; }
.chat-answer strong { font-weight: 600; }
.chat-answer code { font-family: var(--mono); font-size: 13px; }
/* While the deltas arrive the answer is plain text in the same place, so the
   line does not re-wrap under the reader when the markdown lands. */
.chat-streaming { white-space: pre-wrap; }
.chat-streaming::after {
  content: "▍";
  color: var(--accent);
  margin-left: 1px;
}

.chat-code {
  margin: 0 0 var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius);
  background: var(--code-bg);
  color: var(--code-ink);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.55;
  overflow-x: auto;
}
.chat-list { margin: 0 0 var(--sp-3); padding-left: 1.3em; }
.chat-list li { margin-bottom: var(--sp-1); }
.chat-answer em { font-style: italic; }
.chat-heading { font-weight: 600; margin-top: var(--sp-4); }
.chat-heading:first-child { margin-top: 0; }

/* A table in an answer: hairline rows like the console's own tables, a quiet
   header, numbers lined up. It is as wide as its content, and the wrapper
   scrolls, so a wide table stays inside the answer's measure instead of
   pushing the page wider. */
.chat-table-wrap {
  width: fit-content;
  max-width: 100%;
  margin: 0 0 var(--sp-3);
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.chat-table { border-collapse: collapse; font-size: 13px; line-height: 1.45; }
.chat-table-cell {
  padding: 7px var(--sp-3);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.chat-table tbody tr:last-child .chat-table-cell { border-bottom: 0; }
.chat-table th.chat-table-cell {
  background: var(--surface-2);
  color: var(--ink-2);
  font-weight: 600;
}
.chat-table td.chat-table-cell { font-variant-numeric: tabular-nums; }
.chat-table-right { text-align: right; }
.chat-table-center { text-align: center; }

/* What the assistant looked at, over the answer it wrote from. */
.chat-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* A writing call waiting on a decision. The card is the kit's; these are the
   rows inside it, each one a proposal with its arguments under it. */
.chat-call {
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.chat-call-head { display: flex; align-items: center; gap: var(--sp-3); }
.chat-call-title { font-size: 14px; font-weight: 600; }

/* The composer sits on a rule at the foot of the card and never scrolls with
   the transcript. */
.chat-composer {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  border-radius: 0 0 var(--radius-card) var(--radius-card);
}
.chat-input {
  width: 100%;
  min-height: 72px;
  resize: vertical;
  line-height: 1.5;
}
.chat-composer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.chat-composer-row .note { margin: 0; }

/* The first-run panel: three questions, each one a button that asks it. */
.chat-first .choices {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 720px;
}

/* Settings › Assistant: where the key test's answer lands. */
.chat-test:empty { display: none; }
/* "Approve all" is about every proposal above it, not about the last one, so
   it sits under a rule of its own. */
.chat-decide-all { padding-top: var(--sp-3); border-top: 1px solid var(--line); }

/* Extra ports (ADR 0032): one name/number pair per line, the name taking the
   room and the number as wide as five digits. */
.ports-row { flex-wrap: nowrap; }
.ports-row input:first-child { flex: 1 1 auto; min-width: 0; }
.ports-row input[type=number] { flex: 0 0 7.5rem; }

/* ---- jobs and workers (docs/adr/0034-jobs-and-workers.md) -----------------
   The kind choice on the create form, the schedule's sentence and presets,
   a command drawn as its words, and the Runs tab. */

.kind-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--sp-3);
}
.kind-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent);
}
.kind-mark .icon { width: 17px; height: 17px; }

.cron-preview { display: flex; flex-direction: column; gap: 2px; }
.cron-words { color: var(--ink); font-weight: 550; }
.cron-next { font-size: 12px; }
.cron-presets { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.argv { display: inline-flex; flex-wrap: wrap; gap: 4px; vertical-align: middle; }
.argv-word {
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: 12px;
  line-height: 20px;
  white-space: pre;
}
.argv-preview { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12.5px; }
.argv-preview:empty { display: none; }

.runs-head { align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.runs-tab .tiles .tile-value {
  font-family: var(--sans);
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.4;
  font-variant-numeric: normal;
}
.run-cmd { max-width: 260px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pane-runs .run-log pre.log { height: calc(100dvh - 560px); min-height: 260px; max-height: none; }

/* -- why the app restarts (docs/adr/0036-restart-insights.md) --------------
   One card per cause above the deployments: a bar down its edge in the
   cause's status colour, the title and its facts, what Kubernetes said in
   mono, what to do, and the output before the restart on demand. */
.insights { display: flex; flex-direction: column; gap: var(--sp-3); }
.insights:empty { display: none; }
/* In the Deployments tab the cards share a plain block with the list. */
.insights:not(:empty) + .dep-list { margin-top: var(--sp-4); }
.insight { padding: var(--sp-4) var(--sp-5); box-shadow: inset 3px 0 0 var(--warn), var(--shadow-card); }
.insight.bad { box-shadow: inset 3px 0 0 var(--bad), var(--shadow-card); }
.insight-head { display: flex; align-items: flex-start; gap: var(--sp-3); }
.insight-head > .icon { flex: none; width: 18px; height: 18px; margin-top: 3px; color: var(--warn); }
.insight.bad .insight-head > .icon { color: var(--bad); }
.insight-title { flex: 1 1 auto; min-width: 0; }
.insight-title h3 { margin: 0; font-size: 15px; font-weight: 650; }
.insight-meta { font-size: 12.5px; margin-top: 2px; }
.insight-body { margin: var(--sp-3) 0 0 calc(18px + var(--sp-3)); display: flex; flex-direction: column; gap: var(--sp-2); font-size: 13.5px; }
.insight-body p { margin: 0; color: var(--ink-2); }
.insight-body .insight-next { color: var(--ink); }
.insight-detail {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-2);
  border-radius: var(--radius);
}
/* A label over the quote, not a second title under the card's own. */
.insight-detail .eyebrow { font-size: 12px; font-weight: 500; color: var(--ink-2); letter-spacing: 0; }
.insight-detail .mono { font-size: 12.5px; overflow-wrap: anywhere; color: var(--ink); }
.insight-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-1); }
.insight-output { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-1); }
.insight-output pre.log { max-height: 320px; border: 1px solid var(--line); border-radius: var(--radius); }
.insight-more { margin: 0; }
/* The × after the state pill; a dismissed card is muted, untinted, and
   whole again under the pointer, where its Restore is. */
.insight-dismiss { width: 28px; height: 28px; margin: -3px calc(-1 * var(--sp-2)) 0 0; color: var(--ink-3); }
.insight-dismiss:hover { color: var(--ink); }
.insight.dismissed { opacity: .6; box-shadow: var(--shadow-card); }
.insight.dismissed:hover, .insight.dismissed:focus-within { opacity: 1; }
.insight.dismissed .insight-head > .icon { color: var(--ink-3); }
.insight-dismissed { margin: 0; }

/* -- an app's own sign-in form (docs/adr/0040-app-sign-in-rules.md) --------
   The statuses a refused sign-in answers with: one row of ticks that wraps,
   the code in mono and its meaning quieter beside it. */
.signin-statuses { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.signin-statuses > .row { flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }
/* Method and Paths top-aligned: the paths field carries a hint under it. */
.signin-form > .row:first-child { align-items: flex-start; }

/* ---- vulnerabilities (docs/adr/0088-vulnerability-scanning.md) -------------- */

/* The Security section and the admin page's tables: findings read top-down,
   an id and its version never break, a dimmed row is an ignored one. */
.vuln-security td,
.vuln-ignores td { vertical-align: top; }
.vuln-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; }
.vuln-id-cell .mono { white-space: nowrap; }
.vuln-pkg { min-width: 160px; }
.vuln-pkg .mono { overflow-wrap: anywhere; }
.vuln-fix { min-width: 160px; color: var(--ink-2); overflow-wrap: anywhere; }
tr.vuln-ignored td { opacity: 0.6; }
tr.vuln-divider td {
  padding-top: var(--sp-4);
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--surface-2);
}
.vuln-images { margin: 0; line-height: 1.7; }
.vuln-error { font-size: 12px; white-space: pre-wrap; word-break: break-word; }
.vuln-foot { margin: 0; padding: var(--sp-3) var(--sp-4); }
.dep-scan .chip { padding: 0 8px; font-size: 12px; }
/* The add-on off: one button under the sentence, its own width. */
.vuln-off .choices { display: flex; justify-content: center; }
/* A data service's image (docs/adr/0091): the card above its Security view,
   the chip beside its version, the badge in the MySQL table. */
.data-image .card { margin-bottom: var(--sp-4); }
.data-image-lead { margin: 0; }
.data-image-lead .chip { margin-right: 4px; }
.data-image-facts { margin: 0; }
.data-image-chip { display: inline-flex; align-items: center; gap: 6px; margin-left: 8px; }
.mysql-vuln { margin-left: 6px; }
