.cv { position: relative; height: 100%; min-height: 0; background: #e8eef6; }
.cv-stage { position: absolute; inset: 0; }
.cv-layers {
  position: absolute; z-index: 7; left: 12px; top: 64px; bottom: 76px; width: 200px;
  display: none; flex-direction: column; border-radius: 14px; overflow: auto;
  border: 1px solid rgba(23, 49, 90, .08); background: rgba(255, 255, 255, .96);
  box-shadow: 0 12px 32px rgba(23, 49, 90, .12);
}
.cv-layers.is-open { display: flex; }
.cv-layers header { padding: 14px 14px 8px; color: #64748b; font-size: 12px; }
.cv-layers button { display: block; width: calc(100% - 16px); margin: 0 8px 4px; padding: 8px 10px; border: 0; border-radius: 10px; background: transparent; text-align: left; cursor: pointer; color: #17315a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cv-layers button.on, .cv-layers button:hover { background: #eef4ff; }
.cv-layers p { margin: 12px; color: #94a3b8; font-size: 12px; }
.cv-board {
  position: absolute; inset: 0; overflow: hidden; touch-action: none; cursor: grab;
  background-color: #e8eef6;
  background-image: radial-gradient(circle, #c5d2e4 1.1px, transparent 1.1px);
  background-size: 22px 22px;
}
.cv-board.is-panning { cursor: grabbing; }
.cv-world { position: absolute; inset: 0; transform-origin: 0 0; will-change: transform; }
.cv-wires { position: absolute; width: 1px; height: 1px; overflow: visible; pointer-events: none; }
.cv-wires .hit { fill: none; stroke: transparent; stroke-width: 16; pointer-events: stroke; }
.cv-wires .line { fill: none; stroke: #5b7cff; stroke-width: 2; pointer-events: none; }
.cv-wires .pending { stroke-dasharray: 6 4; }
.cnode {
  position: absolute; left: 0; top: 0; border: 2px solid #d5deea; border-radius: 16px;
  background: #fff; box-shadow: 0 10px 24px rgba(23, 49, 90, .08); will-change: transform;
}
.cnode.is-on { z-index: 4; border-color: #1d63ff; box-shadow: 0 0 0 3px rgba(29, 99, 255, .18); }
.cnode.is-related { border-color: #8fb4ff; }
.cnode.is-text { background: #fff8df; border-color: #f0d48a; }
.cnode.is-link { box-shadow: 0 0 0 3px rgba(29, 99, 255, .28); }
.port {
  position: absolute; top: 50%; z-index: 3; width: 18px; height: 18px; margin-top: -9px;
  border: 3px solid #fff; border-radius: 99px; background: #1d63ff; cursor: crosshair; padding: 0;
}
.port.in { left: -11px; }
.port.out { right: -11px; }
.media, .note { width: 100%; height: 100%; border-radius: 14px; overflow: hidden; }
.media { position: relative; background: #141b28; color: #d5deea; }
.media img, .media video { width: 100%; height: 100%; object-fit: cover; -webkit-user-drag: none; user-select: none; pointer-events: none; }
.kind { position: absolute; top: 8px; left: 8px; z-index: 1; padding: 2px 8px; border-radius: 99px; background: rgba(0,0,0,.45); color: #fff; font-size: 12px; }
.empty { display: grid; place-items: center; height: 100%; padding: 16px; text-align: center; white-space: pre-wrap; }
.veil { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; align-content: center; gap: 8px; background: rgba(8, 12, 20, .62); color: #fff; }
.veil[hidden], .fail[hidden] { display: none; }
.spin { width: 28px; height: 28px; border: 3px solid rgba(255,255,255,.35); border-top-color: #fff; border-radius: 99px; animation: cv-spin .7s linear infinite; }
.fail { position: absolute; left: 8px; right: 8px; bottom: 8px; z-index: 2; padding: 6px 8px; border-radius: 8px; background: rgba(180, 40, 40, .92); color: #fff; font-size: 12px; white-space: normal; }
.actbar button.is-loading { position: relative; color: transparent; }
.actbar button.is-loading:before { content: ""; position: absolute; left: 50%; top: 50%; width: 14px; height: 14px; margin: -7px 0 0 -7px; border: 2px solid rgba(29,99,255,.25); border-top-color: #1d63ff; border-radius: 99px; animation: cv-spin .7s linear infinite; }
.state { min-height: 18px; margin: 0; font-size: 13px; }
.state.run { color: #1d63ff; }
.state.ok { color: #0f9f6e; }
.state.bad { color: #d14343; white-space: normal; }
@keyframes cv-spin { to { transform: rotate(360deg); } }
.note { display: flex; flex-direction: column; }
.note em { padding: 10px 12px 0; color: #8a7340; font-size: 11px; font-style: normal; }
.note textarea { flex: 1; border: 0; resize: none; background: transparent; color: #5a4716; padding: 8px 12px 12px; font: inherit; outline: none; pointer-events: none; }
.cnode.is-edit .note textarea { pointer-events: auto; cursor: text; }
.handle { display: none; position: absolute; width: 10px; height: 10px; border: 2px solid #1d63ff; border-radius: 2px; background: #fff; }
.cnode.is-on .handle { display: block; }
.handle.tl { left: -6px; top: -6px; cursor: nwse-resize; }
.handle.tr { right: -6px; top: -6px; cursor: nesw-resize; }
.handle.bl { left: -6px; bottom: -6px; cursor: nesw-resize; }
.handle.br { right: -6px; bottom: -6px; cursor: nwse-resize; }
.cv-top, .cv-dock, .cv-zoom { position: absolute; z-index: 6; }
.cv-side {
  position: absolute; z-index: 7; right: 16px; top: 64px; bottom: 76px; width: 300px;
  display: none; overflow: auto; border-radius: 16px; padding: 14px;
  border: 1px solid rgba(23, 49, 90, .08); background: rgba(255, 255, 255, .98);
  box-shadow: 0 16px 40px rgba(23, 49, 90, .16);
}
.cv-side.is-open { display: block; }
.cv-side h3 { margin: 0 0 8px; font-size: 16px; }
.cv-panel, .cv-side { display: grid; gap: 10px; align-content: start; }
.cv-top { top: 12px; left: 12px; display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 14px; background: rgba(255,255,255,.96); box-shadow: 0 8px 24px rgba(23,49,90,.1); }
.cv-top button { border: 0; background: transparent; cursor: pointer; color: #334155; }
.cv-top input { width: 180px; border: 0; background: transparent; font: inherit; font-weight: 600; color: #17315a; }
.cv-top em { color: #94a3b8; font-style: normal; font-size: 12px; }
.cv-dock, .cv-zoom { display: flex; align-items: center; gap: 4px; height: 48px; padding: 0 8px; border-radius: 14px; background: rgba(255,255,255,.96); box-shadow: 0 10px 28px rgba(23,49,90,.12); }
.cv-dock { left: 50%; bottom: 16px; transform: translateX(-50%); }
.cv-zoom { left: 16px; bottom: 16px; }
.cv-dock i { width: 1px; height: 20px; background: #e2e8f0; margin: 0 4px; }
.cv-dock button, .cv-zoom button { height: 34px; padding: 0 10px; border: 0; border-radius: 9px; background: transparent; cursor: pointer; color: #17315a; }
.cv-dock button:hover, .cv-zoom button:hover { background: #eef4ff; color: #1d63ff; }
.cv-dock .danger { color: #d14343; }
.cv-zoom em { min-width: 42px; text-align: center; font-style: normal; font-weight: 600; }
.cv-side select, .cv-side input[type="text"] { width: 100%; height: 38px; border: 1px solid #e2e8f0; border-radius: 10px; padding: 0 10px; font: inherit; background: #fff; color: #17315a; }
/* 「尺寸」那个可输入下拉（ui.js 的 `upgradeCombos`）也要跟旁边这排一模一样：全站 .combo 的口径
   是 32px + --radius，落到画布侧栏就跟邻居差一截，看着像坏了一个框。画布是独立系统，外观归它自己。 */
.cv-side .combo .select-btn { height: 38px; border-color: #e2e8f0; border-radius: 10px; font: inherit; color: #17315a; }
.cv-side .combo .select-caret { color: #17315a; }
.cv-side label { display: grid; gap: 4px; color: #64748b; font-size: 12px; }
.actbar { display: flex; flex-wrap: wrap; gap: 6px; }
.actbar button { height: 34px; padding: 0 12px; border: 1px solid #e2e8f0; border-radius: 10px; background: #fff; cursor: pointer; font: inherit; color: #17315a; }
.actbar button:hover:not(:disabled) { border-color: #1d63ff; color: #1d63ff; }
.actbar button:disabled { opacity: .4; cursor: default; }
.actbar .danger { color: #d14343; }
.cv-side .hint { margin: 0; color: #94a3b8; font-size: 13px; line-height: 1.5; }
.cv-side .quote { margin: 0; color: #17315a; font-size: 13px; white-space: pre-wrap; line-height: 1.5; }
.cv-marquee { position: absolute; z-index: 5; border: 1px solid #1d63ff; background: rgba(29,99,255,.12); pointer-events: none; }
.cv-overlay { position: absolute; inset: 0; z-index: 20; display: grid; place-items: center; background: rgba(15,23,42,.35); }
.cv-dialog { width: min(640px, calc(100% - 32px)); padding: 14px; border-radius: 16px; background: #fff; }
.cv-dialog header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-weight: 600; }
.cv-dialog header button { border: 0; background: transparent; font-size: 18px; cursor: pointer; }
.cv-dialog img, .cv-dialog video { width: 100%; max-height: 70vh; object-fit: contain; background: #111; border-radius: 12px; }
.cv-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
.cv-crop { position: relative; }
.cv-crop [data-crop] { position: absolute; border: 2px solid #fff; box-shadow: 0 0 0 999px rgba(0,0,0,.35); cursor: move; }
