/* ══════════════════════════════════════════════════════════════════════════
   Image Canvas —— 满屏画布外壳 + 节点/面板（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 阴影没有落脚点。 */
.iccanvas--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 第一条）。 */
.iccanvas--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) {
  .iccanvas--full .react-flow__controls,
  .iccanvas--full .react-flow__minimap,
  .iccanvas--full .react-flow__attribution {
    bottom: calc(58px + var(--m-safe-b) + 15px);
  }
}

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

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

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

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

.icnode.is-sel .icnode__body {
  border-color: var(--icg);
  box-shadow: 0 0 0 1px var(--icg);
}

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

.icnode.is-failed .icnode__body { border-color: #6b2f2b; }
.icnode.is-generating .icnode__body { border-color: var(--icg); }

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

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

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

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

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

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

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

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

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

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

/* ⚠️ 绝对定位 + z-index：面板本身在节点里，下拉必须浮在同级节点上面。
   React Flow 的节点默认 z-index 是 0，所以这里给一个够大的值。 */
.icmodel {
  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(--icl);
  border-radius: 11px;
  background: rgba(23, 25, 29, 0.99);
  box-shadow: 0 18px 42px -14px rgba(0, 0, 0, 0.75);
}

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

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

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

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

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

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

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

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

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

/* 空画布时的一句提示（React Flow 的 <Panel position="top-center">）——
   否则用户面对一片黑色点阵，不知道要双击。 */
.ichint {
  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) {
  .icpanel, .icbar { width: 250px; }
  .icmodel { width: 232px; }
}

/* ══ C. 保存与加载（P0-e，2026-08-21）════════════════════════════════
   🔴 新增两个基名：.icsave（顶栏状态）和 .iclist（画布列表）。
      上线前 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
      吃不到这一条（它们根本不渲染 .icsave，但作用域仍然要写死）。 */
.pai--canvas .ptop .icsave {
  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 .icsave.is-dirty { color: #8a6d10; border-color: #e6d9a8; }
.pai--canvas .ptop .icsave.is-saved { color: #26694a; border-color: #bfe0cd; }
.pai--canvas .ptop .icsave.is-error,
/* 🔴 loaderr = 「打不开」，跟 is-error（保存失败）分开的状态，同一套红色。 */
.pai--canvas .ptop .icsave.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 .icsave { 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 .icsave {
    width: 9px;
    height: 9px;
    padding: 0;
    border-width: 0;
    border-radius: 50%;
    overflow: hidden;
    text-indent: -999px;
    background: var(--ink-3);
  }
  .pai--canvas .ptop .icsave.is-dirty { background: #d1a417; }
  .pai--canvas .ptop .icsave.is-saved { background: #26694a; }
  .pai--canvas .ptop .icsave.is-error,
  .pai--canvas .ptop .icsave.is-loaderr { background: #a82d24; }
}

/* ── 我的画布列表 ──────────────────────────────────────────────────────
   ⚠️ 这一屏在【白底】工作区上（.pcbody 没有背景色，继承 .pai），所以用
      站点自己的 --ink/--line/--gold，【不用】上面那套 --ic* 深色 token
      —— 那套是给 #0e0f12 的画布调的。 */
.iclist {
  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) {
  .iclist { padding: 16px var(--m-pad) calc(58px + var(--m-safe-b) + 24px); }
}

.iclist__bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}
.iclist__h { font-size: 15px; font-weight: 600; color: var(--ink); }
.iclist__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;
}
.iclist__new:disabled { opacity: 0.55; cursor: default; }
.iclist__note { margin: 0; color: var(--ink-3); font-size: 13px; }
.iclist__err { margin: 0 0 12px; color: #a82d24; font-size: 13px; }

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

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

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

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

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

.iclist__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;
}
.iclist__item:hover .iclist__del,
.iclist__del:focus { opacity: 1; }
/* 🔴 触屏没有 hover —— 不给这一条的话手机上永远删不掉画布。 */
@media (hover: none) {
  .iclist__del { opacity: 1; }
}

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

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

.icnav__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;
}
.icnode__body:hover .icnav__btn { opacity: 1; }
.icnav__btn:focus { opacity: 1; }
/* 🔴 触屏没有 hover —— 不给这一条的话手机上翻不了页。 */
@media (hover: none) { .icnav__btn { opacity: 1; } }
.icnav__btn--prev { left: 4px; }
.icnav__btn--next { right: 4px; }

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

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

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

/* ── 画布工具条（添加节点菜单）──────────────────────────────────────
   ⚠️ 在 React Flow 的 <Panel position="top-left"> 里，所以定位由它负责，
      这里只管长相。 */
.ictool {
  display: inline-flex;
  gap: 6px;
  padding: 5px;
  border: 1px solid #2a2e35;
  border-radius: 9px;
  background: rgba(20, 22, 26, 0.92);
}
.ictool__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;
}
.ictool__btn:hover { border-color: var(--icg); color: var(--icg); }
/* label 包着一个视觉隐藏的 file input，行为跟按钮一致 */
.ictool__btn--file { position: relative; }

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

/* ── 上传进度：屏幕底部浮层 ─────────────────────────────────────────
   🔴 fixed 定位。这条链上（.pai--canvas → .pcmain → .pcbody → .iccanvas）
      没有任何 transform / filter / backdrop-filter，所以 fixed 确实相对
      视口 —— 那三个属性中任意一个都会让它改为相对该祖先定位。 */
.icdock {
  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);
}
.icdock__row {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  font-family: var(--font-ui);
  font-size: 11.5px;
  color: #e8eaed;
}
.icdock__name {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 42%;
  overflow: hidden;
  font-weight: 500;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.icdock__bar {
  flex: 1;
  height: 4px;
  min-width: 0;
  overflow: hidden;
  border-radius: 999px;
  background: #2a2e35;
}
.icdock__bar i {
  display: block;
  height: 100%;
  background: var(--icg);
  transition: width 0.15s linear;
}
.icdock__pct { flex: none; color: #9aa0a8; font-style: normal; }
.icdock__err {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: #ff8f85;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.icdock__row.is-err .icdock__name { color: #ff8f85; }

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

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

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

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

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

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

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

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

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

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

.ictl__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;
}
.ictl__btn:hover { border-color: #d9a441; color: #d9a441; }
.ictl__btn[disabled] { opacity: 0.45; cursor: default; }
.ictl__btn i { font-style: normal; font-size: 12px; line-height: 1; }

/* ── 裁剪弹层 ──────────────────────────────────────────────────────────
   🔴 position:fixed 铺满视口。⚠️ 跟 .icdock 同一个理由能成立：
      .pcmain / .pcbody / .iccanvas 这条祖先链上没有 transform / filter /
      backdrop-filter（那三个任意一个都会让 fixed 改成相对该祖先定位）。 */
.iccrop {
  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);
}
.iccrop__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;
}

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

.iccrop__ars { display: flex; gap: 4px; margin-left: 6px; }
.iccrop__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;
}
.iccrop__ar:hover { color: #e8eaed; }
.iccrop__ar.is-on { border-color: #d9a441; color: #d9a441; }

.iccrop__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;
}
.iccrop__btn:hover { border-color: #d9a441; color: #d9a441; }
.iccrop__btn--go {
  border-color: #d9a441;
  background: #d9a441;
  color: #14161a;
  font-weight: 600;
}
.iccrop__btn--go:hover { background: #e6b354; color: #14161a; }
.iccrop__btn[disabled] { opacity: 0.45; cursor: default; }

/* 舞台。⚠️ min-height:0 是必须的：不写的话 flex 子项的默认 min-height:auto
   会让它按内容撑开，图片再大都不会缩，整个面板被顶出屏幕。 */
.iccrop__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;
}
.iccrop__frame { position: relative; user-select: none; }
.iccrop__img {
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
  -webkit-user-drag: none;
}
.iccrop__load,
.iccrop__err {
  color: #9aa0a8;
  font-family: var(--font-ui);
  font-size: 12px;
  text-align: center;
  max-width: 34em;
  line-height: 1.6;
}
.iccrop__err { color: #e0806f; }

/* 裁剪框。🔴 外面那圈阴影就是「被裁掉的部分」的表现 ——
   用 box-shadow 铺一层半透明黑比再叠四个遮罩 div 干净得多。 */
.iccrop__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;
}
.iccrop__h {
  position: absolute;
  width: 14px;
  height: 14px;
  background: #d9a441;
  border: 1px solid #14161a;
  border-radius: 2px;
}
.iccrop__h--nw { left: -7px; top: -7px; cursor: nwse-resize; }
.iccrop__h--ne { right: -7px; top: -7px; cursor: nesw-resize; }
.iccrop__h--sw { left: -7px; bottom: -7px; cursor: nesw-resize; }
.iccrop__h--se { right: -7px; bottom: -7px; cursor: nwse-resize; }

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

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

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

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

   🔴 用 .iccanvas--full 前缀提高优先级（0,2,0 > 0,1,0），保证盖过
      上面 :147 那条 .ichandle，跟文件里的先后顺序无关。 */
.iccanvas--full .ichandle {
  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，否则它会把拖拽起点吃掉。伪元素天生不吃事件。 */
.iccanvas--full .ichandle::before,
.iccanvas--full .ichandle::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  background: var(--icg, #d9a441);
  border-radius: 1px;
  pointer-events: none;
}
.iccanvas--full .ichandle::before { width: 9px; height: 1.5px; margin: -0.75px 0 0 -4.5px; }
.iccanvas--full .ichandle::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。 */
.icnode:hover .ichandle,
.icnode.is-sel .ichandle {
  width: 28px;
  height: 28px;
  border-color: var(--icg);
  background: rgba(20, 22, 26, 0.98);
}
.icnode:hover .ichandle::before,
.icnode.is-sel .ichandle::before { width: 13px; margin-left: -6.5px; }
.icnode:hover .ichandle::after,
.icnode.is-sel .ichandle::after { height: 13px; margin-top: -6.5px; }

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

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

/* 🔴 P1-a 修（实测）：四个按钮带文字时整条 227px，而最窄的节点只有
   143 flow px —— 会从节点左边溢出 28px 被裁掉。改成纯图标之后
   4×~27 + 3×4 = 约 120px，最窄的卡也塞得下。
   ⚠️ 纯追加：同名同优先级、排在后面，盖掉上面那条 padding。 */
.ictl__btn { padding: 4px 6px; }
.ictl__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，只有手动拖到一起才碰得上。 */
.icnode:hover .ichandle,
.icnode.is-sel .ichandle { z-index: 6; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   ⚠️ 🔴 值必须跟本文件另外两处（.icnode / .icref--float）逐字相同，
      三处要一起改。
   ══════════════════════════════════════════════════════════════════════ */
.iccanvas {
  --icb: #17191d;
  --icl: #2a2e35;
  --ict: #e8eaed;
  --ic2: #9aa0a8;
  --icg: #d9a441;
}

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

/* ══════════════════════════════════════════════════════════════════════
   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 由 28-ImageCanvas.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 永远读到起始值，
      这一条上一轮已经骗过我一次，不再给它机会。
   ══════════════════════════════════════════════════════════════════════ */

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

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

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

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

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

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

/* 好看一点：金色实心圆不变，加一层很淡的金光 + 按下去会缩一下。
   ⚠️ 尺寸【一点没动】（还是 26×26）—— 面板宽度是死的 300px，
      按钮变大就是往「再挤出一行」的方向推。 */
.icgo {
  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;
}
.icgo: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 样式，
      不盖的话鼠标一放上去它又变回亮金色，比不做还误导。 */
.icgo:disabled {
  background: #2f333b;
  color: #71767e;
  box-shadow: none;
  cursor: not-allowed;
}
.icgo:disabled:hover { background: #2f333b; }
.icgo:disabled:active { transform: none; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.icfmt {
  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(--icl);
  border-radius: 11px;
  background: rgba(23, 25, 29, 0.99);
  box-shadow: 0 18px 42px -14px rgba(0, 0, 0, 0.75);
}
/* 🔴 在底栏里向上弹 —— 底栏贴着屏幕底，往下弹就出屏幕了。 */
.icbay .icfmt { top: auto; bottom: calc(100% + 5px); }

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

.icfmt__qs { display: flex; gap: 6px; flex-wrap: wrap; }
.icfmt__q {
  flex: 1 1 0;
  min-width: 56px;
  min-height: 34px;
  padding: 6px 8px;
  border: 1px solid var(--icl);
  border-radius: 8px;
  background: #101216;
  color: var(--ict);
  font-family: var(--font-ui);
  font-size: 11.5px;
  cursor: pointer;
}
.icfmt__q:hover:not(:disabled) { border-color: var(--icg); }
.icfmt__q.is-on { border-color: var(--icg); color: var(--icg); }
/* 🔴 没有画质档的模型：整段照常画出来，全部灰掉点不动。 */
.icfmt__q:disabled { opacity: 0.42; cursor: not-allowed; }
.icfmt__q:disabled:hover { border-color: var(--icl); }
.icfmt__note {
  display: block;
  margin: -4px 0 10px;
  color: var(--ic2);
  font-family: var(--font-ui);
  font-size: 10.5px;
}

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

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

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

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

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

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

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

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