﻿/* ===========================================================================
   X-Canvas 扩展样式
   —— 在保留原型视觉的前提下，补上真实功能所需的控件
   =========================================================================== */

/* 选中卡片的连线流光色（深浅主题通用） */
:root { --flow-color: #4aa8ff; }


/* ---------- 端口 / 锚点（灰色 + 加号，参考主流画布） ---------- */
.port {
  position: absolute;
  width: 22px; height: 22px;
  margin-top: -11px;              /* 让 top 百分比代表端口中心 */
  border-radius: 50%;
  background-color: color-mix(in srgb, var(--card-solid) 88%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 28%, transparent);
  color: color-mix(in srgb, var(--text) 55%, transparent);
  z-index: 6;
  cursor: crosshair;
  opacity: .7;
  box-shadow: 0 2px 8px rgba(0,0,0,.14);
  transition: transform .14s var(--ease), box-shadow .14s var(--ease),
              opacity .14s var(--ease), border-color .14s var(--ease),
              color .14s var(--ease);
}
.port.in { left: -11px; }
.port.out { right: -11px; }

/* 加号：两条细横竖，颜色跟随 currentColor */
.port::before,
.port::after {
  content: '';
  position: absolute; left: 50%; top: 50%;
  background: currentColor;
  border-radius: 2px;
  transform: translate(-50%, -50%);
}
.port::before { width: 9px; height: 1.5px; }
.port::after { width: 1.5px; height: 9px; }

.node:hover .port,
.node.selected .port { opacity: 1; }

.port:hover {
  opacity: 1;
  color: var(--text);
  border-color: color-mix(in srgb, var(--text) 40%, transparent);
  transform: scale(1.12);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--text) 8%, transparent),
              0 4px 12px rgba(0,0,0,.2);
}

/* 必填端口：常驻细环（保持低对比，不再用类型色） */
.port.required { border-color: color-mix(in srgb, var(--text) 34%, transparent); }
.port.required.missing { border-color: var(--danger); animation: portPulse 1.2s ease-in-out infinite; }
@keyframes portPulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }

/* 多值端口：右上角一个小圆点提示「可以连多条」 */
.port.multi {
  background-image: radial-gradient(circle at 100% 0%, currentColor 0 2px, transparent 2.5px);
}

/* 拖拽连线时的落点提示 */
.port.dimmed { opacity: .18; }
/**
 * 多口节点里本次运行没命中的口（ComfyUI 的图片/视频二选一）。
 * 置灰 + 收窄命中：既然这次不会有数据，就不该再从它拉线 ——
 * 用户会拖出一条「连上了但永远是空口」的线，排查起来毫无头绪。
 */
.port.idle {
  opacity: .25;
  border-style: dashed;
  pointer-events: none;
}
.port.droppable {
  opacity: 1;
  transform: scale(1.12);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--text) 10%, transparent);
}
.port.drop-target {
  opacity: 1;
  color: var(--text);
  transform: scale(1.3);
  border-color: var(--flow-color);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--flow-color) 30%, transparent);
}
.node.drop-host > .node-head { background: color-mix(in srgb, var(--strong) 6%, transparent); }

/* ---------- 连线（常态统一灰色，选中才点亮） ----------
   参考主流无限画布：没有颜色时画布最干净，节点本身才是主角。
   常态 1.6px 的低对比灰线，hover 略微提亮，选中卡片后才进入蓝色流光。 */
.link {
  stroke: color-mix(in srgb, var(--text) 30%, transparent);
  stroke-width: 1.6;
  fill: none;
  opacity: .5;
  pointer-events: none;
  transition: opacity .15s, stroke-width .15s, stroke .15s, filter .15s;
}
.link.hot {
  stroke: color-mix(in srgb, var(--text) 46%, transparent);
  opacity: 1;
  stroke-width: 2;
  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--text) 18%, transparent));
}
.link.temp {
  stroke: color-mix(in srgb, var(--text) 40%, transparent);
  stroke-dasharray: 6 6;
  opacity: .9;
  stroke-width: 1.8;
}

/* ---------- 连线流光 ----------
   选中某张卡片（或源节点正在运行）时，与它相连的线变成蓝色底 + 一段
   高速移动的亮光。底与流光分成两条 path：
     .link       连续的蓝色底线，保证「这条线是活的」始终可见；
     .link-flow  只有亮光，用 stroke-dashoffset 在底线上滑动。
   这样比单纯把一条线做成虚线更像参考图里的「流光」。 */
.links {
  --flow-speed: 1.15s;
}
.link.flow {
  stroke: var(--flow-color);
  opacity: var(--flow-alpha, .92);
  stroke-width: 2;
  filter: drop-shadow(0 0 5px color-mix(in srgb, var(--flow-color) 55%, transparent));
}
.link-flow {
  /* 老版流光是「整条线铺满短虚线」；新版保留一条连续蓝底，
     亮光只取一段。间距按档位变量放大 10 倍，四档手感仍然区分得开。 */
  --flow-gap-long: calc(var(--flow-gap, 16px) * 10);
  stroke: #eaf8ff;
  stroke-width: 2.4;
  fill: none;
  opacity: var(--flow-alpha, .95);
  pointer-events: none;
  stroke-linecap: round;
  stroke-dasharray: var(--flow-dash, 16px) var(--flow-gap-long);
  animation: linkFlow var(--flow-speed, 1.15s) linear infinite;
  filter: drop-shadow(0 0 4px var(--flow-color));
}
@keyframes linkFlow {
  from { stroke-dashoffset: calc(var(--flow-dash, 16px) + var(--flow-gap-long)); }
  to   { stroke-dashoffset: 0; }
}

/* 强档「双段」：CSS 没法按变量重复 N 次，只能把两段写全。 */
.links[data-flow-count="2"] .link-flow {
  stroke-dasharray: var(--flow-dash, 16px) var(--flow-gap-long)
                   var(--flow-dash, 16px) var(--flow-gap-long);
  animation-name: linkFlow2;
}
@keyframes linkFlow2 {
  from { stroke-dashoffset: calc((var(--flow-dash, 16px) + var(--flow-gap-long)) * 2); }
  to   { stroke-dashoffset: 0; }
}

/* 拖动中：流光要停。节点正在被拖，线条几何每帧都在变，
   还要再叠一层 dashoffset 动画 = 两套动画抢同一条 path，视觉上是抖的。 */
.links.is-dragging .link-flow { animation: none; }

/* 视口外的连线不跑动画（JS 会跳过不可见的两端）。 */
.links[data-flow="off"] .link-flow { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .link-flow { animation: none; opacity: .55; }
}

/* ================================================================
   画布基础交互补充
   ================================================================ */

/* ---------- 空格平移 ---------- */
body.space-pan .board { cursor: grab !important; }
body.space-pan .board.dragging { cursor: grabbing !important; }

/* ---------- 吸附参考线 ---------- */
.snap-guide {
  position: absolute;
  pointer-events: none;
  z-index: 40;
  background: #ff3d8b;
  border-radius: 2px;
}
.snap-guide.v { width: 100%; height: 1px; top: 0; }
.snap-guide.h { height: 100%; width: 1px; left: 0; }

/* ---------- 命令面板 ---------- */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 700;
  background: rgba(0,0,0,.42); backdrop-filter: blur(4px);
  display: none; align-items: flex-start; justify-content: center; padding-top: 14vh;
}
.cmdk-overlay.show { display: flex; }
.cmdk-modal {
  width: min(620px, 92vw); max-height: 62vh;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: 0 24px 64px rgba(0,0,0,.3);
  backdrop-filter: blur(16px);
  display: flex; flex-direction: column; overflow: hidden;
}
.cmdk-input {
  height: 52px; border: none; outline: none; background: transparent;
  color: var(--text); font-size: 16px; font-weight: 700;
  padding: 0 20px; border-bottom: 1px solid var(--line);
  font-family: inherit;
}
.cmdk-list { overflow-y: auto; padding: 8px; }
.cmdk-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: 11px; cursor: pointer;
  font-size: 13px; font-weight: 700; color: var(--text);
}
.cmdk-item .k {
  margin-left: auto; font-size: 10px; color: var(--faint);
  background: var(--soft); border: 1px solid var(--line);
  border-radius: 5px; padding: 1px 6px; font-weight: 800;
}
.cmdk-item.active { background: var(--soft-2); }
.cmdk-item .sub { font-size: 10px; color: var(--faint); font-weight: 600; }
.cmdk-empty { padding: 26px; text-align: center; color: var(--muted); font-size: 12px; }

/* ---------- 侧栏浮层面板（railpanel.js） ----------
   侧栏三个入口（＋ / 模板 / 设置）共用这一个容器，内容各自 build()。
   z-index 45：高于侧栏（40），远低于 cmdk(700) / 配置(500) / 右键菜单(200)。 */
.rail-panel {
  position: fixed; z-index: 45; display: none;
  min-width: 260px; max-width: min(620px, calc(100vw - 32px));
  max-height: min(560px, calc(100vh - 32px));
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: 0 16px 44px var(--shadow);
  backdrop-filter: blur(16px);
  flex-direction: column; overflow: hidden;
}
.rail-panel.show { display: flex; }

.rail-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 11px 14px; border-bottom: 1px solid var(--line); flex-shrink: 0;
}
.rail-head h3 { margin: 0; font-size: 12px; font-weight: 800; color: var(--text); }
.rail-search {
  height: 32px; border: 1px solid var(--line-2); border-radius: 9px;
  background: var(--card-solid); color: var(--text);
  padding: 0 10px; font-size: 12px; font-family: inherit; outline: none;
}
.rail-search:focus { border-color: var(--strong); }
.rail-body { overflow-y: auto; padding: 6px; flex: 1; }

/* 菜单行：左图标 + 中间文字 + 右侧说明 */
.rail-row {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: 9px; cursor: pointer;
  font-size: 12px; font-weight: 700; color: var(--text);
}
.rail-row:hover { background: var(--soft-2); }
.rail-row[aria-disabled="true"] { opacity: .45; cursor: default; }
.rail-row[aria-disabled="true"]:hover { background: none; }
.rail-row svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--faint); }
.rail-row .sub { margin-left: auto; font-size: 10px; font-weight: 600; color: var(--faint); }
.rail-sep { height: 1px; background: var(--line-2); margin: 5px 10px; }

/* ---------- 连线流光档位（设置菜单） ---------- */
.flow-row { padding: 7px 10px; }
.flow-row-head { display: flex; align-items: center; font-size: 12px; font-weight: 700; color: var(--text); }
.flow-toggle {
  margin-left: auto; width: 30px; height: 18px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--soft);
  position: relative; cursor: pointer; transition: background .18s var(--ease);
  display: flex; align-items: center;
}
.flow-toggle svg { width: 10px; height: 10px; position: absolute; left: 3px; color: var(--faint); transition: all .18s var(--ease); }
.flow-toggle.on { background: var(--strong); border-color: var(--strong); }
.flow-toggle.on svg { left: 17px; color: var(--strong-text); }
.flow-chips { display: flex; gap: 5px; margin-top: 8px; transition: opacity .18s var(--ease); }
.flow-chip {
  flex: 1; text-align: center; padding: 5px 0; border-radius: 7px; cursor: pointer;
  background: var(--soft); border: 1px solid var(--line-2);
  font-size: 10.5px; font-weight: 700; color: var(--muted);
  transition: all .14s var(--ease);
}
.flow-chip:hover { color: var(--text); }
.flow-chip.active { background: var(--strong); border-color: var(--strong); color: var(--strong-text); }

/* 设置菜单的头部：现在是真实本地数据，以后放账号名 + 会员等级 */
.rail-me { padding: 12px 14px 10px; }
.rail-me-top { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.rail-me-name { font-size: 13px; font-weight: 800; color: var(--text); }
.rail-me-badge {
  margin-left: auto; font-size: 9px; font-weight: 800; color: var(--muted);
  background: var(--soft); border: 1px solid var(--line); border-radius: 5px; padding: 1px 6px;
}
.rail-me-sub { font-size: 10px; font-weight: 600; color: var(--faint); line-height: 1.6; }
/* 存储占用卡片：数据来自 api.storageStats()，不是估算也不是配额 */
.rail-me-card {
  margin-top: 9px; padding: 8px 10px; border-radius: 9px;
  background: var(--soft); border: 1px solid var(--line-2);
}
.rail-me-card-top { display: flex; align-items: baseline; gap: 6px; font-size: 10px; font-weight: 700; color: var(--muted); }
.rail-me-card-top b { margin-left: auto; font-size: 12px; color: var(--text); }
.rail-me-path {
  margin-top: 3px; font-size: 9px; font-weight: 600; color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; text-align: left;
}

/* ---------- 模板面板：场景预设 / 工作流 ---------- */
.rail-tabs {
  display: flex; gap: 2px; padding: 8px 10px 0; flex-shrink: 0;
  border-bottom: 1px solid var(--line);
}
.rail-tab {
  flex: 1; height: 30px; border: 0; border-radius: 8px 8px 0 0; cursor: pointer;
  background: transparent; color: var(--faint);
  font-size: 12px; font-weight: 800; transition: all .14s var(--ease);
}
.rail-tab:hover { color: var(--text); background: var(--soft-2); }
.rail-tab.active { color: var(--text); background: var(--soft); }
.rail-tabs { align-items: center; }
.rail-expand {
  width: 30px; height: 30px; flex-shrink: 0; margin-left: 3px; margin-bottom: 3px;
  border: 0; border-radius: 8px; background: transparent; color: var(--faint);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.rail-expand:hover { color: var(--text); background: var(--soft-2); }
.rail-expand svg { width: 14px; height: 14px; }
.rail-panel.rail-expanded {
  left: 50% !important; top: 50% !important; transform: translate(-50%, -50%);
  width: min(1000px, calc(100vw - 64px)) !important;
  max-width: min(1000px, calc(100vw - 64px)) !important;
  max-height: min(760px, calc(100vh - 64px)) !important;
  border-radius: 18px; z-index: 760;
}
.rail-panel.rail-expanded .rail-tabs { padding-top: 10px; }
.rail-panel.rail-expanded .preset-grid { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
.preset-body { display: flex; flex-direction: column; min-height: 0; overflow: hidden; flex: 1; }
.preset-body .rail-search { margin: 10px 10px 0; }
.preset-chips { display: flex; gap: 5px; padding: 9px 10px 4px; flex-wrap: wrap; flex-shrink: 0; }
.preset-chip {
  height: 24px; padding: 0 10px; border-radius: 12px; cursor: pointer;
  border: 1px solid var(--line-2); background: var(--card-solid); color: var(--muted);
  font-size: 10px; font-weight: 800; transition: all .14s var(--ease);
}
.preset-chip:hover { border-color: var(--strong); color: var(--text); }
.preset-chip.active { background: var(--strong); color: var(--strong-text); border-color: var(--strong); }

/* 卡片网格：4 列 —— 与参考一致，也能在 620px 面板里放下而不挤成 2 列 */
.preset-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 8px; padding: 8px 10px 12px; overflow-y: auto; min-height: 0;
  max-height: calc(560px - 154px);
}
.rail-panel.rail-expanded .preset-grid { max-height: calc(100vh - 210px); }
.preset-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.preset-card {
  display: flex; flex-direction: column; gap: 5px; padding: 0;
  border: 1px solid var(--line-2); border-radius: 9px; overflow: hidden;
  background: var(--card-solid); cursor: pointer; text-align: left;
  transition: all .14s var(--ease);
}
.preset-card:hover { border-color: var(--strong); transform: translateY(-1px); }
.preset-cover { position: relative; aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center; }
/* 占位阶段的识别靠它；将来换成真图就退到背景 */
.preset-cover-icon { width: 26px; height: 26px; opacity: .34; color: #fff; }
.preset-card-meta { padding: 0 6px 6px; }
.preset-card-name {
  font-size: 10px; font-weight: 800; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.preset-card-tags { display: flex; gap: 3px; margin-top: 3px; }
.preset-tag {
  font-size: 8px; font-weight: 700; color: var(--faint);
  background: var(--soft); border-radius: 4px; padding: 0 4px; line-height: 15px;
}
/* 「N 步」要显眼一档：它意味着**多次计费**，点之前必须看出来。 */
.preset-tag.is-multi { color: var(--warning); background: color-mix(in srgb, var(--warning) 14%, transparent); }

/* ---------- 我的模板 ---------- */
.my-tpl-view { display: flex; flex-direction: column; min-height: 0; flex: 1; overflow: hidden; }
.my-tpl-bar { display: flex; align-items: center; gap: 6px; padding: 10px 10px 0; flex-shrink: 0; }
.my-tpl-bar .rail-search { flex: 1; min-width: 0; margin: 0; }
.my-tpl-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.my-tpl-icon {
  width: 30px; height: 32px; border: 0; border-radius: 8px; background: transparent;
  color: var(--muted); display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0;
}
.my-tpl-icon:hover { color: var(--text); background: var(--soft-2); }
.my-tpl-icon.is-active { color: var(--text); background: var(--soft); }
.my-tpl-icon.is-danger:hover { color: var(--danger); background: var(--danger-bg); }
.my-tpl-icon:disabled { opacity: .35; cursor: default; background: transparent; }
.my-tpl-icon svg { width: 14px; height: 14px; }
.my-tpl-selectbar { display: flex; align-items: center; gap: 4px; padding: 7px 10px 0; flex-shrink: 0; }
.my-tpl-selectbar[hidden] { display: none; }
.my-tpl-text {
  height: 26px; padding: 0 7px; border: 0; background: transparent; border-radius: 7px;
  color: var(--muted); font: inherit; font-size: 11px; font-weight: 700; cursor: pointer;
}
.my-tpl-text:hover { color: var(--text); background: var(--soft-2); }
.my-tpl-count { margin-left: auto; color: var(--faint); font-size: 10px; font-weight: 600; }
.my-tpl-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
  padding: 9px 10px 12px; overflow-y: auto; min-height: 0; align-content: start;
  max-height: calc(560px - 152px);
}
.rail-panel.rail-expanded .my-tpl-grid {
  grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
  max-height: calc(100vh - 210px);
}
.my-tpl-card { position: relative; }
.my-tpl-card.is-selected {
  border-color: var(--strong);
  box-shadow: 0 0 0 1px var(--strong), 0 6px 18px var(--shadow);
}
.my-tpl-check {
  display: none; position: absolute; right: 6px; top: 6px; z-index: 2;
  width: 19px; height: 19px; border-radius: 50%; border: 1.5px solid rgba(255,255,255,.6);
  background: rgba(0,0,0,.38); color: #fff; align-items: center; justify-content: center;
}
.my-tpl-grid.is-selecting .my-tpl-check { display: flex; }
.my-tpl-card.is-selected .my-tpl-check { background: var(--strong); border-color: var(--strong); color: var(--strong-text); }
.my-tpl-check svg { width: 11px; height: 11px; }
.my-tpl-card .preset-cover { background: var(--soft); }
.my-tpl-empty {
  grid-column: 1 / -1; min-height: 180px; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 12px; padding: 28px 12px;
  text-align: center; color: var(--faint); font-size: 11px; font-weight: 600;
}
.rail-empty { padding: 22px 12px; text-align: center; color: var(--faint); font-size: 11px; font-weight: 600; }

/* ---------- 空状态里的场景卡 ----------
   和模板面板用同一套卡片样式：空状态是「新用户第一次看到」，模板面板是
   「老用户回来找」，两处卡片长得不一样会让用户以为是两样东西。 */
.empty-preset-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 8px; margin: 4px 0 14px;
}
.empty-preset-grid .preset-card { width: 100%; }
.empty-preset-grid .preset-cover { aspect-ratio: 4 / 3; }

/* ---------- 快捷键面板 ----------
   内容不手写：直接来自 CMD_ITEMS + bindKeys 里绑的那些，
   所以永远不会和真实快捷键对不上（这是刻意的 —— 手抄一份必然漂移）。 */
.shortcuts-overlay {
  position: fixed; inset: 0; z-index: 700;
  background: rgba(0,0,0,.42); backdrop-filter: blur(4px);
  display: none; align-items: flex-start; justify-content: center; padding-top: 12vh;
}
.shortcuts-overlay.show { display: flex; }
.shortcuts-modal {
  width: min(680px, 92vw); max-height: 72vh;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: 0 24px 64px rgba(0,0,0,.3);
  backdrop-filter: blur(16px);
  display: flex; flex-direction: column; overflow: hidden;
}
.shortcuts-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
}
.shortcuts-head h3 { margin: 0; font-size: 15px; font-weight: 800; color: var(--text); }
.shortcuts-body {
  overflow-y: auto; padding: 6px 18px 14px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px 26px;
}
.shortcuts-group h4 {
  margin: 14px 0 6px; font-size: 10px; font-weight: 800;
  color: var(--faint); text-transform: uppercase; letter-spacing: .06em;
}
.shortcuts-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 5px 0; font-size: 12px; font-weight: 600; color: var(--muted);
  border-bottom: 1px solid var(--line-2);
}
.shortcuts-keys { display: flex; gap: 3px; flex: none; }
.shortcuts-modal kbd {
  font-family: inherit; font-size: 10px; font-weight: 800;
  background: var(--soft); border: 1px solid var(--line); color: var(--text-2);
  border-radius: 5px; padding: 2px 6px; white-space: nowrap;
}
.shortcuts-foot {
  margin: 0; padding: 12px 18px; border-top: 1px solid var(--line);
  font-size: 11px; font-weight: 600; color: var(--faint); line-height: 1.7;
}
.shortcuts-foot kbd {
  font-family: inherit; font-size: 10px; font-weight: 800;
  background: var(--soft); border: 1px solid var(--line); color: var(--text-2);
  border-radius: 5px; padding: 1px 5px;
}

/* ---------- 画布内搜索 ---------- */
.find-overlay {
  position: absolute; top: 78px; left: 50%; transform: translateX(-50%);
  z-index: 30; width: 420px; max-width: 92vw;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 16px; box-shadow: 0 16px 44px var(--shadow);
  backdrop-filter: blur(16px); padding: 10px;
  display: none; gap: 8px; align-items: center;
}
.find-overlay.show { display: flex; }
.find-overlay input {
  flex: 1; height: 32px; border: none; background: transparent;
  color: var(--text); font-size: 13px; font-weight: 700; outline: none;
}
.find-overlay .count { font-size: 11px; font-weight: 800; color: var(--faint); }

/* 结果下拉要脱离 flex 流，绝对定位在输入框下面 */
.find-results {
  position: absolute; top: calc(100% + 6px); left: 8px; right: 8px;
  max-height: 300px; overflow-y: auto; display: none;
  padding: 5px; border-radius: 13px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 16px 40px var(--shadow);
}
.find-results.show { display: block; }
.find-item {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px; border-radius: 10px; cursor: pointer;
}
.find-item:hover, .find-item.active { background: var(--soft-1); }
.find-item i { width: 14px; height: 14px; color: var(--faint); flex-shrink: 0; }
.find-item .t {
  font-size: 12px; font-weight: 700; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.find-item .sub {
  margin-left: auto; font-size: 10px; color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46%;
}

/* ---------- 节点高亮（搜索命中） ---------- */
.node.found { box-shadow: 0 0 0 3px color-mix(in srgb, var(--strong) 40%, transparent); }
.node.found > .node-head { background: color-mix(in srgb, var(--strong) 8%, transparent); }
.node.search-dim { opacity: .28; }

/* ---------- 节点颜色标签 ---------- */
.node-tag-stripe {
  position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  border-radius: var(--radius-node) 0 0 var(--radius-node);
  z-index: 7; pointer-events: none;
}
.tag-dot {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--line); cursor: pointer; flex-shrink: 0;
  transition: transform .12s var(--ease);
}
.tag-dot:hover { transform: scale(1.2); }
.tag-dot.active { border-color: var(--strong); }

/* ---------- 多选浮动工具条 ---------- */
.multi-toolbar {
  position: absolute; z-index: 45;
  display: none; align-items: center; gap: 4px;
  padding: 6px; border-radius: 14px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 12px 36px var(--shadow); backdrop-filter: blur(16px);
}
.multi-toolbar.show { display: flex; }
.multi-toolbar .sep { width: 1px; height: 16px; background: var(--line); margin: 0 2px; }
.multi-toolbar { max-width: calc(100vw - 160px); gap: 2px; }
.tb-action {
  height: 30px; padding: 0 10px; border: 0; border-radius: 9px;
  display: inline-flex; align-items: center; gap: 6px;
  background: transparent; color: var(--text); font: inherit;
  font-size: 11px; font-weight: 700; cursor: pointer; white-space: nowrap;
}
.tb-action:hover { background: var(--soft); }
.tb-action svg { width: 14px; height: 14px; flex-shrink: 0; }
.multi-more {
  position: fixed; z-index: 230; min-width: 176px; padding: 6px;
  border-radius: 12px; background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 16px 44px var(--shadow); backdrop-filter: blur(16px);
  display: flex; flex-direction: column; gap: 1px;
}
.multi-more-item {
  height: 30px; padding: 0 9px; border: 0; border-radius: 8px; background: transparent;
  color: var(--text); font: inherit; font-size: 11px; font-weight: 650;
  display: flex; align-items: center; gap: 8px; cursor: pointer; text-align: left;
}
.multi-more-item:hover { background: var(--soft); }
.multi-more-item.is-danger { color: var(--danger); }
.multi-more-item svg { width: 14px; height: 14px; color: var(--faint); flex-shrink: 0; }
.multi-more-item.is-danger svg { color: currentColor; }
.multi-more-tags {
  display: flex; gap: 6px; align-items: center; padding: 6px 8px 4px;
  border-top: 1px solid var(--line); margin-top: 4px;
}

/* ---------- 分组 / 选框 / 成组工具条 ---------- */
.group-layer { position: absolute; inset: 0; z-index: 1; pointer-events: none; --inv: 1; }
.selection-frame {
  position: absolute; border: calc(1.2px * var(--inv, 1)) dashed #6366f1;
  border-radius: calc(10px * var(--inv, 1)); pointer-events: none;
  background: color-mix(in srgb, #6366f1 4%, transparent); box-sizing: border-box;
}
.selection-handle {
  position: absolute; width: calc(7px * var(--inv, 1)); height: calc(7px * var(--inv, 1));
  border-radius: 50%; background: #6366f1; transform: translate(-50%, -50%);
}
.selection-handle.nw { left: 0; top: 0; }
.selection-handle.ne { left: 100%; top: 0; }
.selection-handle.se { left: 100%; top: 100%; }
.selection-handle.sw { left: 0; top: 100%; }
.selection-anchor, .group-anchor {
  width: calc(22px * var(--inv, 1)); height: calc(22px * var(--inv, 1));
  border-radius: 50%; border: calc(1px * var(--inv, 1)) solid rgba(255,255,255,.28);
  background: #6366f1; color: #fff; cursor: crosshair; padding: 0;
  display: flex; align-items: center; justify-content: center; pointer-events: auto;
  box-shadow: 0 3px 12px rgba(0,0,0,.28);
}
.selection-anchor {
  position: absolute; right: 0; top: 50%; transform: translate(50%, -50%);
  pointer-events: auto;
}
.selection-anchor svg, .group-anchor svg {
  width: calc(12px * var(--inv, 1)); height: calc(12px * var(--inv, 1));
  stroke-width: 2.4;
}
.group-frame {
  position: absolute; box-sizing: border-box; pointer-events: auto;
  border: calc(1.4px * var(--inv, 1)) solid color-mix(in srgb, var(--group-color, #6366f1) 34%, transparent);
  border-radius: calc(12px * var(--inv, 1)); background: transparent;
}
.group-frame.selected {
  border-color: var(--group-color, #6366f1);
  background: color-mix(in srgb, var(--group-color, #6366f1) 5%, transparent);
}
.group-name {
  position: absolute; left: 0; top: calc(-24px * var(--inv, 1));
  width: calc(192px * var(--inv, 1)); height: calc(22px * var(--inv, 1));
  padding: 0; border: 0; outline: none; background: transparent;
  color: var(--group-color, #6366f1); font: inherit;
  font-size: calc(12px * var(--inv, 1)); font-weight: 800;
  line-height: calc(22px * var(--inv, 1)); pointer-events: auto;
}
.group-name[readonly] { cursor: text; }
.group-name:not([readonly]) {
  background: var(--panel); border: calc(1px * var(--inv, 1)) solid var(--group-color, #6366f1);
  border-radius: calc(5px * var(--inv, 1)); padding: 0 calc(6px * var(--inv, 1));
  color: var(--text);
}
.group-handle {
  display: none; position: absolute; z-index: 2;
  width: calc(8px * var(--inv, 1)); height: calc(8px * var(--inv, 1));
  border-radius: 50%; background: var(--card-solid);
  border: calc(1.5px * var(--inv, 1)) solid var(--group-color, #6366f1);
  transform: translate(-50%, -50%);
}
.group-frame.selected .group-handle { display: block; }
.group-handle[data-handle="nw"] { left: 0; top: 0; cursor: nwse-resize; }
.group-handle[data-handle="n"] { left: 50%; top: 0; cursor: ns-resize; }
.group-handle[data-handle="ne"] { left: 100%; top: 0; cursor: nesw-resize; }
.group-handle[data-handle="e"] { left: 100%; top: 50%; cursor: ew-resize; }
.group-handle[data-handle="se"] { left: 100%; top: 100%; cursor: nwse-resize; }
.group-handle[data-handle="s"] { left: 50%; top: 100%; cursor: ns-resize; }
.group-handle[data-handle="sw"] { left: 0; top: 100%; cursor: nesw-resize; }
.group-handle[data-handle="w"] { left: 0; top: 50%; cursor: ew-resize; }
.group-anchor {
  position: absolute; right: 0; top: 50%; transform: translate(50%, -50%);
}
.group-toolbar {
  position: absolute; z-index: 45; display: none; align-items: center; gap: 2px;
  padding: 5px; border-radius: 14px; background: var(--panel);
  border: 1px solid var(--line); box-shadow: 0 12px 36px var(--shadow);
  backdrop-filter: blur(16px); visibility: hidden;
}
.group-toolbar.show { display: flex; visibility: visible; }
.group-tool {
  height: 30px; padding: 0 10px; border: 0; border-radius: 9px;
  background: transparent; color: var(--text); font: inherit;
  font-size: 11px; font-weight: 700; display: inline-flex; align-items: center;
  gap: 6px; cursor: pointer; white-space: nowrap;
}
.group-tool:hover { background: var(--soft); }
.group-tool svg { width: 14px; height: 14px; flex-shrink: 0; }
.group-tool .group-caret { width: 11px; height: 11px; color: var(--faint); }
.group-color-btn { padding: 0 7px; }
.group-color-dot {
  width: 14px; height: 14px; border-radius: 50%;
  border: 1px solid var(--line-2); display: block; box-sizing: border-box;
}
.group-pop {
  position: fixed; z-index: 232; padding: 8px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 16px 44px var(--shadow); backdrop-filter: blur(16px);
}
.group-swatches { display: grid; grid-template-columns: repeat(5, 16px); gap: 7px; }
.group-swatch {
  width: 16px; height: 16px; border-radius: 50%; cursor: pointer;
  border: 1px solid rgba(255,255,255,.18); padding: 0;
}
.group-swatch.is-none { background: transparent !important; border-color: var(--line-2); }
.group-swatch.active { outline: 2px solid var(--text); outline-offset: 2px; }
.group-arrange { display: flex; flex-direction: column; gap: 2px; min-width: 132px; }
.group-arrange-item {
  height: 30px; padding: 0 9px; border: 0; border-radius: 8px; background: transparent;
  color: var(--text); font: inherit; font-size: 11px; font-weight: 650;
  display: flex; align-items: center; gap: 8px; cursor: pointer; text-align: left;
}
.group-arrange-item:hover { background: var(--soft); }
.group-arrange-item svg { width: 14px; height: 14px; color: var(--faint); }
body.group-linking { cursor: crosshair; }

/* ---------- Frame 分组 ---------- */
.frame {
  position: absolute; z-index: 1;
  border: 2px dashed var(--line-2);
  border-radius: 18px;
  background: color-mix(in srgb, var(--soft) 40%, transparent);
  pointer-events: none;
}
.frame.selected { border-color: var(--strong); background: color-mix(in srgb, var(--strong) 5%, transparent); pointer-events: auto; }
.frame-title {
  position: absolute; top: -11px; left: 14px;
  padding: 2px 10px; border-radius: 999px;
  background: var(--card-solid); border: 1px solid var(--line);
  font-size: 11px; font-weight: 800; color: var(--muted);
  pointer-events: auto; cursor: pointer; white-space: nowrap;
  display: flex; align-items: center; gap: 6px;
}
.frame.selected .frame-title { color: var(--text); border-color: var(--strong); }
.frame-title input {
  border: none; background: transparent; outline: none;
  color: inherit; font: inherit; width: 120px;
}

/* ---------- 队列面板 ---------- */
.queue-panel {
  position: absolute; left: 80px; top: 78px; z-index: 28;
  width: 300px; max-height: calc(100vh - 180px);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: 0 16px 44px var(--shadow);
  backdrop-filter: blur(16px);
  display: none; flex-direction: column; overflow: hidden;
}
.queue-panel.open { display: flex; }
.queue-row {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-bottom: 1px solid var(--line);
  font-size: 12px; font-weight: 600;
  /* 行高显式给定：图标行与文字行基线才对得齐 */
  min-height: 34px;
}
.queue-row:last-child { border-bottom: none; }
.queue-row.is-error { color: var(--danger); }

/**
 * 状态徽标。
 *
 * 原来这里是 20px 圆形里塞 '▶' / '✓' / '!' / '×' 这些**字符**，
 * 字符的视觉大小完全不一致（'▶' 撑满、'!' 只占中间一条），
 * 混在一列里大小参差 —— 这是队列面板看着乱的根源。
 * 现在统一换成 lucide 图标：同一套 24 网格画的，缩到同一尺寸天然对齐。
 */
.queue-row .pos {
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--soft); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
/* 图标与序号走各自的排版：图标的基线由 SVG 盒模型决定，
   和文字的 line-height 不是一回事，分开定才不会互相拉扯。 */
.queue-row .pos svg { width: 12px; height: 12px; stroke-width: 2.5; }
.queue-row .pos.is-index { font-size: 11px; font-weight: 700; line-height: 1; }
.queue-row.running .pos { background: var(--strong); color: var(--strong-text); }
.queue-row .pos.st-done { background: color-mix(in srgb, var(--success) 16%, transparent); color: var(--success); }
.queue-row .pos.st-error { background: var(--danger-bg); color: var(--danger); }
.queue-row .pos.st-canceled { color: var(--muted); }

.queue-row .t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/**
 * 耗时单独一列。
 *
 * 原来它和节点名拼在同一段文字里（「API生成 · 47 秒」），
 * 右对齐一个数字反而更整齐，也更容易横向比较哪次慢。
 */
.queue-row .dur {
  font-size: 11px; font-weight: 600; color: var(--muted);
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}

.queue-subtitle {
  padding: 12px 12px 5px;
  font-size: 10px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}

/**
 * 行尾操作按钮。
 *
 * 之前复用 .canvas-nav-btn —— 那是左侧导航栏（56px 宽那一条）的尺寸，
 * 拿到 300px 宽的队列面板里就显得又大又挤，而且 hover 反馈也是导航栏那套。
 * 队列自己一套 24px 方钮，和行高 34px 正好齐平。
 */
.queue-act {
  width: 24px; height: 24px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 7px;
  background: transparent; color: var(--muted);
  cursor: pointer;
  transition: background .14s var(--ease), color .14s var(--ease);
}
.queue-act svg { width: 14px; height: 14px; }
.queue-act:hover { background: var(--soft); color: var(--text); }

/**
 * 抽屉/面板头部的按钮统一成 26px 方形。
 *
 * .prop-close 是 28px 的**圆形**（节点属性面板在用），.queue-act 是 24px 方形，
 * 两者并排就是一大一小 —— 队列面板和资产库头部都栽在这上面。
 * 覆盖只作用在 .prop-head 内部，不动 .prop-close 本身。
 */
.prop-head .prop-close,
.prop-head .queue-act {
  width: 26px; height: 26px; border-radius: 8px;
}
.prop-head .prop-close svg,
.prop-head .queue-act svg { width: 14px; height: 14px; }
.prop-head { padding: 12px 14px; }

/** 危险操作（批量删除）常态就该看得出来，不能和普通按钮一个颜色 */
.queue-act.danger:not(:disabled) { color: var(--danger); }

/**
 * 队列面板那排按钮跟随 .prop-head 的统一规格（见下方 .prop-head 规则），
 * 不再单独覆盖一份 —— 两处各写一遍，尺寸一旦改动就会只改到一处。
 */
.queue-panel .prop-head .prop-close,
.queue-panel .prop-head .queue-act { flex-shrink: 0; }

/* ---------- 节点状态条（运行/错误） ---------- */
.node-state {
  display: flex; flex-direction: column; gap: 5px;
  margin-bottom: 9px; padding: 7px 9px;
  border-radius: 11px; font-size: 10px; font-weight: 800;
}
.node-state.running { background: color-mix(in srgb, var(--strong) 8%, transparent); }
.node-state.error { background: var(--danger-bg); color: var(--danger); }
.node-state.ok { background: color-mix(in srgb, var(--success) 12%, transparent); color: var(--success); }
.node-state.queued {
  background: color-mix(in srgb, var(--muted) 10%, transparent);
  color: var(--muted);
}
/* 节点边框的四态反馈：不用等状态条也能一眼看出 */
.node.state-queued { border-color: color-mix(in srgb, var(--muted) 40%, var(--panel)); }
.node.state-ok { border-color: color-mix(in srgb, var(--success) 45%, var(--panel)); }
.node.state-error { border-color: color-mix(in srgb, var(--danger) 55%, var(--panel)); }
.node.state-error { box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 14%, transparent); }
.node-state .row { display: flex; align-items: center; gap: 6px; }
.node-state .msg { font-weight: 700; opacity: .9; line-height: 1.5; }
.node-state .acts { display: flex; gap: 5px; margin-top: 3px; }

/* ---------- 节点状态条 ----------
 * 它住在 .node 里但**不在 .node-body 里**：
 * body 是「签名变了才重建」的缓存区，重建会 innerHTML=''，
 * 把用户正在输入的内容和焦点一起毁掉。而运行状态每秒都在变，
 * 所以状态条必须有自己的 DOM、独立更新。
 */
.node-status-slot {
  display: flex; flex-direction: column; gap: 5px;
  margin: 0 12px 9px; padding: 7px 9px;
  border-radius: 11px; font-size: 10px; font-weight: 800;
}
.node-status-slot .row { display: flex; align-items: center; gap: 6px; }
.node-status-slot .txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.node-status-slot .pct { opacity: .7; font-variant-numeric: tabular-nums; }
.node-status-slot .msg { font-weight: 700; opacity: .9; line-height: 1.5; }
.node-status-slot .acts { display: flex; gap: 5px; margin-top: 3px; }
.node-status-slot .node-progress { margin: 0; }

.node-status-slot.running { background: color-mix(in srgb, var(--strong) 8%, transparent); }
.node-status-slot.queued { background: color-mix(in srgb, var(--muted) 10%, transparent); color: var(--muted); }
.node-status-slot.error { background: var(--danger-bg); color: var(--danger); }
.node-status-slot.ok { background: color-mix(in srgb, var(--success) 12%, transparent); color: var(--success); }
.node-status-slot.missing { background: var(--danger-bg); color: var(--danger); }

/* ---------- 节点内进度条 ---------- */
.node-progress { margin-bottom: 9px; }
.node-progress .bar {
  height: 4px; border-radius: 2px; background: var(--soft-2); overflow: hidden;
}
.node-progress .fill {
  height: 100%; border-radius: 2px; background: var(--strong);
  transition: width .25s var(--ease);
}
.node-progress .fill.indeterminate { width: 34% !important; animation: slide 1.1s ease-in-out infinite; }
.node-progress .cap {
  display: flex; justify-content: space-between;
  font-size: 9px; font-weight: 800; color: var(--faint); margin-top: 4px;
}

/* ---------- 输入排序条（首帧/尾帧） ---------- */
.port-strip { margin-bottom: 9px; }
.port-strip .head {
  display: flex; align-items: center; gap: 5px;
  font-size: 9px; font-weight: 800; color: var(--faint);
  text-transform: uppercase; letter-spacing: .07em; margin-bottom: 5px;
}
.port-chips { display: flex; gap: 5px; flex-wrap: wrap; }
.port-chip {
  position: relative; width: 34px; height: 34px;
  border-radius: 8px; overflow: hidden;
  border: 1px solid var(--line-2); background: var(--soft);
  cursor: grab; flex-shrink: 0;
}
.port-chip img { width: 100%; height: 100%; object-fit: cover; pointer-events: none; }
.port-chip.dragging { opacity: .35; }
.port-chip.over { border-color: var(--strong); transform: scale(1.12); }
.port-chip .n {
  position: absolute; left: 2px; top: 1px;
  font-size: 9px; font-weight: 900; color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,.8);
}
.port-chip .x {
  position: absolute; right: 1px; top: 1px;
  width: 13px; height: 13px; border-radius: 50%;
  background: rgba(255,255,255,.92); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; opacity: 0; transition: opacity .12s;
}
.port-chip:hover .x { opacity: 1; }
.port-chip .x:hover { color: var(--danger); }

/* ---------- 日志抽屉 ---------- */
.log-drawer {
  position: absolute; right: 16px; bottom: 16px; z-index: 27;
  width: 460px; max-width: 92vw; max-height: 46vh;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: 0 16px 44px var(--shadow);
  backdrop-filter: blur(16px);
  display: none; flex-direction: column; overflow: hidden;
}
.log-drawer.open { display: flex; }
.log-body {
  padding: 12px 14px; overflow: auto; flex: 1;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 11px; line-height: 1.65; white-space: pre-wrap;
  word-break: break-word; color: var(--muted);
}
.log-body .err { color: var(--danger); font-weight: 700; }

/* ---------- 批量结果网格 ---------- */
.batch-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: 5px; margin-top: 8px;
}
.batch-cell {
  position: relative; aspect-ratio: 1; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--line); background: var(--soft); cursor: pointer;
}
.batch-cell img { width: 100%; height: 100%; object-fit: cover; }
.batch-cell .tag {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 2px 4px; background: linear-gradient(transparent, rgba(0,0,0,.78));
  color: #fff; font-size: 8px; font-weight: 800;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.batch-cell.failed { display: flex; align-items: center; justify-content: center; background: var(--danger-bg); }
.batch-cell.pending { background: var(--soft-2); }
.boot-overlay {
  position: fixed; inset: 0; z-index: 900;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  background: var(--page); color: var(--muted);
  font-size: 13px; font-weight: 700; letter-spacing: .04em;
  transition: opacity .3s var(--ease);
}
.boot-overlay.hide { opacity: 0; pointer-events: none; }
.boot-spinner {
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid var(--line); border-top-color: var(--strong);
  animation: spin .8s linear infinite;
}

/* ---------- 通用按钮 ---------- */
.btn {
  height: 32px; padding: 0 14px; border-radius: var(--radius-btn);
  border: 1px solid var(--line); background: var(--card-solid); color: var(--text);
  font-size: 11px; font-weight: 800;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; transition: all .16s var(--ease); white-space: nowrap;
}
.btn:hover { background: var(--soft-2); }
.btn:active { transform: scale(.97); }
.btn.primary { background: var(--strong); color: var(--strong-text); border-color: var(--strong); }
.btn.primary:hover { opacity: .88; }
.btn.ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn.ghost:hover { background: var(--soft-2); color: var(--text); }
.btn.danger { color: var(--danger); border-color: var(--line); }
.btn.danger:hover { background: var(--danger-bg); border-color: var(--danger); }
.btn.sm { height: 26px; padding: 0 10px; font-size: 10px; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn svg { width: 13px; height: 13px; }
.btn.block { width: 100%; }

/* ---------- Toast ---------- */
.toast-host {
  /* 挪到右下角。原来在 top:16px / z-index:800，会直接盖住 z-index:30 的
     顶部工具栏 —— 连续几条 toast 时形成一条点击拦截带，工具栏完全点不动。 */
  position: fixed; right: 16px; bottom: 16px;
  z-index: 800; display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
  pointer-events: none;
  max-height: 60vh; overflow: hidden;
}
.toast {
  pointer-events: auto;
  min-width: 200px; max-width: 460px;
  padding: 10px 16px; border-radius: 14px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 12px 36px var(--shadow); backdrop-filter: blur(16px);
  font-size: 12px; font-weight: 700; color: var(--text);
  display: flex; align-items: center; gap: 8px;
  animation: fadeIn .22s var(--ease);
}
.toast .tx { line-height: 1.5; }
/* 撤销之类的操作按钮 */
.toast-action {
  margin-left: 6px; padding: 3px 10px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--soft);
  font-size: 11px; font-weight: 800; color: var(--text);
  cursor: pointer; flex-shrink: 0; transition: background .12s;
}
.toast-action:hover { background: var(--soft-2); }
.toast svg { width: 15px; height: 15px; flex-shrink: 0; }
.toast.error { border-color: var(--danger); color: var(--danger); background: var(--card-solid); }
.toast.success { border-color: var(--success); color: var(--success); background: var(--card-solid); }
.toast.warning { border-color: var(--warning); color: var(--warning); background: var(--card-solid); }
.toast.leaving { opacity: 0; transform: translateY(8px); transition: all .22s var(--ease); }

/* ---------- 进度条 ---------- */
.progress-track {
  height: 5px; border-radius: 3px; background: var(--soft-2);
  overflow: hidden; margin-top: 8px;
}
.progress-fill {
  height: 100%; border-radius: 3px; background: var(--strong);
  width: 0%; transition: width .25s var(--ease);
}
.progress-fill.indeterminate {
  width: 34% !important; animation: slide 1.1s ease-in-out infinite;
}
@keyframes slide { 0% { margin-left: -34%; } 100% { margin-left: 100%; } }
.progress-note {
  margin-top: 5px; font-size: 10px; font-weight: 700; color: var(--faint);
  display: flex; justify-content: space-between; gap: 6px;
}

/* ---------- 节点运行态 ----------
   三种动画全部是纯 CSS，只挂在 .running / .state-queued 上：
     蜂巢   —— body 上缓慢横向流动的六边形底纹，说明「这台机器在干活」
     圆环   —— 卡片外圈律动，不占卡片内部空间，不会盖住结果图
     圆点   —— 右上角状态胶囊里的呼吸点（原有）
   为什么不用 JS 驱动：500 个节点时每个节点挂一个 rAF，
   光是空转的定时器就能把帧率拖垮；而 CSS 动画由合成器处理，
   节点被视口裁剪（.culled）后浏览器自动跳过不可见部分的绘制。 */

/* 蜂巢：六边形用 repeating-conic 拼不出来，这里用三层 repeating-linear-gradient
   交叠成网格线，比 SVG data-URI 轻，也不会因为转义问题在某些浏览器里整条规则失效。
   z-index:-1 让它待在内容之下；配合下面 .node-body 的 isolation，不会渗到卡片外面。 */
.node.running .node-body::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    repeating-linear-gradient(60deg, color-mix(in srgb, var(--strong) 16%, transparent) 0 1px, transparent 1px 13px),
    repeating-linear-gradient(-60deg, color-mix(in srgb, var(--strong) 16%, transparent) 0 1px, transparent 1px 13px),
    repeating-linear-gradient(0deg, color-mix(in srgb, var(--strong) 12%, transparent) 0 1px, transparent 1px 15.6px);
  background-size: 200% 100%;
  animation: hexflow 5.5s linear infinite;
  pointer-events: none;
  opacity: .5;
  border-radius: 0 0 var(--radius-node) var(--radius-node);
}
@keyframes hexflow { from { background-position: 0 0, 0 0, 0 0; } to { background-position: 26px 0, -26px 0, 0 0; } }

/* 扫光：叠在蜂巢之上，压低不透明度，只当「在动」的提示而不是视觉主体 */
.node.running .node-body::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg, transparent 30%, rgba(120,180,255,.10) 50%, transparent 70%);
  background-size: 220% 100%; animation: shimmer 1.2s linear infinite;
  pointer-events: none; border-radius: 0 0 var(--radius-node) var(--radius-node);
}
@keyframes shimmer { from { background-position: 140% 0; } to { background-position: -40% 0; } }

/* 圆环律动：两圈不同周期的外扩，周期错开才不会看起来像整体闪一下 */
.node.running { outline: 2px solid var(--strong); outline-offset: 2px; }
.node.running::after {
  content: ""; position: absolute; inset: -3px; z-index: 7;
  border: 1.5px solid var(--strong); border-radius: var(--radius-node);
  animation: ring 2.1s var(--ease) infinite;
  pointer-events: none;
}
@keyframes ring {
  0%   { opacity: .55; transform: scale(1); }
  70%  { opacity: 0;   transform: scale(1.035); }
  100% { opacity: 0;   transform: scale(1.035); }
}

/* 排队：静态虚线描边。排队是「还没轮到我」，不该和运行中一样闪 */
.node.state-queued::after {
  content: ""; position: absolute; inset: -3px; z-index: 7;
  border: 1.5px dashed color-mix(in srgb, var(--muted) 60%, transparent);
  border-radius: var(--radius-node); pointer-events: none;
}

.node.node-error { outline: 2px solid var(--danger); outline-offset: 2px; }
.node { position: absolute; }
/* isolation 建立层叠上下文，让 .node-body::before 的 z-index:-1 被关在这个卡片里，
   不会顺着父级一路渗到画布底层去（没有 isolation 时负 z-index 会漏到 body 之下）。 */
.node .node-body { position: relative; isolation: isolate; }

.node-status {
  position: absolute; top: 8px; right: 34px; z-index: 8;
  display: none; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  background: var(--strong); color: var(--strong-text);
  font-size: 9px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
}
.node.running .node-status { display: flex; }
.node-status .dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; animation: pulse 1s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

/* 前庭敏感的用户：保留静态描边，去掉所有循环动画。
   这类动画没有传达文字之外的新信息，去掉不损失可及性。 */
@media (prefers-reduced-motion: reduce) {
  .node.running .node-body::before,
  .node.running .node-body::after,
  .node.running::after,
  .node-status .dot { animation: none; }
  .node.running .node-body::before { opacity: .28; }
  .node.running::after { opacity: .5; }
}

/* ---------- 媒体卡片（图片 / 视频 / 音频）：一张干净的卡 ---------- */
/*
  卡片里**只有媒体本身**。替换、删除、下载这些按钮全在上方悬浮工具条里，
  提示词和参数在下方面板里 —— 卡内不留任何常驻控件。

  为什么必须是 16:9 而不是固定像素高度：参考案例的空卡是 16:9，
  而节点高度默认是自适应的（`h: 0`）。写死高度就得在换图片时同步
  patch 节点高度，那会让节点尺寸跟着媒体变化 —— 节点尺寸是连线端点、
  小地图 fit、框选的输入，一路抖一路重排。用 aspect-ratio 则
  高度完全由 CSS 算，JS 一个字节都不用碰。
*/

/*
  头栏塌成 0 高，而不是 display:none。
  `.node-status`（运行中/成功/失败的胶囊）是 `.node-head` 的子元素，
  一旦 display:none 它跟着消失 —— 那是唯一一处告诉用户「正在生成」的地方。
  只藏标题和删除按钮、保留 status，塌掉的是那条 40px 的空栏。
*/
.is-media .node-head,
.is-text-card .node-head { height: 0; min-height: 0; padding: 0; border: none; cursor: default; }
/* 文本卡仍是 flex 容器，只写 height:0 压不住基础样式的 flex-basis:40px */
.is-text-card .node-head { display: none; }
.is-media .node-title, .is-media .node-head .close-btn, .is-media .node-head .props-btn,
.is-text-card .node-title, .is-text-card .node-head .close-btn, .is-text-card .node-head .props-btn { display: none; }
/*
  圆角必须和卡片**同一个值**。
  之前 `.media-stage` 写死 10px 而 `.node` 是 22px，于是图片的四角比卡片圆角小一圈：
  卡片的底色从四个角上露出来，而 `.node.selected` 的 outline（22px）
  又正好压在图片圆角之外 —— 用户截图里那条「描边不适配」就是这么来的：
  描边和图是两个不同的圆，各自描各自的。
  用同一个变量之后，改主题圆角时两边一起变，不会再漂。
*/
.is-media .node-body { padding: 0; }
.is-media .media-stage,
.is-media .media-empty,
.is-media .media-stage .media-fill { border-radius: var(--radius-node); }
/*
  兜底：高度锁死了，就不该出现任何滚动条。
  即使有老数据残留、或者将来某个分支又写上了 height，
  这一条也保证「图永远全显」而不是「图被裁进一个能滚的盒子」。
*/
.is-media .node-body { overflow: hidden; }
.is-media .media-fill { max-height: none; }

/* 卡片上沿之上的标题行：图标 + 标题 …… 尺寸·比例 */
.node-cap {
  position: absolute; bottom: 100%; left: 0; margin-bottom: 7px;
  display: flex; align-items: center; gap: 6px;
  max-width: 100%; pointer-events: none; white-space: nowrap;
  font-size: 11px; color: var(--muted);
}
.node-cap-icon { display: inline-flex; opacity: .75; }
.node-cap-icon svg { width: 12px; height: 12px; }
.node-cap-title { overflow: hidden; text-overflow: ellipsis; font-weight: 700; }
.node-cap-meta { margin-left: auto; padding-left: 10px; opacity: .8; font-variant-numeric: tabular-nums; }

/* 上方的悬浮工具条。hover / 选中才出现。 */
.node-float {
  position: absolute; bottom: 100%; left: 50%;
  transform: translateX(-50%) scale(var(--panel-scale, 1));
  transform-origin: bottom center;
  display: flex; align-items: center; gap: 2px;
  margin-bottom: 26px; padding: 3px;
  background: var(--card-solid); border: 1px solid var(--line);
  border-radius: 999px; box-shadow: 0 8px 24px var(--shadow);
  opacity: 0; pointer-events: none; z-index: 9;
  transition: opacity .14s var(--ease);
}
/*
  显形条件是**选中 / 面板打开**，不是 hover。

  用户批注：「上方的功能条是点击卡片才显示，不是鼠标悬停显示」。
  这不只是时机问题：工具条上是删除、下载、放大、蒙版 —— 全是
  **会改变数据或离开当前视图**的动作。鼠标扫过画布时逐个弹出它们，
  等于在每张卡上方摆一排随时可能误点的按钮；而它们本来的语境是
  「我已经选中这张卡，正在对它做事」。

  hover 只留给**低危且必要**的那一个入口：卡片内的
  「＋添加资产 / ⬆替换」—— 那个是这张卡唯一的上传方式，
  不指出来用户就没法把图放上去。
*/
.node.selected .node-float,
.node.panel-open .node-float { opacity: 1; pointer-events: auto; }
body.node-multi-select .node.selected .node-float { display: none !important; }
.node-float-btn {
  width: 26px; height: 26px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--muted);
  cursor: pointer; transition: background .12s var(--ease), color .12s;
}
.node-float-btn svg { width: 13px; height: 13px; }
.node-float-btn:hover { background: var(--soft); color: var(--text); }
.node-float-btn[data-act='del']:hover { color: var(--danger); background: var(--danger-bg); }

/* 文本卡：和图片卡同宽，正文干净，格式按钮全部浮在卡片上方 */
.node.is-text-card .node-body { padding: 14px; background: transparent; }
.node.is-text-card .text-pre {
  width: 100%; min-height: 148px; max-height: none; padding: 0;
  /* 正文就是卡片本身的一部分：不要再套一层底色/边框/圆角 */
  background: transparent; border: 0; border-radius: 0;
  overflow: visible; font-family: inherit;
  white-space: pre-wrap; overflow-wrap: anywhere;
  font-size: 13px; line-height: 1.6; color: var(--text);
  outline: none; cursor: text;
}
.node.is-text-card .text-pre.is-editing {
  cursor: text; box-shadow: none; border-radius: 0; padding: 0; margin: 0;
}
.node.is-text-card .resize-handle { display: none; }
.node.is-text-card .text-pre .text-empty { color: var(--faint); }
.node.is-text-card .text-pre h1,
.node.is-text-card .text-pre h2,
.node.is-text-card .text-pre h3 { margin: .45em 0 .25em; line-height: 1.25; }
.node.is-text-card .text-pre h1 { font-size: 22px; }
.node.is-text-card .text-pre h2 { font-size: 18px; }
.node.is-text-card .text-pre h3 { font-size: 15px; }
.node.is-text-card .text-pre p { margin: .35em 0; }
.node.is-text-card .text-pre ul,
.node.is-text-card .text-pre ol { margin: .4em 0; padding-left: 1.4em; }
.node.is-text-card .text-pre hr { border: none; border-top: 1px solid var(--line); margin: .8em 0; }
.node.is-text-card .node-float { gap: 0; border-radius: 14px; }
.text-float-btn {
  width: 22px; height: 24px; border-radius: 7px;
  font-size: 10px; font-weight: 800; letter-spacing: -.03em;
}
.text-float-btn svg { width: 12px; height: 12px; }
.media-empty {
  position: relative;
  aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center;
  background: var(--card-solid); color: var(--faint);
  cursor: default; border-radius: 10px;
  transition: color .15s var(--ease), background .15s;
}
.media-empty:hover { color: var(--muted); background: var(--soft); }
.media-empty.over { color: var(--strong); background: var(--soft); }
.media-empty svg { width: 34px; height: 34px; }

/*
  有图时**高度跟着图片的真实比例**（`height: auto`），
  竖图就是竖的卡片 —— 这是参考案例图 5/图 6 的形态。
*/
.media-stage { position: relative; border-radius: 10px; overflow: hidden; background: var(--card-solid); }
.media-fill { display: block; width: 100%; height: auto; }
/* 重绘遮罩图：彩色笔迹直接叠在原图上，pointer-events 留给卡片本身。 */
.media-mask-preview {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: fill; opacity: .55; pointer-events: none;
  border-radius: inherit; z-index: 1;
}
.media-stage video.media-fill { background: #000; }
.media-stage.is-audio { padding: 10px; }
.media-audio { width: 100%; }
.media-gone { padding: 24px; text-align: center; font-size: 11px; color: var(--faint); }

/*
  覆盖层按钮：hover 时才出现，选中时也留着（选中意味着用户已经在处理这张卡）。
  这里和上方工具条的区别是**动作风险**：上传/替换都是「往这张卡里放东西」，
  不改变别的卡片；而删除是会波及别处的。
*/
.media-add, .media-replace {
  position: absolute; top: 8px;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 9px; border-radius: 999px;
  background: color-mix(in srgb, var(--card-solid) 88%, transparent);
  backdrop-filter: blur(8px);
  border: 1px solid var(--line); color: var(--text);
  font-size: 10.5px; font-weight: 700; font-family: inherit;
  cursor: pointer; white-space: nowrap;
  opacity: 0; transition: opacity .14s var(--ease);
}
.media-add { left: 50%; transform: translateX(-50%); }
.media-replace { right: 8px; }
.media-add svg, .media-replace svg { width: 12px; height: 12px; }
.media-stage:hover .media-add,
.media-stage:hover .media-replace,
.media-empty:hover .media-add,
.node:hover .media-add,
.node:hover .media-replace,
.node.selected .media-add,
.node.selected .media-replace { opacity: 1; }

/*
  卡片光标是 default 而不是 zoom-in / pointer。
  点图片已经不再放大预览了（那是上方工具条里的「放大预览」按钮），
  留着 zoom-in 会把用户引向一个已经改掉的动作。
*/
.media-stage, .media-empty { cursor: default; }

/* 多张图时压在右下角的计数角标 */
/* ---------- 多图：折叠时露边角，展开时左右平铺 ---------- */
.media-stage.has-stack {
  padding: 0 10px 10px 0;
  background: transparent;
  overflow: visible;
}
.media-stage.has-stack .media-fill {
  position: relative; z-index: 4;
  border-radius: var(--radius-node);
}
.media-stack-back {
  position: absolute; inset: 0;
  border-radius: var(--radius-node);
  background: var(--card-solid);
  border: 1px solid var(--line);
}
.media-expand {
  position: absolute; right: 14px; bottom: 14px; z-index: 8;
  display: inline-flex; align-items: center; gap: 4px;
  height: 26px; padding: 0 9px; border-radius: 999px;
  background: color-mix(in srgb, var(--card-solid) 90%, transparent);
  backdrop-filter: blur(8px);
  border: 1px solid var(--line); color: var(--text);
  font: inherit; font-size: 10.5px; font-weight: 750; cursor: pointer;
}
.media-expand svg { width: 12px; height: 12px; }
.media-expand:hover { background: var(--soft-2); }

.media-stage.is-expanded {
  padding: 0; background: transparent; overflow: visible;
}
.media-expanded {
  display: flex; align-items: flex-start; gap: 8px;
}
.media-exp-item {
  position: relative; flex: 1 1 0; min-width: 0;
  border-radius: var(--radius-node);
  overflow: hidden;
  background: var(--card-solid);
}
.media-exp-img { display: block; width: 100%; height: auto; }
.media-exp-actions {
  position: absolute; z-index: 5;
  display: flex; align-items: center; gap: 4px;
}
.media-exp-actions.top { top: 8px; right: 8px; }
.media-exp-actions.bottom { bottom: 8px; right: 8px; }
.media-exp-btn {
  display: inline-flex; align-items: center; gap: 4px;
  height: 26px; padding: 0 8px;
  border-radius: 999px;
  background: color-mix(in srgb, #15161a 84%, transparent);
  border: 1px solid rgba(255,255,255,.18);
  color: rgba(255,255,255,.94);
  backdrop-filter: blur(8px);
  font: inherit; font-size: 10.5px; font-weight: 750;
  cursor: pointer; white-space: nowrap;
}
.media-exp-btn svg { width: 12px; height: 12px; }
.media-exp-btn:hover { background: rgba(38,40,47,.96); border-color: rgba(255,255,255,.32); }
.media-exp-btn.danger:hover { color: #fecaca; border-color: rgba(248,113,113,.55); background: rgba(90,22,22,.9); }
.media-exp-btn.to-node:hover { color: #bfdbfe; border-color: rgba(96,165,250,.55); }

.media-more {
  position: absolute; right: 8px; bottom: 8px;
  padding: 3px 8px; border-radius: 999px;
  background: color-mix(in srgb, var(--card-solid) 88%, transparent);
  backdrop-filter: blur(8px);
  border: 1px solid var(--line); color: var(--text);
  font-size: 10.5px; font-weight: 750; font-family: inherit; cursor: pointer;
}

/* 底图 / 参考 切换（从卡片上的常驻小按钮搬到了属性面板） */
.media-role-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 3px 0; }
.media-role-name { font-size: 12px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- 重绘态：遮罩画在图上，工具条在卡上，输入框在卡下 ---------- */
/*
  这三层都挂在**节点壳**上而不是 .node-body 里：
  body 是「签名变了就重建」的缓存区，换个模型、传张新图都会把它清掉，
  而用户涂了一半的遮罩不能跟着一起没。壳里的元素有独立的生命周期
  （和下方面板 .node-panel 同一个道理）。
*/
.node-body.drafting {
  position: relative;
  border-radius: var(--radius-node);
  overflow: hidden;
}
/*
  两层：下面那层只存白色遮罩笔画（接收指针事件），上面那层画「底图 + 红罩」。
  合成一层的话，每次重绘都要 clearRect + 重画底图，那一瞬间就把刚涂的遮罩擦了
  —— 症状是「画布在、也能拖，但涂完什么都没有」，极难定位。
*/
.draft-mask {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  cursor: crosshair; touch-action: none; z-index: 7;
  border-radius: var(--radius-node);
}
.draft-overlay {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none; z-index: 8;
  border-radius: var(--radius-node);
}

/* 工具条浮在卡片**外面**（上沿之上），和标题行、工具条共用那块地 */
.node-maskbar {
  position: absolute; bottom: 100%; left: 50%;
  transform: translateX(-50%) scale(var(--panel-scale, 1));
  transform-origin: bottom center;
  display: flex; align-items: center; gap: 4px;
  margin-bottom: 28px; padding: 5px;
  background: var(--card-solid); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: 0 8px 24px var(--shadow);
  z-index: 10; opacity: 1; pointer-events: auto;
}
.node-maskbar .maskbar-btn {
  width: 32px; height: 32px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--muted);
  cursor: pointer; transition: background .12s var(--ease), color .12s;
}
.node-maskbar .maskbar-btn svg { width: 17px; height: 17px; }
.node-maskbar .maskbar-btn:hover { background: var(--soft); color: var(--text); }
.node-maskbar .maskbar-btn.on { background: var(--strong); color: var(--strong-text); }
.node-maskbar .mask-size { width: 128px; accent-color: var(--strong); cursor: pointer; }

/* 颜色按钮 + 五色盘（工具条在卡片外，色盘向下弹出，不挡画布） */
.maskbar-color-wrap { position: relative; display: inline-flex; }
.node-maskbar .maskbar-color { position: relative; }
.mask-color-dot {
  width: 17px; height: 17px; border-radius: 50%;
  background: #ff3b5c; border: 2px solid var(--card-solid);
  box-shadow: 0 0 0 1px var(--line);
}
.mask-palette {
  position: absolute; top: calc(100% + 9px); left: 50%;
  transform: translateX(-50%);
  display: none; align-items: center; gap: 6px;
  padding: 7px 9px;
  background: var(--card-solid); border: 1px solid var(--line);
  border-radius: 999px; box-shadow: 0 10px 28px var(--shadow);
  z-index: 12;
}
.mask-palette.open { display: flex; }
.mask-color {
  width: 19px; height: 19px; padding: 0; border-radius: 50%;
  background: var(--dot); border: 2px solid var(--card-solid);
  box-shadow: 0 0 0 1px var(--line); cursor: pointer;
  transition: transform .12s var(--ease), box-shadow .12s;
}
.mask-color:hover { transform: scale(1.12); }
.mask-color.on { box-shadow: 0 0 0 2px var(--strong); transform: scale(1.12); }

/* 下方输入框 */
.redraw-foot {
  position: absolute; top: 100%; left: 50%;
  transform: translateX(-50%) scale(var(--panel-scale, 1));
  transform-origin: top center;
  margin-top: 12px; width: min(560px, 92vw);
  display: flex; flex-direction: column; align-items: stretch; gap: 10px;
  padding: 13px 14px 12px; border-radius: 18px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 18px 48px var(--shadow); z-index: 40;
}
/*
  重绘输入框**没有**镜像层。
  `.np-text` 全局是 `color: transparent`（颜色由 .np-hl 画），
  照搬到这里就是「输入了但一个字都看不见」——用户截图里那个灰块就是光标。
  这里把颜色和 placeholder 都显式还回来。
*/
.redraw-foot .np-text {
  min-height: 86px; font-size: 13px; line-height: 1.55;
  padding: 11px 12px; border-radius: 12px; resize: vertical;
  color: var(--text);
  -webkit-text-fill-color: var(--text);
  caret-color: var(--text);
}
.redraw-foot .np-text::placeholder { color: var(--muted); }
.redraw-foot-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.redraw-hint { font-size: 11px; color: var(--muted); }
.redraw-go {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 16px; border-radius: 999px;
  background: var(--strong); border: none; color: var(--strong-text);
  font-size: 12.5px; font-weight: 700; font-family: inherit; cursor: pointer;
}
.redraw-go svg { width: 16px; height: 16px; }
.node-busy { font-size: 10.5px; color: var(--muted); }

.node-body.image-editing {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-node);
}
.edit-stage {
  position: absolute; inset: 0; z-index: 4;
  overflow: hidden; border-radius: var(--radius-node);
  touch-action: none;
}
.edit-preview-base, .edit-annot, .edit-lamps, .edit-select, .edit-temp {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
.edit-annot, .edit-lamps, .edit-select, .edit-temp { pointer-events: none; }
.edit-preview-base { cursor: default; }
.edit-lamp-del {
  position: absolute; z-index: 22;
  transform: translate(-100%, -50%);
  padding: 4px 9px;
  border: 1px solid rgba(255, 76, 92, .9);
  border-radius: 7px;
  background: rgba(38, 18, 22, .94);
  color: #ff6474;
  font: 700 11px/1.2 Inter, "Microsoft YaHei", sans-serif;
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .4);
}
.edit-lamp-del:hover { background: rgba(92, 24, 32, .98); color: #ff8a96; }
.edit-text-input {
  position: absolute; z-index: 20;
  min-width: 140px; max-width: 80%;
  padding: 6px 9px;
  transform: translateY(-50%);
  background: color-mix(in srgb, var(--card-solid) 92%, transparent);
  border: 1px solid var(--strong);
  border-radius: 8px;
  font-family: inherit; font-weight: 700;
  outline: none; box-shadow: 0 6px 20px var(--shadow);
  pointer-events: auto;
}
.edit-busy {
  position: absolute; inset: 0; z-index: 30;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--card-solid) 72%, transparent);
  color: var(--text); font-size: 12px; font-weight: 700;
  backdrop-filter: blur(3px);
}

.node-editbar {
  position: absolute; bottom: 100%; left: 50%;
  transform: translateX(-50%) scale(var(--panel-scale, 1));
  transform-origin: bottom center;
  display: flex; align-items: center; gap: 2px;
  margin-bottom: 30px; padding: 5px;
  max-width: calc(100vw - 32px);
  background: var(--card-solid); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: 0 12px 34px var(--shadow);
  z-index: 50; pointer-events: auto;
}
.editbar-btn {
  flex: none; width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; border-radius: 50%;
  color: var(--muted); cursor: pointer;
  transition: background .12s var(--ease), color .12s, opacity .12s;
}
.editbar-btn svg { width: 17px; height: 17px; }
.editbar-btn:hover { background: var(--soft); color: var(--text); }
.editbar-btn.on { background: var(--strong); color: var(--strong-text); }
.editbar-btn:disabled { opacity: .28; cursor: default; }
.editbar-btn:disabled:hover { background: transparent; color: var(--muted); }
.editbar-btn.editbar-save { background: var(--strong); color: var(--strong-text); }
.editbar-save svg { width: 18px; height: 18px; }
.editbar-sep {
  flex: none; width: 1px; height: 20px;
  margin: 0 3px; background: var(--line);
}
.edit-color-wrap {
  position: relative; flex: none; width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; cursor: pointer;
  background: conic-gradient(#ff4d4d, #ffd84d, #62e36b, #4dc8ff, #7a6bff, #ff4dc4, #ff4d4d);
}
.edit-color-dot {
  width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid var(--card-solid);
  box-shadow: 0 0 0 1px var(--line);
  pointer-events: none;
}
.edit-color-input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
}
.edit-size-wrap {
  flex: none; display: flex; align-items: center; gap: 6px;
  padding: 0 6px;
}
.edit-size-wrap[hidden] { display: none; }
.edit-size-range { width: 104px; accent-color: var(--strong); cursor: pointer; }
.edit-size-val {
  width: 25px; font-size: 10px; text-align: right;
  color: var(--muted); font-variant-numeric: tabular-nums;
}

.node-lamppanel {
  position: absolute; top: 100%; left: 50%;
  transform: translateX(-50%) translateY(var(--lamp-lift, 0px)) scale(var(--panel-scale, 1));
  transform-origin: top center;
  margin-top: 12px; width: min(560px, 94vw);
  max-height: calc(100vh - 24px); overflow-y: auto; overscroll-behavior: contain;
  padding: 10px 14px 12px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 16px; box-shadow: 0 18px 48px var(--shadow);
  z-index: 42; pointer-events: auto;
}
.node-lamppanel[hidden] { display: none; }
.edit-lamp-head {
  display: flex; align-items: center; gap: 10px;
}
.edit-lamp-title { font-size: 12px; font-weight: 800; color: var(--text); }
.edit-lamp-actions { margin-left: auto; display: flex; align-items: center; gap: 6px; }
.edit-lamp-cancel, .edit-lamp-apply {
  padding: 5px 12px; border-radius: 999px;
  font-family: inherit; font-size: 11.5px; font-weight: 700; cursor: pointer;
}
.edit-lamp-cancel {
  border: 1px solid var(--line); background: transparent; color: var(--muted);
}
.edit-lamp-cancel:hover { background: var(--soft); color: var(--text); }
.edit-lamp-apply {
  border: none; background: var(--strong); color: var(--strong-text);
}
.edit-lamp-apply:disabled { opacity: .35; cursor: default; }
.edit-lamp-hint {
  margin-top: 3px; font-size: 10.5px; color: var(--faint);
}
.edit-lamp-rows {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 18px; margin-top: 8px;
}
@media (max-width: 560px) {
  .edit-lamp-rows { grid-template-columns: minmax(0, 1fr); }
}
.edit-lamp-row {
  display: grid; grid-template-columns: 58px minmax(0, 1fr) 42px;
  align-items: center; gap: 9px;
}
.edit-lamp-name { font-size: 11px; color: var(--muted); white-space: nowrap; }
.edit-lamp-range {
  width: 100%; accent-color: #e8b45d; cursor: pointer;
}
.edit-lamp-val {
  text-align: right; font-size: 10.5px; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.edit-lamp-seg {
  display: flex; align-items: center; gap: 2px;
  padding: 2px; border-radius: 999px; background: var(--soft);
}
.edit-lamp-seg-btn {
  flex: 1; padding: 4px 8px; border: none; border-radius: 999px;
  background: transparent; color: var(--muted);
  font-family: inherit; font-size: 10.5px; cursor: pointer;
}
.edit-lamp-seg-btn:hover { color: var(--text); }
.edit-lamp-seg-btn.on {
  background: var(--card-solid); color: var(--text);
  box-shadow: 0 1px 4px var(--shadow); font-weight: 700;
}
.edit-lamp-color {
  width: 42px; height: 24px; padding: 0;
  border: 1px solid var(--line); border-radius: 7px;
  background: transparent; cursor: pointer;
}

.node-editfoot {
  position: absolute; top: 100%; left: 50%;
  transform: translateX(-50%) scale(var(--panel-scale, 1));
  transform-origin: top center;
  margin-top: 12px; width: min(760px, 94vw);
  padding: 12px 14px 14px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: 0 18px 48px var(--shadow);
  z-index: 40; pointer-events: auto;
}
.node-editfoot[hidden] { display: none; }
.edit-foot-head {
  display: flex; align-items: center; gap: 10px;
}
.edit-foot-title { font-size: 12px; font-weight: 800; color: var(--text); }
.edit-foot-tabs {
  display: flex; align-items: center; gap: 2px;
  padding: 2px; border-radius: 999px; background: var(--soft);
}
.edit-tab {
  padding: 4px 10px; border: none; border-radius: 999px;
  background: transparent; color: var(--muted);
  font-family: inherit; font-size: 11px; cursor: pointer;
}
.edit-tab.on { background: var(--card-solid); color: var(--text); box-shadow: 0 1px 4px var(--shadow); }
.edit-reset {
  margin-left: auto; padding: 4px 10px;
  border: 1px solid var(--line); border-radius: 999px;
  background: transparent; color: var(--muted);
  font-family: inherit; font-size: 11px; cursor: pointer;
}
.edit-reset:hover { background: var(--soft); color: var(--text); }
.edit-adj-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 7px 22px; margin-top: 10px;
  max-height: 180px; overflow: auto;
}
.edit-adj-grid[hidden] { display: none; }
.edit-adj-row {
  display: grid; grid-template-columns: 54px minmax(0, 1fr) 32px;
  align-items: center; gap: 8px;
  color: var(--muted); font-size: 11px;
}
.edit-adj-name { white-space: nowrap; }
.edit-adj-range { width: 100%; accent-color: var(--strong); cursor: pointer; }
.edit-adj-val {
  text-align: right; color: var(--faint);
  font-variant-numeric: tabular-nums;
}

/* ---------- 裁剪 / 宫格切分 ---------- */
/*
  画布用**原图像素尺寸**，缩放只发生在 CSS 呈现层。
  和蒙版编辑器同一条理由：接口要的是原图坐标，不是屏幕上的缩略图坐标。
*/
.crop-editor { display: flex; flex-direction: column; gap: 10px; }
.crop-stage {
  background: var(--soft); border-radius: 12px; padding: 10px;
  display: flex; align-items: center; justify-content: center;
  /* 容器高度跟着图片走，不给固定值 —— 竖图和横图差好几倍 */
  position: relative;
}
.crop-canvas {
  max-width: 100%; max-height: 52vh;
  /* width:auto 而不是 100%：否则竖图会被撑成一整屏宽、只能看到中间一条 */
  height: auto; display: block;
  border-radius: 6px; cursor: crosshair; user-select: none;
  touch-action: none;
}
.crop-info { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.crop-grids { display: flex; flex-wrap: wrap; gap: 5px; }
.crop-grids .btn.active { border-color: var(--strong); color: var(--strong); background: var(--soft); }
/* 比例/宫格按钮和「新节点裁切」同一行：开关靠右，别挤到按钮中间 */
.crop-preset-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.crop-ratios { display: flex; flex-wrap: wrap; gap: 5px; }
.crop-ratios .btn.active { border-color: var(--strong); color: var(--strong); background: var(--soft); }
.crop-toggle {
  margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--muted); white-space: nowrap;
  cursor: pointer; user-select: none;
}
.crop-toggle:hover { color: var(--text); }
.crop-toggle input { width: 14px; height: 14px; margin: 0; accent-color: var(--strong); cursor: pointer; }
/* 宫格裁剪的两个出口：仅裁切 / 创建分镜格子 */
.crop-mode { margin-left: auto; display: inline-flex; gap: 4px; }
.crop-mode-btn {
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card-solid);
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.crop-mode-btn:hover { color: var(--text); border-color: var(--muted); }
.crop-mode-btn.active {
  border-color: var(--strong);
  color: var(--strong);
  background: var(--soft);
  font-weight: 700;
}
/*
  自定义宫格：盖在裁切图上方的 10×10 格子盘。
  拖动范围用「深色实心格」表示，和截图里的交互一致；确认后才改分割方式。
*/
.crop-picker {
  position: absolute; inset: 0; z-index: 4;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.42); border-radius: 12px;
}
.crop-picker[hidden] { display: none; }
.crop-picker-panel {
  width: min(300px, 92%); padding: 14px 14px 12px;
  background: var(--card-solid); border: 1px solid var(--line);
  border-radius: 16px; box-shadow: 0 18px 50px rgba(0,0,0,.35);
}
.crop-picker-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.crop-picker-title { font-size: 13px; font-weight: 600; }
.crop-picker-close {
  width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;
  border-radius: 50%; font-size: 16px; line-height: 1; color: var(--muted);
}
.crop-picker-close:hover { background: var(--soft-2); color: var(--text); }
.crop-picker-grid {
  display: grid; grid-template-columns: repeat(10, 1fr); gap: 3px;
  width: 100%; user-select: none; touch-action: none;
}
.crop-cell {
  aspect-ratio: 1; padding: 0; border-radius: 3px;
  background: var(--soft-2); cursor: crosshair;
}
.crop-cell:hover { background: var(--line-2); }
.crop-cell.on { background: var(--strong); }
.crop-picker-foot { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
.crop-picker-label { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.crop-picker-foot .btn { margin-left: auto; }

/* ---------- 节点内小工具条 ---------- */
.node-tools {
  display: flex; gap: 5px; flex-wrap: wrap; margin-top: 8px;
}
.node-hint {
  margin-top: 6px; font-size: 10px; line-height: 1.5; color: var(--faint); font-weight: 600;
}
.node-hint.warn { color: var(--warning); }
.node-hint.err { color: var(--danger); }
.node-hint.ok { color: var(--success); }

/* ---------- 结果网格 ---------- */
.result-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: 6px; margin-top: 8px;
}
.result-item {
  position: relative; width: 100%; aspect-ratio: 1;
  border-radius: 10px; overflow: hidden;
  border: 1px solid var(--line); background: var(--soft);
  cursor: zoom-in;
}
.result-item img { width: 100%; height: 100%; object-fit: cover; display: block; }

/**
 * 结果格现在也是素材拖拽源（拖到画布空白处新建卡片、拖到已有卡片追加）。
 *
 * 光标保持 zoom-in：单击开大图仍然是这个格子最主要的事，
 * 换成 grab 反而会让人以为「点不开」—— 想拖的人按住拖走就行，
 * 不需要额外的视觉邀请。
 * 只有真的拖起来了才换成抓取光标。
 */
.result-item[draggable='true']:active { cursor: grabbing; }
.result-item.dragging { opacity: .4; }
.video-wrap[draggable='true']:active { cursor: grabbing; }
.video-wrap.dragging { opacity: .4; }
/* 媒体单独一层：加载失败只替换这一层，操作条与主图标记必须留着 */
.result-item .result-media { position: absolute; inset: 0; }
.result-item .result-media video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* 视频上传卡片：88px 的方格里塞完整播放控件会挤成一团，
   所以缩略图给满，控件交给点击后的大预览。 */
.result-item .media-thumb[data-kind='video'] { width: 100%; height: 100%; object-fit: cover; display: block; }
.result-item:has(.media-thumb[data-kind='video']) { cursor: pointer; }
.result-item:has(.media-thumb[data-kind='video'])::after {
  content: '▶';
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 20px; color: #fff; text-shadow: 0 1px 6px rgba(0,0,0,.7);
  pointer-events: none;
}
/* 音频没有画面，占一个方形缩略图纯属浪费空间 —— 改成整行的播放条 */
.result-item:has(.media-thumb[data-kind='audio']) {
  aspect-ratio: auto; cursor: default;
  display: flex; align-items: center; gap: 8px; padding: 6px 8px;
}
.result-item .media-thumb[data-kind='audio'] { width: 100%; height: 32px; }
.result-item:has(.media-thumb[data-kind='audio']) .idx { position: static; }
.result-item .rm {
  position: absolute; top: 3px; right: 3px;
  width: 18px; height: 18px; border-radius: 50%;
  background: rgba(255,255,255,.92); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); opacity: 0; transition: opacity .14s; cursor: pointer;
}
.result-item:hover .rm { opacity: 1; }
.result-item .rm:hover { color: var(--danger); border-color: var(--danger); background: var(--danger-bg); }
.result-item .idx {
  position: absolute; left: 3px; bottom: 3px;
  padding: 1px 5px; border-radius: 5px;
  background: rgba(0,0,0,.6); color: #fff;
  font-size: 9px; font-weight: 800;
}

/* ---------- 非图片结果（.pptx / .pdf / .zip 等）---------- */
/* 这些文件不能当图片渲染，用图标 + 文件名 + 下载入口表达 */
.result-item.is-file {
  aspect-ratio: 4 / 3;
  cursor: default;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 8px;
}
.result-item .file-badge {
  width: 34px; height: 34px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel); border: 1px solid var(--line);
  color: var(--strong); flex: none;
}
.result-item .file-badge svg { width: 17px; height: 17px; }
.result-item .file-badge:hover { background: var(--hover); border-color: var(--strong); }
.result-item .file-name {
  font-size: 9.5px; color: var(--muted); text-align: center;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  line-height: 1.35; word-break: break-all;
}
/* 序号在文件格子里改到右上角，免得和文件名抢底部 */
.result-item.is-file .idx { left: auto; right: 3px; bottom: 3px; }

/* ---------- 结果组：主图标记与逐项操作 ---------- */
/* 主图：下游（图生图）与连线缩略图取的就是这一张，必须一眼看出是哪张 */
.result-item.is-primary {
  border-color: var(--strong);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--strong) 35%, transparent);
}
.result-item .primary-badge {
  position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--strong); color: #fff;
  display: flex; align-items: center; justify-content: center;
}
.result-item .primary-badge svg { width: 11px; height: 11px; }

/* 操作条在 hover 时浮出，不占布局、也不遮挡缩略图 */
.result-item .result-actions {
  position: absolute; inset: auto 0 0 0;
  display: flex; gap: 2px; justify-content: center;
  padding: 3px 2px;
  background: linear-gradient(to top, rgba(0,0,0,.72), transparent);
  opacity: 0; transition: opacity .14s;
}
.result-item:hover .result-actions,
.result-item:focus-within .result-actions { opacity: 1; }
.result-item .result-actions button {
  width: 20px; height: 20px; border-radius: 6px;
  background: rgba(255,255,255,.92); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  color: var(--text); cursor: pointer; transition: background .12s;
}
.result-item .result-actions button:hover { background: #fff; }
.result-item .result-actions button svg { width: 11px; height: 11px; }
.result-item .result-actions button.off { opacity: .3; cursor: not-allowed; }
.result-item .result-actions button:last-child:hover {
  color: var(--danger); border-color: var(--danger); background: var(--danger-bg);
}
.video-wrap {
  width: 100%; border-radius: 12px; overflow: hidden;
  border: 1px solid var(--line); background: #000; margin-top: 8px;
}
.video-wrap video { width: 100%; display: block; max-height: 260px; }

/* ---------- 上传区 ---------- */
.dropzone {
  width: 100%; padding: 16px 12px; border-radius: 12px;
  border: 1.5px dashed var(--line-2); background: var(--card-solid);
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  color: var(--faint); font-size: 11px; font-weight: 700;
  cursor: pointer; transition: all .18s var(--ease); text-align: center;
}
.dropzone:hover, .dropzone.over { border-color: var(--strong); color: var(--strong); background: var(--soft); }
.dropzone svg { width: 22px; height: 22px; opacity: .6; }

/* ---------- 提示词库选择器 ---------- */
.picker-overlay {
  position: fixed; inset: 0; z-index: 600;
  background: rgba(0,0,0,.42); backdrop-filter: blur(4px);
  display: none; align-items: center; justify-content: center; padding: 24px;
}
.picker-overlay.show { display: flex; }
.picker-modal {
  width: 100%; max-width: 720px; max-height: 82vh;
  background: var(--card-solid); border: 1px solid var(--line);
  border-radius: 20px; box-shadow: 0 24px 64px rgba(0,0,0,.28);
  display: flex; flex-direction: column; overflow: hidden;
}
.picker-head { padding: 18px 22px 12px; display: flex; flex-direction: column; gap: 10px; }
.picker-head-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.picker-head h3 { font-size: 16px; font-weight: 800; }
.picker-search {
  height: 38px; border: 1px solid var(--line-2); border-radius: var(--radius-input);
  background: var(--card-solid); color: var(--text); padding: 0 12px; font-size: 13px; outline: none;
}
.picker-search:focus { border-color: var(--strong); }
.picker-list { flex: 1; overflow-y: auto; padding: 0 22px 8px; display: flex; flex-direction: column; gap: 8px; }
.picker-item {
  padding: 11px 14px; border: 1px solid var(--line); border-radius: 12px;
  cursor: pointer; transition: all .14s var(--ease);
}
.picker-item:hover { border-color: var(--strong); background: var(--soft); }
.picker-item .t { font-size: 12px; font-weight: 800; margin-bottom: 3px; }
.picker-item .b {
  font-size: 11px; color: var(--muted); line-height: 1.55;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.picker-item .src { font-size: 9px; color: var(--faint); font-weight: 700; margin-top: 5px; }
.picker-foot { padding: 12px 22px; border-top: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; }
/* 底部的「共 N 条」这类统计文字。之前只有 class 没样式，靠内联硬撑，字号比正文还小一档。 */
.picker-foot .hint-text { font-size: 11px; font-weight: 600; color: var(--faint); }
.picker-empty { padding: 40px; text-align: center; color: var(--muted); font-size: 13px; }

/* ---------- 图片查看器 ---------- */
.viewer-overlay {
  position: fixed; inset: 0; z-index: 700;
  background: rgba(0,0,0,.88); display: none;
  align-items: center; justify-content: center; flex-direction: column; gap: 12px;
}
.viewer-overlay.show { display: flex; }
.viewer-overlay img { max-width: 92vw; max-height: 82vh; object-fit: contain; border-radius: 8px; }
.viewer-bar { display: flex; gap: 8px; align-items: center; }
.viewer-bar .btn { background: rgba(255,255,255,.12); color: #fff; border-color: rgba(255,255,255,.2); }
.viewer-bar .btn:hover { background: rgba(255,255,255,.22); }
.viewer-cap { color: rgba(255,255,255,.7); font-size: 11px; font-weight: 700; }

/* ---------- 渠道编辑弹窗 ---------- */
.modal-overlay {
  position: fixed; inset: 0; z-index: 620;
  background: rgba(0,0,0,.45); backdrop-filter: blur(4px);
  display: none; align-items: center; justify-content: center; padding: 24px;
}
.modal-overlay.show { display: flex; }
.modal {
  width: 100%; max-width: 620px; max-height: 86vh;
  background: var(--card-solid); border: 1px solid var(--line);
  border-radius: 20px; box-shadow: 0 24px 64px rgba(0,0,0,.3);
  display: flex; flex-direction: column; overflow: hidden;
}
.modal-head { padding: 20px 24px 14px; display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.modal-head h3 { font-size: 18px; font-weight: 800; }
.modal-head p { font-size: 11px; color: var(--muted); margin-top: 3px; }
.modal-body { padding: 0 24px; overflow-y: auto; flex: 1; }
.modal-foot { padding: 14px 24px; border-top: 1px solid var(--line); display: flex; justify-content: flex-end; gap: 8px; }

.field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; }
.field > label { font-size: 10px; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: .07em; }
.field input:not([type="checkbox"]):not([type="radio"]), .field select, .field textarea {
  height: 38px; border: 1px solid var(--line-2); border-radius: var(--radius-input);
  background: var(--card-solid); color: var(--text); padding: 0 12px; font-size: 13px; outline: none; width: 100%;
}
.field textarea { height: auto; min-height: 76px; padding: 10px 12px; resize: vertical; line-height: 1.55; font-family: inherit; }
.field input:not([type="checkbox"]):focus, .field select:focus, .field textarea:focus { border-color: var(--strong); }
.field .help { font-size: 10px; color: var(--faint); line-height: 1.5; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* ---------- 模型能力标记 ---------- */
.caps { display: inline-flex; gap: 3px; }
.cap-tag {
  padding: 1px 6px; border-radius: 4px; font-size: 9px; font-weight: 800;
  background: var(--soft); color: var(--muted); border: 1px solid var(--line);
  text-transform: uppercase; letter-spacing: .04em;
}
.cap-tag.image { color: #2563eb; border-color: #bfdbfe; background: #eff6ff; }
.cap-tag.video { color: #7c3aed; border-color: #ddd6fe; background: #f5f3ff; }
.cap-tag.text  { color: #059669; border-color: #a7f3d0; background: #ecfdf5; }
.cap-tag.audio { color: #d97706; border-color: #fde68a; background: #fffbeb; }
.theme-dark .cap-tag.image { background: #172554; border-color: #1e3a8a; color: #93c5fd; }
.theme-dark .cap-tag.video { background: #2e1065; border-color: #4c1d95; color: #c4b5fd; }
.theme-dark .cap-tag.text  { background: #022c22; border-color: #065f46; color: #6ee7b7; }
.theme-dark .cap-tag.audio { background: #451a03; border-color: #78350f; color: #fcd34d; }

/* ---------- 配置页：状态灯 ---------- */
.status-line {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 700; color: var(--muted);
}
.status-line .led { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); flex-shrink: 0; }
.status-line.online .led { background: var(--success); box-shadow: 0 0 0 3px rgba(5,150,105,.18); }
.status-line.offline .led { background: var(--danger); box-shadow: 0 0 0 3px rgba(220,38,38,.15); }

/* ---------- 画布列表行内重命名 ---------- */
.canvas-title-input {
  width: 100%; border: 1px solid var(--strong); border-radius: 8px;
  background: var(--card-solid); color: var(--text);
  font-size: 13px; font-weight: 750; padding: 5px 8px; outline: none;
}

/* ---------- 节点下方的生成面板 ---------- */
/*
  定位的三个关键（参考 basketikun/infinite-canvas 的做法）：

  ① `top: 100%` + `left: 50%` —— 锚在节点**正下方居中**。
  ② `scale(1/画布缩放)` —— 面板跟着节点走，但**屏幕上大小恒定**。
     不这么做的话，画布放大到 200% 时面板也跟着放大一倍，
     而用户是在 100% 的屏幕上操作它 —— 控件会大到变形；缩小到 30% 时
     面板小到点不中。用 fixed 定位也能恒定，但那样每帧都要把节点的世界
     坐标换算成视口坐标，还得自己处理边缘翻转；反向缩放这些全免了。
  ③ `transform-origin: top center` —— 缩放锚在面板**顶边中点**。
     这是"贴在节点下方"能成立的前提：锚在 center 的话，节点往左拖，
     面板会相对节点往右漂。

  ⚠️ 代价：节点贴着视口边缘时面板会溢出视口。left 用 max/min 夹一次，
  但那只解决水平方向；垂直方向交给画布自身的滚动（面板矮，不值得再算）。
*/
.node-panel {
  position: absolute; top: 100%; left: 50%;
  /* 宽度由 renderer 按「卡片宽 × 1.5」写进来（见 node-panel.js panelWidth）。
     这条 width 只是兜底，JS 一定先写。 */
  width: 520px; margin-top: 10px;
  transform-origin: top center;
  transform: translateX(-50%) scale(var(--panel-scale, 1));
  z-index: 40;
  /* 面板比卡片宽，节点窄的时候两侧会露出来 —— 不裁 */
  pointer-events: auto;
}
.node-panel[hidden] { display: none; }
/*
  整块面板的观感照参考案例：
    ┌─────────────────────────────────────┐
    │ [✳][+]                      [⛶]     │  顶排：带底色的方块，不是透明小图标
    │  描述任何你想要生成的内容             │  输入区：撑满，占据面板的大半高度
    │ [✦ GPT Image…⌄][中等(推荐)·自动⌄]  ▓ [↑] │  底排：两颗**大**胶囊 + 成本 + 圆形发送
    └─────────────────────────────────────┘
  之前那一版的问题是底排又小又挤：胶囊 11px 字号、7px 内边距、圆角拉满，
  两个加起来还不到面板宽度的四分之一，右边空出一大片 ——
  视觉上像是「控件掉到了边上」，而参考案例里它们是这一行的主角。
*/
.node-panel-inner {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 16px; padding: 14px 16px 15px;
  box-shadow: 0 18px 48px var(--shadow);
  display: flex; flex-direction: column; gap: 13px;
}
/* 面板里的输入框不能触发画布的框选 / 拖拽 */
.node-panel textarea, .node-panel select, .node-panel input {
  pointer-events: auto;
}

/* ---- 顶排：[✨] [＋] ····· [⛶] */
.np-top { display: flex; align-items: center; gap: 6px; }
.np-spacer { flex: 1; }
.np-audio-mode {
  display: inline-flex; align-items: center; gap: 2px; padding: 2px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--soft);
}
.np-audio-mode-btn {
  height: 26px; padding: 0 10px; border: none; border-radius: 999px;
  display: inline-flex; align-items: center; gap: 5px;
  background: transparent; color: var(--muted); font-size: 11px; font-weight: 700;
  cursor: pointer; transition: background .12s var(--ease), color .12s;
}
.np-audio-mode-btn svg { width: 12px; height: 12px; }
.np-audio-mode-btn:hover { color: var(--text); }
.np-audio-mode-btn.active { background: var(--strong); color: var(--strong-text); }
/*
  顶排按钮要有**自己的底**，不是透明的小图标。
  参考案例里它们是两块深色方块 —— 因为这一排不是装饰，
  「生成模式 / 加参考 / 展开」是三个能点的操作，
  透明图标在这个尺寸下会被当成装饰而不是按钮。
*/
.np-icon-btn {
  width: 30px; height: 30px; padding: 0; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--soft); border: 1px solid var(--line);
  color: var(--muted); cursor: pointer; flex: none;
  transition: background .14s var(--ease), color .14s, border-color .14s;
}
.np-icon-btn svg { width: 15px; height: 15px; }
.np-icon-btn:hover { color: var(--text); border-color: var(--strong); }

/* ---- 「从画布选参考」的状态 */
.np-icon-btn.active {
  background: var(--strong); border-color: var(--strong); color: var(--strong-text);
}
.np-pick-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 12px; border-radius: 10px;
  background: var(--soft); border: 1px dashed var(--line);
  color: var(--text); font-size: 12.5px; font-weight: 650;
}
.np-pick-text { flex: 1; min-width: 0; }
.np-pick-x {
  flex: none; width: 22px; height: 22px; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--muted); cursor: pointer;
}
.np-pick-x:hover { background: var(--soft-2, var(--soft)); color: var(--text); }
.np-pick-x svg { width: 13px; height: 13px; }

/*
  候选卡片高亮。
  用 outline 而不是 box-shadow：box-shadow 会被 .node 的边框圆角吃掉一层，
  而 outline 画在盒子之外，不会被卡片自己的底色压住。
*/
.node.pickable, .node.quote-pickable {
  outline: 2px solid var(--strong);
  outline-offset: 3px;
  cursor: copy;
}
.node.picking-ref { cursor: default; }

/* ---- 提示词：撑满，无边框（整个面板就是输入区） */
/*
  ⚠️ `.np-text` 和 `.np-hl` 的**每一项盒模型/排版属性都必须一致**：
  字体、字号、行高、内边距、换行规则、边框。
  两层靠像素对齐，所以差 1px 的 letter-spacing 就会让芯片底色盖在错的字上。
  写成同一组变量，避免以后只改一处。
*/
.np-prompt { position: relative; }
.np-prompt .np-text,
.np-prompt .np-hl,
.np-prompt .np-ph {
  font-family: inherit;
  font-size: 14px;
  line-height: 1.6;
  letter-spacing: 0;
  padding: 2px;
  border: 0;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: break-word;
  margin: 0;
}
.np-text {
  position: relative; z-index: 1;
  display: block; width: 100%; min-height: 120px; max-height: 320px;
  overflow-y: auto; resize: none; outline: none;
  background: transparent;
  /* 文字**透明**：颜色由上面那层镜像提供，芯片才能画在字底下。
     光标色必须显式给回来，否则输入时看不见自己打在哪。 */
  color: transparent;
  caret-color: var(--text);
}
/* 原生 placeholder 必须彻底关掉：我们自己用 .np-ph 画（为了和镜像逐像素对齐）。
   只要它还有颜色，输入内容后就会和正文叠在一起 —— 用户看到两层字。 */
.np-text::placeholder { color: transparent; }
.np-text::selection { background: color-mix(in srgb, var(--strong) 28%, transparent); }
/* 镜像层：只画，不可交互；芯片自己除外 */
.np-hl {
  position: absolute; inset: 0; z-index: 2;
  overflow: hidden; pointer-events: none;
  color: var(--text);
}
/* 原生 placeholder 在 color:transparent 的 textarea 上依然按自己的颜色显示，
   但空态镜像里没有任何字，所以再加一层给「聚焦前」一个一致的灰。 */
.np-ph {
  position: absolute; inset: 0; z-index: 0;
  color: var(--faint); pointer-events: none;
}
.np-text:focus + .np-hl + .np-ph,
.np-text:not(:placeholder-shown) + .np-hl + .np-ph { opacity: 0; }

/* 提示词里的 @ 引用芯片 */
.np-ref-chip {
  pointer-events: auto;
  display: inline;
  padding: 1px 6px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--success) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--success) 45%, transparent);
  color: var(--success);
  font-weight: 650;
  cursor: zoom-in;
  transition: transform .14s var(--ease), background .14s var(--ease);
}
.np-ref-chip:hover { transform: scale(1.12); background: color-mix(in srgb, var(--success) 26%, transparent); }

/* ---- 「＋」上传参考图：本地上传 / 资产库 */
.np-add-wrap { position: relative; flex: none; }
.np-add-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 8;
  display: none; flex-direction: column; min-width: 160px;
  padding: 5px; border-radius: 10px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 14px 38px var(--shadow);
}
.np-add-menu.open { display: flex; }
.np-add-item {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px; border: 0; border-radius: 7px;
  background: transparent; color: var(--text);
  font: inherit; font-size: 12.5px; text-align: left; cursor: pointer;
  white-space: nowrap;
}
.np-add-item:hover { background: var(--soft); }
.np-add-ic { display: inline-flex; width: 16px; justify-content: center; color: var(--muted); }
.np-add-ic svg { width: 14px; height: 14px; }

/* 资产库选择弹层 */
.np-asset-overlay {
  position: fixed; inset: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .42); backdrop-filter: blur(2px);
}
.np-asset-modal {
  width: min(720px, 92vw); max-height: min(560px, 84vh);
  display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: 0 28px 80px var(--shadow);
  overflow: hidden;
}
.np-asset-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--line);
}
.np-asset-title { flex: 1; font-size: 13px; font-weight: 750; }
.np-asset-x {
  width: 26px; height: 26px; border-radius: 7px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid transparent; color: var(--muted); cursor: pointer;
}
.np-asset-x:hover { background: var(--soft); color: var(--text); }
.np-asset-x svg { width: 14px; height: 14px; }
.np-asset-body { flex: 1; min-height: 0; overflow-y: auto; }
/*
  瀑布流：多列 + 每张图按自身比例全显（不裁切成正方形）。
  之前用 grid + `img { aspect-ratio: 1; object-fit: cover }`，
  在弹层这种高度受限的容器里被压成了横条 —— 用户看到的是
  「图全折叠起来、点不到」，而这正是不该裁切的素材库。
*/
.np-asset-grid {
  columns: 4 150px;
  column-gap: 10px;
  padding: 14px;
}
.np-asset-cell {
  display: block; width: 100%; margin: 0 0 10px;
  break-inside: avoid; page-break-inside: avoid;
  padding: 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  background: var(--soft); color: var(--text); cursor: pointer;
}
.np-asset-cell:hover { border-color: var(--strong); }
.np-asset-cell img { width: 100%; height: auto; display: block; }
.np-asset-name {
  display: block; padding: 5px 7px 7px; font-size: 10.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left;
}
.np-asset-empty {
  padding: 40px 10px; text-align: center;
  color: var(--muted); font-size: 12px;
  break-inside: avoid;
}

/* ---- 参考缩略图条（在面板顶排里，不在卡片里） */
/*
  ⚠️ 这里**不能**写 overflow: hidden（原图截图标的就是「悬停没放大」）。
  缩略图靠 `transform: scale()` 放大，而 overflow:hidden 会把放大的部分
  **裁掉** —— 于是放大这条 CSS 一直生效、用户却完全看不到效果。
  这类「代码没错、现象正好相反」的坑，只有真的悬停一次才会发现。
  用 flex-wrap 代替裁剪：缩略图多了换行，而不是被切掉。
*/
.np-refs { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; min-width: 0; }
.np-ref {
  position: relative; flex: none;
  width: 34px; height: 34px; padding: 0;
  border-radius: 8px; overflow: visible; cursor: pointer;
  border: 1px solid var(--line); background: var(--soft);
  transition: transform .16s var(--ease), border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.np-ref img { width: 100%; height: 100%; object-fit: cover; border-radius: 7px; display: block; }
.np-ref:hover {
  transform: scale(1.22);
  border-color: var(--strong);
  box-shadow: 0 6px 20px var(--shadow);
  z-index: 3;
}
.np-ref.empty { background: var(--soft-2, var(--soft)); }
.np-ref-i {
  position: absolute; right: -3px; bottom: -3px;
  min-width: 14px; height: 14px; padding: 0 3px;
  border-radius: 999px; background: var(--strong); color: var(--strong-text);
  font-size: 9px; font-weight: 800; line-height: 14px; text-align: center;
  pointer-events: none;
}

/* ---- 打 @ 时的候选（批注②） */
.np-ac {
  position: absolute; left: 0; top: 100%; z-index: 6;
  margin-top: 4px; display: none;
}
.np-ac.open { display: block; }
.np-ac-list {
  min-width: 100%; max-width: 340px; max-height: 230px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; padding: 4px;
  box-shadow: 0 14px 38px var(--shadow);
}
.np-ac-row {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 7px; border-radius: 7px; cursor: pointer;
  color: var(--text);
}
.np-ac-row:hover, .np-ac-row.on { background: var(--soft); }
.np-ac-img { width: 26px; height: 26px; border-radius: 5px; object-fit: cover; flex: none; }
.np-ac-ic { display: inline-flex; width: 26px; justify-content: center; color: var(--muted); flex: none; }
.np-ac-ic svg { width: 14px; height: 14px; }
.np-ac-name { flex: 1; min-width: 0; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.np-ac-tag { flex: none; font-size: 9.5px; color: var(--strong); font-weight: 800; }

/* ---- 悬停 @ 芯片时的大预览（批注⑤ / 参考图 5） */
.np-chip-preview {
  position: fixed; z-index: 60; display: none;
  flex-direction: column; gap: 4px; max-width: 280px;
  padding: 6px; border-radius: 10px;
  background: var(--card-solid); border: 1px solid var(--line);
  box-shadow: 0 16px 44px var(--shadow);
  pointer-events: none;
}
.np-chip-preview.on { display: flex; }
.np-chip-preview img { width: 100%; max-height: 220px; object-fit: contain; border-radius: 6px; display: block; }
.np-chip-preview-cap {
  font-size: 10.5px; color: var(--muted); font-weight: 650;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/*
  底排：**不要分割线**（用户批注②）。
  之前那条 `border-top` 是按「表单分组」的思路加的 ——
  把输入区和参数区分成两块。但参考案例里整个面板是一个输入区，
  参数是它的**附属**，一条横线反而把「一个东西」切成了「两个东西」；
  而面板宽度本来就只有 420px，横线一画，剩下的行就显得更挤。
  靠间距分组就够了（gap: 12px）。
*/
.np-foot {
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
}
/* 右组（成本 + 发送）：唯一的 `margin-left:auto`，见 node-panel.js 里的说明 */
.np-foot-right { display: flex; align-items: center; gap: 10px; margin-left: auto; flex: none; }

/*
  胶囊触发器。参考案例里是**圆角矩形**而不是药丸，且明显更大 ——
  药丸形状在两个并排时读起来像「两颗糖」，而不是两个控件。
*/

/*
  ⚠️ 上限必须写在 **wrap** 上，不能写在 chip 上。
  `.np-chip { max-width: 46% }` 的百分比是相对 wrap 的宽度，
  而 wrap 的宽度又是由 chip 的内容撑出来的 —— 互相依赖，浏览器只能解析成
  「不确定」再退化。结果实测：wrap 89px 而 chip 只有 40px，
  多出来的 49px 全是空白，于是两个胶囊之间被这条空白撑开 57px ——
  正是用户截图里「底排空一大片」的样子。
  wrap 上的百分比则相对 `.np-foot` 的内容盒，是确定的。
*/
.np-pop-wrap {
  position: relative; display: flex;
  flex: 0 1 auto; min-width: 0; max-width: 44%;
}
.np-chip {
  display: inline-flex; align-items: center; gap: 7px;
  min-width: 0; max-width: 100%;
  padding: 7px 11px; border-radius: 10px;
  background: var(--soft); border: 1px solid var(--line);
  color: var(--text); font-size: 12.5px; font-weight: 650;
  font-family: inherit; cursor: pointer; text-align: left;
  transition: background .14s var(--ease), color .14s, border-color .14s;
}
.np-chip:hover, .np-pop-wrap.open .np-chip { border-color: var(--strong); background: var(--soft-2, var(--soft)); }
/* 模型胶囊左侧那个小图标（sparkles），参考案例里也有 */
.np-chip-glyph { display: inline-flex; flex: none; opacity: .75; }
.np-chip-glyph svg { width: 13px; height: 13px; }
.np-chip-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.np-caret { display: inline-flex; flex: none; opacity: .7; }
.np-caret svg { width: 13px; height: 13px; transition: transform .15s var(--ease); }

/* 向上弹的浮层。bottom:100% 而不是 top:100% —— 面板本身已经在卡片下方，
   再往下弹会掉出视口，而参考案例的参数/模型浮层都是朝卡片方向弹的。 */
.np-pop {
  position: absolute; bottom: calc(100% + 8px); left: 0;
  min-width: 100%; width: max-content; max-width: 420px;
  max-height: 46vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: 8px;
  box-shadow: 0 14px 38px var(--shadow);
  display: none; z-index: 3;
}
.np-pop.wide { min-width: 300px; }
.np-pop.open { display: block; }

/*
  参数浮层里的**分组**：小标题 + 一排档位。
  刻意不给每组加边框或底色 —— 浮层已经有自己的边框，再套一层框会变成
  「框里的框」，参考案例图 7 就是纯靠小标题和留白分组的。
*/
.field-groups { display: flex; flex-direction: column; gap: 12px; }
.field-group { display: flex; flex-direction: column; gap: 6px; }
.field-group-cap {
  font-size: 10.5px; font-weight: 700; color: var(--faint);
  letter-spacing: .02em;
}
/* chip 行在浮层里允许换行：档位多的组（比例 9 项、清晰度 5 项）一行放不下 */
.field-groups .chip-row, .field-groups .seg-row { flex-wrap: wrap; }

/* 模型列表：每行 模型名 + 渠道，右侧单价 */
.np-model-list { display: flex; flex-direction: column; gap: 1px; }
.np-model-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline; column-gap: 10px;
  padding: 7px 9px; border-radius: 9px; cursor: pointer;
  color: var(--text); transition: background .12s var(--ease);
}
.np-model-row:hover { background: var(--soft); }
.np-model-row.active { background: var(--soft); box-shadow: inset 0 0 0 1px var(--strong); }
.np-model-name { font-size: 12.5px; font-weight: 750; }
.np-model-sub {
  grid-column: 1; font-size: 10.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.np-model-price {
  grid-row: 1 / span 2; grid-column: 2; align-self: center;
  font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.np-empty { padding: 8px; font-size: 11.5px; color: var(--muted); }

/* 模型分组：本地 ComfyUI 始终在上，API 在下 */
.np-model-picker { display: flex; flex-direction: column; min-width: 340px; }
.np-model-search {
  position: sticky; top: -8px; z-index: 3;
  height: 30px; margin-bottom: 5px; padding: 0 10px;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--panel); color: var(--text);
  font: inherit; font-size: 11.5px; outline: none;
}
.np-model-search:focus { border-color: var(--strong); }
.np-model-group {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 9px 4px;
  font-size: 10px; color: var(--muted);
}
.np-model-group:not(:first-of-type) {
  margin-top: 4px; border-top: 1px solid var(--line);
}
.np-model-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.np-model-dot.is-comfyui { background: #34d399; box-shadow: 0 0 0 3px rgb(52 211 153 / .12); }
.np-model-dot.is-api { background: #60a5fa; box-shadow: 0 0 0 3px rgb(96 165 250 / .12); }
.np-model-group-note { margin-left: auto; font-weight: 600; color: var(--faint); }
.np-model-row.is-comfyui { box-shadow: inset 2px 0 0 rgb(52 211 153 / .45); }
.np-model-row.is-api { box-shadow: inset 2px 0 0 rgb(96 165 250 / .35); }
.np-model-name { display: flex; align-items: center; gap: 5px; min-width: 0; }
.np-model-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.np-model-src {
  flex: none; padding: 0 4px; border-radius: 4px;
  font-size: 8px; font-weight: 800; line-height: 14px;
  border: 1px solid transparent;
}
.np-model-src.is-comfyui {
  color: #6ee7b7; border-color: rgb(52 211 153 / .3); background: rgb(52 211 153 / .1);
}
.np-model-src.is-api {
  color: #93c5fd; border-color: rgb(96 165 250 / .3); background: rgb(96 165 250 / .1);
}
.np-model-more {
  margin: 7px 4px 2px; padding: 7px 9px;
  border: 1px dashed var(--line-2); border-radius: 9px;
  background: transparent; color: var(--muted);
  font: inherit; font-size: 11px; font-weight: 700; cursor: pointer;
}
.np-model-more:hover { color: var(--text); border-color: var(--strong); background: var(--soft); }
.np-model-more:disabled { opacity: .55; cursor: default; }
.np-model-more-hint { padding: 7px 9px 2px; font-size: 10.5px; color: var(--faint); }

/* 成本 */
.np-cost {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums;
  flex: none;
}
.np-cost svg { width: 13px; height: 13px; }

/*
  发送：圆形实心。
  `margin-left: auto` 现在只存在于 `.np-foot-right` 一处 ——
  之前成本和发送键各自挂了一个，而 flex 里一个容器只能有一个 auto 吃掉剩余空间，
  于是两个平分，胶囊被挤到最左、中间空出一大片。
*/
.np-send {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--strong); border: none; color: var(--strong-text);
  cursor: pointer;
  transition: opacity .14s var(--ease), transform .14s var(--ease);
}
.np-send svg { width: 17px; height: 17px; }
.np-send:hover:not(:disabled) { transform: scale(1.06); }
.np-send:disabled { opacity: .35; cursor: default; }

/* 面板触发按钮（⚡）：贴在卡片右上角，点开/收起下方面板 */
.node-gen-btn {
  position: absolute; right: 6px; top: 6px; z-index: 6;
  width: 26px; height: 26px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--card-solid); border: 1px solid var(--line);
  color: var(--muted); cursor: pointer;
  opacity: 0; transition: opacity .15s var(--ease), color .15s;
}
.node-gen-btn svg { width: 14px; height: 14px; }
/* 只在 hover / 选中 / 面板打开时露出来 —— 常驻会在每张卡片右上角多一个噪点 */
.node:hover .node-gen-btn,
.node.selected .node-gen-btn,
.node-gen-btn.active { opacity: 1; }
.node-gen-btn:hover { color: var(--text); }
.node-gen-btn.active { color: var(--strong); border-color: var(--strong); }
.node-gen-btn[hidden] { display: none; }

/* ---------- 生成记录抽屉 ---------- */
.jobs-drawer {
  position: absolute; right: 16px; top: 70px; width: 340px; max-height: calc(100vh - 160px);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 20px; box-shadow: 0 16px 44px var(--shadow); backdrop-filter: blur(16px);
  z-index: 26; display: none; flex-direction: column; overflow: hidden;
}
/* flex 子项默认 min-width:auto，内容过宽时会把父容器撑开而不是自己滚动。
   资产库的网格就是这么被顶出可视区的。 */
.jobs-drawer > * { min-width: 0; }
.jobs-drawer.open { display: flex; }

/*
  画布助手悬浮球（右下角）。

  为什么是 fixed：它要一直在，不该跟着画布平移或缩放 ——
  用户滚到画布另一头也想能直接搭句话。

  位置避开了两样：右下角状态栏（#statusbar，高约 30px，所以 bottom 给 46）
  和右侧抽屉（drawer 宽 420px + right 16px ≈ 436px，所以球固定在 24px 不会进抽屉区）。
  抽屉打开时整体藏起来，规则在下面。
*/
.agent-fab {
  position: fixed; right: 24px; bottom: 46px; z-index: 27;
  width: 46px; height: 46px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--card-solid); border: 1px solid var(--line); color: var(--muted);
  box-shadow: 0 8px 24px var(--shadow);
  cursor: pointer; transition: color .18s var(--ease), background .18s var(--ease), transform .18s var(--ease);
}
.agent-fab:hover { color: var(--text); background: var(--soft-2); transform: translateY(-1px); }
.agent-fab svg { width: 20px; height: 20px; }
/* 键盘可达：Tab 过来时要有可见焦点圈，否则键盘用户不知道停在哪 */
.agent-fab:focus-visible { outline: 2px solid var(--strong); outline-offset: 2px; }

/*
  抽屉开着就藏球。
  用 :has() 盯着抽屉自己的 class，而不是靠 JS 事件同步 ——
  抽屉的关闭入口有好几处，都是直接 classList.remove('open')，
  没有一个统一的 close 事件，事件同步必然漏一处，
  漏了球就压在抽屉边上：挡内容，而且点它等于「关掉刚打开的抽屉」。
*/
body:has(#agentDrawer.open) .agent-fab { display: none; }

#agentDrawer {
  top: 90px; right: 20px; bottom: 16px;
  width: min(520px, calc(100vw - 40px));
  height: auto; max-height: none;
  display: flex;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateX(22px) scale(.992);
  transform-origin: 100% 18%;
  box-shadow: 0 28px 90px rgba(0,0,0,.46);
  transition:
    opacity .26s cubic-bezier(.22,.61,.36,1),
    transform .38s cubic-bezier(.2,.86,.28,1),
    visibility 0s linear .38s;
  will-change: opacity, transform;
}
#agentDrawer.open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: none;
  transition:
    opacity .26s cubic-bezier(.22,.61,.36,1),
    transform .38s cubic-bezier(.2,.86,.28,1),
    visibility 0s;
}
@media (prefers-reduced-motion: reduce) {
  #agentDrawer, #agentDrawer.open { transition-duration: .01ms; transform: none; }
}

.agent-drawer-head { padding: 15px 18px 12px; align-items: flex-start; }
.agent-head-main { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.agent-head-icon {
  width: 34px; height: 34px; min-width: 34px; border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  color: var(--strong); background: var(--soft-2); border: 1px solid var(--line);
}
.agent-head-icon svg { width: 17px; height: 17px; }
.agent-head-copy { min-width: 0; }
.agent-head-title-row { display: flex; align-items: center; gap: 7px; }
.agent-head-sub { margin-top: 3px; padding: 0; font-size: 10.5px; line-height: 1.5; }
.agent-head-actions { display: flex; gap: 4px; flex: none; }
.agent-pill {
  max-width: 180px; padding: 2px 7px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--soft);
  color: var(--muted); font-size: 9.5px; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#agentDrawer .agent-chat {
  flex: 1; min-height: 120px; max-height: none;
  margin: 0; padding: 6px 18px 10px;
  border: 0; border-radius: 0; background: transparent;
}
#agentDrawer .agent-note-box { padding: 0 18px 6px; }
#agentDrawer .agent-note-box:empty { display: none; }
#agentDrawer .agent-drawer-body {
  flex: none; max-height: 62%; overflow-y: auto;
  padding: 10px 18px 16px; gap: 7px;
  border-top: 1px solid var(--line);
  background: linear-gradient(180deg, transparent, var(--soft) 120%);
}

.agent-capabilities {
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--soft); overflow: hidden;
}
.agent-cap-summary {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 11px; cursor: pointer; list-style: none;
  font-size: 11px; font-weight: 800; color: var(--text); user-select: none;
}
.agent-cap-summary::-webkit-details-marker { display: none; }
.agent-cap-summary > span:first-child { display: flex; align-items: center; gap: 6px; }
.agent-cap-summary svg { width: 13px; height: 13px; color: var(--strong); }
.agent-cap-summary-hint { color: var(--faint); font-size: 9.5px; font-weight: 600; }
.agent-cap-body {
  display: flex; flex-direction: column; gap: 9px;
  max-height: 220px; overflow-y: auto;
  padding: 0 10px 10px;
}
.agent-cap-block { display: flex; flex-direction: column; gap: 5px; }
.agent-cap-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 9.5px; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: var(--faint);
}
.agent-cap-head > span:last-child { font-weight: 600; letter-spacing: 0; text-transform: none; }
.agent-skill-grid, .agent-app-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px;
}
.agent-skill, .agent-app {
  min-width: 0; text-align: left; cursor: pointer;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card-solid); color: var(--muted);
  transition: border-color .15s var(--ease), background .15s var(--ease), color .15s var(--ease);
}
.agent-skill { padding: 7px 8px; display: flex; flex-direction: column; gap: 2px; }
.agent-skill-top { display: flex; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 800; color: var(--text); }
.agent-skill-top svg { width: 12px; height: 12px; }
.agent-skill-state {
  margin-left: auto; font-size: 8.5px; font-weight: 800;
  color: var(--faint); text-transform: uppercase;
}
.agent-skill-hint { font-size: 9px; line-height: 1.4; color: var(--muted); }
.agent-skill:hover, .agent-app:hover { border-color: var(--line-2); background: var(--soft-2); }
.agent-skill.active { border-color: var(--strong); background: var(--soft-2); }
.agent-skill.active .agent-skill-state { color: var(--strong); }
.agent-app { padding: 7px 8px; display: flex; align-items: flex-start; gap: 7px; }
.agent-app-ic {
  width: 22px; height: 22px; min-width: 22px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  color: var(--strong); background: var(--soft-2);
}
.agent-app-ic svg { width: 12px; height: 12px; }
.agent-app-copy { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.agent-app-copy b { font-size: 10.5px; color: var(--text); }
.agent-app-copy small { font-size: 9px; line-height: 1.4; color: var(--muted); }

.agent-runbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.agent-mode-switch {
  display: inline-flex; gap: 2px; padding: 2px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--soft);
}
.agent-mode-btn {
  height: 24px; padding: 0 10px; border-radius: 999px;
  color: var(--muted); background: transparent; font-size: 10px; font-weight: 700;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.agent-mode-btn:hover { color: var(--text); }
.agent-mode-btn.active { background: var(--strong); color: var(--strong-text); }
.agent-usage { font-size: 9.5px; color: var(--faint); text-align: right; }
.agent-permission-hint { margin-top: -3px; font-size: 9.5px; line-height: 1.45; color: var(--muted); }

.agent-compose-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.agent-tool-btn {
  height: 27px; padding: 0 9px; border-radius: 9px;
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--line); background: var(--card-solid);
  color: var(--muted); font-size: 10px; font-weight: 700;
  transition: background .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease);
}
.agent-tool-btn:hover { color: var(--text); background: var(--soft-2); }
.agent-tool-btn svg { width: 12px; height: 12px; }
.agent-tool-btn[aria-pressed="true"] {
  color: var(--strong); border-color: var(--strong); background: var(--soft-2);
}
.agent-mode-hint { flex: 1; min-width: 130px; font-size: 9.5px; line-height: 1.4; }
.agent-img-params { margin: 0; }
.agent-img-params > summary { cursor: pointer; user-select: none; font-size: 10px; }

.agent-compose {
  display: flex; align-items: flex-end; gap: 8px;
  padding: 7px 8px 7px 11px;
  border: 1px solid var(--line-2); border-radius: 15px;
  background: var(--card-solid);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.03);
}
.agent-compose:focus-within { border-color: var(--strong); }
.agent-input {
  flex: 1; width: 100%; min-height: 44px; max-height: 150px;
  padding: 3px 0; border: 0; background: transparent;
  font-size: 12.5px; line-height: 1.55; resize: none;
}
.agent-input:focus { outline: none; }
.agent-generate-btn {
  height: 38px; min-width: 88px; padding: 0 13px;
  border-radius: 11px; display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  font-size: 11px; font-weight: 800;
}
.agent-generate-btn svg { width: 14px; height: 14px; }
.agent-footnote { font-size: 9.5px; line-height: 1.5; }

/* ---------- 画布助手：轻量输入区 ---------- */
.agent-drawer-head { padding: 14px 16px 12px; align-items: center; }
.agent-head-icon {
  width: 30px; height: 30px; min-width: 30px; border-radius: 9px;
}
.agent-head-icon svg { width: 15px; height: 15px; }
.agent-head-title-row { gap: 6px; }
.agent-head-sub {
  max-width: 400px; margin-top: 2px; padding: 0;
  font-size: 9.5px; line-height: 1.45;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#agentDrawer .agent-chat { padding: 8px 16px 10px; }
#agentDrawer .agent-note-box { padding: 0 16px 6px; }
#agentDrawer .agent-drawer-body {
  flex: none; max-height: none; overflow: visible;
  padding: 0 16px 14px; gap: 8px;
  border-top: 0; background: transparent;
}

.agent-composer {
  position: relative;
  padding: 10px 10px 9px;
  border: 1px solid var(--line-2); border-radius: 18px;
  background: var(--card-solid);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.03), 0 10px 28px rgba(0,0,0,.16);
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.agent-composer:focus-within {
  border-color: color-mix(in srgb, var(--strong) 55%, var(--line-2));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04), 0 0 0 3px color-mix(in srgb, var(--strong) 9%, transparent);
}
.agent-compose-mini { display: flex; align-items: center; gap: 4px; margin-bottom: 2px; min-width: 0; }
.agent-mini-spacer { flex: 1; }
.agent-mini-btn {
  width: 26px; height: 26px; flex: none; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted); background: transparent;
  transition: color .15s var(--ease), background .15s var(--ease);
}
.agent-mini-btn:hover { color: var(--text); background: var(--soft-2); }
.agent-mini-btn svg { width: 14px; height: 14px; }
.agent-add-wrap { position: relative; flex: none; }
.agent-add-menu {
  position: absolute; left: 0; bottom: calc(100% + 7px); z-index: 70;
  display: none; flex-direction: column; min-width: 166px;
  padding: 5px; border-radius: 11px;
  background: var(--card-solid); border: 1px solid var(--line-2);
  box-shadow: 0 16px 42px rgba(0,0,0,.48);
}
.agent-add-menu.open { display: flex; }
.agent-add-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px; border-radius: 8px;
  color: var(--text); font-size: 11px; font-weight: 650; text-align: left;
}
.agent-add-item:hover { background: var(--soft-2); }
.agent-add-ic {
  width: 22px; height: 22px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  color: var(--strong); background: var(--soft);
}
.agent-add-ic svg { width: 13px; height: 13px; }

.agent-pick-bar {
  display: none; align-items: center; gap: 8px;
  padding: 12px 13px; border-radius: 12px;
  border: 1px dashed var(--line-2); background: var(--soft);
  color: var(--text); font-size: 12.5px; font-weight: 650;
  animation: agentPopIn .18s var(--ease);
}
.agent-drawer.is-quoting .agent-pick-bar { display: flex; }
.agent-drawer.is-quoting .agent-composer { display: none; }
.agent-pick-bar > svg { width: 15px; height: 15px; color: var(--strong); flex: none; }
.agent-pick-x {
  width: 24px; height: 24px; margin-left: auto; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted);
}
.agent-pick-x:hover { color: var(--text); background: var(--soft-2); }
.agent-pick-x svg { width: 13px; height: 13px; }

.agent-ref-ac {
  position: absolute; left: 50%; bottom: calc(100% + 8px); z-index: 80;
  width: min(400px, calc(100% - 20px)); max-height: 300px; overflow-y: auto;
  display: none; flex-direction: column; gap: 2px;
  padding: 5px; border-radius: 13px;
  background: var(--card-solid); border: 1px solid var(--line-2);
  box-shadow: 0 20px 56px rgba(0,0,0,.52);
  transform: translateX(-50%);
}
.agent-ref-ac.open { display: flex; }
.agent-ref-ac-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px; border-radius: 8px; cursor: pointer;
  color: var(--text); font-size: 11px;
}
.agent-ref-ac-row:hover, .agent-ref-ac-row.on { background: var(--soft-2); }
.agent-ref-ac-img {
  width: 30px; height: 30px; min-width: 30px; object-fit: cover; border-radius: 6px;
  border: 1px solid var(--line); background: var(--soft);
}
.agent-ref-ac-ic {
  width: 30px; height: 30px; min-width: 30px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  background: var(--soft); color: var(--strong);
}
.agent-ref-ac-ic svg { width: 14px; height: 14px; }
.agent-ref-ac-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 650;
}
.agent-ref-ac-tag {
  flex: none; padding: 1px 5px; border-radius: 5px;
  background: var(--soft); color: var(--faint);
  font-size: 8.5px; font-weight: 700;
}

.agent-asset-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px; max-height: min(56vh, 460px); overflow-y: auto; padding: 2px;
}
.agent-asset-cell {
  min-width: 0; padding: 5px; border: 1px solid var(--line); border-radius: 11px;
  background: var(--card-solid); color: var(--muted); text-align: left;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.agent-asset-cell:hover { border-color: var(--line-2); background: var(--soft-2); }
.agent-asset-cell.on { border-color: var(--strong); background: var(--soft-2); }
.agent-asset-cell img {
  width: 100%; height: 104px; object-fit: cover; display: block;
  border-radius: 7px; background: var(--soft);
}
.agent-asset-cell span {
  display: block; margin-top: 5px; padding: 0 2px 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 9.5px; font-weight: 600;
}
.agent-asset-empty {
  grid-column: 1 / -1; padding: 34px 12px; text-align: center;
  color: var(--muted); font-size: 11px;
}
.agent-input {
  min-height: 54px; max-height: 180px;
  padding: 2px 2px 4px; border: 0; border-radius: 0;
  font-size: 13px; line-height: 1.6; background: transparent;
}
.agent-composer.is-expanded .agent-input { min-height: 150px; }
.agent-mini-btn[aria-pressed="true"] { color: var(--strong); background: var(--soft-2); }
.agent-prompt { position: relative; min-width: 0; }
.agent-input { position: relative; z-index: 1; }
.agent-input-hl {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none; overflow: hidden;
  padding: 2px 2px 4px;
  font-family: inherit; font-size: 13px; line-height: 1.6; letter-spacing: 0;
  white-space: pre-wrap; overflow-wrap: break-word; word-break: break-word;
  color: transparent;
}
.agent-input-chip {
  pointer-events: auto; cursor: pointer;
  border-radius: 5px;
  background: color-mix(in srgb, var(--success) 22%, transparent);
  color: transparent;
  box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.agent-input-chip:hover { background: color-mix(in srgb, var(--success) 34%, transparent); }
.agent-compose-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding-top: 7px;
}
.agent-compose-tools { display: flex; align-items: center; gap: 2px; min-width: 0; flex-wrap: nowrap; }
.agent-menu { position: static; flex: none; }
.agent-menu > summary {
  list-style: none; cursor: pointer; user-select: none;
  height: 28px; padding: 0 8px; border-radius: 8px;
  display: inline-flex; align-items: center; gap: 5px;
  color: var(--muted); font-size: 10.5px; font-weight: 700; white-space: nowrap;
  transition: color .15s var(--ease), background .15s var(--ease);
}
.agent-menu > summary::-webkit-details-marker { display: none; }
.agent-menu > summary:hover { color: var(--text); background: var(--soft-2); }
.agent-menu[open] > summary { color: var(--text); background: var(--soft-2); }
.agent-menu > summary svg { width: 13px; height: 13px; flex: none; }
.agent-menu-badge { font-size: 9px; font-weight: 700; color: var(--faint); }
.agent-pop {
  position: absolute; left: 50%; bottom: calc(100% + 9px); z-index: 60;
  width: min(420px, calc(100% - 20px));
  max-width: calc(100vw - 64px);
  max-height: min(460px, calc(100vh - 300px)); overflow-y: auto;
  transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px;
  border: 1px solid var(--line-2); border-radius: 16px;
  background: color-mix(in srgb, var(--card-solid) 94%, var(--strong) 6%);
  box-shadow: 0 24px 64px rgba(0,0,0,.56), inset 0 1px 0 rgba(255,255,255,.04);
  animation: agentPopCenter .18s var(--ease);
}
@keyframes agentPopCenter {
  from { opacity: 0; transform: translate(-50%, 6px) scale(.985); }
  to { opacity: 1; transform: translateX(-50%); }
}
.agent-pop-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 1px 1px 2px;
}
.agent-pop-head b { color: var(--text); font-size: 11px; }
.agent-pop-head span { color: var(--faint); font-size: 9px; font-weight: 600; }
.agent-pop-skills, .agent-pop-params { width: min(420px, calc(100% - 20px)); }
.agent-pop-perm { width: min(340px, calc(100% - 20px)); }
.agent-skill-grid, .agent-app-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.agent-skill { padding: 8px; }
.agent-app { padding: 8px; }
.agent-app-copy small {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.agent-mode-switch { display: flex; width: 100%; border-radius: 10px; }
.agent-mode-btn { flex: 1; height: 28px; font-size: 10.5px; }
.agent-permission-hint { margin: 0; font-size: 10px; line-height: 1.5; }
.agent-usage { font-size: 9.5px; color: var(--faint); text-align: left; }

.agent-img-mode {
  width: 100%; height: 32px; padding: 0 10px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--soft);
  color: var(--text); font-size: 10.5px; font-weight: 700; text-align: left;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.agent-img-mode:hover { border-color: var(--line-2); background: var(--soft-2); }
.agent-img-mode[aria-pressed="true"] { color: var(--strong); border-color: var(--strong); }
.agent-img-params .agent-params-grid { padding: 0; gap: 7px; }
.agent-mode-hint { font-size: 9.5px; line-height: 1.45; }
.agent-generate-btn {
  width: 36px; height: 36px; min-width: 36px; padding: 0;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--strong); color: var(--strong-text);
  box-shadow: 0 4px 14px rgba(0,0,0,.24);
  transition: transform .15s var(--ease), opacity .15s var(--ease);
}
.agent-generate-btn:hover { transform: translateY(-1px); opacity: .92; }
.agent-generate-btn:active { transform: none; }
.agent-generate-btn svg { width: 15px; height: 15px; }
.agent-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- 对话线程 ---------- */
.agent-drawer-head { position: relative; }
.agent-thread-btn {
  max-width: 340px; min-width: 0; height: 24px; padding: 0 5px 0 0;
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--text); font-size: 12px; font-weight: 800; text-align: left;
  background: transparent; border-radius: 7px;
}
.agent-thread-btn:hover { background: var(--soft-2); }
.agent-thread-btn span {
  max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.agent-thread-btn svg { width: 12px; height: 12px; color: var(--faint); flex: none; }
.agent-thread-menu {
  position: absolute; top: calc(100% - 5px); left: 14px; z-index: 90;
  display: none; width: min(340px, calc(100% - 28px));
  max-height: min(52vh, 420px); overflow-y: auto;
  padding: 5px; border-radius: 13px;
  border: 1px solid var(--line-2); background: var(--card-solid);
  box-shadow: 0 22px 60px rgba(0,0,0,.55);
}
.agent-thread-menu.open { display: flex; flex-direction: column; gap: 2px; }
.agent-thread-row { display: flex; align-items: center; gap: 2px; border-radius: 8px; }
.agent-thread-row:hover { background: var(--soft); }
.agent-thread-main {
  flex: 1; min-width: 0; padding: 8px; border-radius: 8px;
  display: flex; flex-direction: column; gap: 2px; text-align: left;
}
.agent-thread-main.active { background: var(--soft-2); }
.agent-thread-name {
  color: var(--text); font-size: 11px; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.agent-thread-state { color: var(--faint); font-size: 9px; font-weight: 650; }
.agent-thread-act {
  width: 25px; height: 25px; flex: none; border-radius: 7px;
  display: flex; align-items: center; justify-content: center; color: var(--faint);
}
.agent-thread-act:hover { color: var(--text); background: var(--soft-2); }
.agent-thread-act svg { width: 12px; height: 12px; }
.agent-thread-empty { padding: 14px; text-align: center; color: var(--faint); font-size: 10px; }

/* ---------- 思考 / 工具步骤 ---------- */
.agent-steps { display: flex; flex-direction: column; gap: 4px; width: 100%; margin-bottom: 5px; }
.agent-thought {
  border: 1px solid var(--line); border-radius: 9px;
  background: color-mix(in srgb, var(--soft) 70%, transparent);
}
.agent-thought > summary {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 8px; cursor: pointer; list-style: none;
  color: var(--muted); font-size: 10px; font-weight: 700;
}
.agent-thought > summary::-webkit-details-marker { display: none; }
.agent-thought[open] > summary .agent-step-caret { transform: rotate(90deg); }
.agent-thought-text {
  padding: 0 9px 8px; color: var(--muted);
  font-size: 10px; line-height: 1.65; white-space: pre-wrap;
}
.agent-step {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  padding: 5px 7px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--soft);
  color: var(--muted); font-size: 10px;
}
.agent-step-ic {
  width: 14px; height: 14px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--strong);
}
.agent-step-ic svg { width: 13px; height: 13px; }
.agent-step-label { color: var(--text); font-weight: 700; flex: none; }
.agent-step-summary { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agent-step-ms { margin-left: auto; color: var(--faint); font-size: 9px; flex: none; }
.agent-step-caret { width: 11px; height: 11px !important; transition: transform .15s var(--ease); }
.agent-turn-meta { margin-top: 4px; color: var(--faint); font-size: 9px; }
.agent-legacy-tools { width: 100%; margin-bottom: 4px; }
.agent-legacy-tools > summary { cursor: pointer; color: var(--faint); font-size: 9.5px; }
.agent-streaming { border-style: dashed; }

/* ---------- 确认 / 澄清 / 恢复卡片 ---------- */
.agent-confirm-card, .agent-question-card, .agent-recovery-card {
  padding: 10px; border-radius: 13px;
  border: 1px solid var(--line-2); background: var(--card-solid);
  box-shadow: 0 12px 36px rgba(0,0,0,.25);
  display: flex; flex-direction: column; gap: 8px;
}
.agent-card-kicker {
  display: flex; align-items: center; gap: 5px;
  color: var(--faint); font-size: 9px; font-weight: 800;
  letter-spacing: .04em; text-transform: uppercase;
}
.agent-confirm-title { color: var(--text); font-size: 11.5px; font-weight: 750; line-height: 1.5; }
.agent-confirm-preview { display: flex; flex-direction: column; gap: 5px; }
.agent-confirm-node {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) auto;
  gap: 5px 7px; align-items: center;
  padding: 7px; border-radius: 9px; background: var(--soft);
}
.agent-confirm-node-ic {
  width: 22px; height: 22px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  color: var(--strong); background: var(--soft-2);
}
.agent-confirm-node-ic svg { width: 12px; height: 12px; }
.agent-confirm-node-name { color: var(--text); font-size: 10.5px; font-weight: 750; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agent-confirm-node-meta { color: var(--faint); font-size: 9px; }
.agent-confirm-prompt {
  grid-column: 2 / -1; color: var(--muted); font-size: 9.5px; line-height: 1.55;
  white-space: pre-wrap; max-height: 90px; overflow-y: auto;
}
.agent-confirm-meta { color: var(--muted); font-size: 9.5px; line-height: 1.5; }
.agent-confirm-actions, .agent-question-actions, .agent-recovery-actions {
  display: flex; justify-content: flex-end; gap: 6px;
}
.agent-question-intro { color: var(--muted); font-size: 10px; line-height: 1.6; }
.agent-question-counter { color: var(--faint); font-size: 9px; font-weight: 700; }
.agent-question-title { color: var(--text); font-size: 11.5px; font-weight: 750; line-height: 1.5; }
.agent-question-desc { color: var(--muted); font-size: 10px; line-height: 1.55; }
.agent-question-options { display: flex; flex-direction: column; gap: 5px; }
.agent-question-option {
  display: flex; align-items: flex-start; gap: 7px;
  padding: 8px; border-radius: 9px; cursor: pointer;
  border: 1px solid var(--line); background: var(--soft);
}
.agent-question-option:hover { border-color: var(--line-2); background: var(--soft-2); }
.agent-question-option input { margin-top: 2px; flex: none; }
.agent-question-option-copy { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.agent-question-option-copy b { color: var(--text); font-size: 10.5px; }
.agent-question-option-copy small { color: var(--muted); font-size: 9.5px; line-height: 1.45; }
.agent-question-custom {
  width: 100%; min-height: 48px; resize: vertical;
  padding: 8px; border-radius: 9px;
  border: 1px solid var(--line-2); background: var(--card-solid);
  color: var(--text); font-size: 10.5px; line-height: 1.5; outline: none;
}
.agent-question-custom:focus { border-color: var(--strong); }
.agent-recovery-text { color: var(--muted); font-size: 10px; line-height: 1.55; }
.agent-recovery-actions { justify-content: flex-start; }

.agent-empty {
  min-height: 220px; padding: 34px 8px 18px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; text-align: center;
}
.agent-empty-ic {
  width: 38px; height: 38px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  color: var(--strong); background: var(--soft-2); border: 1px solid var(--line);
}
.agent-empty-ic svg { width: 18px; height: 18px; }
.agent-empty > small { color: var(--faint); font-size: 10px; font-weight: 700; }
.agent-empty > b { color: var(--text); font-size: 18px; letter-spacing: -.01em; }
.agent-empty > p {
  max-width: 330px; color: var(--muted); font-size: 11px; line-height: 1.65;
}
.agent-empty-apps {
  width: min(100%, 400px); margin-top: 12px;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px;
}
.agent-empty-app {
  min-width: 0; padding: 10px; text-align: left; cursor: pointer;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card-solid); color: var(--muted);
  transition: border-color .15s var(--ease), background .15s var(--ease), transform .15s var(--ease);
}
.agent-empty-app:hover { border-color: var(--line-2); background: var(--soft-2); transform: translateY(-1px); }
.agent-empty-app-top { display: flex; align-items: center; gap: 6px; color: var(--text); font-size: 10.5px; font-weight: 800; }
.agent-empty-app-top svg { width: 12px; height: 12px; color: var(--strong); }
.agent-empty-app small { display: block; margin-top: 5px; color: var(--muted); font-size: 9.5px; line-height: 1.45; }

.agent-quote-list {
  display: flex; flex-direction: column; gap: 6px;
  max-height: min(52vh, 420px); overflow-y: auto; padding-right: 2px;
}
.agent-quote-row {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 8px 9px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card-solid); cursor: pointer;
}
.agent-quote-row:hover { border-color: var(--line-2); background: var(--soft); }
.agent-quote-row input { margin-top: 2px; flex: none; }
.agent-quote-ic {
  width: 24px; height: 24px; min-width: 24px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  color: var(--strong); background: var(--soft-2);
}
.agent-quote-ic svg { width: 13px; height: 13px; }
.agent-quote-copy { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.agent-quote-copy b { font-size: 11px; color: var(--text); }
.agent-quote-copy small {
  font-size: 10px; line-height: 1.45; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.jobs-list { padding: 8px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.job-row {
  padding: 9px 11px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--card-solid); display: flex; flex-direction: column; gap: 5px;
}
.job-row .jt { font-size: 11px; font-weight: 800; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.job-row .jm { font-size: 10px; color: var(--muted); }
.job-row .badge { padding: 1px 6px; border-radius: 4px; font-size: 9px; font-weight: 800; }
.job-row .badge.done { background: rgba(5,150,105,.14); color: var(--success); }
.job-row .badge.error { background: var(--danger-bg); color: var(--danger); }
.job-row .badge.running { background: rgba(37,99,235,.14); color: #2563eb; }
.job-row .badge.canceled { background: var(--soft); color: var(--muted); }
.job-empty { padding: 30px; text-align: center; color: var(--muted); font-size: 12px; }

/* ---------- 节点版本历史（5-6） ---------- */
.ver-section { margin-top: 6px; }
.ver-head { display: flex; align-items: center; font-size: 11px; font-weight: 800; color: var(--muted); margin-bottom: 6px; }
.ver-head > span:first-child { flex: 1; }
.ver-list { display: flex; flex-direction: column; gap: 5px; }
.ver-empty { font-size: 10px; color: var(--faint); line-height: 1.6; font-weight: 600; padding: 4px 0; }
.ver-item {
  display: flex; align-items: center; gap: 7px; padding: 6px;
  background: var(--soft); border: 1px solid var(--line-2); border-radius: 9px;
}
.ver-thumbs { display: flex; gap: 3px; flex-shrink: 0; }
.ver-thumb { width: 34px; height: 34px; border-radius: 6px; overflow: hidden; background: var(--card-solid); }
.ver-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: zoom-in; }
.ver-when { flex: 1; font-size: 10px; color: var(--muted); font-weight: 600; }
.ver-sig {
  font-size: 9px; color: var(--faint); font-weight: 700; font-family: ui-monospace, monospace;
  max-width: 84px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 0;
}
.ver-cur { font-size: 9px; font-weight: 800; color: var(--success); background: color-mix(in srgb, var(--success) 12%, transparent); padding: 2px 6px; border-radius: 5px; }

/* ---------- 待取回（5-5 任务取回中心） ---------- */
.job-recover {
  border: 1px solid color-mix(in srgb, var(--warning) 42%, var(--line));
  background: color-mix(in srgb, var(--warning) 8%, transparent);
  border-radius: var(--radius-card); padding: 9px 10px; margin-bottom: 6px;
  display: flex; flex-direction: column; gap: 7px;
}
.job-recover-head {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 800; color: var(--warning);
}
.job-recover-head svg { width: 13px; height: 13px; flex-shrink: 0; }
.job-recover-note { font-size: 10px; line-height: 1.6; color: var(--muted); font-weight: 600; }
.job-recover-row { background: var(--card-solid); margin: 0; }
.job-recover-acts { display: flex; gap: 5px; margin-top: 6px; flex-wrap: wrap; }
.job-orphan-note {
  font-size: 10px; color: var(--faint); font-weight: 600;
  padding: 5px 8px; margin-bottom: 5px; line-height: 1.6;
}

/* ================================================================ 任务历史 */
.jobs-backdrop {
  position: fixed; inset: 0; z-index: 759;
  background: rgba(0, 0, 0, .42); backdrop-filter: blur(2px);
  opacity: 0; visibility: hidden; transition: opacity .18s var(--ease), visibility .18s;
}
.jobs-backdrop.open { opacity: 1; visibility: visible; }

.jobs-drawer.jobs-hub {
  left: 80px; right: auto; top: 50%; transform: translateY(-50%);
  width: 300px; max-height: calc(100vh - 180px); border-radius: 18px; z-index: 760;
  background: color-mix(in srgb, var(--panel) 97%, transparent);
}
.jobs-drawer.jobs-hub.is-expanded {
  left: 50%; right: auto; top: 50%; transform: translate(-50%, -50%);
  width: min(1080px, calc(100vw - 80px));
  height: min(720px, calc(100vh - 80px));
  max-height: none; border-radius: 16px;
}
.jobs-head {
  height: 52px; flex-shrink: 0; display: flex; align-items: center; gap: 6px;
  padding: 0 8px 0 14px; border-bottom: 1px solid var(--line);
}
.jobs-panel-title {
  flex: 1; min-width: 0; font-size: 13px; font-weight: 800; letter-spacing: .01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.jobs-back-btn { display: none; }
.jobs-hub.is-detail:not(.is-expanded) .jobs-back-btn { display: flex; margin-right: -2px; }
.jobs-hub.is-detail:not(.is-expanded) #jobsMultiBtn,
.jobs-hub.is-detail:not(.is-expanded) .jobs-view-mode { display: none; }
.jobs-hub.is-detail:not(.is-expanded) #jobsSelectTools { display: none !important; }
.jobs-head-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.jobs-icon-btn {
  width: 28px; height: 28px; border: 0; border-radius: 8px; background: transparent;
  color: var(--muted); display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0; transition: color .15s, background .15s;
}
.jobs-icon-btn:hover { color: var(--text); background: var(--soft); }
.jobs-icon-btn.is-active { color: var(--text); background: var(--soft); }
.jobs-icon-btn:disabled { opacity: .35; cursor: default; background: transparent; }
.jobs-icon-btn.jobs-danger:hover { color: var(--danger); background: var(--danger-bg); }
.jobs-icon-btn svg { width: 15px; height: 15px; }
.jobs-icon-btn[hidden] { display: none !important; }
.jobs-select-tools { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.jobs-select-tools[hidden] { display: none !important; }
.jobs-text-btn {
  height: 26px; padding: 0 7px; border: 0; background: transparent; border-radius: 7px;
  color: var(--muted); font: inherit; font-size: 11px; font-weight: 700; cursor: pointer;
}
.jobs-text-btn:hover { color: var(--text); background: var(--soft); }
.jobs-body { display: flex; flex-direction: column; min-height: 0; flex: 1; overflow: hidden; }
.jobs-list-pane { display: flex; flex-direction: column; min-height: 0; flex: 1; }
.jobs-detail-pane { display: none; flex-direction: column; min-height: 0; flex: 1; overflow-y: auto; }
.jobs-hub.is-detail:not(.is-expanded) .jobs-list-pane { display: none; }
.jobs-hub.is-detail:not(.is-expanded) .jobs-detail-pane { display: flex; max-height: calc(100vh - 232px); }
.jobs-hub.is-expanded .jobs-body {
  display: grid; grid-template-columns: minmax(0, 1.16fr) minmax(320px, .84fr);
  flex: 1; min-height: 0;
}
.jobs-hub.is-expanded .jobs-list-pane { display: flex; min-width: 0; min-height: 0; }
.jobs-hub.is-expanded .jobs-list { max-height: none; }
.jobs-hub.is-expanded .jobs-detail-pane {
  display: flex; max-height: none; border-left: 1px solid var(--line);
  min-width: 0; overflow-y: auto;
}

.jobs-search { position: relative; margin: 10px 12px 8px; flex-shrink: 0; }
.jobs-search > svg {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  width: 13px; height: 13px; color: var(--faint); pointer-events: none;
}
.jobs-search input {
  width: 100%; height: 35px; padding: 0 10px 0 30px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--soft); color: var(--text);
  font: inherit; font-size: 11px; outline: none;
}
.jobs-search input:focus { border-color: var(--line-2); background: var(--card-solid); }
.jobs-search input::placeholder { color: var(--faint); }
.jobs-search input::-webkit-search-cancel-button { display: none; }

.jobs-filters {
  display: flex; align-items: center; gap: 16px; flex-shrink: 0;
  padding: 0 14px 9px; border-bottom: 1px solid var(--line);
}
.jobs-filter {
  border: 0; background: transparent; padding: 0; color: var(--muted);
  font: inherit; font-size: 11px; font-weight: 700; cursor: pointer; position: relative;
}
.jobs-filter:hover { color: var(--text); }
.jobs-filter.is-active { color: var(--text); }
.jobs-filter.is-active::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -10px; height: 2px;
  background: var(--text); border-radius: 2px;
}

.jobs-hub .jobs-list {
  flex: 1; min-height: 0; max-height: calc(100vh - 340px);
  padding: 6px 8px 10px; gap: 3px; scrollbar-width: thin;
}
.jobs-item {
  width: 100%; min-width: 0; display: flex; align-items: center; gap: 9px;
  padding: 8px; border: 1px solid transparent; border-radius: 11px;
  background: transparent; color: var(--text); text-align: left; font: inherit;
  cursor: pointer; transition: background .14s, border-color .14s;
}
.jobs-item:hover { background: var(--soft); border-color: var(--line); }
.jobs-item.is-active { background: color-mix(in srgb, var(--text) 7%, transparent); border-color: var(--line-2); }
.jobs-check {
  width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0;
  border: 1.5px solid var(--line-2); color: var(--strong-text);
  display: none; align-items: center; justify-content: center; background: transparent;
}
.jobs-hub.is-selecting .jobs-check { display: flex; }
.jobs-check.is-checked { border-color: var(--strong); background: var(--strong); }
.jobs-check svg { width: 11px; height: 11px; }
.jobs-thumb {
  width: 40px; height: 40px; border-radius: 9px; overflow: hidden; flex-shrink: 0;
  background: var(--soft); color: var(--faint); position: relative;
  display: flex; align-items: center; justify-content: center;
}
.jobs-thumb img, .jobs-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.jobs-thumb > svg { width: 16px; height: 16px; }
.jobs-thumb-count {
  position: absolute; right: 2px; bottom: 2px; min-width: 14px; height: 14px; padding: 0 3px;
  border-radius: 7px; background: rgba(0, 0, 0, .65); color: #fff;
  font-size: 8px; font-weight: 800; display: flex; align-items: center; justify-content: center;
}
.jobs-item-main { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
.jobs-item-title {
  font-size: 12px; font-weight: 700; line-height: 1.35;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.jobs-item-sub {
  font-size: 10px; color: var(--muted); line-height: 1.35;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.jobs-item-side { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }
.jobs-item-time { font-size: 9.5px; color: var(--faint); font-variant-numeric: tabular-nums; }
.jobs-status { display: inline-flex; align-items: center; gap: 4px; font-size: 9.5px; font-weight: 700; white-space: nowrap; }
.jobs-status-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.jobs-status.is-done { color: var(--success); }
.jobs-status.is-error { color: var(--danger); }
.jobs-status.is-running { color: #3b82f6; }
.jobs-status.is-queued { color: var(--warning); }
.jobs-status.is-canceled { color: var(--faint); }

.jobs-hub .jobs-list.is-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 8px; padding: 10px 12px; align-content: start;
}
.jobs-list.is-grid .jobs-item {
  position: relative; flex-direction: column; align-items: stretch; gap: 7px;
  padding: 7px; background: var(--card-solid); border-color: var(--line);
}
.jobs-list.is-grid .jobs-thumb { width: 100%; height: auto; aspect-ratio: 1.45; }
.jobs-list.is-grid .jobs-check {
  position: absolute; left: 12px; top: 12px; z-index: 2; background: var(--panel);
}
.jobs-list.is-grid .jobs-item-side {
  flex-direction: row; align-items: center; justify-content: space-between; width: 100%;
}

.jobs-detail-empty {
  flex: 1; min-height: 180px; display: flex; align-items: center; justify-content: center;
  padding: 30px; color: var(--faint); font-size: 12px; text-align: center;
}
.jobs-detail { position: relative; display: flex; flex-direction: column; gap: 12px; padding: 12px; min-height: 100%; }
.jobs-detail-close {
  position: absolute; right: 20px; top: 20px; z-index: 3;
  width: 26px; height: 26px; border: 0; border-radius: 50%;
  background: rgba(0, 0, 0, .5); color: #fff; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.jobs-detail-close:hover { background: rgba(0, 0, 0, .72); }
.jobs-detail-close svg { width: 13px; height: 13px; }
.jobs-detail-media {
  min-height: 170px; height: clamp(170px, 25vh, 250px); border-radius: 12px;
  background: #080808; overflow: hidden; position: relative;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.jobs-detail-media img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; cursor: zoom-in; }
.jobs-detail-media video { max-width: 100%; max-height: 100%; display: block; }
.jobs-detail-media audio { width: calc(100% - 24px); }
.jobs-detail-noimg {
  color: #666; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; font-size: 10px;
}
.jobs-detail-noimg svg { width: 20px; height: 20px; }
.jobs-detail-count {
  position: absolute; right: 8px; bottom: 8px; padding: 2px 6px; border-radius: 6px;
  background: rgba(0, 0, 0, .55); color: #fff; font-size: 9px; font-weight: 700;
}
.jobs-meta { display: flex; flex-direction: column; }
.jobs-meta-row { display: flex; align-items: flex-start; gap: 8px; min-width: 0; padding: 5px 0; font-size: 11px; }
.jobs-meta-label {
  width: 76px; flex-shrink: 0; display: flex; align-items: center; gap: 6px;
  color: var(--faint); line-height: 1.55;
}
.jobs-meta-label svg { width: 12px; height: 12px; flex-shrink: 0; }
.jobs-meta-value {
  flex: 1; min-width: 0; color: var(--text); font-weight: 600; line-height: 1.55;
  word-break: break-all; font-variant-numeric: tabular-nums;
}
.jobs-copy {
  width: 20px; height: 20px; margin-top: -2px; border: 0; border-radius: 5px;
  background: transparent; color: var(--faint); cursor: pointer;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.jobs-copy:hover { color: var(--text); background: var(--soft); }
.jobs-copy svg { width: 11px; height: 11px; }
.jobs-detail-error {
  padding: 8px 10px; border-radius: 9px; background: var(--danger-bg); color: var(--danger);
  font-size: 10px; font-weight: 600; line-height: 1.55; word-break: break-word;
}
.jobs-locate {
  height: 34px; width: 100%; border: 1px solid var(--line); border-radius: 9px;
  background: var(--soft); color: var(--text); font: inherit; font-size: 11px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer;
}
.jobs-locate:hover:not(:disabled) { border-color: var(--line-2); background: var(--card-solid); }
.jobs-locate:disabled { color: var(--faint); cursor: default; opacity: .6; }
.jobs-locate svg { width: 13px; height: 13px; }
.jobs-steps { border-top: 1px solid var(--line); margin-top: 2px; padding-top: 10px; }
.jobs-steps-title { font-size: 10px; font-weight: 700; color: var(--faint); margin-bottom: 9px; }
.jobs-step-row { display: flex; align-items: flex-start; gap: 8px; }
.jobs-step-dot {
  width: 6px; height: 6px; border-radius: 50%; background: currentColor;
  margin-top: 5px; flex-shrink: 0;
}
.jobs-step-dot.is-done { color: var(--success); }
.jobs-step-dot.is-error { color: var(--danger); }
.jobs-step-dot.is-running { color: #3b82f6; }
.jobs-step-dot.is-queued { color: var(--warning); }
.jobs-step-dot.is-canceled { color: var(--faint); }
.jobs-step-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.jobs-step-name { font-size: 11px; font-weight: 700; line-height: 1.4; color: var(--text); }
.jobs-step-sub { font-size: 9.5px; color: var(--faint); }
.jobs-step-side {
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  font-size: 9.5px; color: var(--muted); flex-shrink: 0;
}
.jobs-step-time { color: var(--faint); font-variant-numeric: tabular-nums; }
.jobs-hub .job-recover { margin: 0 0 4px; }
.jobs-hub .job-empty { padding: 26px 12px; }

@media (max-width: 760px) {
  .jobs-drawer.jobs-hub { left: 74px; width: calc(100vw - 90px); }
  .jobs-drawer.jobs-hub.is-expanded { width: calc(100vw - 24px); height: calc(100vh - 24px); }
  .jobs-hub.is-expanded .jobs-body { grid-template-columns: 1fr; }
  .jobs-hub.is-expanded .jobs-detail-pane { display: none; }
  .jobs-hub.is-expanded.is-detail .jobs-list-pane { display: none; }
  .jobs-hub.is-expanded.is-detail .jobs-detail-pane { display: flex; border-left: 0; }
  .jobs-hub.is-expanded.is-detail #jobsBackBtn { display: flex; }
  .jobs-hub.is-expanded.is-detail #jobsMultiBtn,
  .jobs-hub.is-expanded.is-detail .jobs-view-mode { display: none; }
}

/* ================================================================
   画布内缩：资产库打开时把画布往左让
   ================================================================

   /**
    * 唯一的「面板占位」开关。
    *
    * --canvas-right 是**画布右侧被占掉的宽度**，所有需要跟着画布走的浮层
    * 都从这一个变量推导，不各自硬编码 —— 否则改面板宽度时要改五处，
    * 漏一处就会出现「面板盖住了画布但画布还按满宽算坐标」的错位。
    *
    * 之所以能让画布真的「让出」空间而不是被面板盖住：
    * screenToWorld / zoomAt / fitBounds 全部以 board.getBoundingClientRect()
    * 为基准，#board 一变窄，坐标换算自动跟着变，JS 一行都不用改。
    */
   .shell {
     --canvas-right: 0px;
     --ease-panel: .25s var(--ease);
   }
   body.assets-open .shell { --canvas-right: 376px; }

/**
   * 必须显式写 right，不能只靠 inset:0。
   *
   * base.css 的 .board 是 `inset: 0`，简写会展开成 top/right/bottom/left: 0，
   * 于是 `right` 被**同一个简写**里的声明决定了 —— 变量那条虽然写在后面，
   * 实际生效与否取决于两条规则的层叠顺序，很脆。
   * 显式写一遍就没有这个歧义了。
   */
  #board {
    right: var(--canvas-right);
    transition: right var(--ease-panel);
  }
   /**
    * 底部提示条按**画布**居中，而不是按视口居中。
    *
    * 视口居中的话，画布被压缩后它会往右偏、最后一半落在面板底下 ——
    * 而这行字正好是用户学快捷键的地方，被遮掉等于没有。
    */
   #hint {
     left: calc(50% - var(--canvas-right) / 2);
     transition: left var(--ease-panel);
   }

   /**
    * 资产库自身。
    *
    * 全部用 #assetsPanel 前缀覆盖，**不动 .jobs-drawer** ——
    * 那个类被汇报 PPT 抽屉、生成记录抽屉、画布助手三个面板共用，
    * 改它的定位会连带把另外三个也挪走。
    *
* 宽度 320 = 三列 × 96px。480 太宽了：抽屉右边一条 480px，
   * 在 1584px 的窗口上吃掉三成画面，而里面装的内容只有三列缩略图。
   * top 保持 70px 避开顶部工具栏（工具栏 z-index 30 > 本面板 26，
   * 真重叠的话被盖住的是工具栏），bottom 撑到画布底部。
   *
   * 改宽度时三处必须一起改，否则画布让位和助手错位都会跟着错：
   *   这里 320 / --canvas-right 336（320+right16）/ agentDrawer right 352
   */
   .assets-panel {
     width: 360px;
     top: 90px; bottom: 16px;
     max-height: none;
   }
/**
 * 画布助手往左让开，两个面板才能同时用。
 *
 * 之前打开画布助手会直接把资产库关掉（agentdrawer.js 里 remove('open')），
 * 于是「用助手时就没法用资产库里的资源」—— 而这恰恰是最需要两者同时在的场合：
 * 让助手照着素材库里的图继续生成。
 *
 * 数值推导（别写死就完事，窗口一变就错位）：
 *   资产库 = right 16 + 宽 320 → 占 窗口-336 ~ 窗口-16（窗口 1584 时 1248~1568）
 *   助手右边缘要停在 1248 - 16(间隙) = 1232
 *   CSS 的 right 定位的是**右**边缘 → right = 窗口宽 - 1232 = 352px
 *
 * 助手宽度用 index.html 上的内联 width:420px，本规则不要碰：
 * 内联样式压过任何选择器，这里写 width 是不生效的（写过了，白写）。
 */
body.assets-open #agentDrawer {
  right: 392px;
  width: min(520px, calc(100vw - 424px));
}

/* ---------- 资产库面板 ---------- */

/**
 * 瀑布流。
 *
 * 之前是 auto-fill + aspect-ratio:1 + object-fit:cover —— 每张图都被裁成
 * 同样大小的正方形，横图只剩中间一条，竖图被切掉两头。列表看着很整齐，
 * 但**认不出图**：选素材靠的就是一眼认出内容，裁没了等于没有。
 *
 * 改成格子高度跟着图片原始比例走，完整显示不裁切。
 *
 * 为什么不用 CSS `columns`（写起来最省事）：它是「先灌满第一列再灌第二列」，
 * 素材按时间倒序排，用户从上往下读，第一列却是跨时间的 —— 顺序整个反过来。
 * grid + JS 算 span 能保持「先横后竖」的正常阅读顺序。
 *
 * 为什么用 grid-auto-rows: 0 + row-gap 撑间距，而不是留一条最小行高：
 * 格高 = (span-1) × rowGap，所以量化步长就是 rowGap(6px)。
 * 一旦行高留了 2px（步长变 8px），6:1 的横图目标高度才 16px，
 * 误差就占到 60%（实测 26px 对期望 16px）—— 矮图全是空白。
 *
 * 代价是格高完全依赖 JS 算 span。所以下面给了一个 CSS 兜底值，
 * JS 没跑起来时至少显示成一条固定高度的格子，而不是整块空白。
 */
.asset-grid {
  display: grid;
  /** 固定三列，不写 auto-fill：auto-fill 的列数会随面板宽度浮动 */
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 0;
  column-gap: 6px; row-gap: 6px;
  padding: 10px;
  align-content: start;
  /**
   * 滚动容器里必须给确定的宽度。
   *
   * 父级 .jobs-drawer 是 flex 列布局，asset-grid 拿的是 flex:1 的**高度**。
   * 没有 width 约束时，1fr 的可用宽度算不出来，格子宽度退化成 0 ——
   * 格子有、列数对，就是什么都看不见。
   */
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  overflow-y: auto;
  flex: 1;
}
/** 空状态不能占瀑布流的一格 */
.asset-grid > .job-empty { grid-column: 1 / -1; }

.asset-item {
  position: relative; border-radius: 10px;
  border: 1px solid var(--line); background: var(--soft); cursor: pointer;
  /** JS 没跑时的兜底高度：(30-1)×6 = 174px。inline style 会覆盖它 */
  grid-row-end: span 30;
  /**
   * 刻意**不**写 overflow:hidden。
   *
   * 之前裁切是为了让图片自己贴着圆角。现在圆角交给图片自己（下面那条），
   * 换来的是工具条可以溢出格子 —— 6:1 的扁图格子只有 18px 高，
   * 工具条 24px，裁掉的话「显示不全」；不裁的话它只是往上探出 6px，
   * 至少四个按钮全都点得到。
   */
  transition: border-color .14s var(--ease);
}
/**
 * 悬停反馈只做两件事：描边变亮 + 图片提亮一点。
 *
 * 之前这里还有 transform: scale(1.1)，在瀑布流里是个明确的错误：
 * 格子高度本来就不一（实测 18 ~ 576px），放大 10% 意味着一个 564px 的
 * 竖图往上多长 28px，直接压住上面那张卡的整条工具条 —— 于是
 * 「有些图的按钮显示不全」。而且格子一变大，光标下的元素就在
 * 自己和邻居之间来回跳，hover 反复开关，工具条「闪一下就没」。
 *
 * 放大换不回定位能力：工具条从底部滑出本身就是很强的信号。
 */
.asset-item:hover { border-color: var(--strong); z-index: 2; }
.asset-item:hover > img, .asset-item:hover > video { filter: brightness(1.08); }
/**
 * contain 而非 cover —— 这就是「全显」那两个字。
 *
 * span 是按 API 返回的 width/height 算出来的，比例本来就对；
 * 真出现偏差（老素材没记尺寸、比例算错）时宁可留一圈白边，
 * 也不能把图裁掉：裁掉的那部分用户永远看不到了。
 *
 * 圆角挂在图片自己身上（外层不裁切，见上面的 overflow 说明）。
 */
.asset-item > img, .asset-item > video {
  width: 100%; height: 100%; object-fit: contain; display: block;
  border-radius: 9px;
}

/* 拖拽中：半透明 + 抓取光标，明确告诉用户「正在拖走」 */
.asset-item.dragging { opacity: .4; cursor: grabbing; }

/**
 * 拖拽幽灵。
 *
 * 之前是 setDragImage(cell, 40, 40)，直接拿缩略图格子当幽灵，
 * 结果浏览器给出一张巨大的图片虚影：既挡视线，又不轻巧。
 * 挡视线的代价比「不够好看」严重得多 —— 光标底下是什么完全看不见，
 * 于是每次都落在空白处，拖拽只剩「新建卡片」一种结果。
 *
 * 现在专门造一个 72px 的小图，像从资源管理器往桌面拖那样。
 *
 * `z-index` 必须是 90，不能是 -1。
 * -1 会把幽灵甩到页面背景层**之下**：它于是不被绘制，
 * Chrome 给 setDragImage 截到一张空快照，却**不报任何错**，
 * 直接退回默认幽灵 —— 而默认幽灵对 <img> 就是按原始像素尺寸画出来的大图，
 * 也就是「改完之后还是巨大虚影」这个现象。
 *
 * 放在左下角而不是跟着光标，是因为幽灵是拖拽**开始前**截的一张静态快照，
 * 不会跟着鼠标走；摆在会被拖动路径扫过的位置只会一直碍眼。
 * z-index 90 是刻意低于 dropOverlay(100)：拖拽时画布被压暗，
 * 幽灵跟着一起暗下去，不会成为整块画面里最亮的东西。
 */
.drag-ghost {
  position: fixed; left: 14px; bottom: 14px; z-index: 90;
  width: 72px; height: 72px; border-radius: 12px; overflow: hidden;
  border: 1px solid rgba(255, 255, 255, .45);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .5);
  pointer-events: none;
}
.drag-ghost img, .drag-ghost video { width: 100%; height: 100%; object-fit: cover; display: block; }

/**
 * 拖拽悬停在节点上。
 *
 * 节点 zone 一直会给 .node 加 .over，但样式表里**根本没有 .node.over 这条规则**
 * —— 瞄准卡片时一点反馈都没有。用户没法判断「松手是加进这张卡片」
 * 还是「在旁边新建一张」，于是宁可往空白处丢，拖拽就只剩新建一种结果。
 *
 * 空态的 .dropzone 变个色不算反馈：那只是卡片内部一小块区域，
 * 而节点 zone 的命中范围是整张卡，两者视觉上对不上。
 */
.node.over {
  outline: 2px dashed var(--strong);
  outline-offset: 3px;
}
.node.over::after {
  content: '松手加进这张卡片';
  position: absolute; left: 50%; top: 8px; z-index: 6;
  transform: translateX(-50%);
  padding: 4px 10px; border-radius: 999px;
  background: var(--strong); color: var(--strong-text);
  font-size: 10.5px; font-weight: 800; white-space: nowrap;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .35);
  pointer-events: none;
}

/**
 * 悬停详情浮层。
 *
 * 宿主是 body 末尾的 #assetPeek（不在面板里）——
 * 放面板内部会被 .jobs-drawer 的 overflow:hidden 裁掉，
 * 而它本来就要溢出到画布那一侧。
 *
 * 固定定位 + JS 定位，不跟着面板滚动走：面板一动就重算太频繁，
 * 而且用户悬停时面板本来就不该滚。
 */
.asset-peek {
  position: fixed; z-index: 45;
  padding: 8px 10px; border-radius: 10px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 12px 32px var(--shadow); backdrop-filter: blur(16px);
  font-size: 10px; font-weight: 600; color: var(--muted);
  pointer-events: none;
  opacity: 0; transition: opacity .12s var(--ease);
  max-width: 220px;
}
.asset-peek.on { opacity: 1; }
.asset-peek .pk-t { color: var(--text); font-weight: 800; margin-bottom: 3px; }
.asset-peek .pk-r { display: flex; gap: 6px; align-items: baseline; }
.asset-peek .pk-k { color: var(--faint); flex-shrink: 0; }
.asset-peek .pk-v { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/**
 * 缩略图上的操作条（收藏 / 下载 / 删除 / 添加到画布）。
 *
 * 之前这四个钮各自带一整串内联样式（position/尺寸/圆角/颜色全写一遍），
 * 和标题栏那排按钮是两套尺寸体系 —— 同一个面板里两种按钮规格，
 * 看着就是「不协调」的主要来源。现在统一到这一处。
 */
.asset-act {
  position: relative; width: 18px; height: 18px; flex: none;
  border-radius: 6px;
  background: rgba(20, 20, 24, .72);
  border: 1px solid rgba(255, 255, 255, .16);
  backdrop-filter: blur(6px);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity .14s var(--ease), background .14s var(--ease), transform .14s var(--ease);
}
.asset-act svg { width: 11px; height: 11px; }
.asset-act:hover { background: rgba(20, 20, 24, .92); transform: scale(1.12); }
.asset-act.danger:hover { background: var(--danger); }
.asset-act:disabled { opacity: .5; cursor: wait; }
/** 已收藏的星要常亮 —— 否则收藏完一屏星全是暗的，等于没收藏 */
.asset-act[data-on] { color: #fbbf24; }

/**
 * 工具条：从格子底部往上滑出。
 *
 * 面板收到 320px 后每格只有 96px 宽，而四个钮一行要 4×18+3×2 = 78px ——
 * 再加一个左上角常驻的勾选框就放不下了。所以分两层：
 *   勾选框常驻左上（选没选中必须一直看得见，不能藏进 hover 里）
 *   四个操作钮放进工具条，hover 才从底部滑出来
 *
 * 用 translateY(100%) 而不是 opacity：格子是 overflow:hidden 的，
 * 「藏在格子外面再滑进来」才是真的浮出，光淡入的话像图片自己变了。
 * 工具条同时 pointer-events:none，不占点击，鼠标不动它自己就消失。
 *
 * 已知的取舍：工具条高 24px，遇上宽高比超过 4:1 的扁图（格高不足 24px）
 * 会盖满整格。格子只有一条缝那么大时，任何贴边工具条都放不下，
 * 而为它单独设计一套交互不值得 —— 这类图在素材库里本来就少见，
 * 而且悬停详情浮层（面板外侧那个）照样给得出全部信息。
 */
.asset-tools {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; gap: 2px; align-items: center; justify-content: center;
  padding: 3px 2px;
  background: linear-gradient(transparent, rgba(12, 12, 16, .82) 40%);
  transform: translateY(100%);
  opacity: 0;
  pointer-events: none;
  transition: transform .16s var(--ease), opacity .16s var(--ease);
}
.asset-item:hover .asset-tools { transform: translateY(0); opacity: 1; pointer-events: auto; }
/* 工具条里的按钮不再各自延迟显隐 —— 整条一起进出，避免逐个闪 */
.asset-tools .asset-act { opacity: 1; }

/** 勾选框常驻左上。它和操作钮共用 .asset-act（同一套尺寸/悬停/边框），
    这里只补位置和「不受 hover 控制」的显隐。 */
.asset-act.asset-pick { position: absolute; top: 4px; left: 4px; z-index: 2; opacity: .9; }
.asset-act.asset-pick:hover { opacity: 1; }
.asset-act.asset-pick.on { background: var(--strong); border-color: var(--strong); }

/**
 * 尺寸角标。
 *
 * 之前贴在底部，现在工具条也从底部来 —— 两个都占底部会互相压住，
 * 而且 hover 时工具条一滑出来就把尺寸盖掉了。所以角标改到右上，
 * 那里只有工具条不会来（它从下往上）。
 */
.asset-item .meta {
  position: absolute; top: 4px; right: 4px; padding: 2px 4px;
  border-radius: 5px;
  background: rgba(12, 12, 16, .62);
  color: #fff; font-size: 8.5px; font-weight: 700;
  opacity: 0; transition: opacity .14s;
  pointer-events: none;
}
.asset-item:hover .meta { opacity: 1; }

/* 收藏筛选按钮的选中态：跟其它筛选控件一致，不能只靠图标自己解释 */
#assetFavOnlyBtn.active { color: var(--strong); border-color: var(--strong); }

/* ---------- 折叠式高级参数 ---------- */
.adv-toggle {
  margin-top: 8px; font-size: 10px; font-weight: 800; color: var(--faint);
  cursor: pointer; user-select: none; display: flex; align-items: center; gap: 4px;
  text-transform: uppercase; letter-spacing: .06em;
}
.adv-toggle:hover { color: var(--text); }
  /* 箭头的稳定外层：lucide 会把里面的 <i> 换成 <svg>，
     所以旋转必须发生在这个 span 上，而不是里面的图标元素。 */
  .adv-caret {
    display: inline-flex; align-items: center; justify-content: center;
    width: 11px; height: 11px; transition: transform .15s var(--ease);
  }
  .adv-caret svg, .adv-caret i { width: 11px; height: 11px; }
  .adv-panel { display: none; margin-top: 8px; flex-direction: column; gap: 8px; }
.adv-panel.open { display: flex; }

/* ---------- 尺寸选择：预设按钮组 ---------- */
.chip-row { display: flex; gap: 4px; flex-wrap: wrap; }
.chip {
  height: 26px; padding: 0 9px; border-radius: 8px;
  border: 1px solid var(--line-2); background: var(--card-solid); color: var(--muted);
  font-size: 10px; font-weight: 800; cursor: pointer; transition: all .14s var(--ease);
}
.chip:hover { border-color: var(--strong); color: var(--text); }
.chip.active { background: var(--strong); color: var(--strong-text); border-color: var(--strong); }

/* ================================================================
   声明式字段引擎（web/js/nodes/fields.js）的控件样式
   摘要行默认折叠，展开才占地方 —— 500 节点的画布放不下展开态。
   ================================================================ */
.field-panel { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }

.field-summary {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  width: 100%; padding: 5px 8px; border-radius: 8px; text-align: left; cursor: pointer;
  border: 1px solid var(--line-2); background: var(--card-solid);
  color: var(--muted); font-size: 10px; font-weight: 800; transition: all .14s var(--ease);
}
.field-summary:hover { border-color: var(--strong); color: var(--text); }
.field-sum-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.field-caret { width: 12px; height: 12px; flex: none; opacity: .5; }

.field-body { display: flex; flex-direction: column; gap: 7px; padding: 4px 2px 2px; }

/* ---------- 分段控件：比 chip 紧凑，读起来是「一个控件的几种档位」 ---------- */
.seg-row {
  display: flex; gap: 0; border: 1px solid var(--line-2);
  border-radius: 8px; overflow: hidden; background: var(--card-solid);
}
.seg {
  flex: 1; height: 26px; border: 0; border-right: 1px solid var(--line-2);
  background: transparent; color: var(--muted);
  font-size: 10px; font-weight: 800; cursor: pointer; transition: all .14s var(--ease);
}
.seg:last-child { border-right: 0; }
.seg:hover { color: var(--text); background: var(--card-solid); }
.seg.active { background: var(--strong); color: var(--strong-text); }

/* ---------- 档位附注（付费 / 按次计费） ----------
   挂在档位按钮里而不是标题上：塞进标题会让这一档看起来更长，
   而它只是更贵、不是更复杂。 */
.opt-note {
  font-size: 8px; font-weight: 700; letter-spacing: .02em;
  opacity: .72; line-height: 1; white-space: nowrap; pointer-events: none;
}
.seg.has-note {
  height: auto; min-height: 30px; padding: 3px 5px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  /* 数字仍是主体，字重压回去一点，免得和附注抢 */
  font-size: 11px;
}
.chip.has-note {
  display: flex; align-items: center; gap: 4px;
}
/* 选中态下附注要仍然可读：--strong-text 是白字，压到 .72 还能看清，
   再压就糊了 —— 这行如果是为了「淡雅」而牺牲可读性，那不如不做这个附注。 */
.seg.active .opt-note, .chip.active .opt-note { opacity: .8; }

/* ---------- 开关 ---------- */
.switch {
  width: 34px; height: 19px; padding: 2px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--line-2); background: var(--card-solid);
  display: inline-flex; align-items: center; transition: all .16s var(--ease);
}
.switch.on { background: var(--strong); border-color: var(--strong); }
.switch-dot {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--muted); transition: transform .16s var(--ease), background .16s var(--ease);
}
.switch.on .switch-dot { transform: translateX(15px); background: var(--strong-text); }

/* ---------- 滑块 ---------- */
.slider-wrap { display: flex; align-items: center; gap: 8px; flex: 1; }
.slider { flex: 1; min-width: 0; accent-color: var(--strong); }
.slider-out {
  min-width: 34px; text-align: right;
  color: var(--text-2); font-size: 11px; font-weight: 800;
}

/* ---------- 图形化比例方框 ----------
   按真实宽高比画小矩形：人眼不用读文字就能认出「竖构图」。
   shape 用 aspect-ratio，不需要 JS 测量。 */
.ratio-grid { display: flex; flex-wrap: wrap; gap: 6px; align-items: flex-end; }
.ratio-box {
  width: 46px; padding: 4px 0 3px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line-2); background: var(--card-solid);
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  transition: all .14s var(--ease);
}
.ratio-box:hover { border-color: var(--strong); }
.ratio-box.active { border-color: var(--strong); background: var(--strong); color: var(--strong-text); }
/* 所有方框按同一「面积」缩放，否则 1:1 会比 21:9 的细条重得多 */
.ratio-shape {
  display: block; width: 20px; max-height: 24px;
  border: 1.5px solid currentColor; border-radius: 2px; opacity: .72;
}
.ratio-box.active .ratio-shape { opacity: 1; }
.ratio-cap { font-size: 9px; font-weight: 800; letter-spacing: -.2px; }

.ratio-grid .custom-toggle { height: 24px; align-self: center; }
.ratio-dims { display: flex; gap: 6px; width: 100%; margin-top: 2px; }
.ratio-dim { display: flex; align-items: center; gap: 4px; flex: 1; }
.ratio-dim-cap { font-size: 10px; font-weight: 800; color: var(--faint); flex: none; }
.ratio-dim .setting-input { height: 28px; }

/* ---------- 空状态 ---------- */
.empty-state {
  padding: 26px 14px; text-align: center;
  color: var(--faint); font-size: 11px; font-weight: 700; line-height: 1.7;
}
.empty-state svg { width: 26px; height: 26px; opacity: .45; margin-bottom: 6px; }

/* ---------- 状态栏 ---------- */
.statusbar {
  position: absolute; left: 80px; bottom: 16px; z-index: 20;
  display: flex; align-items: center; gap: 10px;
  padding: 6px 12px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 8px 24px var(--shadow); backdrop-filter: blur(16px);
  font-size: 10px; font-weight: 700; color: var(--muted);
}
.statusbar .sep { width: 1px; height: 11px; background: var(--line); }
.statusbar b { color: var(--text); font-weight: 800; }

/* 运行状态独立成一个槽位：之前它和保存状态抢同一个节点，
   「已保存」会被「3 个运行中」覆盖，反之亦然 */
.statusbar .status-slot {
  color: var(--strong); font-weight: 800;
  white-space: nowrap;
}
.statusbar #stSave { white-space: nowrap; }
.statusbar #stSave[style*="cursor: pointer"] { text-decoration: underline dotted; }

/* ---------- 滚动条（覆盖原型的 5px，媒体区需要更可点） ---------- */
.prop-body::-webkit-scrollbar, .llm-chat::-webkit-scrollbar,
.modal-body::-webkit-scrollbar, .picker-list::-webkit-scrollbar,
.jobs-list::-webkit-scrollbar { width: 7px; }
/* ---------- 启动失败兜底 ---------- */
.boot-error {
  max-width: 560px; margin: 48px auto; padding: 24px 28px;
  background: var(--card-solid); border: 1px solid var(--line);
  border-left: 3px solid var(--danger);
  border-radius: 16px; box-shadow: 0 16px 44px var(--shadow);
}
.boot-error h3 { margin: 0 0 8px; font-size: 15px; color: var(--danger); }
.boot-error p { margin: 0 0 14px; font-size: 12px; color: var(--muted); line-height: 1.7; }
.boot-error pre {
  margin: 0 0 16px; padding: 10px 12px; max-height: 180px; overflow: auto;
  background: var(--soft); border-radius: 10px;
  font-size: 11px; color: var(--text); white-space: pre-wrap; word-break: break-all;
}
.boot-error-actions { display: flex; gap: 8px; }
.boot-error-actions .btn { height: 30px; padding: 0 16px; }

/* ---------- 空画布引导 ---------- */
/* 放在左上角而不是屏幕中心：
   居中的卡片 520px 宽会遮住画布中央那块区，而那里正好是节点和拖线落点最常用的位置。
   pointer-events:none 只能保护卡片之外的区域，卡片自身仍会遮住它覆盖的那块。 */
.empty-guide {
  position: absolute; left: 72px; top: 72px; z-index: 6;
  max-width: 380px;
  pointer-events: none;
}
.empty-guide-card {
  pointer-events: auto;
  padding: 20px 22px; text-align: left;
  background: var(--card-solid); border: 1px solid var(--line);
  border-radius: 22px; box-shadow: 0 24px 64px var(--shadow);
}
.empty-guide-card h2 { margin: 0 0 8px; font-size: 17px; color: var(--text); }
.empty-guide-card p { margin: 0 0 20px; font-size: 12px; color: var(--muted); line-height: 1.7; }
.empty-guide-actions { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-bottom: 20px; }
.empty-guide-actions .btn { height: 32px; padding: 0 16px; }
.empty-guide-actions .btn.primary { border-color: var(--strong); color: var(--strong); }
.empty-guide-tips {
  margin: 0; padding: 16px 0 0; list-style: none;
  border-top: 1px solid var(--line);
  text-align: left;
}
.empty-guide-tips li {
  font-size: 11px; color: var(--faint); line-height: 1.9;
}
.empty-guide-tips b { color: var(--muted); }

/* ---------- 节点状态：警告 ---------- */
/* 运行成功但有参数未生效，不能用绿色「完成」盖过去 */
.node.state-warn .node-status-slot .msg,
.node-status-slot.state-warn .msg { color: var(--warning); }
.node.state-warn .node-status { border-color: var(--warning); }
.node-status-slot.warn .txt { color: var(--warning); }

/* ---------------- 批量参数面板 ---------------- */
.batch-target {
  font-size: 12px;
  color: var(--muted);
  padding: 6px 8px;
  margin-bottom: 10px;
  background: var(--strong);
  border-radius: 6px;
}
.batch-empty {
  font-size: 12px;
  color: var(--muted);
  padding: 14px 8px;
  text-align: center;
  line-height: 1.7;
}
/* 取值不一致的提示：紧贴它上面那个字段，位置固定不跳动 */
.batch-mixed {
  font-size: 11px;
  color: var(--warning);
  margin: -6px 0 8px;
  padding-left: 2px;
}

/* ---------------- 画布卡片右上角按钮组 ----------------
   删除在 right:8、重命名在 right:40（base.css 里已有），
   这里补上「复制」与「导出」—— 复制按钮此前完全没有样式，
   也就是说那个按钮从来没被正确定位过。 */
.canvas-card-copy {
  position: absolute;
  right: 72px;
  top: 8px;
  width: 26px;
  height: 26px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  background: var(--card-solid);
  border: 1px solid var(--line);
  opacity: 0;
  transition: all .14s var(--ease);
  z-index: 2;
}
.canvas-item:hover .canvas-card-copy { opacity: 1; }
.canvas-card-copy:hover { color: var(--text); background: var(--soft-2); }
.canvas-card-copy svg { width: 14px; height: 14px; }

.canvas-card-export {
  position: absolute;
  right: 104px;
  top: 8px;
  width: 26px;
  height: 26px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  background: var(--card-solid);
  border: 1px solid var(--line);
  opacity: 0;
  transition: all .14s var(--ease);
  z-index: 2;
}
.canvas-item:hover .canvas-card-export { opacity: 1; }
.canvas-card-export:hover { color: var(--text); background: var(--soft-2); }
.canvas-card-export svg { width: 14px; height: 14px; }

/* ---------------- Civitai 模型信息卡 ---------------- */
.civitai-box {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.civitai-line {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 11px;
  color: var(--muted);
}
.civitai-ckpt {
  font-family: var(--mono, monospace);
  font-size: 11px;
  color: var(--text);
  word-break: break-all;
}
.civitai-head {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.civitai-name {
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
  text-decoration: none;
}
.civitai-name:hover { text-decoration: underline; }
.civitai-meta,
.civitai-stats,
.civitai-words,
.civitai-rec {
  font-size: 11px;
  color: var(--muted);
}
.civitai-rec { color: var(--text); }
/* 连不上与「查不到」要一眼可辨：这是两种完全不同的原因 */
.civitai-warn {
  font-size: 11px;
  color: var(--warning);
  line-height: 1.6;
}

/* ---- 蒙版编辑器 ---- */
.mask-editor { display: flex; flex-direction: column; gap: 10px; }
.mask-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mask-label { font-size: 12px; color: var(--text-dim); white-space: nowrap; }
.mask-label.grow { margin-left: auto; }
.mask-size { width: 160px; }
/* stage 里三层缠在同一位置：底图 / 红罩 / 画布 */
.mask-stage { position: relative; display: inline-block; line-height: 0;
  border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
  max-width: 100%; background: repeating-conic-gradient(#2a2a2a 0 25%, #333 0 50%) 0 0/16px 16px; }
.mask-stage > img, .mask-stage > canvas {
  display: block; max-width: 100%; height: auto; }
/* 画布在最上层，才能接到手写事件 */
.mask-stage > canvas.mask-canvas { position: absolute; inset: 0; width: 100%; height: 100%;
  cursor: crosshair; touch-action: none; }
.mask-stage > canvas.mask-overlay { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.mask-status { font-size: 12px; color: var(--text-dim); min-height: 1.2em; }
.mask-note { font-size: 12px; color: var(--text-dim); line-height: 1.6; }
.btn.mini.on { background: var(--accent); color: #fff; border-color: var(--accent); }
.mask-chip { display: flex; align-items: center; gap: 6px; margin-top: 6px;
  font-size: 11px; color: var(--text-dim); }

/* ---- 图片节点：底图 / 参考标注 ---- */
/* 标注必须比序号更眼才，否则多图时难以扫一眼看出哪张要出图 */
.img-role {
  position: absolute; left: 3px; bottom: 3px;
  font-size: 9px; line-height: 1.4; padding: 0 4px;
  border-radius: 4px; cursor: pointer;
  background: rgba(16, 185, 129, .92); color: #04140d; font-weight: 800;
  border: none; box-shadow: 0 1px 3px rgba(0,0,0,.4);
}
/* 参考图：弱化色，与底图形成对比 */
.img-role.is-ref { background: rgba(120, 120, 140, .82); color: #e9e9f2; }
.img-role:hover { filter: brightness(1.15); }

/* ---- 结果格来源角标（逐张 1:1） ---- */
/* 必须与 .idx 分左右：右下角已经有 ★ 主图和操作条 */
.src-badge {
  position: absolute; right: 3px; bottom: 3px;
  font-size: 9px; line-height: 1.4; padding: 0 4px;
  border-radius: 4px; font-weight: 800;
  background: rgba(99, 102, 241, .92); color: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.4);
}

/* ================================================================
   汇报 PPT 抽屉
   ----------------------------------------------------------------
   预览区是「所见即所得」的一部分，所以这里的样式只影响 chrome
   （边框、间距、滚动），**绝不碰 .deck-page 内部**——
   那些坐标必须与导出 PPTX 时逐点一致。
   ================================================================ */
.deck-drawer { max-height: calc(100vh - 96px); }

/* ---------- 第一步：需求 + 模型 + 选图 ---------- */
.deck-setup {
  padding: 10px 14px 14px;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 12px;
}
.deck-setup[hidden] { display: none; }
.deck-brief {
  width: 100%; resize: vertical; min-height: 62px;
  font-family: inherit; line-height: 1.5;
}
.deck-picked {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(58px, 1fr));
  gap: 6px; margin-bottom: 8px;
}
.deck-pick {
  position: relative; aspect-ratio: 1; border-radius: 9px; overflow: hidden;
  border: 1px solid var(--line); background: var(--soft);
}
.deck-pick img { width: 100%; height: 100%; object-fit: cover; display: block; }
.deck-pick.is-bad { outline: 1px dashed var(--danger); }
/* 序号对应提示词里的「第 N 张图」，模型是按这个编号回答的 */
.deck-pick-no {
  position: absolute; left: 3px; bottom: 3px;
  padding: 0 4px; border-radius: 4px;
  background: rgba(0,0,0,.62); color: #fff;
  font-size: 9px; font-weight: 800;
}
.deck-pick-x {
  position: absolute; top: 2px; right: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  border: 1px solid var(--line); background: rgba(255,255,255,.9);
  color: var(--muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity .14s;
}
.deck-pick:hover .deck-pick-x { opacity: 1; }
.deck-pick-x svg { width: 10px; height: 10px; }
.deck-pick-x:hover { color: var(--danger); border-color: var(--danger); }
.deck-pick-actions { display: flex; gap: 6px; }
.deck-pick-actions .btn { flex: 1; justify-content: center; }
.deck-pick-actions .btn svg { width: 13px; height: 13px; }
/* 画布上确实有选中图片时才提示，避免用户点了才知道是空的 */
.deck-pick-actions .btn.has-pick { border-color: var(--strong); color: var(--strong); }
.deck-generate { width: 100%; justify-content: center; padding: 9px; }
.deck-generate svg { width: 14px; height: 14px; }

.deck-busy {
  font-size: 11px; color: var(--muted);
  text-align: center; padding: 2px 0;
}
.deck-busy[hidden] { display: none; }
.deck-generate.is-busy { opacity: .6; cursor: progress; }

/* ---------- 第二步：预览 + 对话 ---------- */
.deck-work { min-height: 0; flex: 1; display: flex; flex-direction: column; }
.deck-work[hidden] { display: none; }
.deck-pages-wrap {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; padding: 10px 0 10px 0;
  background: var(--soft);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.deck-pages {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 0 12px;
}
.deck-status {
  font-size: 10.5px; color: var(--muted);
  padding: 5px 14px; border-bottom: 1px solid var(--line);
}
.deck-status.is-warn { color: var(--danger); }
.deck-talk { padding: 8px 14px 12px; display: flex; flex-direction: column; gap: 7px; }
.deck-chat {
  max-height: 168px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 7px;
}
.deck-msg {
  font-size: 11.5px; line-height: 1.55;
  padding: 7px 9px; border-radius: 9px;
  background: var(--card-solid); border: 1px solid var(--line);
  align-self: stretch;
}
.deck-msg.is-user { background: var(--strong); color: #fff; border-color: transparent; }
.deck-reasons {
  margin: 5px 0 0; padding-left: 15px;
  font-size: 10.5px; color: var(--muted); line-height: 1.5;
}
.deck-msg.is-user .deck-reasons { color: rgba(255,255,255,.85); }
.deck-reasons.is-warn { color: var(--danger); }
.deck-reasons.is-muted { opacity: .75; }

/* ---------- 模型选择器 ----------
   渠道里配好的对话模型实测有几百个，原生 <select> 既搜不了
   也看不出哪个能读图，所以用「输入框 + 候选列表」。 */
.deck-model-list {
  margin-top: 4px; max-height: 216px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel); box-shadow: 0 8px 24px var(--shadow);
}
.deck-model-list[hidden] { display: none; }
.deck-model {
  padding: 6px 9px; cursor: pointer;
  display: flex; flex-direction: column; gap: 2px;
  border-bottom: 1px solid var(--line);
}
.deck-model:last-child { border-bottom: none; }
.deck-model:hover { background: var(--hover); }
.deck-model.is-picked { background: color-mix(in srgb, var(--strong) 14%, transparent); }
.deck-model-name {
  font-size: 11.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.deck-model-tags { display: flex; gap: 4px; flex-wrap: wrap; }
.deck-model-tag {
  font-size: 9px; padding: 0 5px; border-radius: 4px;
  background: var(--soft); color: var(--muted); border: 1px solid var(--line);
}
/* 可读图单独标出来：PPT 选错模型的代价是「付了钱但没看图」 */
.deck-model-tag.is-vision {
  background: color-mix(in srgb, var(--strong) 18%, transparent);
  color: var(--strong); border-color: transparent; font-weight: 700;
}
.deck-model-empty {
  padding: 9px 10px; font-size: 11px; color: var(--muted); line-height: 1.5;
}

/* ---------- 大纲（直接手改，不经过 AI）---------- */
.deck-outline {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
.deck-outline[open] { background: var(--soft); }
.deck-outline-sum {
  padding: 7px 14px; cursor: pointer; user-select: none;
  font-size: 11.5px; font-weight: 700; color: var(--text);
  display: flex; align-items: center; gap: 6px;
}
.deck-outline-sum svg { width: 13px; height: 13px; color: var(--muted); }
.deck-outline-sum span { margin-left: auto; font-weight: 600; color: var(--muted); }
.deck-outline-list {
  max-height: 260px; overflow-y: auto;
  padding: 4px 10px 8px;
  display: flex; flex-direction: column; gap: 7px;
}
.deck-outline-row {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 9px; padding: 7px 8px;
  display: flex; flex-direction: column; gap: 5px;
}
.deck-outline-head { display: flex; align-items: center; gap: 5px; }
.deck-outline-no {
  width: 18px; height: 18px; border-radius: 5px; flex: none;
  background: var(--strong); color: #fff;
  font-size: 10px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.deck-outline-layout {
  flex: 1; min-width: 0;
  font-size: 11px; padding: 2px 4px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--card-solid); color: var(--text);
}
.deck-outline-move, .deck-outline-del {
  width: 20px; height: 20px; flex: none; border-radius: 5px;
  border: 1px solid var(--line); background: var(--card-solid);
  color: var(--muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.deck-outline-move svg, .deck-outline-del svg { width: 12px; height: 12px; }
.deck-outline-move:hover, .deck-outline-del:hover {
  color: var(--strong); border-color: var(--strong);
}
.deck-outline-del:hover { color: var(--danger); border-color: var(--danger); }
/* 只剩一页时删按钮置灰，而不是点了才告诉用户不能删 */
.deck-outline-del:disabled { opacity: .35; cursor: not-allowed; }
.deck-outline-input, .deck-outline-bullets {
  width: 100%; font-size: 11.5px; line-height: 1.5;
  padding: 4px 6px; border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--card-solid); color: var(--text);
  font-family: inherit; resize: vertical;
}
.deck-outline-input:focus, .deck-outline-bullets:focus {
  outline: none; border-color: var(--strong);
}
.deck-outline-input.is-quiet { font-size: 10.5px; color: var(--muted); }
.deck-outline-foot {
  padding: 6px 12px 9px;
  display: flex; align-items: center; gap: 8px;
}
.deck-outline-foot .btn svg { width: 12px; height: 12px; }

/* ================================================================
   助手附件（回形针 / 拖拽 / 粘贴）
   ----------------------------------------------------------------
   目标是「像豆包一样把图丢进对话框」，所以三个入口的视觉反馈
   必须一致：待发附件、气泡回显、拖拽高亮。
   ================================================================ */

/* 回形针按钮：和其他按钮同高，只是没有文字 */
.btn.icon-only {
  width: 30px; padding: 0; flex: none;
  display: flex; align-items: center; justify-content: center;
}
.btn.icon-only svg { width: 15px; height: 15px; }
.btn.icon-only:disabled { opacity: .5; cursor: progress; }

/* 待发送附件条 */
.agent-attach-strip {
  display: flex; flex-wrap: nowrap; gap: 4px; min-width: 0;
  max-width: 300px; overflow-x: auto; scrollbar-width: none;
  padding: 0;
}
.agent-attach-strip::-webkit-scrollbar { display: none; }
.agent-attach-strip[hidden] { display: none; }
.agent-attach {
  position: relative; flex: none;
  width: 34px; height: 34px;
  border-radius: 8px; overflow: hidden;
  border: 1px solid var(--line); background: var(--soft);
  cursor: pointer;
  transition: border-color .15s var(--ease), transform .15s var(--ease);
}
.agent-attach:hover { border-color: var(--strong); transform: scale(1.08); }
.agent-attach img { width: 100%; height: 100%; object-fit: cover; display: block; }
.agent-attach.is-bad { outline: 1px dashed var(--danger); }
.agent-attach-file {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted);
}
.agent-attach-file svg { width: 18px; height: 18px; }
/* 文件名压在底部：图片靠缩略图认，文件只能靠名字认 */
.agent-attach-name { display: none; }
.agent-attach-no {
  position: absolute; left: 2px; bottom: 2px;
  min-width: 14px; height: 14px; padding: 0 3px;
  border-radius: 999px; background: var(--strong); color: var(--strong-text);
  font-size: 8.5px; font-weight: 800; line-height: 14px; text-align: center;
  pointer-events: none;
}
.agent-attach-x {
  position: absolute; top: 2px; right: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  border: 1px solid var(--line); background: rgba(255,255,255,.92);
  color: var(--muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity .14s;
}
.agent-attach:hover .agent-attach-x { opacity: 1; }
.agent-attach-x svg { width: 10px; height: 10px; }
.agent-attach-x:hover { color: var(--danger); border-color: var(--danger); }

/* 拖拽悬停：整块面板描边，不只是输入框 ——
   用户是往面板里丢的，光高亮输入框会让人以为只接受那里 */
#agentDrawer.is-dropping {
  outline: 2px dashed var(--strong);
  outline-offset: -3px;
}
#agentDrawer.is-dropping::after {
  content: '松手就加进来';
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%,-50%);
  padding: 8px 16px; border-radius: 10px;
  background: var(--panel); border: 1px solid var(--strong);
  color: var(--strong); font-size: 12px; font-weight: 700;
  pointer-events: none; z-index: 5;
  box-shadow: 0 8px 24px var(--shadow);
}
#agentDrawer.is-dropping > * { opacity: .35; }

/* 气泡里的附件：非图片要能看出是文件，且有名字 */
.llm-bubble-cell .agent-attach-file { height: 62px; }
.llm-bubble-cell .agent-attach-name {
  position: static; display: block;
  background: none; color: var(--muted);
  font-size: 9px; padding: 2px 2px 0;
  white-space: normal; word-break: break-all;
  text-align: center;
}
.llm-bubble-bad {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; color: var(--danger);
  background: var(--danger-bg);
}

/* ---------- 文稿切换（多份管理）---------- */
.deck-switch {
  display: flex; align-items: center; gap: 5px;
  background: none; border: none; cursor: pointer;
  font-size: 13px; font-weight: 800; color: var(--text);
  padding: 2px 4px; border-radius: 6px;
  max-width: 260px; min-width: 0;
}
.deck-switch:hover { background: var(--hover); }
.deck-switch span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.deck-switch svg { width: 13px; height: 13px; color: var(--muted); flex: none; }
.deck-switch-list {
  border-bottom: 1px solid var(--line);
  background: var(--soft);
  max-height: 300px; overflow-y: auto;
  padding: 5px 8px 7px;
  display: flex; flex-direction: column; gap: 3px;
}
.deck-switch-list[hidden] { display: none; }
.deck-switch-row {
  display: flex; align-items: center; gap: 3px;
  border-radius: 8px; padding: 1px 2px 1px 0;
}
.deck-switch-row:hover { background: var(--hover); }
/* 当前这一份要一眼看出，不然用户在两份之间切了却不知道自己在哪 */
.deck-switch-row.is-cur { background: color-mix(in srgb, var(--strong) 14%, transparent); }
.deck-switch-main {
  flex: 1; min-width: 0; cursor: pointer;
  padding: 5px 7px;
  display: flex; flex-direction: column; gap: 1px;
}
.deck-switch-name {
  font-size: 11.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.deck-switch-meta { font-size: 9.5px; color: var(--muted); }
.deck-switch-input {
  width: 100%; font-size: 11.5px; padding: 2px 4px;
  border: 1px solid var(--strong); border-radius: 5px;
  background: var(--panel); color: var(--text); font-family: inherit;
}
.deck-switch-input:focus { outline: none; }
.deck-switch-act {
  width: 22px; height: 22px; flex: none; border-radius: 5px;
  border: 1px solid transparent; background: none;
  color: var(--muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.deck-switch-act svg { width: 12px; height: 12px; }
.deck-switch-act:hover { color: var(--strong); border-color: var(--line); }
.deck-switch-act.is-danger:hover { color: var(--danger); border-color: var(--danger); }
.deck-switch-empty { padding: 8px 9px; font-size: 11px; color: var(--muted); line-height: 1.5; }
.deck-switch-all {
  margin-top: 3px; padding: 5px;
  font-size: 10.5px; color: var(--strong);
  background: none; border: 1px dashed var(--line); border-radius: 7px;
  cursor: pointer; font-family: inherit;
}
.deck-switch-all:hover { border-color: var(--strong); }

/* ---------- 导出前体检 ---------- */
.deck-check {
  padding: 6px 14px 8px;
  border-bottom: 1px solid var(--line);
  max-height: 220px; overflow-y: auto;
  background: var(--soft);
}
.deck-check[hidden] { display: none; }
.deck-check-busy { font-size: 11px; color: var(--muted); padding: 6px 0; }
.deck-check-sum { padding: 6px 8px; border-radius: 8px; background: var(--panel); border: 1px solid var(--line); }
.deck-check-sum.is-ok { border-color: color-mix(in srgb, var(--strong) 45%, transparent); }
.deck-check-sum.is-warn { border-color: color-mix(in srgb, #d99a20 55%, transparent); }
.deck-check-sum.is-error { border-color: color-mix(in srgb, var(--danger) 55%, transparent); }
.deck-check-head { font-size: 11.5px; font-weight: 700; color: var(--text); }
.deck-check-sum.is-error .deck-check-head { color: var(--danger); }
.deck-check-sub { font-size: 9.5px; color: var(--muted); margin-top: 1px; }
.deck-check-note {
  margin-top: 5px; padding: 5px 7px; border-radius: 7px;
  font-size: 10.5px; line-height: 1.5;
  background: var(--panel); border: 1px solid var(--line); color: var(--muted);
}
.deck-check-note.is-warn { color: #b8791a; border-color: color-mix(in srgb, #d99a20 40%, transparent); }
.deck-check-page { margin-top: 6px; }
.deck-check-page-head {
  font-size: 10.5px; font-weight: 700; color: var(--text);
  padding: 3px 2px; border-bottom: 1px solid var(--line); margin-bottom: 3px;
}
.deck-check-item {
  padding: 5px 7px; margin-bottom: 3px; border-radius: 7px;
  background: var(--panel); border-left: 3px solid var(--line);
}
.deck-check-item.is-error { border-left-color: var(--danger); }
.deck-check-item.is-warn { border-left-color: #d99a20; }
.deck-check-item.is-info { border-left-color: var(--strong); }
.deck-check-item-title { font-size: 11px; font-weight: 600; color: var(--text); }
.deck-check-item-detail { font-size: 10.5px; color: var(--muted); line-height: 1.5; margin-top: 1px; }
.deck-check-item-fix { font-size: 10.5px; color: var(--strong); margin-top: 2px; line-height: 1.5; }
.deck-check-fidelity { margin-top: 7px; }
.deck-check-fidelity summary {
  font-size: 10.5px; color: var(--muted); cursor: pointer; padding: 3px 2px;
  user-select: none;
}
.deck-check-fidelity summary:hover { color: var(--text); }

/* ================================================================
   节点运行/排队的**居中覆盖层**
   ----------------------------------------------------------------
   之前状态条是插在 body 之前的普通块，运行时会从卡片上沿「挤」出一条
   横条，把标题和图往下推 —— 用户看到的是一条莫名其妙的进度条长在卡片外面。

   现在 running / queued 改成铺满卡片的居中覆盖层（转圈 + 文案 + 进度），
   卡片本身的布局一个像素都不动。ok / warn / error / missing 仍是
   上面那条轻量状态条（它们需要常驻，且带按钮）。
   ================================================================ */
.node-status-slot.running,
.node-status-slot.queued {
  position: absolute; inset: 0;
  margin: 0; padding: 16px;
  border-radius: var(--radius-node);
  display: flex; align-items: center; justify-content: center;
  z-index: 12; text-align: center;
  background: color-mix(in srgb, var(--panel) 66%, transparent);
  backdrop-filter: blur(3px);
}
.node-status-slot.queued { color: var(--muted); }
.node-status-slot .ov {
  width: 100%; max-width: 220px;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.node-status-slot .ov .row { width: 100%; justify-content: center; }
.node-status-slot .ov .node-progress { width: 100%; margin: 0; }
.node-status-slot .spinner {
  width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid color-mix(in srgb, var(--strong) 22%, transparent);
  border-top-color: var(--strong);
  animation: stat-spin .8s linear infinite;
}
@keyframes stat-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .node-status-slot .spinner { animation: none; }
}

/* ================================================================
   分镜格子：一张卡里装 cols×rows 个格子
   ================================================================ */
.node-storyboard .node-body {
  border-radius: var(--radius-node);
  overflow: hidden;
}
.sb-grid {
  display: grid;
  gap: 1px;
  width: 100%;
  background: var(--line);
  border-radius: var(--radius-node);
  overflow: hidden;
}
.sb-cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--card-solid);
  color: var(--faint);
}
.sb-cell.is-empty svg { width: 18px; height: 18px; opacity: .55; }
.sb-cell.is-uploadable { cursor: pointer; }
.sb-cell.is-uploadable:hover { background: var(--soft); color: var(--muted); }
.sb-img { display: block; width: 100%; height: 100%; object-fit: cover; }
.sb-gone { font-size: 10px; color: var(--faint); }

.sb-tools {
  position: absolute;
  top: 5px;
  left: 50%;
  transform: translateX(-50%) scale(var(--panel-scale, 1));
  transform-origin: top center;
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 2px;
  border-radius: 999px;
  background: color-mix(in srgb, #000 62%, transparent);
  box-shadow: 0 3px 10px rgb(0 0 0 / .35);
  z-index: 2;
}
.sb-cell-btn {
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: #fff;
  cursor: pointer;
}
.sb-cell-btn svg { width: 12px; height: 12px; }
.sb-cell-btn:hover { background: rgb(255 255 255 / .18); }
.sb-cell-btn.danger:hover { background: var(--danger); }

.sb-float {
  gap: 4px;
  padding: 4px;
  border-radius: 14px;
}
.sb-chip {
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--soft);
  color: var(--muted);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sb-tool {
  height: 24px;
  padding: 0 8px;
  border: none;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: transparent;
  color: var(--muted);
  font-size: 10.5px;
  cursor: pointer;
  white-space: nowrap;
}
.sb-tool svg { width: 12px; height: 12px; }
.sb-tool:hover { background: var(--soft); color: var(--text); }
.sb-tool.danger:hover { background: var(--danger-bg); color: var(--danger); }

.sb-collapsed {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 14px 10px;
  color: var(--muted);
  font-size: 11px;
}
.sb-collapsed svg { width: 14px; height: 14px; }



/* ---------- outpaint ---------- */
.op-overlay {
  position: fixed; inset: 0; z-index: 780;
  display: flex; flex-direction: column;
  background: rgba(8, 9, 12, .95);
  color: #fff;
  font-family: inherit;
  animation: op-in .16s ease-out;
}
@keyframes op-in { from { opacity: 0; } to { opacity: 1; } }
.op-top {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; padding: 18px 24px 0;
}
.op-heading { display: flex; align-items: baseline; gap: 10px; }
.op-heading b { font-size: 16px; letter-spacing: .02em; }
.op-heading span { font-size: 11px; color: rgba(255,255,255,.5); }
.op-readout { display: flex; align-items: center; gap: 8px; }
.op-size, .op-zoom {
  padding: 4px 9px; border-radius: 999px;
  background: rgba(255,255,255,.09);
  border: 1px solid rgba(255,255,255,.08);
  color: rgba(255,255,255,.82);
  font-size: 11px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.op-zoom:empty { display: none; }
.op-stage {
  position: relative; flex: 1; min-height: 0;
  margin: 8px 20px 0; overflow: hidden;
}
.op-image {
  position: absolute; display: block; border-radius: 6px;
  box-shadow: 0 18px 60px rgb(0 0 0 / .55);
  pointer-events: none; user-select: none;
}
.op-frame {
  position: absolute; box-sizing: border-box;
  border: 1.5px dashed rgba(255,255,255,.94);
  box-shadow: 0 0 0 9999px rgb(0 0 0 / .42);
  cursor: move; touch-action: none;
}
.op-frame::after {
  content: ''; position: absolute; inset: -1.5px;
  border: 1px solid rgba(0,0,0,.35); border-radius: 1px;
  pointer-events: none;
}
.op-tag {
  position: absolute; z-index: 3; top: -30px; left: 50%;
  transform: translateX(-50%);
  padding: 4px 9px; border-radius: 999px;
  background: rgba(20,21,25,.92); color: rgba(255,255,255,.72);
  font-size: 10px; white-space: nowrap; pointer-events: none;
}
.op-handle {
  position: absolute; width: 11px; height: 11px; box-sizing: border-box;
  background: #fff; border: 1.5px solid rgba(0,0,0,.45); border-radius: 3px;
  box-shadow: 0 2px 8px rgb(0 0 0 / .35);
  touch-action: none;
}
.op-handle.nw { left: -6px; top: -6px; cursor: nwse-resize; }
.op-handle.n  { left: 50%; top: -6px; margin-left: -5.5px; cursor: ns-resize; }
.op-handle.ne { right: -6px; top: -6px; cursor: nesw-resize; }
.op-handle.e  { right: -6px; top: 50%; margin-top: -5.5px; cursor: ew-resize; }
.op-handle.se { right: -6px; bottom: -6px; cursor: nwse-resize; }
.op-handle.s  { left: 50%; bottom: -6px; margin-left: -5.5px; cursor: ns-resize; }
.op-handle.sw { left: -6px; bottom: -6px; cursor: nesw-resize; }
.op-handle.w  { left: -6px; top: 50%; margin-top: -5.5px; cursor: ew-resize; }
.op-bar {
  width: min(620px, calc(100% - 40px));
  margin: 10px auto 22px;
  padding: 8px 10px;
  display: flex; align-items: center; gap: 10px;
  background: rgba(34,35,41,.94);
  border: 1px solid rgba(255,255,255,.13);
  border-radius: 999px;
  box-shadow: 0 14px 40px rgb(0 0 0 / .45);
}
.op-bar-spacer { flex: 1; }
.op-close {
  width: 34px; height: 34px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%;
  background: transparent; color: rgba(255,255,255,.8);
  cursor: pointer;
}
.op-close:hover { background: rgba(255,255,255,.1); color: #fff; }
.op-close:disabled { opacity: .4; cursor: default; }
.op-close svg { width: 17px; height: 17px; }
.op-model-wrap {
  position: relative; display: flex; align-items: center;
  min-width: 0; max-width: 250px;
}
.op-model-wrap::after {
  content: '\2304'; position: absolute; right: 10px; top: 50%;
  transform: translateY(-56%);
  color: rgba(255,255,255,.55); font-size: 14px; pointer-events: none;
}
.op-model {
  width: 100%; min-width: 120px; height: 34px;
  padding: 0 30px 0 12px;
  appearance: none; -webkit-appearance: none;
  border: 1px solid transparent; border-radius: 999px;
  background: rgba(255,255,255,.07);
  color: rgba(255,255,255,.9);
  font: inherit; font-size: 12px; text-overflow: ellipsis;
  outline: none; cursor: pointer;
}
.op-model:hover { background: rgba(255,255,255,.11); }
.op-model:focus { border-color: rgba(255,255,255,.28); }
.op-model option { color: #111; background: #fff; }
.op-model:disabled { cursor: default; opacity: .65; }
.op-run {
  height: 34px; flex: none;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0 16px;
  border: none; border-radius: 999px;
  background: #fff; color: #111;
  font: inherit; font-size: 12px; font-weight: 700;
  cursor: pointer;
  transition: transform .12s ease, opacity .12s ease;
}
.op-run:hover:not(:disabled) { transform: translateY(-1px); }
.op-run:disabled { opacity: .45; cursor: default; }
.op-run.busy { opacity: .7; }
.op-run svg { width: 14px; height: 14px; }
@media (max-width: 620px) {
  .op-top { padding: 14px 14px 0; }
  .op-heading span { display: none; }
  .op-stage { margin: 6px 10px 0; }
  .op-bar { width: calc(100% - 20px); margin-bottom: 12px; gap: 6px; }
  .op-model-wrap { max-width: 180px; }
  .op-run { padding: 0 12px; }
}

/* ---------- canvas board menus ---------- */
.context-menu.ctx-board { min-width: 216px; }
.context-menu.ctx-board-dbl { min-width: 238px; }
.ctx-section {
  padding: 9px 12px 4px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .08em;
  color: var(--faint);
  user-select: none;
}
.ctx-section:first-child { padding-top: 4px; }
.ctx-item.ctx-parent { padding-right: 10px; }
.ctx-subwrap { position: relative; }
.context-menu .ctx-chevron {
  width: 13px;
  height: 13px;
  margin-left: auto;
  color: var(--faint);
}
.ctx-submenu {
  position: absolute;
  left: calc(100% - 4px);
  top: -6px;
  z-index: 2;
  display: none;
  min-width: 224px;
  padding: 6px;
  border-radius: 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: 0 16px 44px var(--shadow);
  backdrop-filter: blur(16px);
}
.context-menu.ctx-flip-sub .ctx-submenu {
  left: auto;
  right: calc(100% - 4px);
}
.ctx-subwrap.open > .ctx-submenu { display: block; }
.ctx-subwrap.open > .ctx-item { background: var(--soft-2); }
.ctx-submenu .ctx-item { padding: 9px 12px; }

