/* Genesys Release UI — the Console design.
   ==================================================================

   One stylesheet, no build step, no network at runtime: both typefaces are
   embedded next door in fonts/.

   The room: a near-white, faintly cool page ground with white cards on it.
   Hairline borders, 10px radii, almost no shadow. Near-black headings, dark
   grey body, mid-grey secondary text, and exactly one accent — a saturated
   blue that does every structural job (primary button, active tab underline,
   link, selected row, focus). Colour that is not blue is a status: green ok,
   amber pending, red failed, grey neutral.

   Type: one sans for everything a person reads, one mono for what the machine
   says — env keys and values, commit SHAs, image references, log lines, DNS
   values, file paths. Never for labels, column heads or status words.

   The two themes are the same design in two lights:

     light  — the design. Cool paper, white cards, ultramarine blue.
     dark   — graphite. The same tokens on a neutral charcoal ground, cards
              lifted one step off it, the blue lifted a step lighter.

   Every colour is a custom property declared on the bare :root. A colour
   written literally in a rule below the token blocks is a bug: it will be
   wrong in one of the two themes.

   Order: fonts, tokens, base, shell, page header, tabs, cards & sections,
   tables, rail, controls, status, stepper, wizard, banners, log, empty,
   overlays, toasts, boot, responsive. */

/* ---- typefaces -----------------------------------------------------------
   Schibsted Grotesk — the sans, used for everything.
   Azeret Mono — the mono, used only for machine values.
   Both are variable (one file each, latin subset), SIL OFL; the notice is
   fonts/LICENSE.txt and it ships with them. 72 KB total. */

@font-face {
  font-family: "Schibsted Grotesk";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("fonts/schibsted-grotesk-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Azeret Mono";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/azeret-mono-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---- tokens: light, which is the design ----------------------------------
   The bare :root carries the complete palette. The two dark blocks below
   restate values only — no component ever gets a colour that lives inside a
   media query or a [data-theme] block. */

:root {
  color-scheme: light dark;

  /* Surfaces. --ground is the page, --surface is a card on it, --surface-2 is
     a quiet fill inside one (hover, a table head, a read-only input),
     --surface-3 the step above that (an avatar, a skeleton bar). */
  --ground: #f5f6f8;
  --surface: #ffffff;
  --surface-2: #f4f6f9;
  --surface-3: #e8ecf2;

  /* Ink: near-black heading, dark grey body, mid grey secondary. */
  --ink: #111827;
  --ink-2: #4b5563;
  --ink-3: #8a93a3;

  /* Hairlines. --line is a card edge or a row rule; --line-2 is the edge of a
     control, which has to read as touchable. */
  --line: #e5e8ee;
  --line-2: #d3d8e2;

  /* The one accent. Primary buttons, active tabs, links, the selected row and
     card, focus. Never a status. */
  --accent: #1a6cf0;
  --accent-hover: #1557c7;
  --accent-ink: #ffffff;
  --accent-soft: #eaf1fe;
  --accent-line: #b9d2fb;

  /* Status pigments, each with the tint used as the ground of its pill or
     banner. Green ok, amber pending, red failed, grey neutral. */
  --ok: #12805c;
  --ok-soft: #dcf2e8;
  --warn: #96620a;
  --warn-soft: #fcefd6;
  --bad: #c4291f;
  --bad-soft: #fdeae7;
  --slate: #5a6472;
  --slate-soft: #eef0f4;

  /* The log. It is a white panel here, not a terminal: grey line numbers, a
     grey timestamp column, the line itself in ink, and a failed line tinted.
     --code-note is the controller's own annotation lines, --code-warn the
     tint of a warning, --code-hover the row under the pointer. */
  --code-bg: #fcfcfd;
  --code-ink: #1f2430;
  --code-ink-2: #8a93a3;
  --code-note: #1a6cf0;
  --code-fail: #fdeae7;
  --code-warn: #fdf4e0;
  --code-hover: #f2f4f7;

  /* Elevation. A card has almost none; only what floats gets a real shadow. */
  --shadow-card: 0 1px 2px rgb(17 24 39 / 4%);
  --shadow-pop: 0 12px 32px rgb(17 24 39 / 12%), 0 2px 6px rgb(17 24 39 / 6%);
  --overlay-shadow: 0 18px 44px rgb(17 24 39 / 18%), 0 2px 8px rgb(17 24 39 / 8%);
  --scrim: rgb(17 24 39 / 40%);

  /* The canvas console (ADR 0030): the dotted ground a project is drawn on,
     its node cards, the edges between them, the panel that slides over it,
     and the amber of a change that is staged but not yet deployed. */
  --canvas-bg: #f3f4f7;
  --canvas-dot: #d3d8e1;
  --node-bg: #ffffff;
  --node-border: #dde1e8;
  --node-shadow: 0 1px 2px rgb(17 24 39 / 6%), 0 4px 14px rgb(17 24 39 / 5%);
  --node-selected: #1a6cf0;
  --staged: #c77c05;
  --staged-soft: #fdf1dc;
  --panel-shadow: -14px 0 40px rgb(17 24 39 / 12%), -1px 0 0 rgb(17 24 39 / 5%);
  --edge: #aeb7c6;
  --edge-active: #1a6cf0;

  /* The categorical palette (the log explorer's sources, docs/design/LOGS.md):
     eight hues in a fixed order, checked for colour-blind separation on the
     log paper of each theme. Identity only — never a status, never text: a
     source's swatch sits beside its name. */
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --series-5: #e87ba4;
  --series-6: #008300;
  --series-7: #4a3aa7;
  --series-8: #e34948;
  /* The explorer's hits strip: lines that are neither errors nor warnings
     (--slate-soft is too close to the paper to read as a mark), and the
     highlighter a searched word is marked with in a log line. */
  --hits-other: #b8c0cd;
  --log-hit: #fde9a6;

  /* Shape. */
  --radius: 8px;
  --radius-card: 10px;
  --radius-tag: 999px;
  --radius-sm: 6px;

  /* Motion. One duration, one curve, and nothing moves that is not a response
     to a click or a stream. */
  --dur: 140ms;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);

  /* Type. */
  --sans: "Schibsted Grotesk", ui-sans-serif, -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: "Azeret Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
    Consolas, "Liberation Mono", monospace;

  /* Rhythm: a 4px grid. 8 / 12 / 16 / 24 do almost all the work. */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  /* The shell. */
  --topbar: 56px;
  --ctxbar: 49px;
  --page-max: 1500px;
  --gutter: 32px;
  --rail: 400px;
}

/* ---- tokens: dark, a graphite variant of the same design -----------------
   Not a second identity: the same roles, on a neutral charcoal ground, with
   the blue lifted one step so it still reads as the accent against it.

   Stated twice on purpose: once for the OS preference (guarded so an explicit
   light choice still wins) and once for an explicit dark choice. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --ground: #14161a;
    --surface: #1b1e24;
    --surface-2: #22262d;
    --surface-3: #2c313a;

    --ink: #e9ebef;
    --ink-2: #a9b1bd;
    --ink-3: #79828f;

    --line: #2b3038;
    --line-2: #3b424d;

    --accent: #5b9dff;
    --accent-hover: #82b5ff;
    --accent-ink: #0a1424;
    --accent-soft: #17253c;
    --accent-line: #2f4a76;

    --ok: #4ec08a;
    --ok-soft: #13291f;
    --warn: #e0ac54;
    --warn-soft: #2b2113;
    --bad: #ff8b7b;
    --bad-soft: #301b18;
    --slate: #9aa3b0;
    --slate-soft: #262b33;

    --code-bg: #16181d;
    --code-ink: #d8dce3;
    --code-ink-2: #79828f;
    --code-note: #5b9dff;
    --code-fail: #301b18;
    --code-warn: #2b2213;
    --code-hover: #1d2026;

    --shadow-card: 0 1px 2px rgb(0 0 0 / 30%);
    --shadow-pop: 0 12px 32px rgb(0 0 0 / 46%), 0 2px 6px rgb(0 0 0 / 34%);
    --overlay-shadow: 0 18px 44px rgb(0 0 0 / 56%), 0 2px 8px rgb(0 0 0 / 40%);
    --scrim: rgb(6 8 12 / 62%);

    --canvas-bg: #111316;
    --canvas-dot: #2a2f37;
    --node-bg: #1b1e24;
    --node-border: #2f353f;
    --node-shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 6px 18px rgb(0 0 0 / 28%);
    --node-selected: #5b9dff;
    --staged: #f0b457;
    --staged-soft: #33260f;
    --panel-shadow: -16px 0 48px rgb(0 0 0 / 52%), -1px 0 0 rgb(0 0 0 / 40%);
    --edge: #4b5360;
    --edge-active: #5b9dff;

    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
    --series-6: #008300;
    --series-7: #9085e9;
    --series-8: #e66767;
    --hits-other: #4f5866;
    --log-hit: #5b4813;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --ground: #14161a;
  --surface: #1b1e24;
  --surface-2: #22262d;
  --surface-3: #2c313a;

  --ink: #e9ebef;
  --ink-2: #a9b1bd;
  --ink-3: #79828f;

  --line: #2b3038;
  --line-2: #3b424d;

  --accent: #5b9dff;
  --accent-hover: #82b5ff;
  --accent-ink: #0a1424;
  --accent-soft: #17253c;
  --accent-line: #2f4a76;

  --ok: #4ec08a;
  --ok-soft: #13291f;
  --warn: #e0ac54;
  --warn-soft: #2b2113;
  --bad: #ff8b7b;
  --bad-soft: #301b18;
  --slate: #9aa3b0;
  --slate-soft: #262b33;

  --code-bg: #16181d;
  --code-ink: #d8dce3;
  --code-ink-2: #79828f;
  --code-note: #5b9dff;
  --code-fail: #301b18;
  --code-warn: #2b2213;
  --code-hover: #1d2026;

  --shadow-card: 0 1px 2px rgb(0 0 0 / 30%);
  --shadow-pop: 0 12px 32px rgb(0 0 0 / 46%), 0 2px 6px rgb(0 0 0 / 34%);
  --overlay-shadow: 0 18px 44px rgb(0 0 0 / 56%), 0 2px 8px rgb(0 0 0 / 40%);
  --scrim: rgb(6 8 12 / 62%);

  --canvas-bg: #111316;
  --canvas-dot: #2a2f37;
  --node-bg: #1b1e24;
  --node-border: #2f353f;
  --node-shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 6px 18px rgb(0 0 0 / 28%);
  --node-selected: #5b9dff;
  --staged: #f0b457;
  --staged-soft: #33260f;
  --panel-shadow: -16px 0 48px rgb(0 0 0 / 52%), -1px 0 0 rgb(0 0 0 / 40%);
  --edge: #4b5360;
  --edge-active: #5b9dff;

  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --series-5: #d55181;
  --series-6: #008300;
  --series-7: #9085e9;
  --series-8: #e66767;
  --hits-other: #4f5866;
  --log-hit: #5b4813;
}

:root[data-theme="light"] { color-scheme: light; }

/* ---- base ---------------------------------------------------------------- */

* { box-sizing: border-box; }

/* The UI toggles visibility with el.hidden; several components set display,
   so the UA rule for [hidden] needs help to win. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 400 14px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
body.locked { overflow: hidden; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

h1, h2, h3 {
  margin: 0;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: -0.014em;
  text-wrap: balance;
}
p { margin: 0; }
/* The browser's own margin on a definition list is dropped by an element
   selector, not by `.facts`: a class here would out-rank `.stack > * + *` and
   a facts grid that is not the first child of a stack would then collide with
   the block above it. */
dl, dd { margin: 0; }

code, pre, .mono { font-family: var(--mono); }
.mono { font-size: 0.94em; }

/* Numbers line up between renders — but only where the text is figures.
   Schibsted Grotesk's tabular set puts the full stop, the comma and the
   semicolon on a figure's width too, so a sentence set in it reads with a gap
   around every mark ("No volumes .  Without one …", "9/10/2026 ,  3:06:29 PM").
   So the rule names the things that hold numbers — the machine voice, a count,
   a duration, a tile's figure — and prose in a table cell, a fact, a pill or an
   empty row is set normally. `.nums` is the opt-in for anything else. */
code, pre, .mono, .nums, .num, .log-num, .log-time, .cell-sub,
.tile-value, .kpi-value, .kpi-delta, .health {
  font-variant-numeric: tabular-nums;
}

.muted { color: var(--ink-2); }
.small { font-size: 13px; }
.nowrap { white-space: nowrap; }
.grow { flex: 1 1 auto; min-width: 0; }
.error-text { color: var(--bad); overflow-wrap: anywhere; }
.stack > * + * { margin-top: var(--sp-3); }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }

/* The eyebrow is the block-title voice: the name of a card, a section or a
   rail, set in the sans at the weight a heading takes. It is not a label in
   small caps — the Console design has no small caps anywhere. */
.eyebrow {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink);
  letter-spacing: -0.006em;
}

.note, .hint {
  font-size: 13px;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}

.icon { flex: none; vertical-align: -0.15em; }

.offscreen { position: fixed; left: -9999px; top: 0; opacity: 0; }

::selection { background: var(--accent-soft); color: var(--ink); }

/* One focus treatment everywhere, in the accent. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---- shell ---------------------------------------------------------------
   A single top bar with the wordmark, the primary nav, a search field and the
   avatar; a context bar under it with the breadcrumb, a centre slot and the
   page's secondary actions. No sidebar. */

.shell { display: flex; flex-direction: column; min-height: 100vh; }

.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(200px, 520px) minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-4);
  height: var(--topbar);
  padding: 0 var(--sp-5);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* The wordmark: the registration mark — the crosshair a press prints to line
   one impression up with the next — and the name beside it. */
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.wordmark:hover { text-decoration: none; }
.wordmark:hover .mark { color: var(--accent-hover); }
.wordmark-text { display: inline-flex; align-items: baseline; gap: 6px; min-width: 0; }
.wordmark-name {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.022em;
  line-height: 1.1;
}
.mark { width: 20px; height: 20px; flex: none; color: var(--accent); display: block; }
.mark.big { width: 40px; height: 40px; margin: 0 auto var(--sp-3); }

/* The primary nav sits in the middle of the bar and is underlined, not
   filled: the tab is a place, and the underline is where you are. */
.topnav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  height: 100%;
  min-width: 0;
}
.topnav-item {
  display: inline-flex;
  align-items: center;
  height: 100%;
  padding: 0 var(--sp-3);
  color: var(--ink-2);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.topnav-item:hover { color: var(--ink); text-decoration: none; }
.topnav-item.on { color: var(--accent); font-weight: 600; border-bottom-color: var(--accent); }

.topbar-right { display: flex; align-items: center; gap: var(--sp-3); justify-self: end; }

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  line-height: 1;
}
.avatar-btn {
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  border-radius: 50%;
}
.avatar-btn:hover .avatar { background: var(--accent-soft); color: var(--accent); }

.icon-btn.nav-toggle { display: none; }

/* The canvas console's bar (ADR 0030 §1): the mark and the project switcher
   on the left, the search/command field in the middle, the menu of screens
   and the avatar on the right. */
.topbar-left { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.topbar-centre { display: flex; justify-content: center; min-width: 0; }
.topbar-slash { color: var(--line-2); font-size: 20px; font-weight: 300; line-height: 1; }
.switcher {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  max-width: 280px;
  height: 34px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  color: var(--ink-2);
  font: 500 14px/1 var(--sans);
  cursor: pointer;
}
.switcher:hover, .switcher[aria-expanded="true"] { background: var(--surface-2); color: var(--ink); }
.switcher.in-project { color: var(--ink); font-weight: 600; }
.switcher .icon { color: var(--ink-3); }
.switcher-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.switcher-panel { width: 320px; max-width: 100%; }
.switcher-head { padding: var(--sp-2); border-bottom: 1px solid var(--line); }
.switcher-filter { width: 100%; }
.switcher-list { max-height: 320px; overflow-y: auto; padding: var(--sp-1); }
.switcher-empty { padding: var(--sp-3); font-size: 13.5px; }
.switcher-item {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: var(--sp-2);
  align-items: center;
  padding: 6px var(--sp-2);
  border-radius: var(--radius-sm);
  color: var(--ink);
  text-decoration: none;
}
.switcher-item:hover, .switcher-item:focus-visible { background: var(--surface-2); text-decoration: none; }
.switcher-item.on { background: var(--accent-soft); }
.switcher-initial {
  grid-row: 1 / 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 12px;
  font-weight: 700;
}
.switcher-item.on .switcher-initial { background: var(--accent); color: var(--accent-ink); }
.switcher-item-name { font-weight: 600; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.switcher-item-sub { font-size: 12px; color: var(--ink-3); }
/* A pull-request preview (ADR 0057) sits under its parent, one step in. */
.switcher-item.switcher-preview { margin-left: 18px; }
.switcher-preview .switcher-initial { background: none; color: var(--ink-3); }

.shell-search { width: 100%; max-width: 520px; }
.shell-search .search-input { padding-right: 64px; }
.kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border: 1px solid var(--line-2);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--surface);
  color: var(--ink-3);
  font: 500 11px/1 var(--sans);
  white-space: nowrap;
}
.shell-kbd { position: absolute; right: 8px; cursor: pointer; }
.shell-kbd:hover { color: var(--ink); border-color: var(--ink-3); }
.menu-item.current { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.menu-item.current .icon { color: var(--accent); }

/* Inside a project the canvas is the page: no context bar, no gutters, no
   page scroll — the canvas pans and the panel scrolls on its own. */
body.canvas-mode { overflow: hidden; }
body.canvas-mode .shell { height: 100vh; height: 100dvh; min-height: 0; }
body.canvas-mode .ctxbar { display: none; }
body.canvas-mode .main { display: flex; flex-direction: column; min-height: 0; }
body.canvas-mode .page {
  position: relative;
  flex: 1 1 auto;
  max-width: none;
  min-height: 0;
  margin: 0;
  padding: 0;
  gap: 0;
  background: var(--canvas-bg);
}

/* The context bar: where you are on the left, an environment/status slot in
   the middle, the page's secondary links on the right. Empty slots collapse,
   so a screen that fills nothing still gets a clean rule under the nav. */
.ctxbar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--ctxbar);
  padding: var(--sp-2) var(--gutter);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.ctx-trail { min-width: 0; }
.ctx-centre { display: flex; align-items: center; justify-content: center; gap: var(--sp-2); }
.ctx-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.ctx-actions a { font-size: 13.5px; font-weight: 500; }

.main { min-width: 0; flex: 1 1 auto; }
.page {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--sp-6) var(--gutter) 72px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

/* The home screen spends the whole width on its cards; the gutters stay. */
body.home-mode .page { max-width: none; }

/* ---- page header ---------------------------------------------------------
   A large title, one grey line under it, an optional machine line under that,
   and the primary actions pinned right. No rule under it: the cards below
   carry their own edges. */

.page-head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.crumbs {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: 13.5px;
  color: var(--ink-2);
  flex-wrap: wrap;
  min-width: 0;
}
.crumb-sep { display: inline-flex; color: var(--ink-3); }
.crumb-sep .icon { width: 13px; height: 13px; }
.crumbs a { color: var(--ink-2); text-decoration: none; }
.crumbs a:hover { color: var(--accent); text-decoration: none; }
.crumb-here { color: var(--ink); font-weight: 600; }

.page-head-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  flex-wrap: wrap;
}
.page-head-main { min-width: 0; flex: 1 1 auto; }
.title-page { font-size: 27px; font-weight: 650; letter-spacing: -0.026em; }
.title-app { font-size: 30px; font-weight: 650; letter-spacing: -0.028em; }
.page-sub {
  margin-top: 5px;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  font-size: 14px;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}
.chip-host, .url-host { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.url-host { overflow-wrap: anywhere; }

/* page-meta is the meta row: icons and facts about this thing that do not
   change while the page is read — a branch, an actor, a time, a duration. */
.page-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  margin-top: var(--sp-3);
  font-size: 13px;
  color: var(--ink-2);
}
.page-meta > * { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.page-meta .icon { width: 14px; height: 14px; color: var(--ink-3); }
.page-meta .mono { font-size: 12.5px; }

.page-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

.ext { display: inline-flex; align-items: center; gap: 4px; }
.ext .icon { width: 13px; height: 13px; }

/* ---- tabs ----------------------------------------------------------------
   Under the page header: 14px labels, a 2px accent underline on the active
   one, a hairline under the whole row. */

.tabs {
  display: flex;
  gap: var(--sp-1);
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}
.tabs a, .tabs button {
  flex: none;
  white-space: nowrap;
  padding: 10px 12px;
  margin-bottom: -1px;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--ink-2);
  font: 500 14px/1.5 var(--sans);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}
.tabs a:hover, .tabs button:hover { color: var(--ink); text-decoration: none; }
.tabs a.on, .tabs button.on {
  color: var(--accent);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* ---- sections and cards --------------------------------------------------
   Most content sits in a card. A .section is the flat block that carries a
   title and a run of cards under it; a .card is the paper. */

.cards { display: flex; flex-direction: column; gap: var(--sp-5); }
.cards > div:empty { display: none; }
.split > div:empty { display: none; }

.section { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.section-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
/* The rule is filler between the title and whatever sits at the right end of
   the head. Its flex basis is 0 because it is filler — a rule that asked for
   width of its own would push the toolbar beside it onto a second line. */
.section-rule {
  flex: 1 1 0;
  height: 1px;
  min-width: 16px;
  background: transparent;
}
.section-body { display: flex; flex-direction: column; gap: var(--sp-3); }
.section.danger .eyebrow { color: var(--bad); }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  min-width: 0;
}
.card-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  min-height: 52px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.card-head .eyebrow { flex: 1 1 auto; }
.card-sub { flex: 1 1 100%; font-size: 13px; color: var(--ink-2); margin-top: -2px; }
.card-action { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.card-body { padding: var(--sp-4); }
/* A card whose whole body is a table meets the table at its own edges. */
.card > .table-wrap { border-radius: 0 0 var(--radius-card) var(--radius-card); }

/* The danger zone is the one red-outlined card. */
.card.danger-zone, .danger-zone {
  border-color: var(--bad);
  background: var(--surface);
}
.card.danger-zone .card-head, .danger-zone .card-head {
  background: var(--bad-soft);
  border-bottom-color: var(--bad-soft);
}
.card.danger-zone .card-head .eyebrow, .danger-zone .card-head .eyebrow { color: var(--bad); }

/* split is two blocks of one page side by side (a project's apps and its
   databases). It is not the detail rail — that is .twocol. */
.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: var(--sp-5);
}
.split > * { min-width: 0; }
.split th, .split td { padding-left: var(--sp-3); padding-right: var(--sp-3); }

/* ---- tiles ---------------------------------------------------------------
   One strip of figures divided by rules, not four floating boxes: they are
   one reading of one thing. */

.tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.tile, .kpi {
  padding: var(--sp-4) var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  border-left: 1px solid var(--line);
}
.tile:first-child, .kpi:first-child { border-left: none; }
.tile .eyebrow, .kpi-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  letter-spacing: 0;
}
.tile-value, .kpi-value {
  font-size: 28px;
  font-weight: 650;
  line-height: 1.15;
  letter-spacing: -0.03em;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile-value .chip, .tile-value .pill, .tile-value .health {
  font-size: 12px;
  vertical-align: 4px;
}
.tile-sub, .kpi-sub { font-size: 12.5px; color: var(--ink-2); }
.tile-sub .chip { font-size: 12px; }

/* A KPI tile pairs the figure with its change, side by side, the way the
   monitoring strip does. */
.kpi-row { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.kpi-delta { font-size: 13px; font-weight: 600; color: var(--ink-2); }
.kpi-delta.ok { color: var(--ok); }
.kpi-delta.bad { color: var(--bad); }

/* ---- facts and key/value rows -------------------------------------------- */

/* facts is the read-only truth about a thing: a grey key, a plain value. */
.facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 10px var(--sp-5);
  font-size: 13.5px;
}
.facts dt { color: var(--ink-2); font-weight: 500; }
.facts dd { overflow-wrap: anywhere; }

/* fact-list is the rail's version: full-width rows separated by hairlines,
   the key left and the value right, which is how every detail rail in the
   design reads. */
.fact-list { display: flex; flex-direction: column; }
.kv-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13.5px;
  min-width: 0;
}
.kv-row:last-child { border-bottom: none; }
.kv-key { color: var(--ink-2); flex: none; }
.kv-val { text-align: right; overflow-wrap: anywhere; min-width: 0; }
.kv-val .mono { font-size: 12.5px; }

.preview { font-size: 13px; color: var(--ink-2); overflow-wrap: anywhere; }

/* ---- tables --------------------------------------------------------------
   Tables live inside cards, rows are separated by hairlines, and the header
   row is a grey label — no small caps anywhere. A table never widens the
   page: it scrolls inside its own box. */

.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}
th, td {
  text-align: left;
  padding: 12px var(--sp-4);
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
th {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-2);
  background: var(--surface);
  white-space: nowrap;
}
/* A bare table (one dropped straight into a section, no card around it) still
   needs an edge to scroll inside. */
.section > .table-wrap {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
tbody tr:last-child td { border-bottom: none; }
tbody tr.clickable { cursor: pointer; }
tbody tr.clickable:hover, tbody tr.clickable:focus-within { background: var(--surface-2); }
/* A selectable row is a tab stop, so the ring is drawn inside it: an offset
   ring on a table row is clipped by the wrap that scrolls it. */
tbody tr.clickable:focus-visible { outline-offset: -2px; }
td.col-actions, th.col-actions { text-align: right; white-space: nowrap; }
td.col-actions > * + * { margin-left: var(--sp-2); }
td.detail { overflow-wrap: anywhere; }

.cell-title { font-weight: 600; white-space: nowrap; }
.cell-title a { color: var(--ink); text-decoration: none; }
.cell-title a:hover { color: var(--accent); text-decoration: none; }
/* The source line is a machine value, so it is mono and never breaks
   mid-word: a narrow viewport scrolls the table instead of shredding
   "acme/api@main" into four lines. */
.cell-sub {
  margin-top: 2px;
  font: 400 12px/1.5 var(--mono);
  color: var(--ink-2);
  white-space: nowrap;
}
.deploy-cell { display: inline-flex; align-items: center; gap: var(--sp-2); color: inherit; }
.deploy-cell:hover { text-decoration: none; }
.icon-link { display: inline-flex; color: var(--ink-3); }
.icon-link:hover { color: var(--accent); }

/* The selected row of a selectable table: an accent bar down its leading edge
   and a soft tint, which is what tells you which object the rail is showing.
   The bar is a border rather than an inset shadow — a collapsed table paints
   an inset shadow on both edges of the cell — and the padding gives the bar's
   width back so a selected row's title sits on the same left edge as the
   others. A collapsed border sits astride the grid line, so only half of it
   (1.5px) is inside the cell. */
tbody tr.selected > td { background: var(--accent-soft); }
tbody tr.selected > td:first-child {
  border-left: 3px solid var(--accent);
  padding-left: calc(var(--sp-4) - 1.5px);
}
tbody tr.selected:hover > td { background: var(--accent-soft); }
.selectable tbody tr { cursor: pointer; }

/* The tone stripe marks a row that is not fine, so the rows that want an
   operator are found before a single pill is read. Only warn and bad mark. */
tr.tone-warn > td:first-child,
tr.tone-bad > td:first-child {
  border-left: 3px solid transparent;
  padding-left: calc(var(--sp-4) - 1.5px);
}
tr.tone-warn > td:first-child { border-left-color: var(--warn); }
tr.tone-bad > td:first-child { border-left-color: var(--bad); }

td.empty, .empty {
  padding: var(--sp-6) var(--sp-4);
  text-align: center;
  color: var(--ink-2);
}
.empty > * + * { margin-top: var(--sp-3); }

/* Loading placeholder: the real header row over grey bars, so a card keeps
   its shape while the fetch is in flight instead of collapsing to nothing. */
.skel {
  display: block;
  height: 10px;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--surface-2), var(--surface-3), var(--surface-2));
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
}
.skel-row td { opacity: 0.7; }
@keyframes shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* ---- the detail rail -----------------------------------------------------
   Main content left, the selected object's card on the right. Selecting a row
   in the left column repaints the rail. */

.twocol {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--rail));
  align-items: start;
  gap: var(--sp-5);
}
.twocol > * { min-width: 0; }
/* The rail is paper, like the mockups draw it: one card on the right holding
   the selected object's facts, its actions and a secondary list. The surface
   is the rail's own, so a screen hands rail() its head and its body and never
   a card of its own — and a block that arrives already in a card (a project's
   Domain and Mail blocks) sheds it below and reads as a section of this one. */
.rail {
  position: sticky;
  top: calc(var(--topbar) + var(--ctxbar) + var(--sp-4));
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
/* A host div nothing has filled yet takes no room, so a block that draws
   nothing leaves no gap and no rule behind it. */
.rail > div:empty { display: none; }
/* The rhythm is margins and not `gap`, because half the rails fill a host div
   rather than handing rail() their blocks, and `gap` only knows about direct
   children. */
.rail > * + * { margin-top: var(--sp-4); }
/* Inside the one card, a hairline says where one reading stops and the next
   starts: under the masthead, and over every block after the first. */
.rail-head + .rail-body,
.rail-body + .rail-head,
.rail-body + .card,
.rail > div + div {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}
.rail-head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  min-width: 0;
}
.rail-head .eyebrow { font-size: 17px; }
.rail-title { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.rail-sub { font-size: 13px; color: var(--ink-2); overflow-wrap: anywhere; }
.rail-body { display: flex; flex-direction: column; gap: var(--sp-4); }
/* A card inside the rail is not a second sheet of paper: it is a titled block
   of the rail's own card, so it drops the surface and the padding and keeps
   only its head. */
.rail .card {
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
}
.rail .card > .card-head {
  min-height: 0;
  padding: 0 0 var(--sp-3);
  background: none;
  border-bottom: none;
}
.rail .card > .card-body { padding: 0; }
.rail .card > .table-wrap { border-radius: 0; }

/* The footer strip: the page's totals, on a rule at the very bottom. */
.footer-strip {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
  font-size: 13px;
  color: var(--ink-2);
}
.footer-strip > * { display: inline-flex; align-items: center; gap: 6px; }
.footer-strip > *:not(:last-child)::after { content: "·"; color: var(--ink-3); margin-left: var(--sp-2); }
.footer-strip .grow::after { content: none; }

/* ---- controls ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: 500 14px/1.4 var(--sans);
  padding: 8px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.btn:hover { background: var(--surface-2); border-color: var(--ink-3); text-decoration: none; }
.btn:disabled { opacity: 0.5; cursor: default; }
.btn:disabled:hover { background: var(--surface); border-color: var(--line-2); }
.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
.btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.small { font-size: 13px; padding: 5px 10px; }
.btn.block { width: 100%; }
.btn.quiet { border-color: transparent; background: none; color: var(--accent); }
.btn.quiet:hover { background: var(--accent-soft); border-color: transparent; }
.btn.danger { color: var(--bad); border-color: var(--line-2); }
.btn.danger:hover { background: var(--bad-soft); border-color: var(--bad); }
.btn.danger.solid {
  background: var(--bad);
  border-color: var(--bad);
  color: var(--surface);
  font-weight: 600;
}
.btn.danger.solid:hover { filter: brightness(1.08); }
/* A toggle stays pressed: Follow, Wrap, Reveal. */
.btn.toggle { font-size: 13px; color: var(--ink-2); padding: 6px 11px; }
.btn.toggle.on {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
  font-weight: 600;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  padding: 0;
  border-radius: var(--radius);
  border: 1px solid transparent;
  background: none;
  color: var(--ink-2);
  cursor: pointer;
}
.icon-btn:hover { background: var(--surface-2); color: var(--ink); }
.icon-btn.framed { border-color: var(--line-2); background: var(--surface); }

input, select, textarea {
  font: 400 14px/1.4 var(--sans);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  padding: 9px 12px;
  min-width: 0;
}
/* An input is 40px, whichever voice it is set in. Padding alone made the box
   the height of its own line box, so a mono field came out 38px beside a sans
   one at 40px and a row of both was ragged. The height is stated; the padding
   stays for the horizontal inset and centres the text through the line box. */
input, select {
  height: 40px;
  line-height: 20px;
}
input[type="checkbox"], input[type="radio"], input[type="range"] { height: auto; }
input.mono, .mono input, textarea.mono, select.mono {
  font-family: var(--mono);
  font-size: 13px;
}
input::placeholder, textarea::placeholder { color: var(--ink-3); }
input:hover, select:hover, textarea:hover { border-color: var(--ink-3); }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}
input[readonly] { background: var(--surface-2); color: var(--ink-2); }
input[readonly]:hover { border-color: var(--line-2); }
input[type="checkbox"] { accent-color: var(--accent); width: 16px; height: 16px; }
input[type="radio"] { accent-color: var(--accent); width: 16px; height: 16px; }
input[type="search"] { appearance: none; }
input[type="range"] { accent-color: var(--accent); padding: 0; border: none; background: none; }

/* Label above the input, helper text under it. */
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: 13px; font-weight: 500; color: var(--ink); }
.field .hint { font-size: 12.5px; }
.field input, .field select, .field textarea { width: 100%; }

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-4);
}
/* .thirds caps the same grid at three columns, so a field is wide enough for
   a one-line hint and the fourth starts a second row. */
.form-grid.thirds {
  grid-template-columns: repeat(auto-fit, minmax(max(220px, (100% - 2 * var(--sp-4)) / 3), 1fr));
}

.check { display: inline-flex; align-items: center; gap: var(--sp-2); cursor: pointer; font-size: 13.5px; }

/* The search box. A width, not a flex basis: an input's intrinsic width is
   about 240px, and a flex ancestor sizing itself to its content asks for that
   number rather than for the basis. */
.search {
  position: relative;
  display: flex;
  align-items: center;
  flex: 0 1 auto;
  width: 320px;
  max-width: 100%;
}
.search .icon { position: absolute; left: 11px; color: var(--ink-3); pointer-events: none; }
/* The box narrows before its placeholder does, so the placeholder is clipped
   with an ellipsis rather than cut mid-word: "Search project…" is a truncation
   a reader can see, "Search projec" is one they cannot. */
.search-input { width: 100%; padding-left: 34px; text-overflow: ellipsis; }
/* The shell's own box is smaller and sits on the bar. */
.topbar-right .search { width: 240px; }
.topbar-right .search-input { background: var(--surface-2); border-color: transparent; }
.topbar-right .search-input:hover { border-color: var(--line-2); }
.topbar-right .search-input:focus { background: var(--surface); border-color: var(--accent); }

.toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding: var(--sp-2) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  min-height: 52px;
}
/* In a card header or a section head the strip is already there, so the box
   drops away and the toolbar is only its controls. */
.card-head .toolbar, .section-head .toolbar {
  border: none;
  border-radius: 0;
  background: none;
  padding: 0;
  min-height: 0;
}

/* The segmented control: joined buttons, the selected one filled. */
.seg {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}
.seg-btn {
  padding: 7px 14px;
  border: none;
  border-left: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink-2);
  font: 500 13.5px/1.4 var(--sans);
  cursor: pointer;
  white-space: nowrap;
}
.seg-btn:first-child { border-left: none; }
.seg-btn:hover { background: var(--surface-2); color: var(--ink); }
.seg-btn.on { background: var(--accent); border-left-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
.seg-btn.on + .seg-btn { border-left-color: var(--accent); }
.seg-btn:focus-visible { outline-offset: -3px; }

/* The toggle: an iOS-style pill over a real checkbox, so the control is a
   checkbox to the keyboard and to a screen reader and a switch to the eye. */
.switch { display: inline-flex; align-items: center; gap: var(--sp-3); cursor: pointer; font-size: 13.5px; }
.switch input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  padding: 0;
}
.switch-track {
  position: relative;
  width: 38px;
  height: 22px;
  flex: none;
  border-radius: var(--radius-tag);
  background: var(--surface-3);
  /* An outline, so an off switch still reads as a control on a dark card. */
  box-shadow: inset 0 0 0 1px var(--line);
  transition: background var(--dur) var(--ease);
}
.switch-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: var(--shadow-card);
  transition: transform var(--dur) var(--ease);
}
/* Off, the knob is ink-toned so it shows on both grounds; on, it is the
   surface colour on the accent. */
.switch input:not(:checked) + .switch-track .switch-knob { background: var(--ink-3); box-shadow: none; }
.switch input:checked + .switch-track { background: var(--accent); box-shadow: none; }
.switch input:checked + .switch-track .switch-knob { transform: translateX(16px); }
/* A disabled switch is disabled all through: the track fades, the label fades
   with it, and the pointer stops promising a click. Half a control greyed reads
   as a control you may still use. */
.switch input:disabled + .switch-track { opacity: 0.5; }
.switch input:disabled ~ .switch-label { color: var(--ink-3); }
.switch:has(input:disabled) { cursor: not-allowed; }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
.switch-label { min-width: 0; }

/* A radio card: an exclusive choice the size of a card. */
.radio-card {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-card);
  background: var(--surface);
  cursor: pointer;
  min-width: 0;
}
.radio-card:hover { border-color: var(--accent-line); }
.radio-card.on { border-color: var(--accent); background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent); }
.radio-card input { margin: 2px 0 0; flex: none; order: 2; }
.radio-card-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; order: 1; }
.radio-card-title { font-size: 14px; font-weight: 600; }
.radio-card-blurb { font-size: 12.5px; color: var(--ink-2); }

/* The inline error a form keeps until the next attempt. */
.form-error {
  padding: 10px var(--sp-3);
  border-radius: var(--radius);
  background: var(--bad-soft);
  color: var(--bad);
  font-size: 13px;
  overflow-wrap: anywhere;
}

.copy-field { display: flex; flex-direction: column; gap: 6px; }
.copy-row { display: flex; gap: var(--sp-2); align-items: stretch; }
.copy-row input { flex: 1 1 auto; }

/* ---- status --------------------------------------------------------------
   A dot before a word for a live state, a soft pill for a recorded one. Both
   take their colour from the four semantics and never from the accent — with
   one exception: `info` is the accent, which is what the design uses for a
   state that is simply true rather than good or bad. */

.status-dot {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13.5px;
  color: var(--ink);
  white-space: nowrap;
}
.status-dot .dot { background: var(--slate); }
.status-dot.ok .dot { background: var(--ok); }
.status-dot.warn .dot { background: var(--warn); }
.status-dot.bad .dot { background: var(--bad); }
.status-dot.info .dot { background: var(--accent); }
.status-dot.idle .dot { background: var(--ink-3); }

.dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }

/* A pill, a chip and a health reading are words a person reads, so each names
   the sans itself: one of them lands inside a cell that is set in the machine
   voice (a pill beside a hostname on the Domains tab) and would otherwise
   inherit the mono. There is no `text-transform` here — capitalising is done
   where the label is made, because CSS can only title-case *every* word and
   "not installed" is one state, not two names. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--radius-tag);
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  background: var(--slate-soft);
  color: var(--slate);
}
.pill.ok, .pill.live, .pill.on, .pill.completed { background: var(--ok-soft); color: var(--ok); }
.pill.bad, .pill.failed { background: var(--bad-soft); color: var(--bad); }
.pill.warn, .pill.building, .pill.deploying, .pill.running, .pill.started {
  background: var(--warn-soft);
  color: var(--warn);
}
.pill.info { background: var(--accent-soft); color: var(--accent); }
.pill.neutral, .pill.queued, .pill.superseded, .pill.off, .pill.pending, .pill.unknown {
  background: var(--slate-soft);
  color: var(--slate);
}

.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-tag);
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}
.badge.secret { background: var(--warn-soft); border-color: transparent; color: var(--warn); }
.badge.env { background: var(--slate-soft); border-color: transparent; color: var(--slate); }
/* .badge.src marks where an app's code lives — the platform's own git service.
   It rides on lines that are already small and already mono (the machine line
   under an app's name, an app's facts), so it takes their size and pulls its
   icon down with it rather than setting the line height itself. */
.badge.src {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  font-size: 11.5px;
}
.badge.src .icon { width: 12px; height: 12px; }

/* chip is the live verdict about a running thing; health is the same shape
   carrying ready-over-desired. */
.chip, .health {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 3px 10px;
  border-radius: var(--radius-tag);
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
  background: var(--slate-soft);
  color: var(--slate);
}
/* health is the one of the three that is a figure and not a word, so it keeps
   the machine voice — stated here rather than inherited. */
.health { font-family: var(--mono); font-size: 12px; }
.chip.ok, .health.ok { background: var(--ok-soft); color: var(--ok); }
.chip.warn, .health.warn { background: var(--warn-soft); color: var(--warn); }
.chip.bad, .health.bad { background: var(--bad-soft); color: var(--bad); }
.chip.idle, .health.idle { background: var(--slate-soft); color: var(--slate); }

/* ---- stepper -------------------------------------------------------------
   A horizontal run of marks joined by lines: a check in the accent for done,
   a cross in red for failed, a spinner-less ring for the step in flight, a
   number in grey for one not started. The label sits beside the mark and the
   state or duration under it. */

.stepper {
  display: flex;
  align-items: flex-start;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 0;
}
.step {
  flex: 1 1 0;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-width: 0;
}
.step:last-child { flex: 0 1 auto; }
.step-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  border: 1.5px solid var(--line-2);
  background: var(--surface);
  color: var(--ink-3);
  font-size: 12.5px;
  font-weight: 600;
}
.step-mark .icon { width: 15px; height: 15px; }
.step-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.step-row { display: flex; flex-direction: column; gap: 1px; min-width: 0; padding-top: 2px; }
.step-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
}
.step-sub { font-size: 12.5px; color: var(--ink-3); }
/* The connector: a 2px rule from this step's label out to the next mark. */
.step-bar {
  flex: 1 1 auto;
  min-width: 16px;
  height: 2px;
  margin: 12px var(--sp-3) 0;
  background: var(--line);
  border-radius: 1px;
}
.step:last-child .step-bar { display: none; }

.step.done .step-mark { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.step.done .step-label { color: var(--ink); }
.step.done .step-bar { background: var(--accent); }

.step.current .step-mark, .step.active .step-mark {
  border-color: var(--accent);
  color: var(--accent);
  animation: pulse 1.6s ease-in-out infinite;
}
.step.current .step-label, .step.active .step-label { color: var(--ink); }

.step.failed .step-mark { background: var(--bad); border-color: var(--bad); color: var(--surface); }
.step.failed .step-label { color: var(--bad); }

.step.end .step-mark { background: var(--slate-soft); border-color: var(--line-2); color: var(--slate); }
.step.end .step-label { color: var(--ink-2); }

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--accent-soft); }
  50% { box-shadow: 0 0 0 5px var(--accent-soft); }
}

.progress-note { margin-top: var(--sp-4); font-size: 13.5px; }

/* ---- wizard header -------------------------------------------------------
   The numbered steps beside the title of a create flow: 1 Source — 2
   Configure — 3 Review. */

.wizard {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  min-width: 0;
}
.wizard-step { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.wizard-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: 12.5px;
  font-weight: 600;
}
.wizard-label { font-size: 14px; font-weight: 500; color: var(--ink-2); white-space: nowrap; }
.wizard-step.on .wizard-num { background: var(--accent); color: var(--accent-ink); }
.wizard-step.on .wizard-label { color: var(--accent); font-weight: 600; }
.wizard-step.done .wizard-num { background: var(--accent-soft); color: var(--accent); }
.wizard-line { width: 44px; height: 1px; background: var(--line-2); flex: none; }

/* A step is also the way to that step: every create flow wires a click onto
   the span, so it says so under the pointer and shows its focus ring clear of
   the numbered disc. */
.wizard-jump { cursor: pointer; }
.wizard-jump:focus-visible { outline-offset: 3px; }

/* The numbered step of a long form ("1 Choose engine"), used down the left of
   a create page. */
.step-section { display: flex; flex-direction: column; gap: var(--sp-3); }
.step-head { display: flex; align-items: center; gap: var(--sp-3); }
.step-head h3 { font-size: 15px; font-weight: 600; }
.step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12.5px;
  font-weight: 600;
}

.howto { font-size: 13.5px; }
.howto h3 { font-size: 15px; margin-bottom: var(--sp-2); }
.howto ol { margin: 0; padding-left: 20px; color: var(--ink-2); }
.howto li + li { margin-top: var(--sp-2); }
.howto .mono { color: var(--ink); }

/* ---- banners -------------------------------------------------------------
   A full-width tinted strip with an icon: blue for info, amber for warning,
   red for error, green for done. The text stays ink so it reads as a
   sentence, not as a colour. */

.banner {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-card);
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink);
  font-size: 13.5px;
}
.banner .icon { margin-top: 2px; flex: none; width: 18px; height: 18px; color: var(--ink-2); }
.banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.banner-title { font-weight: 600; }
.banner-body { color: var(--ink-2); overflow-wrap: anywhere; }
.banner-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.banner .mono { overflow-wrap: anywhere; }
.banner.info { background: var(--accent-soft); border-color: transparent; }
.banner.info .icon { color: var(--accent); }
.banner.ok { background: var(--ok-soft); border-color: transparent; }
.banner.ok .icon { color: var(--ok); }
.banner.warn { background: var(--warn-soft); border-color: transparent; }
.banner.warn .icon { color: var(--warn); }
.banner.bad { background: var(--bad-soft); border-color: transparent; }
.banner.bad .icon { color: var(--bad); }

/* ---- the log -------------------------------------------------------------
   A white panel: line numbers in grey, a timestamp column, then the line in
   mono. A failed line is tinted red and a warning amber, each with a bar down
   its edge; the row under the pointer is lifted so a long line can be
   followed across. */

pre.log {
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  background: var(--code-bg);
  color: var(--code-ink);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.65;
  max-height: min(62vh, 640px);
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  tab-size: 4;
}
pre.log:focus-visible { outline-offset: -2px; }
pre.log:empty::before {
  content: "waiting for output\2026";
  color: var(--code-ink-2);
}
/* logPane()'s rows run edge to edge so a tint does; the padding moves onto
   the row. The time and the pod take their own columns — each drawn only once
   the stream has one to show — then the line. */
pre.log.rows { padding: var(--sp-2) 0; }
pre.log.rows:empty::before { display: block; padding: var(--sp-1) var(--sp-4); }
.log-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  padding: 0 var(--sp-4);
}
.log-row .log-time, .log-row .log-pod { display: none; flex: none; user-select: none; }
pre.log.timed .log-row .log-time { display: inline-block; min-width: 8ch; }
pre.log.pods .log-row .log-pod { display: inline-block; min-width: 5ch; color: var(--code-ink-2); font-size: 11.5px; }
.log-row .log-text { flex: 1 1 auto; min-width: 0; }
pre.log.nowrap .log-row { width: max-content; min-width: 100%; }
pre.log.nowrap .log-text { white-space: pre; overflow-wrap: normal; }
/* The controller's own "[genesys] " lines are its commentary on the builder's
   output, so they are set in the accent rather than in the body ink. */
.log-status { color: var(--code-note); }
/* A log toolbar's Copy is its icon alone; the toggles beside it need the room. */
.btn.only-icon { padding-left: 10px; padding-right: 10px; }

/* .log-panel is the card the pane lives in. */
.log-panel { background: var(--code-bg); }
.log-panel .card-head { background: var(--code-bg); }
.log-panel pre.log { min-height: 280px; }
.log-panel .empty { color: var(--code-ink-2); }

/* logPanel() is the line-numbered variant: a grid of number, time and text,
   which is what a build log looks like in this design. */
.logpanel {
  background: var(--code-bg);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.65;
  max-height: min(62vh, 640px);
  overflow: auto;
  padding: var(--sp-2) 0;
}
.log-line {
  display: grid;
  grid-template-columns: 52px auto minmax(0, 1fr);
  gap: 0 var(--sp-3);
  padding: 0 var(--sp-4) 0 0;
}
.log-num {
  text-align: right;
  color: var(--code-ink-2);
  user-select: none;
  font-size: 11.5px;
  padding-right: var(--sp-3);
  border-right: 1px solid var(--line);
}
.log-time { color: var(--code-ink-2); white-space: nowrap; font-size: 11.5px; }
.log-text { color: var(--code-ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.log-row:hover, .log-line:hover { background: var(--code-hover); }
.log-row.fail, .log-line.fail { background: var(--code-fail); box-shadow: inset 2px 0 0 var(--bad); }
.log-row.warn, .log-line.warn { background: var(--code-warn); box-shadow: inset 2px 0 0 var(--warn); }
.log-line.fail .log-text { color: var(--bad); }
.log-row.debug .log-text { color: var(--code-ink-2); }
.log-line.note .log-text { color: var(--code-note); }
/* A JSON or logfmt line: the level word in its status colour, the message in
   ink and the fields after it quieter. */
.log-lvl { display: inline-block; min-width: 5ch; font-size: 11px; font-weight: 600; color: var(--code-ink-2); }
.log-lvl.error, .log-lvl-word.error { color: var(--bad); }
.log-lvl.warn, .log-lvl-word.warn { color: var(--warn); }
.log-lvl-word { font-weight: 600; }
.log-lvl-word.info, .log-lvl-word.debug { color: var(--code-ink-2); }
.log-kv { color: var(--code-ink-2); }
.log-k { opacity: 0.72; }
.log-v.error { color: var(--bad); }
.log-v.warn { color: var(--warn); }
/* The kubelet asking the app's health-check path: kept, but out of the way. */
.log-row.probe .log-text, .log-row.probe .log-lvl-word { color: var(--code-ink-2); }
.log-row.probe .log-kv { opacity: 0.8; }
/* BuildKit's steps (logPanel's `steps`): the step's number in grey, its
   first line set heavier, its DONE and CACHED lines quiet. A digest is drawn
   short and grey. */
.log-step { color: var(--code-ink-2); }
.log-line.step-head .log-text { font-weight: 600; }
.log-line.step-head .log-step { font-weight: 400; }
.log-line.step-end .log-text { color: var(--code-ink-2); }
.log-digest { color: var(--code-ink-2); }
/* A phase of a run inside one log (logPanel's `phase`): a deployment's release
   command. The run of lines is marked down its edge under a drawn heading
   that is not a line of the log. */
.log-phase {
  margin-top: var(--sp-2);
  padding: 2px var(--sp-4) 2px 64px;
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--code-note);
}
.log-line.in-phase { box-shadow: inset 3px 0 0 var(--code-note); }
.log-line.in-phase:not(.fail) { background: var(--accent-soft); }
.log-line.in-phase + .log-line:not(.in-phase) { margin-top: var(--sp-2); }
.logpanel:empty::before {
  content: "waiting for output\2026";
  display: block;
  padding: var(--sp-3) var(--sp-4);
  color: var(--code-ink-2);
}

/* ---- timeline ------------------------------------------------------------
   A rule down the left with a mark on it per event, the time beside it and
   what happened next to that. */

.timeline { display: flex; flex-direction: column; list-style: none; margin: 0; padding: 0; }
.tl-item {
  display: grid;
  grid-template-columns: 14px auto minmax(0, 1fr);
  gap: 0 var(--sp-3);
  padding-bottom: var(--sp-4);
  min-width: 0;
}
.tl-item:last-child { padding-bottom: 0; }
.tl-mark { position: relative; display: flex; justify-content: center; padding-top: 5px; }
.tl-mark::before {
  content: "";
  position: absolute;
  top: 5px;
  bottom: calc(-1 * var(--sp-4));
  left: 50%;
  width: 1px;
  margin-left: -0.5px;
  background: var(--line);
}
.tl-item:last-child .tl-mark::before { display: none; }
.tl-dot {
  position: relative;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
.tl-item.done .tl-dot { background: var(--ok); }
.tl-item.idle .tl-dot { background: var(--ink-3); }
.tl-item.bad .tl-dot { background: var(--bad); }
.tl-item.warn .tl-dot { background: var(--warn); }
.tl-time { font-size: 13px; color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums; }
.tl-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.tl-title { font-size: 13.5px; font-weight: 600; overflow-wrap: anywhere; }
.tl-sub { font-size: 12.5px; color: var(--ink-2); overflow-wrap: anywhere; }

/* ---- menus ---------------------------------------------------------------
   The ⋮ and "Actions ▾" dropdowns. They live inside a popover, so the
   keyboard contract (Escape, focus return, one open at a time) is the
   popover's. */

.menu { display: flex; flex-direction: column; padding: var(--sp-1); min-width: 0; }
.menu-head {
  padding: 10px var(--sp-3) var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.menu-name { font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
.menu-sub { font-size: 12.5px; color: var(--ink-2); overflow-wrap: anywhere; }
.menu-sep { height: 1px; margin: var(--sp-1) 0; background: var(--line); }
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: 8px var(--sp-3);
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink);
  font: 400 13.5px/1.5 var(--sans);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.menu-item:hover { background: var(--surface-2); text-decoration: none; }
.menu-item .icon { color: var(--ink-3); }
.menu-item.danger { color: var(--bad); }
.menu-item.danger .icon { color: var(--bad); }
.menu-item:disabled { opacity: 0.5; cursor: default; }

/* ---- empty states -------------------------------------------------------- */

.empty-state {
  padding: 56px var(--sp-5);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}
.empty-state h2 { font-size: 19px; }
.empty-state p { max-width: 48ch; }
.empty-state .choices { margin-top: var(--sp-5); }

/* The first-project hero: the outline drawing, one large sentence, the form
   inline, and the three-step strip under a rule. */
.empty-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: 64px 0 0;
  text-align: center;
}
.hero-art { color: var(--line-2); }
.hero-art .icon { width: 56px; height: 56px; stroke-width: 1; }
.empty-hero h2 { font-size: 30px; font-weight: 650; letter-spacing: -0.03em; }
.hero-sub { font-size: 15px; color: var(--ink-2); max-width: 46ch; }
.hero-form {
  width: min(460px, 100%);
  margin-top: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  text-align: left;
}
.hero-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--sp-5);
  width: 100%;
  margin-top: 56px;
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  text-align: left;
}
.hero-step { display: flex; align-items: flex-start; gap: var(--sp-3); min-width: 0; }
.hero-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
}
.hero-step-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.hero-step-title { font-size: 14px; font-weight: 600; }
.hero-step-sub { font-size: 13px; color: var(--ink-2); }

.choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-3);
  width: 100%;
  max-width: 660px;
  text-align: left;
}
/* Three choices sit on one row rather than two and one: a third card widens
   the grid by the card it adds. */
.choices:has(> :nth-child(3)) { max-width: 900px; }
.choice {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: 15px var(--sp-4);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-card);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  font: inherit;
  text-align: left;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.choice:hover { border-color: var(--accent-line); background: var(--surface-2); }
.choice.on { border-color: var(--accent); background: var(--accent-soft); }
.choice-title { font-weight: 600; font-size: 14px; }
.choice-blurb { font-size: 12.5px; color: var(--ink-2); }
.choice.on .choice-blurb { color: var(--ink-2); }

/* ---- overlays ------------------------------------------------------------ */

.overlay { position: fixed; inset: 0; z-index: 60; }
.scrim { position: absolute; inset: 0; background: var(--scrim); }

.drawer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(480px, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-left: 1px solid var(--line);
  box-shadow: var(--overlay-shadow);
}
.drawer-head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--line);
}
.drawer-title { font-size: 18px; font-weight: 600; }
.drawer-sub { margin-top: 4px; font-size: 13.5px; }
.drawer-form { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.drawer-body { flex: 1 1 auto; overflow-y: auto; padding: var(--sp-5); }
.drawer-body > * + * { margin-top: var(--sp-5); }
.drawer-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--line);
  background: var(--surface);
}
.drawer-foot .form-error { flex: 1 1 100%; }

/* The confirm (confirmAction): the drawer's dialog, centred and sized to its
   sentence. */
.overlay.centred { display: grid; place-items: center; padding: var(--sp-4); }
.drawer.confirm {
  position: relative;
  inset: auto;
  width: min(460px, 100%);
  max-height: calc(100vh - 32px);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  overflow: hidden;
}
.drawer.confirm .drawer-body { padding: var(--sp-4) var(--sp-5); }
.drawer.confirm .drawer-body > * + * { margin-top: var(--sp-4); }
.drawer.confirm .drawer-foot { border-top: none; padding-top: 0; }

/* The context menu: a popover pinned where the right click was. */
.popover.context-menu { position: fixed; top: 0; left: 0; right: auto; z-index: 70; min-width: 200px; }

/* ---- the command palette ------------------------------------------------ */

.palette-overlay { display: flex; justify-content: center; align-items: flex-start; padding: 12vh var(--sp-4) var(--sp-4); }
.palette {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(640px, 100%);
  max-height: min(560px, 76vh);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--overlay-shadow);
  overflow: hidden;
}
.palette-field {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0 var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.palette-field .icon { color: var(--ink-3); font-size: 18px; }
.palette-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 52px;
  border: none;
  outline: none;
  background: none;
  color: var(--ink);
  font: 400 16px/1 var(--sans);
}
.palette-input:focus-visible { outline: none; }
.palette-list { flex: 1 1 auto; overflow-y: auto; padding: var(--sp-2); }
.palette-group {
  padding: var(--sp-2) var(--sp-2) var(--sp-1);
  color: var(--ink-3);
  font-size: 12px;
  font-weight: 600;
}
.palette-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 8px var(--sp-2);
  border-radius: var(--radius-sm);
  color: var(--ink);
  cursor: pointer;
}
.palette-item.on { background: var(--accent-soft); }
.palette-item.on .palette-icon { color: var(--accent); }
.palette-icon { display: inline-flex; color: var(--ink-3); }
.palette-label { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.palette-hint {
  margin-left: auto;
  padding-left: var(--sp-3);
  color: var(--ink-3);
  font-size: 12.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.palette-empty { padding: var(--sp-5) var(--sp-3); color: var(--ink-2); text-align: center; }
.palette-foot {
  display: flex;
  gap: var(--sp-4);
  padding: 8px var(--sp-4);
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-3);
  font-size: 12px;
}
.palette-foot > span { display: inline-flex; align-items: center; gap: 3px; }

/* ---- staged changes (ADR 0030 §5) ----------------------------------------
   The floating bar the canvas mounts at its top centre, its list, and the
   note a form shows after a save that staged. Amber is the staged colour
   everywhere: the bar's dot, a node's dot, a dashed edge. */

.staged-dot {
  display: inline-block;
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--staged);
  box-shadow: 0 0 0 3px var(--staged-soft);
}
.staged-note {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 2px 10px 2px 8px;
  border-radius: var(--radius-tag);
  background: var(--staged-soft);
  color: var(--ink);
  font-size: 12.5px;
  font-weight: 500;
}
/* The bar does not place itself: the canvas mounts it in a floating host at
   its top centre, so it is only as wide as what it says. */
.changes-bar {
  position: relative;
  width: max-content;
  max-width: min(720px, 100%);
  background: var(--surface);
  border: 1px solid var(--staged);
  border-radius: 12px;
  box-shadow: var(--shadow-pop);
}
.changes-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 6px 6px 6px var(--sp-4);
}
.changes-what { font-size: 13.5px; color: var(--ink-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.changes-what strong { color: var(--ink); font-weight: 600; }
.changes-actions { display: flex; align-items: center; gap: var(--sp-1); margin-left: auto; }
.changes-list {
  list-style: none;
  margin: 0;
  padding: var(--sp-1) var(--sp-2) var(--sp-2);
  max-height: 50vh;
  overflow-y: auto;
  border-top: 1px solid var(--line);
}
.changes-item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-2);
  border-radius: var(--radius-sm);
}
.changes-item + .changes-item { border-top: 1px solid var(--line); }
.changes-item.failed { background: var(--bad-soft); }
.changes-item-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.changes-item-head { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.changes-app {
  padding: 0 7px;
  border-radius: var(--radius-tag);
  background: var(--staged-soft);
  color: var(--ink);
  font-size: 12px;
  font-weight: 600;
}
.changes-summary { font-size: 13.5px; color: var(--ink); }
.changes-vars { display: flex; flex-direction: column; font-size: 12px; color: var(--ink-2); overflow-wrap: anywhere; }
.changes-meta { font-size: 12px; color: var(--ink-3); }
.changes-bar .changes-item .icon-btn { width: 26px; height: 26px; border: none; background: none; }
.changes-bar .changes-actions .btn, .changes-bar .changes-item .icon-btn { box-shadow: none; }

.pop-host { position: relative; }
.popover {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 50;
  width: max-content;
  min-width: 220px;
  max-width: min(360px, calc(100vw - 32px));
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-pop);
  overflow: hidden;
}
.popover.pop-left { left: 0; right: auto; }
.pop-inner { display: block; }
.pop-tabs { display: flex; border-bottom: 1px solid var(--line); background: var(--surface); }
.pop-tab {
  flex: 1 1 0;
  padding: 10px 12px;
  border: none;
  background: none;
  color: var(--ink-2);
  font: 500 13.5px/1.4 var(--sans);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.pop-tab:hover { color: var(--ink); }
.pop-tab.on { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.pop-form { padding: var(--sp-4); min-width: 300px; }
.pop-form .btn { width: 100%; }

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

.toasts {
  position: fixed;
  right: var(--sp-4);
  bottom: var(--sp-4);
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-width: min(380px, calc(100vw - 32px));
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: 10px 8px 10px 14px;
  border-radius: var(--radius-card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--bad);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-pop);
  font-size: 13.5px;
}
.toast.ok { border-left-color: var(--ok); }
.toast-text { flex: 1 1 auto; overflow-wrap: anywhere; }
.toast .icon-btn { width: 24px; height: 24px; font-size: 16px; line-height: 1; }

/* ---- boot & sign in ------------------------------------------------------ */

.boot, .signin-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
}
.boot { flex-direction: column; gap: var(--sp-3); text-align: center; }
.boot h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.025em; }
.signin {
  width: min(400px, 100%);
  padding: 40px var(--sp-6);
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-pop);
}
.signin h1 { font-size: 22px; margin-bottom: var(--sp-2); }
.signin .btn { margin-top: var(--sp-5); padding: 11px 14px; }
.signin .hint { margin-top: var(--sp-3); display: block; }

/* ---- the projects grid ----------------------------------------------------
   The home screen: one card per project, a link to its canvas, with a tiny
   drawing of its nodes (projects.js lays it out with the canvas's own
   layoutRows and positions each chip through the CSSOM). */

.pcards {
  display: grid;
  /* 340px leaves the preview its 300px drawing (projects.js PREVIEW) inside the padding. */
  grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
  gap: var(--sp-4);
}
.pcard {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  color: var(--ink);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.pcard:hover { border-color: var(--accent-line); box-shadow: var(--shadow-pop); text-decoration: none; }
.pcard.tone-bad { border-left: 3px solid var(--bad); }
.pcard.tone-warn { border-left: 3px solid var(--warn); }
.pcard-head { display: flex; align-items: flex-start; gap: var(--sp-3); min-width: 0; }
.pcard-initial {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
}
.pcard-title { flex: 1 1 auto; min-width: 0; }
.pcard-name { font-size: 15.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pcard-about {
  color: var(--ink-2);
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pcard-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 104px;
  border-radius: var(--radius);
  background-color: var(--canvas-bg);
  background-image: radial-gradient(var(--canvas-dot) 1px, transparent 1px);
  background-size: 12px 12px;
  overflow: hidden;
}
.pcard-preview-empty { color: var(--ink-3); font-size: 12.5px; }
.pcard-stage { position: relative; flex: none; }
.pcard-node {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 0 6px;
  background: var(--node-bg);
  border: 1px solid var(--node-border);
  border-radius: 5px;
  box-shadow: var(--node-shadow);
  color: var(--ink-2);
  font-size: 11px;
  line-height: 1;
  overflow: hidden;
}
.pcard-node-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); }
.pcard-node.kind-db .pcard-node-dot, .pcard-node.kind-redis .pcard-node-dot,
.pcard-node.kind-mysql .pcard-node-dot { border-radius: 2px; background: var(--slate); }
.pcard-node.tone-ok .pcard-node-dot { background: var(--ok); }
.pcard-node.tone-warn .pcard-node-dot { background: var(--warn); }
.pcard-node.tone-bad .pcard-node-dot { background: var(--bad); }
.pcard-node-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The foot's two halves share a line while they fit; on a narrow card the
   last deploy drops under the tally, and only its app name is cut short. */
.pcard-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-1) var(--sp-3);
  color: var(--ink-2);
  font-size: 12.5px;
  min-width: 0;
}
.pcard-deploy { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; min-width: 0; max-width: 100%; }
.pcard-deploy > .pill, .pcard-deploy > .nowrap { flex: none; }
.pcard-deploy-app { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pcard-node-count { flex: none; margin-left: auto; color: var(--ink-3); }
/* The card's usage line (usage.js): a problem in its tone first, then the
   replicas ready and what they use, cut short rather than wrapped. */
.pcard-usage {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  margin-top: calc(var(--sp-1) * -1);
  color: var(--ink-2);
  font-size: 12.5px;
  white-space: nowrap;
}
.pcard-usage-phrase { display: inline-flex; align-items: center; gap: 6px; flex: none; max-width: 60%; font-weight: 550; }
.pcard-usage-phrase { overflow: hidden; text-overflow: ellipsis; }
.pcard-usage-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pcard-usage.tone-warn .pcard-usage-phrase { color: var(--warn); }
.pcard-usage.tone-bad .pcard-usage-phrase { color: var(--bad); }
.pcard-usage-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.skel-card { min-height: 190px; }
.skel-card .skel { display: block; }

/* ---- the health row (docs/adr/0054-health-row.md) -------------------------
   One line of what is running, wrong and happening (healthrow.js): a strip
   with the cards' edge over the Projects grid, and a floating bar at the
   bottom of a project's canvas (canvas.css places it). The slot is the
   container its cells give way by, lowest first, so the row never scrolls
   sideways; nothing is measured. The dot pulses once each time the row is
   painted from fresh readings — that is the "live". */

.hrow-slot { container: hrow / inline-size; min-width: 0; }
.hrow {
  display: flex;
  align-items: center;
  height: 44px;
  min-width: 0;
  padding: 0 var(--sp-1);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  color: var(--ink-2);
  font-size: 12.5px;
  white-space: nowrap;
}
.hrow.tone-bad { border-left: 3px solid var(--bad); }
.hrow.tone-warn { border-left: 3px solid var(--warn); }
.hrow-cell {
  position: relative;
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 0 12px;
}
.hrow-cell + .hrow-cell::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 1px;
  height: 16px;
  margin-top: -8px;
  background: var(--line-2);
}
.hrow-verdict { color: var(--ink); font-weight: 600; font-size: 13px; }
.hrow-dot {
  position: relative;
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-3);
}
.hrow-dot.ok { background: var(--ok); }
.hrow-dot.warn { background: var(--warn); }
.hrow-dot.bad { background: var(--bad); }
.hrow-dot.busy { background: var(--accent); }
.hrow-dot::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: inherit;
  opacity: 0;
  animation: hrow-pulse 1.2s var(--ease) 1;
}
.hrow.loading .hrow-dot::after { animation: none; }
@keyframes hrow-pulse {
  from { transform: scale(1); opacity: 0.6; }
  to { transform: scale(3.2); opacity: 0; }
}
.hrow-live {
  padding: 1px 6px;
  border-radius: var(--radius-tag);
  background: var(--slate-soft);
  color: var(--slate);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.hrow-label { color: var(--ink-3); }
.hrow-value { color: var(--ink); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.hrow-cell.tone-warn .hrow-value { color: var(--warn); }
.hrow-cell.tone-bad .hrow-value { color: var(--bad); }
.hrow-cell.tone-busy .hrow-value { color: var(--accent); }
.hrow-cell .icon { width: 13px; height: 13px; color: var(--ink-3); }
.hrow-cell.tone-warn .icon { color: var(--warn); }
.hrow-dismiss { width: 20px; height: 20px; margin-right: -6px; }
.hrow-cell .hrow-dismiss .icon { width: 12px; height: 12px; color: var(--ink-3); }
.hrow-cell .hrow-dismiss:hover .icon { color: var(--ink); }
.hrow .pill { padding: 0 8px; font-size: 11.5px; }
.hrow-problems { flex: 0 1 auto; }
/* The last deploy gives way before a problem is cut short. */
.hrow-last { flex: 0 12 auto; }
.hrow-chip {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 240px;
  padding: 2px 9px;
  border-radius: var(--radius-tag);
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 550;
  text-decoration: none;
}
.hrow-chip.bad { background: var(--bad-soft); color: var(--bad); }
.hrow-chip.warn { background: var(--warn-soft); color: var(--warn); }
a.hrow-chip:hover { text-decoration: none; box-shadow: inset 0 0 0 1px currentColor; }
.hrow-more { flex: none; color: var(--ink-3); }
.hrow-more.narrow { display: none; }
.hrow.loading .skel { width: 96px; }
/* The home's strip is as wide as the grid: the last deploy sits at its end. */
.hrow-last { margin-left: auto; }

@container hrow (max-width: 1330px) { .hrow-drop-6 { display: none; } }
@container hrow (max-width: 1220px) { .hrow-drop-5 { display: none; } }
@container hrow (max-width: 960px) { .hrow-drop-4 { display: none; } }
@container hrow (max-width: 640px) {
  .hrow-drop-3, .hrow-chip.second, .hrow-more.wide { display: none; }
  .hrow-more.narrow { display: inline; }
}
@container hrow (max-width: 480px) { .hrow-drop-2 { display: none; } }
@container hrow (max-width: 330px) { .hrow-drop-1 { display: none; } }
@container hrow (max-width: 200px) { .hrow-live { display: none; } }
@container hrow (max-width: 130px) { .hrow { display: none; } }

/* ---- responsive ---------------------------------------------------------- */

/* The detail rail needs a real column. Below this the rail moves under the
   main content, which is where it reads on a laptop in a split window. */
@media (max-width: 1199px) {
  .twocol { grid-template-columns: minmax(0, 1fr); }
  .rail { position: static; }
}

/* Two tables side by side need a whole column each. */
@media (max-width: 1359px) {
  .split { grid-template-columns: minmax(0, 1fr); }
  .split th, .split td { padding-left: var(--sp-4); padding-right: var(--sp-4); }
}

@media (max-width: 999px) {
  :root { --gutter: 20px; }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tile:nth-child(3), .kpi:nth-child(3) { border-left: none; }
  .tile:nth-child(n + 3), .kpi:nth-child(n + 3) { border-top: 1px solid var(--line); }
  .search { width: 260px; }
  .topbar-right .search { width: 180px; }
}

/* Narrow: the nav tabs collapse into the menu button and drop down as a
   panel under the bar. Nothing may scroll the body sideways. */
@media (max-width: 859px) {
  :root { --gutter: 16px; }
  .topbar { grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--sp-2); }
  .icon-btn.nav-toggle { display: inline-flex; order: -1; }
  .topnav {
    display: none;
    position: absolute;
    top: var(--topbar);
    left: 0;
    right: 0;
    z-index: 45;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    height: auto;
    padding: var(--sp-2);
    gap: 2px;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow-pop);
  }
  .shell[data-nav="open"] .topnav { display: flex; }
  .topnav-item {
    height: auto;
    padding: 10px var(--sp-3);
    border-bottom: none;
    border-radius: var(--radius);
  }
  .topnav-item.on { background: var(--accent-soft); }
  .topbar-right .search { width: 150px; }
  .topbar { padding: 0 var(--gutter); }
  .shell-kbd { display: none; }
  .shell-search .search-input { padding-right: 10px; }
  .switcher { max-width: 180px; }
  .ctxbar { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--sp-2); }
  .ctx-centre { justify-content: flex-end; }
  .ctx-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

@media (max-width: 639px) {
  .page { padding: var(--sp-4) var(--gutter) 56px; gap: var(--sp-4); }
  .cards { gap: var(--sp-4); }
  .split, .twocol { gap: var(--sp-4); }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .facts { grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
  .facts dt { margin-top: 10px; }
  .page-meta { gap: var(--sp-1) var(--sp-3); }
  .topbar-right .search { display: none; }
  .wordmark-text, .topbar-slash { display: none; }
  .switcher { max-width: 140px; }
  .changes-row { flex-wrap: wrap; }
  tr.tone-warn > td:first-child,
  tr.tone-bad > td:first-child,
  tbody tr.selected > td:first-child { padding-left: calc(var(--sp-3) - 1.5px); }
  .title-app { font-size: 24px; }
  .title-page { font-size: 20px; }
  .empty-hero h2 { font-size: 24px; }
  .card-body, .drawer-body, .drawer-head, .drawer-foot { padding: var(--sp-3); }
  .drawer-body > * + * { margin-top: var(--sp-4); }
  th, td { padding: 10px var(--sp-3); }
  .toasts { right: var(--sp-2); left: var(--sp-2); bottom: var(--sp-2); max-width: none; }
  .drawer { width: 100vw; border-left: none; }
  .popover {
    position: fixed;
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100vw;
    max-width: none;
    border-radius: var(--radius-card) var(--radius-card) 0 0;
  }
  .pop-form { min-width: 0; }
  pre.log, .logpanel { max-height: 55vh; font-size: 11.5px; }
  .log-line { grid-template-columns: 38px minmax(0, 1fr); }
  .log-line .log-time { display: none; }
  .page-actions { width: 100%; }
  .page-actions .pop-host, .page-actions .btn { width: 100%; }
  /* Four steps in 360px is four words nobody can read; the run stacks. */
  .stepper { flex-direction: column; gap: var(--sp-3); }
  .step { flex: none; width: 100%; }
  .step-bar { display: none; }
  .wizard-line { width: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .skel, .step.current .step-mark, .step.active .step-mark, .hrow-dot::after { animation: none; }
  .btn, .choice, .switch-track, .switch-knob, .radio-card { transition: none; }
}
