/* ══════════════════════════════════════════════════════════════════════════
   Canvas 2.0 —— 满屏画布外壳 + 节点/面板（P0-b + P0-c，2026-08-20）

   🔴🔴 功能隔离铁律：这个文件里【每一条选择器】都以
      .pai--canvas / .pcmain / .pcbody / .ic… 开头，全部是新造的名字
      （上线前 grep 过全站 styles + components + index.html，零命中）。
      → 现有 63 个 template + 11 个工具页【一条规则都吃不到】。

   ⚠️ 加载顺序：这份必须排在 propai.css 和 mobile.css 【之后】——
      .pai--canvas 和 .pai 都是【一个类】，特异度打平，靠源码顺序定胜负。
      index.html 里它是最后一条 <link>。

   ⚠️ 【不改】.pai / .pmain / .pside / .tabbar / .react-flow 本身。
   ══════════════════════════════════════════════════════════════════════════ */

/* ══ A. 外壳（P0-b）══════════════════════════════════════════════════ */

/* .pai 原本是 `min-height: 100vh`（可以往下长）。画布要的是相反的东西：
   【恰好一屏，不许长】。
   🔴 用 dvh 不用 vh：100vh 取的是最大那一档（== 100lvh），地址栏可见时
      比实际视口高 —— 表现就是「画布满屏了，页面却还能往下拽一点」。
   ⚠️ .pai 的 grid-template-columns 不动：226px / ≤1024px 200px /
      ≤860px 单列，三档原样继承。 */
.pai--canvas {
  min-height: 0;
  height: 100dvh;
  overflow: hidden;
}

/* ⚠️ mobile.css 在 ≤860px 已把 .pside 改成 height:100dvh 的抽屉；
   这条只影响 861px 以上那两档（那里是 100vh），补掉几像素的缝。 */
.pai--canvas .pside {
  height: 100%;
}

/* 🔴 第一行写 auto 不是写死 62px：mobile.css 在 ≤860px 把 .ptop 改成
   height:auto / min-height:58px / padding-top:calc(8px + safe-area)。
   auto 让它自报高度 —— 这样一条媒体查询都不用写。
   🔴 grid-template-columns 也必须写 minmax(0,1fr)。实测坑（375px）：
      只声明 rows 的话隐式列轨道是 auto，被 .ptop__right（204px）按
      max-content 撑到 382px，顶栏右侧被裁 7px（头像被切）。
      ⚠️ 页面级横向滚动【不会】出现，只看 scrollWidth 发现不了。 */
.pcmain {
  min-width: 0;
  height: 100dvh;
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
}

/* grid 子项默认 min-width:auto，会顶破轨道。两道都要。 */
.pcmain > * { min-width: 0; }

.pcbody { min-width: 0; min-height: 0; overflow: hidden; }

/* ⚠️ 顶上留 1px 分隔线，否则深色画布和白色顶栏直接相接，
   顶栏的 sticky 阴影没有落脚点。 */
.c2canvas--full {
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  overflow: hidden;
  background: #0e0f12;
}

/* React Flow 要求容器有确定宽高（官方 FAQ 第一条）。 */
.c2canvas--full > .react-flow { width: 100%; height: 100%; }

/* 🔴 手机底部有 PSidebar 渲染的 fixed tabbar（58px，z-index 120）。
   别的页面靠 `.pai .pmain{padding-bottom}` 让位，我们没有 .pmain ——
   画布保持满屏贴边，只把三个浮动挂件抬上去。
   🔴 `58px + var(--m-safe-b)` 照抄 mobile.css:225 的 .tabbar 高度表达式，
      不另写数字；15px 是 React Flow 自己的默认边距。 */
@media (max-width: 860px) {
  .c2canvas--full .react-flow__controls,
  .c2canvas--full .react-flow__minimap,
  .c2canvas--full .react-flow__attribution {
    bottom: calc(58px + var(--m-safe-b) + 15px);
  }
}

/* ══ B. 节点（P0-c）══════════════════════════════════════════════════ */

/* ⚠️ 深色画布上的一套独立配色 —— 【不复用】 .pai 的 --ink/--paper：
   那套是给白底工作区调的，直接搬到 #0e0f12 上对比度全部不达标。
   🔴🔴 这五个变量【一共声明在三处】，值必须逐字相同，改一处就得改三处：
        .c2node        ← 就是这里          节点及其后代
        .c2ref--float  ← 本文件 P1-c 那段  两个浮动菜单
        .c2canvas      ← 本文件 P1-d 那段  画布本身 + 不在节点里的浮层
      （谁近谁生效，值一样所以结果一样；分三处是为了各自作用域清楚。）
   ⚠️ 原话是「作用域限定在 .c2node / .c2panel，出不去」—— P1-c/P1-d 之后
      不再准确：最外圈已经到 .c2canvas 了。但仍然【出不了画布】，
      画布之外的页面拿不到这五个变量。 */
.c2node {
  --c2b: #17191d;      /* 卡片底 */
  --c2l: #2a2e35;      /* 描边   */
  --c2t: #e8eaed;      /* 主文字 */
  --c22: #9aa0a8;      /* 次文字 */
  --c2g: #d9a441;      /* 金（跟站点主色一致） */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  font-family: var(--font-ui);
  color: var(--c2t);
}

.c2node__tag {
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c22);
  padding-left: 2px;
}

/* 🔴 宽高由 JS 按比例算好后写在 style 上（_c2NodeSize），这里只管外观。
   固定的是【面积】不是宽或高 —— 固定宽的话 21:9 会矮成一条、9:16 会高
   得离谱，同一张画布上大小差三倍，看着像 bug。 */
.c2node__body {
  position: relative;
  display: grid;
  place-items: center;
  border: 1px solid var(--c2l);
  border-radius: 12px;
  background: var(--c2b);
  overflow: hidden;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}

.c2node.is-sel .c2node__body {
  border-color: var(--c2g);
  box-shadow: 0 0 0 1px var(--c2g);
}

.c2node__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.c2node__ph  { font-size: 26px; color: #3a3f47; }
.c2node__err { font-size: 11.5px; line-height: 1.5; color: #ff8a80; padding: 0 12px; text-align: center; }

.c2node.is-failed .c2node__body { border-color: #6b2f2b; }
.c2node.is-generating .c2node__body { border-color: var(--c2g); }

.c2node__spin {
  font-size: 22px;
  color: var(--c2g);
  animation: c2spin 900ms linear infinite;
}
@keyframes c2spin { to { transform: rotate(360deg); } }

/* 端口。⚠️ 8px 太小，触屏点不中；12px + 透明外扩到 20px 的命中区。 */
.c2handle {
  width: 12px;
  height: 12px;
  border: 2px solid var(--c2b);
  background: var(--c2g);
  border-radius: 50%;
}

/* ══ C. 提示词面板（选中时在节点下方展开）══════════════════════════ */

.c2panel {
  width: 300px;
  padding: 9px;
  border: 1px solid var(--c2l);
  border-radius: 12px;
  background: rgba(20, 22, 26, 0.97);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* 🔴 S3（2026-09-13）：提示词框上方的标签页行。
   ⚠️ 只【新增】这一组类；.c2panel / .c2panel__ta / .c2bay__refs 一个字节没动。
   ⚠️ 灰标签的暗度 0.42 跟 .c2add__row.is-off 一致（同一套表达，不另调）。 */
/* 🔴 常驻高度是【硬成本】：.c2bay 是 fixed 浮层，它高多少就从画布底部
   吃掉多少 —— A/B 实测 53px 的常驻增高会把底部节点的 ⊕ 整个盖住。
   所以收起状态只有一行 24px 的标签行（+ .c2panel 的 8px gap = 32px），
   「即将推出」并进这一行，不另占一行。 */
.c2tabs { display: flex; flex-direction: column; gap: 6px; }
.c2tabs__row { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; }
.c2tabs__t {
  flex: none;
  min-height: 24px;
  padding: 3px 9px;
  border: 1px solid var(--c2l);
  border-radius: 999px;
  background: #101216;
  color: var(--c2t);
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1.4;
  cursor: pointer;
}
.c2tabs__t:hover:not(:disabled) { border-color: var(--c2g); }
.c2tabs__t.is-on { border-color: var(--c2g); color: var(--c2g); }
.c2tabs__t.is-off { opacity: 0.42; cursor: not-allowed; }
.c2tabs__t.is-off:hover { border-color: var(--c2l); }
.c2tabs__soon {
  flex: none;
  margin-left: 2px;
  color: var(--c22);
  font-family: var(--font-ui);
  font-style: normal;
  font-size: 9.5px;
  line-height: 1.35;
}
.c2tabs__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--c2l);
  border-radius: 10px;
  background: rgba(16, 18, 22, 0.97);
}
.c2tabs__up {
  align-self: flex-start;
  min-height: 30px;
  padding: 5px 12px;
  border: 1px solid var(--c2g);
  border-radius: 8px;
  background: #101216;
  color: var(--c2g);
  font-family: var(--font-ui);
  font-size: 11.5px;
  cursor: pointer;
}
.c2tabs__up:hover { background: rgba(255, 255, 255, 0.06); }
.c2tabs__spec,
.c2tabs__tip {
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 10px;
  line-height: 1.45;
}

.c2panel__ta {
  width: 100%;
  box-sizing: border-box;
  resize: none;
  padding: 7px 8px;
  border: 1px solid var(--c2l);
  border-radius: 8px;
  background: #101216;
  color: var(--c2t);
  font: inherit;
  font-size: 12.5px;
  line-height: 1.5;
}
.c2panel__ta::placeholder { color: #5d636b; }
.c2panel__ta:focus { outline: none; border-color: var(--c2g); }

.c2panel__row { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.c2panel__mwrap { position: relative; display: inline-flex; }

.c2chip {
  appearance: none;
  -webkit-appearance: none;
  padding: 4px 8px;
  border: 1px solid var(--c2l);
  border-radius: 7px;
  background: #101216;
  color: var(--c2t);
  font: inherit;
  font-size: 11.5px;
  line-height: 1.5;
  cursor: pointer;
  max-width: 132px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.c2chip:hover { border-color: #3d434c; }
.c2chip--model { font-weight: 600; }

.c2panel__cost {
  margin-left: auto;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--c2g);
}
.c2panel__cost::before { content: '◉ '; }

.c2go {
  width: 26px;
  height: 26px;
  flex: none;
  border: 0;
  border-radius: 50%;
  background: var(--c2g);
  color: #17181a;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.c2go:hover { background: #f0cd82; }

/* ══ D. 模型下拉 ════════════════════════════════════════════════════ */

/* ⚠️ 绝对定位 + z-index：面板本身在节点里，下拉必须浮在同级节点上面。
   React Flow 的节点默认 z-index 是 0，所以这里给一个够大的值。 */
.c2model {
  position: absolute;
  top: calc(100% + 5px);
  left: 0;
  z-index: 30;
  width: 272px;
  max-height: 250px;
  overflow-y: auto;
  padding: 5px;
  border: 1px solid var(--c2l);
  border-radius: 11px;
  background: rgba(23, 25, 29, 0.99);
  box-shadow: 0 18px 42px -14px rgba(0, 0, 0, 0.75);
}

.c2model__row {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 8px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--c2t);
  font: inherit;
  cursor: pointer;
}
.c2model__row:hover { background: #23262c; }
.c2model__row.is-on { background: #23262c; }

/* 🔴 买不起的【显示但锁住】—— 藏起来最糟：用户不知道有更好的模型存在，
   也不知道为什么别人的图更好。 */
.c2model__row.is-locked { cursor: not-allowed; opacity: 0.45; }
.c2model__row.is-locked:hover { background: transparent; }

.c2model__top { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
.c2model__top b { font-weight: 600; }

.c2model__new {
  font-style: normal;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 1px 5px;
  border-radius: 999px;
  background: #29e0a6;
  color: #06281d;
}
.c2model__lock { font-style: normal; color: #ff8a80; font-size: 12px; }
.c2model__tick { font-style: normal; margin-left: auto; color: var(--c2g); }

.c2model__sub { display: flex; align-items: center; gap: 5px; margin-top: 3px; }
.c2model__sub i { font-style: normal; font-size: 9.5px; }

/* 最高分辨率徽章 —— 录像里每一行模型下面都有这个。 */
.c2model__res {
  padding: 1px 5px;
  border: 1px solid var(--c2l);
  border-radius: 4px;
  color: var(--c22);
}
.c2model__cr { color: var(--c2g); }
.c2model__cr::before { content: '◉ '; }
/* ⚠️ 「没有成功记录」不等于「坏了」，文案用 untested 不用 broken。 */
.c2model__warn { color: #d9a441; opacity: 0.8; }

.c2model__up {
  display: block;
  margin-top: 3px;
  font-size: 9.5px;
  color: var(--c22);
}

/* 🔴 提交失败的文案就显示在面板里，不要只丢进 console。
   后端 402 / 403 / 400 各有各的说法（积分不够 / 档位不够 / 这个模型要
   参考图 / 上游还没出图），翻成一句「失败了」等于把用户能自己解决的
   问题藏起来。 */
.c2panel__err {
  font-size: 11px;
  line-height: 1.5;
  color: #ff8a80;
  padding: 0 2px;
}

/* ══ E. 完成后的紧凑栏 ══════════════════════════════════════════════
   🔴 done 之后面板不收起，留这一条只读的。它是节点式工作流的卖点本身：
      「点开任意一张图，当初用什么模型、写什么提示词都能找到」。 */
.c2bar {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 300px;
  box-sizing: border-box;
  padding: 5px 7px;
  border: 1px solid var(--c2l);
  border-radius: 9px;
  background: rgba(20, 22, 26, 0.92);
  font-size: 10.5px;
}
.c2bar__p {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--c22);
}
.c2bar i { font-style: normal; flex: none; }
.c2bar__q, .c2bar__m { color: var(--c22); }
.c2bar__c { color: var(--c2g); }
.c2bar__c::before { content: '◉ '; }
.c2bar__go {
  flex: none;
  width: 20px;
  height: 20px;
  border: 1px solid var(--c2l);
  border-radius: 50%;
  background: transparent;
  color: var(--c2t);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
}
.c2bar__go:hover { border-color: var(--c2g); color: var(--c2g); }

/* 空画布时的一句提示（React Flow 的 <Panel position="top-center">）——
   否则用户面对一片黑色点阵，不知道要双击。 */
.c2hint {
  display: inline-block;
  padding: 5px 11px;
  border: 1px solid #2a2e35;
  border-radius: 999px;
  background: rgba(20, 22, 26, 0.9);
  color: #9aa0a8;
  font-family: var(--font-ui);
  font-size: 11.5px;
}

/* ⚠️ 手机上把面板收窄到卡片宽度附近，否则 300px 的面板比 9:16 的卡片
   (132px) 宽一倍多，视觉上像是面板拖着一张小图。
   ⚠️ 这条【不影响布局安全】—— 面板在 React Flow 的 transform 容器里，
      再宽也溢不出页面。纯粹是观感。 */
@media (max-width: 860px) {
  .c2panel, .c2bar { width: 250px; }
  .c2model { width: 232px; }
}

/* ══ C. 保存与加载（P0-e，2026-08-21）════════════════════════════════
   🔴 新增两个基名：.c2save（顶栏状态）和 .c2list（画布列表）。
      上线前 grep 过全站 styles + components + index.html，两个都零命中。
      ⚠️ 不能用 .ic[a-z]+ 那种模糊白名单去自查 —— 站上有个 .icon，
         会被误放行。基名要一个一个列。 */

/* ── 顶栏的保存状态 ────────────────────────────────────────────────────
   🔴 margin-left:auto 是这条规则的全部意义所在：.ptop 是
      justify-content:space-between，直接往里塞第四个孩子会被均匀铺开，
      chip 就飘在顶栏正中间。auto 边距先把剩余空间吃光，space-between
      就没得分配了 → [汉堡][面包屑] ………… [chip][右侧那一组]。
   ⚠️ 作用域写死 .pai--canvas .ptop —— PToolShell 那 12 个页面的 .ptop
      吃不到这一条（它们根本不渲染 .c2save，但作用域仍然要写死）。 */
.pai--canvas .ptop .c2save {
  flex: none;
  margin-left: auto;
  margin-right: 12px;
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-3);
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1.5;
  white-space: nowrap;
}
.pai--canvas .ptop .c2save.is-dirty { color: #8a6d10; border-color: #e6d9a8; }
.pai--canvas .ptop .c2save.is-saved { color: #26694a; border-color: #bfe0cd; }
.pai--canvas .ptop .c2save.is-error,
/* 🔴 loaderr = 「打不开」，跟 is-error（保存失败）分开的状态，同一套红色。 */
.pai--canvas .ptop .c2save.is-loaderr { color: #a82d24; border-color: #f0c4c0; }

/* 🔴 ≤860px 必须把 margin-left:auto 撤掉。
   mobile.css:1963 给 .ptop__right 也加了 margin-left:auto —— flexbox 会把
   剩余空间【平分】给所有 auto 边距，两个 auto 的结果是 chip 停在中间偏左。
   撤掉之后由 .ptop__right 那个 auto 独占，chip 紧跟在面包屑后面。 */
@media (max-width: 860px) {
  .pai--canvas .ptop .c2save { margin-left: 0; margin-right: 0; }
}

/* ≤520px 顶栏实在挤（.ptop__right 就 204px），只留一个圆点。
   ⚠️ 用 text-indent 挤出去而不是 display:none —— 这个元素是
      role="status" aria-live="polite"，读屏还要念它。 */
@media (max-width: 520px) {
  .pai--canvas .ptop .c2save {
    width: 9px;
    height: 9px;
    padding: 0;
    border-width: 0;
    border-radius: 50%;
    overflow: hidden;
    text-indent: -999px;
    background: var(--ink-3);
  }
  .pai--canvas .ptop .c2save.is-dirty { background: #d1a417; }
  .pai--canvas .ptop .c2save.is-saved { background: #26694a; }
  .pai--canvas .ptop .c2save.is-error,
  .pai--canvas .ptop .c2save.is-loaderr { background: #a82d24; }
}

/* ── 我的画布列表 ──────────────────────────────────────────────────────
   ⚠️ 这一屏在【白底】工作区上（.pcbody 没有背景色，继承 .pai），所以用
      站点自己的 --ink/--line/--gold，【不用】上面那套 --c2* 深色 token
      —— 那套是给 #0e0f12 的画布调的。 */
.c2list {
  height: 100%;
  overflow: auto;
  padding: 22px 30px 40px;
  -webkit-overflow-scrolling: touch;
}

/* 🔴 手机上底部有 PSidebar 渲染的 fixed tabbar（58px，z-index 120）。
   画布本体是靠把浮动挂件抬上去避让的；列表是可滚动内容，只能靠
   padding-bottom，否则最后一行卡片永远被压在 tabbar 底下点不到。
   ⚠️ 58px + safe-area 照抄 mobile.css:225 的 .tabbar 高度表达式。 */
@media (max-width: 860px) {
  .c2list { padding: 16px var(--m-pad) calc(58px + var(--m-safe-b) + 24px); }
}

.c2list__bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}
.c2list__h { font-size: 15px; font-weight: 600; color: var(--ink); }
.c2list__new {
  flex: none;
  margin-left: auto;
  padding: 7px 14px;
  border: 1px solid var(--gold);
  border-radius: 8px;
  background: var(--gold);
  color: #1b1b1a;
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.c2list__new:disabled { opacity: 0.55; cursor: default; }
.c2list__note { margin: 0; color: var(--ink-3); font-size: 13px; }
.c2list__err { margin: 0 0 12px; color: #a82d24; font-size: 13px; }

/* 面包屑第一段做成「回列表」的链接 —— 画布是满屏的，没有别的出口。 */
.c2list__back { color: inherit; text-decoration: none; }
.c2list__back:hover { color: var(--gold); text-decoration: underline; }

.c2list__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 16px;
}
/* grid 子项默认 min-width:auto，长名字会顶破轨道。 */
.c2list__item { position: relative; min-width: 0; }

.c2list__card {
  display: block;
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fff;
  color: inherit;
  text-decoration: none;
}
.c2list__card:hover { border-color: var(--gold); }

.c2list__thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: #f4f4f2;
}
.c2list__thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.c2list__ph { font-style: normal; font-size: 26px; color: #c8c8c4; }

.c2list__name {
  display: block;
  overflow: hidden;
  padding: 9px 11px 0;
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.c2list__meta {
  display: block;
  padding: 3px 11px 11px;
  color: var(--ink-3);
  font-size: 11.5px;
}

.c2list__del {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 24px;
  height: 24px;
  border: 0;
  border-radius: 50%;
  background: rgba(20, 22, 26, 0.62);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
}
.c2list__item:hover .c2list__del,
.c2list__del:focus { opacity: 1; }
/* 🔴 触屏没有 hover —— 不给这一条的话手机上永远删不掉画布。 */
@media (hover: none) {
  .c2list__del { opacity: 1; }
}

/* ══ D. 上传 / @ 引用 / 多张（P0-f，2026-08-21）══════════════════════
   🔴 新增四个基名：.c2nav（结果轮播）· .c2tool（画布工具条）
                    .c2dock（上传进度浮层）· .c2ref（@ 引用菜单）
      上线前 grep 过全站 styles + components + index.html，四个都零命中。
   ⚠️ 拖拽高亮【没有】新基名，用的是 .c2canvas--drop 修饰类。 */

/* ── 结果轮播（多张）────────────────────────────────────────────────
   ⚠️ 整块只在 .c2node__body 里出现，绝对定位，不占位 ——
      加了它不会改变节点的尺寸计算（_c2NodeSize 那套完全不受影响）。 */
.c2nav { position: absolute; inset: 0; pointer-events: none; }

.c2nav__btn {
  position: absolute;
  top: 50%;
  width: 22px;
  height: 30px;
  margin-top: -15px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: rgba(12, 13, 16, 0.62);
  color: #e8eaed;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: auto;
  transition: opacity 0.12s;
}
.c2node__body:hover .c2nav__btn { opacity: 1; }
.c2nav__btn:focus { opacity: 1; }
/* 🔴 触屏没有 hover —— 不给这一条的话手机上翻不了页。 */
@media (hover: none) { .c2nav__btn { opacity: 1; } }
.c2nav__btn--prev { left: 4px; }
.c2nav__btn--next { right: 4px; }

.c2nav__dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 6px;
  display: flex;
  gap: 5px;
  justify-content: center;
  pointer-events: auto;
}
.c2nav__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(232, 234, 237, 0.38);
  cursor: pointer;
}
.c2nav__dot.is-on { background: var(--c2g); }

/* 生成中且要多张：右上角显示 已完成/总数 */
.c2nav__count {
  position: absolute;
  top: 5px;
  right: 5px;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(12, 13, 16, 0.7);
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 10.5px;
}

/* 紧凑栏里的「N 张」 */
.c2bar__n { color: var(--c22); }

/* ── 画布工具条（添加节点菜单）──────────────────────────────────────
   ⚠️ 在 React Flow 的 <Panel position="top-left"> 里，所以定位由它负责，
      这里只管长相。 */
.c2tool {
  display: inline-flex;
  gap: 6px;
  padding: 5px;
  border: 1px solid #2a2e35;
  border-radius: 9px;
  background: rgba(20, 22, 26, 0.92);
}
.c2tool__btn {
  display: inline-flex;
  align-items: center;
  padding: 5px 10px;
  border: 1px solid #2a2e35;
  border-radius: 6px;
  background: #17191d;
  color: #e8eaed;
  font-family: var(--font-ui);
  font-size: 11.5px;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
}
.c2tool__btn:hover { border-color: var(--c2g); color: var(--c2g); }
/* label 包着一个视觉隐藏的 file input，行为跟按钮一致 */
.c2tool__btn--file { position: relative; }

/* ── 拖文件进画布时的高亮 ───────────────────────────────────────────
   🔴 用 inset 阴影而不是 border：加 border 会让容器尺寸变化，
      React Flow 的画布跟着重新测量，拖动过程中会抖。 */
.c2canvas--drop { box-shadow: inset 0 0 0 2px var(--c2g); }

/* ── 上传进度：屏幕底部浮层 ─────────────────────────────────────────
   🔴 fixed 定位。这条链上（.pai--canvas → .pcmain → .pcbody → .c2canvas）
      没有任何 transform / filter / backdrop-filter，所以 fixed 确实相对
      视口 —— 那三个属性中任意一个都会让它改为相对该祖先定位。 */
.c2dock {
  position: fixed;
  left: 50%;
  bottom: 22px;
  z-index: 130;
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: min(420px, calc(100vw - 40px));
  padding: 9px 11px;
  transform: translateX(-50%);
  border: 1px solid #2a2e35;
  border-radius: 10px;
  background: rgba(20, 22, 26, 0.95);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.45);
}
.c2dock__row {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  font-family: var(--font-ui);
  font-size: 11.5px;
  color: #e8eaed;
}
.c2dock__name {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 42%;
  overflow: hidden;
  font-weight: 500;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.c2dock__bar {
  flex: 1;
  height: 4px;
  min-width: 0;
  overflow: hidden;
  border-radius: 999px;
  background: #2a2e35;
}
.c2dock__bar i {
  display: block;
  height: 100%;
  background: var(--c2g);
  transition: width 0.15s linear;
}
.c2dock__pct { flex: none; color: #9aa0a8; font-style: normal; }
.c2dock__err {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: #ff8f85;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.c2dock__row.is-err .c2dock__name { color: #ff8f85; }

/* 🔴 手机上底部有 58px 的 tabbar —— 浮层要抬上去，否则压在它下面。
   ⚠️ 表达式照抄 mobile.css:225 的 .tabbar 高度，不另写数字。 */
@media (max-width: 860px) {
  .c2dock { bottom: calc(58px + var(--m-safe-b) + 14px); }
}

/* ── @ 引用菜单 ─────────────────────────────────────────────────────
   ⚠️ 长相跟 .c2model（模型下拉）保持一致 —— 同一个面板里两个弹出层
      风格不同会显得是两拨人做的。 */
.c2ref {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 5px);
  z-index: 12;
  max-height: 210px;
  overflow: auto;
  padding: 4px;
  border: 1px solid var(--c2l);
  border-radius: 8px;
  background: #14161a;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.5);
}
.c2ref__row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--c2t);
  font-family: var(--font-ui);
  font-size: 11.5px;
  text-align: left;
  cursor: pointer;
}
.c2ref__row:hover { background: #1d2026; }
.c2ref__thumb {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  overflow: hidden;
  border: 1px solid var(--c2l);
  border-radius: 4px;
  background: #0e0f12;
}
.c2ref__thumb img { width: 100%; height: 100%; object-fit: cover; }
.c2ref__thumb i { font-style: normal; color: #4a4f57; font-size: 13px; }
.c2ref__warn {
  margin-left: auto;
  padding: 1px 5px;
  border: 1px solid var(--c2l);
  border-radius: 3px;
  color: var(--c22);
  font-style: normal;
  font-size: 9.5px;
}
.c2ref__empty { display: block; padding: 8px 7px; color: var(--c22); font-size: 11px; }

/* ══ E. 破图第三态 + 浮层遮挡（P0-f 修，2026-08-21）════════════════
   🔴 没有新基名：全部挂在既有的 c2node / c2tool / c2dock / c2canvas 下面。 */

/* ── 「这张图打不开了」──────────────────────────────────────────────
   ⚠️ 跟空态（▧）刻意长得【不一样】：空态是「你还没生成」，这个是
      「生成过、扣过钱，但图取不回来了」。长一样等于把两件事混在一起。 */
.c2node__broken {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: 100%;
  height: 100%;
  padding: 8px;
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 11px;
  text-align: center;
}
.c2node__brokenIcon { font-style: normal; font-size: 19px; color: #7a5c5c; }
.c2node__broken b { font-weight: 500; color: #c9a3a3; }
.c2node__retry {
  padding: 3px 10px;
  border: 1px solid var(--c2l);
  border-radius: 5px;
  background: transparent;
  color: var(--c2t);
  font-family: var(--font-ui);
  font-size: 10.5px;
  cursor: pointer;
}
.c2node__retry:hover { border-color: var(--c2g); color: var(--c2g); }

/* ── 🔴 浮层遮挡节点 ────────────────────────────────────────────────
   参考录像里那个画布的做法（抽帧确认过）：
     · 左侧竖排面板 = 半透明圆角面板，【背后的节点透出来看得见】
     · 底部工具条  = 半透明圆角胶囊，画布的点阵从上边缘透过去
     · 【不留安全边距】—— 浮层就是浮在内容上
   为什么不留边距：节点可以拖到任何位置，无限画布上「安全边距」这个概念
   不成立 —— 用户平移一下就把边距用光了。Figma / Miro / ComfyUI 也都是
   浮层压内容 + 用户自己平移。

   🔴 所以这里做两件事，第二件才是真正解决问题的：
     ① 半透明 + 毛玻璃 —— 至少【看得见】底下压着什么
     ② 容器 pointer-events:none，只有按钮本身 auto
        → 面板的内边距、按钮之间的缝隙【不再拦截点击】，
          压在缝里的节点仍然点得到、拖得动。
   ⚠️ 毛玻璃只能加在【浮层自己】身上。加到 .c2canvas--full 或 .pcbody 上会
      让它成为 position:fixed 的包含块，.c2dock 会当场错位（这条在 D 段
      注释里已经写过一次）。 */
.c2tool {
  pointer-events: none;
  background: rgba(20, 22, 26, 0.72);
  -webkit-backdrop-filter: blur(9px);
  backdrop-filter: blur(9px);
}
.c2tool__btn {
  pointer-events: auto;
  background: rgba(23, 25, 29, 0.86);
}

/* 上传浮层是【纯状态显示】，没有任何可点的东西 —— 整块不拦点击。 */
.c2dock {
  pointer-events: none;
  background: rgba(20, 22, 26, 0.82);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

/* React Flow 自己的左下控件 / 右下缩略图：同样半透明 + 毛玻璃，
   ⚠️ 【不动】它们的 pointer-events —— 那两个整块都是要点的，
      改了会把缩放按钮和小地图拖动一起弄坏。
   ⚠️ 作用域写死 .c2canvas--full，站上别处没有 React Flow。 */
.c2canvas--full .react-flow__controls,
.c2canvas--full .react-flow__minimap {
  -webkit-backdrop-filter: blur(9px);
  backdrop-filter: blur(9px);
  border-radius: 8px;
  overflow: hidden;
}
.c2canvas--full .react-flow__controls { background: rgba(20, 22, 26, 0.72); }
.c2canvas--full .react-flow__minimap { background: rgba(20, 22, 26, 0.62); }

/* 🔴🔴 承载工具条的那个 <Panel> 容器也要放开点击 —— 它是个透明定位盒子，
   铺在左上角，不放开的话「缝隙不拦点击」就只做了一半。
   ⚠️⚠️ 【绝对不能】写成 .react-flow__panel{pointer-events:none}：
      实测 DOM，Controls 和 MiniMap 自己也带这个类 ——
        react-flow__panel react-flow__controls vertical bottom left
        react-flow__panel react-flow__minimap bottom right
      那样写会把缩放按钮和小地图一起变成点不动的。
      所以给自己的 Panel 挂一个 c2tool__wrap 类，只作用在它身上。 */
.c2tool__wrap { pointer-events: none; }

/* ══════════════════════════════════════════════════════════════════════
   P1 画布工具（裁剪 / 旋转）—— 纯追加，上面一行都没动。
   两个新前缀：
     c2tl    节点上的工具条（旋转 / 裁剪）
     c2crop  裁剪弹层
   ⚠️ 复用 .c2node 上那几个变量是不行的 —— 弹层挂在 .c2canvas 下、
      不在任何 .c2node 里面，继承不到。所以这里自己写死同样的色值。
   ══════════════════════════════════════════════════════════════════════ */

/* ── 节点工具条 ────────────────────────────────────────────────────────
   🔴 平时半透明、hover / 选中才实体化：画布上十几个节点，每个常驻两个
      亮按钮的话，画面全是按钮，图反而看不见了。
   ⚠️ 必须挂 .nodrag（在 JSX 里加的）—— 不加的话按住按钮会变成拖节点。 */
.c2tl {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 3;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.14s ease;
}
.c2node:hover .c2tl,
.c2node.is-sel .c2tl { opacity: 1; }

.c2tl__btn {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px 7px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 5px;
  background: rgba(12, 13, 16, 0.72);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: #e8eaed;
  font-family: var(--font-ui);
  font-size: 10.5px;
  line-height: 1.25;
  white-space: nowrap;
  cursor: pointer;
}
.c2tl__btn:hover { border-color: #d9a441; color: #d9a441; }
.c2tl__btn[disabled] { opacity: 0.45; cursor: default; }
.c2tl__btn i { font-style: normal; font-size: 12px; line-height: 1; }

/* ── 裁剪弹层 ──────────────────────────────────────────────────────────
   🔴 position:fixed 铺满视口。⚠️ 跟 .c2dock 同一个理由能成立：
      .pcmain / .pcbody / .c2canvas 这条祖先链上没有 transform / filter /
      backdrop-filter（那三个任意一个都会让 fixed 改成相对该祖先定位）。 */
.c2crop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(8, 9, 11, 0.72);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.c2crop__panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 1040px;
  height: 100%;
  max-height: 760px;
  border: 1px solid #2a2e35;
  border-radius: 12px;
  background: #17191d;
  overflow: hidden;
}

.c2crop__head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 10px 12px;
  border-bottom: 1px solid #2a2e35;
}
.c2crop__title {
  color: #e8eaed;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
}
.c2crop__name { color: #9aa0a8; font-style: normal; font-weight: 400; }
.c2crop__spacer { flex: 1 1 auto; }

.c2crop__ars { display: flex; gap: 4px; margin-left: 6px; }
.c2crop__ar {
  padding: 3px 8px;
  border: 1px solid #2a2e35;
  border-radius: 5px;
  background: #101216;
  color: #9aa0a8;
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1.25;
  cursor: pointer;
}
.c2crop__ar:hover { color: #e8eaed; }
.c2crop__ar.is-on { border-color: #d9a441; color: #d9a441; }

.c2crop__btn {
  padding: 5px 12px;
  border: 1px solid #2a2e35;
  border-radius: 6px;
  background: #101216;
  color: #e8eaed;
  font-family: var(--font-ui);
  font-size: 11.5px;
  line-height: 1.2;
  cursor: pointer;
}
.c2crop__btn:hover { border-color: #d9a441; color: #d9a441; }
.c2crop__btn--go {
  border-color: #d9a441;
  background: #d9a441;
  color: #14161a;
  font-weight: 600;
}
.c2crop__btn--go:hover { background: #e6b354; color: #14161a; }
.c2crop__btn[disabled] { opacity: 0.45; cursor: default; }

/* 舞台。⚠️ min-height:0 是必须的：不写的话 flex 子项的默认 min-height:auto
   会让它按内容撑开，图片再大都不会缩，整个面板被顶出屏幕。 */
.c2crop__stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px;
  background:
    repeating-conic-gradient(#1b1e23 0% 25%, #15181c 0% 50%) 50% / 18px 18px;
}
.c2crop__frame { position: relative; user-select: none; }
.c2crop__img {
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
  -webkit-user-drag: none;
}
.c2crop__load,
.c2crop__err {
  color: #9aa0a8;
  font-family: var(--font-ui);
  font-size: 12px;
  text-align: center;
  max-width: 34em;
  line-height: 1.6;
}
.c2crop__err { color: #e0806f; }

/* 裁剪框。🔴 外面那圈阴影就是「被裁掉的部分」的表现 ——
   用 box-shadow 铺一层半透明黑比再叠四个遮罩 div 干净得多。 */
.c2crop__box {
  position: absolute;
  box-sizing: border-box;
  border: 1px solid rgba(255, 255, 255, 0.92);
  box-shadow: 0 0 0 9999px rgba(6, 7, 9, 0.55);
  cursor: move;
}
.c2crop__h {
  position: absolute;
  width: 14px;
  height: 14px;
  background: #d9a441;
  border: 1px solid #14161a;
  border-radius: 2px;
}
.c2crop__h--nw { left: -7px; top: -7px; cursor: nwse-resize; }
.c2crop__h--ne { right: -7px; top: -7px; cursor: nesw-resize; }
.c2crop__h--sw { left: -7px; bottom: -7px; cursor: nesw-resize; }
.c2crop__h--se { right: -7px; bottom: -7px; cursor: nwse-resize; }

.c2crop__foot {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border-top: 1px solid #2a2e35;
  font-family: var(--font-ui);
  font-size: 11px;
}
.c2crop__meta { color: #e8eaed; }
.c2crop__note { color: #9aa0a8; margin-left: auto; }

/* 窄屏：面板铺满，比例按钮换行，四角手柄放大一点（手指比鼠标粗）。 */
@media (max-width: 640px) {
  .c2crop { padding: 0; }
  .c2crop__panel { max-height: none; border: 0; border-radius: 0; }
  .c2crop__h { width: 18px; height: 18px; }
  .c2crop__h--nw { left: -9px; top: -9px; }
  .c2crop__h--ne { right: -9px; top: -9px; }
  .c2crop__h--sw { left: -9px; bottom: -9px; }
  .c2crop__h--se { right: -9px; bottom: -9px; }
  .c2crop__note { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════
   P1-a 拖线把手做成 ⊕（2026-08-22）—— 纯追加，上面一行都没动。
   新前缀：没有。全部是给现成的 .c2handle / .c2tl 加规则。
   ══════════════════════════════════════════════════════════════════════ */

/* 🔴 【常显】—— 不挂 hover 门。
   触屏没有 hover，而 (hover:none) 这条媒体查询在开发环境里量不出来
   （宿主是鼠标，iframe 继承宿主的指针能力）。所以干脆【不依赖它】：
   opacity 恒为 1，任何设备上都看得见。hover 只让它更明显，不决定它在不在。
   ⚠️ 这是本轮唯一一条「用设计绕开量不到的东西」的决定，不是漏验。

   🔴 用 .c2canvas--full 前缀提高优先级（0,2,0 > 0,1,0），保证盖过
      上面 :147 那条 .c2handle，跟文件里的先后顺序无关。 */
.c2canvas--full .c2handle {
  width: 20px;
  height: 20px;
  border: 1px solid rgba(217, 164, 65, 0.55);
  background: rgba(20, 22, 26, 0.92);
  opacity: 1;
  transition: width 0.12s ease, height 0.12s ease,
              border-color 0.12s ease, background-color 0.12s ease;
}

/* ⊕ 的两笔。🔴 用伪元素画，【不往 Handle 里塞 children】——
   Handle 是 React Flow 渲染的，塞进去的子元素还得单独设
   pointer-events:none，否则它会把拖拽起点吃掉。伪元素天生不吃事件。 */
.c2canvas--full .c2handle::before,
.c2canvas--full .c2handle::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  background: var(--c2g, #d9a441);
  border-radius: 1px;
  pointer-events: none;
}
.c2canvas--full .c2handle::before { width: 9px; height: 1.5px; margin: -0.75px 0 0 -4.5px; }
.c2canvas--full .c2handle::after { width: 1.5px; height: 9px; margin: -4.5px 0 0 -0.75px; }

/* hover 到这个节点 / 选中 → 长大 + 变亮。
   🔴 长大【不会让边抖】：React Flow 给左把手写的是 left:0 + translate(-50%,-50%)，
      右把手是 right:0 + translate(50%,-50%)（实测 computed 值确认过）——
      两边的圆心都钉在节点边线上，跟宽高无关。边的端点取的是圆心，所以
      把手变大变小，连线一个像素都不动。
   ⚠️ 同一时刻只有一个节点被 hover，所以「两个 28px 的 ⊕ 撞在一起」不可能；
      最坏是 hover 的 14px + 邻居静止的 10px = 24px。 */
.c2node:hover .c2handle,
.c2node.is-sel .c2handle {
  width: 28px;
  height: 28px;
  border-color: var(--c2g);
  background: rgba(20, 22, 26, 0.98);
}
.c2node:hover .c2handle::before,
.c2node.is-sel .c2handle::before { width: 13px; margin-left: -6.5px; }
.c2node:hover .c2handle::after,
.c2node.is-sel .c2handle::after { height: 13px; margin-top: -6.5px; }

/* 连线过程中把手要更显眼一点 —— React Flow 会给能接的把手加
   .connectingto / .valid。⚠️ 只改颜色不改尺寸：拖拽中改尺寸会让
   正在跟手的那条线跳一下。 */
.c2canvas--full .c2handle.connectingto,
.c2canvas--full .c2handle.valid {
  border-color: #7ee2b8;
  background: rgba(20, 40, 32, 0.98);
}

/* 🔴 触屏：节点工具条常显（桌面仍然是 hover / 选中才出）。
   ⚠️ 这条在开发机上恒不生效（(hover:none) 不匹配），是给真机的。
      验证方式见交付说明：拿【真的这份 css】+ 真的类名，在开真触屏模拟的
      浏览器里量的。 */
@media (hover: none) {
  .c2tl { opacity: 1; }
}

/* 🔴 P1-a 修（实测）：四个按钮带文字时整条 227px，而最窄的节点只有
   143 flow px —— 会从节点左边溢出 28px 被裁掉。改成纯图标之后
   4×~27 + 3×4 = 约 120px，最窄的卡也塞得下。
   ⚠️ 纯追加：同名同优先级、排在后面，盖掉上面那条 padding。 */
.c2tl__btn { padding: 4px 6px; }
.c2tl__btn i { font-size: 13px; }

/* 🔴 P1-a 实测补丁：两个节点横向间隙 < 20 flow px 时，A 的右 ⊕ 和 B 的左 ⊕
   会重叠 —— 实测间隙 12px 时重叠 6px（20px 的圆各往外探 10px，20 是临界值）。
   把【正在指的那个节点】的把手提到最上层：视觉上还是叠着，但你瞄哪个就点中
   哪个，交互没有歧义。
   ⚠️ 同一时刻只有一个节点 hover/选中，所以不会出现两个都是 z-index:6。
   ⚠️ 节点之间没有各自的 stacking context（transform 在 viewport 上，不在节点上），
      所以这个 z-index 跨节点是可比的 —— 这条才成立。
   ⚠️ 【不做】拖动时自动吸附最小间距：那会夺走用户自己摆位置的自由，而且
      工具自动摆的节点本来就隔 56px，只有手动拖到一起才碰得上。 */
.c2node:hover .c2handle,
.c2node.is-sel .c2handle { z-index: 6; }

/* ══════════════════════════════════════════════════════════════════════
   P1-b 浮动菜单（+ Reference 面板 / 右键菜单）—— 纯追加，上面一行都没动。
   🔴 一个新基名都没有：全部挂在 .c2ref 上（@ 菜单那一套），
      只加两个修饰类 --float 和 __row--danger。
   🔴 P1-a 的 .c2handle / .c2tl 一个字节都没碰。
   ══════════════════════════════════════════════════════════════════════ */

/* .c2ref 原本是【贴在提示词面板上方】的 absolute（left:0 right:0
   bottom:calc(100%+5px)）。浮动版要跟着鼠标走，所以四个定位属性全要盖掉。
   ⚠️ z-index 用 40：要盖住画布(自然层)和节点，但要低于裁剪弹层(60)和
      放大灯箱(300) —— 那两个是模态，模态开着的时候这个菜单不该压在上面。 */
.c2ref--float {
  position: fixed;
  left: 0;
  right: auto;
  bottom: auto;
  z-index: 40;
  min-width: 180px;
  max-width: 260px;
  max-height: 300px;
}

/* 删除那一行标红。⚠️ 只改文字色，不改底色 —— 一整行红底在深色面板上
   太抢眼，而且它旁边三行是无害操作，视觉权重差太多反而容易误点。 */
.c2ref__row--danger { color: #e0806f; }
.c2ref__row--danger:hover { background: #2a1a18; }

/* 已经连过的那一行：点不动 + 变淡。
   ⚠️ 用 [disabled] 而不是再造一个类 —— 按钮本来就是 disabled 的，
      样式跟着状态走，不用前端再记一个类名。 */
.c2ref__row[disabled] { opacity: 0.45; cursor: default; }
.c2ref__row[disabled]:hover { background: transparent; }

/* 🔴 P1-b 实测补丁：多张节点上工具条变成【5 个】按钮，量到 143 flow px，
   而最窄的节点（9:16）正好也是 143 —— 一点余量都没有，必溢出。
   两手都上：
     ① 按钮再收 1px 内边距（26px → 24px，仍在能点的范围）
     ② 🔴 给工具条上宽度上限 + 允许换行 —— 这条才是【根治】的：
        以后再加第 6、第 7 个按钮，它自己换到第二行，不会再溢出一次。
   ⚠️ justify-content:flex-end —— 换行之后第二行要跟着右边线对齐，
      否则第二行会左对齐，跟上面那行错开，看着像坏了。 */
.c2tl {
  max-width: calc(100% - 12px);
  flex-wrap: wrap;
  justify-content: flex-end;
}
.c2tl__btn { padding: 4px 5px; }

/* ══════════════════════════════════════════════════════════════════════
   P1-c ① 浮动菜单的配色 —— 纯追加，上面一行都没动。

   🔴 病根不是「颜色写错了」，是【变量根本不在作用域里】：
      --c2b / --c2l / --c2t / --c22 / --c2g 只声明在 .c2node 上（本文件
      第 91 行那块），而 CSS 自定义属性是沿【DOM 树】继承的。P1-b 那两个
      浮动菜单（+ Reference 面板 / 右键菜单）渲染在 <Flow> 【外面】、
      挂在 .c2canvas 底下 —— 不是任何 .c2node 的后代，var(--c2t) 解不出来，
      color 这条声明就「在计算值时无效」，退回继承来的深色。
      实测（生产页真实层叠，.pai>.pcmain>.pcbody>.c2canvas 那条链）：
        字色 rgb(23,24,26) 压在底色 rgb(20,22,26) 上 → 对比度 1.02:1
      「删除 Delete」看得见，只是因为它写的是【字面色】#e0806f，绕开了变量。

   🔴 同一个病根一起坏掉的还有四条，全在这一处修好：
        .c2ref        border: 1px solid var(--c2l)  → 整条 border 失效 → 无边框
        .c2ref__thumb border                         → 缩略图无边框
        .c2ref__warn  color + border（「已连」那个标）→ 深色不可见
        .c2ref__empty color（「没有可选的」）         → 深色不可见

   🔴 只挂在 .c2ref--float 这个【修饰类】上，不动 .c2ref 本体：
      @ 引用菜单（C2RefMenu）在 .c2panel 里、.c2panel 在 .c2node 里，
      它一直拿得到这五个变量 —— 本来就是好的，动本体等于去修没坏的东西。
      实测本轮改动前后 @ 菜单的 5 个计算值一位不差。

   ⚠️ 这五个值跟第 91 行那份【逐字相同】，是刻意的：这两个菜单本来就该跟
      节点里的 @ 菜单长得一模一样。以后改配色两处要一起改。
   ══════════════════════════════════════════════════════════════════════ */
.c2ref--float {
  --c2b: #17191d;
  --c2l: #2a2e35;
  --c2t: #e8eaed;
  --c22: #9aa0a8;
  --c2g: #d9a441;
}

/* ══════════════════════════════════════════════════════════════════════
   P1-d ③：把五个变量补到 .c2canvas 上 —— 纯追加，上面一行都没动。

   🔴 修的是 P0-f 就带进来的三个 bug，全是同一个病根（元素在 .c2node
      【外面】却用 var(--c2*)，变量解不出来 → 那条声明在计算值时无效）：
        .c2canvas--drop     box-shadow 失效 → 拖文件进画布【没有金色高亮框】
        .c2dock__bar i      background 失效 → 上传进度条【只有灰槽没填充】
        .c2tool__btn:hover  border-color / color 失效 → 工具条【悬停没反馈】
      实测（同一个元素放进 / 放出 .c2node 做 A/B，生产页真实层叠）：
        .c2canvas--drop   外: none            内: rgb(217,164,65) 0 0 0 2px inset
        .c2dock__bar i    外: rgba(0,0,0,0)   内: rgb(217,164,65)

   🔴 为什么补变量而不是在这三处各自硬编码金色：它们要的本来就是同一个
      --c2g，硬编码等于把同一个颜色抄第四遍，下次改主色漏一处。

   ⚠️ 【不会外泄】—— 两个方向都确认过：
      · 往上：.c2canvas 全站只出现在画布根节点（29-Canvas2.js:1294，
        一处），自定义属性只往子树继承，画布之外的页面拿不到这五个变量。
      · 往下：.c2node 上有【同名同值】的一份，离得更近，节点子树里的
        计算值一位不变；变的只有上面那三处本来就解不出来的。

   ⚠️ 🔴 值必须跟本文件另外两处（.c2node / .c2ref--float）逐字相同，
      三处要一起改。
   ══════════════════════════════════════════════════════════════════════ */
.c2canvas {
  --c2b: #17191d;
  --c2l: #2a2e35;
  --c2t: #e8eaed;
  --c22: #9aa0a8;
  --c2g: #d9a441;
}

/* P1-d 乙：面板里的【提示】而不是【错误】—— 用户还什么都没做错，
   一进节点就看到一行红字会让人以为已经出事了。
   ⚠️ 排版整条沿用 .c2panel__err，这里只把颜色换成次要文字色。 */
.c2panel__err--hint { color: var(--c22); }

/* ══════════════════════════════════════════════════════════════════════
   P1-f ② 连线高亮 —— 纯追加，上面一行都没动。

   🔴 走的是 edge.className，不动 React Flow 的默认边组件：
      12.9.3 的边包装器把 edge.className 拼进 <g>（在
      vendor/xyflow-react-12.9.3.umd.js 里查过原文）：
        className: cc(["react-flow__edge", "react-flow__edge-<type>",
                       edge.className, ...])
      className 由 29-Canvas2.js 的 viewEdges 派生出来，
      【edges state 一个字节不动】—— 否则点一下节点就会触发一次存盘。

   ⚠️ 选择器权重：React Flow 自己那条是
        .react-flow__edge .react-flow__edge-path { stroke: var(--xy-edge-stroke, …) }
      两个类 = (0,2,0)。这里四个类 = (0,4,0)，稳压过去，不用 !important。

   ⚠️ 【故意不写 transition】：一是高亮本来就该是瞬时的；二是隐藏标签页里
      CSS transition 根本不推进，getComputedStyle 永远读到起始值，
      这一条上一轮已经骗过我一次，不再给它机会。
   ══════════════════════════════════════════════════════════════════════ */

/* 选中那个节点的【入边 + 出边】—— 金色 + 加粗 */
.c2canvas .react-flow__edge.c2edge--on .react-flow__edge-path {
  stroke: var(--c2g);
  stroke-width: 2.4;
}

/* hover 那个节点的边 —— 比选中【弱一档】：颜色暗、线也细一点。
   ⚠️ 两个类互斥（JS 那边 on 优先、else if 才给 hov），不会同时挂上。 */
.c2canvas .react-flow__edge.c2edge--hov .react-flow__edge-path {
  stroke: #8a7440;
  stroke-width: 1.8;
}

/* + Reference 选完那一瞬间：新连的这条闪一下。
   🔴 纯 CSS animation，跑一次自己停（没有 fill-mode）。JS 那边只设一个
      flashId，【没有 setTimeout 去清 state】。
   ⚠️ 最后一帧落回 React Flow 的默认灰：那条边如果没被选中，动画结束时是
      无缝的；如果它同时是选中节点的边，结束瞬间会用一帧灰再回到金色
      —— 16ms，肉眼看不到。 */
.c2canvas .react-flow__edge.c2edge--flash .react-flow__edge-path {
  animation: c2edgeflash 0.85s ease-out 1;
}

@keyframes c2edgeflash {
  0%   { stroke: #ffe9b0; stroke-width: 5; }
  45%  { stroke: var(--c2g); stroke-width: 3; }
  100% { stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default)); stroke-width: 1; }
}

/* ══════════════════════════════════════════════════════════════════════
   P1-f ④ 提交按钮 —— 纯追加。

   🔴 病根实测（真 React 渲染 + getBoundingClientRect）：
      .c2panel 是【固定 300px】，行内可用宽 280px。这一行的内容宽：
        没有画质下拉（模型 N）  265px  → 不换行，按钮在行尾，正常
        有画质下拉（模型 G）     292px  → 超了，flex-wrap 生效，
                                      .c2go 单独掉到第二行【最左边】
      —— 用户截图里「→ 单独占一行、在面板左下角」就是这么来的。
   🔴 修法：把「积分 + 按钮」包成 .c2panel__end 一组。要换行就整组一起换，
      margin-left:auto 仍然把它顶到右端 —— 按钮永远是行尾、永远紧挨积分。
   ⚠️ margin-left:auto 从积分身上移到组身上：不改 .c2panel__cost 本体那条
      规则（它可能还有别的用处），用一条更具体的选择器把它归零。
   ══════════════════════════════════════════════════════════════════════ */
.c2panel__end {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 5px;
  margin-left: auto;
}
.c2panel__end .c2panel__cost { margin-left: 0; }

/* 好看一点：金色实心圆不变，加一层很淡的金光 + 按下去会缩一下。
   ⚠️ 尺寸【一点没动】（还是 26×26）—— 面板宽度是死的 300px，
      按钮变大就是往「再挤出一行」的方向推。 */
.c2go {
  box-shadow: 0 1px 7px rgba(217, 164, 65, 0.32);
  /* 只过渡 transform。box-shadow 【故意不过渡】：实测在同源 iframe 里
     首帧量到的是 rgba(0,0,0,0) 0 0 0 —— 隐藏标签页里 transition 不推进，
     金光要等过渡跑完才出来。这条路上已经被坑过三次，不给它第四次。 */
  transition: transform 0.09s ease;
}
.c2go:active { transform: scale(0.92); }

/* 🔴 disabled 态。改之前【跟能按的时候长得一模一样】：
     实测 status='generating' 时 background rgb(217,164,65)、
     color rgb(23,24,26)、opacity 1、cursor pointer —— 一个字都看不出禁用。
   ⚠️ 还要单独盖掉 :hover：浏览器对 disabled 的按钮照样应用 :hover 样式，
      不盖的话鼠标一放上去它又变回亮金色，比不做还误导。 */
.c2go:disabled {
  background: #2f333b;
  color: #71767e;
  box-shadow: none;
  cursor: not-allowed;
}
.c2go:disabled:hover { background: #2f333b; }
.c2go:disabled:active { transform: none; }

/* ══════════════════════════════════════════════════════════════════════
   P1-g：屏幕底部的提示词栏 —— 纯追加，上面一行都没动。

   🔴 为什么搬出节点（P1-f ① 实测的数）：面板在节点里就吃
      .react-flow__viewport 的 scale(zoom)。zoom 0.3 时它只有 90px 宽，
      字全糊；反向缩放 scale(1/zoom) 又会让它变成节点的 6.99 倍宽、
      压住邻居 197px。钉在屏幕上是唯一两头都不占的做法。

   🔴 新基名只有一个：.c2bay（上线前 grep 过全站 + lumiq-shell，零命中）。
      ⚠️ 【不能】叫 .c2dock —— 那个已经被上传进度浮层占着了（本文件 D 段）。

   🔴 五个配色变量的作用域：底栏是 .c2canvas 的直接子节点，而
      .c2canvas 上有那五个变量（P1-d ③ 补的），所以 var(--c2t) / var(--c2l)
      / var(--c2g) 在这里全解得出来 —— 不会重演 P1-c 那个黑底黑字。
   ══════════════════════════════════════════════════════════════════════ */

.c2bay {
  position: fixed;
  left: 50%;
  bottom: 18px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 7px;
  /* 🔴 宽度不再写死 300px；也不在超宽屏上拉满 —— 一行提示词拉到 2000px
     宽读起来更累。 */
  width: min(720px, calc(100vw - 32px));
  transform: translateX(-50%);
}

/* 面板在底栏里：宽度交给底栏。
   ⚠️ 用后代选择器盖，【不动 .c2panel 本体那条规则】（本文件 :164）——
      本体还是 width:300px，万一以后又要在节点里用它，原样还在。
   🔴 position: relative 是必须的：@ 菜单和模型下拉都是 absolute 定位，
      得有个确定的定位祖先。不给的话它们会往上找到 .c2bay（fixed），
      left/right:0 就变成整条底栏那么宽，看着像 bug 其实是没锚点。 */
.c2bay .c2panel {
  position: relative;
  width: auto;
}

/* 🔴 模型下拉在底栏上要【向上弹】。
   本体是 top: calc(100% + 5px)（本文件 :242），底栏贴着屏幕底，
   往下弹就直接出屏幕了。这里翻过来。
   ⚠️ 同样是后代选择器，.c2model 本体一个字节没动 —— 节点里那份
      （如果哪天回去用）还是向下弹。 */
.c2bay .c2model {
  top: auto;
  bottom: calc(100% + 5px);
}

/* ⚠️ @ 引用菜单（.c2ref 本体，bottom: calc(100% + 5px)）【不用改】：
   它本来就是向上弹的，在底栏上正好在屏幕内。
   🔴 而且这里【一个字节都不碰 .c2ref 本体】—— + Reference 面板
      （.c2ref--float）跟它共用本体，改本体就会连带改到它。
      + Reference 是 position:fixed 挂在 .c2canvas 下的，根本不在 .c2bay
      里面，这几条后代选择器一条都命中不了它。 */

/* 参考图缩略图条。⚠️ 方框复用 .c2ref__thumb（@ 菜单那套 30×30），
   不新造缩略图样式。 */
.c2bay__refs {
  display: flex;
  align-items: center;
  gap: 5px;
  overflow-x: auto;
  padding: 6px 8px;
  border: 1px solid var(--c2l);
  border-radius: 10px;
  background: rgba(20, 22, 26, 0.97);
}
.c2bay__refsN {
  flex: none;
  margin-left: 2px;
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 10.5px;
  font-style: normal;
}

/* 「另有 N 个节点在生成」—— 可点，点了跳过去。 */
.c2bay__busy {
  display: inline-flex;
  align-self: center;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--c2l);
  border-radius: 999px;
  background: rgba(20, 22, 26, 0.97);
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 11px;
  cursor: pointer;
}
.c2bay__busy:hover { border-color: var(--c2g); color: var(--c2t); }
/* ⚠️ 复用现成的 @keyframes c2spin（本文件 :151，.c2node__spin 在用），
   不另写一个转圈动画。 */
.c2bay__spin {
  color: var(--c2g);
  font-style: normal;
  animation: c2spin 900ms linear infinite;
}

/* 没选中任何节点时的细提示条。
   🔴 为什么不是整个消失：底栏凭空出现/消失会让画面跳一下，而且用户会
      找不到「到底在哪写字」。留一条一行高的，位置就固定住了。 */
.c2bay--hint { align-items: center; }
.c2bay__hint {
  padding: 7px 14px;
  border: 1px solid var(--c2l);
  border-radius: 999px;
  background: rgba(20, 22, 26, 0.9);
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 11.5px;
}

/* ── 给底栏让位 ────────────────────────────────────────────────────────
   🔴 全部挂在 .c2canvas--bay 下面：没有底栏（画布上一个节点都没有）的时候
      这些规则一条都不生效，行为跟 P1-f 之前一个字节不差。
   ⚠️ --c2bay-h 是实测出来的底栏高度（见报告里的四档 zoom 表），
      写成变量是为了以后调高度只改一处。 */
/* 🔴 122px 是我最早拍脑袋写的，实测不够—— 带参考图条是 160px，
   再加「另有 N 个在生成」是 193px。现在真值由 C2Bay 的
   useLayoutEffect 量出来写回来（行内样式，压过这一条）。
   ⚠️ 这里只做【首帧兜底】，取最高那档 + 一点余量，
      宁可首帧抬高一点，也不要首帧压住缩放控件。 */
.c2canvas--bay { --c2bay-h: 200px; }

.c2canvas--full.c2canvas--bay .react-flow__controls,
.c2canvas--full.c2canvas--bay .react-flow__minimap,
.c2canvas--full.c2canvas--bay .react-flow__attribution {
  bottom: calc(var(--c2bay-h) + 26px);
}
/* 上传进度浮层本来 bottom:22px，正好被底栏盖住 —— 抬上去。
   ⚠️ .c2dock 本体那条规则没动。 */
.c2canvas--bay .c2dock { bottom: calc(var(--c2bay-h) + 30px); }

/* 手机：底部有 58px 的 tabbar（照抄本文件 :82 那段的表达式，不另写数字）。 */
@media (max-width: 860px) {
  .c2bay {
    bottom: calc(58px + var(--m-safe-b) + 12px);
    width: calc(100vw - 20px);
  }
  .c2canvas--full.c2canvas--bay .react-flow__controls,
  .c2canvas--full.c2canvas--bay .react-flow__minimap,
  .c2canvas--full.c2canvas--bay .react-flow__attribution {
    bottom: calc(58px + var(--m-safe-b) + var(--c2bay-h) + 26px);
  }
  .c2canvas--bay .c2dock { bottom: calc(58px + var(--m-safe-b) + var(--c2bay-h) + 30px); }
}

/* ══════════════════════════════════════════════════════════════════════
   比例 / 画质合并面板 —— 纯追加，上面一行都没动。

   🔴 合并之前是两个原生 <select>：8 个比例挤成一条 24px 行高的竖列。
      现在一个按钮点开一个两段式面板，比例走 4 列网格（8 个正好两行），
      每格 ≥46px 高，点得到。

   🔴 定位祖先是 .c2panel（P1-g 给底栏里的面板加过 position:relative），
      不是那个几十像素宽的 .c2panel__mwrap —— 所以 left:0 + width:min(...,100%)
      是按【整条面板】算的，375px 上天然不出屏。
   🔴 底栏钉在屏幕底部 → 面板必须【向上弹】。照 P1-g 给 .c2model 做过的
      那套：本体 top: calc(100% + 5px)，在 .c2bay 里翻成 bottom。
   ⚠️ 一个既有规则都没改：.c2chip / .c2model / .c2panel__row /
      .c2panel__end 全部原样。
   ══════════════════════════════════════════════════════════════════════ */
.c2chip--fmt { display: inline-flex; align-items: center; gap: 5px; }

.c2fmt {
  position: absolute;
  top: calc(100% + 5px);
  left: 0;
  z-index: 30;
  width: min(360px, 100%);
  box-sizing: border-box;
  padding: 10px;
  border: 1px solid var(--c2l);
  border-radius: 11px;
  background: rgba(23, 25, 29, 0.99);
  box-shadow: 0 18px 42px -14px rgba(0, 0, 0, 0.75);
}
/* 🔴 在底栏里向上弹 —— 底栏贴着屏幕底，往下弹就出屏幕了。 */
.c2bay .c2fmt { top: auto; bottom: calc(100% + 5px); }

.c2fmt__h {
  display: block;
  margin: 2px 0 6px;
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.c2fmt__h + * { margin-bottom: 10px; }

.c2fmt__qs { display: flex; gap: 6px; flex-wrap: wrap; }
.c2fmt__q {
  flex: 1 1 0;
  min-width: 56px;
  min-height: 34px;
  padding: 6px 8px;
  border: 1px solid var(--c2l);
  border-radius: 8px;
  background: #101216;
  color: var(--c2t);
  font-family: var(--font-ui);
  font-size: 11.5px;
  cursor: pointer;
}
.c2fmt__q:hover:not(:disabled) { border-color: var(--c2g); }
.c2fmt__q.is-on { border-color: var(--c2g); color: var(--c2g); }
/* 🔴 没有画质档的模型：整段照常画出来，全部灰掉点不动。 */
.c2fmt__q:disabled { opacity: 0.42; cursor: not-allowed; }
.c2fmt__q:disabled:hover { border-color: var(--c2l); }
/* 🔴 S2（2026-09-12）：生成时长 = 滑轨 + 数字框 + 「s」，对标 LibTV。
   ⚠️ 只【新增】这一组类，上面 .c2fmt__q / .c2fmt__qs 一个字节没动 ——
      比例 / 清晰度 / 音频三组还在用它们。
   ⚠️ 轨道和滑块必须 -webkit- 和 -moz- 各写一份：滑轨是少数几个
      没有统一伪元素的原生控件，只写一边在另一边就是系统默认蓝。 */
.c2fmt__dur { display: flex; align-items: center; gap: 9px; }
.c2fmt__slider {
  flex: 1 1 auto;
  min-width: 0;
  height: 22px;
  margin: 0;
  padding: 0;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  cursor: pointer;
}
.c2fmt__slider::-webkit-slider-runnable-track {
  height: 4px; border-radius: 2px; background: var(--c2l);
}
.c2fmt__slider::-moz-range-track {
  height: 4px; border-radius: 2px; background: var(--c2l);
}
.c2fmt__slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; margin-top: -5px;
  border: 0; border-radius: 50%; background: var(--c2g);
}
.c2fmt__slider::-moz-range-thumb {
  width: 14px; height: 14px;
  border: 0; border-radius: 50%; background: var(--c2g);
}
.c2fmt__slider:focus-visible { outline: 2px solid var(--c2g); outline-offset: 3px; }
.c2fmt__durnum {
  width: 54px;
  min-height: 34px;
  box-sizing: border-box;
  padding: 6px;
  border: 1px solid var(--c2l);
  border-radius: 8px;
  background: #101216;
  color: var(--c2t);
  font-family: var(--font-ui);
  font-size: 11.5px;
  text-align: center;
}
.c2fmt__durnum:focus { outline: none; border-color: var(--c2g); }
.c2fmt__duru {
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 11.5px;
}

.c2fmt__note {
  display: block;
  margin: -4px 0 10px;
  color: var(--c22);
  font-family: var(--font-ui);
  font-size: 10.5px;
}

/* 🔴 比例走网格不走竖列：4 列 × 2 行正好放下 8 个。 */
.c2fmt__rs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.c2fmt__r {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 46px;
  padding: 6px 2px;
  border: 1px solid var(--c2l);
  border-radius: 8px;
  background: #101216;
  color: var(--c2t);
  font-family: var(--font-ui);
  font-size: 10.5px;
  cursor: pointer;
}
.c2fmt__r b { font-weight: 500; font-variant-numeric: tabular-nums; }
.c2fmt__r:hover { border-color: var(--c2g); }
.c2fmt__r.is-on { border-color: var(--c2g); color: var(--c2g); }

/* 比例形状：一个跟着比例变形的线框。currentColor 让它跟着 is-on 一起变金。 */
.c2fmt__box {
  display: block;
  flex: none;
  border: 1.5px solid currentColor;
  border-radius: 2px;
  opacity: 0.8;
}
.c2fmt__box--chip { opacity: 0.7; }

@media (max-width: 860px) {
  .c2fmt { width: 100%; }
  .c2fmt__rs { grid-template-columns: repeat(4, 1fr); }
}

/* ══════════════════════════════════════════════════════════════════════
   出图后隐藏紧凑栏（2026-08-26）—— 纯追加，上面一行都没动。
   🔴 一个新基名都没有：全部挂在现成的 .c2node / .c2bar 上，
      所以不用再做「新前缀有没有跟站上撞名」那一轮自查。
   🔴 JSX 一个字节没改 —— C2CompactBar 本来就只在 st === 'done' 时渲染
      （29b-Canvas2Nodes.js:751 那个三元），generating / failed / empty
      三态【根本没有这条栏】，它们的进度圈、N/M 计数、错误红字全在
      .c2node__body 里面，跟这里无关。这也是这一轮只写 CSS 的原因。
   ══════════════════════════════════════════════════════════════════════ */

/* ① 给 done 节点一个定位祖先。
   ⚠️ 只写 .c2node.is-done，不写 .c2node —— 空节点 / 生成中 / 失败的节点
      连这条栏都没有，没必要陪着改。
   ⚠️ 只加 position、【不加 z-index】：加了 z-index 就会给每个节点造一个
      层叠上下文，把「hover 的节点 ⊕ 提到 z-index:6」那条的跨节点比较打断
      —— 那条是 P1-a 实测过的（两个 ⊕ 叠在一起时靠它决定点中谁）。 */
.c2node.is-done { position: relative; }

/* ② 把栏抽出正常流 —— 节点高度从此 = Image 标签 + 图，栏出现/消失都不改高度。
   🔴 这一条是关键，不是随手挑的定位方式：
      · 留在流里用 display:none 的话，hover 进出会让节点高度来回跳一截，
        React Flow 的 ResizeObserver 每次都要重量一遍，白抖。
      · 抽出流之后维度恒定，而 ⊕ 两个把手在 .c2node__body 里面、body 在栏
        【上面】—— 下面的东西不在流里，数学上不可能挪动上面的东西，
        所以圆心和连线端点这一轮【不可能】动。
   ⚠️ top 用 calc(100% + 6px)：那 6px 原本是 .c2node 的 gap 撑开的，
      抽出流之后 gap 不再作用于它，得自己补回来，不然栏会贴着图。
   ⚠️ pointer-events 必须跟 opacity 一起开关：只做 opacity:0 的话，
      看不见的栏还铺在图底下那一片，鼠标划过去会点到 ↻ —— 那是要花钱的按钮。 */
.c2node.is-done .c2bar {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.14s ease;
}

/* ③ hover / 选中才出 —— 跟节点工具条 .c2tl【同一个门】，
      不是新发明的交互：.c2tl / .c2nav__btn / .c2handle 三处都是这一套。
   🔴 触屏没有 hover：靠后半条 .is-sel。触屏上点一下节点 = 选中，
      栏就出来了；同时底栏 .c2bay 会显示这个节点的【完整面板】
      （提示词全文 + 模型 + 比例/画质 + ↑ 重跑），比这条摘要还全。
      所以触屏不需要 @media (hover:none) 兜底 —— 那条媒体查询在开发机上
      恒不匹配、量不出来，而 .is-sel 是能直接量的。 */
.c2node.is-done:hover .c2bar,
.c2node.is-done.is-sel .c2bar { opacity: 1; pointer-events: auto; }

/* ④ hover 的「桥」。
   🔴 图和栏之间那 6px 空隙已经不属于 .c2node 的盒子了（栏被抽出流），
      鼠标从图往下移动到栏上会先掉出 :hover —— 栏当场淡出，↻ 永远点不到。
      拿一个伪元素把这段空隙补成可命中区域。
   ⚠️ 伪元素算宿主元素的一部分，命中它 = 命中 .c2bar = .c2node:hover 成立。
   ⚠️ pointer-events 跟着 .c2bar 继承：栏藏着的时候桥也是 none，
      不会变成一条看不见的挡路条。
   ⚠️ 8px 比空隙 6px 多 2px，把两头各 1px 的边框也盖进去，中间不留缝。 */
.c2node.is-done .c2bar::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 8px;
}

/* ══════════════════════════════════════════════════════════════════════
   P2-a：左侧图标栏 + 添加节点面板 + 节点查找面板 + 空画布快捷入口
   （2026-08-29）—— 纯追加，上面一行都没动。

   🔴 四个新基名 + 一个段落容器，上线前 grep 过 propai-shell + lumiq-shell，
      五个全部零命中：c2rail · c2add · c2find · c2start · c2seg
   🔴 .c2tool / .c2tool__btn / .c2tool__wrap 那几条【留着没删】——
      它们的使用者（左上角浮动工具条）这一轮从 JSX 里删掉了，规则命中不到
      任何元素。删规则要动既有段落，留着零风险也零影响。
   ══════════════════════════════════════════════════════════════════════ */

/* ── 左侧栏 ────────────────────────────────────────────────────────────
   🔴 挂在 React Flow 自己的 <Panel position="center-left"> 上：
      .react-flow__panel.left.center 已经把「贴左 + 垂直居中」算好了
      （top:50% + translateY(-15px) translateY(-50%)），而且 Panel 在
      transform 容器【外面】，不跟画布一起缩放 —— 正好是参考视频里的行为。
   ⚠️ 只补一个 z-index：Controls / MiniMap 也是 .react-flow__panel（z-index:5），
      给栏一个更高的值，面板展开时不会被小地图压住。 */
.c2rail__wrap { z-index: 12; }

.c2rail { position: relative; display: block; }

.c2rail__box {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  border: 1px solid var(--c2l);
  border-radius: 15px;
  background: rgba(20, 22, 26, 0.86);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.42);
}

.c2rail__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--c22);
  cursor: pointer;
  text-decoration: none;
}
.c2rail__btn:hover { background: rgba(255, 255, 255, 0.07); color: var(--c2t); }
.c2rail__btn.is-on { background: rgba(217, 164, 65, 0.16); color: var(--c2g); }

/* ── 栏挂的两个面板（添加 / 查找）共用的外框 ──────────────────────────
   ⚠️ 跟栏一起垂直居中，不是从栏顶往下长：栏本身居中，面板要是从栏顶
      往下长，内容一多就会顶穿画布底边。居中 + max-height 才是封死的。 */
.c2add,
.c2find {
  position: absolute;
  left: calc(100% + 8px);
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  box-sizing: border-box;
  width: 238px;
  /* 🔴 上限是【实测】出来的，不是拍的：面板跟栏一起居中，有底栏时整块上移
     半个底栏（下一条），所以它上方能用的空间 = 画布高/2 − 半个底栏 −14。
     高度超过这个的两倍就会顶穿画布上沿 —— 1366×680 那种矮窗口实测溢出 22px。
     两条约束取小：540 是内容本身要的（约 494 + 边距），
     calc(100vh − 268px) 是「不许顶穿上沿」那条。 */
  max-height: min(540px, calc(100vh - 268px));
  overflow-y: auto;
  padding: 10px;
  border: 1px solid var(--c2l);
  border-radius: 15px;
  background: rgba(20, 22, 26, 0.96);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
  font-family: var(--font-ui);
  color: var(--c2t);
}

/* 🔴 有底栏时整块上移半个底栏。
   实测（1280×740）：不上移的话面板是 y 111…629，而底栏顶在 522 ——
   右下角有 29×107 一块被压住，而且压它的是底栏（z-index:20 > 栏的 12），
   看着像面板被啃掉一角。上移之后 20…492，干净让开。
   ⚠️ --c2bay-h 是 P1-g 那条量出来写回去的底栏高度，直接用，不另猜数。 */
.c2canvas--bay .c2add,
.c2canvas--bay .c2find {
  transform: translateY(-50%) translateY(calc(var(--c2bay-h) / -2 - 14px));
}

/* ── 添加节点面板 ────────────────────────────────────────────────────── */
.c2seg + .c2seg { margin-top: 6px; }
.c2seg__h {
  display: block;
  padding: 3px 6px 4px;
  color: var(--c22);
  font-size: 10.5px;
  letter-spacing: 0.05em;
}

.c2add__row {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  box-sizing: border-box;
  padding: 5px 8px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--c2t);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.c2add__row:hover:not(:disabled) { background: rgba(255, 255, 255, 0.06); }

.c2add__ic {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--c22);
  font-style: normal;
  font-size: 14px;
  line-height: 1;
}
.c2add__txt { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.c2add__txt b { font-size: 12.5px; font-weight: 600; }
.c2add__txt i {
  font-style: normal;
  font-size: 9.5px;
  color: var(--c22);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 🔴 灰项：整行变暗，但右边那枚「即将推出」【不跟着变暗】——
   参考视频里灰项什么都不写，用户看不出是"还没做"还是"坏了"。
   把说明留亮一点才是这三个字存在的意义。 */
.c2add__row:disabled { cursor: not-allowed; }
.c2add__row.is-off .c2add__ic,
.c2add__row.is-off .c2add__txt { opacity: 0.42; }
.c2add__soon {
  flex: none;
  margin-left: auto;
  padding: 2px 6px;
  border: 1px solid var(--c2l);
  border-radius: 999px;
  color: var(--c22);
  font-style: normal;
  font-size: 9.5px;
  line-height: 1.35;
  white-space: nowrap;
}

/* ── 搜索 / 节点列表 ─────────────────────────────────────────────────── */
.c2find__h {
  display: block;
  padding: 2px 4px 8px;
  color: var(--c2t);
  font-size: 12px;
  font-weight: 600;
}
.c2find__q {
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 8px;
  padding: 6px 9px;
  border: 1px solid var(--c2l);
  border-radius: 9px;
  background: #101216;
  color: var(--c2t);
  font-family: inherit;
  font-size: 12px;
}
.c2find__q:focus { outline: none; border-color: var(--c2g); }
.c2find__q::placeholder { color: #6b7280; }

.c2find__list { display: flex; flex-direction: column; gap: 2px; }
.c2find__row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  padding: 5px 6px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--c2t);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.c2find__row:hover { background: rgba(255, 255, 255, 0.06); }
.c2find__row.is-on { background: rgba(217, 164, 65, 0.16); }

.c2find__thumb {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 1px solid var(--c2l);
  border-radius: 7px;
  overflow: hidden;
  background: #101216;
  color: var(--c22);
}
.c2find__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.c2find__thumb i { font-style: normal; font-size: 12px; }

.c2find__txt { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.c2find__txt b {
  font-size: 12px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.c2find__txt i { font-style: normal; font-size: 10px; color: var(--c22); }
.c2find__none { margin: 6px 4px; color: var(--c22); font-size: 11.5px; }

/* ── 空画布快捷入口 ───────────────────────────────────────────────────
   🔴 RF 的 top-center Panel 本体是 top:0 + transform:translateX(-15px)
      translateX(-50%)。这里把它挪到垂直居中：X 的两段【原样保留】，
      只在后面补一段 Y —— 少写一段就会横向偏 15px + 半个宽。
   ⚠️ 三个类的特异度跟 RF 的 .react-flow__panel.top.center 打平，
      靠 canvas2.css 排在 vendor 之后取胜（index.html:114 vendor / :129 本表）。 */
.c2canvas--full .react-flow__panel.c2start__wrap {
  top: 50%;
  transform: translateX(-15px) translateX(-50%) translateY(-50%);
}

.c2start {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}
.c2start__h { color: var(--c22); font-family: var(--font-ui); font-size: 12.5px; }
.c2start__row { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
.c2start__btn {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 13px;
  border: 1px solid var(--c2l);
  border-radius: 12px;
  background: rgba(20, 22, 26, 0.9);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: var(--c2t);
  font-family: var(--font-ui);
  text-align: left;
  cursor: pointer;
}
.c2start__btn:hover { border-color: var(--c2g); }
.c2start__btn > i { flex: none; font-style: normal; font-size: 15px; color: var(--c22); }
.c2start__btn span { display: flex; flex-direction: column; gap: 1px; }
.c2start__btn b { font-size: 12.5px; font-weight: 600; }
.c2start__btn span i { font-style: normal; font-size: 10px; color: var(--c22); }
.c2start__cost { font-style: normal; color: var(--c22); font-family: var(--font-ui); font-size: 10.5px; }

/* ── 🔴 碰撞：把缩放控件让开一个栏宽 ──────────────────────────────────
   实测（1280×740 的画布）：
     左侧栏     x 15…61（left:0 + margin:15，宽 46）  y 居中 ±77
     缩放控件   x 15…41（同样 left:0 + margin:15，宽 26）  贴底
   x 完全重叠；y 上，画布高度不到 830px 时两者就压在一起 —— 1280 窗口正是。
   ⚠️ 只挪【左边距】，不挪它的 bottom：bottom 那一串（--c2bay-h / tabbar /
      安全区）是 P1-g 算好的，动它会把底栏让位那套一起弄坏。
   ⚠️ 小地图在右下角，跟左侧栏在 x 上不可能相交，不用动。 */
.c2canvas--full .react-flow__controls { margin-left: 72px; }

/* ── 手机 ──────────────────────────────────────────────────────────────
   🔴 375px 上垂直居中的栏会跟底栏撞：底栏是 left:50% + width:calc(100vw-32px)
      = x 16…359，x 上把栏整个盖住；而屏幕一矮，y 上也会重叠。
   🔴 解法：手机上把栏改成【贴顶】—— 那正好是老工具条空出来的位置。
      底栏在下、缩放控件在下，三者从此谁也压不着谁。
   ⚠️ 特异度：.c2canvas--full .react-flow__panel.c2rail__wrap 是三个类，
      跟 RF 的 .react-flow__panel.left.center 打平，靠排在后面取胜。
   ⚠️ transform:none 是必须的 —— RF 那条 translateY(-15px) translateY(-50%)
      不清掉的话，贴顶之后会往上跑出画布。 */
@media (max-width: 860px) {
  .c2canvas--full .react-flow__panel.c2rail__wrap {
    top: 0;
    transform: none;
  }
  .c2add,
  .c2find {
    top: 0;
    width: min(238px, calc(100vw - 92px));
    max-height: min(56vh, calc(100vh - 300px));
  }
  /* ⚠️ 必须把 .c2canvas--bay 那条一起列进来：它是两个类（0,2,0），
     光写 .c2add（0,1,0）压不住，手机上面板会被 --bay 的 transform 拉到
     画布外面去（实测：375×667 上 y = −292）。 */
  .c2add,
  .c2find,
  .c2canvas--bay .c2add,
  .c2canvas--bay .c2find {
    transform: none;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   P2-b：拖出来松手弹的「引用该节点生成」菜单 —— 纯追加，上面一行都没动。

   🔴 只有一个新基名 c2drop（上线前 grep 过 propai-shell + lumiq-shell，零命中）。
      ⚠️ 跟已有的 .c2dock（上传进度浮层）、.c2canvas--drop（拖文件进画布的
         金色高亮）都不是同一个串。
   🔴 行的样式一条都没新写：菜单里用的是 P2-a 那套
      .c2seg / .c2seg__h / .c2add__row / .c2add__ic / .c2add__txt / .c2add__soon，
      所以灰项的暗度和「即将推出」那枚标跟 ⊕ 面板【是同一份规则】，不会漂。
   ══════════════════════════════════════════════════════════════════════ */

/* ⚠️ z-index 40 = 跟 .c2ref--float（+ Reference / 右键菜单）同一档：
      盖得住画布、节点、底栏(20)、左侧栏(12)，但低于裁剪弹层(60)。
   ⚠️ 五个配色变量解得出来 —— 菜单渲染在 <Flow> 外面但仍在 .c2canvas 里，
      而 .c2canvas 上有那五个（P1-d ③ 补的）。不会重演 P1-c 那种黑底黑字。
   ⚠️ max-height 用 min(…, calc(100vh - 40px))：菜单出现在松手点上，
      越界钳制只管「往回收」，管不了「比屏幕还高」。 */
.c2drop {
  position: fixed;
  z-index: 40;
  box-sizing: border-box;
  width: 216px;
  max-height: min(420px, calc(100vh - 40px));
  overflow-y: auto;
  padding: 8px;
  border: 1px solid var(--c2l);
  border-radius: 14px;
  background: rgba(20, 22, 26, 0.96);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
  font-family: var(--font-ui);
  color: var(--c2t);
}

/* ══════════════════════════════════════════════════════════════════════
   S8：连线上的剪刀（删边）—— 纯追加，上面一行都没动。

   🔴 新基名只有 c2cut（上线前 grep 过 propai-shell + lumiq-shell）。
   ⚠️ 外层那个 div 是 React Flow 的 EdgeToolbar 自己画的（.react-flow__edge-toolbar，
      内联 transform + pointer-events:all），这里一条规则都不给它。
   ⚠️ 按钮尺寸不随缩放变：EdgeToolbar 的 transform 里带了 scale(1/zoom)。
   ⚠️ 【不动】边自己的任何样式 —— P1-f 的 c2edge--on / --hov / --flash 三条原样。
   ══════════════════════════════════════════════════════════════════════ */
.c2cut {
  position: relative;
  box-sizing: border-box;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin: 0;
  padding: 0;
  border: 1px solid var(--c2l);
  border-radius: 50%;
  background: rgba(20, 22, 26, 0.94);
  color: var(--c2t);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
  cursor: pointer;
}
/* 命中区比看得见的圆大一圈（28 → 40px）。伪元素算元素本身，点在这一圈上 target 仍是按钮。 */
.c2cut::before {
  content: '';
  position: absolute;
  inset: -6px;
  border-radius: 50%;
}
.c2cut:hover,
.c2cut:focus-visible {
  border-color: var(--c2g);
  color: var(--c2g);
  outline: none;
}
.c2cut svg {
  display: block;
  width: 14px;
  height: 14px;
  pointer-events: none;
}

/* 🔴 触屏（没有 hover）：点一下边 = 选中 → 剪刀常显；按钮放大到 36px、命中区 48px。
   ⚠️ 边的判定带也加宽：React Flow 默认 interactionWidth=20（flow 单位），
      这里用 CSS 把那条透明的 .react-flow__edge-interaction 压到 32 ——
      CSS 属性压得过 SVG 的 stroke-width 属性，【边的 DOM 一个字节不变】。
   ⚠️ 只在 (hover: none) 下生效：鼠标用户那 20 的带宽不动。 */
@media (hover: none) {
  .c2cut {
    width: 36px;
    height: 36px;
  }
  .c2cut svg {
    width: 16px;
    height: 16px;
  }
  .c2canvas .react-flow__edge-interaction {
    stroke-width: 32px;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   S9：底栏缩略图放大预览 + 「换成 X」按钮 —— 纯追加，上面一行都没动。
   🔴 新基名 c2bay__peek* / c2panel__swap（上线前 grep 过 propai-shell + lumiq-shell）。
   ══════════════════════════════════════════════════════════════════════ */
/* ⚠️ .c2bay 是 position:fixed，这里 absolute 相对它定位；不占位 → 底栏高度不变。
   ⚠️ pointer-events:none：鼠标从缩略图挪上来不会把预览「接住」，挪开就收。 */
.c2bay__peek {
  position: absolute;
  bottom: calc(100% + 8px);
  z-index: 3;
  box-sizing: border-box;
  width: 168px;
  padding: 6px;
  border: 1px solid var(--c2l);
  border-radius: 12px;
  background: rgba(20, 22, 26, 0.97);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  pointer-events: none;
}
.c2bay__peek img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 200px;
  object-fit: contain;
  border-radius: 8px;
  background: #0e0f12;
}
.c2bay__peekPh {
  display: grid;
  place-items: center;
  height: 120px;
  border-radius: 8px;
  background: #0e0f12;
  color: #4a4f57;
  font-style: normal;
  font-size: 22px;
}
.c2bay__peekTag {
  position: absolute;
  left: 12px;
  top: 12px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.66);
  color: #fff;
  font-family: var(--font-ui);
  font-size: 10.5px;
  font-weight: 600;
}
/* 缩略图可以点（触屏上点开预览）。⚠️ 只加一个 cursor，方框本体一条没动。 */
@media (hover: none) {
  .c2bay__refs .c2ref__thumb { cursor: pointer; }
}

/* 「换成 X」：挂在提示行末尾的小按钮，颜色跟提示同一档，悬停变金。 */
.c2panel__swap {
  margin-left: 8px;
  padding: 1px 8px;
  border: 1px solid var(--c2l);
  border-radius: 999px;
  background: transparent;
  color: var(--c2t);
  font: inherit;
  cursor: pointer;
}
.c2panel__swap:hover { border-color: var(--c2g); color: var(--c2g); }

/* ══════════════════════════════════════════════════════════════════════
   🔴 C2U（2026-09-18）：空节点上传区 .c2up + 节点上方工具条 .c2ntb
   两个基名上线前 grep 过 propai-shell + lumiq-shell，零命中。
   颜色全部沿用 .c2node 上那四个变量（--c2b / --c2l / --c2t / --c22 / --c2g），
   不新造色值。
   ══════════════════════════════════════════════════════════════════════ */

/* ── 空节点 = 上传区 ─────────────────────────────────────────────────── */
.c2up {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  height: 100%;
  padding: 8px 10px;
  border-radius: 12px;
  /* 虚线框收在里面 2px，不跟卡片自己的实线描边重在一起 */
  outline: 1px dashed #343a43;
  outline-offset: -8px;
  text-align: center;
  cursor: pointer;
  transition: outline-color 0.14s ease, background 0.14s ease;
}
.c2up:hover { outline-color: var(--c2g); background: rgba(217, 164, 65, 0.05); }
.c2up:focus-visible { outline: 2px solid var(--c2g); outline-offset: -8px; }
/* 拖着文件悬在节点上 */
.c2up.is-over {
  outline: 2px dashed var(--c2g);
  outline-offset: -8px;
  background: rgba(217, 164, 65, 0.1);
}
.c2up.is-busy { cursor: default; outline-color: var(--c2g); }
.c2up.is-err  { outline-color: #6b2f2b; }

.c2up__ic  { font-size: 22px; line-height: 1; color: #3a3f47; font-style: normal; }
.c2up:hover .c2up__ic, .c2up.is-over .c2up__ic { color: var(--c2g); }
.c2up__t   { font-size: 11.5px; line-height: 1.45; font-weight: 600; color: var(--c22); }
.c2up.is-over .c2up__t { color: var(--c2t); }
.c2up__sub { font-size: 10.5px; line-height: 1.4; color: #6b7178; font-style: normal; }
.c2up__err { color: #ff8f85; font-weight: 600; }
/* 上传进度：跟底部浮层 .c2dock__bar 同一套尺寸和颜色 */
.c2up__bar {
  width: 72%;
  height: 4px;
  overflow: hidden;
  border-radius: 999px;
  background: #2a2e35;
}
.c2up__bar i { display: block; height: 100%; background: var(--c2g); transition: width 0.15s linear; }

/* 窄卡片（9:16 最窄 143px）上把字收一收，别撑破 */
@media (max-width: 900px) {
  .c2up__t   { font-size: 11px; }
  .c2up__sub { display: none; }
}

/* ── 节点【上方】的小工具条 ───────────────────────────────────────────
   🔴 位置：节点顶边往上 —— .c2node 是 flex column（tag / body），
      工具条绝对定位到 tag 那一行的右侧，不占布局、不推开 body。
   ⚠️ 不挡连接点：⊕ 把手在 body 左右两侧的垂直中点，这条在 body 上面。 */
.c2ntb {
  position: absolute;
  top: -2px;
  right: 0;
  z-index: 6;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.14s ease;
}
.c2node { position: relative; }
.c2node:hover .c2ntb,
.c2node.is-sel .c2ntb { opacity: 1; }
/* 触屏没有 hover —— 不给这一条的话手机上按不到（跟 .c2nav__btn 同一条理由） */
@media (hover: none) { .c2ntb { opacity: 1; } }

.c2ntb__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 5px;
  background: rgba(12, 13, 16, 0.72);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: #e8eaed;
  font-family: var(--font-ui);
  line-height: 1;
  cursor: pointer;
}
.c2ntb__btn:hover { border-color: var(--c2g); color: var(--c2g); }
.c2ntb__btn--del:hover { border-color: #ff8f85; color: #ff8f85; }
.c2ntb__btn i { font-style: normal; font-size: 12px; line-height: 1; }
