/* ============================================================================
   无限画布 · 工作台 —— 视觉基准层（studio.css）
   ----------------------------------------------------------------------------
   本文件在 style.css 之后加载，负责把整体外观对齐参考图：
     · #141414 画布 + 极淡点阵（无渐变、无暗角、无线框网格）
     · 节点 = 「浮在卡片外的标题」+「深灰圆角卡片」+「卡片下方独立的功能区卡片」
     · 圆形 + 端口浮在卡片左右两侧中点
     · 右键菜单 = 「添加节点」面板（搜索框 + 图标列表 + 分组标签）
     · 顶部工具栏整体搬到左侧垂直居中图标栏（上下各留约 1/3 空白）
   只做覆盖与新增，不改动 style.css 里既有的业务规则。
   ========================================================================== */

:root {
  /* ---------- 画布 ---------- */
  --bg-app: #0a0a0b;
  /* 画布底色 = 参考图实测值。这里原本是 #0d0d0e（覆盖了 style.css 的 #0a0b0d），
     比参考图暗 7 个灰阶；参考图原图 2468×1280 采样，#141414 占 67% 像素。 */
  --bg-canvas: #141414;

  /* ---------- 面 ----------
     参考图里「节点卡片 / 节点下方输入框 / 右键菜单」是同一个颜色 #262626 ——
     三处独立采样分别占 91% / 64% / 61% 的像素，不是估计值。
     我们原来是 #1b1b1e（比参考图暗 11 个灰阶，还偏蓝）。
     右键菜单是半透明毛玻璃，0.94×底 + 0.06×画布(20) = 38 才能落到 #262626，
     所以菜单的底色调到 #272727 而不是直接写 #262626。 */
  --bg-panel: #17171a;
  --bg-node: #262626;
  --bg-node-head: #262626;
  --bg-inner: #262626;
  --bg-input: #262626;
  --bg-hover: #333333;
  --bg-menu-glass: rgba(39, 39, 39, 0.94);
  --bg-rail: rgba(24, 24, 27, 0.9);

  /* ---------- 线 ---------- */
  --border: rgba(255, 255, 255, 0.075);
  --border-strong: rgba(255, 255, 255, 0.14);

  /* ---------- 字 ---------- */
  --text-primary: #ececee;
  --text-muted: #8e8e96;
  --text-faint: #5c5c64;

  /* ---------- 强调 ---------- */
  --accent: #2ad3d8;
  --accent-hover: #46e2e6;
  --accent-soft: rgba(42, 211, 216, 0.12);
  --accent-line: rgba(42, 211, 216, 0.4);
  --accent-glow: rgba(42, 211, 216, 0.26);

  /* ---------- 语义 ---------- */
  --danger: #ff5f5f;
  --danger-soft: rgba(255, 95, 95, 0.13);
  --warn: #ffab3d;
  --success: #34d399;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.55);
  --shadow-lg: 0 22px 60px rgba(0, 0, 0, 0.68);
  --radius: 16px;
  --radius-sm: 10px;

  /* ---------- 尺寸 ---------- */
  --rail-gap: 4px;
  --rail-btn: 32px;
}

/* 声明在文档根上：<select> 展开后的弹层属于顶层渲染，
 * 只写在 body 上时，部分 Chromium/Edge 版本不会把它算进去 —— 那时弹层就用系统浅色底，
 * 配上继承来的浅色文字，成了「白底白字」。根元素上写一份，弹层才跟着主题走。 */
html { color-scheme: dark; }

body {
  background: var(--bg-canvas);
  color-scheme: dark;
}

/* ============================================================================
   ① 画布背景：#141414 纯色底 + 极淡点阵（按参考图逐像素实测）
   ----------------------------------------------------------------------------
   实测参考图（1135×628，逐像素扫描）：
     底色       #141414                      —— 占 98.55% 像素
     点阵间距   横 23.17px / 纵 23.15px      —— 49 列 27 行实测平均，误差 <0.02px
     圆点       覆盖面积 ≈2.2px²（等效半径 0.837px），核心色 #414141
                = rgba(255,255,255,0.19) 叠在 #141414 上（20 + 0.19×235 = 65）
                实现用 24×24 的内联 SVG（cx/cy=12.5 落在像素中心，r=0.86）：
                实测等效半径 0.84 —— 和参考图的 0.837 对齐。
                注意**不要**改回 CSS radial-gradient：半径 <1px 时 Chromium 会把它
                按最小特征尺寸放大着画，实测等效半径 1.11（墨量多 75%），
                而且把 0.85px→1.6px 改成 0.7px→1.35px 渲染结果一模一样，调不动。
   之前我判断「参考图是纯色、没有网格」是错的：一个圆点只占 0.4% 的像素，
   整行平均亮度只被抬高 0.18 个灰阶，加上参考图是 JPEG，很容易被当成压缩噪声。
   现在按实测补回点阵；点距取 24px（参考图那次截图的缩放约 96.5%，24 是整数值）。
   点阵的平移/缩放由 js/app.js 的 applyGrid() 跟着视口一起动。
   ========================================================================== */

.canvas {
  inset: 0;
  background-color: var(--bg-canvas);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Ccircle cx='12.5' cy='12.5' r='0.86' fill='%23ffffff' fill-opacity='0.19'/%3E%3C/svg%3E");
  background-size: 24px 24px;
  background-position: 0 0;
}

.world { z-index: 2; }

/* 画布底部提示条弱化，参考图里没有，但保留可用性 */
.canvas-hint {
  bottom: 16px;
  left: 50%;
  background: rgba(22, 22, 25, 0.72);
  border-color: var(--border);
  color: var(--text-faint);
  font-size: 11.5px;
  backdrop-filter: blur(10px);
  z-index: 6;
}

/* ============================================================================
   ② 节点：标题浮在卡片外 / 卡片 / 端口 / 功能区
   ========================================================================== */

.node {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: transparent;
  border: none;
  box-shadow: none;
  border-radius: 0;
  overflow: visible;
  padding: 0;
}

/* ---- 卡片上方的浮动标题 ---- */
.node-label {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 20px;
  padding: 0 2px;
  font-size: 12.5px;
  color: var(--text-muted);
  user-select: none;
  cursor: grab;
  white-space: nowrap;
  overflow: hidden;
}

.node-label:active { cursor: grabbing; }

.node-label .nl-icon {
  display: inline-flex;
  width: 15px;
  height: 15px;
  color: var(--text-faint);
  flex: 0 0 auto;
}

.node-label .nl-icon svg { width: 15px; height: 15px; display: block; }

.node-label .nl-text {
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 500;
  letter-spacing: 0.01em;
}

.node.selected .node-label .nl-text { color: var(--accent); }

/* 标题右侧的更多菜单按钮 */
.node-label .nl-more {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--text-faint);
  border-radius: 6px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.14s ease, background 0.14s ease;
  font-size: 13px;
  line-height: 1;
  padding: 0;
}

.node:hover .node-label .nl-more { opacity: 1; }
.node-label .nl-more:hover { background: var(--bg-inner); color: var(--text-primary); }

/* ---- 主卡片 ---- */
.node .card {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  background: var(--bg-node);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}

.node.selected .card {
  border-color: rgba(42, 211, 216, 0.55);
  box-shadow: 0 0 0 2px var(--accent-glow), var(--shadow-md);
}

.node.connect-target .card {
  border-color: rgba(52, 211, 153, 0.6);
  box-shadow: 0 0 0 2px rgba(52, 211, 153, 0.24), var(--shadow-md);
}

.node[data-status="running"] .card { border-color: rgba(255, 171, 61, 0.5); }
.node[data-status="error"] .card { border-color: rgba(255, 95, 95, 0.45); }

.card-body {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ---- 空状态：居中的示意图形 + 「尝试：」建议列表 ---- */
.empty-state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 18px 20px;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.15s ease;
}

.card.has-content .empty-state,
.card.is-editing .empty-state { opacity: 0; }

.es-glyph {
  color: #3f3f46;
  margin-bottom: 12px;
}

.es-glyph svg { display: block; }

.es-title {
  align-self: flex-start;
  font-size: 12px;
  color: var(--text-faint);
  margin: 0 0 6px 2px;
}

.es-list {
  align-self: flex-start;
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
}

.es-item {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 8px;
  font-size: 13px;
  color: #d5d5da;
  background: transparent;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  pointer-events: auto;
  transition: background 0.13s ease;
}

.es-item:hover { background: rgba(255, 255, 255, 0.055); }

.es-item .es-ico {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  color: #9a9aa2;
}

.es-item .es-ico svg { width: 16px; height: 16px; display: block; }

/* ---- 文本节点：整卡片就是输入区 ---- */
.card-body > .prompt-area {
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  padding: 14px 16px;
  border: none;
  outline: none;
  resize: none;
  background: transparent;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13.5px;
  line-height: 1.7;
}

.card-body > .prompt-area::placeholder { color: transparent; }

/* ---- 图片 / 视频主体的媒体区 ---- */
.media-area {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #131316;
  overflow: hidden;
}

.media-area img,
.media-area video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  background: #0c0c0d;
}

/* ============================================================================
   ③ 卡片下方的功能区（独立卡片）
   ========================================================================== */

.node-dock {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 9px 11px 8px;
  background: var(--bg-node);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  transition: border-color 0.16s ease;
}

.node-dock:focus-within { border-color: rgba(255, 255, 255, 0.16); }

.dock-chips {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
}

.dock-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 9px;
  font-size: 12px;
  font-family: inherit;
  color: #9d9da5;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.13s ease;
}

.dock-chip:hover { background: rgba(255, 255, 255, 0.09); color: #e2e2e6; }
.dock-chip.on {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}

.dock-chip svg { width: 13px; height: 13px; display: block; }

.dock-input {
  width: 100%;
  min-height: 42px;
  max-height: 132px;
  padding: 2px 3px;
  border: none;
  outline: none;
  resize: none;
  background: transparent;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13px;
  line-height: 1.62;
  overflow-y: auto;
}

.dock-input::placeholder { color: var(--text-faint); }

/* ---- 工具条：模型 | 规格 | 图标 ---- */
.dock-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  min-height: 28px;
}

.dock-spacer { flex: 1 1 auto; }

.dock-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 8px;
  font-size: 12px;
  font-family: inherit;
  color: #b6b6bd;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.13s ease;
  max-width: 100%;
  overflow: hidden;
}

.dock-btn:hover { background: rgba(255, 255, 255, 0.07); color: #ececee; }
.dock-btn .db-txt { overflow: hidden; text-overflow: ellipsis; }
.dock-btn .db-caret { color: var(--text-faint); font-size: 9px; }
.dock-btn svg { width: 14px; height: 14px; display: block; flex: 0 0 auto; }

.dock-sep {
  width: 1px;
  height: 14px;
  background: var(--border-strong);
  flex: 0 0 auto;
}

.dock-icon {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #9d9da5;
  background: transparent;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  position: relative;
  transition: all 0.13s ease;
  flex: 0 0 auto;
}

.dock-icon:hover { background: rgba(255, 255, 255, 0.07); color: #ececee; }
.dock-icon svg { width: 15px; height: 15px; display: block; }

.dock-icon.has-dot::after {
  content: "";
  position: absolute;
  top: 4px;
  right: 4px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

/* 生成（发送）按钮：参考图里是右侧圆形高亮按钮 */
.dock-send {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  color: #d8d8dd;
  cursor: pointer;
  flex: 0 0 auto;
  transition: all 0.14s ease;
}

.dock-send:hover { background: var(--accent); color: #06202a; }
.dock-send.primary { background: var(--accent); color: #06202a; }
.dock-send[disabled] { opacity: 0.45; cursor: default; }
.dock-send svg { width: 15px; height: 15px; display: block; }

/* ============================================================================
   ④ 圆形「+」端口
   ========================================================================== */

.node .port {
  position: absolute;
  top: 50%;
  width: 22px;
  height: 22px;
  margin-top: -11px;
  border-radius: 50%;
  background: #1b1b1e;
  border: 1px solid var(--border-strong);
  color: #8b8b93;
  font-size: 13px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: crosshair;
  padding: 0;
  z-index: 4;
  opacity: 1;
  transition: all 0.14s ease;
}

.node .port-in { left: -11px; }
.node .port-out { right: -11px; }

.node .port:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: #12262a;
  transform: scale(1.12);
}

.node .port.port-in { cursor: crosshair; }

/* 缩放手柄：做成右下角一个低调的小角 */
.node-resize {
  position: absolute;
  right: -4px;
  bottom: -4px;
  width: 18px;
  height: 18px;
  z-index: 4;
  opacity: 0;
  transition: opacity 0.14s ease;
}

.node:hover .node-resize { opacity: 1; }

/* ============================================================================
   ⑤ 生成中 / 失败浮层
   ========================================================================== */

.node-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 16px;
  text-align: center;
  background: rgba(14, 14, 16, 0.9);
  backdrop-filter: blur(4px);
  font-size: 12px;
  color: var(--text-muted);
  z-index: 3;
}

.node-overlay.running { color: var(--warn); font-weight: 600; }
.node-overlay.error { color: var(--danger); }
.node-overlay .err-msg { max-height: 120px; overflow: auto; }
.node-overlay .retry-btn {
  margin-top: 6px;
  padding: 5px 16px;
  font-size: 12px;
  font-family: inherit;
  color: #fff;
  background: var(--danger);
  border: none;
  border-radius: 8px;
  cursor: pointer;
}

/* ============================================================================
   ⑥ 左侧垂直图标栏（原顶部工具栏）
   ========================================================================== */

.rail {
  position: fixed;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--rail-gap);
  padding: 7px;
  border-radius: 18px;
  background: var(--bg-rail);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  /* 上下各留约 1/3：栏体最多占视口中间的 1/3，超出部分内部滚动 */
  max-height: 33.4vh;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
}

.rail::-webkit-scrollbar { width: 0; height: 0; }

.rail-btn {
  position: relative;
  width: var(--rail-btn);
  height: var(--rail-btn);
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: #a6a6ae;
  border-radius: 10px;
  cursor: pointer;
  font-family: inherit;
  padding: 0;
  transition: background 0.14s ease, color 0.14s ease;
}

.rail-btn svg { width: 17px; height: 17px; display: block; }

.rail-btn:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
.rail-btn:active { background: rgba(255, 255, 255, 0.13); }

.rail-btn.accent { color: var(--accent); }
.rail-btn.accent:hover { background: var(--accent-soft); }

.rail-btn.primary {
  background: var(--accent);
  color: #052226;
  box-shadow: 0 3px 14px var(--accent-glow);
}

.rail-btn.primary:hover { background: var(--accent-hover); color: #052226; }

.rail-sep {
  width: 20px;
  height: 1px;
  flex: 0 0 auto;
  margin: 3px 0;
  background: var(--border);
}

/* 悬停中文提示 ——
   注意：.rail 为了「上下各留 1/3」开了 overflow-y:auto，而它自己又带
   backdrop-filter（会生成 containing block），所以伪元素形式的提示一定会被裁掉。
   这里改成由 JS 渲染一个挂到 <body> 的 .rail-tip，保证任何高度都完整显示。 */
.rail-tip {
  position: fixed;
  z-index: 400;
  padding: 5px 10px;
  font-size: 12px;
  font-weight: 500;
  font-family: inherit;
  line-height: 1.2;
  white-space: nowrap;
  color: #f0f0f2;
  background: rgba(38, 38, 42, 0.97);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  box-shadow: var(--shadow-md);
  pointer-events: none;
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 0.13s ease, transform 0.13s ease;
}

.rail-tip.on { opacity: 1; transform: translateX(0); }

/* 窄屏收紧 */
@media (max-height: 720px) {
  :root { --rail-btn: 28px; }
  .rail { padding: 5px; gap: 3px; }
  .rail-btn svg { width: 15px; height: 15px; }
  .rail-btn::after { display: none; }
}

/* 批量生成进度：脱离图标栏的布局流，浮在左上角当 HUD */
.rail .tb-badge {
  position: fixed;
  left: 74px;
  top: 16px;
  margin: 0;
  padding: 3px 9px;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: #b9f5f7;
  background: rgba(42, 211, 216, 0.15);
  border: 1px solid rgba(42, 211, 216, 0.38);
  border-radius: 999px;
  z-index: 95;
  pointer-events: none;
}

.rail .tb-badge[hidden] { display: none !important; }

/* ============================================================================
   ⑦ 右键菜单：添加节点面板
   ========================================================================== */

.ctx-menu {
  min-width: 268px;
  max-width: 320px;
  padding: 8px;
  background: var(--bg-menu-glass);
  backdrop-filter: blur(24px) saturate(150%);
  -webkit-backdrop-filter: blur(24px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 16px;
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.7),
    0 2px 10px rgba(0, 0, 0, 0.45);
  animation: ctxIn 0.14s cubic-bezier(0.22, 0.9, 0.32, 1.1);
  max-height: min(72vh, 640px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.ctx-menu::-webkit-scrollbar { width: 6px; }
.ctx-menu::-webkit-scrollbar-thumb { background: #3a3a42; border-radius: 3px; }

@keyframes ctxIn {
  from { opacity: 0; transform: scale(0.965) translateY(-4px); }
  to { opacity: 1; transform: none; }
}

/* 顶部：标题 + 搜索 */
.ctx-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 6px 8px;
}

.ctx-search input {
  flex: 1 1 auto;
  min-width: 0;
  height: 30px;
  padding: 0 10px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 9px;
  outline: none;
}

.ctx-search input::placeholder { color: var(--text-faint); }
.ctx-search input:focus { border-color: var(--accent-line); background: rgba(255, 255, 255, 0.08); }

.ctx-search .ctx-search-ico {
  display: inline-flex;
  width: 18px;
  height: 18px;
  color: var(--text-faint);
  flex: 0 0 auto;
}

.ctx-search .ctx-search-ico svg { width: 18px; height: 18px; display: block; }

/* 菜单项 */
.ctx-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 40px;
  padding: 8px 10px;
  font-size: 13.5px;
  font-family: inherit;
  line-height: 1.25;
  color: #e4e4e8;
  background: transparent;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  text-align: left;
  transition: background 0.12s ease;
}

.ctx-item::before { display: none; }

.ctx-item:hover { background: rgba(255, 255, 255, 0.075); color: #fff; }
.ctx-item:active { background: rgba(255, 255, 255, 0.12); }

.ctx-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  color: #a2a2aa;
  transition: color 0.12s ease;
}

.ctx-icon svg { width: 17px; height: 17px; display: block; }
.ctx-item:hover .ctx-icon { color: #fff; }

.ctx-label { flex: 1 1 auto; min-width: 0; }

/* 小徽标：Beta / NEW / 折扣 */
.ctx-tag {
  flex: 0 0 auto;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.09);
  color: #b9b9c1;
}

.ctx-tag.hot {
  background: rgba(42, 211, 216, 0.16);
  color: #63e3e7;
}

.ctx-tag.new {
  background: rgba(42, 211, 216, 0.9);
  color: #04262a;
}

/* 右侧「>」表示有子菜单 */
.ctx-arrow {
  flex: 0 0 auto;
  color: var(--text-faint);
  font-size: 15px;
  line-height: 1;
}

.ctx-item:hover .ctx-arrow { color: #cfcfd6; }

.ctx-hint {
  flex: 0 0 auto;
  padding: 1px 6px;
  font-size: 10.5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--text-faint);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border);
  border-radius: 5px;
}

.ctx-item:hover .ctx-hint { color: #b9b9c1; border-color: var(--border-strong); }

.ctx-sep {
  height: 1px;
  margin: 6px 8px;
  background: var(--border);
}

/* 分组容器：搜索过滤时整组一起隐藏 */
.ctx-section { display: block; }
.ctx-section + .ctx-section { margin-top: 2px; }

.ctx-group {
  padding: 10px 10px 4px;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-faint);
  user-select: none;
}

/* 二级菜单（脚本 / 素材库 的「›」） */
.ctx-menu.ctx-submenu {
  z-index: 310;
  min-width: 186px;
  max-width: 240px;
  padding: 6px;
  animation: ctxIn 0.12s ease;
}

.ctx-item.has-sub .ctx-label { color: inherit; }

.ctx-item.danger { color: #ff8b8b; }
.ctx-item.danger .ctx-icon { color: #ff8b8b; }
.ctx-item.danger:hover { background: var(--danger-soft); color: #ffb0b0; }

/* 空搜索结果 */
.ctx-empty {
  padding: 18px 10px;
  text-align: center;
  font-size: 12px;
  color: var(--text-faint);
}

/* 搜索过滤用：js 给元素挂 .is-hide 隐藏（hidden 属性斗不过 .ctx-item 的 display） */
.ctx-item.is-hide,
.ctx-section.is-hide,
.ctx-empty.is-hide { display: none !important; }

/* ============================================================================
   ⑧ 缩略图（右下角）：加收起按钮
   ========================================================================== */

.minimap {
  right: 14px;
  bottom: 14px;
  width: 208px;
  height: 146px;
  border-radius: 14px;
  pointer-events: auto;
  background: rgba(20, 20, 23, 0.86);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(14px);
  transition: opacity 0.16s ease;
}

.minimap.collapsed {
  width: 34px;
  height: 34px;
  border-radius: 12px;
  overflow: hidden;
}

/* 收起时只藏「内容」，绝不能连 .minimap-tools 一起藏 ——
   展开按钮就住在里面，把它藏掉就等于把门从外面锁死，再也点不回来。
   这里同时藏掉 .minimap-body（canvas / 标签 / 空态都在它里面，
   且它铺满整块会挡住小方块的点击命中）。 */
.minimap.collapsed canvas,
.minimap.collapsed .minimap-tag,
.minimap.collapsed .minimap-empty,
.minimap.collapsed .minimap-body { display: none; }

.minimap-body { position: absolute; inset: 0; }
.minimap canvas { width: 100%; height: 100%; }

.minimap-tools {
  position: absolute;
  top: 5px;
  right: 5px;
  display: flex;
  gap: 3px;
  z-index: 3;
}

/* 缩略图内的图标按钮：只给图标，不挂中文提示 */
.mm-btn {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.07);
  color: #b4b4bc;
  cursor: pointer;
  padding: 0;
  transition: all 0.13s ease;
}

.mm-btn:hover { background: rgba(255, 255, 255, 0.16); color: #fff; }
.mm-btn svg { width: 13px; height: 13px; display: block; }

/* 收起态：整个 34×34 的小方块就是「展开」按钮 ——
   按钮撑满整块，点方块的任何位置都能展开（只留正中 13px 的图标太难点）。
   .minimap-tools 在这一态必须显式 display:flex，否则会被上面的
   「收起时藏内容」规则继承成 none。 */
.minimap.collapsed .minimap-tools {
  display: flex;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  top: 0;
  right: 0;
}

.minimap.collapsed .mm-btn {
  width: 100%;
  height: 100%;
  border-radius: 12px;
  background: transparent;
}

.minimap.collapsed .mm-btn:hover {
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
}

/* 视图控制（缩放 / 适应）：贴在缩略图左上 */
.view-cluster {
  position: absolute;
  right: 14px;
  bottom: 168px;
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 4px;
  border-radius: 12px;
  background: var(--bg-rail);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
  backdrop-filter: blur(14px);
  z-index: 6;
  transition: bottom 0.16s ease;
}

.view-cluster.minimap-collapsed { bottom: 56px; }

.view-cluster .vw-btn {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: #b4b4bc;
  border-radius: 8px;
  cursor: pointer;
  padding: 0;
  font-family: inherit;
  transition: all 0.13s ease;
}

.view-cluster .vw-btn:hover { background: rgba(255, 255, 255, 0.09); color: #fff; }
.view-cluster .vw-btn svg { width: 14px; height: 14px; display: block; }

.view-cluster .vw-zoom {
  min-width: 40px;
  padding: 0 4px;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  text-align: center;
  user-select: none;
  cursor: pointer;
}

/* ============================================================================
   ⑨ 面板 / 弹窗：统一到参考图的观感
   ========================================================================== */

.panel,
.wf-panel {
  background: var(--bg-panel);
  border: 1px solid var(--border-strong);
  border-radius: 18px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.72);
}

.panel-head {
  height: 54px;
  padding: 0 10px 0 20px;
  border-bottom: 1px solid var(--border);
}

.panel-head h3 { font-size: 15px; font-weight: 600; letter-spacing: 0.01em; }

.panel-close {
  width: 32px;
  height: 32px;
  border-radius: 9px;
  color: var(--text-muted);
}

.panel-close:hover { background: rgba(255, 255, 255, 0.07); color: #fff; }

.head-btn {
  height: 30px;
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  color: #d6d6dc;
}

.head-btn:hover { background: rgba(255, 255, 255, 0.1); }
.head-btn.primary { background: var(--accent); border-color: var(--accent); color: #04262a; }

.modal-backdrop { background: rgba(0, 0, 0, 0.66); backdrop-filter: blur(5px); }

/* ============================================================================
   ⑩ 资产库
   ========================================================================== */

.lib-panel {
  width: min(1080px, calc(100vw - 40px));
  height: min(780px, calc(100vh - 90px));
}

.lib-layout {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: 208px 1fr;
}

.lib-side {
  min-height: 0;
  overflow-y: auto;
  padding: 12px 10px;
  border-right: 1px solid var(--border);
}

.lib-side-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  margin: 10px 4px 6px;
}

.lib-side-label:first-child { margin-top: 2px; }

.lib-tab {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 9px 10px;
  margin-bottom: 3px;
  font-family: inherit;
  font-size: 13px;
  color: #ccced4;
  background: transparent;
  border: none;
  border-radius: 9px;
  cursor: pointer;
  text-align: left;
  transition: all 0.13s ease;
}

.lib-tab:hover { background: rgba(255, 255, 255, 0.06); }

.lib-tab.active {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.lib-tab .lt-ico { width: 16px; height: 16px; display: inline-flex; flex: 0 0 auto; }
.lib-tab .lt-ico svg { width: 16px; height: 16px; display: block; }
.lib-tab .lt-count {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.lib-sub {
  margin: 2px 0 8px 12px;
  padding-left: 9px;
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.lib-sub button {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 8px;
  font-family: inherit;
  font-size: 12.5px;
  color: var(--text-muted);
  background: transparent;
  border: none;
  border-radius: 7px;
  cursor: pointer;
  text-align: left;
  transition: all 0.13s ease;
}

.lib-sub button:hover { background: rgba(255, 255, 255, 0.055); color: #e6e6ea; }
.lib-sub button.active { color: var(--accent); font-weight: 600; }
.lib-sub .ls-count { margin-left: auto; font-size: 10.5px; color: var(--text-faint); }

.lib-main {
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.lib-toolbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}

.lib-search {
  flex: 1 1 200px;
  min-width: 140px;
  height: 32px;
  padding: 0 11px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 9px;
  outline: none;
}

.lib-search::placeholder { color: var(--text-faint); }
.lib-search:focus { border-color: var(--accent-line); }

.lib-user {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 10px;
  border-radius: 9px;
  font-size: 12px;
  color: var(--text-muted);
  background: rgba(255, 255, 255, 0.05);
}

.lib-user .lu-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-faint);
}

.lib-user.online .lu-dot { background: var(--success); box-shadow: 0 0 8px rgba(52, 211, 153, 0.7); }

.lib-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 16px;
}

.lib-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
}

.lib-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--bg-inner);
  cursor: pointer;
  transition: all 0.15s ease;
}

.lib-card:hover { border-color: var(--accent-line); transform: translateY(-2px); }

.lib-thumb {
  position: relative;
  aspect-ratio: 1 / 1;
  background: #0e0e10;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.lib-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.lib-thumb .lib-badge {
  position: absolute;
  left: 6px;
  top: 6px;
  padding: 1px 6px;
  font-size: 10px;
  border-radius: 5px;
  background: rgba(10, 10, 12, 0.75);
  color: #cfd0d6;
  backdrop-filter: blur(6px);
}

.lib-thumb .lib-cloud {
  position: absolute;
  right: 6px;
  top: 6px;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 5px;
  background: rgba(10, 10, 12, 0.72);
  color: var(--accent);
}

.lib-thumb .lib-cloud svg { width: 11px; height: 11px; display: block; }

.lib-meta {
  padding: 8px 9px 9px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.lib-title {
  font-size: 12px;
  color: #dcdce1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lib-sub-title {
  font-size: 10.5px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lib-card-acts {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  gap: 5px;
  padding: 7px;
  background: linear-gradient(to top, rgba(10, 10, 12, 0.94), rgba(10, 10, 12, 0));
  opacity: 0;
  transition: opacity 0.14s ease;
}

.lib-card:hover .lib-card-acts { opacity: 1; }

.lib-act {
  flex: 1 1 auto;
  height: 26px;
  font-size: 11.5px;
  font-family: inherit;
  color: #e8e8ec;
  background: rgba(255, 255, 255, 0.12);
  border: none;
  border-radius: 7px;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: all 0.13s ease;
}

.lib-act:hover { background: var(--accent); color: #04262a; }
.lib-act.ghost { flex: 0 0 auto; padding: 0 9px; }
.lib-act.danger:hover { background: var(--danger); color: #fff; }

.lib-empty {
  padding: 64px 20px;
  text-align: center;
  color: var(--text-faint);
  font-size: 13px;
  line-height: 1.8;
}

/* ============================================================================
   ⑪ 通用：表单控件在深色底上的观感
   ========================================================================== */

.panel input[type="text"],
.panel input[type="password"],
.panel input[type="number"],
.panel select,
.panel textarea,
.wf-panel input[type="text"],
.wf-panel input[type="password"],
.wf-panel input[type="number"],
.wf-panel select,
.wf-panel textarea,
.lib-panel input,
.lib-panel select,
.lib-panel textarea {
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 10px;
}

.panel input:focus,
.panel select:focus,
.panel textarea:focus,
.wf-panel input:focus,
.wf-panel select:focus,
.wf-panel textarea:focus {
  border-color: var(--accent-line);
  background: rgba(255, 255, 255, 0.08);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.chip {
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.05);
  border-color: transparent;
}

/* ----------------------------------------------------------------------------
   原生下拉的弹层：白底白字 = 「能点但看不见」
   ----------------------------------------------------------------------------
   <select> 展开后的那个列表不是 DOM 里画出来的，Windows 上由系统绘制：
   底色默认走浅色，而 option 的文字颜色继承自深色主题的浅色前景 ——
   于是就成了「白底白字」，选项其实都在、能选，但一个字也看不见
   （用户报的原话：「这些选项能选但是看不到」）。

   修法两条一起上：
     1) 给 select 声明 color-scheme: dark，让弹层整体走深色；
     2) 再显式给 option 上背景与前景色 —— 部分 Chromium/Edge 版本对
        原生弹层不理 color-scheme，但认 option 的自己的颜色。
   这两条必须放在面板作用域之外，才对任意位置的 select 都生效。
-------------------------------------------------------------------------- */
select,
select option,
select optgroup {
  color-scheme: dark;
}

select option,
select optgroup {
  background-color: #26262c;
  color: #eceaf0;
}

select optgroup {
  background-color: #1c1c21;
  color: #9aa1a9;
  font-style: normal;
}

select option:checked {
  background-color: #17708a;
  color: #ffffff;
}

.chip.active { background: var(--accent); color: #04262a; }

.wide-btn { border-radius: 10px; }

.toast {
  border-radius: 12px;
  background: #2b2b30;
  border-color: var(--border-strong);
}

/* 滚动条统一 */
.lib-body::-webkit-scrollbar,
.lib-side::-webkit-scrollbar,
.panel-body::-webkit-scrollbar { width: 8px; }

.lib-body::-webkit-scrollbar-thumb,
.lib-side::-webkit-scrollbar-thumb { background: #35353c; border-radius: 4px; }

/* ============================================================================
   ⑫ 节点功能区弹出菜单（模型 / 模式 / 规格）
   ========================================================================== */

.dock-menu {
  position: fixed;
  z-index: 300;
  min-width: 210px;
  max-width: min(340px, calc(100vw - 20px));
  max-height: 62vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 10px;
  background: var(--bg-menu-glass);
  backdrop-filter: blur(24px) saturate(150%);
  -webkit-backdrop-filter: blur(24px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 14px;
  box-shadow: 0 22px 54px rgba(0, 0, 0, 0.66), 0 2px 9px rgba(0, 0, 0, 0.42);
  animation: ctxIn 0.13s cubic-bezier(0.22, 0.9, 0.32, 1.1);
}

.dock-menu::-webkit-scrollbar { width: 6px; }
.dock-menu::-webkit-scrollbar-thumb { background: #3a3a42; border-radius: 3px; }

.dm-group + .dm-group {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.dm-label {
  margin: 0 2px 7px;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-faint);
  user-select: none;
}

.dm-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.dm-chip {
  padding: 5px 10px;
  font-family: inherit;
  font-size: 12px;
  line-height: 1.2;
  color: #d4d4d9;
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.12s ease;
}

.dm-chip:hover { background: rgba(255, 255, 255, 0.11); color: #fff; }

.dm-chip.active {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
  font-weight: 600;
}

.dm-list { display: flex; flex-direction: column; gap: 2px; }

.dm-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 10px;
  font-family: inherit;
  font-size: 12.5px;
  color: #dcdee4;
  background: transparent;
  border: none;
  border-radius: 9px;
  cursor: pointer;
  text-align: left;
  transition: background 0.12s ease;
}

.dm-item:hover { background: rgba(255, 255, 255, 0.075); color: #fff; }
.dm-item.active { background: var(--accent-soft); color: var(--accent); }

.dm-item-t { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dm-item-s { flex: 0 0 auto; font-size: 10.5px; color: var(--text-faint); }

.dm-note {
  padding: 10px 4px;
  font-size: 12px;
  line-height: 1.7;
  color: var(--text-faint);
}

/* ============================================================================
   ⑬ 资产库登录界面
   ========================================================================== */

.auth-box {
  max-width: 400px;
  margin: 4px auto 40px;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.022);
}

.auth-head { text-align: center; margin-bottom: 18px; }

.auth-glyph {
  width: 42px;
  height: 42px;
  margin: 0 auto 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 13px;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
}

.auth-glyph svg { width: 20px; height: 20px; display: block; }

.auth-head h4 { margin: 0 0 8px; font-size: 15.5px; color: var(--text-primary); }

.auth-head p {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.85;
  color: var(--text-muted);
  text-align: left;
}

.auth-head p strong { color: var(--accent); }

.auth-tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  margin-bottom: 14px;
  border-radius: 11px;
  background: rgba(255, 255, 255, 0.05);
}

.auth-tab {
  flex: 1 1 0;
  padding: 8px 4px;
  font-family: inherit;
  font-size: 12.5px;
  color: var(--text-muted);
  background: transparent;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.13s ease;
}

.auth-tab:hover { color: #e6e6ea; }
.auth-tab.active { background: var(--accent); color: #04262a; font-weight: 600; }

.auth-form { display: flex; flex-direction: column; gap: 11px; }

.auth-code-row { display: flex; gap: 8px; }

.auth-code-row input {
  flex: 1 1 auto;
  min-width: 0;
  height: 38px;
  padding: 0 11px;
  font-family: inherit;
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 10px;
  outline: none;
}

.auth-code-row input:focus { border-color: var(--accent-line); }
.auth-code-row .wide-btn {
  flex: 0 0 auto;
  /* .wide-btn 基础样式带 width:100%，而 flex-basis:auto 会拿它当基准宽度，
     结果把输入框挤到 0 宽。这里必须显式收回。 */
  width: auto;
  padding: 0 14px;
  height: 38px;
}

.auth-alt { margin-top: 12px; }
.auth-alt .wide-btn { width: 100%; }

.auth-err {
  margin-bottom: 12px;
  padding: 9px 11px;
  font-size: 12px;
  line-height: 1.7;
  color: #ffb0b0;
  background: var(--danger-soft);
  border: 1px solid rgba(255, 92, 92, 0.34);
  border-radius: 10px;
}

.auth-msg {
  min-height: 18px;
  margin-top: 10px;
  font-size: 12px;
  line-height: 1.7;
  color: var(--text-faint);
}

.auth-msg.ok { color: var(--success); }
.auth-msg.error { color: #ff9b9b; }

/* ============================================================================
   ⑭ 随机角色生成器
   ========================================================================== */

.cg-modes {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
}

.cg-mode-hint {
  flex: 1 1 220px;
  min-width: 160px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-faint);
  font-style: normal;
}

.cg-layout {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
}

.cg-scroll {
  min-height: 0;
  overflow-y: auto;
  padding: 4px 16px 20px;
  border-right: 1px solid var(--border);
}

.cg-scroll::-webkit-scrollbar { width: 8px; }
.cg-scroll::-webkit-scrollbar-thumb { background: #35353c; border-radius: 4px; }

.cg-group-title {
  position: sticky;
  top: 0;
  z-index: 2;
  margin: 14px 0 6px;
  padding: 7px 0 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  background: linear-gradient(var(--bg-panel) 72%, transparent);
  user-select: none;
}

.cg-group-title:first-child { margin-top: 6px; }

.cg-row {
  display: grid;
  grid-template-columns: 108px minmax(0, 1fr) 34px;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
}

.cg-row-multi { align-items: start; }
.cg-row-multi .cg-label { padding-top: 8px; }

.cg-label {
  font-size: 12.5px;
  color: var(--text-muted);
  line-height: 1.5;
}

.cg-hint {
  display: block;
  margin-top: 2px;
  font-size: 10.5px;
  font-style: normal;
  color: var(--text-faint);
}

.cg-control { min-width: 0; }

.cg-input {
  width: 100%;
  height: 34px;
  padding: 0 9px;
  font-family: inherit;
  font-size: 12.5px;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 9px;
  outline: none;
}

textarea.cg-input { height: auto; padding: 7px 9px; line-height: 1.6; resize: vertical; }
.cg-input:focus { border-color: var(--accent-line); background: rgba(255, 255, 255, 0.08); }

.cg-lock {
  width: 30px;
  height: 30px;
  font-family: inherit;
  font-size: 11px;
  color: var(--text-faint);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.12s ease;
}

.cg-lock:hover { color: #e6e6ea; background: rgba(255, 255, 255, 0.1); }

.cg-lock.on {
  color: #04262a;
  background: var(--accent);
  font-weight: 700;
}

.cg-lock-ghost { background: transparent; border: none; pointer-events: none; }

.cg-multi { display: flex; flex-wrap: wrap; gap: 5px; padding: 2px 0; }

.cg-chip {
  padding: 4px 9px;
  font-family: inherit;
  font-size: 11.5px;
  line-height: 1.35;
  color: #cfd1d7;
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 7px;
  cursor: pointer;
  transition: all 0.12s ease;
}

.cg-chip:hover { background: rgba(255, 255, 255, 0.11); }

.cg-chip.on {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
  font-weight: 600;
}

.cg-out {
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 14px;
  background: rgba(0, 0, 0, 0.18);
}

.cg-out-label {
  flex: 0 0 auto;
  margin-bottom: 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text-faint);
}

.cg-out-text {
  flex: 1 1 auto;
  min-height: 200px;
  width: 100%;
  padding: 10px 11px;
  font-family: inherit;
  font-size: 12.5px;
  line-height: 1.8;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
  border-radius: 11px;
  outline: none;
  resize: none;
}

.cg-out-text:focus { border-color: var(--accent-line); }

.cg-out-acts {
  flex: 0 0 auto;
  display: flex;
  gap: 7px;
  margin-top: 10px;
}

.cg-out-acts .head-btn { flex: 1 1 0; }

/* 窄屏：右栏落到下面去 */
@media (max-width: 900px) {
  .cg-layout { grid-template-columns: minmax(0, 1fr); }
  .cg-scroll { border-right: none; border-bottom: 1px solid var(--border); }
  .cg-out { height: 260px; }
  .cg-row { grid-template-columns: 92px minmax(0, 1fr) 32px; }
}

/* ==================== ⑮ 工具箱：封面简介 / 要素提取 ==================== */

/* 这两个面板比通用面板宽一档：提取结果是一列一列的卡片，440px 塞不下 */
#coverPanel,
#extractPanel { width: min(560px, calc(100vw - 40px)); }

/* .detect-result 在 style.css 里默认 display:none，只有带 .info/.success 这类
   状态类才显示。这两个面板是自己渲染 HTML 的，所以补一个「有内容就显示」。 */
.detect-result.has-content { display: block; }
/* 结果区不自己套滚动条 —— 面板 body 已经能滚，嵌套滚动只会让人在小窗口里翻内容。
   长列表交给面板本身滚。 */
#cvResult.has-content,
#exResult.has-content { margin-top: 12px; }

/* ---------- 一行式提示条（进度 / 错误 / 说明） ---------- */
.pl-note {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 8px 10px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-muted);
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.pl-note + .pl-note { margin-top: 6px; }
.pl-note svg { width: 14px; height: 14px; flex: 0 0 auto; margin-top: 2px; color: var(--accent-hover); }
.pl-note.err { color: #ff9a9a; background: var(--danger-soft); border-color: rgba(255, 92, 92, 0.35); }
.pl-note.err svg { color: var(--danger); }

/* ---------- 三路提取进度 ---------- */
.pl-lanes { display: flex; flex-direction: column; gap: 6px; }
.pl-lane {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  font-size: 11.5px;
  color: var(--text-muted);
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.pl-lane-ic { display: inline-flex; color: var(--text-faint); }
.pl-lane-ic svg { width: 14px; height: 14px; }
.pl-lane-t { color: var(--text-primary); font-weight: 600; }
.pl-lane-n { margin-left: auto; color: var(--text-faint); font-size: 11px; }
.pl-lane.run { border-color: var(--accent-line); background: var(--accent-soft); }
.pl-lane.run .pl-lane-ic { color: var(--accent-hover); }
.pl-lane.done .pl-ok { color: var(--success); font-weight: 700; }
.pl-lane.err .pl-bad { color: var(--danger); font-weight: 700; }
.pl-wait { color: var(--text-faint); }
.pl-spin {
  width: 11px; height: 11px; flex: 0 0 auto;
  border: 1.6px solid var(--accent-line);
  border-top-color: var(--accent-hover);
  border-radius: 50%;
  animation: pl-spin 0.75s linear infinite;
}
@keyframes pl-spin { to { transform: rotate(360deg); } }

/* ---------- 封面 / 简介 的完成回执 ---------- */
.cv-done { display: flex; flex-direction: column; gap: 5px; margin-bottom: 8px; }
.cv-done-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 7px 10px;
  font-size: 11.5px;
  color: var(--text-muted);
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.cv-done-row b { color: var(--text-primary); font-weight: 600; }

/* 简介字数：合适是绿、偏短是橙、偏长是红 —— 与站点红涨绿跌无关，纯状态色 */
.cv-count.ok { color: var(--success); }
.cv-count.warn { color: var(--warn); }
.cv-count.over { color: var(--danger); }

/* ---------- 提取结果：汇总条 / 操作条 / 分组 ---------- */
.ex-sum { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.ex-sum-item {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px;
  font-size: 11.5px;
  color: var(--text-muted);
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.ex-sum-item:hover { border-color: var(--accent-line); color: var(--text-primary); }
.ex-sum-item svg { width: 13px; height: 13px; color: var(--accent-hover); }
.ex-sum-item b { color: var(--text-primary); font-weight: 600; }
.ex-sum-item em { font-style: normal; color: var(--text-faint); }

.ex-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.ex-acts .head-btn { flex: 0 0 auto; }

.ex-group + .ex-group { margin-top: 12px; }
.ex-group-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
  padding: 0 2px 6px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 8px;
}
.eg-title {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; font-weight: 600;
  color: var(--text-primary);
}
.eg-title svg { width: 14px; height: 14px; color: var(--accent-hover); }

.ex-items { display: flex; flex-direction: column; gap: 6px; }
.ex-item {
  padding: 8px 10px;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.ex-item-top { display: flex; align-items: center; gap: 6px; }
.ex-name {
  font-size: 12px; font-weight: 600;
  color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ex-pri {
  flex: 0 0 auto;
  padding: 1px 6px;
  font-size: 10px;
  border-radius: 999px;
  border: 1px solid transparent;
}
.ex-pri.major { color: #ffb46b; background: rgba(255, 165, 61, 0.14); border-color: rgba(255, 165, 61, 0.35); }
.ex-pri.minor { color: var(--text-faint); background: rgba(255, 255, 255, 0.05); border-color: var(--border); }
.ex-mini {
  margin-left: auto;
  flex: 0 0 auto;
  padding: 2px 8px;
  font-family: inherit;
  font-size: 10.5px;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
.ex-mini:hover { color: var(--text-primary); border-color: var(--border-strong); }
.ex-desc {
  margin-top: 5px;
  font-size: 11px;
  line-height: 1.65;
  color: var(--text-muted);
  word-break: break-word;
}
.ex-aliases {
  margin-top: 4px;
  font-size: 10.5px;
  color: var(--text-faint);
  word-break: break-word;
}

/* ==================== ⑯ 右上角站点名 ==================== */
/* 原来 .brand 长在横向工具栏的左端（见 style.css 的「工具栏」段）。
   工具栏整体搬到左侧图标栏之后，站点名单独固定到右上角。
   定点在 0/52px：和左侧图标栏的顶端对齐，不压住画布中央的操作区。 */
.brand-top {
  position: fixed;
  top: 0;
  right: 18px;
  height: 52px;
  z-index: 100;
  /* 去掉「左侧品牌位」留下的竖分隔线和右内边距 —— 现在是贴右边缘 */
  padding-right: 0;
  border-right: none;
  /* 文字本身不吃鼠标：别在画布右上角凭空多出一块不能拖拽的死区 */
  pointer-events: none;
  user-select: none;
}
/* 版本号胶囊要能悬停看 title，单独把事件收回来 */
.brand-top .brand-ver { pointer-events: auto; }

/* 窄屏：站点名让位给画布 —— 和 .brand-name 在工具栏时代的处理保持一致 */
@media (max-width: 720px) {
  .brand-top .brand-name { display: none; }
}

/* ==================== ⑰ 工具箱锁屏：卡在「没放行」时要说清楚 ====================
 * 背景：授权是两步 —— ① 激活码在云端兑换（成功了才弹「激活成功」），
 *       ② 站点门禁 /.gate 放行（工具箱解不解锁看的是它）。
 * 两步各自独立，② 失败时若界面上什么都不说，用户看到的就是
 * 「提示激活成功，锁还在这儿」—— 最难查、也最容易被当成 bug 的故障。
 * 所以：把原因摊开说，并给一个不用重输激活码的重试按钮。
 * ========================================================================== */

.wf-lock .gate-hint {
  /* 用醒目但不吓人的琥珀色；.wf-lock-note 的 !important 靠特异性更高的选择器压过 */
  margin: 14px 0 0 !important;
  color: var(--warn) !important;
  background: rgba(255, 171, 61, 0.09);
  border: 1px solid rgba(255, 171, 61, 0.28);
  border-radius: 8px;
  padding: 9px 11px;
}

.wf-lock .gate-retry {
  margin-top: 10px;
  font-size: 12.5px;
  padding: 8px 12px;
}

/* ==================== ⑱ 拖到画布：投放提示 ====================
 * 各面板（资产库 / 要素提取 / 封面 / 角色生成器）里的条目都可以直接拖到画布上落成节点。
 * 拖拽过程中必须给一个「松手真的会发生什么」的信号，否则用户不知道画布收不收。
 * 只在「鼠标不在任何面板上方」时亮 —— 拖到面板上不亮，用户就知道那儿不算数。
 * ============================================================ */

.drop-hint {
  position: fixed;
  left: 50%;
  /* 放顶部而不是底部：底部中间已经挤着 toast 和画布空状态提示，
     拖拽过程中不该跟它们叠在一起。 */
  top: 18px;
  transform: translate(-50%, -8px);
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(39, 39, 39, 0.94);
  color: var(--text-primary);
  border: 1px solid var(--accent-line);
  font-size: 12.5px;
  letter-spacing: 0.3px;
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  z-index: 400;
  transition: opacity 0.14s ease, transform 0.14s ease;
}

body.drop-ready .drop-hint {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* 画布亮一圈边框：一眼看出「这里可以放」 */
body.drop-ready .canvas {
  box-shadow: inset 0 0 0 2px var(--accent-line);
}

/* 被拖起来的那个条目自己淡下去，确认「拖的就是它」 */
.is-dragging {
  opacity: 0.5;
}

/* 面板里可拖的条目给个抓取手势 */
.lib-card[draggable="true"],
.ex-item[draggable="true"],
.cv-result-box[draggable="true"] {
  cursor: grab;
}

.lib-card[draggable="true"]:active {
  cursor: grabbing;
}

/* 面板里那句「可以拖到画布上」的提示 —— 拖拽是隐式能力，不写出来没人会试 */
.lib-hint {
  margin: 0 0 10px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-faint);
}

/* ==================== ⑲ 分组框 ====================
 * 多选之后按 Ctrl+G（或右键「打组」）把几个节点框成一组，
 * 组框会自动包住成员（连节点下方那条功能区一起算），成员一动框就重算。
 *
 * 关键一点：组框单独放在 #groups 这一层、并且压在节点下面（z-index 更低）。
 * 如果和节点同层，框体就会盖在节点上，把节点上的点击全吃掉。
 * ================================================= */

.groups {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
}

/* 节点永远盖在组框上面 */
.nodes { z-index: 1; }

.group-frame {
  position: absolute;
  border: 1.5px dashed rgba(120, 200, 255, 0.40);
  border-radius: 18px;
  background: rgba(120, 200, 255, 0.045);
  cursor: grab;
  transition: border-color 0.12s ease, background 0.12s ease;
}

.group-frame:hover {
  border-color: rgba(120, 200, 255, 0.62);
  background: rgba(120, 200, 255, 0.07);
}

.group-frame.selected {
  border-color: var(--accent);
  border-style: solid;
  background: rgba(56, 209, 219, 0.10);
}

.group-frame:active { cursor: grabbing; }

/* 组名：贴在框内左上角（组框上方留了 40px 的 padding，正好放它） */
.group-name {
  position: absolute;
  left: 14px;
  top: 9px;
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: rgba(150, 212, 236, 0.85);
  white-space: nowrap;
  user-select: none;
  pointer-events: none;
}

.group-frame.selected .group-name { color: var(--accent); }

/* ==================== ⑳ 从端口拉出来的「接一个新节点」菜单 ====================
 * 复用右键菜单的结构，只在顶部加一行说明，告诉用户「这条线要接去哪个节点」。
 * ======================================================================== */

.ctx-menu .ctx-link-tip {
  padding: 9px 12px 7px;
  margin: 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-faint);
  border-bottom: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.025);
}

/* ==================== ㉑ 通用确认 / 输入弹窗（替代原生 confirm / prompt） ====================
 * 原生 confirm / prompt 在应用内置预览面板（webview）里会被宿主拦掉、静默返回 false，
 * 表现就是「点了清空画布完全没反应」。这里用页面自己的弹窗顶上，任何环境行为一致。
 * 层级：高于所有面板（.panel 是 400），避免和设置面板叠在一起。
 * ========================================================================= */

.ask-modal {
  position: fixed;
  inset: 0;
  z-index: 460;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(5px);
}

.ask-modal[hidden] { display: none; }

.ask-box {
  width: 400px;
  max-width: calc(100vw - 40px);
  background: var(--bg-panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: ask-in 0.14s ease-out;
}

@keyframes ask-in {
  from { transform: translateY(8px) scale(0.98); opacity: 0; }
  to { transform: none; opacity: 1; }
}

.ask-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 50px;
  padding: 0 10px 0 20px;
  border-bottom: 1px solid var(--border);
}

.ask-head h3 { font-size: 14.5px; font-weight: 600; color: var(--text-primary); }

.ask-body { padding: 18px 20px 4px; }

.ask-desc {
  margin: 0;
  font-size: 13px;
  line-height: 1.65;
  color: var(--text-muted);
  white-space: pre-wrap;
}

.ask-desc[hidden] { display: none; }

.ask-input {
  margin-top: 12px;
  width: 100%;
  height: 38px;
  padding: 0 12px;
  font-size: 13.5px;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 10px;
  outline: none;
}

.ask-input[hidden] { display: none; }

.ask-input:focus {
  border-color: var(--accent-line);
  background: rgba(255, 255, 255, 0.08);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.ask-foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 16px 20px 18px;
}

.ask-btn {
  height: 36px;
  padding: 0 18px;
  border: none;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 500;
  color: #dcdce2;
  background: rgba(255, 255, 255, 0.07);
  cursor: pointer;
  transition: background 0.12s ease;
}

.ask-btn:hover { background: rgba(255, 255, 255, 0.12); }

.ask-btn.primary {
  background: var(--accent);
  color: #04262a;
  font-weight: 600;
}

.ask-btn.primary:hover { background: var(--accent-hover); }

.ask-btn.primary.danger { background: var(--danger); color: #fff; }
.ask-btn.primary.danger:hover { background: #ff7676; }

/* ==================== ㉒ 多选浮条 ====================
 * 选中 ≥2 个节点就浮出来。放在画布底部居中：
 * 不占节点区域、不跟着视口缩放，视线自然落在下方操作区。
 * z-index 低于面板（400），面板一开它就退到后面。
 * ================================================= */

.sel-bar {
  position: fixed;
  left: 50%;
  /* 抬到画布底部提示条（bottom:16px）和 toast 上面，三条不叠 */
  bottom: 96px;
  transform: translateX(-50%);
  z-index: 320;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 9px;
  background: var(--bg-menu-glass);
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(14px);
  animation: selbar-in 0.15s ease-out;
}

.sel-bar[hidden] { display: none; }

@keyframes selbar-in {
  from { transform: translate(-50%, 10px); opacity: 0; }
  to { transform: translate(-50%, 0); opacity: 1; }
}

.sel-count {
  padding: 0 10px 0 8px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--accent);
  white-space: nowrap;
}

.sel-sep {
  width: 1px;
  height: 18px;
  margin: 0 2px;
  background: var(--border-strong);
}

.sel-btn {
  height: 30px;
  padding: 0 13px;
  border: none;
  border-radius: 9px;
  font-size: 12.5px;
  font-weight: 500;
  color: #dcdce2;
  background: rgba(255, 255, 255, 0.06);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease;
}

.sel-btn:hover { background: rgba(255, 255, 255, 0.13); color: #fff; }

.sel-btn[hidden] { display: none; }

.sel-btn.primary { background: var(--accent); color: #04262a; font-weight: 600; }
.sel-btn.primary:hover { background: var(--accent-hover); color: #04262a; }

.sel-btn.danger:hover { background: var(--danger-soft); color: var(--danger); }

.sel-btn.ghost { background: transparent; color: var(--text-muted); }
.sel-btn.ghost:hover { background: rgba(255, 255, 255, 0.07); color: var(--text-primary); }

/* ==================== ㉓ 视频三兄弟节点：逐帧拉片 / 导演台 / 智能剪辑 ====================
 * 这三个不是「提示词 → 出图」的节点，卡片里装的是一整套操作台。
 * 所以第一件事是把固定高度放开：行数不定，写死高度只会把内容切掉。
 * 内容区各自滚动（宫格、素材条），卡片高度随内容长。
 * ============================================================================== */

.node-frame .card,
.node-director .card,
.node-clip .card { height: auto !important; }

.vn-body { gap: 8px; padding: 10px; }

/* ---- 预览区 ----
 * flex: 0 0 auto 是必须的：.vn-body 是纵向 flex，
 * 不加这一条，预览区会被后面的宫格/时间线挤扁，16:9 直接失真。 */
.vn-stage-box {
  position: relative;
  flex: 0 0 auto;
  border-radius: 12px;
  overflow: hidden;
  background: #0d0d0f;
  border: 1px solid var(--border);
  min-height: 132px;
}

.vn-video-box { aspect-ratio: 16 / 9; }

.vn-video-box video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  background: #000;
}

.vn-stage {
  width: 100%;
  display: block;
  aspect-ratio: 16 / 9;
  background: #0d0d0f;
}

.vn-stage-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 26px;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-faint);
  pointer-events: none;
}

.vn-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 16px;
}

.vn-empty-title { font-size: 12.5px; color: var(--text-faint); }

.vn-empty-btns {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}

/* ---- 按钮 ---- */
.vn-btn {
  height: 30px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  border-radius: 9px;
  font-size: 12.5px;
  font-weight: 500;
  color: #dcdce2;
  background: rgba(255, 255, 255, 0.07);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease;
}

.vn-btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.13); color: #fff; }
.vn-btn:disabled { opacity: 0.38; cursor: not-allowed; }
.vn-btn.primary { background: var(--accent); color: #04262a; font-weight: 600; }
.vn-btn.primary:hover:not(:disabled) { background: var(--accent-hover); color: #04262a; }

/* ---- 参数行 / 播放条 ---- */
.vn-controls,
.vn-playbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.vn-field {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--text-muted);
}

.vn-field select,
.vn-field input {
  height: 28px;
  padding: 0 8px;
  font-size: 12px;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 8px;
  outline: none;
}

.vn-field input[type="number"] { width: 60px; text-align: center; }
.vn-field select:focus,
.vn-field input:focus { border-color: var(--accent-line); }

.vn-unit { color: var(--text-faint); }

.vn-time {
  font-size: 11.5px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.vn-spacer { flex: 1 1 auto; }

/* ---- 帧宫格 ---- */
.vn-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(86px, 1fr));
  gap: 6px;
  max-height: 292px;
  overflow-y: auto;
  padding: 2px;
}

.vn-grid-empty {
  /* 宫格是 grid，这一条要横跨整行 —— 不然那句提示被压成一列窄字，很难读 */
  grid-column: 1 / -1;
  font-size: 12px;
  color: var(--text-faint);
  padding: 14px 4px;
  line-height: 1.6;
}

.vn-cell {
  position: relative;
  padding: 0;
  aspect-ratio: 16 / 9;
  border: 2px solid transparent;
  border-radius: 8px;
  overflow: hidden;
  background: #000;
  cursor: pointer;
  transition: border-color 0.12s ease;
}

.vn-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vn-cell:hover { border-color: rgba(255, 255, 255, 0.22); }
.vn-cell.on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-glow); }

.vn-tc {
  position: absolute;
  left: 4px;
  bottom: 3px;
  font-size: 10px;
  padding: 1px 4px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.62);
  color: #cfcfd6;
}

.vn-cell.on .vn-tc { background: rgba(42, 211, 216, 0.88); color: #04262a; font-weight: 600; }

/* ---- 底部操作行 / 状态 ---- */
.vn-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.vn-count { font-size: 11.5px; color: var(--text-muted); }

.vn-status {
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--text-faint);
}

.vn-status:empty { display: none; }
.vn-status.ok { color: var(--success); }
.vn-status.err { color: #ff8f8f; }

.vn-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: rgba(255, 255, 255, 0.08);
}

.vn-progress-bar {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width 0.15s linear;
}

/* ---- 素材条（导演台 / 剪辑的时间线） ---- */
.vn-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 2px 2px 8px;
}

.vn-strip-empty {
  font-size: 12px;
  color: var(--text-faint);
  padding: 12px 4px;
  line-height: 1.6;
  flex: 1 1 auto;
}

.vn-card {
  flex: 0 0 134px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 7px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
}

/* 只在本机有效的素材（没登录云端）挂一条琥珀边，一眼看得出哪几段换了电脑就没了 */
.vn-card.local { border-color: rgba(255, 171, 61, 0.34); }

.vn-card-thumb {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 6px;
  overflow: hidden;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.vn-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vn-thumb.empty { color: var(--text-faint); font-size: 11px; }

.vn-card-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.vn-card-name {
  font-size: 11.5px;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.vn-card-sub { font-size: 10.5px; color: var(--text-faint); }

.vn-card-row { display: flex; align-items: center; gap: 4px; }

.vn-mini {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.06);
  color: #cfcfd6;
  font-size: 11px;
  cursor: pointer;
}

.vn-mini:hover:not(:disabled) { background: rgba(255, 255, 255, 0.14); color: #fff; }
.vn-mini:disabled { opacity: 0.32; cursor: not-allowed; }
.vn-mini.danger:hover { background: var(--danger-soft); color: var(--danger); }

.vn-mini-field {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
  font-size: 10.5px;
  color: var(--text-faint);
}

.vn-dur {
  width: 46px;
  height: 22px;
  padding: 0 4px;
  font-size: 11px;
  text-align: center;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.055);
  border: 1px solid transparent;
  border-radius: 6px;
  outline: none;
}

/* ---- 资产库选素材弹窗 ---- */
.vn-picker { width: 460px; }

.vn-picker-body {
  max-height: 52vh;
  overflow-y: auto;
  padding: 10px 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.vn-pick-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border: none;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-primary);
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
}

.vn-pick-row:hover { background: rgba(255, 255, 255, 0.1); }

.vn-pick-kind {
  flex: 0 0 auto;
  font-size: 10.5px;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(42, 211, 216, 0.14);
  color: var(--accent);
}

.vn-pick-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 节点内部滚动条（沿用面板那套细滚动条） */
.vn-grid::-webkit-scrollbar,
.vn-strip::-webkit-scrollbar,
.vn-picker-body::-webkit-scrollbar { width: 8px; height: 8px; }
.vn-grid::-webkit-scrollbar-thumb,
.vn-strip::-webkit-scrollbar-thumb,
.vn-picker-body::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.13);
  border-radius: 4px;
}

/* 主按钮禁用时换成灰底：只降透明度的话，青色按钮看起来还是「能点」，
   用户点了没反应又会以为坏了。 */
.vn-btn.primary:disabled {
  background: rgba(255, 255, 255, 0.09);
  color: var(--text-muted);
  opacity: 1;
}

/* ============================================================================
   ㉔ 三个节点视觉对齐参考图：模型药丸 / 金色宝石 / 空态大图标 / 尝试清单 / 端口+
   ========================================================================== */

/* 标题行里的「金色宝石」徽标 + 模型药丸（参考图「逐帧拉片 ◆ SD 2.5」） */
.node-label .nl-gem {
  flex: 0 0 auto;
  color: #e8c46a;
  font-size: 10px;
  line-height: 1;
  transform: translateY(-0.5px);
}
.node-label .nl-pill {
  flex: 0 0 auto;
  font-size: 10.5px;
  line-height: 1;
  padding: 3px 7px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--accent-line);
  white-space: nowrap;
  letter-spacing: 0.02em;
}

/* 圆形端口上补一个「+」，对齐参考图卡片外沿那个接线钮 */
.node .port::before {
  content: "+";
  font-weight: 500;
  line-height: 1;
}

/* 逐帧拉片空态：居中大图标 + 一句灰字 */
.vn-empty-ico {
  width: 40px;
  height: 40px;
  color: var(--text-faint);
  opacity: 0.7;
}
.vn-empty-ico svg { width: 40px; height: 40px; display: block; }

/* 智能剪辑空态：「尝试:」四条带图标清单 */
.vn-try {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  align-items: center;
}
.vn-try-label {
  font-size: 12px;
  color: var(--text-muted);
}
.vn-try-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid var(--border-strong);
  background: var(--bg-inner);
  color: var(--text-primary);
  font-size: 12px;
  cursor: pointer;
  transition: all 0.14s ease;
}
.vn-try-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.vn-try-ico { color: var(--accent); font-size: 11px; }

/* ============================================================================
   ㉕ 3D 导演台：全屏覆盖层（js/studio-director.js）
   ========================================================================== */

.dir3d-root {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
}

.dir3d-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 7, 9, 0.7);
}

.dir3d {
  position: relative;
  flex: 1;
  /* 关键：flex/grid 子项的默认 min-height 是 auto，姿势面板几十根滑杆
     会把整行撑到视口之外（实测 canvas 被拉到 1676px 高，构图全变形）。
     min-height:0 把「内容可以滚、行高不许涨」钉死，这是这套布局的命门。 */
  min-height: 0;
  display: grid;
  grid-template-columns: 200px 1fr 300px;
  grid-template-rows: 1fr;
  margin: 18px;
  border-radius: 16px;
  background: #141414;
  border: 1px solid var(--border-strong);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}

/* 左栏：项目树 */
.dir3d-left {
  background: #17171a;
  border-right: 1px solid var(--border);
  padding: 12px 8px;
  overflow: auto;
  min-height: 0;
}
.dir3d-tree-title {
  font-size: 11px;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 8px 8px;
}
.dir3d-tree-item {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 8px;
  border-radius: 8px;
  font-size: 13px;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}
.dir3d-tree-item:hover { background: var(--bg-hover); color: var(--text-primary); }
.dir3d-tree-item.active { background: var(--accent-soft); color: var(--accent); }
.dir3d-caret { width: 14px; font-size: 10px; color: var(--text-faint); }
.dir3d-tree-group { margin-left: 10px; }
.dir3d-tree-label { color: inherit; }

/* 中部：视口 + 顶栏 + 工具条 */
.dir3d-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.dir3d-topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 46px;
  padding: 0 12px;
  border-bottom: 1px solid var(--border);
  background: #17171a;
}
.dir3d-seg {
  display: flex;
  border: 1px solid var(--border-strong);
  border-radius: 9px;
  overflow: hidden;
}
.dir3d-seg-btn {
  height: 30px;
  padding: 0 14px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 13px;
  cursor: pointer;
}
.dir3d-seg-btn.active { background: var(--accent-soft); color: var(--accent); }
.dir3d-iconbtn {
  width: 30px;
  height: 30px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 13px;
}
.dir3d-iconbtn:hover { color: var(--text-primary); border-color: var(--accent); }
.dir3d-spacer { flex: 1; }

.dir3d-viewport {
  position: relative;
  flex: 1;
  min-height: 0;
  background: #060608;
  overflow: hidden;
}
.dir3d-canvas { display: block; width: 100%; height: 100%; cursor: grab; }
.dir3d-canvas:active { cursor: grabbing; }

.dir3d-gizmo {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: rgba(23, 23, 26, 0.8);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
}
.dir3d-resetview {
  position: absolute;
  top: 56px;
  right: 12px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: rgba(23, 23, 26, 0.8);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 15px;
}
.dir3d-resetview:hover { color: var(--accent); border-color: var(--accent); }
.dir3d-view-hint {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  color: var(--text-faint);
  pointer-events: none;
}

.dir3d-toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 40px;
  padding: 0 12px;
  border-top: 1px solid var(--border);
  background: #17171a;
}
.dir3d-tool {
  width: 28px;
  height: 28px;
  border-radius: 7px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 14px;
}
.dir3d-tool:hover { background: var(--bg-hover); color: var(--text-primary); }
.dir3d-tool.active { background: var(--accent-soft); color: var(--accent); }
.dir3d-tool-sep { width: 1px; height: 18px; background: var(--border); margin: 0 4px; }
.dir3d-tool-label { font-size: 12px; color: var(--text-muted); }
.dir3d-tool-dim { font-size: 11px; color: var(--text-faint); }

/* 右栏：三组面板 */
.dir3d-right {
  background: #17171a;
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.dir3d-panel-tabs {
  display: flex;
  border-bottom: 1px solid var(--border);
}
.dir3d-tab {
  flex: 1;
  height: 40px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 12.5px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.dir3d-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.dir3d-panels { flex: 1; overflow: auto; min-height: 0; }
.dir3d-panel { display: none; padding: 14px 16px 20px; }
.dir3d-panel.active { display: block; }

.dir3d-sec {
  font-size: 11px;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 16px 0 8px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.dir3d-sec:first-child { margin-top: 0; border-top: none; padding-top: 0; }

.dir3d-field {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 30px;
  margin-bottom: 6px;
}
.dir3d-label {
  flex: 0 0 64px;
  font-size: 12px;
  color: var(--text-muted);
  white-space: nowrap;
}
.dir3d-field input[type="range"] { flex: 1; min-width: 0; accent-color: var(--accent); }
.dir3d-num {
  flex: 0 0 42px;
  text-align: right;
  font-size: 11.5px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.dir3d-vecs { display: flex; gap: 6px; flex: 1; }
.dir3d-vec {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 1;
}
.dir3d-vec span { font-size: 10px; color: var(--text-faint); }
.dir3d-vec input {
  width: 100%;
  min-width: 0;
  height: 26px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-inner);
  color: var(--text-primary);
  font-size: 12px;
  padding: 0 6px;
  font-variant-numeric: tabular-nums;
}
.dir3d-field input[type="color"] {
  width: 40px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-inner);
  cursor: pointer;
}
.dir3d-field input[type="text"] {
  flex: 1;
  height: 26px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-inner);
  color: var(--text-primary);
  font-size: 12px;
  padding: 0 8px;
}
.dir3d-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 26px;
}
.dir3d-value { font-size: 12px; color: var(--success); }
.dir3d-hint { font-size: 11px; color: var(--text-faint); margin-bottom: 8px; }

/* 开关 */
.dir3d-switch {
  position: relative;
  width: 38px;
  height: 20px;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--bg-inner);
  cursor: pointer;
  padding: 0;
  flex: 0 0 auto;
  transition: background 0.16s ease;
}
.dir3d-switch .dir3d-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--text-muted);
  transition: all 0.16s ease;
}
.dir3d-switch.on { background: var(--accent-soft); border-color: var(--accent-line); }
.dir3d-switch.on .dir3d-knob { left: 20px; background: var(--accent); }

/* 姿势预设网格 */
.dir3d-pose-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.dir3d-pose {
  height: 30px;
  border-radius: 7px;
  border: 1px solid var(--border);
  background: var(--bg-inner);
  color: var(--text-muted);
  font-size: 11.5px;
  cursor: pointer;
  transition: all 0.14s ease;
}
.dir3d-pose:hover { border-color: var(--accent); color: var(--text-primary); }
.dir3d-pose.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }


/* ============================================================================
   图片放大预览：悬停浮层（.img-peek） + 全屏查看器（.img-viewer）
   缩略图看不清封面上的书名，所以图上悬停就要能放大看。
   ========================================================================== */

.media-area.has-zoom { cursor: zoom-in; }

.img-peek {
  position: fixed;
  z-index: 850;
  padding: 8px;
  border: 1px solid var(--border-strong, var(--border));
  border-radius: 12px;
  background: var(--bg-panel, #17171a);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  opacity: 0;
  transform: scale(0.97);
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.img-peek.is-on { opacity: 1; transform: scale(1); pointer-events: auto; cursor: zoom-in; }

.img-peek img {
  display: block;
  max-width: min(46vw, 560px);
  max-height: min(74vh, 760px);
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 8px;
  background: #0c0c0d;
}

.img-peek-hint {
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--text-faint);
  text-align: center;
  white-space: nowrap;
}

.img-viewer {
  position: fixed;
  inset: 0;
  z-index: 860;
  background: rgba(8, 8, 10, 0.92);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.14s ease;
}
.img-viewer.is-on { opacity: 1; }
/* 用 hidden 属性控制显隐，但上面给了 display:flex —— 必须显式压掉，否则盖满全屏 */
.img-viewer[hidden] { display: none; }

.img-viewer-stage {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 56px 24px 64px;
}

.img-viewer img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 6px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
  transform-origin: center center;
  transition: transform 0.06s linear;
  will-change: transform;
  user-select: none;
  -webkit-user-drag: none;
}

.img-viewer-bar {
  position: absolute;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 12px;
  border: 1px solid var(--border-strong, var(--border));
  background: rgba(24, 24, 28, 0.92);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}

.img-viewer-bar .iv-size {
  font-size: 11.5px;
  color: var(--text-faint);
  padding: 0 4px;
  font-variant-numeric: tabular-nums;
}

.img-viewer-bar button {
  height: 28px;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-inner, #222226);
  color: var(--text-muted);
  font-size: 12px;
  cursor: pointer;
  transition: all 0.14s ease;
}

.img-viewer-bar button:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}
