/* 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-bg: #fcfcfd;
  --code-ink: #1f2430;
  --code-ink-2: #8a93a3;
  --code-note: #1a6cf0;
  --code-fail: #fdeae7;

  /* 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%);

  /* 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;

    --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%);
  }
}

: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;

  --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%);
}

: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; }

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

/* Numbers line up between renders. */
table, .facts, .tile-value, .kpi-value, .mono, .nums, .pill, .health, .log-num {
  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: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-4);
  height: var(--topbar);
  padding: 0 var(--gutter);
  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 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);
}

/* ---- 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);
  margin: 0;
  font-size: 13.5px;
}
.facts dt { color: var(--ink-2); font-weight: 500; }
.facts dd { margin: 0; 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); }
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 3px
   back so a selected row's title sits on the same left edge as the others. */
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) - 3px);
}
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) - 3px);
}
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; }
.rail {
  position: sticky;
  top: calc(var(--topbar) + var(--ctxbar) + var(--sp-4));
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
}
.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); }

/* 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;
}
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; }
.search-input { width: 100%; padding-left: 34px; }
/* 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);
  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);
}
.switch input:checked + .switch-track { background: var(--accent); }
.switch input:checked + .switch-track .switch-knob { transform: translateX(16px); }
.switch input:disabled + .switch-track { opacity: 0.5; }
.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; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--radius-tag);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  text-transform: capitalize;
  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); }

/* 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-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
  background: var(--slate-soft);
  color: var(--slate);
}
.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; }

/* 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. */

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.7;
  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.nowrap { white-space: pre; overflow-wrap: normal; }
pre.log:empty::before {
  content: "waiting for output\2026";
  color: var(--code-ink-2);
}
/* 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); }

/* .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.7;
  max-height: min(62vh, 640px);
  overflow: auto;
  padding: var(--sp-2) 0;
}
.log-line {
  display: grid;
  grid-template-columns: 44px 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;
}
.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-line.fail { background: var(--code-fail); }
.log-line.fail .log-text { color: var(--bad); }
.log-line.note .log-text { color: var(--code-note); }
.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; }
.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;
}
.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%; }

.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;
}
.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; }

/* ---- 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; }
  .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; }
  tr.tone-warn > td:first-child,
  tr.tone-bad > td:first-child,
  tbody tr.selected > td:first-child { padding-left: calc(var(--sp-3) - 3px); }
  .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: 30px minmax(0, 1fr); }
  .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 { animation: none; }
  .btn, .choice, .switch-track, .switch-knob, .radio-card { transition: none; }
}
