/* The project canvas (docs/adr/0030-canvas-console.md §3): the screen under a
   project route, the canvas itself, its nodes and edges, and the floating
   controls over it. Colours are tokens from app.css, never literals; the
   canvas-specific ones (--canvas-*, --node-*, --edge*, --staged*) are defined
   there for both themes. */

/* ---- the screen ------------------------------------------------------------ */

/* The whole of the page under the top bar (body.canvas-mode takes the page's
   gutters and the context bar away). --cv-right is the
   width the panel covers, written by projectdetail.js as the panel opens and
   resizes, so the floating controls move out from under it. */
.cv-screen {
  --cv-right: 0px;
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--canvas-bg);
  isolation: isolate;
}

.cv-screen > .cv { position: absolute; inset: 0; }

.cv-float {
  position: absolute;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.cv-float.top-left { top: 16px; left: 16px; }
/* The changes bar (changes.js) places itself at the top centre; on the
   canvas that is the centre of the part the panel leaves visible, level with
   the Create button while there is room beside it, and a row below it when
   the panel leaves too little (so the count is never cut off). */
.cv-screen > .changes-bar {
  top: 16px;
  left: calc((100% - var(--cv-right)) / 2);
  transform: translateX(-50%);
  max-width: min(720px, calc(100% - var(--cv-right) - 400px));
  z-index: 15;
  transition: left var(--dur) var(--ease);
}
/* Over the canvas the open list stays short, so the Internet node and the
   top row of cards are not buried under a long one. */
.cv-screen > .changes-bar .changes-list { max-height: min(40vh, 320px); }
/* Discard is destructive: not the accent Hide and Deploy share. */
.changes-bar .changes-actions .btn.quiet.danger { color: var(--bad); border-color: transparent; }
.changes-bar .changes-actions .btn.quiet.danger:hover { background: var(--bad-soft); }
.cv-float.bottom-right {
  right: calc(var(--cv-right) + 16px);
  bottom: 16px;
  transition: right var(--dur) var(--ease);
}
/* The health row (healthrow.js, docs/adr/0054-health-row.md) floats along
   the bottom in the gap the panel leaves between the zoom tools (bottom left,
   about 340px with their margin) and Activity (about 140px), centred in it.
   The slot is that gap and lets the pointer through; the bar inside it is as
   wide as its cells, which give way as the gap narrows, and goes when there
   is no room at all. It sits in the canvas's 56px bottom inset, as tall as
   the floating tools. */
.cv-screen > .hrow-slot {
  position: absolute;
  z-index: 10;
  bottom: 16px;
  left: 340px;
  width: calc(100% - var(--cv-right) - 480px);
  pointer-events: none;
  transition: width var(--dur) var(--ease);
}
.cv-screen > .hrow-slot > .hrow {
  width: fit-content;
  max-width: 100%;
  height: 38px;
  margin: 0 auto;
  border: 1px solid var(--node-border);
  border-radius: 10px;
  background: var(--node-bg);
  box-shadow: var(--node-shadow);
  pointer-events: auto;
}
.cv-screen > .hrow-slot .hrow-last { margin-left: 0; }

.cv-float .btn,
.cv-float .icon-btn { box-shadow: var(--node-shadow); }

.cv-create.btn { gap: 6px; padding-left: 12px; padding-right: 14px; }

.cv-project-name {
  font-weight: 600;
  font-size: 14px;
  color: var(--ink);
  padding: 0 4px 0 8px;
  white-space: nowrap;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cv-float .icon-btn {
  background: var(--node-bg);
  border: 1px solid var(--node-border);
  width: 34px;
  height: 34px;
}

.cv-float .icon-btn:hover { background: var(--surface-2); }

.cv-activity.btn { gap: 6px; background: var(--node-bg); }

/* The panel lives in a host that spans the screen and lets the pointer
   through everywhere the panel is not. */
.cv-panel-host {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: flex;
  justify-content: flex-end;
  pointer-events: none;
}

.cv-panel-host > * { pointer-events: auto; }

.cv-error {
  position: absolute;
  top: 96px;
  left: 50%;
  transform: translateX(-50%);
  width: min(560px, calc(100% - 32px));
  z-index: 12;
}

/* ---- the canvas ------------------------------------------------------------ */

.cv { position: relative; overflow: hidden; }

.cv-viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
  cursor: grab;
  touch-action: none;
  user-select: none;
  background-color: var(--canvas-bg);
  background-image: radial-gradient(circle at 1px 1px, var(--canvas-dot) 1px, transparent 1.4px);
  background-size: 22px 22px;
  outline: none;
}

.cv-viewport:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-soft); }

.cv.cv-panning .cv-viewport { cursor: grabbing; }

.cv-world {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  transform-origin: 0 0;
  will-change: transform;
}

.cv.cv-animate .cv-world { transition: transform 280ms var(--ease); }
.cv.cv-animate .cv-viewport { transition: background-position 280ms var(--ease), background-size 280ms var(--ease); }

.cv-nodes { position: absolute; left: 0; top: 0; }

/* The edges are one SVG of no size whose paths are drawn outside it. */
.cv-edges {
  position: absolute;
  left: 0;
  top: 0;
  overflow: visible;
  pointer-events: none;
}

/* ---- nodes ----------------------------------------------------------------- */

.cv-node {
  position: absolute;
  box-sizing: border-box;
  background: var(--node-bg);
  border: 1px solid var(--node-border);
  border-radius: 12px;
  box-shadow: var(--node-shadow);
  color: var(--ink);
  cursor: pointer;
  transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.cv-node:hover { border-color: var(--line-2); }

.cv-node.on {
  border-color: var(--node-selected);
  box-shadow: 0 0 0 3px var(--accent-soft), var(--node-shadow);
}

.cv-node.dragging {
  cursor: grabbing;
  box-shadow: 0 0 0 3px var(--accent-soft), var(--panel-shadow);
  z-index: 3;
}

.cv-node.has-staged { border-color: var(--staged); }
.cv-node.has-staged.on { border-color: var(--node-selected); }

/* The whole card is one button, stretched under the content, so a node is a
   single tab stop with a name; the content is drawn over it and lets the
   pointer through, except its link and its connector. */
.cv-hit {
  position: absolute;
  inset: -1px;
  border: 0;
  margin: 0;
  padding: 0;
  background: transparent;
  border-radius: inherit;
  cursor: inherit;
  z-index: 0;
}

.cv-hit:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.cv-node-body {
  position: relative;
  z-index: 1;
  pointer-events: none;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.cv-node-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 14px 0;
  min-width: 0;
}

.cv-node-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--ink-2);
}

.kind-db .cv-node-icon,
.kind-redis .cv-node-icon,
.kind-mysql .cv-node-icon { color: var(--accent); }

.cv-node-titles { min-width: 0; flex: 1; }

.cv-node-name {
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cv-node-sub {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ink-2);
  line-height: 1.45;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cv-staged {
  flex: none;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 20px;
  padding: 0 7px;
  border-radius: var(--radius-tag);
  background: var(--staged-soft);
  color: var(--staged);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}

.cv-staged-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--staged);
}

.cv-node-meta {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 12px 14px 12px 56px;
  min-width: 0;
}

.cv-status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink);
}

.cv-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-3);
  flex: none;
}

.cv-status.ok .cv-dot { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-soft); }
.cv-status.warn .cv-dot { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
.cv-status.bad .cv-dot { background: var(--bad); box-shadow: 0 0 0 3px var(--bad-soft); }
.cv-status.bad { color: var(--bad); }
.cv-status.busy .cv-dot {
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  animation: cv-pulse 1.4s var(--ease) infinite;
}
.cv-status.idle { color: var(--ink-2); }

@keyframes cv-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.cv-node-host {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  max-width: 100%;
  font-size: 12px;
  color: var(--ink-2);
  text-decoration: none;
  pointer-events: auto;
  cursor: pointer;
}

.cv-node-host span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cv-node-host .icon { flex: none; width: 13px; height: 13px; color: var(--ink-3); }
.cv-node-host:hover { color: var(--accent); text-decoration: underline; }
.cv-node-host.none { color: var(--ink-3); pointer-events: none; cursor: default; }

.cv-node-vol {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 7px;
  height: 30px;
  padding: 0 14px;
  border-top: 1px solid var(--line);
  border-radius: 0 0 11px 11px;
  background: var(--surface-2);
  font-size: 12px;
  color: var(--ink-2);
}
/* Sizes read in the sans, as on the database and Redis cards; the volume's
   name stays mono. */
.cv-node-vol .mono { font-family: var(--mono); font-size: 11.5px; }

.cv-node-vol .icon { width: 13px; height: 13px; color: var(--ink-3); }
.cv-more { color: var(--ink-3); }

/* A worker and a cron job (ADR 0034): the kind's icon in the accent, a badge
   beside the name, and a cron job's strip with its next and last run. */
.app-worker .cv-node-icon,
.app-cron .cv-node-icon { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); }
.cv-node-name.with-kind { display: flex; align-items: center; gap: 7px; }
.cv-name-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cv-kind {
  flex: none;
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 7px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-tag);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.cv-kind.cron { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent); }
.cv-node-cron {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  height: 30px;
  padding: 0 14px;
  border-top: 1px solid var(--line);
  border-radius: 0 0 11px 11px;
  background: var(--surface-2);
  font-size: 12px;
  color: var(--ink-2);
}
.cv-node-cron:not(:last-child) { border-radius: 0; }
.cv-node-cron + .cv-node-vol { margin-top: 0; }
.cv-cron-next { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cv-node-cron .cv-status { flex: none; font-size: 12px; font-weight: 500; }

/* Live usage (ADR 0035, usage.js): replicas, CPU and memory under the status,
   each a small label, a figure and a 3px bar. Fixed height (STATS_STRIP):
   the canvas lays nodes out by arithmetic. The cells take the pointer for
   their tooltips; canvas.js turns a click on them into a click on the node. */
.cv-stats {
  margin-top: auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  height: 52px;
  padding: 8px 14px 0;
  border-top: 1px solid var(--line);
  min-width: 0;
}
.cv-stats.trouble { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
.cv-stats:last-child { border-radius: 0 0 11px 11px; }
.cv-stats + .cv-node-vol { margin-top: 0; }
.cv-stat { display: flex; flex-direction: column; min-width: 0; pointer-events: auto; cursor: inherit; }
.cv-stat-label { font-size: 11px; line-height: 14px; color: var(--ink-3); }
.cv-stat-value {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  font-size: 13px;
  font-weight: 550;
  line-height: 18px;
  color: var(--ink);
  white-space: nowrap;
}
.cv-stat.idle .cv-stat-value { color: var(--ink-3); font-weight: 400; }
.cv-stat.warn .cv-stat-value { color: var(--warn); }
.cv-stat.bad .cv-stat-value { color: var(--bad); }
.cv-restarts { display: inline-flex; align-items: center; gap: 2px; font-size: 11.5px; font-weight: 500; color: var(--warn); }
.cv-restarts svg { width: 11px; height: 11px; }
.cv-reason .cv-stat-value { color: var(--bad); }
.cv-reason-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cv-stat.bad .cv-restarts { color: var(--bad); }

.cv-bar {
  display: flex;
  gap: 2px;
  height: 3px;
  margin-top: 4px;
  border-radius: 2px;
  background: var(--surface-3);
  overflow: hidden;
}
.cv-bar.none { background: transparent; border-top: 1px dashed var(--line-2); height: 0; margin-top: 5px; }
.cv-bar-fill { height: 100%; border-radius: 2px; background: var(--accent); }
.cv-bar.warn .cv-bar-fill { background: var(--warn); }
.cv-bar.bad .cv-bar-fill { background: var(--bad); }
.cv-bar.segs { background: transparent; }
.cv-seg { flex: 1; height: 100%; border-radius: 2px; background: var(--surface-3); }
.cv-stat.ok .cv-seg.on { background: var(--ok); }
.cv-stat.warn .cv-seg.on { background: var(--warn); }
.cv-stat.busy .cv-seg.on { background: var(--accent); }
.cv-stat .cv-bar:not(.segs) > .cv-bar-fill { min-width: 2px; }
.cv-since { font-weight: 400; color: var(--ink-3); white-space: nowrap; }

/* The Internet: a pill, not a card. */
.cv-node.kind-internet {
  border-radius: var(--radius-tag);
  background: var(--node-bg);
}

.kind-internet .cv-node-body {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}

.kind-internet .cv-node-body .icon { color: var(--accent); }

/* The connector: a small ring on an app's bottom edge, shown on hover or
   focus, dragged to a database or Redis to attach it. */
.cv-handle {
  position: absolute;
  left: 50%;
  bottom: -8px;
  width: 16px;
  height: 16px;
  margin-left: -8px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid var(--accent);
  background: var(--node-bg);
  cursor: crosshair;
  pointer-events: auto;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  z-index: 2;
}

.cv-node.has-vol .cv-handle { bottom: -8px; }

.cv-node:hover .cv-handle,
.cv-node.on .cv-handle,
.cv-handle:focus-visible,
.cv.cv-connecting .cv-handle { opacity: 1; transform: scale(1); }

.cv-handle:hover { background: var(--accent); }
.cv-handle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.cv.cv-connecting .cv-viewport { cursor: crosshair; }
.cv.cv-connecting .kind-app,
.cv.cv-connecting .kind-internet { opacity: 0.55; }
.cv.cv-connecting .kind-db,
.cv.cv-connecting .kind-redis,
.cv.cv-connecting .kind-mysql { border-style: dashed; border-color: var(--accent-line); }
.cv.cv-connecting .cv-node.cv-drop {
  border-style: solid;
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft), var(--node-shadow);
}

.cv.cv-dragging .cv-node-host { pointer-events: none; }

/* ---- edges ----------------------------------------------------------------- */

.cv-edge-line {
  fill: none;
  stroke: var(--edge);
  stroke-width: 1.5;
  transition: stroke var(--dur) var(--ease);
}

.cv-edge-hit {
  fill: none;
  stroke: transparent;
  stroke-width: 16;
}

.cv-edge.detachable .cv-edge-hit { pointer-events: stroke; cursor: pointer; }
.cv-edge.detachable .cv-edge-chip,
.cv-edge.detachable .cv-edge-label { pointer-events: all; cursor: pointer; }

.cv-edge-end { fill: var(--edge); }

.cv-edge-chip {
  fill: var(--node-bg);
  stroke: var(--edge);
  stroke-width: 1;
}

.cv-edge-label {
  fill: var(--ink-2);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.cv-edge.lit .cv-edge-line,
.cv-edge.on .cv-edge-line,
.cv-edge.detachable:hover .cv-edge-line { stroke: var(--edge-active); stroke-width: 2; }
.cv-edge.lit .cv-edge-end,
.cv-edge.on .cv-edge-end { fill: var(--edge-active); }
.cv-edge.lit .cv-edge-chip,
.cv-edge.on .cv-edge-chip { stroke: var(--edge-active); }
.cv-edge.lit .cv-edge-label,
.cv-edge.on .cv-edge-label { fill: var(--ink); }

/* A reference (ADR 0033): one app reading another's address. Dotted, not
   solid like an attachment, with an arrow into the app it reads; its chip
   names the variable. */
.cv-edge.ref .cv-edge-line { stroke-dasharray: 1.5 4.5; stroke-linecap: round; stroke-width: 1.75; }
.cv-edge-arrow { fill: var(--edge); stroke: none; }
.cv-edge.lit .cv-edge-arrow,
.cv-edge.on .cv-edge-arrow { fill: var(--edge-active); }
.cv-edge.ref .cv-edge-chip { rx: 4px; }
.cv-edge.staged-add .cv-edge-arrow { fill: var(--staged); }

.cv-edge.staged-add .cv-edge-line {
  stroke: var(--staged);
  stroke-dasharray: 6 5;
  stroke-width: 1.75;
}
.cv-edge.staged-add .cv-edge-end { fill: var(--staged); }
.cv-edge.staged-add .cv-edge-chip { stroke: var(--staged); fill: var(--staged-soft); }
.cv-edge.staged-add .cv-edge-label { fill: var(--staged); font-weight: 600; }

.cv-edge.staged-drop { opacity: 0.45; }
.cv-edge.staged-drop .cv-edge-line { stroke-dasharray: 3 5; stroke: var(--staged); }
.cv-edge.staged-drop .cv-edge-label { text-decoration: line-through; }
.cv-edge.staged-drop .cv-edge-chip { stroke: var(--staged); }

/* A bundle (canvas.js opts.bundle): the edges between two frames drawn as
   one heavier line, its chip how many. Its members are drawn only while one
   of their nodes, or the bundle, is under the pointer or selected; the
   bundle is faint under them then. */
.cv-bundle-line {
  fill: none;
  stroke: var(--edge);
  stroke-width: 3;
  stroke-linecap: round;
  transition: stroke var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.cv-bundle .cv-edge-hit { pointer-events: stroke; }
.cv-bundle .cv-edge-chip,
.cv-bundle .cv-edge-label { pointer-events: all; }
.cv-bundle:hover .cv-bundle-line { stroke: var(--edge-active); }
.cv-bundle:hover .cv-edge-chip { stroke: var(--edge-active); }
.cv-bundle.open { opacity: 0.3; }
.cv-edge.member { display: none; }
.cv-edge.member.lit { display: inline; }

.cv-live-wire {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-dasharray: 5 4;
}

.cv-edge-tools { position: absolute; left: 0; top: 0; }

.cv-edge-more.icon-btn {
  position: absolute;
  width: 24px;
  height: 24px;
  min-height: 0;
  border-radius: 50%;
  background: var(--node-bg);
  border: 1px solid var(--edge-active);
  box-shadow: var(--node-shadow);
}

/* ---- the toolbar ------------------------------------------------------------ */

.cv-toolbar {
  position: absolute;
  left: 16px;
  bottom: 16px;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--node-bg);
  border: 1px solid var(--node-border);
  border-radius: 10px;
  box-shadow: var(--node-shadow);
}

.cv-toolbar .icon-btn {
  width: 30px;
  height: 30px;
  border: 0;
  background: transparent;
  box-shadow: none;
}

.cv-toolbar .icon-btn:hover,
.cv-tool:hover,
.cv-zoom-level:hover { background: var(--surface-2); }

.cv-zoom-level,
.cv-tool {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 8px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
}

.cv-zoom-level {
  min-width: 50px;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}

.cv-tool .icon { color: var(--ink-3); }

.cv-toolbar-sep {
  width: 1px;
  height: 18px;
  margin: 0 4px;
  background: var(--line);
}

.cv.is-empty .cv-toolbar { display: none; }

/* ---- the empty project -------------------------------------------------------- */

.cv-empty {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.cv-empty-card {
  pointer-events: auto;
  width: min(640px, calc(100% - 48px));
  text-align: center;
}

.cv-empty-card h2 {
  margin: 0 0 6px;
  font-size: 22px;
  font-weight: 650;
  letter-spacing: -0.02em;
}

.cv-empty-card p { margin: 0 0 24px; color: var(--ink-2); }

.cv-empty-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 10px;
}

.cv-empty-choice {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--node-border);
  border-radius: 12px;
  background: var(--node-bg);
  box-shadow: var(--node-shadow);
  color: var(--ink);
  font-weight: 550;
  font-size: 13.5px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}

.cv-empty-choice:hover { border-color: var(--accent-line); background: var(--surface-2); }
.cv-empty-choice:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.cv-empty-choice .cv-node-icon { width: 30px; height: 30px; color: var(--accent); }

/* ---- the popovers the canvas opens ------------------------------------------ */

.cv-connect-pop {
  position: fixed;
  z-index: 60;
  width: 300px;
  padding: 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-pop);
}

.cv-connect-pop h3 { margin: 0 0 4px; font-size: 14px; font-weight: 600; }
.cv-connect-pop p { margin: 0 0 12px; font-size: 12.5px; color: var(--ink-2); }
.cv-connect-pop input { width: 100%; }
.cv-connect-pop .row { justify-content: flex-end; margin-top: 12px; }
.cv-connect-pop select { width: 100%; }
.cv-connect-pop .field + .field { margin-top: 10px; }

/* The reference dialog's value and what it resolves to now. */
.ref-preview-box {
  margin-top: 10px;
  padding: 8px 10px;
  background: var(--surface-2);
  border-radius: var(--radius);
  font-size: 11.5px;
  overflow-wrap: anywhere;
}
.ref-preview-to { color: var(--ink-2); margin-top: 2px; }
.cv-connect-pop .ref-clash { margin: 4px 0 0; color: var(--warn); }
.cv-connect-pop .ref-clash + .field { margin-top: 10px; }

/* The activity drawer's two lists. */
.cv-activity-list { display: flex; flex-direction: column; }

.cv-activity-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  text-decoration: none;
}

.cv-activity-row:last-child { border-bottom: 0; }
a.cv-activity-row:hover .cv-activity-what { color: var(--accent); }
.cv-activity-what { font-weight: 550; font-size: 13.5px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cv-activity-when { font-size: 12.5px; color: var(--ink-3); white-space: nowrap; }
.cv-activity-sub { grid-column: 1 / -1; font-size: 12.5px; color: var(--ink-2); display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

.cv-settings-block + .cv-settings-block { margin-top: var(--sp-4); }

@media (prefers-reduced-motion: reduce) {
  .cv.cv-animate .cv-world,
  .cv.cv-animate .cv-viewport { transition: none; }
  .cv-status.busy .cv-dot { animation: none; }
  .cv-screen > .hrow-slot { transition: none; }
}

.cv-screen.cv-panel-open > .changes-bar { top: 64px; max-width: calc(100% - var(--cv-right) - 32px); }
@media (max-width: 639px) {
  .cv-screen > .changes-bar { top: 64px; max-width: calc(100% - 32px); }
  .cv-tool span { display: none; }
}

/* ---- the platform view (docs/adr/0039-platform-view.md) --------------------- */

/* The platform's canvas is read only: no Create button, so its name and a
   one-line tally sit where the Create button and a project's name do. */
.pf-title {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: 7px 12px 7px 4px;
  background: var(--node-bg);
  border: 1px solid var(--node-border);
  border-radius: var(--radius);
  box-shadow: var(--node-shadow);
}
.pf-summary { font-size: 12.5px; white-space: nowrap; }
.pf-summary.bad { color: var(--bad); }
/* A service with a newer version (ADR 0048): the staged chip's shape in the
   accent, in the node's head, a link to Settings › Versions. */
.cv-node-host.pf-update {
  flex: none;
  align-self: flex-start;
  height: 20px;
  padding: 0 8px;
  border-radius: var(--radius-tag);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}
.cv-node-host.pf-update:hover { background: var(--accent-line); color: var(--accent); text-decoration: none; }
/* A single-instance dependency in ha mode (docs/adr/0075-ha-console.md): the
   same shape in the warning tone, words only; its title is the reason. */
.pf-noha {
  flex: none;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border-radius: var(--radius-tag);
  background: var(--warn-soft);
  color: var(--warn);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: help;
}
/* The servers strip (platformha.js) floats along the bottom like a
   project's health row, between the zoom tools and the panel: the platform
   view has no Activity button on the right. */
.cv-screen.pf-screen > .hrow-slot { width: calc(100% - var(--cv-right) - 356px); }
.pf-servers .pf-sv-tags { color: var(--ink-3); font-size: 11.5px; }
.pf-servers .pf-sv .hrow-value { font-size: 12px; }
.pf-servers .pf-sv.tone-bad .hrow-value { color: var(--bad); }
@container hrow (max-width: 1000px) { .pf-servers .pf-sv-tags { display: none; } }
@container hrow (max-width: 260px) { .pf-servers .pf-sv-count { display: none; } }
/* A frame (canvas.js data.frames): a named box behind a set of nodes — the
   platform's groups. Quiet enough that the nodes and edges stay the thing. */
.cv-frame-box {
  fill: var(--surface-2);
  fill-opacity: 0.55;
  stroke: var(--line-2);
  stroke-width: 1;
}
.cv-frame-label {
  fill: var(--ink-2);
  font-size: 15px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* A controller without the view: no dotted ground under the sentence. */
.cv-screen.pf-off { background: var(--ground); }

/* ---- the edge server a project runs on (docs/adr/0049-edge-servers.md) ----
   A quiet chip beside the project's name: "Edge: w1 · Ready", its dot the
   server's state. It links to the server for an admin. */
.cv-edge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--node-border);
  border-radius: var(--radius-tag);
  background: var(--node-bg);
  box-shadow: var(--node-shadow);
  font-size: 12.5px;
  color: var(--ink-2);
  white-space: nowrap;
  text-decoration: none;
}
a.cv-edge:hover { color: var(--ink); border-color: var(--accent-line); text-decoration: none; }
.cv-edge strong { font-weight: 600; color: var(--ink); }
.cv-edge .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-3); flex: none; }
.cv-edge.ok .dot { background: var(--ok); }
.cv-edge.warn .dot { background: var(--warn); }
.cv-edge.bad .dot { background: var(--bad); }
.cv-edge-state { color: var(--ink-2); }

/* ---- pull-request previews (docs/adr/0057-pr-previews.md) -----------------
   A parent's chip beside the project's name ("Previews 2"), the shape of the
   edge server's (not its class: .cv-edge is also the canvas's edges), which
   opens the drawer of its previews; and a preview's banner, floating under
   the top row. */
.cv-previews {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--node-border);
  border-radius: var(--radius-tag);
  background: var(--node-bg);
  box-shadow: var(--node-shadow);
  font: inherit;
  font-size: 12.5px;
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
}
.cv-previews:hover { color: var(--ink); border-color: var(--accent-line); }
.cv-previews strong { font-weight: 600; color: var(--ink); }
.cv-previews .icon { color: var(--ink-3); }
.cv-previews[hidden] { display: none; }
.cv-screen > .cv-preview-slot {
  position: absolute;
  z-index: 10;
  top: 60px;
  left: 16px;
  max-width: min(680px, calc(100% - var(--cv-right) - 32px));
  transition: max-width var(--dur) var(--ease);
}
.cv-screen > .cv-preview-slot[hidden] { display: none; }
.cv-preview-slot .banner { box-shadow: var(--node-shadow); }

/* The drawer's list: one block per pull request, under a hairline. */
.pv-list { display: flex; flex-direction: column; }
.pv-row { display: flex; flex-direction: column; gap: 6px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.pv-row:last-child { border-bottom: 0; }
.pv-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pv-title { font-weight: 600; font-size: 13.5px; min-width: 0; overflow-wrap: anywhere; }
.pv-sub, .pv-when { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12.5px; color: var(--ink-2); }
.pv-sub .icon { color: var(--ink-3); }
.pv-when { color: var(--ink-3); }
.pv-why { margin: 0; font-size: 12.5px; }
.pv-apps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.pv-app { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; }
.pv-actions { display: flex; gap: 8px; margin-top: 2px; }
.pv-missing { margin: 4px 0; padding-left: 18px; }
.cv-preview-slot .banner-actions { white-space: nowrap; }

/* A node whose app or platform service runs a fixable Critical, or a
   known-exploited vulnerability (docs/adr/0088-vulnerability-scanning.md): the
   kit's pill at the staged chip's size, at the end of the head. */
.cv-node-head .cv-badge {
  flex: none;
  align-self: flex-start;
  height: 20px;
  padding: 0 7px;
  font-size: 11.5px;
  cursor: help;
}
