:root{color-scheme:dark;--bg:#17191b;--line:#363b3e;--text:#eeeae2;--muted:#a4aaa9;--dim:#747d7e;--accent:#dab783;--accent-dark:#272117;--success:#9abc9e;--error:#efa29a;font-family:"Segoe UI","Microsoft YaHei UI","Microsoft YaHei",sans-serif;font-size:14px}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);overflow:hidden}button,input,textarea,select{font:inherit}button{border:1px solid var(--line);border-radius:6px;padding:8px 12px;background:transparent;color:var(--text);cursor:pointer;transition:background .15s,border-color .15s}button:hover{background:#34393b;border-color:#61696a}button:focus-visible,a:focus-visible{outline:2px solid var(--accent);outline-offset:3px}button:disabled{opacity:.4;cursor:not-allowed}.primary{background:var(--accent);color:#211c15;border-color:var(--accent);font-weight:600}.primary:hover{background:#ecd0a3;border-color:#ecd0a3}.muted,.hint{color:var(--muted);line-height:1.65}.hint{font-size:12px}.eyebrow{font-size:10px;letter-spacing:2px;color:var(--dim);font-weight:600}.topbar{height:76px;display:flex;align-items:center;gap:14px;padding:0 24px;background:#1d2022;border-bottom:1px solid var(--line)}.brand-mark{font-family:Georgia,serif;font-size:29px;color:var(--accent);border:1px solid #6f604a;width:40px;height:40px;display:grid;place-items:center}.brand{font-size:17px;font-weight:600;letter-spacing:1px}.brand span{display:block;font-size:10px;color:var(--muted);letter-spacing:2px;margin-top:5px;font-weight:400}.version{font-size:11px;color:var(--dim);align-self:center}.top-actions{margin-left:auto;display:flex;gap:8px;align-items:center}.divider{height:22px;background:var(--line);width:1px;margin:0 7px}.workspace{height:calc(100dvh - 113px);display:grid;grid-template-columns:64px minmax(300px,1fr) 348px}.rail{background:#1d2022;border-right:1px solid var(--line);display:flex;align-items:center;flex-direction:column;gap:12px;padding:22px 0}.rail-label{font-size:10px;color:var(--dim);letter-spacing:2px}.rail button{border-color:transparent;padding:0;width:38px;height:38px;color:var(--muted);font-size:12px}.rail button.active{background:#42382a;border:1px solid #857052;color:var(--accent)}.rail-line{width:20px;height:1px;background:var(--line)}.rail .rail-help{margin-top:auto;border:1px solid var(--line);border-radius:50%;width:26px;height:26px}.canvas-shell{position:relative;display:grid;grid-template-rows:89px minmax(0,1fr) 52px 141px;min-width:0;background:#171a1c}.canvas-heading{padding:25px 28px 0;display:flex;gap:20px;justify-content:space-between;pointer-events:none;z-index:1}.canvas-heading h1{font-size:21px;font-weight:500;margin:8px 0 0;max-width:45vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-label{font-size:11px;color:var(--dim);padding-top:8px}.session-label span{display:inline-block;width:6px;height:6px;background:var(--success);border-radius:50%;margin-right:7px}#stage{position:relative;overflow:hidden;touch-action:none;outline:none;background-image:radial-gradient(#40474b 0.65px,transparent .65px);background-size:22px 22px;cursor:grab}#stage.panning{cursor:grabbing}#world{position:absolute;width:1px;height:1px;transform-origin:0 0}#edges{overflow:visible;position:absolute;pointer-events:none;width:1600px;height:1100px}.edge{fill:none;stroke:#5d686b;stroke-width:1.5}.node{position:absolute;border:1px solid #42494b;background:#24282b;border-radius:10px;box-shadow:0 5px 16px #10111255;overflow:hidden;cursor:pointer}.node.selected{border-color:var(--accent);box-shadow:0 0 0 1px #dab78330}.node:before,.node:after{content:"";position:absolute;width:6px;height:6px;border:1px solid #8c999b;background:#24282b;top:50%;border-radius:50%}.node:before{left:-4px}.node:after{right:-4px}.node-head{display:flex;align-items:center;gap:10px;padding:15px 16px;border-bottom:1px solid #363e42;cursor:move;touch-action:none}.node-num{color:var(--accent);font-size:11px}.node-head h3{margin:0;font-size:14px;font-weight:500}.node-state{margin-left:auto;color:var(--dim);font-size:10px;border:1px solid #414a4d;border-radius:20px;padding:2px 7px}.node-state.done{color:var(--success);border-color:#536859}.node-state.running{color:var(--accent);border-color:#857052}.node-state.error{color:var(--error);border-color:#89544c}.node-body{padding:15px 16px;color:var(--muted);font-size:12px;line-height:1.7;overflow:hidden}.canvas-bottom{display:flex;justify-content:space-between;align-items:center;padding:0 20px;border-bottom:1px solid var(--line);gap:10px}.canvas-hint{font-size:11px;color:var(--dim)}.zoom-bar{display:flex;align-items:center;gap:2px;border:1px solid var(--line);border-radius:7px;background:#222729;padding:3px}.zoom-bar button{font-size:11px;border:0;padding:5px 8px}.zoom-bar span{height:15px;width:1px;background:var(--line);margin:0 3px}.history-tray{padding:13px 22px;background:#1c2022;overflow:hidden}.history-title{display:flex;align-items:center;gap:10px;font-size:11px}.history-title strong{font-weight:500;color:#cbd0ce}.history-title span{color:var(--dim)}.history-title button{margin-left:auto;border:0;font-size:10px;color:var(--muted);padding:0}#historyList{display:flex;gap:10px;margin-top:10px;overflow-x:auto;padding-bottom:4px}.history-empty{font-size:11px;color:var(--dim);margin:16px 0}.history-item{padding:0;width:110px;min-width:110px;height:78px;position:relative;overflow:hidden;background:#161a1c;border:1px solid #495155}.history-item img{width:100%;height:100%;object-fit:contain}.history-item span{position:absolute;bottom:0;left:0;right:0;text-align:left;font-size:10px;padding:3px 6px;background:#151a1dde}.history-item.active{border-color:var(--accent)}label{display:block;color:#c9cecb;font-size:12px;margin-bottom:9px}input,textarea,select{width:100%;background:#1a1e20;border:1px solid #424b4f;border-radius:6px;padding:10px 11px;color:var(--text);outline:0;min-width:0}input:focus,textarea:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 2px #dab78312}input[type=checkbox]{width:15px;height:15px;accent-color:var(--accent);margin:0}input[type=range]{padding:0;accent-color:var(--accent)}input[type=color]{height:35px;padding:3px}textarea{resize:vertical;line-height:1.8;min-height:150px;font-size:13px}input::placeholder,textarea::placeholder{color:#737f83}.field{margin-bottom:22px}.field-meta{display:flex;justify-content:space-between;margin-top:7px;color:var(--dim);font-size:10px}.note{padding:13px;border-left:2px solid #73664e;background:#2d2c27;color:#d0c4ae;font-size:12px;line-height:1.7;margin:20px 0}.ratio-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.ratio-btn{height:61px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px;font-size:10px;padding:8px 4px}.ratio-btn i{border:1px solid #929c9b;display:block;width:23px;height:13px}.ratio-btn.active{border-color:var(--accent);background:#373127;color:var(--accent)}.two-fields{display:flex;gap:8px;align-items:center}.two-fields span{color:var(--dim)}.checkline{display:flex;gap:9px;align-items:flex-start;line-height:1.6;font-size:12px;margin:18px 0}.checkline input{margin-top:3px;flex-shrink:0}.statusbar{height:37px;border-top:1px solid var(--line);display:flex;align-items:center;gap:14px;padding:0 18px;background:#1b1e20;font-size:11px}#statusBadge{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}#statusBadge.error{color:var(--error)}#statusBadge.success{color:var(--success)}.statusbar button{border:0;font-size:11px;padding:3px 7px}.local-label{color:var(--dim)}#logCount{color:var(--accent);margin-left:4px}.dialog{background:#24292c;color:var(--text);border:1px solid #50595d;border-radius:12px;padding:28px;width:480px;max-width:94vw;max-height:90dvh;overflow:auto;box-shadow:0 20px 100px #0007}.dialog::backdrop{background:#0c1015bb;backdrop-filter:blur(4px)}.dialog h2{font-size:24px;font-weight:500;margin:12px 0}.dialog p{font-size:12px}.dialog label{margin-top:18px}.dialog-close{position:absolute;right:14px;top:12px}.dialog-close button{border:0;font-size:24px;padding:0 6px;color:var(--muted)}
/* V0.1.35：查看器的关闭 × 原来是「无边框、灰字、28×32」—— 在深色弹窗上几乎看不见，
   用户反馈「图片放大后找不到关闭按钮」。给它实心按钮外观 + 更大的点击区。 */
.viewer-head>button{width:36px;height:36px;display:flex;align-items:center;justify-content:center;flex:none;padding:0;font-size:20px;line-height:1;border:1px solid #4a5257;border-radius:9px;background:#2c3236;color:var(--text);cursor:pointer}
.viewer-head>button:hover{border-color:var(--accent);color:var(--accent);background:#343b40}
.viewer-head>button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:24px}.settings{width:520px}.form-section{border-top:1px solid var(--line);margin-top:25px;padding-top:6px}.form-section h3{font-size:14px;font-weight:500}.form-section h3 span{color:var(--dim);font-weight:400;margin-left:10px;font-size:12px}.viewer{width:min(1680px,94vw);height:90dvh;max-width:none;padding:24px;overflow:hidden}.viewer[open]{display:flex;flex-direction:column}.viewer-head{display:flex;align-items:flex-start;justify-content:space-between;flex:none}.viewer-head h2{font-size:19px;margin:6px 0}.viewer-head .muted{font-size:11px}.viewer-tabs{display:flex;gap:8px;padding:16px 0;flex:none}.viewer-tabs button{font-size:12px;border-color:transparent}.viewer-tabs button.active{border-color:#817055;background:#393327;color:var(--accent)}.viewer-main{flex:1;min-height:0;display:flex;gap:22px}.image-stage{flex:1;min-width:0;min-height:0;background:#15191b;border:1px solid #363d41;border-radius:6px;display:flex;flex-direction:column;justify-content:center;align-items:center;padding:18px;overflow:hidden}#compareWrap{position:relative;width:100%;height:100%;min-height:0}#viewImage,#compareBefore{width:100%;height:100%;object-fit:contain;position:absolute;inset:0}#compareBefore{display:none}#compareLine{display:none;position:absolute;width:1px;height:100%;left:50%;background:var(--accent);pointer-events:none}#compareControls:not([hidden]){display:flex;gap:15px;align-items:center;width:min(100%,500px);padding:15px 0 0;font-size:12px}.viewer-actions{display:flex;gap:10px;align-items:center;padding-top:16px;flex:none}.viewer-actions>span{flex:1}.viewer-actions button{font-size:12px}#exportPanel{width:240px;flex-shrink:0;overflow:auto;padding:0 5px}#exportPanel h3{font-size:14px;margin:0}#exportPanel>button{width:100%;margin-top:12px;font-size:12px}#exportPreview{width:100%;max-height:120px;object-fit:contain;margin-top:12px;background:#161a1b}#exportMeta{display:block;font-size:11px;margin-top:5px}.logs{width:680px}#logList{max-height:60vh;overflow:auto;margin-top:20px}.log-entry{border-top:1px solid var(--line);padding:12px 0;font-size:12px;line-height:1.6;overflow-wrap:anywhere}.log-entry time{font-size:10px;display:block;color:var(--dim);margin-bottom:3px}.log-entry.error{color:var(--error)}.log-entry.success{color:var(--success)}[hidden]{display:none!important}::-webkit-scrollbar{height:5px;width:5px}::-webkit-scrollbar-thumb{background:#505b60;border-radius:4px}::-webkit-scrollbar-track{background:transparent}@media(max-width:1150px){.workspace{grid-template-columns:52px minmax(200px,1fr) 310px}.topbar{padding:0 16px;gap:10px}.top-actions{gap:5px}.top-actions button{font-size:12px;padding:8px}.version{display:inline;font-size:10px}.canvas-heading{padding-left:20px;padding-right:20px}.canvas-hint,.session-label{display:none}.canvas-bottom{justify-content:flex-end}}@media(max-width:760px){body{overflow:auto}.topbar{height:auto;min-height:98px;flex-wrap:wrap;padding:12px;gap:8px}.brand{font-size:14px}.brand span{font-size:9px}.brand-mark{height:30px;width:30px;font-size:22px}.top-actions{width:100%;justify-content:flex-end;gap:4px}.top-actions button{font-size:11px;padding:6px}.divider{margin:0 2px}.workspace{height:auto;min-height:calc(100dvh - 130px);grid-template-columns:44px minmax(0,1fr)}.rail{grid-row:1 / 3}.canvas-shell{height:560px;grid-template-rows:76px minmax(0,1fr) 48px 125px}.canvas-heading h1{max-width:65vw;font-size:18px}.history-tray{padding:12px}.statusbar{position:sticky;bottom:0}.local-label{display:none}.viewer{height:94dvh;padding:14px}.viewer-main{flex-direction:column}.image-stage{min-height:200px}#exportPanel{width:100%;max-height:220px}.viewer-actions{flex-wrap:wrap}.viewer-actions>span{flex-basis:100%}.rail button{width:30px;height:32px}}@media(prefers-reduced-motion:reduce){*{transition:none!important;scroll-behavior:auto!important}}
@media(max-width:760px){.top-actions{justify-content:flex-start;overflow-x:auto;flex-wrap:nowrap;padding-bottom:2px}.top-actions button{flex:none;white-space:nowrap}.canvas-heading{gap:6px;padding-left:10px;padding-right:10px}.canvas-heading h1{max-width:52vw;font-size:16px}.zoom-bar{flex:none}.zoom-bar button{padding:4px 6px}.workspace{grid-template-columns:40px minmax(0,1fr)}.canvas-shell{min-width:0}.statusbar{gap:6px;padding:0 8px;font-size:10px}}

/* V0.1.6: inline high-resolution enhancement controls. */
#upscalePanel{flex:none;padding-top:10px}.upscale-tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.upscale-tools label{margin:0;font-size:12px}.upscale-tools select{width:auto;max-width:100%;margin:0}.upscale-tools #upscaleModel{min-width:220px}#upscaleStatus{margin:8px 0 0;max-height:55px;overflow:auto}
/* V0.1.5: local edits in the existing viewer, no sidebar. */
#imageViewport.selecting{cursor:crosshair}
/* V0.1.9: per-image price line for the selected model / resolution / quality tier. */
.price-line{margin:8px 0 0;padding:6px 8px;border:1px solid var(--line);border-left:2px solid var(--accent);background:#24282a;color:var(--accent);font-size:12px;line-height:1.5}
/* V0.1.9: keep the size node (ratio + model + resolution + quality + price) readable without inner scrolling. */
[data-node="size"] .node-body label{margin-bottom:4px}
[data-node="size"] .node-body select{margin-bottom:8px;height:34px;padding:0 8px}
/* V0.1.47：比例从 8 个加到 10 个（官网 nano-banana 系多 5:4 / 4:5）。
   ⚠️ **必须走 5 列**：02 号节点内容余量实测是 0px（bodyClientH === bodyScrollH = 512），
   4 列时 10 个按钮会变成 3 行（+38px）→ 包围盒 1060 → 1098 → 缩放掉到 0.466，
   **低于 fit() 的 0.48 下限** → 一屏看不全。5 列仍是 2 行，高度一点不变。 */
[data-node="size"] .node-body .ratio-grid{gap:6px;margin-bottom:10px;grid-template-columns:repeat(5,1fr)}
[data-node="size"] .node-body .ratio-btn{height:32px}
[data-node="size"] .node-body .hint{line-height:1.5}
[data-node="size"] .node-body .price-line{margin-top:4px}
/* V0.1.9: make a locked select (e.g. quality tier on non-tier models) visibly disabled. */
#nodes select:disabled,[data-node="size"] .node-body select:disabled{opacity:.45;cursor:not-allowed}
/* V0.1.10: topic node now also holds the space-carrier select; keep the hint visible without inner scrolling. */
[data-node="topic"] .node-body textarea{min-height:88px}
[data-node="topic"] .node-body .field{margin-bottom:10px}
/* V0.1.11: copy length lock — limit row + over-limit counter. */
.limit-row{display:flex;align-items:center;gap:8px;margin:0 0 6px}
.limit-row label{margin:0;flex:none;font-size:13px}
.limit-row input{width:86px;margin:0;padding:6px 8px;text-align:right}
.limit-row .muted{font-size:11px}
/* V0.1.20: 02 号节点新增「生成张数」下拉，复用 03 号节点那条紧凑行的版式。 */
.limit-row select{width:auto;flex:none;margin:0;height:34px;padding:0 8px}
.over-limit{color:var(--error);font-weight:600}
#compareControls{flex:none}

/* V0.1.1: direct editing inside nodes; no inspector. */
.workspace{grid-template-columns:52px minmax(0,1fr)}
.canvas-shell{grid-template-rows:66px minmax(0,1fr) 42px 116px}
.canvas-heading{padding:14px 24px 0}.canvas-heading h1{font-size:18px;margin-top:4px}
.node-head{padding:13px 16px}.node-body{height:calc(100% - 48px);padding:16px;overflow:auto;cursor:default}
.node-body textarea{font-size:14px;min-height:100px;resize:vertical;line-height:1.65}
.node-body .field{margin:0 0 14px}.node-body .field-meta{font-size:11px}
.node-body label{font-size:13px;margin:0 0 8px}.node-body select{margin-bottom:16px;font-size:14px}
.node-body .ratio-grid{gap:7px;margin-bottom:18px}.node-body .ratio-btn{height:36px;font-size:12px;padding:6px}
.node-body .checkline{margin:18px 0;font-size:12px}.node-body .checkline input{min-width:15px}
.node-actions{display:flex;gap:8px;margin:12px 0}.node-actions .primary{flex:1}.node-actions button{font-size:13px}
.node-actions.secondary{margin:8px 0 14px}.node-actions.secondary button{padding:5px 10px;font-size:12px}
.node-run{width:100%;margin:0 0 14px;font-size:14px;padding:10px}
.node-result{height:202px;width:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#191e20;padding:0;color:var(--muted);font-size:13px}
.node-result img{max-width:100%;max-height:100%;object-fit:contain}.node-result span svg{display:block;width:32px;height:32px;stroke:#849092;fill:none;margin:0 auto 12px}
.node-result:disabled{opacity:1;cursor:default}

.history-tray{padding:9px 20px}.history-item{height:65px}.history-empty{margin:9px 0}
#topicInput{height:124px}#copyInput{height:376px}#promptInput{height:168px}#editInput{height:115px}
@media(max-width:760px){.workspace{grid-template-columns:42px minmax(0,1fr);height:calc(100dvh - 148px);min-height:600px}.rail{grid-row:auto}.canvas-shell{height:auto;min-height:0;grid-template-rows:60px minmax(0,1fr) 42px 110px}.node-body textarea{font-size:15px}.canvas-heading{padding:12px}.session-label{display:none}}

/* ═══════════════════════════════════════════════════════════════════
   V0.1.13 合并段：从锚定版搬来的样式（参考图节点 / 图片修改台 / 保护清单 /
   图钉评论 / 已通过版本 / 改图参数）。
   上面 1–63 行是工作室版原有样式，一字未动 ——
   特别是 V0.1.10 的「空间载体」与 V0.1.11 的「文案字数锁」两条，
   锚定版没有，必须留在这里。
   ═══════════════════════════════════════════════════════════════════ */

#imageViewport.selecting.panning{cursor:grabbing}
#localSelection{position:absolute;border:2px solid #dab783;background:rgba(218,183,131,.22);pointer-events:none}
#localEditPanel{flex:none;padding-top:10px}
.local-edit-tools{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--muted);margin-bottom:8px;flex-wrap:wrap}
.local-edit-tools span{flex:1}.local-edit-tools button{padding:4px 9px;font-size:12px}
/* ── 参考图节点（V0.5.0 成为独立节点，V0.5.1 重做预览区，V0.6.0 改成动态插入）──
   V0.5.0 第一版的错：缩略图 68px 高 + object-fit:cover ——
   等于把参考图中间横着裁一条出来，什么图都看不出是什么。
   用户原话：「你把参考图的框弄这么小，到景来的参考图都显示不全，我怎么去看参考图？而且一点都不美观」

   重做后定下的三条原则，V0.6.0 一条都没变：
   ① 预览框要够大：现在 188px 高（68 → 132 → 188）。
      V0.6.0 能再放大是因为「一个节点只放一张图」——
      V0.5.x 是三张并排挤在 580 宽里，每张只有约 178px 宽；
      现在一张独占 300 宽，宽高都宽裕了。
   ② **绝不裁图**：object-fit:contain —— 参考图是拿来看的，不是拿来填满格子的。
      多出来的空白用深色底吃掉，看起来是「装裱」而不是「缺一块」。
   ③ 点一下能看全图：整块预览区可点，弹出 #refZoom 看大图。

   节点高 332 是配着这套尺寸算的（实测，不是拍的）：
   node-body 的 clientHeight = 332 − 48（节点头）= 284
   284 − 内边距 12×2 = 260
   260 = 预览 188 + 间距 6 + 用途框 28 + 间距 6 + 连线行 26   ← 正好用完，留 6px 余量
   这里用 overflow:hidden 而不是 auto —— 差几像素就冒出滚动条比裁掉一像素更难看。 */
.node[data-kind="ref"] .node-body{padding:12px;overflow:hidden}
/* 参考图节点的编号是「参考图 N」而不是两位数字，所以要比流程节点那个角标宽一点。
   它是编号的**唯一出处**：提示词里写「参考图 2」时，指的就是这个 2。 */
.node-num.ref{padding:2px 7px;border:1px solid #6f604a;border-radius:9px;background:#2b2519;white-space:nowrap;font-size:10px}
/* 参考图节点的装饰性小圆点（.node:after，那个"接口"样的圆）要藏掉 ——
   它现在真的有一个可拖的连线端口（.node-port），两个圆挨在一起会让人分不清哪个能用。 */
.node[data-kind="ref"]:after{display:none}
.ref-slot{height:100%;display:flex;flex-direction:column;gap:6px}
.ref-thumb{position:relative;height:188px;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#101314;cursor:zoom-in}
.ref-thumb img{width:100%;height:100%;object-fit:contain;display:block}
.ref-thumb:hover{border-color:#61696a}
/* 编号角标：用户就是靠这个数字在提示词里指代参考图的（「参考图 2 的花纹」），
   所以写成完整词而不是光秃秃一个数字 —— 省得他猜该写「2」还是「参考图2」。
   V0.6.0 起编号是「只增不减」的（删掉中间一张，别的不会改号），
   否则用户写好的「参考图 2」会在删除后悄悄指向另一张图。 */
.ref-num{position:absolute;top:6px;left:6px;height:18px;padding:0 6px;border-radius:9px;background:#000000c4;color:#e8d7b6;font-size:11px;font-weight:600;line-height:18px;white-space:nowrap}
.ref-thumb .ref-del{position:absolute;top:6px;right:6px;width:18px;height:18px;padding:0;line-height:1;font-size:12px;border:0;border-radius:50%;background:#000000c4;color:#fff}
.ref-thumb .ref-del:hover{background:#8a2f22}
/* 尺寸角标压在预览区底部：不占布局高度，又能让人一眼知道加的是多大的图
   （改图时「参考图比原图小很多」是画面变糊的常见原因）。 */
.ref-meta{position:absolute;left:0;right:0;bottom:0;padding:3px 7px;background:linear-gradient(transparent,#000000c4);color:#cfd4d3;font-size:10px;text-align:right;letter-spacing:.4px}
.ref-note{height:28px;padding:0 8px;margin:0;font-size:12px}
.ref-note:disabled{opacity:.5}
/* 连线行：显示这张参考图当前连到了谁。点一下就断开（见 canvas.js 的绑定）。
   没连线时是一句提示，不占按钮的位置 —— 但高度和按钮一致，否则节点会抖一下。 */
.ref-links{height:26px;display:flex;align-items:center;gap:6px;overflow:hidden}
.ref-link{height:26px;padding:0 8px;font-size:11px;line-height:1;border-color:#7d6a4c;background:#332c22;color:#e8d7b6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ref-link:hover{background:#4a3d2b;border-color:#a98b5f}
.ref-link b{font-weight:400;color:var(--muted);margin-left:3px}
.ref-link-none{font-size:11px;color:#c08e6e;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 连线端口：节点**左边缘**那个可拖的圆点。只有参考图节点有 ——
   端口就是「可以连出去」的承诺，画在别的节点上等于骗人去试。
   拖拽期间（body.linking）放大并变亮，明确「现在正在拉线」。
   ⚠️ V0.1.43：端口从右侧改到左侧。原因是**端口位置和线的出线侧必须一致**：
   参考图排在修改台右边，sideFor() 按几何算出来的出线侧是「左出、右接」，
   而端口画在右边缘 —— 于是「端口在右、线从左边出」，用户得反着拖（实测投诉过）。
   现在端口在左，和线的起点重合，顺着拖就行。
   ⚠️ 别改回 right —— 除非参考图的默认落点也搬回修改台左边。 */
.node-port{position:absolute;left:-10px;right:auto;top:50%;margin-top:-10px;width:20px;height:20px;display:flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid #8c7a5c;background:#2c2620;color:#e8d7b6;font-size:11px;line-height:1;cursor:crosshair;touch-action:none;z-index:2}
.node-port:hover{border-color:var(--accent);background:#443728;box-shadow:0 0 0 3px #dab78326}
body.linking{cursor:crosshair}
body.linking .node-port{background:var(--accent);color:#211c15;border-color:var(--accent)}
/* 拖拽中「可以接这里」的目标：亮起 + 呼吸圈。
   只给合法目标（图片修改 / 图片修改台）加这个类 —— 全亮等于骗人去试 01/02/03。 */
.node.link-target{border-color:var(--accent);box-shadow:0 0 0 3px #dab78344,0 5px 16px #10111255}
.node.link-target .node-head{border-bottom-color:#857052}
/* 用户拉的连线：和固定的流程线区分开 —— 虚线 + 强调色，
   一眼就能看出「哪条线是我自己拉的」。 */
.edge-ref{stroke:var(--accent);stroke-width:1.6;stroke-dasharray:5 4}
/* 拖拽中的临时线：跟着指针走的那条。 */
.edge-temp{stroke:var(--accent);stroke-width:1.6;stroke-dasharray:3 3;opacity:.85}
/* 顶部栏那行提示（V0.6.0 从节点里搬过来）。
   为什么搬到顶部栏：参考图现在是「要几张加几张」，提示行不能再挂在某一个节点上；
   而且它说的是全局状态（总共几张、连了几条），放在固定的位置才不会被漏看。
   警告色只给「没连线」和「提示词里没提参考图」两种情况 ——
   加满上限不染色：那是「上限」不是「出错」，染色会让人以为哪里做错了。 */
.top-actions .ref-hint{max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:var(--muted)}
.top-actions .ref-hint.warn{color:#e0a58c}
#addRefBtn{border-color:#7d6a4c;color:#e8d7b6}
#addRefBtn:hover{background:#3b3226;border-color:#a98b5f}

/* 参考图放大预览（V0.5.1）。为什么自己写一层而不是复用 #viewer：
   #viewer 的 openImage() 会把 store.currentImage 换成传进去的那张 ——
   拿它预览参考图，等于把「正在改的那张图」悄悄换成参考图，用户再点「修改选区」
   改的就是参考图了。这里只做「看」，一行状态都不碰。
   [hidden] 必须显式写：本行声明了 display:flex，会盖掉浏览器对 [hidden] 的默认 display:none
   （.bench-body 踩过同一个坑）。 */
#refZoom{position:fixed;inset:0;z-index:80;display:flex;align-items:center;justify-content:center;padding:40px;background:#0b0d0eeb;cursor:zoom-out}
#refZoom[hidden]{display:none}
#refZoom img{max-width:100%;max-height:100%;object-fit:contain;border-radius:4px;box-shadow:0 24px 70px #000b}
#refZoom .ref-zoom-cap{position:absolute;bottom:16px;left:0;right:0;text-align:center;font-size:12px;color:var(--muted)}
.local-edit-input{display:flex;gap:10px;align-items:stretch}.local-edit-input textarea{flex:1;min-height:58px;max-height:120px;resize:vertical}.local-edit-input button{flex:none}
#localEditStatus{margin:6px 0 0;max-height:46px;overflow:auto}
/* V0.1.4: image-only zoom and pan; compare images share one transform. */
.viewer-zoom{display:flex;align-items:center;gap:12px;flex:none;padding:0 0 12px;color:var(--muted);font-size:12px;flex-wrap:wrap}
.viewer-zoom output{margin-left:auto;min-width:64px;text-align:right;font-variant-numeric:tabular-nums}
.viewer-zoom button{padding:5px 10px;font-size:12px}
#imageViewport{position:relative;flex:1;width:100%;min-height:0;overflow:hidden;touch-action:none;overscroll-behavior:contain;cursor:grab;user-select:none}
#imageViewport.panning{cursor:grabbing}
#compareWrap{transform-origin:0 0;pointer-events:none}
#viewImage,#compareBefore{-webkit-user-drag:none;user-select:none}
/* 让所有图片对浏览器的原生图片交互"隐形"。
   症状：左键拖画布时，鼠标旁边弹出 "@image#1:文件名.png" 这样的小菜单 / 提示框，
   看起来像嵌了别人的站。那不是我们的 UI —— 是浏览器（或扩展）检测到光标下有图，
   就挂出自己的图片浮层（拖拽残影、Lens/以图搜图、悬停菜单等）。

   三管齐下：
   - user-drag:none  → 不产生拖拽残影
   - pointer-events:none → 浏览器根本不认为光标下有图，悬停菜单 / Lens / 右键图片菜单全触发不了
   点击会穿透到父容器（#baseDrop / .history-item / 查看器），业务功能不受影响。
   画布的平移缩放本来就走 PointerEvent（#stage / #imageViewport），不依赖 img 收事件。 */
img{-webkit-user-drag:none;user-drag:none;pointer-events:none}

/* 画布内默认禁止选中文字。
   症状：在节点里按住左键拖一下，节点内的文字（"仅本次会话"、"0 字"、价格行、
   按钮文字…）整片变蓝，还跟着弹出浏览器对"选中文字"的原生拖拽浮层。
   看起来像个普通网页，完全不像无限画布。

   原因：左键拖动本该是"平移画布"，但平移的起手条件把整个 .node 排除在外，
   事件就落回浏览器，变成了"划选文字"。

   这里从源头掐掉：画布范围内一律不可选中。输入框 / 文本域必须恢复可选，
   否则没法编辑内容。 */
#stage,.node,#stage *{user-select:none;-webkit-user-select:none}
#stage input,#stage textarea,#stage [contenteditable]{user-select:text;-webkit-user-select:text}

/* V0.1.30：这里原本又复制了一份「V0.1.1 直接编辑」样式段（与文件上方 44–65 行逐字节相同）。
   已删除。上方那份照常生效，声明完全一致，所以视觉零变化。 */
/* V0.2.5: 局部编辑的「保护清单」。勾选项只影响提示词文本，不额外发请求。
   做成 chip 而不是多选框列表，是因为它要跟「严格模式」「看提示词」挤在同一行里。 */
.preserve-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px;font-size:12px;color:var(--muted)}
.preserve-bar .preserve-label{color:var(--dim)}
.preserve-bar .chip{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border:1px solid var(--line);border-radius:999px;background:#202426;cursor:pointer;user-select:none}
.preserve-bar .chip:hover{border-color:#61696a}
.preserve-bar .chip:has(input:checked){border-color:var(--accent);color:var(--accent);background:var(--accent-dark)}
.preserve-bar .chip input{margin:0;accent-color:var(--accent)}
.preserve-bar .chip-strict{border-style:dashed}
.preserve-bar #togglePromptPreview{margin-left:auto;padding:3px 9px;font-size:12px}
#promptPreview,#editPromptPreview{margin:8px 0 0;padding:8px 10px;max-height:140px;overflow:auto;background:#161a1c;border:1px solid var(--line);border-radius:6px;font-family:ui-monospace,Consolas,monospace;font-size:11px;line-height:1.6;color:var(--muted);white-space:pre-wrap;word-break:break-word}
/* V0.1.17: 05 号「图片修改」并入 06 号台子的「整图重绘」档，底图选择收成一行。
   缩略图用 object-fit:contain —— 它是给人确认「改的是哪一张」的，不能裁。 */
.whole-base{display:flex;align-items:center;gap:8px;margin-bottom:9px}
.whole-base-label{flex:none;font-size:12px;color:var(--dim)}
.whole-thumb{flex:none;width:48px;height:48px;padding:0;border:1px dashed #5b666a;border-radius:6px;background:#1a1e20;overflow:hidden;display:grid;place-items:center;font-size:10px;line-height:1.2;color:var(--muted);text-align:center}
.whole-thumb img{width:100%;height:100%;object-fit:contain;display:block}
.whole-thumb.dragover{background:#383126;border-color:var(--accent)}
.whole-mini{flex:none;padding:4px 9px;font-size:12px}
#wholePanel .price-line{margin-top:8px}
/* V0.2.5: 已通过版本标记。绿色边框 + 右上角角标，在缩略图里一眼能认出来。 */
.history-item.approved{border-color:var(--success)}
/* V0.1.30（检查报告第 18 条）：成果栏缩略图原先是 object-fit:cover（裁掉两边）。
   参考图 / 底图 / 整图重绘的缩略图早就都是 contain 了，只剩这里还在裁 —— 出图是挑候选用的，
   裁掉两边就看不出构图差别。已在文件开头的 .history-item img 里改成 contain
   （声明仍只有那一处，这里不重复写），配合 .history-item 自带的 #161a1c 底色，
   比例不符时上下（或左右）留黑边，但整张图完整可见。
   ⚠️ 并排对比档的 .grid-chip img 也一并改了（见文件下方 .grid-chip img 处的说明）。 */
.history-item .approved-tag{position:absolute;top:0;right:0;bottom:auto;left:auto;padding:2px 6px;background:var(--success);color:#12180f;font-size:10px;font-weight:600;border-radius:0 0 0 6px}
/* V0.1.35 成果栏：单张删除 × + 批量勾选角标 + 管理条。
   ⚠️ `.history-item span` 是「绝对定位到底部的标签」，所以这两个角标必须像
   `.approved-tag` 那样把 top/right/bottom/left 全部显式覆盖，否则会被拉到底部去。 */
.history-item .hist-del{position:absolute;top:0;right:0;bottom:auto;left:auto;width:20px;height:20px;display:flex;align-items:center;justify-content:center;padding:0;font-size:14px;line-height:1;background:#151a1dcc;color:#e8e2d8;border-radius:0 0 0 7px;cursor:pointer;z-index:3}
.history-item .hist-del:hover{background:#a33b2e;color:#fff}
.history-item .pick-tag{position:absolute;top:0;left:0;right:auto;bottom:auto;padding:2px 7px;background:var(--accent);color:#1a1208;font-size:11px;font-weight:700;border-radius:0 0 7px 0;box-shadow:0 0 0 1px #00000059;z-index:3}
.history-item.picked{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}
/* ⚠️ #pickBar 是 display:flex —— `[hidden]` 会被它顶掉，必须显式写 [hidden]{display:none}。 */
/* V0.1.35：「管理」要贴着它管的那个「N 张」。
   ⚠️ 不能靠 `.history-title button{margin-left:auto}` —— 三个按钮各分一份空白，
   「管理」会被推到离计数 604px 的中间（实测 x=760），看着像飘在半空。 */
.history-title #historyManage{margin-left:0}
.history-title #pickBar{display:flex;align-items:center;gap:8px;margin-left:auto}
.history-title #pickBar[hidden]{display:none}
.history-title #pickBar span{color:var(--text)}
.history-title #pickBar button{margin-left:0;color:var(--muted)}
.history-title #pickBar button:hover:not(:disabled){color:var(--text)}
.history-title #pickBar #pickDelete{color:#e08b74}
.history-title #pickBar #pickDelete:hover:not(:disabled){color:#fff;background:#a33b2e}
.history-title #pickBar #pickDelete:disabled{color:var(--dim);cursor:not-allowed}
.history-title.selecting #backToApproved,.history-title.selecting #saveImages{display:none}
.history-title #saveImages{margin-left:auto}
.history-title #backToApproved{margin-left:0;border:1px solid var(--line);border-radius:6px;padding:3px 9px;color:var(--text)}
.history-title #backToApproved:hover:not(:disabled){border-color:var(--success);color:var(--success)}
.viewer-actions #approveImage{border-color:#5c6b5e}
.viewer-actions #approveImage:hover:not(:disabled){border-color:var(--success);color:var(--success)}
/* V0.2.6: 保护清单现在三处共用（指哪改哪 / 图钉评论 / 画布上的「图片修改」节点），
   「看提示词」按钮挪到 .preserve-bar 外面 —— 里面的内容由 JS 整块重写，放里面会被覆盖掉。 */
.preserve-row{display:flex;align-items:center;gap:8px;margin-top:8px}
.preserve-row .preserve-bar{flex:1;margin-top:0}
.preserve-row>button{flex:none;padding:3px 9px;font-size:12px}
/* V0.2.6: 画布节点的提示词预览。用原生 <details>，不需要 JS 控制开合。 */



/* V0.2.6: 节点里的保护清单默认折叠。展开的 chip 条有 90px 高，
   节点本来就被底部成果栏挡着一截，摊开会让整块内容被推出可视区。
   摘要那行一直可见，所以"正在保护什么"不用展开也能看到。 */




/* V0.2.6: 图钉评论。图钉画在 #pinLayer 里 —— 它在 #compareWrap 内部，
   跟着缩放平移一起动，而且继承 pointer-events:none，不会挡住点击放新图钉。 */
#commentPanel{flex:none;padding-top:10px}
.comment-tools{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--muted);margin-bottom:8px;flex-wrap:wrap}
.comment-tools span{flex:1}.comment-tools button{padding:4px 9px;font-size:12px}
/* V0.1.36：下面那列逐条输入框已按用户要求整块去掉 —— 图钉内容改成「扎在哪就在哪弹小窗」。
   原来 .pin-list 的 min-height:132px 是为了「面板高度不变、图钉不跟着乱跳」；
   现在列表没了，高度天然恒定，这个顾虑自动消失。
   .pin-num 保留：就地弹窗的标题里还在用它当编号徽章。 */
.pin-num{flex:none;width:22px;height:22px;display:grid;place-items:center;border-radius:50%;background:var(--accent);color:#211c15;font-size:11px;font-weight:600}
/* V0.1.36：这一行原来是 max-height:46px、靠上面 .pin-list 的 min-height:132px 撑着面板。
   列表去掉之后它得自己把高度钉死 —— 用固定 46px（正好放下 2 行 19.8px 的字）而不是
   min-height，因为状态文字长短不一（「还没有图钉」1 行、完成语可能 2 行），
   高度一变，#imageViewport 就跟着变，所有图钉会**整片视觉位移**。
   46px 固定 = 文字在 1~2 行之间怎么变，图片区纹丝不动。超出 2 行的走 overflow:auto。 */
#commentStatus{margin:6px 0 10px;height:46px;overflow:auto}
#runPins{width:100%;font-size:13px;padding:9px}
#pinLayer{position:absolute;inset:0;pointer-events:none}
.pin{position:absolute;width:28px;height:28px;margin:-14px 0 0 -14px;display:grid;place-items:center;border-radius:50%;background:var(--accent);color:#211c15;font-size:12px;font-weight:600;border:2px solid #17191b}
/* V0.1.36：图钉变成可点的 —— 点它就弹出「这一处要改什么」。
   ⚠️ #pinLayer 是 pointer-events:none（整层不挡图片的点击），
   必须在这里单独放开，否则图钉收不到任何鼠标事件。
   ⚠️ 不加 transform 过渡：位移/缩放是 drawPins() 用行内 transform 写的，
   这里再加过渡会和它打架。 */
.pin{cursor:pointer;pointer-events:auto}
.pin:hover,.pin:focus-visible{box-shadow:0 0 0 3px #dab78366;outline:none}

/* ── 图钉就地弹窗（V0.1.36）──
   扎在哪就在哪弹。位置由 placePinDialog() 按「图钉的屏幕矩形」算，不是图片坐标，
   所以图片放大到 400% 时文字也不会跟着糊、也不会被祖先的 scale 缩小。
   ⚠️ ::backdrop 必须透明：这是「边看图边写」的小窗，盖一层深色蒙版会把要改的地方遮掉。
   ⚠️ 小三角要画两层（::before 描边色 + ::after 底色）才像一根实心箭头；
      .flip 是「右边放不下、翻到图钉左边」时把箭头翻到右侧。 */
/* ⚠️ overflow:visible 是必须的，不是随手写的：
   Chrome 对 dialog:modal（showModal() 打开的那种）的**浏览器默认样式**带 `overflow:auto`，
   会把挂在边框外面的 ::before/::after 两个小三角整个裁掉 —— 实测伪元素算出来
   display:block / width:0 / border-right:9px，但屏幕上什么都不显示，极难从现象反推。
   内容本来就不会溢出（textarea 固定两行），所以这里可以放心改成 visible。 */
.pin-dialog{position:fixed;margin:0;width:300px;max-width:calc(100vw - 20px);padding:12px;border:1px solid #50595d;border-radius:12px;background:#24292c;color:var(--text);box-shadow:0 18px 60px #0009;overflow:visible}
.pin-dialog::backdrop{background:transparent}
.pin-dialog::before,.pin-dialog::after{content:"";position:absolute;top:var(--arrow-top,24px);width:0;height:0;border:9px solid transparent}
.pin-dialog::before{left:-9px;margin-top:-9px;border-left:0;border-right-color:#50595d}
.pin-dialog::after{left:-8px;margin-top:-8px;border-width:8px;border-left:0;border-right-color:#24292c}
.pin-dialog.flip::before{left:auto;right:-9px;border-right:0;border-left:9px solid #50595d}
.pin-dialog.flip::after{left:auto;right:-8px;border-right:0;border-left:8px solid #24292c}
/* 窗口太窄、左右都塞不下 300px 时退化成贴着视口边 —— 这时小三角指不到图钉，
   宁可不画。判据在 placePinDialog() 里（roomR / roomL 都不够）。 */
.pin-dialog.no-arrow::before,.pin-dialog.no-arrow::after{display:none}
.pd-head{display:flex;align-items:center;gap:8px;margin-bottom:9px}
.pd-title{font-size:12.5px;color:var(--text)}
.pd-count{margin-left:auto;font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums}
/* ⚠️ 全局 textarea{min-height:150px} 必须覆盖掉，否则这个小窗会被撑到 150px 高。 */
.pin-dialog textarea{min-height:0;font-size:13px;line-height:1.55;padding:8px 10px;resize:none}
.pd-foot{display:flex;align-items:center;gap:6px;margin-top:10px}
.pd-foot button{padding:5px 9px;font-size:11.5px;border-color:var(--line);white-space:nowrap}
#pdDelete{margin-left:auto;color:#e08b74}
#pdDelete:hover:not(:disabled){border-color:var(--error);color:var(--error)}
#pdDone{padding:5px 12px}

/* ── V0.3.1: 图片修改台 ──
   台子是画布上的第 06 个节点（「图片修改台」），不是贴在画布旁边的侧栏。
   它和别的节点完全同构：跟着 view 平移缩放、拖标题就能挪走、被 #stage 裁切。
   唯一的不同是里面的内容是常驻 DOM（#benchBody）——
   节点每次 renderNodes() 都是整块重渲染的，syncBench() 负责把它搬回新的挂载点，
   所以事件监听器和图片缩放状态都不会丢。

   为什么不做成右侧侧栏：那是 V0.3.0 的做法，用户明确否掉了 ——
   改图时要一边看着画布上的其它节点一边改，固定在右侧等于把画布挤窄一半。 */
/* 台子的 node-body 不参与通用内边距：里面的 .bench-body 自己排布，
   通用 padding:16px 会让 580 宽的节点只剩 548 可用。 */
.node[data-node="bench"] .node-body{padding:0;overflow:hidden;cursor:default}
.bench-mount{height:100%;display:flex;min-width:0}
/* .bench-body 自己声明了 display:flex，会盖掉浏览器对 [hidden] 的默认 display:none */
.bench-body{flex:1;min-width:0;height:100%;display:flex;flex-direction:column;gap:8px;padding:10px 12px;overflow:hidden}
.bench-body[hidden]{display:none}
/* V0.1.28: gap 6→5、按钮内边距 9→8。加了「导入图片」之后标签行正好卡满 554px，
   benchLabel 只剩 110px —— 刚好等于「生成 01a · 1024×1024」的宽度，零余量。
   各收 1px 换回 18px 缓冲，高度和外观都没变。 */
.bench-tabs{flex:none;display:flex;align-items:center;gap:5px;min-width:0}
.bench-tabs button{flex:none;font-size:12px;border-color:transparent;padding:5px 8px}
.bench-tabs button.active{border-color:#817055;background:#393327;color:var(--accent)}
.bench-tabs #benchViewer{margin-left:auto}
/* V0.1.28: 外部导入入口常驻在标签行。原来只有「整图重绘」档的底图区有「上传底图」，
   而台子平时停在「指哪改哪」—— 那一档整块底图区都不显示，入口等于不存在。
   虚线边框 + 强调色，让它读起来是一个「动作」，不是第 5 个档位。 */
.bench-tabs #importImage{border-style:dashed;border-color:#817055;color:var(--accent)}
.bench-tabs #importImage:hover:not(:disabled){background:#393327;border-color:var(--accent)}
/* 台子常驻在画布上，看不到弹窗的标题栏 —— 当前在改哪张图要写在这里 */
.bench-label{flex:1 1 auto;min-width:0;text-align:right;font-size:11px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 图片区保底 340：台子里的图片区是主工作面，不能被下面的面板挤成一条缝。
   实测 880 高的节点里「图钉评论」档只剩 242 —— 800×600 的图在里面只有 322×242，
   连拖框选区都费劲。340 是「还能正常框选」的下限。 */
.bench-stage{flex:1 1 auto;min-height:340px;display:flex;min-width:0}
.bench-stage>.image-stage{flex:1;width:100%;padding:10px}
/* 大图被「放大查看」搬去弹窗时，槽是空的 —— 留一句话，而不是一块空黑板 */
.bench-stage:empty::before{content:'图片正在弹窗里查看';display:flex;align-items:center;justify-content:center;width:100%;color:var(--dim);font-size:12px;border:1px dashed var(--line);border-radius:6px}
.bench-panels{flex:0 1 auto;max-height:42%;overflow:auto}
/* ── V0.3.2: 台子自己的改图参数 ──
   默认折叠。折叠时是一行「改图参数：模型 · 档位 · ¥/张」，不占多少高度；
   展开才有三个下拉框 —— 这块平时不用碰，但一旦要调就必须能在台子里调完，
   不能再跑回 02 号节点（那会把生图参数一起改掉）。 */
.bench-config{flex:none;font-size:12px;color:var(--muted);min-width:0}
.bench-config>summary{cursor:pointer;display:flex;align-items:center;gap:8px;min-width:0;padding:5px 8px;background:#1d2123;border:1px solid var(--line);border-radius:6px;line-height:1.5}
.bench-config>summary:hover{border-color:#61696a}
.bench-config>summary::marker{color:var(--accent)}
.bench-config-hint{flex:1;min-width:0;color:var(--dim);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bench-config[open]>summary{border-radius:6px 6px 0 0;border-bottom-color:transparent}
.bench-config-body{padding:8px 8px 2px;background:#1b1f21;border:1px solid var(--line);border-top:0;border-radius:0 0 6px 6px}
/* 节点通用样式给 label/select 加了 13px / 16px 下边距，这里必须压回去，否则三个下拉框会占掉 150px */
.bench-config-body label{display:block;font-size:12px;margin:0 0 4px}
.bench-config-body select{width:100%;height:32px;padding:0 8px;margin-bottom:8px;font-size:13px}
.bench-config-pair{display:flex;gap:8px}
.bench-config-pair>div{flex:1;min-width:0}
.bench-config-body .hint{margin:0;line-height:1.5}
/* 「这一档会把当前图缩下去」是丢细节的直接原因，用警告色顶出来 */
.bench-config-body .hint.warn{color:var(--error)}
/* 面板顶部的内边距是给弹窗底部留的；搬进台子后上面已经有标签页了 */
#localEditPanel,#commentPanel{padding-top:0}
/* 图片显示区在两个宿主之间搬家，尺寸由所在槽决定，两边都不写死 */
.stage-slot{flex:1;min-width:0;min-height:0;display:flex}
.stage-slot>.image-stage{flex:1;width:100%}
/* 缩放条长在 .image-stage 里面，而它是 align-items:center 的列容器 ——
   不给宽度就会缩成内容宽，「恢复全图」会飘到中间去。 */
.image-stage>.viewer-zoom{width:100%}
/* 台子的操作行必须换行：580px 也放不下「警告 + 三个按钮 + 状态」一行 */
.bench-body .viewer-actions{flex-wrap:wrap;gap:8px;padding-top:10px;border-top:1px solid var(--line)}
.bench-body .viewer-actions>span{flex:1 1 100%}
.bench-body .viewer-actions button{font-size:12px}
/* V0.1.1 去掉右侧检查器后，1150px 断点里还留着三列的旧写法：
   第 3 列是个空的 310px，白白吃掉画布宽度。顺手清掉。 */
@media(max-width:1150px){.workspace{grid-template-columns:52px minmax(0,1fr)}}
/* ── 提示词助手（V0.1.14）──
   复用 .dialog / .viewer-head / .viewer-tabs / .dialog-actions，只加宽和分组网格。
   刻意**不用 data-view** 属性：lockLocalUI 会禁用全部 [data-view]，台子的标签切换
   又按 dataset.view 设高亮 —— 复用会让「任务运行时禁用」和「高亮当前档」互相误伤。
   这里用 data-ph，互不干扰。 */
.dialog.prompt-helper{width:760px}
.ph-main{flex:1;min-height:0;overflow:auto}
#phSegGroups{display:grid;gap:14px;margin-top:6px}
.ph-group{display:grid;gap:7px}
.ph-group>span{font-size:12px;color:var(--muted)}
.ph-chips{display:flex;flex-wrap:wrap;gap:6px}
.ph-chip{font-size:12px;padding:5px 10px;border-color:var(--line);border-radius:999px}
.ph-chip.on{background:var(--accent);color:#211c15;border-color:var(--accent)}
/* 文化墙类别档（V0.1.25）：
   ① 组与组之间要有间距，否则「材质工艺」的标签会紧贴上一组的 chip；
   ② 「自动带上的约束」不可取消，但**不能沿用 disabled 的默认置灰** —— 那样字几乎看不清。
      改成虚线边框 + 正常文字色，表达「这是自动生效的提示，不是可选项」。 */
#catDetail{display:grid;gap:12px}
/* 12 个类别用等宽网格排，避免 flex 换行排出「11 + 1」这种尾巴（用户要过「美观」）。
   auto-fit + minmax 让窄屏自动降列数，不会把 chip 挤成一条。 */
#catChips{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px}
@media (max-width:640px){#catChips{grid-template-columns:repeat(4,minmax(0,1fr))}}
#catChips .ph-chip{width:100%}
#catDetail .ph-chip[disabled]{opacity:1;cursor:default;background:transparent;color:var(--muted);border:1px dashed var(--line)}
/* 负面约束组里「已用在正面」的项（V0.1.26）—— 和 segmentLines() 的过滤是同一套判据，
   看得见但不会写进提示词。刻意不用按钮那种半透明禁用态：那套在这块深色底上几乎看不清字。 */
#phSegGroups .ph-chip.taken{opacity:1;cursor:not-allowed;background:transparent;color:var(--muted);border:1px dashed var(--line)}
/* 负面约束组有 10 项，用 flex 换行会排成「9 + 1」，「长城板」变成孤零零的一个 ——
   和 V0.1.25 修过的类别 chips 是同一类毛病。这里单独给它 5 列网格，10 项正好 2 行。
   字号降到 11px 是为了让最长的「变形的手和手指」在单列里排得下、不折行。 */
#phSegGroups .ph-group[data-neg] .ph-chips{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:6px}
#phSegGroups .ph-group[data-neg] .ph-chip{width:100%;padding:5px 6px;font-size:11px}
@media (max-width:640px){#phSegGroups .ph-group[data-neg] .ph-chips{grid-template-columns:repeat(3,minmax(0,1fr))}}
/* 变体档（V0.1.26）：沿用 ph-group / ph-chips，多一块能直接看出差别的对比预览。 */
#varOpts{display:grid;gap:12px}
#varOpts .ph-preview{max-height:200px}
#varDim{margin-bottom:14px}



.ph-preview{margin:16px 0 0;padding:14px;background:#1c1f21;border:1px solid var(--line);border-radius:8px;font-size:12px;line-height:1.75;white-space:pre-wrap;max-height:260px;overflow:auto;color:var(--text);font-family:inherit}

/* ── V0.1.37：06 号「模板库」节点（从提示词助手的「套模板」页签搬出来）──────────
   横向三栏：左 模板列表 / 中 短变量槽（两列）/ 右 正文 + 实时预览。
   节点 body 的内容区是 710×286（742 宽 − 16×2 padding；366 高 − head 51 − 15×2 padding），
   所以 .tpl-grid 固定 240 高、底部按钮行 34 + 上边距 9，加起来 283，正好塞得下。
   ⚠️ 改节点高度（nodes 里 tpl 那条的 h）就要回来重算这个 240。
   ⚠️ 「正文」**不在中栏**，它是右栏预览上方的独立一块 —— 节点高 366 已顶到包围盒下沿
   （y 662..1028）、右边紧邻 05 号台子，中栏可视只有 215px；8~9 个槽排两列要 5 行
   （正文 textarea 独占一行）根本放不下，会切在「视觉风格 / 版式」中间。
   正文搬走之后中栏只剩 4 行短字段（主题/主标题/副标题/落款/视觉风格/版式/字体方向），
   正好 215px 放下、不用滚。 */
.tpl-grid{display:grid;grid-template-columns:168px minmax(0,1fr) 226px;gap:10px;height:240px}
.tpl-col{display:flex;flex-direction:column;min-height:0;min-width:0}
/* 右栏是三块：正文槽 / 「预览」小标题 / 预览正文。用 gap 隔开，
   并去掉小标题自带的 padding-bottom（否则正文槽和预览之间会空 14px）。 */
.tpl-col-prev{gap:8px}
.tpl-col-prev>.tpl-col-head{padding-bottom:0}
.tpl-col-head{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--dim);padding-bottom:6px;flex:none}
.tpl-count{color:var(--accent);font-size:10px;border:1px solid #6f604a;border-radius:999px;padding:0 6px}
.tpl-list,.tpl-vars,.tpl-preview{overflow:auto;flex:1;min-height:0}
.tpl-list{display:flex;flex-direction:column;gap:3px}
.tpl-item{flex:none;width:100%;text-align:left;font-size:12px;line-height:1.4;padding:4px 8px;border-radius:6px;border:1px solid transparent;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:transparent}
.tpl-item:hover{background:#2f3437;border-color:#4a5256}
.tpl-item.active{background:#373127;border-color:var(--accent);color:var(--accent)}
.tpl-item i{font-style:normal;font-size:10px;color:var(--accent);border:1px solid #6f604a;border-radius:999px;padding:0 5px;margin-left:5px}
.tpl-vars{display:grid;grid-template-columns:1fr 1fr;gap:5px 7px;align-content:start}
/* ⚠️ 必须写成 .tpl-grid .tpl-field 这种两段式：全局有
   `.node-body label{margin:0 0 8px}`（0,1,1）和 `.node-body select{margin-bottom:16px}`（0,1,1），
   单写 `.tpl-field`（0,1,0）/ `.tpl-field select`（0,1,1）压不住前者、只靠「后来居上」压后者。
   不压掉的话：每个槽白吃 8px、每个下拉框白吃 16px，中栏 4 行凭空多出 64px 直接滚起来。 */
.tpl-grid .tpl-field{display:flex;flex-direction:column;gap:2px;margin:0;min-width:0}
.tpl-grid .tpl-field>span{font-size:10.5px;color:var(--dim);line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tpl-grid .tpl-field input,.tpl-grid .tpl-field select,.tpl-grid .tpl-field textarea{padding:4px 6px;font-size:12px;border-radius:5px;margin:0}
.tpl-grid .tpl-field textarea{min-height:0;height:46px;resize:none;line-height:1.5}
/* 右栏的「正文」：不参与 .tpl-preview 的伸缩，固定 3 整行高（12px×1.5×3 + 8 padding + 2 border = 64）。
   226px 宽下约 18 字/行，长文内部自己滚。 */
.tpl-grid .tpl-body{flex:none}
.tpl-grid .tpl-field.tpl-body textarea{height:64px}
.tpl-preview{margin:0;padding:9px 10px;background:#1c1f21;border:1px solid var(--line);border-radius:7px;font-size:11.5px;line-height:1.6;white-space:pre-wrap;word-break:break-word;color:var(--text);font-family:inherit}
.tpl-foot{display:flex;align-items:center;gap:8px;margin-top:9px}
.tpl-hint{flex:1;min-width:0;font-size:11px;color:var(--dim);line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tpl-foot button{flex:none;font-size:11.5px;padding:6px 11px;white-space:nowrap}

/* ═══════════════════════════════════════════════════════════════════
   V0.1.29 新增：局部编辑的两种语义（改造 / 擦除）
   「改造」= 把选区里的东西换成别的内容（原来的行为）。
   「擦除」= 把选区里的东西拿掉，用周围内容补全（生成式移除）。
   两者走同一条请求链路（image + mask + prompt），区别只在提示词那一句话 ——
   所以这里只是一个模式开关，不新增请求类型、不额外计费。
   为什么做成 segmented 而不是下拉：这个开关决定「发出去的是哪一套提示词」，
   必须一眼看见当前在哪一档；下拉框收起来时看不出状态。
   ⚠️ 必须写 .local-edit-tools .local-mode 两级选择器：上面那条
   `.local-edit-tools span{flex:1}` 优先级是 (0,1,1)，只写 .local-mode 是 (0,1,0)，
   压不住它，整个 segmented 会被拉成满宽。 */
.local-edit-tools .local-mode{display:flex;flex:none;border:1px solid var(--line);border-radius:6px;overflow:hidden}
.local-edit-tools .local-mode button{padding:4px 10px;font-size:12px;border:0;border-radius:0;background:transparent;color:var(--muted)}
.local-edit-tools .local-mode button+button{border-left:1px solid var(--line)}
.local-edit-tools .local-mode button.active{background:var(--accent-dark);color:var(--accent)}
.local-edit-tools .local-mode button:hover:not(.active):not(:disabled){background:#34393b;color:var(--text)}

/* ═══════════════════════════════════════════════════════════════════
   V0.1.29 新增：结果 A/B 并排对比（查看器第 4 档）
   为什么是「一排等宽图」而不是「自己写个 canvas 拼图」：
   并排对比要解决的是「选图」，不是「出图」。等宽 + 按比例自适应高度，
   两张候选的构图差异一眼能看出来；做成拼图反而要重新编码、还丢原图。
   缩放为什么改的是「行宽」而不是每张图的 transform：
   行宽变宽 → 每格跟着变宽 → 天然同步，不需要逐张设置、也不会失步。
   离开 100% 时行比容器宽，由 .grid-wrap 横向滚动 —— 一行一起滚，
   对比时位置始终对齐（这是并排对比的关键，各滚各的就没法比了）。 */
#gridPanel{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}
.grid-tools{display:flex;align-items:center;gap:10px;flex:none;color:var(--muted);font-size:12px;flex-wrap:wrap}
.grid-tools>span{flex:1;min-width:200px}
.grid-tools label{margin:0;font-size:12px;color:var(--muted)}
.grid-tools input[type=range]{width:150px;flex:none;margin:0}
.grid-tools output{min-width:48px;text-align:right;font-variant-numeric:tabular-nums;color:var(--text)}
.grid-pick{display:flex;gap:8px;overflow-x:auto;flex:none;padding-bottom:4px}
.grid-chip{width:86px;min-width:86px;height:62px;padding:0;position:relative;overflow:hidden;background:#161a1c;border:1px solid #495155;border-radius:6px}
/* V0.1.30：与成果栏同一处理（检查报告 #18）。并排对比的勾选盘也是「挑候选」用的，
   cover 会把横图裁掉左右、竖图裁掉上下 —— 两个候选可能因此看起来差不多，
   而实际差别正好在被裁掉的那部分。改成 contain，配合 .grid-chip 自带的 #161a1c 底色留黑边。 */
.grid-chip img{width:100%;height:100%;object-fit:contain}
.grid-chip span{position:absolute;left:0;right:0;bottom:0;font-size:9px;padding:2px 4px;background:#151a1dde;text-align:left;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.grid-chip.on{border-color:var(--accent);box-shadow:0 0 0 1px #dab78366}
.grid-chip.on span{color:var(--accent)}
/* display:flex + align-items:safe center：图比工作区矮时垂直居中（不然下方留一大块空框，
   看着像没加载出来）；图比工作区高时 safe 保证顶部不被裁掉、正常滚动。
   不加 justify-content:center —— 横向溢出时居中会把左边那张裁掉，并排对比最左边那张恰恰是最常看的。 */
.grid-wrap{flex:1;min-height:0;display:flex;align-items:safe center;overflow:auto;background:#15191b;border:1px solid #363d41;border-radius:6px;padding:12px}
.grid-row{display:flex;gap:12px;align-items:flex-start;flex:none}
.grid-cell{margin:0;flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:6px}
/* 宽度定、高度 auto：整张图看得见，不做裁切（素材类预览一律 contain 的同一理由）。 */
.grid-cell img{width:100%;height:auto;display:block;background:#101416;border:1px solid #2c3336;border-radius:4px}
.grid-cell figcaption{display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--muted);line-height:1.5}
.grid-cell figcaption strong{color:var(--text);font-weight:500}
.grid-cell figcaption button{font-size:11px;padding:4px 8px;align-self:flex-start}
#gridStatus{margin:0;flex:none}

/* ═══════════════════════════════════════════════════════════════════
   V0.1.29 新增：参数预设弹窗
   宽 640px：预设要在一行里放下「名字 + 存于什么时候 + 一条摘要 + 四个按钮」，
   480px（.dialog 的默认宽）会把摘要挤成两三行，扫一眼看不出这套是什么。 */
.dialog.presets{width:640px}
/* 帮助弹窗（V0.1.39）：原来是一坨 alert()，无排版、不能搜、不能复制。
   照 logsDialog 的 dialog 骨架，只是加宽 + 加目录 + 分节排版。 */
.dialog.help{width:760px}
/* V0.1.44：06 号模板库「折页折法速查」弹窗 —— 整页 iframe 嵌那份手册。
   手册是自包含的深色单文件，所以这里只做「框」：定尺寸、抹掉 .dialog 的 padding、
   头部固定、iframe 吃满剩余高度。用 dvh 而不是 vh，免得移动端地址栏把底部切掉。 */
.dialog.fold-guide{width:min(1180px,94vw);height:min(880px,90dvh);padding:0;overflow:hidden;display:flex;flex-direction:column}
.dialog.fold-guide .rc-head{flex:none;border-bottom:1px solid var(--line)}
.dialog.fold-guide iframe{flex:1;min-height:0;width:100%;border:0;display:block;background:#17191b}
/* ═══════════════════════════════════════════════════════════════════
   V0.1.45 新增：从客户资料导入弹窗
   宽 720px：原文区要一眼看下十几行，480px（.dialog 默认宽）读长文太窄。
   ⚠️ 骨架走 flex：头部固定、.di-body 自己滚。**不要用 position:sticky 让头部吸顶** ——
      实测在 <dialog> 上 sticky 表现不稳，滚到底时头部会跑到正文中间去（截图验过）。
      这套 flex 骨架和 fold-guide 那个弹窗是同一个套路，稳当。
   ⚠️ display / max-height / overflow 都显式写死，不依赖 .dialog 基础样式 ——
      基础样式在一行压缩过的 CSS 里，继承过来的值不好预估。 */
.dialog.doc-import{display:flex;flex-direction:column;width:min(720px,94vw);max-height:min(880px,90dvh);overflow:hidden}
.dialog.doc-import .rc-head{flex:none;border-bottom:1px solid var(--line)}
.di-body{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden}
.di-lead{padding:12px 18px 0;margin:0}
.di-step{padding:12px 18px 0}
/* ⚠️ [hidden] 会被 display:flex 顶掉 —— 这几个区块一律显式写死。 */
.di-step[hidden]{display:none}
.di-step h4{display:flex;align-items:center;gap:8px;margin:0 0 6px;font-size:13px;font-weight:600;color:#d5d1c8}
.di-step .hint{margin:0 0 8px}
.di-tag{font-size:11px;font-weight:400;color:var(--accent);border:1px solid var(--line);border-radius:20px;padding:1px 8px}
.di-drop{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;min-height:86px;padding:14px;border:1.5px dashed var(--line);border-radius:10px;background:#1a1e20;cursor:pointer;text-align:center}
.di-drop strong{font-size:13px;font-weight:600;color:#d5d1c8}
.di-drop span{font-size:11.5px;color:var(--muted)}
.di-drop:hover,.di-drop.dragover{border-color:var(--accent);background:#22262a}
.di-drop.dragover strong{color:var(--accent)}
.di-status{min-height:0;margin:8px 0 0}
#diTextArea,#diResult{width:100%;box-sizing:border-box;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:12.5px;line-height:1.75;color:#cfcbc2;background:#17191b;border:1px solid var(--line);border-radius:8px;padding:11px 13px;resize:vertical}
#diTextArea{height:150px}
#diResult{height:140px}
#diTextArea:focus,#diResult:focus{outline:none;border-color:var(--accent)}
.di-row{display:flex;align-items:center;gap:10px;margin:8px 0 0}
.di-row .hint{flex:1;margin:0}
.di-row button{flex:none}
.di-fill{justify-content:flex-end;padding-bottom:18px}
.di-fill button{font-size:12.5px;padding:6px 14px}
.di-imgs{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;margin:0 0 4px}
.di-img{position:relative;display:flex;flex-direction:column;gap:4px;padding:6px;border:1px solid var(--line);border-radius:8px;background:#1a1e20;cursor:pointer;font-size:11px;color:var(--muted)}
/* 素材预览一律 contain（硬约束）：文档里的图长宽比什么都有，cover 会裁掉内容 */
.di-img img{width:100%;height:74px;object-fit:contain;background:#121415;border-radius:5px}
.di-img span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.di-img input{position:absolute;top:9px;left:9px;margin:0;accent-color:var(--accent)}
.di-img:has(input:checked){border-color:var(--accent);background:#22262a}
/* V0.1.46：文件清单（一次可拖多个 + 读不了的也要显式列出原因）。
   ⚠️ 不列出来就是「静默跳过」—— 导入类入口的大忌，用户会以为文件没被识别。 */
#diFileList{margin:0}
.di-files{list-style:none;margin:10px 0 0;padding:0;display:flex;flex-direction:column;gap:5px}
.di-file{display:flex;align-items:baseline;gap:10px;font-size:11.5px;line-height:1.6;padding:6px 10px;border:1px solid var(--line);border-radius:7px;background:#1a1e20}
.di-file .di-fname{flex:1 1 auto;min-width:0;color:#cfcbc2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.di-file .di-finfo{flex:none;color:var(--muted);font-size:11px}
.di-file.bad{border-color:#5a3030;background:#221a1a}
.di-file.bad .di-fname{color:#e2705f}
/* ⚠️ [hidden] 会被 .di-row 的 display:flex 顶掉 —— 必须显式写死。 */
#diClearRow[hidden]{display:none}
.help-toc{display:flex;flex-wrap:wrap;gap:6px;margin:14px 0 4px;padding-bottom:14px;border-bottom:1px solid var(--line)}
.help-toc a{font-size:11px;color:var(--muted);text-decoration:none;border:1px solid var(--line);border-radius:20px;padding:3px 11px}
.help-toc a:hover{color:var(--accent);border-color:var(--accent)}
.dialog.help section{margin-top:22px}
.dialog.help h3{font-size:13px;font-weight:600;color:var(--accent);margin:0 0 9px;padding-left:9px;border-left:2px solid var(--accent)}
.dialog.help ol{margin:0;padding-left:22px}
.dialog.help li{font-size:12.5px;line-height:1.85;color:var(--muted);margin:7px 0}
.dialog.help li b{color:#d5d1c8;font-weight:600}
.dialog.help code{font-size:11.5px;background:#1a1e20;border:1px solid var(--line);border-radius:4px;padding:1px 5px;color:var(--accent)}
.dialog.help li.help-note{list-style:none;margin-left:-22px;border-left:2px solid #73664e;background:#2d2c27;color:#d0c4ae;padding:9px 13px;border-radius:0 6px 6px 0;font-size:12px}
.help-warn{color:#d9b26a;font-weight:600}
.help-foot{font-size:11.5px;color:var(--dim);margin-top:24px;padding-top:14px;border-top:1px solid var(--line)}
.preset-save{display:flex;gap:8px;align-items:center}
.preset-save input{flex:1;min-width:0}
.preset-save button{flex:none;white-space:nowrap}
.preset-list{display:flex;flex-direction:column;gap:10px}
.preset-item{border:1px solid var(--line);border-radius:8px;padding:12px 14px;background:#1f2427}
.preset-item-head{display:flex;align-items:baseline;gap:10px}
.preset-item-head strong{font-size:14px;font-weight:500;color:var(--text)}
.preset-item-head span{margin-left:auto;font-size:10px;color:var(--dim);flex:none}
.preset-item-sum{margin:6px 0 10px;font-size:11px;color:var(--muted);line-height:1.7;overflow-wrap:anywhere}
.preset-item-actions{display:flex;gap:8px;flex-wrap:wrap}
.preset-item-actions button{font-size:12px;padding:6px 10px}

/* ═══════════════════════════════════════════════════════════════════
   V0.1.29 新增：画笔选区
   #brushLayer 是叠在图片上的那层「看得见的选区」。它跟 #localSelection 一样
   长在 #compareWrap 里（那棵树已经跟着画布的缩放平移一起 transform，
   坐标就是 imageLayout() 给的未缩放坐标，不需要自己再换算一次）。
   位置和尺寸由 JS 按 imageLayout() 写内联样式，这里只负责定位方式与外观。
   opacity 放在元素上而不是把笔色做成半透明：两笔重叠处若各叠一层半透明会更深，
   看起来像「涂得越重越算数」，但掩膜其实是二值的 —— 界面不能骗人。 */
#brushLayer{position:absolute;pointer-events:none;opacity:.45}
.brush-tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding-top:6px}
.brush-tools label{margin:0;font-size:12px;color:var(--muted);flex:none}
.brush-tools input[type=range]{width:130px;flex:none;margin:0}
.brush-tools output{min-width:44px;text-align:right;font-size:12px;font-variant-numeric:tabular-nums;color:var(--text);flex:none}
.brush-tools button{font-size:12px;padding:4px 10px;flex:none}
.brush-tools .hint{flex:1;min-width:150px;margin:0}

/* ═══════════════════════════════════════════════════════════════════
   V0.1.29 新增：扩图（生成式外扩）
   版式沿用「高清放大」：图片区留在上面、参数与预览在下面一条。
   预览为什么必须给：画幅 + 位置一改，留白量和原图缩放比就全变 ——
   只看「21:9」三个字判断不了这张图放进去会变成什么样。
   斜纹底 = 「这块是新长出来的」，原图按实际位置尺寸贴上去，金框是保留区边界。 */
#expandPanel{flex:none;padding-top:10px}
.expand-tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.expand-tools label{margin:0;font-size:12px;color:var(--muted)}
.expand-tools select{width:auto;max-width:100%;margin:0}
#expandAnchor{min-width:190px}
.expand-body{display:flex;gap:16px;margin-top:10px;align-items:stretch}
.expand-preview-wrap{flex:1;min-width:0;min-height:150px;max-height:230px;display:flex;align-items:center;justify-content:center;background:#12161a;border:1px solid #363d41;border-radius:6px;padding:8px}
/* canvas 是有固有尺寸的替换元素：max-width/max-height + auto 才能等比缩，
   直接写 width:100% 会把长卷压扁成一条。 */
#expandPreview{max-width:100%;max-height:214px;width:auto;height:auto;display:block}
.expand-side{width:268px;flex:none;display:flex;flex-direction:column;gap:8px}
.expand-side p{margin:0;max-height:105px;overflow:auto}

/* ═══ V0.1.29 新增：尺寸 / 规范校验叠加（查看器第 6 档）═══
   为什么要这一档：生成图没有物理尺度，光看图判断不了「1.7 m 的人在这面墙
   前有多大」。人形剪影是最快的尺度参照。
   版式沿用「并排对比」：图片区占满上面，参数在下面一行 —— 这一档要看的就是
   图本身，把图收小反而没法比。 */
#scalePanel{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}
.scale-tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap;flex:none}
.scale-tools label{margin:0;font-size:12px;color:var(--muted)}
.scale-tools input[type=number]{width:76px;margin:0}
.scale-tools input[type=range]{width:170px;margin:0}
.scale-tools .scale-unit{color:var(--muted);font-size:12px;margin-left:-6px}
.scale-tools output{font-size:12px;color:var(--accent);min-width:38px}
/* V0.1.30：三个叠加开关。做成 chip（同 .preserve-bar .chip 的观感），
   和「墙面总高 / 剪影位置」分两行 —— 一行里塞五个控件在窄屏必然折得很难看。 */
.scale-toggles{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:none}
.scale-toggles .scale-toggles-label{font-size:11px;color:var(--dim);letter-spacing:1px}
.scale-toggles label{display:inline-flex;align-items:center;gap:5px;margin:0;padding:3px 9px;border:1px solid var(--line);border-radius:999px;background:#202426;cursor:pointer;user-select:none;font-size:12px;color:var(--muted)}
.scale-toggles label:hover{border-color:#61696a}
.scale-toggles label:has(input:checked){border-color:var(--accent);color:var(--accent);background:var(--accent-dark)}
.scale-toggles input{margin:0;accent-color:var(--accent)}
.scale-body{flex:1;min-height:0;display:flex;gap:16px;align-items:stretch}
/* safe center：图矮时垂直居中（否则下方一大块空框像没加载出来），图高时顶部不被裁掉。 */
.scale-canvas-wrap{flex:1;min-width:0;display:flex;align-items:safe center;justify-content:center;overflow:auto;background:#12161a;border:1px solid #363d41;border-radius:6px;padding:10px}
/* 同 #expandPreview：canvas 是固有尺寸的替换元素，max-width/max-height + auto
   才能等比缩；写 width:100% 会把 21:9 的长卷压扁成一条。 */
#scalePreview{max-width:100%;max-height:100%;width:auto;height:auto;display:block}
/* V0.1.30：加了「叠加显示」的说明段落后文字变长，给个滚动兜底 ——
   .viewer 是 overflow:hidden，文字顶出去会被直接切掉。 */
.scale-side{width:268px;flex:none;display:flex;flex-direction:column;gap:8px;overflow:auto;min-height:0}
.scale-side p{margin:0}
.scale-side .scale-legend{color:var(--dim);border-left:2px solid #73664e;padding-left:8px}

/* ═══ V0.1.29 新增：批量多主题（提示词助手第 5 档）═══
   一个项目要做多面墙时用：每行一个主题，其余沿用画布设置，串行跑。
   只补 textarea 的高度与两行间距，其余复用 .hint / .checkline / .price-line。 */
#batchTopics{min-height:132px;font-size:13px;line-height:1.7}
#batchCount{margin:8px 0 0}
#batchStatus{margin:10px 0 0}

/* ═══ V0.1.31 新增：垫图（把参考图「点对点」贴到选区）═══
   和「修改选区」并列的一行。上一行是「我描述怎么改」，这一行是「把这张图贴到这个位置」。
   控件全部 flex:none 并排，hint 吃掉剩余宽度 —— 和 .local-edit-tools 同一套排版习惯。 */
.ref-place{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px;padding-top:8px;border-top:1px dashed var(--line)}
.ref-place .rp-label{font-size:12px;color:var(--muted);flex:none}
.ref-place select{font-size:12px;padding:3px 6px;max-width:190px;flex:none}
.ref-place>button{font-size:12px;padding:4px 9px;flex:none}
.ref-place .hint{flex:1;min-width:190px;margin:0;font-size:12px}
.ref-place .local-mode{display:flex;flex:none;border:1px solid var(--line);border-radius:6px;overflow:hidden}
.ref-place .local-mode button{padding:4px 10px;font-size:12px;border:0;border-radius:0;background:transparent;color:var(--muted)}
.ref-place .local-mode button+button{border-left:1px solid var(--line)}
.ref-place .local-mode button.active{background:var(--accent-dark);color:var(--accent)}
.ref-place .local-mode button:hover:not(.active):not(:disabled){background:#34393b;color:var(--text)}

/* 参考图取块弹窗。宽度要压过 .dialog 的 480px ——
   300px 宽的节点上框「这块石头」差几个像素，贴到 2048px 上就是几十像素的偏差。 */
.ref-crop{width:min(1120px,96vw);padding:0;overflow-x:hidden;overflow-y:auto;max-height:96dvh}
.rc-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-bottom:1px solid var(--line)}
.rc-head h3{margin:0;font-size:14px;font-weight:500}
.rc-head button{font-size:12px;padding:4px 10px;flex:none}
.rc-tip{margin:10px 18px 0}
.rc-stage{position:relative;margin:10px 18px;height:min(56vh,460px);background:#101416;border:1px solid var(--line);border-radius:6px;display:flex;align-items:center;justify-content:center;overflow:hidden;cursor:crosshair;touch-action:none}
.rc-stage img{max-width:100%;max-height:100%;display:block;user-select:none;-webkit-user-drag:none}
.rc-sel{position:absolute;border:1px solid var(--accent);background:#5dcaa52e;pointer-events:none}
#rcSel[hidden]{display:none}
.rc-foot{display:flex;align-items:center;gap:12px;padding:0 18px 16px}
.rc-foot .hint{flex:1;margin:0}
.rc-foot button{flex:none}
/* ── V0.1.43 四角透视贴合弹窗 ──
   为什么单独开一个弹窗、不在台子上直接点四角：台子上已经压着「矩形框选 / 画笔选区 / 图钉」
   三套手势，再塞一套四角拖拽必然打架（同一个 pointerdown 归谁？）；
   而且四角要拖得准，画面得比节点宽得多 —— 台子节点只有 580px 宽。
   ⚠️ .warp-stage 复用了 .rc-stage 的定位与 flex 居中，只把高度调高一点。 */
.warp-dialog{width:min(1120px,94vw);padding:0;overflow:hidden}
.warp-stage{height:min(62vh,580px);cursor:default}
/* 实时预览：直接画在底图上（不是浮层），位置尺寸由 JS 按 img 的实际显示框算。
   pointer-events:none —— 事件统一交给四个手柄和 stage。 */
.warp-preview{position:absolute;pointer-events:none}
.warp-handles{position:absolute;inset:0;pointer-events:none}
/* 四个角的手柄。⚠️ 必须 pointer-events:auto —— 父层 .warp-handles 是 none，
   否则手柄收不到 pointerdown，四角拖不动（和 #pinLayer 的 .pin 同一个坑）。 */
.warp-h{position:absolute;width:24px;height:24px;margin:-12px 0 0 -12px;border-radius:50%;border:2px solid #0f1315;background:var(--accent);color:#211c15;font:600 12px/1 ui-monospace,monospace;display:flex;align-items:center;justify-content:center;cursor:grab;touch-action:none;pointer-events:auto;box-shadow:0 2px 7px #0009}
.warp-h:hover{box-shadow:0 0 0 5px #dab78355,0 2px 7px #0009}
.warp-h.dragging{cursor:grabbing;background:#f0d3a6;box-shadow:0 0 0 5px #dab78388,0 2px 7px #0009}
.warp-tools{display:flex;align-items:center;gap:10px;margin:8px 18px 0}
.warp-tools button{flex:none;font-size:12px;padding:4px 10px}
.warp-tools .hint{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ═══ V0.1.31 新增：底图的更换 / 移除 ═══ */
/* 浮动条要绝对定位，先给容器一个定位上下文。只补 position 一个属性，
   不动 .bench-stage 的 flex / min-height —— 节点尺寸和 fit() 都不受影响。 */
.bench-stage{position:relative}
.base-overlay{position:absolute;right:10px;bottom:10px;z-index:6;display:flex;align-items:center;gap:6px;
  max-width:calc(100% - 20px);padding:5px 8px;border:1px solid var(--line);border-radius:8px;
  background:#17191bd9}
.base-overlay[hidden]{display:none}
.base-overlay span{font-size:11px;color:var(--muted);max-width:170px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.base-overlay button{flex:none;font-size:11px;padding:3px 8px}
.base-overlay button:disabled{opacity:.45}
/* [hidden] 必须显式写：这两个元素的容器都是 display:flex，
   光靠 UA 的 [hidden]{display:none} 会被 flex 的 display 盖掉。 */
#removeBase[hidden]{display:none}
/* 拖文件到窗口上时的提示。position:fixed + pointer-events:none ——
   完全不参与布局，出现和消失都不会动到任何节点、任何 fit()。 */
body.dropping-file::before{content:'';position:fixed;inset:0;z-index:9998;pointer-events:none;
  border:3px dashed var(--accent);background:#dab7831a}
body.dropping-file::after{content:'松手 · 这张图会作为改图底图导入';position:fixed;left:50%;top:16px;
  transform:translateX(-50%);z-index:9999;pointer-events:none;padding:7px 18px;border-radius:999px;
  background:var(--accent-dark);color:var(--accent);border:1px solid var(--accent);
  font-size:13px;letter-spacing:.03em;box-shadow:0 6px 20px #0007}

/* V0.1.51: continuous navigation through the bottom result gallery. */
.viewer-nav{display:flex;align-items:center;justify-content:center;gap:12px;flex:none;padding:0 0 12px}.viewer-nav button{min-width:98px}.viewer-nav span{min-width:220px;text-align:center;color:var(--text);font-variant-numeric:tabular-nums}.viewer-nav small{color:var(--muted);font-size:11px}.viewer-nav[hidden]{display:none!important}@media(max-width:760px){.viewer-nav{gap:6px;flex-wrap:wrap}.viewer-nav span{min-width:120px;flex:1}.viewer-nav small{flex-basis:100%;text-align:center}}

/* V0.1.52: compact preview chrome to give the image more room. */
.viewer{padding:12px}
.viewer-head h2{font-size:16px;margin:2px 0}
.viewer-head .eyebrow{font-size:9px;letter-spacing:.12em}
.viewer-head .muted{font-size:10px}
.viewer-head>button{width:30px;height:30px;font-size:18px}
.viewer-tabs{gap:4px;padding:8px 0}
.viewer-tabs button{font-size:11px;padding:5px 9px}
.viewer-nav{gap:8px;padding-bottom:7px}
.viewer-nav button{min-width:84px;padding:5px 9px;font-size:11px}
.viewer-nav span{font-size:11px;min-width:190px}
.viewer-nav small{font-size:10px}
.viewer-main{gap:12px}
.image-stage{padding:8px}
.viewer-zoom{gap:8px;padding-bottom:6px;font-size:11px}
.viewer-zoom button{padding:4px 8px;font-size:11px}
@media(max-width:760px){.viewer{padding:10px}.viewer-tabs{gap:2px;padding:6px 0}.viewer-tabs button{padding:4px 6px;font-size:10px}.viewer-nav{gap:5px;padding-bottom:5px}}
.viewer-head{align-items:center}.viewer-cdr-launch{margin-left:auto;color:var(--accent);border-color:#776448}.viewer-head>.viewer-cdr-launch{width:auto;height:auto;min-height:30px;flex-shrink:0;padding:6px 10px;font-size:12px;white-space:nowrap}.cdr-dialog{width:min(700px,94vw);padding:22px}.cdr-dialog header{display:flex;justify-content:space-between}.cdr-dialog header h2{font-size:20px}.cdr-dialog>img{width:220px;height:160px;object-fit:contain;background:#161a1c}.cdr-dialog p{line-height:1.7;padding:10px;background:#302d27}.cdr-dialog textarea{min-height:70px;margin:12px 0}.cdr-dialog button{margin:4px}.cdr-dialog ol{max-height:200px;overflow:auto}

/* V0.5.4 CDR reconstruction review */
.cdr-dialog{width:min(1120px,96vw);max-height:94vh;overflow:auto;padding:24px}.cdr-dialog header{align-items:center;gap:20px}.cdr-dialog header h2{margin:4px 0}.cdr-dialog p{padding:10px 14px;margin:12px 0;background:#292b2b}.cdr-comparison{display:grid;grid-template-columns:1fr 1fr;gap:14px}.cdr-comparison figure{margin:0;min-width:0;border:1px solid var(--line,#444);border-radius:8px;overflow:hidden;background:#181c1e}.cdr-comparison figcaption{font-size:12px;color:#b5b5ad;padding:10px}.cdr-comparison img,.cdr-comparison #cdrPreview{width:100%;height:235px;object-fit:contain;display:block}.cdr-comparison svg{width:100%;height:100%;display:block}.cdr-options{display:flex;align-items:center;gap:18px;flex-wrap:wrap;padding-top:14px}.cdr-options label{display:flex;align-items:center;gap:8px;margin:0;white-space:nowrap}.cdr-options input{width:100px}.cdr-options select{width:165px}.cdr-options #cdrMeta{font-size:12px;color:#b5b5ad}.cdr-dialog textarea{width:100%;min-height:56px;margin:12px 0}.cdr-actions{display:flex;flex-wrap:wrap;gap:5px}.cdr-dialog progress{width:100%;height:6px;margin:14px 0 8px;accent-color:#d5b580}.cdr-dialog #cdrStatus{line-height:1.6;min-height:25px;overflow-wrap:anywhere}.cdr-dialog details{margin:14px 0;border-top:1px solid #444;padding-top:12px}.cdr-dialog ol{max-height:245px;overflow:auto;padding-left:32px}.cdr-dialog li{padding:6px 4px;font-size:12px}.cdr-dialog li textarea{display:block;width:95%;min-height:34px;margin:4px 0}.cdr-dialog .cdr-files{display:flex;gap:20px;padding:8px 0}.cdr-dialog .cdr-files a{color:#e2c696}.cdr-plan-tools{display:flex;align-items:center;gap:8px;flex-wrap:wrap}@media(max-width:750px){.cdr-comparison{grid-template-columns:1fr}.cdr-comparison img,.cdr-comparison #cdrPreview{height:175px}}
