/**
 * 画布样式。
 *
 * 一整张大白窗口 —— 用户原话。所以底色就是 --panel（白/深色下是深灰），
 * 网格用两层径向渐变画出来，比一张背景图轻，而且缩放时不用改图。
 * 线用 SVG 画（贝塞尔），节点用普通 div 绝对定位；两者都放在 #world 里，
 * 这样平移缩放只要改 #world 的 transform，不用挨个算坐标。
 *
 * 第二版（2026-10-29）的骨架变化：
 *   每个框是 flex 竖排三块 —— 标题栏（定高）/ 主体（吃掉剩余高度）/ 底部吸附条（定高）。
 *   所以框能自由拉大拉小，只要给 .node 改 width/height，里面自然重排。
 *   主体区必须写 min-height:0，否则 flex 子项会撑破父容器（老坑）。
 */
body.cvbody { overflow: hidden; height: 100vh; }

#board {
  position: fixed;
  inset: 52px 0 0 0;
  overflow: hidden;
  background-color: var(--panel);
  cursor: default;
  touch-action: none;
}
#board.panning { cursor: grabbing; }
#board.resizing { cursor: nwse-resize; }

/* 网格：随缩放层级变密度，太密会晃眼 */
#board::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(var(--line2) 1px, transparent 1px);
  background-size: var(--grid, 22px) var(--grid, 22px);
  opacity: .55;
}

#world {
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 1px;
  transform-origin: 0 0;
  /* 由 JS 设 translate + scale */
}

#edges {
  position: absolute;
  left: 0;
  top: 0;
  width: 100000px;
  height: 100000px;
  overflow: visible;
  pointer-events: none;
  color: var(--ink3);
}
#edgeLayer path.wire {
  fill: none;
  stroke: var(--line2);
  stroke-width: 2;
  pointer-events: stroke;
  cursor: pointer;
}
#edgeLayer path.wire:hover { stroke: var(--ink3); }
#edgeLayer path.wire.sel { stroke: var(--accent); stroke-width: 2.5; }
#edgeLayer path.wire.temp { stroke: var(--accent); stroke-dasharray: 5 4; }
#edgeLayer path.wire.video { stroke: #c9a227; }

/* 拖动文件悬在画布上方时的一圈提示：不说一句，用户不知道松手会发生什么 */
#board.dropping::after {
  content: '松手，放到画布上';
  position: absolute;
  inset: 12px;
  z-index: 5;
  pointer-events: none;
  border: 2px dashed var(--accent);
  border-radius: 14px;
  background: color-mix(in srgb, var(--accent) 5%, transparent);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
  font-size: 13px;
  letter-spacing: .04em;
}

/* ------------------------------- 节点骨架 ------------------------------- */
.node {
  position: absolute;
  background: var(--panel);
  border: 1px solid var(--line2);
  border-radius: 12px;
  box-shadow: 0 2px 6px rgba(20, 24, 31, .06), 0 10px 24px rgba(20, 24, 31, .07);
  user-select: none;
  display: flex;
  flex-direction: column;
  overflow: visible;
}
.node.sel { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft), 0 10px 24px rgba(20, 24, 31, .1); }

.node .nhead {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  cursor: grab;
  font-size: 12px;
  font-weight: 600;
  border-radius: 12px 12px 0 0;
}
.node .nhead .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.node.prompt .nhead .dot { background: #7c8cf8; }
/* 文本框：不给它"能生成"的颜色，石板灰一眼就看得出它只是个记事贴 */
.node.text   .nhead .dot { background: #6b7a8f; }
.node.image  .nhead .dot { background: #2f9e6e; }
.node.video  .nhead .dot { background: #c9a227; }
.node .nhead .t { flex: 1; min-width: 0; }
.node .nhead .x {
  border: 0; background: transparent; color: var(--ink3); cursor: pointer;
  font-size: 14px; line-height: 1; padding: 2px 4px; border-radius: 5px; flex: none;
}
.node .nhead .x:hover { background: var(--danger-soft); color: var(--danger); }
/* 上游数量小徽标：端口挪到边框正中之后，原来贴在端口旁边那行字没地方站了 */
.node .nhead .hdcount {
  flex: none; font-size: 9.5px; font-weight: 500; color: var(--ink3);
  background: var(--soft); border: 1px solid var(--line); border-radius: 20px; padding: 1px 6px;
}

/* 主体：吃掉剩余高度。min-height:0 少了就会被里面的东西撑破 */
.node .nmain {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  display: flex;
  position: relative;
}

/* ------------------------------- 提示词框 ------------------------------- */
.node textarea.ptext {
  flex: 1 1 auto;
  width: 100%;
  height: 100%;
  resize: none;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 12.5px;
  line-height: 1.7;
  padding: 10px 11px;
}
.node textarea.ptext::placeholder { color: var(--ink3); }

/* ------------------------------- 文本框 ------------------------------- */
/*
 * 只放字、不生成（2026-11-01 用户要的「独立文本框」）。所以这里没有模型下拉、
 * 没有生成按钮、没有 AI 改写 —— 只有字数和「复制」。少放一个按钮，它就少一个误会的来源。
 */
.node.text textarea.ntext { font-size: 12.5px; }
.node .ndock .tlen {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 11px;
  color: var(--ink3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --------------------------- 图片 / 视频 监视区 --------------------------- */
.node .stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 9px;
  position: relative;
}
.node .stage .shot {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 7px;
  display: block;
  background: var(--soft);
}
.node.image .stage .shot { cursor: zoom-in; }
.node .stage video.shot { background: #000; }

/* 右上角 ⤓：本地图是「在文件夹中显示」（<button>），云端图是「下载」（<a download>）。
   两种元素共用这一套外观，所以 button 那边要把浏览器默认的 padding/字体清掉。 */
.node .stage .dlbtn {
  position: absolute;
  right: 8px;
  top: 8px;
  z-index: 3;
  width: 26px;
  height: 26px;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 7px;
  border: 1px solid var(--line2);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  color: var(--ink2);
  font-size: 14px;
  line-height: 1;
  text-decoration: none;
  /* 平时淡一点也要能看见 —— 只在 hover 才出现的话，用户根本不知道有这功能 */
  opacity: 0.45;
  transition: opacity .15s, color .15s, border-color .15s;
}
.node:hover .stage .dlbtn,
.node .stage .dlbtn:focus-visible {
  opacity: 1;
}
.node .stage .dlbtn:hover { color: var(--accent); border-color: var(--accent); }
/* 文件夹图标（内联 SVG）：尺寸在这里定，跟旁边 14px 的 ⤓ 视觉重量对齐 */
.node .stage .dlbtn svg {
  width: 15px;
  height: 15px;
  display: block;
  pointer-events: none;
}

/* 「存进素材库」：生成完才出现，压在画面左下。
   为什么必须显式点：生成产物默认落在临时目录，不点就不占云端空间。 */
.node .stage .svbtn {
  position: absolute;
  left: 8px;
  bottom: 8px;
  z-index: 3;
  padding: 4px 9px;
  border-radius: 7px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line2));
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  color: var(--accent);
  font-family: inherit;
  font-size: 11.5px;
  line-height: 1.4;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.node .stage .svbtn:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
.node .stage .svbtn:disabled { cursor: default; opacity: .6; }

/* 吸附条里的「参考了什么」：连线=参考这件事得看得见，不能靠猜 */
.node .ndock .refhint {
  font-size: 11px;
  color: var(--ink3);
  padding: 2px 2px 0;
  line-height: 1.5;
}

/* 连进来的图：一排小图 + 「图1 / 图2」角标（2026-11-01 用户要求）。
   编号顺序 = 画布从上到下，跟真正发出去的参考图顺序是同一个，提示词里能对着说。 */
.node .ndock .refstrip {
  display: flex;
  gap: 6px;
  padding: 2px 2px 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}
.node .ndock .refthumb {
  position: relative;
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--line2);
  background: var(--soft);
}
.node .ndock .refthumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.node .ndock .refthumb .refno {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  font-size: 10px;
  font-weight: 600;
  line-height: 14px;
  text-align: center;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
  letter-spacing: 0.5px;
}
/* 视频框只吃第 1 张，多出来的图在这说明一下没参与，别让人以为都用上了 */
.node .ndock .refmore {
  flex: none;
  align-self: center;
  font-size: 10.5px;
  color: var(--ink3);
  padding: 0 2px;
  white-space: nowrap;
}

/* 连了提示词框之后，原来那个输入框换成这一条说明（见 canvas.js 的 genRow） */
.node .ndock .useup {
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px 9px;
  border: 1px dashed var(--line2);
  border-radius: 7px;
  background: var(--soft);
  color: var(--ink2);
  font-size: 11.5px;
  line-height: 1.5;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 顶部的项目切换器：跟 header 里其它控件同一套观感 */
header .pselect {
  max-width: 220px;
  font-size: 12px;
  padding: 4px 8px;
  border: 1px solid var(--line2);
  border-radius: 7px;
  background: var(--panel);
  color: var(--ink);
  font-family: inherit;
  cursor: pointer;
}
header .pselect:disabled { color: var(--ink3); cursor: default; }

/* 空画框：虚线框 + 一句说明。不做真实比例预览 —— 比例一多，
   各种宽高比在几百像素的框里算来算去，最后一定有一边溢出，不如老实占满 */
.node .stage .ph {
  width: 100%;
  height: 100%;
  border: 1px dashed var(--line2);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  color: var(--ink3);
  text-align: center;
  padding: 10px;
}
.node .stage .ph b { font-size: 12px; font-weight: 600; color: var(--ink2); }
.node .stage .ph span { font-size: 10.5px; line-height: 1.5; }

/* 生成中：转圈 + 主文案 + 小字说明。
   底下**不再画占位框**（canvas.js 里 busy 时跳过 .ph），所以这一层是唯一的内容。
   背景给到 86%：有旧图重生成时能透出一点轮廓，但不会跟字打架。 */
.node .stage .bustag {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 12px;
  color: var(--ink2);
  background: color-mix(in srgb, var(--panel) 86%, transparent);
  border-radius: 8px;
  pointer-events: none;
  text-align: center;
}
.node .stage .bustag .spin {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  box-sizing: border-box;
  border: 2px solid var(--line2);
  border-top-color: var(--accent);
  animation: cvspin .8s linear infinite;
}
.node .stage .bustag .btxt { font-size: 12px; font-weight: 600; }
.node .stage .bustag .bsub { font-size: 10.5px; line-height: 1.5; color: var(--ink3); }
@keyframes cvspin { to { transform: rotate(360deg) } }
@media (prefers-reduced-motion: reduce) {
  .node .stage .bustag .spin { animation-duration: 2.4s }
}

/* ------------------------------ 底部吸附条 ------------------------------ */
.node .ndock {
  flex: none;
  border-top: 1px solid var(--line);
  background: var(--panel2);
  padding: 7px 8px 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-radius: 0 0 12px 12px;
}
.node .drow { display: flex; gap: 5px; align-items: center; min-width: 0; }
.node .drow.grow { align-items: stretch; }

.node .dsel {
  flex: 1 1 0;
  min-width: 0;
  font-size: 11px;
  padding: 4px 5px;
  border: 1px solid var(--line2);
  border-radius: 6px;
  background: var(--panel);
  color: var(--ink);
  font-family: inherit;
  cursor: pointer;
}
/* 模型名最长（"GPT Image 2.5 Flare · 2K · 16:9"），多分点宽度，不然被截成 "· 2l"。
   2026-11-01 起一条真实模型一行、名字里带分辨率+画幅，所以这里再让一点。
   比例下拉已经整个删了（画幅改由提示词决定），所以底栏只剩「模型 + 模组」两个框，
   宽度按 3:2 分就够，不用再留那个 62px 的窄位。 */
.node .dsel.mdl { flex: 3 1 0; }

.node textarea.dprompt {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 42px;
  max-height: 76px;
  resize: none;
  font-family: inherit;
  font-size: 11.5px;
  line-height: 1.5;
  padding: 6px 7px;
  border: 1px solid var(--line2);
  border-radius: 7px;
  background: var(--panel);
  color: var(--ink);
}
.node input.dchat {
  flex: 1 1 auto;
  min-width: 0;
  font-family: inherit;
  font-size: 12px;
  padding: 6px 8px;
  border: 1px solid var(--line2);
  border-radius: 7px;
  background: var(--panel);
  color: var(--ink);
}
.node .gen {
  flex: 0 0 auto;
  white-space: nowrap;
  font-size: 11.5px;
  padding: 0 9px;
  align-self: stretch;
}
.node .undo { flex: 0 0 auto; font-size: 11px; padding: 4px 8px; }
.node .nowarn {
  flex: 1 1 0;
  min-width: 0;
  font-size: 10.5px;
  color: var(--warn);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.node .err {
  font-size: 10.5px;
  color: var(--danger);
  background: var(--danger-soft);
  border-radius: 6px;
  padding: 5px 7px;
  word-break: break-all;
  max-height: 76px;
  overflow: auto;
}

/* ------------------------------ 拉角改大小 ------------------------------ */
.node .rz {
  position: absolute;
  width: 14px;
  height: 14px;
  z-index: 4;
  opacity: 0;
  transition: opacity .12s;
}
.node:hover .rz, .node.sel .rz { opacity: 1; }
.node .rz::after {
  content: '';
  position: absolute;
  width: 7px;
  height: 7px;
  border: 1.5px solid var(--accent);
  border-radius: 2px;
  background: var(--panel);
}
.node .rz.nw { left: -7px; top: -7px; cursor: nwse-resize; }
.node .rz.nw::after { left: 3px; top: 3px; }
.node .rz.ne { right: -7px; top: -7px; cursor: nesw-resize; }
.node .rz.ne::after { right: 3px; top: 3px; }
.node .rz.sw { left: -7px; bottom: -7px; cursor: nesw-resize; }
.node .rz.sw::after { left: 3px; bottom: 3px; }
.node .rz.se { right: -7px; bottom: -7px; cursor: nwse-resize; }
.node .rz.se::after { right: 3px; bottom: 3px; }

/* -------------------------------- 端口 -------------------------------- */
/* 左右边框的垂直正中。鼠标要容易点到，所以热区比圆点大。
   注意 hover 的 transform 必须带上 translateY —— 漏了端口会瞬间跳回顶上去 */
.port {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--panel);
  border: 2px solid var(--line2);
  cursor: crosshair;
  z-index: 3;
  transition: border-color .12s, background .12s;
}
.port:hover { border-color: var(--accent); transform: translateY(-50%) scale(1.2); }
.port.on { border-color: var(--accent); background: var(--accent-soft); }
.port.pin { left: -7px; }
.port.pout { right: -7px; }

/* ------------------------------- 右键菜单 ------------------------------- */
.ctxmenu {
  position: fixed;
  z-index: 999;
  display: none;
  min-width: 190px;
  background: var(--panel);
  border: 1px solid var(--line2);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(20, 24, 31, .16);
  padding: 6px;
  font-size: 12.5px;
}
.ctxmenu.on { display: block; }
.ctxmenu .it {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px; border-radius: 7px; cursor: pointer; color: var(--ink);
}
.ctxmenu .it:hover { background: var(--hover); }
.ctxmenu .it.danger { color: var(--danger); }
.ctxmenu .it.danger:hover { background: var(--danger-soft); }
.ctxmenu .sep { height: 1px; background: var(--line); margin: 5px 4px; }
.ctxmenu .hd { padding: 5px 9px 3px; font-size: 10.5px; color: var(--ink3); }
.ctxmenu kbd { font-family: inherit; font-size: 10px; color: var(--ink3); margin-left: auto; }

/* ---------------------------- 画布里的素材库 ---------------------------- */
/*
 * 下拉面板：顶在 header 下面，高度只占窗口的 1/3 —— 用户要的是「随时瞄一眼素材，
 * 不用离开画布」，所以它不能遮满屏。点画布空白、按 Esc、再点一次 header 按钮都能收起。
 */
.aspanel {
  position: fixed;
  left: 0;
  right: 0;
  top: 52px; /* = header 高度，#board 也是从 52px 开始 */
  height: 33vh;
  min-height: 210px;
  z-index: 30; /* 高过 #board，低于右键菜单（999）和 toast */
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 14px 10px;
  background: var(--panel);
  border-bottom: 1px solid var(--line2);
  box-shadow: 0 14px 34px rgba(20, 24, 31, 0.16);
  animation: aspIn .14s ease-out;
}
/* [hidden] 必须显式压回去：这里 display:flex 会盖掉浏览器默认的 display:none */
.aspanel[hidden] {
  display: none;
}
@keyframes aspIn {
  from {
    transform: translateY(-8px);
    opacity: 0;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

.aspbar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
}
.aspbar .spacer {
  flex: 1 1 auto;
}
.aspcount {
  color: var(--ink3);
  font-size: 11.5px;
}

.aspcats {
  flex: none;
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding-bottom: 2px;
}
.aspcat {
  flex: none;
  padding: 3px 11px;
  border-radius: 999px;
  border: 1px solid var(--line2);
  background: var(--panel);
  color: var(--ink2);
  font-family: inherit;
  font-size: 11.5px;
  line-height: 1.5;
  cursor: pointer;
  white-space: nowrap;
}
.aspcat:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.aspcat.on {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* 拖着素材悬到分类芯片上：明确告诉你松手就归到这里 */
.aspcat.drop {
  background: var(--accent-soft, var(--soft));
  border-color: var(--accent);
  border-style: dashed;
  color: var(--accent);
}

.aspgrid {
  flex: 1 1 auto;
  min-height: 0; /* flex 子项不加这行会被内容撑破，滚动条消失 */
  overflow: auto;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
  gap: 8px;
  align-content: start;
}
.aspitem {
  position: relative;
  border: 1px solid var(--line2);
  border-radius: 8px;
  overflow: hidden;
  background: var(--soft);
  /* 只能拖，不能点（2026-11-01 取消了"点一下就放进画布"）。手感上也要说清楚这一点 */
  cursor: grab;
  transition: border-color .12s, transform .12s;
}
.aspitem:active { cursor: grabbing; }
.aspitem:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
}
.aspthumb {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--soft);
}
.aspitem .aspname {
  font-size: 10.5px;
  line-height: 1.5;
  color: var(--ink2);
  padding: 2px 6px 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 操作按钮压在图右上角，平时半透明 —— 全隐藏的话没人知道能点 */
.aspacts {
  position: absolute;
  right: 3px;
  top: 3px;
  display: flex;
  gap: 3px;
  opacity: 0;
  transition: opacity .12s;
}
.aspitem:hover .aspacts {
  opacity: 1;
}
.aspacts button {
  width: 20px;
  height: 20px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 5px;
  border: 1px solid rgba(255, 255, 255, 0.5);
  background: rgba(18, 24, 32, 0.62);
  color: #fff;
  font-family: inherit;
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
}
.aspacts button:hover {
  background: #fff;
  color: #111;
}
/* 卡片只有 20px，图标得比 11px 的字形再小一档才不顶边 */
.aspacts button svg {
  width: 12px;
  height: 12px;
  display: block;
  pointer-events: none;
}
.aspacts button.danger:hover {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}

.aspempty {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  text-align: center;
  color: var(--ink3);
  font-size: 12px;
}
.aspempty[hidden] {
  display: none;
}

/* ------------------------------- 帮助 ------------------------------- */
.cvhelp {
  position: fixed; right: 18px; bottom: 18px; z-index: 500;
  display: none; width: 340px; max-height: 70vh; overflow: auto;
  background: var(--panel); border: 1px solid var(--line2); border-radius: 12px;
  box-shadow: 0 18px 44px rgba(20, 24, 31, .18); padding: 16px 18px;
}
.cvhelp.on { display: block; }
.cvhelp h3 { margin: 0 0 10px; font-size: 13px; }
.cvhelp ul { margin: 0; padding-left: 18px; }
.cvhelp li { font-size: 12px; line-height: 1.85; color: var(--ink2); }
.cvhelp li b { color: var(--ink); }
.cvhelp p { margin: 10px 0 0; font-size: 11px; }

@keyframes cvpulse { 0%, 100% { opacity: .55 } 50% { opacity: 1 } }

.cvemptyhint {
  position: fixed; left: 50%; top: 46%; transform: translate(-50%, -50%);
  text-align: center; color: var(--ink3); font-size: 13px; pointer-events: none;
}
.cvemptyhint b { color: var(--ink2); }

/* ---------------------------------------------------------------
   看大图浮层。
   以前点图是 window.open(url) —— 云端图会弹个新窗口，本地图（wf://）
   则直接把导航转给 Windows，弹「需要使用新应用以打开此 wf 链接」。
   2026-11-01 用户点了图就问「我点击图片不是放大么」。改成画布内放大。
   --------------------------------------------------------------- */
.zoommask {
  position: fixed;
  inset: 0;
  z-index: 900; /* 高过 #board(30) 和素材面板，低于右键菜单(999) */
  display: none;
  align-items: center;
  justify-content: center;
  padding: 3vmin;
  background: rgba(8, 11, 16, .84);
  cursor: zoom-out;
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.zoommask.on { display: flex; }
.zoommask img {
  max-width: 96vw;
  max-height: 92vh;
  object-fit: contain;
  border-radius: 8px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, .5);
  /* 大图别把点击冒泡拦掉——点图本身也该能关（落在 mask 上） */
  cursor: zoom-out;
}
