/* Shebaka console — Railway-shaped layout, Shebaka palette. */

:root {
  --night: #080E14; --night-2: #0C161E; --panel: #0F1B24; --panel-2: #12212C;
  --line: #274540; --line-soft: #1A2E2E; --line-faint: #14232A;
  --brass: #D4A93C; --brass-hi: #EDCB6E; --brass-dim: rgba(212,169,60,0.14);
  --emerald: #34A87D;
  --ink: #F2EDE2; --ink-2: #B8B2A4; --ink-3: #7E8378;
  --ok: #34A87D; --warn: #D08736; --crit: #C4554D;
  --display: "Reem Kufi", "Gill Sans", sans-serif;
  --body: "IBM Plex Sans", -apple-system, "Segoe UI", sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, monospace;
  color-scheme: dark;
}

* { box-sizing: border-box; margin: 0; }

/* ── text size ───────────────────────────────────────────────────────── */
/* Every font size in this sheet is a rem, so one number here scales the whole
   console. The root is pinned to 16px rather than 100% on purpose: the default
   has to render byte-identically to what people already have, and 100% would
   quietly resize the console for anyone whose browser default is not 16
   (the console has always been px, so it has always ignored that setting).
   The Large and Larger settings are the deliberate path for bigger type
   (2026-08-31 UX feedback, item 37 — operator: nobody's console changes unless
   they choose). */
html { font-size: 16px; }
html[data-type="large"]  { font-size: 17.84px; }  /* 13px → 14.5px */
html[data-type="larger"] { font-size: 20.3px; }   /* 13px → 16.5px */

html, body { height: 100%; }
body { background: var(--night); color: var(--ink); font: 0.875rem/1.5 var(--body); }
a { color: var(--brass-hi); text-decoration: none; }
a:hover { text-decoration: underline; }
code { font-family: var(--mono); font-size: 0.78125rem; background: var(--night-2); padding: 2px 6px; border-radius: 4px; }
h1 { font-size: 1.375rem; font-weight: 600; letter-spacing: -0.2px; }
/* Card titles under a view h1 are h2 for heading order; same visual voice. */
h3, .card h2 { font-size: 0.8125rem; font-weight: 600; color: var(--ink-2); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 10px; }
h4 { font-size: 0.78125rem; font-weight: 600; color: var(--ink-2); margin: 16px 0 8px; }
.dim { color: var(--ink-3); font-size: 0.78125rem; }
[hidden] { display: none !important; }

/* ── layout ─────────────────────────────────────────────────────────── */
.shell { display: flex; height: 100vh; }
.main { flex: 1; overflow-y: auto; padding: 26px 34px; }
.view-canvas { display: flex; flex-direction: column; height: calc(100vh - 52px); }

/* ── sidebar ────────────────────────────────────────────────────────── */
.sidebar {
  width: 264px; flex-shrink: 0; display: flex; flex-direction: column;
  background: var(--night-2); border-right: 1px solid var(--line-faint);
  padding: 16px 12px 12px;
}
.brand { display: flex; align-items: center; gap: 9px; padding: 2px 10px 14px; font-weight: 600; font-size: 0.9375rem; }
.workspace {
  display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 8px;
  border: 1px solid var(--line-faint); background: var(--panel); margin-bottom: 14px;
  color: var(--ink);
}
.workspace:hover { text-decoration: none; border-color: var(--line); background: var(--panel-2); }
/* Account switcher. Only rendered when the signed-in identity belongs to more
   than one account — a single-account user keeps a plain link to Settings and
   never sees a menu that would have exactly one entry. */
.ws-wrap { position: relative; }
.ws-caret { margin-left: auto; opacity: 0.6; flex-shrink: 0; }
.workspace:hover .ws-caret { opacity: 1; }
.ws-menu {
  position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% - 10px);
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  padding: 4px; box-shadow: 0 8px 24px rgba(0,0,0,0.35);
}
.ws-menu button {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 7px 8px; border: 0; border-radius: 6px; background: none;
  color: var(--ink); font: inherit; font-size: 0.8125rem; cursor: pointer;
}
.ws-menu button:hover { background: var(--panel); }
.ws-menu button[aria-current="true"] { color: var(--brass-hi); }
.ws-menu .ws-menu-role { margin-left: auto; font-size: 0.6875rem; color: var(--dim); text-transform: uppercase; letter-spacing: 0.06em; }
.ws-menu hr { border: 0; border-top: 1px solid var(--line-faint); margin: 4px 2px; }
.ws-avatar, .sf-avatar {
  width: 30px; height: 30px; border-radius: 7px; flex-shrink: 0;
  background: var(--brass-dim); color: var(--brass-hi); font-weight: 600;
  display: flex; align-items: center; justify-content: center; font-size: 0.875rem;
}
.ws-name { font-weight: 600; font-size: 0.84375rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 160px; }
.ws-plan { font-size: 0.6875rem; color: var(--brass); text-transform: uppercase; letter-spacing: 0.08em; }
/* Trial pill: sits between the workspace block and the nav; warn/crit shift
   the palette as the credit runs down so the state is legible at a glance. */
.ws-trial {
  display: block; text-align: center; font-size: 0.71875rem; font-weight: 600;
  color: var(--brass-hi); background: var(--brass-dim);
  border: 1px solid rgba(212,169,60,0.45); border-radius: 99px;
  padding: 5px 10px; margin: -6px 2px 14px;
}
.ws-trial:hover { text-decoration: none; border-color: var(--brass); }
.ws-trial.warn { color: var(--warn); background: rgba(208,135,54,0.08); border-color: rgba(208,135,54,0.5); }
.ws-trial.crit { color: #E28680; background: rgba(196,85,77,0.1); border-color: rgba(196,85,77,0.5); }
.nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 7px;
  color: var(--ink-2); font-size: 0.84375rem; font-weight: 500;
}
.nav-item:hover { background: var(--panel); color: var(--ink); text-decoration: none; }
.nav-item.on { background: var(--brass-dim); color: var(--brass-hi); }
.nav-item svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; flex-shrink: 0; }
.nav-sep { height: 1px; background: var(--line-faint); margin: 10px 6px; }
.nav-ext { font-size: 0.78125rem; }
.side-foot {
  border-top: 1px solid var(--line-faint); padding-top: 10px; margin-top: 10px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.sf-user { display: flex; align-items: center; gap: 9px; min-width: 0; padding: 5px 7px; margin-left: -7px; border-radius: 7px; color: inherit; }
.sf-user:hover { background: var(--panel); text-decoration: none; }
.sf-avatar { width: 26px; height: 26px; font-size: 0.75rem; border-radius: 50%; }
.sf-meta { min-width: 0; font-size: 0.78125rem; }
.sf-meta div { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sf-logout { color: var(--ink-3); padding: 6px; border-radius: 6px; }
.sf-logout:hover { color: var(--crit); background: var(--panel); }
.sf-logout svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.5; display: block; }

/* ── common widgets ─────────────────────────────────────────────────── */
.btn {
  font: 600 0.8125rem var(--body); color: var(--ink); background: var(--panel-2);
  border: 1px solid var(--line-soft); border-radius: 7px; padding: 8px 14px; cursor: pointer;
}
.btn:hover { border-color: var(--line); }
.btn-sm { padding: 2px 8px; font-size: 0.75rem; }
.btn-brass { background: var(--brass); border-color: var(--brass); color: #14100A; }
.btn-brass:hover { background: var(--brass-hi); border-color: var(--brass-hi); }
/* Full-width login buttons. width:100% is required because a <button> is a
   form control that shrinks to its content even at display:block (unlike the
   <a> SSO link), which left it small and left-aligned next to the wide SSO
   button. box-sizing:border-box (global) keeps the padding inside the 100%. */
.btn-wide { display: block; width: 100%; text-align: center; }
.btn-danger { background: rgba(196,85,77,0.12); border-color: rgba(196,85,77,0.4); color: #E28680; }
.btn-danger:hover { background: rgba(196,85,77,0.22); }
.btn-danger-ghost { color: #E28680; }
.btn[disabled] { opacity: 0.5; cursor: default; }
/* DEVEX-17: a disabled destructive button reads clearly inert; an armed one is
   loud — so "Delete forever" never looks the same before and after confirmation. */
.btn-danger[disabled] { opacity: 0.4; background: none; border-color: var(--line-faint); color: var(--ink-3); }
.btn-danger:not([disabled]) { background: rgba(196,85,77,0.24); border-color: rgba(196,85,77,0.75); color: #F0A19B; font-weight: 600; }
/* M2 plan picker */
.plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.plan-card {
  border: 1px solid var(--line-faint); border-radius: 10px; padding: 14px;
  background: var(--panel-2); cursor: pointer; transition: border-color .12s;
}
.plan-card:hover { border-color: var(--line); }
.plan-card.current { border-color: var(--brass); box-shadow: inset 0 0 0 1px var(--brass); cursor: default; }
.plan-card.locked { opacity: 0.6; cursor: not-allowed; }
.plan-card h4 { margin: 0 0 4px; text-transform: capitalize; }
.plan-card .markup { color: var(--brass); font-weight: 600; font-size: 0.8125rem; }
.plan-card .blurb { color: var(--ink-3); font-size: 0.78125rem; margin-top: 6px; }
.plan-card .tag { font-size: 0.6875rem; color: var(--ink-3); display: block; margin-top: 6px; }
/* External-link glyph inside a button: the ↗ text glyph rendered hairline
   against btn-brass and read off-brand; the nav's own stroke icon, heavier. */
.btn .ext { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; vertical-align: -2px; margin-left: 4px; }
.icon-btn { background: none; border: 0; color: var(--ink-3); font-size: 0.9375rem; cursor: pointer; padding: 6px; border-radius: 6px; }
.icon-btn:hover { color: var(--ink); background: var(--panel-2); }

.view-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; gap: 14px; }
.view-head.slim { margin-bottom: 0; padding-bottom: 14px; }
.crumbs { display: flex; align-items: center; gap: 8px; font-size: 0.875rem; }
.crumbs a { color: var(--ink-3); }
.crumbs span:last-of-type { font-weight: 600; }
.env-pill {
  font-size: 0.6875rem; font-weight: 600; color: var(--emerald); border: 1px solid rgba(52,168,125,0.4);
  padding: 2px 9px; border-radius: 99px; margin-left: 6px;
}
.list-bar { color: var(--ink-3); font-size: 0.78125rem; margin-bottom: 14px; }

.card {
  background: var(--panel); border: 1px solid var(--line-faint); border-radius: 10px;
  padding: 16px 18px; margin-bottom: 16px;
}
.cols-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
/* Boxes in a two-column row line up at the bottom whatever their label's
   hint wraps to — "Dockerfile path (monorepos; blank = auto-detect)" used to
   push its input a line below "Port". */
.cols-2 > label { display: flex; flex-direction: column; justify-content: flex-end; }
.cols-2 > label > input, .cols-2 > label > select { margin-top: 5px; }
.tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px; }
.tile { background: var(--panel); border: 1px solid var(--line-faint); border-radius: 10px; padding: 14px 16px; }
.tile .v { font-size: 1.25rem; font-weight: 600; }
/* Cluster health on the Infrastructure view. The tile value is the only place
   a colour carries meaning on its own, so each state also reads as a word
   ("Ready", "Degraded") — the colour is reinforcement, never the message. */
.tile .v.v-ok { color: var(--ok); }
.tile .v.v-warn { color: var(--warn); }
.tile .v.v-crit { color: var(--crit); }
.tile .k { font-size: 0.71875rem; color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 2px; }

table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
th { text-align: left; color: var(--ink-3); font-size: 0.71875rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; padding: 6px 10px; border-bottom: 1px solid var(--line-faint); }
td { padding: 8px 10px; border-bottom: 1px solid var(--line-faint); }
tr:last-child td { border-bottom: 0; }
th.num, td.num { text-align: right; font-variant-numeric: tabular-nums; }
td.empty { color: var(--ink-3); text-align: center; padding: 22px; }
table.kv td:first-child { color: var(--ink-3); width: 40%; }
/* An editable value inside a key/value table. The row has to keep reading as
   a sentence — "0.5 vCPU · 0.5 GiB × 1" — so the field is sized to its
   content, not to the column, and sits quietly until it is hovered or
   focused. Inputs are width:100% by default here, which would turn each row
   into a form. */
table.kv .kv-in {
  width: auto; display: inline-block; vertical-align: baseline;
  padding: 3px 7px; border-radius: 5px; font-size: 0.8125rem;
  background: var(--night); border-color: var(--line-faint);
}
table.kv .kv-in:hover { border-color: var(--line-soft); }
table.kv .kv-in:focus { background: var(--night-2); border-color: var(--brass); }
table.kv .kv-num { width: 5.5ch; text-align: right; font-variant-numeric: tabular-nums; }
/* A port is up to five digits, so it does not share the size of a "0.5". */
table.kv .kv-port { width: 8ch; }
table.kv .kv-wide { width: 17ch; text-align: left; }
/* Only this table carries fields, so it gets the narrower key column — the
   40% default left "0.5 vCPU · 0.5 GiB × 1 replicas" wrapping mid-sentence.
   Scoped, so every other kv table keeps the shared proportion. */
#d-source td:first-child { width: 30%; }
table.kv .kv-in + .dim { margin-left: 6px; }
/* The Apply footer exists only while something is edited; while it is hidden
   the row above it keeps the table's flat bottom edge. */
table.kv tr.kv-apply td { border-bottom: 0; padding-top: 12px; }
table.kv tr.kv-apply .dim { margin: 8px 0 0; }
table.kv tr:has(+ tr.kv-apply[hidden]) td { border-bottom: 0; }

input, textarea, select {
  width: 100%; background: var(--night-2); color: var(--ink); border: 1px solid var(--line-soft);
  border-radius: 7px; padding: 8px 11px; font: 0.84375rem var(--body);
}
textarea { font-family: var(--mono); font-size: 0.78125rem; }
input:focus, textarea:focus { outline: none; border-color: var(--brass); }
/* Keyboard users get an unmistakable focus ring; pointer users are unaffected. */
:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
input:focus-visible, textarea:focus-visible, select:focus-visible { outline-offset: 0; }
label { display: block; font-size: 0.78125rem; color: var(--ink-2); margin-bottom: 12px; }
label input, label textarea { margin-top: 5px; }
.row-flex { display: flex; align-items: center; gap: 10px; margin: 10px 0; flex-wrap: wrap; }
.check { display: flex; align-items: center; gap: 6px; font-size: 0.78125rem; color: var(--ink-2); }
.check input { width: auto; }

.snippet {
  background: var(--night-2); border: 1px solid var(--line-faint); border-radius: 8px;
  padding: 12px 14px; font: 0.75rem/1.7 var(--mono); overflow-x: auto; color: var(--ink-2);
}
.log {
  background: #060A0E; border: 1px solid var(--line-faint); border-radius: 8px;
  padding: 12px 14px; font: 0.71875rem/1.6 var(--mono); overflow: auto; color: var(--ink-2);
  max-height: 340px; min-height: 120px; white-space: pre-wrap; word-break: break-all;
}
.notice {
  border: 1px solid var(--line-soft); border-left: 3px solid var(--brass); border-radius: 8px;
  background: var(--panel); padding: 12px 16px; font-size: 0.8125rem; color: var(--ink-2); margin-bottom: 16px;
}
.notice.warn { border-left-color: var(--warn); }
.notice.err { border-left-color: var(--crit); color: #E2A19C; }

.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; background: var(--ink-3); flex-shrink: 0; }
.status-dot.online { background: var(--ok); box-shadow: 0 0 6px rgba(52,168,125,0.7); }
.status-dot.degraded { background: var(--warn); }
/* Running, but not the build the primary is on — healthy-looking and wrong. */
.status-dot.drifted { background: var(--warn); box-shadow: 0 0 0 2px rgba(210,153,34,0.35); }
.status-dot.offline { background: var(--crit); }
.status-dot.building { background: var(--brass); animation: pulse 1.2s infinite; }
/* pods ready, URL dead — distinct from offline, and never green */
.status-dot.unreachable { background: var(--warn); box-shadow: 0 0 0 2px rgba(210,153,34,0.35); }
.warn-banner { margin: 0; padding: .6rem 1rem; background: rgba(210,153,34,0.12);
  border-left: 3px solid var(--warn); color: var(--ink-1); font-size: .9rem; }
@keyframes pulse { 50% { opacity: 0.4; } }

/* Build phase, in words, on the service node (DEVEX 31). Brass because it is
   the accent that already means "the platform is doing something" — the dot
   above it pulses in the same hue, so the two read as one signal. */
.sn-building {
  margin-top: 6px; font-size: 0.71875rem; color: var(--brass-hi);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Deploys in flight, sidebar. Sits with the trial pill as a persistent
   account-level signal rather than a toast that expires. */
.ws-deploys {
  display: block; margin: 0 0 14px; padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--line-faint); background: var(--panel);
  color: var(--brass-hi); font-size: 0.75rem; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ws-deploys:hover { text-decoration: none; border-color: var(--line); background: var(--panel-2); }
.ws-deploys::before {
  content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--brass); margin-right: 7px; vertical-align: middle;
  animation: pulse 1.2s infinite;
}
@media (prefers-reduced-motion: reduce) {
  .status-dot.building, .status-dot.going, .ws-deploys::before { animation: none; }
}

/* ── projects grid ──────────────────────────────────────────────────── */
.projects-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 16px; }
.project-card {
  display: block; color: inherit; /* it is an <a>: keyboard, cmd-click, bookmark */
  background: var(--panel); border: 1px solid var(--line-faint); border-radius: 12px;
  padding: 14px; cursor: pointer; transition: border-color 0.15s;
}
.project-card:hover { border-color: var(--line); text-decoration: none; }
.project-card h2 { font-size: 0.9375rem; font-weight: 600; margin-bottom: 10px; }
/* The net (شبكة): crossed diagonal threads with knots at the crossings. */
/* One exact 32px tile: diagonals corner-to-corner, brass knots ON the
   crossings (the old gradient layers drifted — dots tiled at 31.1px against
   a 22√2 ≈ 31.113px crossing pitch). Crossings sit at every (16i,16j) with
   i+j even, so anything positioned on a 32px grid lands its corners on
   knots — the canvas layout below is built around that cell. */
.net-bg {
  background-color: var(--night-2);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M0 0L32 32M0 32L32 0' stroke='rgba(39,69,64,0.30)' stroke-width='1' fill='none'/%3E%3Cg fill='rgba(212,169,60,0.16)'%3E%3Ccircle cx='16' cy='16' r='1.5'/%3E%3Ccircle cx='0' cy='0' r='1.5'/%3E%3Ccircle cx='32' cy='0' r='1.5'/%3E%3Ccircle cx='0' cy='32' r='1.5'/%3E%3Ccircle cx='32' cy='32' r='1.5'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 32px 32px;
  background-position: 0 0;
}
.mini-canvas {
  height: 150px; border-radius: 8px; border: 1px solid var(--line-faint); position: relative;
  display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; padding: 10px;
}
.mini-node {
  width: 44px; height: 44px; border-radius: 10px; background: var(--panel-2);
  border: 1px solid var(--line-soft); display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 0.9375rem; color: var(--brass-hi);
}
.project-foot { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 0.78125rem; color: var(--ink-3); }

/* ── project canvas ─────────────────────────────────────────────────── */
.canvas {
  flex: 1; border: 1px solid var(--line-faint); border-radius: 12px; position: relative; overflow: auto;
  /* padding and gap are lattice cells (32px) so node corners land on the
     net's crossings; node width/height snap to the same cell. */
  padding: 32px; min-height: 380px;
  display: flex; align-items: flex-start; gap: 32px; flex-wrap: wrap; align-content: flex-start;
}
/* The empty canvas is an instruction, not an error state: centred, quiet, and
   it names the gesture (right-click) that the lattice gives no other hint of. */
.canvas-empty { margin: auto; max-width: 34em; text-align: center; }
.canvas-empty p { margin: 0 0 .4em; }
.canvas-edges { position: absolute; top: 0; left: 0; pointer-events: none; }
.canvas-edges path { stroke: var(--brass); stroke-width: 1.5; fill: none; opacity: 0.55; }
.canvas-edges circle { fill: var(--brass); opacity: 0.8; }
.service-node {
  font: inherit; text-align: left; color: inherit; /* it is a <button> */
  width: 256px; /* 8 lattice cells — corners stay on crossings */
  background: var(--panel); border: 1px solid var(--line-soft); border-radius: 12px;
  padding: 14px; cursor: pointer; transition: border-color 0.15s, transform 0.1s;
  position: relative; z-index: 1;
}
.service-node:hover { border-color: var(--brass); transform: translateY(-1px); }
/* Free-layout mode: once any node has been dragged, every node is pinned
   absolutely (flow positions frozen first, so nothing jumps). Width stays
   the 8-cell 256px so corners keep landing on lattice crossings. */
.canvas.free .service-node { position: absolute; margin: 0; }
.service-node.dragging { z-index: 5; border-color: var(--brass); cursor: grabbing; transform: none; }
/* Corner ports: the knots a wire is dragged from. Sit exactly on the node's
   corners (the lattice crossings), invisible until the node is hovered. */
.sn-port {
  position: absolute; width: 14px; height: 14px; margin: -7px; border-radius: 50%;
  background: var(--brass); opacity: 0; cursor: crosshair; z-index: 6;
  box-shadow: 0 0 0 3px rgba(212,169,60,0.18); transition: opacity .15s;
}
.sn-port-tl { left: 0; top: 0; } .sn-port-tr { right: 0; top: 0; margin-right: -7px; }
.sn-port-bl { left: 0; bottom: 0; margin-bottom: -7px; } .sn-port-br { right: 0; bottom: 0; margin-right: -7px; margin-bottom: -7px; }
.service-node:hover .sn-port, .sn-port:hover { opacity: 0.9; }
.service-node.wire-target { border-color: var(--brass-hi); box-shadow: 0 0 0 1px rgba(212,169,60,0.35), 0 0 18px rgba(212,169,60,0.2); }
/* ── in flight: what the platform is doing, on the node it is doing to ──
   Reported 2026-09-02: "whenever i delete, add, connect or do any process on
   the canvas there is no indication that a process is running unless you look
   for it in the side panel." Three states, and the difference between them is
   the whole point — a placeholder is NOT YET, busy is BEING CHANGED, deleting
   is GOING. The words are on the node (.sn-building); this is the shape that
   makes them legible from across the canvas. */

/* Being made. It wears the SAME busy state a redeploy does (operator,
   2026-09-02) — the platform is doing the same work, and the only difference
   is that this one has no address yet, which is why its address line is
   absent rather than guessed.

   A create that FAILED is the exception, and keeps the dashed outline: it
   never became a service and is not going to, so it must not read as one. */
.service-node.ghost {
  border-style: dashed; border-color: var(--crit); background: var(--night-2);
  cursor: default;
}
.service-node.ghost:hover { transform: none; border-color: var(--crit); }
.service-node.ghost .sn-name { color: var(--ink-2); }
.service-node.ghost.failed .sn-building { color: #E28680; white-space: normal; }
.sn-ghost-actions { display: flex; gap: 8px; margin-top: 8px; }

/* Being changed. The node keeps its shape — it is still a real service — and
   gains a brass thread across its top edge that travels while the job runs. */
.service-node.busy { border-color: var(--brass); }
.service-node.busy::after {
  content: ''; position: absolute; left: 0; right: 0; top: -1px; height: 2px;
  border-radius: 12px 12px 0 0; overflow: hidden;
  background: linear-gradient(90deg, transparent 0%, var(--brass-hi) 50%, transparent 100%);
  background-size: 45% 100%; background-repeat: no-repeat;
  animation: sn-sweep 1.5s linear infinite;
}
@keyframes sn-sweep { from { background-position: -45% 0; } to { background-position: 145% 0; } }

/* On its way out. Dimmed and struck through, and its ports go with it: a wire
   dragged to a namespace being torn down is a build failure minutes later
   with no visible relation to the delete (ADR-009's dangling link). */
.service-node.deleting { opacity: 0.55; border-color: rgba(196,85,77,0.5); }
.service-node.deleting::after { background-image: linear-gradient(90deg, transparent 0%, #E28680 50%, transparent 100%); }
.service-node.deleting .sn-name { text-decoration: line-through; }
.service-node.deleting .sn-building { color: #E28680; }
.service-node.deleting .sn-port { display: none; }
/* Going, not building: the same pulse would say "we are making this". */
.status-dot.going { background: var(--crit); animation: pulse 1.2s infinite; }

.canvas-wire { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 7; }
.canvas-wire path { stroke: var(--brass-hi); stroke-width: 1.5; stroke-dasharray: 6 5; fill: none; }
.canvas-edges path.connecting { opacity: 0.95; stroke: var(--brass-hi); }
.canvas-edges .wire-light { fill: var(--brass-hi); filter: drop-shadow(0 0 6px rgba(237,203,110,0.9)); }

/* 24 h CPU / RAM sparklines under the live usage line (metrics_history.go). */
.sparks { display: flex; flex-wrap: wrap; gap: 14px 24px; margin: 6px 0 10px; }
.spark { display: flex; flex-direction: column; gap: 3px; font-size: 0.71875rem; color: var(--ink-3); }
.spark svg { display: block; background: var(--night-2); border: 1px solid var(--line-faint); border-radius: 6px; }
.spark polyline { fill: none; stroke: var(--brass); stroke-width: 1.5; stroke-linejoin: round; }
.spark b { color: var(--ink-2); font-weight: 600; }

/* Templates: a card per approved stack. */
.tpl-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; margin: 4px 0 14px; }
.tpl-card {
  font: inherit; text-align: left; color: inherit; cursor: pointer; display: flex; flex-direction: column; gap: 4px;
  padding: 12px 14px; border-radius: 10px; border: 1px solid var(--line-soft); background: var(--panel);
}
.tpl-card:hover { border-color: var(--brass); }
.tpl-card.on { border-color: var(--brass); box-shadow: 0 0 0 1px rgba(212,169,60,0.25); }
.tpl-card b { font-size: 0.875rem; }
.tpl-card span { font-size: 0.78125rem; color: var(--ink-2); }
.tpl-card code { font-size: 0.6875rem; }

/* Right-click menu on the canvas: plain buttons, no native menu. */
.ctx-menu {
  position: fixed; z-index: 70; min-width: 220px; padding: 4px; border-radius: 8px;
  background: var(--panel-2); border: 1px solid var(--line); box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.ctx-menu .ctx-title { font-size: 0.6875rem; color: var(--ink-3); padding: 6px 10px 4px; text-transform: uppercase; letter-spacing: .06em; }
.ctx-menu button {
  display: block; width: 100%; text-align: left; font: inherit; color: var(--ink);
  background: none; border: 0; border-radius: 6px; padding: 7px 10px; cursor: pointer;
}
.ctx-menu button:hover, .ctx-menu button:focus-visible { background: var(--brass-dim); outline: none; }
.ctx-menu button[disabled] { color: var(--ink-3); cursor: default; }
.ctx-menu button.danger { color: #E28680; }
/* ── image palette ──────────────────────────────────────────────────── */
/* Same floating body as the context menu it grows out of, one step wider so a
   repository name and its description fit on one line. */
.img-palette {
  position: fixed; z-index: 72; width: 420px; max-width: calc(100vw - 24px);
  /* It grows when a result is chosen and the form appears, so it needs both a
     ceiling and its own scroll — otherwise the Deploy button lands below the
     fold on a short viewport (found in the browser, 2026-09-01). */
  max-height: calc(100vh - 24px); overflow: auto;
  padding: 10px; border-radius: 10px;
  background: var(--panel-2); border: 1px solid var(--line); box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.img-palette .ip-search { display: flex; align-items: center; gap: 8px; }
.img-palette .ip-search input { margin: 0; flex: 1; }
.ip-source { font-size: 0.6875rem; white-space: nowrap; }
.ip-results { list-style: none; margin: 8px 0 0; padding: 0; max-height: 46vh; overflow: auto; }
.ip-results:empty { margin: 0; }
.ip-results button {
  display: block; width: 100%; text-align: left; font: inherit; color: var(--ink);
  background: none; border: 0; border-radius: 6px; padding: 7px 10px; cursor: pointer;
}
.ip-results button:hover, .ip-results button:focus-visible { background: var(--brass-dim); outline: none; }
.ip-results .ip-name { font-weight: 600; font-size: 0.8125rem; }
.ip-results .ip-official { color: var(--brass-hi); font-size: 0.6875rem; margin-left: .4em; }
.ip-results .ip-desc {
  font-size: 0.75rem; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ip-note { font-size: 0.75rem; margin: 8px 2px 0; }
.ip-note:empty { display: none; }
.ip-config { margin-top: 8px; }
.ip-chosen { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 0.8125rem; }
.ip-chosen b { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.btn.btn-tiny { padding: 2px 8px; font-size: 0.6875rem; }
/* ── compose review rows ────────────────────────────────────────────── */
.compose-rows { max-height: 52vh; overflow: auto; margin-bottom: 12px; }
.compose-row {
  border: 1px solid var(--line-faint); border-radius: 10px; padding: 12px 14px; margin-bottom: 10px;
}
.compose-row > header {
  display: flex; align-items: center; gap: 10px; margin-bottom: 10px;
}
.compose-row .cr-name { font-weight: 600; font-size: 0.875rem; }
.compose-row .cr-src { font-size: 0.75rem; color: var(--ink-3); flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.compose-row .cr-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
@media (max-width: 640px) { .compose-row .cr-grid { grid-template-columns: repeat(2, 1fr); } }
.compose-row label { margin: 0; font-size: 0.75rem; }
.compose-row .cr-notes { margin: 10px 0 0; padding-left: 1.1em; font-size: 0.75rem; color: var(--ink-3); }
.compose-row .cr-notes li { margin: 2px 0; }
.compose-row .cr-links { font-size: 0.75rem; color: var(--ink-3); margin: 8px 0 0; }
.compose-row .cr-managed { font-size: 0.75rem; margin: 8px 0 0; }
.sn-head { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
.sn-icon {
  width: 30px; height: 30px; border-radius: 8px; background: var(--brass-dim); color: var(--brass-hi);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.sn-icon svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.5; }
.sn-name { font-weight: 600; font-size: 0.875rem; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sn-domain { font-size: 0.75rem; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sn-meta { display: flex; align-items: center; gap: 7px; margin-top: 9px; font-size: 0.71875rem; color: var(--ink-3); }

/* ── drawer ─────────────────────────────────────────────────────────── */
.drawer-scrim, .modal-scrim { position: fixed; inset: 0; background: rgba(4,8,12,0.6); z-index: 40; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(480px, 94vw); z-index: 41;
  background: var(--night-2); border-left: 1px solid var(--line-faint);
  display: flex; flex-direction: column; box-shadow: -18px 0 40px rgba(0,0,0,0.4);
}
.drawer-head { display: flex; align-items: flex-start; justify-content: space-between; padding: 18px 20px 12px; }
.drawer-title { display: flex; align-items: center; gap: 9px; font-size: 1.0625rem; font-weight: 600; }
.drawer-url { font-size: 0.78125rem; }
/* Private-cluster marker (privateedge.go): the URL will ask for a sign-in. */
/* DR — the two clouds and the light between them (renderDRTab / drMap).
   Brass on the serving side, the same pulse the homepage lattice walks;
   while traffic moves the light crosses the link until the gateway confirms
   the move. Everything the map says is also in the table under it. */
.dr-map { display: grid; grid-template-columns: 1fr 72px 1fr; align-items: center; gap: 0; margin: 4px 0 14px; }
.dr-node {
  position: relative; display: flex; flex-direction: column; gap: 1px; min-width: 0;
  padding: 10px 12px 11px; border: 1px solid var(--line-soft); border-radius: 10px; background: var(--panel);
  transition: border-color .4s ease, box-shadow .4s ease, opacity .4s ease;
}
.dr-node .dr-node-head { display: flex; justify-content: space-between; align-items: center; gap: 6px; margin-bottom: 2px; }
.dr-node .dr-role { font-size: 0.65625rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dr-node b { font-size: 0.84375rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dr-node .dr-region { font-size: 0.75rem; color: var(--ink-2); }
.dr-node .dr-badge {
  flex-shrink: 0; font-size: 0.625rem; line-height: 1; padding: 3px 6px; white-space: nowrap;
  border-radius: 999px; border: 1px solid var(--line-soft); color: var(--ink-3); background: var(--night-2);
}
.dr-node.serving { border-color: var(--brass); box-shadow: 0 0 0 1px rgba(212,169,60,0.22), 0 0 22px rgba(212,169,60,0.16); }
.dr-node.serving .dr-badge { border-color: var(--brass); color: var(--brass-hi); background: var(--brass-dim); }
.dr-node.leaving { opacity: 0.55; }
/* a cluster a cutover left behind: its copy is stale, never to be reattached */
.dr-node.fenced { border-style: dashed; border-color: rgba(196,85,77,0.55); opacity: 0.8; }
.dr-node.fenced .dr-badge { border-color: rgba(196,85,77,0.55); color: #E28680; }
.dr-phase { margin: -8px 0 14px; }
.dr-link { position: relative; height: 2px; margin: 0 -1px; background: linear-gradient(90deg, var(--line-soft), var(--line), var(--line-soft)); }
.dr-light {
  position: absolute; top: 50%; left: 0; width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 50%;
  background: var(--brass-hi); box-shadow: 0 0 6px 2px rgba(237,203,110,0.55), 0 0 18px 6px rgba(212,169,60,0.28);
  opacity: 0.9;
}
/* the short trail the hero pulse leaves behind its head */
.dr-light::before { content: ''; position: absolute; top: 50%; height: 2px; width: 26px; margin-top: -1px; opacity: 0; }
.dr-map.settled.to-right .dr-light { left: 100%; }
.dr-map.settled.to-left  .dr-light { left: 0; }
.dr-map.moving.to-right .dr-light { animation: dr-travel-right 1.5s cubic-bezier(0.45, 0, 0.55, 1) infinite; }
.dr-map.moving.to-left  .dr-light { animation: dr-travel-left  1.5s cubic-bezier(0.45, 0, 0.55, 1) infinite; }
.dr-map.moving.to-right .dr-light::before { right: 100%; opacity: 1; background: linear-gradient(90deg, rgba(212,169,60,0), rgba(212,169,60,0.8)); }
.dr-map.moving.to-left  .dr-light::before { left: 100%; opacity: 1; background: linear-gradient(90deg, rgba(212,169,60,0.8), rgba(212,169,60,0)); }
.dr-map.moving .dr-link { background: linear-gradient(90deg, rgba(212,169,60,0.35), rgba(212,169,60,0.15), rgba(212,169,60,0.35)); }
@keyframes dr-travel-right { 0% { left: 0; opacity: 0; } 12% { opacity: 1; } 88% { opacity: 1; } 100% { left: 100%; opacity: 0; } }
@keyframes dr-travel-left  { 0% { left: 100%; opacity: 0; } 12% { opacity: 1; } 88% { opacity: 1; } 100% { left: 0; opacity: 0; } }
.private-pill.serving-pill { border-color: var(--brass); color: var(--brass-hi); background: var(--brass-dim); }

.private-pill { display: inline-block; font-size: 0.6875rem; line-height: 1; padding: 4px 7px; border-radius: 999px; border: 1px solid var(--line); color: var(--ink-2); background: var(--bg-2, transparent); white-space: nowrap; margin-left: 6px; vertical-align: middle; }
.sn-domain .lock { font-size: 0.6875rem; }
/* DR badge on a canvas node: which services carry a warm standby, without
   opening the drawer. Brass when failed over — that one is about where
   traffic is right now, not a setting. */
.sn-dr { font-size: 0.625rem; line-height: 1; padding: 3px 6px; border-radius: 999px; border: 1px solid var(--line); color: var(--ink-2); white-space: nowrap; flex: none; }
.sn-dr-over { border-color: var(--brass); color: var(--brass-hi); background: var(--brass-dim); }
.tabs { display: flex; gap: 2px; padding: 0 14px; border-bottom: 1px solid var(--line-faint); }
.tabs button {
  background: none; border: 0; color: var(--ink-3); font: 600 0.8125rem var(--body);
  padding: 9px 12px; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs button:hover { color: var(--ink); }
.tabs button.on { color: var(--brass-hi); border-bottom-color: var(--brass); }
.drawer-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
.deploy-current { background: var(--panel); border: 1px solid var(--line-faint); border-radius: 8px; padding: 12px 14px; font-size: 0.78125rem; margin-bottom: 12px; }
.deploy-current div { margin: 2px 0; color: var(--ink-2); }
.deploy-current b { color: var(--ink); }
.history { list-style: none; padding: 0; }
.history li { font: 0.75rem var(--mono); color: var(--ink-3); padding: 6px 0; border-bottom: 1px solid var(--line-faint); overflow-wrap: anywhere; }
/* A deploy in flight: brass-lit row with the shared spinner. */
.history li.deploy-live { color: var(--brass-hi); }
.history li.deploy-live .spinner { width: 11px; height: 11px; }
.domains { list-style: none; padding: 0; margin-bottom: 8px; }
.domains li { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0; font-size: 0.8125rem; border-bottom: 1px solid var(--line-faint); }
/* Drawer Settings grouping (DEVEX 28). The panel carried eight <h4> sections
   at identical weight, so finding one control meant reading all of them. The
   legend is the scan target and the rule above it does the separating —
   deliberately a hairline and space rather than a card, because nesting a card
   inside the drawer panel would add a box without adding meaning. */
.dgroup-legend {
  margin: 30px 0 4px; padding-top: 14px; border-top: 1px solid var(--line-faint);
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-3);
}
.dgroup-legend:first-of-type { margin-top: 4px; padding-top: 0; border-top: 0; }
/* Items sit tighter under their legend than the groups sit apart. */
.dgroup-legend + h4 { margin-top: 10px; }

/* Danger is separated by distance, not decoration: a wider gap than any
   group boundary, so it cannot be mistaken for the next section down. */
.dgroup-danger { margin-top: 40px; padding-top: 18px; border-top: 1px solid var(--line-faint); }
.danger-h { color: #E28680; margin-top: 0; }
#d-env { width: 100%; }

/* Post-deploy outcome (DEVEX 29). The build-done state used to present the
   URL, the webhook note, the timeline and the log at one weight; the URL is
   the news and everything else is the record, so the news gets the room. */
.build-outcome {
  margin: 0 0 12px; padding: 14px 16px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--panel);
}
.build-outcome-label {
  margin: 0 0 4px; font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-3);
}
.build-outcome-url {
  display: block; font-size: 1rem; font-weight: 600; color: var(--brass-hi);
  word-break: break-all; line-height: 1.35;
}
.build-outcome-url:hover { text-decoration: underline; }
/* The diagnosis, when we have one. Styled as the headline of a failure the
   way .build-outcome is the headline of a success: the person's next action is
   in here, and the log below is the evidence for it. */
.build-diag {
  margin: 0 0 12px; padding: 14px 16px; border-radius: 9px;
  border: 1px solid var(--line); border-left: 3px solid var(--crit);
  background: var(--panel);
}
.build-diag-title {
  margin: 0 0 6px; font-size: 0.875rem; font-weight: 600; color: var(--ink-1);
  line-height: 1.4;
}
.build-diag-detail { margin: 0 0 10px; font-size: 0.78125rem; color: var(--ink-2); line-height: 1.55; }
.build-diag-detail:last-child { margin-bottom: 0; }
/* A log you cannot copy is a log you cannot get help with: the header carries
   the one control that turns a wall of docker output into a support message. */
.log-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 6px;
}
/* Once the build lands the log is history, not the headline — a fade, nothing
   more.
   Until 2026-09-07 this rule also capped the box at 190px and eased that cap
   over 350ms, meaning "shrink the log when the build settles". It never did
   that. `.settled` is only ever applied to #build-log (both classList sites
   in app.js are that element), and the ID rule further down sets the same cap
   to 46vh — an ID selector outranks this one on specificity, and always has.
   The shrink was dead on arrival; only the dimming was ever visible.
   So the eased cap was a LAYOUT animation of a value that could not change:
   a relayout every frame, buying nothing. Both are gone.
   Reaching for a grid row track instead — the usual advice for that warning —
   would not have helped either, because that is a layout value too. It is
   recommended for its ability to ease to an intrinsic size, which was never
   what this needed. No box size eases off the main thread.
   If the shrink is actually wanted, that is a deliberate design change and a
   new ID-strength rule, not a revert of this one. */
.log.settled { opacity: 0.82; transition: opacity .35s ease; }

/* ── modal ──────────────────────────────────────────────────────────── */
.modal-scrim { display: flex; align-items: flex-start; justify-content: center; padding: 8vh 20px; z-index: 50; overflow-y: auto; }
.modal {
  width: min(560px, 100%); background: var(--night-2); border: 1px solid var(--line-soft);
  border-radius: 14px; padding: 20px 24px;
}
/* The compose review is a table of decisions, not a form: it needs the width
   a name, a kind, a port and a disk take side by side. */
.modal.wide { width: min(760px, 100%); }
.modal header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.modal h2 { font-size: 1.0625rem; }
.modal details { margin-bottom: 12px; }
.modal summary { cursor: pointer; color: var(--ink-3); font-size: 0.78125rem; margin-bottom: 8px; }
.modal-actions { margin-top: 14px; }
.modal-tabs { margin: -6px 0 16px; padding: 0; }
.build-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; font-weight: 600; }
.spinner {
  width: 14px; height: 14px; border: 2px solid var(--line); border-top-color: var(--brass);
  border-radius: 50%; animation: spin 0.8s linear infinite; flex-shrink: 0;
}
.spinner.done { border-color: var(--ok); animation: none; }
.spinner.fail { border-color: var(--crit); animation: none; }
@keyframes spin { to { transform: rotate(360deg); } }
/* The build log gets the viewport-relative cap while it IS the subject, and a
   short fixed strip once it is history. Both live here, at ID strength, and
   that is the point: `.log.settled` up in the log block cannot reach this
   element — an ID selector outranks it — which is why the shrink it asked for
   silently never happened between whenever 46vh landed and 2026-09-07.
   The change is deliberately instant, not eased. Easing a box's cap is a
   layout animation: relaid out every frame, and a finished log is routinely
   thousands of lines. The fade on `.log.settled` carries the transition; the
   size just lands. (app.js settleLog re-pins the scroll, because shrinking
   the viewport moves the bottom out from under a log that was following it.) */
#build-log { max-height: 46vh; }
#build-log.settled { max-height: 190px; }

/* ── onboarding, login, misc ────────────────────────────────────────── */
.onboard { max-width: 560px; margin: 8vh auto 0; text-align: center; }
.onboard h2 { font-size: 1.25rem; margin-bottom: 10px; }
.onboard p { color: var(--ink-2); }
.onboard-actions { margin: 20px 0 34px; }
/* First-run stepper: the step that wants attention is full-strength ("now"),
   finished steps keep their brightness with a green check, the rest wait dim. */
.ob-steps { list-style: none; margin: 26px 0 30px; padding: 0; text-align: left; }
.ob-step { display: flex; gap: 14px; padding: 14px 2px; border-top: 1px solid var(--line-faint); opacity: 0.55; }
.ob-step:last-child { border-bottom: 1px solid var(--line-faint); }
.ob-step.now, .ob-step.done { opacity: 1; }
.ob-num {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; margin-top: 1px;
  border: 1px solid var(--line-soft); color: var(--ink-2);
  display: flex; align-items: center; justify-content: center;
  font: 600 0.78125rem var(--body);
}
.ob-step.now .ob-num { border-color: var(--brass); color: var(--brass-hi); background: var(--brass-dim); }
.ob-step.done .ob-num { border-color: var(--ok); color: var(--ok); }
.ob-body h3 { margin: 3px 0 4px; font-size: 0.875rem; }
.ob-body p { margin: 0; font-size: 0.8125rem; }
/* Flex with a real row gap: the connect buttons plus the "or skip ahead"
   hint wrap on narrow mains, and inline flow left the wrapped line touching
   the buttons above it. */
.ob-actions { margin-top: 10px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
.ob-actions .dim { flex: 1 1 16em; min-width: 0; }
.ob-actions:empty { display: none; }
.onboard-cli { text-align: left; background: var(--panel); border: 1px solid var(--line-faint); border-radius: 12px; padding: 18px 20px; }
.onboard-cli h3 { margin-bottom: 10px; }
.onboard-cli .dim { margin-top: 10px; }

#login-overlay { position: fixed; inset: 0; background: var(--night); z-index: 100; display: flex; align-items: center; justify-content: center; }
.login-card { width: min(400px, 92vw); text-align: center; }
.login-card p { color: var(--ink-2); margin: 14px 0 22px; }
.login-foot { font-size: 0.75rem; color: var(--ink-3); margin-top: 16px; }
.wordmark { display: flex; align-items: center; justify-content: center; gap: 12px; font-size: 1.625rem; font-weight: 600; }
.wordmark .ar { font-family: var(--display); color: var(--brass); }

.play-out { min-height: 120px; background: var(--night-2); border: 1px solid var(--line-faint); border-radius: 8px; padding: 12px 14px; font-size: 0.84375rem; color: var(--ink-2); margin-top: 12px; white-space: pre-wrap; }
.chat-row { display: flex; gap: 10px; }
.chat-row input { flex: 1; }

.toasts { position: fixed; bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 8px; z-index: 60; }
.toast {
  background: var(--panel-2); border: 1px solid var(--line-soft); border-left: 3px solid var(--brass);
  border-radius: 8px; padding: 11px 16px; font-size: 0.8125rem; max-width: 360px;
  animation: slidein 0.18s ease-out;
}
.toast.ok { border-left-color: var(--ok); }
.toast.err { border-left-color: var(--crit); }
@keyframes slidein { from { transform: translateX(30px); opacity: 0; } }

@media (max-width: 900px) {
  .sidebar { width: 68px; padding: 14px 8px; }
  .sidebar .brand span, .ws-meta, .nav-item :not(svg), .sf-meta { display: none; }
  .nav-item { justify-content: center; font-size: 0; gap: 0; }
  .cols-2, .tiles { grid-template-columns: 1fr; }
}

/* Motion here is all status signalling — a pulsing dot, a build spinner, a
   toast sliding in. Under reduced motion the signal has to survive without the
   movement, so each one holds its meaning statically rather than disappearing:
   the dot keeps its colour, the spinner keeps its ring, the toast still lands.
   Mirrors site.css — DESIGN.md treats a divergence between them as a defect. */
@media (prefers-reduced-motion: reduce) {
  .status-dot.building, .status-dot.going { animation: none; }
  /* A frozen gradient sits at its start position and reads as a progress bar
     stuck at 45% (browser, 2026-09-02). Flat and half-lit instead: still
     "something is happening", with nothing that looks like a measurement. */
  .service-node.busy::after { animation: none; background: var(--brass); opacity: 0.5; }
  .service-node.deleting::after { background: #E28680; }
  .spinner { animation: none; border-top-color: var(--brass); }
  .toast { animation: none; }
  /* the crossing holds still at the destination; the words say it is moving */
  .dr-map.moving .dr-light { animation: none; }
  .dr-map.moving.to-right .dr-light { left: 100%; }
  .dr-map.moving.to-left .dr-light { left: 0; }
  .dr-node { transition: none; }
  .canvas-edges .wire-light { display: none; } /* the connecting edge stays brighter; the light stays still */
  * { scroll-behavior: auto; }
}

/* Source picker in the New-project modal: one tab per connected code host,
   plus a manual Git URL. Sits inside the modal body rather than at its head,
   so it needs its own inset — .tabs is padded for the modal header. */
.source-tabs { padding: 0; margin: 0 0 12px; }
.source-tabs button { padding: 7px 12px; font-size: 0.8125rem; }
.source-tabs .dim { font-size: 0.6875rem; }

/* A connected code host in Settings → Git providers. */
.git-conn {
  padding: 10px 12px;
  border: 1px solid var(--line-faint);
  border-radius: 8px;
  margin: 8px 0;
}
.git-conn .dim { font-size: 0.75rem; margin-top: 2px; }
/* The repo note follows a .cols-2 row and precedes another; without its own
   bottom margin it reads as a label for the row below it. Both pickers
   (the New-service wizard and the compose importer) have the same problem. */
#n-repo-note, #compose-repo-note { margin: -2px 0 14px; }
#n-repo-filter { margin-bottom: 10px; }

/* The status line inside a provider card: a dot, a word, and the host. The
   dot is the same vocabulary the service tiles use, so "offline" means the
   same thing in both places. */
.git-conn-status { display: flex; align-items: center; gap: 6px; }
.git-conn .row-flex { margin: 0; }
/* The inline rename / disconnect-confirm panel. Hidden until asked for, and
   it pushes the row taller rather than covering anything — a confirmation you
   have to dismiss to re-read the thing it is asking about is the modal
   problem in miniature. */
.git-conn-form { margin-top: 10px; }
.git-conn-form .notice { margin: 0; }
.git-conn-form input[type="text"], .git-conn-form input:not([type]) {
  padding: 7px 10px;
  font-size: 0.8125rem;
}
.check { display: flex; align-items: flex-start; gap: 8px; font-size: 0.78125rem; margin: 8px 0; }
.check input { margin-top: 2px; flex-shrink: 0; }

/* ── the New dialog as a wizard ───────────────────────────────────────── */
/* The rail: where you are in a short sequence, and what is still to come.
   Horizontal, because four steps read as a path rather than a menu. */
.wiz-rail {
  display: flex;
  gap: 4px;
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  border-bottom: 1px solid var(--line-faint);
}
.wiz-s { flex: 1; min-width: 0; }
.wiz-s-body {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px 10px;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--ink-3);
  font: inherit;
}
button.wiz-s-body { cursor: pointer; }
button.wiz-s-body:hover { color: var(--ink-1); }
.wiz-s-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  margin-right: 6px;
  border-radius: 50%;
  border: 1px solid currentColor;
  font-size: 0.6875rem;
  vertical-align: -3px;
}
.wiz-s-label { font-weight: 600; font-size: 0.8125rem; }
.wiz-s-hint { display: block; margin-top: 2px; font-size: 0.71875rem; }
.wiz-s.now .wiz-s-body { color: var(--brass); border-bottom-color: var(--brass); }
.wiz-s.done .wiz-s-body { color: var(--ink-2); }
.wiz-s.done .wiz-s-num { border-color: var(--ok); color: var(--ok); }
@media (max-width: 640px) {
  .wiz-s-hint { display: none; }
  .wiz-s-label { font-size: 0.75rem; }
}

/* The Data step's three choices. Radios laid out as cards, so the one that
   provisions a database reads as a decision rather than a checkbox. */
.wiz-choice { border: 0; padding: 0; margin: 0 0 14px; }
.wiz-choice legend { padding: 0; font-size: 0.75rem; color: var(--ink-3); text-transform: uppercase; letter-spacing: .04em; }
.choice {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 9px 11px;
  margin: 7px 0;
  border: 1px solid var(--line-faint);
  border-radius: 8px;
  cursor: pointer;
}
.choice:hover { border-color: var(--line); }
.choice input { margin-top: 3px; width: auto; flex-shrink: 0; }
.choice b { display: block; font-size: 0.8125rem; }
.choice .dim { display: block; margin-top: 2px; }
.choice:has(input:checked) { border-color: var(--brass); background: rgba(198,160,90,0.06); }
/* A control this account cannot use is shown, dimmed, and explained — never
   hidden and never offered-then-refused (2026-08-31 feedback, item 14). */
.choice.off, label.off { opacity: .5; }
.choice.off { cursor: not-allowed; }

.wiz-foot { justify-content: flex-start; }

/* ── tooltips ─────────────────────────────────────────────────────────── */
/* One floating node for the whole page (showTip). Fixed, because it is
   positioned from a viewport rect and must not be clipped by the canvas'
   overflow. */
.tip {
  position: fixed;
  z-index: 90;
  max-width: 260px;
  padding: 6px 9px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-2, #14161a);
  color: var(--ink-1);
  font-size: 0.75rem;
  line-height: 1.35;
  box-shadow: 0 6px 20px rgba(0,0,0,0.45);
  pointer-events: none;
}
.tip::after {
  content: '';
  position: absolute;
  left: 50%;
  margin-left: -5px;
  border: 5px solid transparent;
  bottom: -10px;
  border-top-color: var(--line);
}
.tip.below::after { bottom: auto; top: -10px; border-top-color: transparent; border-bottom-color: var(--line); }

/* ── people: members and invitations ─────────────────────────────────── */
/* Status is a badge rather than a word in the row, because the one thing an
   admin scans this table for is who has actually turned up. */
.pill {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  font-size: 0.71875rem; letter-spacing: .01em; white-space: nowrap;
  border: 1px solid var(--line-soft);
}
.pill-ok { color: var(--ok); border-color: rgba(52,168,125,0.35); background: rgba(52,168,125,0.10); }
.pill-wait { color: var(--warn); border-color: rgba(208,135,54,0.35); background: rgba(208,135,54,0.10); }
.role-pick { width: auto; padding: 4px 8px; font-size: 0.78125rem; }
.role-pick:disabled { opacity: .55; cursor: not-allowed; }

/* The invitation prompt. Above every view rather than inside People: an
   invitation is answered by the person invited, who has no reason to be
   looking at the People page of an account they are not on yet. */
.invite-prompt {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  border: 1px solid var(--line-soft); border-left: 3px solid var(--brass);
  border-radius: 8px; background: var(--panel);
  padding: 12px 16px; margin: 0 0 16px;
}
.invite-prompt .ip-text { flex: 1; min-width: 260px; font-size: 0.8125rem; line-height: 1.6; }
.invite-prompt .ip-acts { display: flex; gap: 8px; flex-shrink: 0; }

/* ── confirmation in place ────────────────────────────────────────────── */
/* Same grammar as the git disconnect panel: it pushes the thing it is asking
   about taller rather than covering it, so you can still read what you are
   about to change. */
.confirm-panel { margin: 10px 0 0; }
.confirm-panel .notice { margin: 0; }
.confirm-panel .notice p { margin: 6px 0 10px; font-size: 0.8125rem; line-height: 1.6; }
.confirm-panel .row-flex { margin: 0; }

/* The workspace menu's footnote: joining someone else's workspace is by
   invitation, and saying so beats leaving people to look for a button. */
.ws-menu-note {
  margin: 6px 10px 8px; font-size: 0.71875rem; line-height: 1.5; color: var(--ink-3);
}

/* ── narrow screens ──────────────────────────────────────────────────── */
/* The console has one desktop-shaped layout and a 900px pass that shrinks the
   sidebar to icons. Below that it was still asking a phone to render a 264px
   grid, a fixed-width canvas and wide tables (2026-08-31 feedback, item 38). */
@media (max-width: 700px) {
  .main { padding: 16px 14px; }
  .sidebar { width: 56px; padding: 12px 6px; }
  h1 { font-size: 1.1875rem; }
  .view-head { flex-wrap: wrap; gap: 8px; }

  /* One column everywhere a grid was sized for a laptop. */
  .projects-grid { grid-template-columns: 1fr; }
  .plan-grid, .tiles, .cols-2, .cols-3 { grid-template-columns: 1fr; }

  /* The canvas becomes a list. A 256px node on a 32px lattice inside a
     horizontally scrolling box is unusable on a phone, and dragging wires
     between corner ports is a mouse gesture — so below this width the same
     nodes stack full-width in flow, and free-layout pinning is dropped rather
     than leaving nodes absolutely positioned off-screen. */
  .view-canvas { height: auto; }
  .canvas {
    display: block; padding: 12px; min-height: 0; overflow: visible;
    background-image: none; /* the lattice reads as noise at this size */
  }
  .canvas.free .service-node { position: static; }
  .service-node { width: 100%; margin: 0 0 10px; }
  /* Wires and ports are a pointer affordance with no touch equivalent; the
     attachment they describe is still shown in the node body and the drawer. */
  .canvas-edges, .canvas-wire, .sn-port { display: none; }

  /* Panels take the whole screen rather than a sliver of it. */
  .drawer { width: 100vw; border-left: 0; }
  .modal { width: 100%; border-radius: 12px; padding: 16px 14px; }
  .modal-scrim { padding: 0; align-items: stretch; }

  /* Wide tables scroll inside their card instead of pushing the page sideways. */
  .card { padding: 14px; }
  .card table { display: block; overflow-x: auto; }

  /* Rows of controls wrap instead of overflowing. */
  .row-flex { flex-wrap: wrap; }
  .row-flex input, .row-flex select { min-width: 0; }
  .invite-prompt .ip-acts { width: 100%; }
  .wiz-rail { overflow-x: auto; }
}

/* ── text-size control ───────────────────────────────────────────────── */
.seg { display: inline-flex; border: 1px solid var(--line-soft); border-radius: 8px; overflow: hidden; }
.seg-btn {
  background: transparent; border: 0; border-right: 1px solid var(--line-faint);
  color: var(--ink-2); font: inherit; padding: 7px 14px; cursor: pointer;
}
.seg-btn:last-child { border-right: 0; }
.seg-btn:hover { color: var(--ink); background: var(--panel); }
.seg-btn.on { background: var(--brass-dim); color: var(--brass-hi); }

/* ── docs ────────────────────────────────────────────────────────────── */
/* In the console, because the questions it answers are asked mid-task. Topics
   on the left, one pane at a time — a single scrolling page would bury the
   thing you came for under six things you did not. */
.docs-wrap { display: grid; grid-template-columns: 190px 1fr; gap: 20px; align-items: start; }
.docs-nav { display: flex; flex-direction: column; gap: 2px; position: sticky; top: 0; }
.docs-nav button {
  background: none; border: 0; border-left: 2px solid transparent;
  color: var(--ink-3); font: inherit; text-align: left;
  padding: 7px 12px; border-radius: 0 6px 6px 0; cursor: pointer;
}
.docs-nav button:hover { color: var(--ink-2); background: var(--panel); }
.docs-nav button.on { color: var(--brass-hi); border-left-color: var(--brass); background: var(--brass-dim); }
/* A docs pane's h2 is the document's title, not a card label — the console's
   card headings are uppercased small caps, which is wrong for a page heading. */
.doc-pane h2 { text-transform: none; letter-spacing: -0.2px; font-size: 1.125rem; color: var(--ink); margin-bottom: 12px; }
.doc-pane h3 { margin: 18px 0 8px; text-transform: none; letter-spacing: 0; font-size: 0.875rem; color: var(--ink); }
.doc-pane p { margin: 0 0 10px; line-height: 1.7; color: var(--ink-2); font-size: 0.84375rem; }
.doc-pane ul, .doc-pane ol { margin: 0 0 10px; padding-left: 20px; color: var(--ink-2); font-size: 0.84375rem; line-height: 1.8; }
.doc-pane .snippet { margin: 0 0 12px; white-space: pre-wrap; }
.doc-pane table { margin-top: 4px; }
.doc-pane table td:first-child { white-space: nowrap; }

/* "Coming soon" beside a nav item that is not for sale yet, so the page is not
   mistaken for something broken. */
.nav-soon {
  margin-left: auto; font-size: 0.625rem; letter-spacing: .03em;
  color: var(--ink-3); border: 1px solid var(--line-faint);
  border-radius: 999px; padding: 1px 7px; white-space: nowrap;
}

@media (max-width: 700px) {
  .docs-wrap { grid-template-columns: 1fr; }
  .docs-nav { flex-direction: row; overflow-x: auto; position: static; gap: 4px; }
  .docs-nav button { border-left: 0; border-bottom: 2px solid transparent; border-radius: 6px 6px 0 0; white-space: nowrap; }
  .docs-nav button.on { border-left: 0; border-bottom-color: var(--brass); }
  .doc-pane table { display: block; overflow-x: auto; }
}

/* Pair health strip above the project canvas (2026-09-04): one chip per
   cluster the project touches, one line per managed database. Colours are
   the status-dot tokens; nothing new. */
.pair-health { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; padding: 8px 12px; margin: 0 0 8px; font-size: 13px; color: var(--ink-2); border: 1px solid var(--rule); border-radius: 6px; }
.pair-health .chip { display: inline-flex; gap: 6px; align-items: center; padding: 3px 8px; border-radius: 999px; background: var(--paper-2, rgba(0,0,0,0.04)); }
.pair-health .chip b { color: var(--ink); font-weight: 600; }
.pair-health .db-line { display: inline-flex; gap: 6px; align-items: center; }
