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

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

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

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

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

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