*{margin:0;padding:0;box-sizing:border-box;}
/* 让所有原生控件(下拉/滚动条/日期等)按暗色渲染 → 下拉弹层不再是刺眼白底看不清(用户反馈) */
:root{color-scheme:dark;}
/* 下拉选项显式暗色兜底(部分平台 color-scheme 不覆盖 option 弹层) */
select option, select optgroup{background-color:#141a2a;color:#ffffff;}
:root{
  /* R18-T1.2: 颜色 token 由原子档(--bg/p1/p2/p3/bd)迁移到语义层(--bg-*/--border-*).
   * 旧 5 token 已在 T1.2(c) commit 中删除,引用全部走语义层. */
  --ac:#3b82f6;--ac2:#8b5cf6;
  --ok:#10b981;--wn:#f59e0b;--er:#ef4444;
  --t1:#ffffff;--t2:#a1a1aa;
  /* R16-T1.1 + R24-T8c-22: --t3 从 #4a5270(3.04:1)→#71717a(实测 3.7:1 仍不达 WCAG AA)→#8b8b94(≥ 4.5:1) */
  --t3:#8b8b94;
  /* R18-T1.2(a): 语义颜色阶梯 (HUD 玻璃态升级) */
  --bg-base:#050505;        /* 页面 / canvas 背景 */
  --bg-sunken:#000000;      /* well / 页脚 / 下凹容器 */
  --bg-elevated:rgba(18, 22, 31, 0.75);    /* header / panel / modal / dropdown 容器 (含透明度) */
  --bg-input:rgba(255, 255, 255, 0.05);       /* input / select / button 默认态 */
  --bg-hover:rgba(255, 255, 255, 0.08);       /* hover / focus 态 */
  --bg-active:rgba(255, 255, 255, 0.12);      /* pressed / active 态 */
  --bg-overlay:rgba(0,0,0,.6);  /* modal backdrop */
  --bg-disabled:rgba(255,255,255,0.02); /* 禁用态按钮/输入框背景 */
  --border-subtle:rgba(255, 255, 255, 0.04);  /* 弱化分隔线 */
  --border-default:rgba(255, 255, 255, 0.08); /* 默认 1px 边框 */
  --border-strong:var(--ac);  /* focus 强边框(同 --ac) */
  /* R16-T1.1: 字体阶梯(type-scale) */
  --fs-xxs: 10px;
  --fs-xs: 11px;
  --fs-sm: 12px;
  --fs-md: 13px;
  --fs-lg: 14px;
  --fs-xl: 16px;
  --fs-h1: 18px;
  --fs-h2: 15px;
  --fs-h3: 13px;
  --fs-icon-sm: 14px;
  --fs-icon-md: 16px;
  --fs-icon-lg: 18px;
  --fs-display: 40px;
  /* font-weight ladder */
  --fw-normal: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  /* line-height ladder */
  --lh-tight: 1.3;
  --lh-base: 1.5;
  --lh-loose: 1.7;
  /* "数字/数据"字体族。WHY 不用真正的等宽编码字体:JetBrains Mono(点心零)、
     Cascadia / Consolas(斜杠零)的 "0" 中间都有点或斜杠,而 JetBrains Mono 无纯圆零选项
     (上游 issue #625 仍未实现)。用户要求所有 "0" 必须是纯圆圈,故改用带 tabular-nums 的
     无衬线系统字体(Inter/Segoe UI/SF/system-ui 的 "0" 实测中心 0% 墨,即纯圆),
     等宽对齐由各处的 font-variant-numeric/tnum 保证。改回等宽字体会让斜杠/点心零复发。 */
  --ff-mono: 'Inter','Segoe UI','SF Pro Text',system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;
  /* motion duration tokens (T5: 统一过渡时长) - 升级为丝滑弹性曲线 */
  --motion-fast: 150ms cubic-bezier(0.16, 1, 0.3, 1);
  --motion-base: 250ms cubic-bezier(0.16, 1, 0.3, 1);
  --motion-slow: 400ms cubic-bezier(0.16, 1, 0.3, 1);
  /* R16-T1.1: 间距系统(4/8/12/16/24/32) */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  /* R16-T1.1 → R18-T1.1: z-index 系统(10 档)
   * DEVLOG-2026-07-11-attr-hover-tooltip-flicker 补 4 档(--z-menu-chrome/--z-aiwindow/--z-menu/--z-tooltip):
   *   根因 —— "更多"下拉菜单(.ctx-menu-shared,含右键菜单/AI 消息菜单共用同一组件)之前硬编码
   *   z-index:99999,后被 46-skin-overlays.js 提到 100003,反而盖过了 AI 悬浮窗(#ai-panel,当时仍在
   *   --z-sticky:100)和全局 tooltip(当时 100002,比 ctx-menu 还低一档)。三者相对顺序此前从未被设计过,
   *   纯粹是历次局部补丁各自加数字撞出来的。现补齐显式分层,唯一顶层只留给 tooltip:
   *     --z-menu-chrome(顶栏"更多"下拉专用,低于 AI 悬浮窗)
   *       < --z-aiwindow(AI 装载助手悬浮窗 #ai-panel)
   *       < --z-modal(既有档,未变)
   *       < --z-menu(通用右键菜单默认档,含卡片/AI消息/场景标签/模板选择器等 —— 必须高于其宿主面板,
   *                  否则从面板内部弹出的菜单会被面板自身盖住,如 AI 面板内右键消息的"复制/删除"、
   *                  或模板选择器 .tpl-popup:999 打开时右键模板卡片弹出的菜单)
   *       < --z-toast(既有档,未变)
   *       < --z-max(既有档未变 —— 现仅供 .tpl-ctx-menu / _confirm2 二次确认弹窗使用)
   *       < --z-tooltip(全局 tooltip 专属最高层,任何面板/菜单/模态都不可遮挡它) */
  --z-deep: -1;        /* 背景层 / 沉底元素 */
  --z-base: 1;         /* 默认流 */
  --z-raised: 5;       /* mo-x 关闭按钮 / splitter / 浅浮起 */
  --z-sticky: 100;     /* 侧栏 / header / anim-panel */
  --z-dropdown: 200;   /* 下拉 / box-sel-del */
  --z-popup: 500;      /* 气泡 / popover */
  --z-menu-chrome: 550; /* 顶栏"更多"下拉菜单专用(见 13-main.js _bindMoreMenu) — 必须低于 --z-aiwindow,不许盖住 AI 悬浮窗 */
  --z-aiwindow: 700;   /* AI 装载助手悬浮窗(#ai-panel) — 高于顶栏下拉菜单,低于通用右键菜单/模态 */
  --z-modal: 1000;     /* 模态遮罩 / 主模态 */
  /* --z-menu 原定 800(< --z-modal), 审核员 agent 发现回归: 29-templates.js 的模板卡片/网格右键菜单
     也走 .ctx-menu-shared 默认档, 但会在 .tpl-popup(z-index:999 !important, 见 46-skin-overlays.js
     .tpl-popup 规则) 打开时弹出 —— 800 < 999 会把菜单压到模板弹层背后、点不到。改到 1010(> 999 且
     > --z-modal), 让"通用右键菜单必须盖住其宿主"这条设计意图对 .tpl-popup 也成立。没有任何右键菜单
     会在 #mbg 模态内部弹出(已 grep 确认 10-ui-modals.js 无 _ctxMenu 调用), 所以盖过 --z-modal 无副作用。 */
  --z-menu: 1010;      /* 通用右键菜单(.ctx-menu-shared 默认档) — 卡片/消息/场景标签/模板选择器等,必须高于其宿主面板(含 .tpl-popup:999) */
  --z-overlay: 3000;   /* 拖拽幽灵 / pal-ghost */
  --z-toast: 5000;     /* toast 提示 */
  --z-max: 99999;      /* 模板右键菜单(.tpl-ctx-menu)/ 二次确认弹窗(_confirm2)等旧版"顶层"标记 */
  --z-tooltip: 100010; /* 全局 tooltip(.global-tip) — 唯一最高层,任何面板/菜单/模态都不可遮挡它 */

  /* R24-T8b: 布局动态变量(由 25-shortcuts-resize-share.js splitter 拖动时 setProperty 维护;
   *   未拖动时回退默认值。显式在 :root 定义是为了让 S3 CSS 变量自检不再 WARN) */
  --lp-width: 280px;  /* 左栏宽度(left panel width) */
  --rp-width: 300px;  /* 右栏宽度(right panel width) */
  --bg-panel: #161b2b;/* 模态/浮层背景色(fallback for var(--bg-panel, #161b2b)) */

  /* ============================================================
   * DEVLOG-2026-05-27-glass-system-token-builders §1 T2:
   *   Cross-panel design token 系统 (R25-B 单一源码).
   *   旧 token 全部保留, 新组件层 (.gpanel / .gbtn) 引用这些新 token.
   *   迁移完成后旧 token 才考虑收敛.
   * ============================================================ */
  /* —— 玻璃 tint 3 阶 —— blur / bg / border 组合, 三档语义清晰
   *   tier-1 强 (modal / AI panel / anim panel / context menu / tooltip)  blur 32
   *   tier-2 中 (header / lp / rp / scene-tabs / help)                    blur 24
   *   tier-3 弱 (chip / popover / dropdown 浅悬浮)                        blur 12
   */
  /* DEVLOG-...-glass-system §3 iter5 perf-fix: blur 强度全面下调.
   * 用户反馈右键平移变慢 — backdrop-filter(blur 24-32 + saturate)
   * 在 canvas mousemove 时触发持续重绘, GPU 开销大. 提高 bg 不透明度补偿. */
  --glass-1-blur: 20px;
  --glass-1-bg: rgba(18, 22, 31, 0.88);
  --glass-1-border: rgba(255, 255, 255, 0.14);
  --glass-1-stroke: rgba(255, 255, 255, 0.06);
  --glass-2-blur: 14px;
  --glass-2-bg: rgba(18, 22, 31, 0.82);
  --glass-2-border: rgba(255, 255, 255, 0.10);
  --glass-2-stroke: rgba(255, 255, 255, 0.05);
  --glass-3-blur: 8px;
  --glass-3-bg: rgba(18, 22, 31, 0.78);
  --glass-3-border: rgba(255, 255, 255, 0.06);
  --glass-3-stroke: rgba(255, 255, 255, 0.04);

  /* —— Radius scale —— 强制收敛 (旧值 4/5/6/7/8/9/10/12/14/16 10 种 → 6 档) */
  --r-xs: 4px;       /* chip / 角标 / inline kbd */
  --r-sm: 6px;       /* 小按钮 / 输入框内部 / segment */
  --r-md: 8px;       /* 主按钮 / input / dropdown */
  --r-lg: 12px;      /* 面板 / popover / tooltip */
  --r-xl: 16px;      /* modal / AI panel / 强 elevated 容器 */
  --r-pill: 999px;   /* 标签条 / 通知带 */

  /* —— Elevation scale —— ambient + key + accent inset 三层组合 */
  --elev-1: 0 1px 2px rgba(0,0,0,0.18), 0 0 0 1px var(--glass-3-stroke) inset;
  --elev-2: 0 4px 16px rgba(0,0,0,0.22), 0 0 0 1px var(--glass-2-stroke) inset;
  --elev-3: 0 8px 32px rgba(0,0,0,0.32), 0 0 0 1px var(--glass-2-stroke) inset;
  --elev-4: 0 24px 64px rgba(0,0,0,0.55), 0 0 0 1px var(--glass-1-stroke) inset;

  /* —— Motion library —— spring 曲线给 hover lift / 出场, fast 给开关切换, fade 给透明度 */
  --ease-out-soft: cubic-bezier(0.16, 1, 0.3, 1);            /* 已有 (--motion-fast/base/slow 同源), 别名给语义 */
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);          /* 弹性 — hover lift / panel 入场 */
  --ease-in-fast: cubic-bezier(0.4, 0, 1, 1);                /* 收起 / 关闭 */
  --motion-hover: 180ms var(--ease-spring);                  /* 按钮 / 卡片 hover */
  --motion-fade: 160ms var(--ease-out-soft);                 /* 透明度 / 颜色 */
  --motion-modal: 280ms var(--ease-out-soft);                /* modal 入/出场 */
  --motion-tab-stagger: 220ms var(--ease-spring);            /* tab underline 滑动 */

  /* —— Accent gradients —— 替换 4+ 处散落的 inline linear-gradient(135deg, var(--ac), var(--ac2)) */
  --grad-accent: linear-gradient(135deg, #4f8fff, #8b5cf6);
  --grad-danger: linear-gradient(135deg, #ef4444, #dc2626);
  --grad-success: linear-gradient(135deg, #22c55e, #16a34a);
  --grad-warn: linear-gradient(135deg, #f59e0b, #d97706);

  /* —— Focus ring (WCAG visible focus, R25-A 反黑箱) —— */
  --focus-ring: 0 0 0 2px var(--bg-base), 0 0 0 4px var(--ac);
  --focus-ring-inset: inset 0 0 0 2px var(--ac);
}
html,body{height:100%;overflow:hidden;}
body{font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC',sans-serif;background:var(--bg-base);color:var(--t1);display:flex;flex-direction:column;-webkit-font-smoothing:antialiased;}
/* DEVLOG-...-glass-system §1 T5: #hdr backdrop/border/shadow 已搬到 .gpanel-2.gpanel-bar.
   保留布局相关声明 (display/padding/z-index/flex-wrap), 玻璃质感走共享层. */
#hdr{
  height:auto;
  min-height:54px;
  display:flex;
  align-items:center;
  /* DEVLOG-2026-07-11-attr-hover-tooltip-flicker: wrap → nowrap (用户原话"让所有顶栏按钮都能一排展示,
   * 留充足位置给可变化的信息栏;目前顶栏 UI 设计很奇怪,好好设计下")。
   * 旧版用 wrap 是为了不在窄屏上把按钮裁到屏外不可达 —— 但 wrap 的代价是:#stats 内容变长
   * (件数/重量/利用率/异常数会动态增减)会把整排顶栏挤到换行,按钮跟着跳位置,视觉上像"闪烁/卡顿"。
   * 新方案:所有按钮 flex-shrink:0(见 .btn,已有)永不挤压/换行;唯一会变长变短的 #stats 改
   * flex:1 1 auto + min-width:0 + ellipsis 吸收所有富余/不足空间(见 #stats 规则);
   * 极窄视口下(按钮总宽本身就超出可视区)靠 overflow-x:auto 横向滚动兜底,不再是唯一手段
   * 而是"够宽=单行不裁,不够=可横向滚动但仍不换行/不抖动"。 */
  flex-wrap:nowrap;
  overflow-x:auto;
  overflow-y:hidden;
  row-gap:4px;
  padding:6px 16px;
  gap:6px;
  flex-shrink:0;
  z-index:var(--z-sticky);
  border-bottom:1px solid var(--glass-2-border);
}
#hdr h1{font-size:var(--fs-icon-md);font-weight:var(--fw-bold);background:linear-gradient(135deg,var(--t1),var(--t2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;white-space:nowrap;margin-right:12px;flex-shrink:0;display:flex;align-items:baseline;gap:6px;}
/* 2026-05-27: 项目名旁的版本号小字 — 不参与渐变,保持暗色易识别 */
#hdr h1 .hdr-ver{font-size:var(--fs-xxs);font-weight:var(--fw-medium);color:var(--t3);background:none;-webkit-text-fill-color:var(--t3);letter-spacing:1px;text-transform:uppercase;}
/* DEVLOG-...-glass-system §1 T5: 旧 .tb 走 .gbtn-group 提供 padding+bg+radius+hover.
   保留 .tb 仅给 layout (display/gap/align/shrink), 视觉走 .gbtn-group. */
.tb{display:flex;gap:4px;flex-shrink:0;align-items:center;}
/* .tb:hover 旧的整组浅底删 — 用户感觉干扰 (R20 N6 反馈). 改为 .dv:hover 相邻组高亮 (含蓄). */
/* DEVLOG-...-glass-system §1 T11 iter1: .btn color 从 var(--t2) 调亮到 78% 白 (与 .gbtn 同). 玻璃毛底下灰文字读不清 */
.btn{padding:7px 14px;border:1px solid transparent;border-radius:8px;background:transparent;color:rgba(255,255,255,0.78);font-size:var(--fs-sm);font-weight:var(--fw-medium);cursor:pointer;transition:all var(--motion-fast);display:flex;align-items:center;gap:var(--space-1);white-space:nowrap;flex-shrink:0;letter-spacing:0.2px;}
.btn:hover{background:var(--bg-hover);color:var(--t1);}
.btn.on{background:rgba(255,255,255,0.08);color:var(--ac);font-weight:var(--fw-semibold);}
/* 检视切片 内联控件 — 与玻璃工具栏风格统一(替换原生丑控件) */
#inspect-ctrls{display:inline-flex;align-items:center;gap:8px;margin-left:6px;}
#inspect-axis{background:var(--bg-input);color:var(--t1);border:1px solid var(--border-default);border-radius:6px;padding:3px 8px;font-size:var(--fs-xs);font-family:inherit;cursor:pointer;outline:none;transition:border-color var(--motion-fast);}
#inspect-axis:hover,#inspect-axis:focus{border-color:var(--ac);}
/* 切片位置滑块:轨道画在 input 自身背景上(6px 居中带,Chromium 下最稳,不依赖 ::track 伪元素);
   input 高 16px 让 14px thumb 不被裁;已走过部分品牌色填充由 JS _fillRange 设到背景渐变。 */
#inspect-ctrls input[type="range"]{-webkit-appearance:none;appearance:none;width:120px;height:16px;outline:none;cursor:ew-resize;vertical-align:middle;background-color:transparent;background-image:linear-gradient(rgba(255,255,255,.22),rgba(255,255,255,.22));background-repeat:no-repeat;background-position:center;background-size:100% 6px;}
#inspect-ctrls input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:var(--ac);border:2px solid #fff;box-shadow:0 1px 3px rgba(0,0,0,.5);cursor:ew-resize;}
#inspect-ctrls input[type="range"]::-moz-range-track{height:6px;border-radius:3px;background:rgba(255,255,255,.22);}
#inspect-ctrls input[type="range"]::-moz-range-progress{height:6px;border-radius:3px;background:var(--ac);}
#inspect-ctrls input[type="range"]::-moz-range-thumb{width:14px;height:14px;border:2px solid #fff;border-radius:50%;background:var(--ac);cursor:ew-resize;}
.inspect-hide-lbl{display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-xs);color:var(--t2);white-space:nowrap;cursor:pointer;}
.inspect-hide-lbl input{accent-color:var(--ac);cursor:pointer;}
/* AI 实时装载演示:顶部居中"当前步理由 + 进度"飘字 overlay */
#ai-demo-overlay{position:absolute;top:60px;left:50%;transform:translateX(-50%);z-index:60;min-width:260px;max-width:62%;padding:10px 18px;text-align:center;pointer-events:none;}
#ai-demo-overlay .aidemo-head{font-size:var(--fs-md);font-weight:var(--fw-semibold);color:var(--ac);letter-spacing:.3px;}
#ai-demo-overlay .aidemo-reason{font-size:var(--fs-sm);color:var(--t1);margin-top:4px;line-height:1.5;}
#ai-demo-overlay .aidemo-bar{margin-top:8px;height:4px;border-radius:3px;background:rgba(255,255,255,.12);overflow:hidden;}
#ai-demo-overlay .aidemo-fill{height:100%;width:0;background:linear-gradient(90deg,var(--ac),var(--ac2));border-radius:3px;transition:width var(--motion-fast);}
/* AI 装载演示 实时纠正条(P5) */
#ai-steer{display:none;align-items:center;gap:6px;padding:6px 16px;border-top:1px solid var(--glass-2-border);}
#ai-steer input{flex:1;min-width:0;background:var(--bg-input);border:1px solid var(--border-default);border-radius:6px;color:var(--t1);font-size:var(--fs-xs);padding:5px 9px;outline:none;transition:border-color var(--motion-fast);}
#ai-steer input:focus{border-color:var(--ac);}
#ai-steer button{padding:5px 12px;border-radius:6px;border:1px solid var(--ac);background:rgba(59,130,246,.16);color:var(--ac);font-size:var(--fs-xs);font-weight:var(--fw-medium);cursor:pointer;white-space:nowrap;}
#ai-steer button:hover{background:rgba(59,130,246,.28);}
/* T6: 统一禁用/加载视觉 — .btn / .bb / .pbtn / .lp-action-btn / .opt-spec-btn 共用 */
.btn:disabled,.btn.disabled,
.bb:disabled,.bb.disabled,
.pbtn:disabled,.pbtn.disabled,
.lp-action-btn:disabled,.lp-action-btn.disabled,
.opt-spec-btn:disabled,.opt-spec-btn.disabled{
  background:var(--bg-disabled,rgba(255,255,255,0.02));
  color:var(--t3);
  border-color:transparent;
  cursor:not-allowed;
  opacity:.5;
}
.btn.loading,.bb.loading,.pbtn.loading,.lp-action-btn.loading,.opt-spec-btn.loading{
  position:relative;pointer-events:none;opacity:.8;
}
.btn.loading::after,.bb.loading::after,.pbtn.loading::after,.lp-action-btn.loading::after,.opt-spec-btn.loading::after{
  content:'';position:absolute;right:8px;top:50%;width:10px;height:10px;margin-top:-5px;
  border:2px solid currentColor;border-right-color:transparent;border-radius:50%;
  animation:btn-spin .8s linear infinite;
}
@keyframes btn-spin{to{transform:rotate(360deg);}}
/* R22-T4 / T5: 分隔符加强 — 高度对齐 button + 加渐变让边界更柔 + hover 状态告诉用户"组之间" */
.dv{
  width:1px;
  height:24px;
  background:linear-gradient(to bottom, transparent, var(--glass-2-border), transparent);
  margin:0 4px;
  flex-shrink:0;
  transition:background var(--motion-fade);
}
.dv:hover{
  background:linear-gradient(to bottom, transparent, var(--ac), transparent);
}
/* DEVLOG-2026-07-11-attr-hover-tooltip-flicker: .sp 原是 flex:1 的"增长型"占位符,把它右边的
 * #stats/#validity-badge/#save-ind/#mtag 一起推到顶栏最右侧。现在 #stats 自己改成 flex:1 1 auto
 * 来吸收富余空间(见下),.sp 若还留 flex:1 会跟 #stats 抢空间(各分一半,#stats 拿不到全部富余量)。
 * 改成固定小间距,把"增长"这件事完全交给 #stats 一个人做。 */
.sp{flex:0 0 6px;}  /* 2026-08-12: 12->6px。顶栏在 1440(桌面版默认窗口)差 87px 才装得下,
                       * 这里省下的每一 px 都直接换成右侧结论区的可见宽度。 */
/* iter5 user-feedback fix: mtag/stats 必须 nowrap, 否则窄宽度下文本竖向换行让 header 变 100px+ 高 */
/* #39 (2026-05-29): 顶栏状态区固化 — 给会变化的统计/保存态/状态消息预留固定宽度槽,
 *   内容变化(件数/重量/利用率/就绪…)只在各自槽内变,不再推动整条顶栏左右抖动。 */
#mtag{font-size:var(--fs-xs);padding:4px 10px;background:rgba(255,255,255,0.05);border-radius:var(--r-sm);color:var(--t3);border:1px solid var(--border-default);white-space:nowrap;flex:0 1 auto;min-width:56px;max-width:220px;text-align:center;overflow:hidden;text-overflow:ellipsis;box-sizing:border-box;}
/* #46 (2026-05-29): 各信息栏可视化预留框 —— 固定宽度槽 + 浅边框/底,内容(件数/重量/利用率/异常)只在框内变,不推动邻栏。
 * DEVLOG-2026-07-11-attr-hover-tooltip-flicker: min-width:268px(纯下限,无上限)→ flex:1 1 auto +
 * min-width:0 + max-width + ellipsis。用户原话"让所有顶栏按钮都能一排展示,留充足位置给可变化的信息栏":
 * #stats 内容(件数/重量/利用率/异常/警告拼接)本来没有上限,只有下限,一旦变长就把 #hdr 撑到换行(按钮跟着
 * 跳位置)。现在 #stats 是顶栏里唯一会"长大/缩小"的元素 —— 变长时优先占满富余空间(flex-grow:1),
 * 富余用完仍不够就在自己框内单行省略号截断(...展示),鼠标悬停仍有 data-tip-desc 说明浮层;
 * 按钮(.btn 已有 flex-shrink:0)永远不会被这个过程波及,不会跳位置。
 *
 * BUGFIX 2026-08-09: 「利用率」数值被吃掉 —— min-width:0 把下限也让给了 flex-shrink。
 * 根因：#hdr 是 flex-wrap:nowrap + overflow-x:auto，本意是"实在装不下就横向滚动，不逼
 * #stats 硬缩"。但 #stats 是全排里唯一 flex-shrink 非 0 的项（按钮都 flex-shrink:0），
 * 且 min-width:0 允许它被压缩到 0——flexbox 的收缩算法会优先把可收缩项压到底，而不是让容器
 * 溢出触发 overflow-x 滚动；于是"横向滚动兜底"这条路径实际上从来没被走到过：只要顶栏按钮
 * 总宽 + #stats 内容宽度超过视口，#stats 就被压扁到只剩个位数像素，核心指标（件数/kg/利用率）
 * 全部被 ellipsis 吃掉——包括用户最关心的「利用率」，因为它在字符串最后一段，最先被裁。
 * 实测：1400px 视口下 scrollWidth=345 但 clientWidth 被压到 20（正常内容时同样会被压扁,
 * 只是数字更小更不容易被注意到）。
 * 修法：给 min-width 一个不为 0 的下限——刚好够放下"核心三项"（件数 · kg · 利用率%）不留余量
 * 给异常数/最优装载提示这些"锦上添花"的追加片段。220px 是实测 "999件 · 999999kg · 利用率100%"
 * （四位数场景的最坏情况）在 --fs-xs/--ff-mono 下的渲染宽度（含左右 padding），留了安全余量。
 * 低于这个下限时 #hdr 的 overflow-x:auto 才终于有机会真正生效（横向滚动),而不是 #stats 自己
 * 先一步被压没——这才是本来就写在注释里的设计意图,只是 min-width:0 让它从未被触发过。
 * 附加片段（异常数/警告提示/作用域标签）仍可能在极端情况下被 ellipsis 截断,这是刻意保留的
 * 降级路径,只是不再牵连核心指标。 */
#stats{font-size:var(--fs-xs);padding:4px 10px;background:rgba(255,255,255,0.04);border:1px solid var(--border-default);border-radius:var(--r-sm);color:var(--t2);font-family:var(--ff-mono);line-height:1.4;white-space:nowrap;flex:1 1 auto;min-width:64px;max-width:640px;text-align:left;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;}
/* #18 (2026-05-31): 统计作用域标签 + 空间非法提示 —— 让用户清楚当前统计的是聚焦货箱/框选子集还是整场 */
/* 2026-08-12: #hdr-info 只做"把右侧四件套归为一组"的语义容器,不做定位。
 * 曾试过给它 position:sticky right:0 让它永远贴着视口右边 —— 在 1440 上确实好看,
 * 但 1024(软件自己声明的 minWidth)下它会盖住工具栏右半边:免滚动可点按钮从 14/17
 * 掉到 2/17(scripts/verify-hdr-fits.js 的对照实测)。为了让一个结论露脸而把七个按钮
 * 藏起来是亏的,所以退回普通流式布局,改从"顶栏到底需要多宽"下手。 */
#hdr-info{
  display:flex;
  align-items:center;
  gap:4px;
  margin-left:auto;
  flex:1 1 auto;
  min-width:0;
}

/* 2026-08-12: min-width 220px -> 64px。#hdr 的规则注释(main.css:176-183)写的是
 * "#stats 改 flex:1 1 auto + min-width:0 + ellipsis 吸收所有富余/不足空间",但这里实际写的是
 * 220px —— 说明与实现对不上,于是 #stats 死活不肯让位,把它右边的三个全顶出了视口:
 * 桌面版默认窗口(electron/main.js 1440x900)实测 #validity-badge x=1449..1597、
 * #save-ind x=1603..1665、#mtag x=1671..1763,三个整体在屏幕外 —— "本方案物理有效"这个
 * 结论在默认窗口下**一次都没露过面**,而 overflow-x:auto 那条兜底没人会去横滚工具栏。
 * 留 64px 而不是 0:压到极限时仍能看见"N 件"这一段,不至于缩成一条缝。
 * 闸门: scripts/verify-hdr-fits.js (self-check S82)。 */
#stats .stats-scope{color:var(--ac);font-weight:600;letter-spacing:.3px;}
#stats .stats-hint{color:var(--t3);font-weight:500;}
#stats b{color:var(--t1);font-weight:var(--fw-semibold);}
#save-ind{font-size:var(--fs-xs);padding:4px 8px;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.2);border-radius:6px;color:var(--ok);opacity:0;transition:opacity var(--motion-base);pointer-events:none;flex:0 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-align:center;box-sizing:border-box;}
#save-ind.show{opacity:1;}
#mtag.drag{color:var(--ac);background:rgba(59,130,246,.15);border-color:rgba(59,130,246,.3);}
#main{display:flex;flex:1;overflow:hidden;min-height:0;position:relative;background:var(--bg-base);}
/* P2 #8: 左栏宽度可调（splitter 拖动时 JS 直接改 inline width；min/max 由 JS 钳制 [180,600]） */
/* iter9 user-feedback fix: LP 整体下移避开 scene-tabs (top:12 处), 上移避开 help 文字 (bottom:12 处).
 * scene-tabs 在 #cw top:12 浮动 → LP 顶部之前与 scene-tabs 同行, 视觉上像 LP 上方被挡.
 * 新位置: top:60 (避开 scene-tabs), bottom:12 (lp 内部 lp-actions 有自带 padding 已足够) */
#lp{position:absolute;top:12px;left:12px;bottom:12px;width:280px;min-width:180px;max-width:600px;display:flex;flex-direction:column;overflow:hidden;z-index:var(--z-sticky);}
/* 货箱 / 货物 两区:鼠标移到哪个区,哪个区放大展开(flex-grow),另一区收缩到只剩表头+搜索,
 *   这样不用在小框里滚滚轮就能看完该区全部卡片。纯 CSS hover + flex-grow 过渡。min-height 保表头不被压没。 */
/* 结构基线;flex-grow / min-height / hover-放大 由 44-skin-panels.js 的 #lp .ps 规则统一管(id 特异性 + 后注入)。 */
.ps{border-bottom:1px solid var(--border-default);display:flex;flex-direction:column;overflow:hidden;flex:1 1 0;transition:flex-grow .3s cubic-bezier(.4,0,.2,1);}
/* DEVLOG-...-glass-system §1 T6: ph (section header) 加底分隔线, 让 lp/rp 内的"货箱/货物/属性" section 界面更清晰 */
/* DEVLOG-...-glass-system §1 T11 iter2: .ph (section header) — 字号 + 颜色加强让区分更清晰 */
.ph{
  padding:14px 16px 10px;
  display:flex;
  justify-content:space-between;
  align-items:center;
  flex-wrap:wrap;
  row-gap:6px;
  font-size:11px;
  font-weight:var(--fw-bold);
  color:rgba(255,255,255,0.55);
  text-transform:uppercase;
  letter-spacing:1.2px;
  border-bottom:1px solid var(--glass-2-stroke);
  margin-bottom:4px;
}
/* ph 第一个 span (label) 加发光点装饰 — 视觉锚点 */
.ph > span:first-child{position:relative;padding-left:12px;}
.ph > span:first-child::before{
  content:'';
  position:absolute;
  left:0; top:50%;
  width:3px; height:3px;
  margin-top:-1.5px;
  border-radius:50%;
  background:var(--ac);
  box-shadow:0 0 6px rgba(79,143,255,0.6);
}
.ph button{padding:4px 8px;border:1px solid transparent;border-radius:6px;background:rgba(255,255,255,0.04);color:var(--t2);font-size:var(--fs-xs);cursor:pointer;transition:all var(--motion-fast);}
.ph button:hover{background:rgba(255,255,255,0.08);color:var(--t1);}
.pb{padding:8px 12px;overflow-y:auto;flex:1 1 0;min-height:0;}
.pb.grow{flex:1 1 0;}
.pb::-webkit-scrollbar{width:4px;}
.pb::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:2px;}
.oi{display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--bg-input);border:1px solid transparent;border-radius:10px;margin-bottom:6px;cursor:pointer;transition:all var(--motion-fast);}
.oi:hover{background:var(--bg-hover);transform:translateY(-1px);}
.oi.sel{border-color:var(--ac);background:rgba(59,130,246,.1);box-shadow:0 0 0 1px rgba(59,130,246,0.2) inset;}
.oi.has-wn{border-color:var(--wn);}
.oi.has-er{border-color:var(--er);background:rgba(239,68,68,.08);}
.oi-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;box-shadow:inset 0 0 0 1px rgba(0,0,0,0.2);}
.oi-info{flex:1;min-width:0;}
.oi-name{font-size:var(--fs-md);font-weight:var(--fw-semibold);margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.oi-desc{font-size:var(--fs-xs);color:var(--t3);}
.oi-badge{font-size:9px;line-height:1.4;padding:2px 6px;border-radius:4px;font-weight:var(--fw-semibold);white-space:nowrap;flex-shrink:0;}
.ok-b{background:rgba(16,185,129,.15);color:var(--ok);}
.wn-b{background:rgba(245,158,11,.15);color:var(--wn);}
.er-b{background:rgba(239,68,68,.15);color:var(--er);}
#cw{position:absolute;inset:0;background:var(--bg-base);overflow:hidden;display:flex;flex-direction:column;z-index:var(--z-base);}
#cv{display:block;touch-action:none;flex:1;width:100%;height:100%;}
/* DEVLOG-...-glass-system §1 T6: scene-tabs 走 .gpanel-2 (但 padding 不一样, 这里 override) */
/* iter: 标签栏右侧用 --rp-width 收边(原来只设 left 无 right → flex 行无限向右长到属性面板下方,用户反馈"标签被挡")。
 *   left/right 都跟随 splitter 写的 --lp-width/--rp-width;标签超宽时本行内横向滚动(overflow-x:auto)而非溢出。 */
/* #scene-tabs is now a non-scrolling flex row: [ scroller(flex:1) ][ fixed cluster(flex:0) ]
   overflow-x is on #scene-tabs-scroll so the fixed cluster never gets covered by tabs */
#scene-tabs{position:absolute;top:12px;left:calc(var(--lp-width, 280px) + 24px);right:calc(var(--rp-width, 300px) + 24px);display:flex;align-items:center;padding:4px;border-radius:var(--r-md);z-index:var(--z-sticky);}
#scene-tabs-scroll{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:4px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;position:relative;isolation:isolate;}
#scene-tabs-scroll::-webkit-scrollbar{display:none;}
/* FIX-#2 (2026-05-29): 旧版 .scene-tab 自身 overflow:hidden + max-width → 标签名长 / 场景多时把
 *   末尾的关闭× 直接裁掉(用户反馈"x符号被遮挡")。改:tab 不裁剪、不收缩(flex:0 0 auto),
 *   省略号下放到 .scene-tab-label,× 用 flex:0 0 auto 永不被挤掉;一排放不下时 #scene-tabs 横向滚动。 */
/* 场景多时:标签 flex-shrink 自适应压缩,所有场景 + 右侧按钮都在一排可见,不靠横向滚动也不挤掉按钮。
 *   名字超长用省略号 + 原生 title tooltip;窗口极窄时 #scene-tabs-scroll 仍可横向滚动兜底。 */
.scene-tab{display:flex;align-items:center;gap:6px;padding:6px 12px;background:transparent;border:1px solid transparent;border-radius:6px;font-size:var(--fs-sm);color:var(--t2);cursor:pointer;user-select:none;white-space:nowrap;min-width:54px;max-width:160px;flex:1 1 auto;transition:background var(--motion-fast),color var(--motion-fast),border-color var(--motion-fast);}
.scene-tab-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
/* 悬停只做底色/描边强调(不再 scale + max-width 放大,避免在滚动边缘被裁切成"半截发光"残影);完整名走 title tooltip。 */
.scene-tab:hover{background:rgba(255,255,255,0.08);color:var(--t1);border-color:rgba(255,255,255,0.12);}
.scene-tab:hover .scene-tab-label{overflow:hidden;text-overflow:ellipsis;}
/* DEVLOG-...-glass-system §1 T6: scene-tab.active 升级 — 底部滑动 accent underline (复刻 Chrome/Edge tab) */
.scene-tab{position:relative;}
/* DEVLOG-...-glass-system §1 T11 iter3: scene-tab active — accent tint + 微 glow */
.scene-tab.active{
  background:linear-gradient(180deg, rgba(79,143,255,0.18), rgba(79,143,255,0.05));
  color:#fff;
  font-weight:var(--fw-semibold);
  box-shadow:0 1px 2px rgba(0,0,0,0.2), inset 0 0 0 1px rgba(79,143,255,0.30);
}
.scene-tab.active::after{
  content:'';
  position:absolute;
  left:8px; right:8px; bottom:2px;
  height:2px;
  background:var(--grad-accent);
  border-radius:2px;
  animation:tab-underline-slide var(--motion-tab-stagger);
  pointer-events:none;
}
@keyframes tab-underline-slide{
  from{transform:scaleX(0); transform-origin:left;}
  to{transform:scaleX(1); transform-origin:left;}
}
.scene-tab-x{flex:0 0 auto;background:transparent;border:0;color:var(--t3);font-size:12px;padding:2px;cursor:pointer;border-radius:4px;line-height:1;margin-left:4px;transition:all var(--motion-fast);}
.scene-tab-x:hover{background:rgba(239,68,68,.2);color:var(--er);}
.scene-tab-add{flex:0 0 auto;background:transparent;border:1px dashed var(--border-subtle);color:var(--t3);padding:4px 10px;font-size:16px;line-height:1;border-radius:6px;cursor:pointer;margin-left:2px;transition:all var(--motion-fast);}
.scene-tab-add:hover{background:rgba(255,255,255,0.05);color:var(--t1);border-color:var(--border-default);}
/* #8 (2026-05-29): 删除所有场景按钮 — 危险操作,红色调,放在 + 新场景 右侧 */
.scene-tab-clear{flex:0 0 auto;background:transparent;border:1px solid var(--border-subtle);color:var(--t3);padding:4px 10px;font-size:var(--fs-sm);line-height:1;border-radius:6px;cursor:pointer;margin-left:2px;white-space:nowrap;transition:all var(--motion-fast);}
.scene-tab-clear:hover{background:rgba(239,68,68,.16);color:var(--er);border-color:rgba(239,68,68,.5);}
/* #20 fix: fixed cluster is a non-scrolling flex sibling of #scene-tabs-scroll;
   no sticky, no gradient mask needed — it always sits fully visible at the right */
.scene-tabs-fixed{flex:0 0 auto;display:flex;align-items:center;gap:4px;padding-left:8px;border-left:1px solid rgba(255,255,255,0.08);margin-left:4px;}
/* DEVLOG-...-glass-system §1 T6: #dov 走 .gpanel-3, 仅保留位置 + pill 圆角 + display none */
#dov{position:absolute;top:70px;left:50%;transform:translateX(-50%);padding:10px 24px;border-radius:var(--r-pill);font-size:var(--fs-sm);font-weight:var(--fw-medium);color:var(--t1);text-align:center;display:none;pointer-events:none;}
#dov.show{display:block;}
/* 轴重配载分析面板:锚定画布左下,跟随 --lp-width(面板收起/拖宽自动适配,不写死像素,不用 translate 定位)。 */
#axle-panel{position:absolute;bottom:16px;left:calc(var(--lp-width, 280px) + 24px);width:340px;max-width:42vw;padding:12px 14px;border-radius:var(--r-md);font-size:var(--fs-xs);color:var(--t1);z-index:var(--z-sticky);}
#axle-panel .axp-title{font-size:var(--fs-sm);font-weight:var(--fw-semibold);color:var(--ac);margin-bottom:8px;letter-spacing:.3px;}
#axle-panel .axp-row{margin-bottom:7px;}
#axle-panel .axp-lbl{display:flex;justify-content:space-between;font-size:11px;color:var(--t2);margin-bottom:2px;}
#axle-panel .axp-lbl .axp-over{color:var(--er);font-weight:var(--fw-semibold);}
#axle-panel .axp-bar{position:relative;height:9px;border-radius:5px;background:rgba(255,255,255,.08);overflow:hidden;}
#axle-panel .axp-fill{position:absolute;top:0;left:0;height:100%;border-radius:5px;background:var(--ok);transition:width var(--motion-fast);}
#axle-panel .axp-fill.over{background:var(--er);}
#axle-panel .axp-fill.nolimit{background:var(--ac);}
#axle-panel .axp-foot{margin-top:8px;font-size:10px;color:var(--t3);line-height:1.5;}
#axle-panel .axp-cg{color:var(--ac2);font-weight:var(--fw-medium);}
/* 装载 KPI 面板 — 镜像 #axle-panel 玻璃风格(DOM/SVG,零额外 GPU)。放右上,避开左侧 axle-panel。 */
#kpi-panel{position:absolute;top:72px;right:calc(var(--rp-width, 300px) + 24px);width:296px;max-width:38vw;padding:12px 14px;border-radius:var(--r-md);font-size:var(--fs-xs);color:var(--t1);z-index:var(--z-sticky);}
#kpi-panel .kpi-title{font-size:var(--fs-sm);font-weight:var(--fw-semibold);color:var(--ac);margin-bottom:8px;letter-spacing:.3px;}
#kpi-panel .kpi-row{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-bottom:6px;}
#kpi-panel .kpi-k{color:var(--t2);font-size:11px;}
#kpi-panel .kpi-v{color:var(--t1);font-weight:var(--fw-semibold);font-variant-numeric:tabular-nums;}
#kpi-panel .kpi-v b{color:var(--ac);}
#kpi-panel .kpi-bar{position:relative;height:7px;border-radius:4px;background:rgba(255,255,255,.08);overflow:hidden;margin:1px 0 8px;}
#kpi-panel .kpi-fill{position:absolute;top:0;left:0;height:100%;border-radius:4px;background:var(--ok);transition:width var(--motion-fast);}
#kpi-panel .kpi-fill.warn{background:var(--ac);}
#kpi-panel .kpi-fill.over{background:var(--er);}
#kpi-panel .kpi-sub{color:var(--t3);font-size:10px;margin:-3px 0 8px;line-height:1.5;}
#kpi-panel .kpi-est{color:var(--ac2);font-size:9px;font-weight:var(--fw-medium);border:1px solid var(--ac2);border-radius:3px;padding:0 3px;margin-left:5px;opacity:.85;}
#kpi-panel .kpi-co2-edit{display:flex;align-items:center;gap:6px;margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,.08);}
#kpi-panel .kpi-co2-edit label{color:var(--t3);font-size:10px;flex:1;line-height:1.4;}
#kpi-panel .kpi-co2-edit input{width:62px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.14);border-radius:4px;color:var(--t1);font-size:11px;padding:2px 5px;text-align:right;font-variant-numeric:tabular-nums;}
#kpi-panel .kpi-foot{margin-top:8px;font-size:10px;color:var(--t3);line-height:1.5;}
#kpi-panel .kpi-empty{color:var(--t3);font-size:11px;line-height:1.7;}
/* 物理有效装载认证徽章 — 顶栏内联 pill(紧邻 #stats 右侧),风格对齐 #stats/#mtag。
 * #46 (2026-05-29) 固定宽度槽思路:min-width 预留槽,文本(通过 / N 项违规·不通过)只在槽内变,
 *   不推动邻栏(#save-ind/#mtag)左右抖动。pill 本身相对定位,展开清单作为 absolute popover 锚在其下方,
 *   不撑高顶栏。绿=通过 / 红=不通过。 */
#validity-badge{position:relative;font-size:var(--fs-xs);flex-shrink:0;min-width:148px;box-sizing:border-box;}
#validity-badge .vb-head{display:inline-flex;align-items:center;gap:7px;width:100%;padding:4px 10px;background:rgba(255,255,255,0.04);border:1px solid var(--border-default);border-radius:var(--r-sm);cursor:pointer;font-weight:var(--fw-semibold);white-space:nowrap;user-select:none;outline:none;box-sizing:border-box;}
#validity-badge .vb-head:focus-visible{box-shadow:inset 0 0 0 2px var(--ac);}
#validity-badge .vb-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;}
#validity-badge .vb-head.ok .vb-dot{background:var(--ok);box-shadow:0 0 6px var(--ok);}
#validity-badge .vb-head.bad .vb-dot{background:var(--er);box-shadow:0 0 6px var(--er);}
#validity-badge .vb-head.ok .vb-txt{color:var(--ok);}
#validity-badge .vb-head.bad .vb-txt{color:var(--er);}
#validity-badge .vb-txt{flex:1;overflow:hidden;text-overflow:ellipsis;}
#validity-badge .vb-caret{color:var(--t3);font-size:10px;font-weight:var(--fw-medium);flex:0 0 auto;}
/* 展开违规清单:body 级 fixed popover(逃逸 #hdr 的 backdrop-filter stacking context)。
   DEVLOG-2026-07-12-validity-badge-zindex-and-falsepos 根因:
   .vb-list 原 position:absolute 锚在 #validity-badge 内,而 #hdr(.gpanel-2)有 backdrop-filter →
   既建 stacking context 又是 fixed 的 containing block,清单再高的 z-index 也被 #hdr 整体(--z-sticky:100)
   裁在 #scene-tabs(同 --z-sticky,DOM 靠后 → 后绘覆盖)之下 → 用户点开看不到明细。
   解法同 tooltip(.global-tip)/AI 窗:JS(09-ui-render refreshValidityBadge)把清单挂到 <body>、
   position:fixed、getBoundingClientRect 定位、z 用 --z-menu(1010,高于 scene-tabs/侧栏/AI 窗)。
   选择器随之从 `#validity-badge .vb-*` 改为 `.vb-list .vb-*`(清单已不再是 #validity-badge 的后代)。 */
.vb-list{position:fixed;width:296px;max-width:38vw;padding:6px 10px 10px;background:var(--glass-2-bg);border:1px solid var(--glass-2-border);border-radius:var(--r-md);box-shadow:var(--elev-3);backdrop-filter:blur(var(--glass-2-blur));-webkit-backdrop-filter:blur(var(--glass-2-blur));max-height:46vh;overflow-y:auto;z-index:var(--z-menu);}
.vb-list .vb-item{display:flex;align-items:baseline;gap:6px;padding:4px 0;border-bottom:1px solid rgba(255,255,255,.05);line-height:1.5;}
.vb-list .vb-k{flex:0 0 auto;color:var(--er);font-weight:var(--fw-semibold);font-size:11px;}
.vb-list .vb-n{flex:0 0 auto;color:var(--t1);font-size:11px;max-width:96px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.vb-list .vb-d{flex:1;color:var(--t3);font-size:10px;}
/* iter12 user-feedback fix: help 移到 canvas 右下角附近, 远离 LP 右侧.
 * 位置: right:540 (avoid minimap@right:324+200=524, 留 16px gap), bottom:16.
 * minimap 高 150 + bottom:14, top edge 在 bottom:164 — help 在 bottom:16 行高 1.7 大约只占 50px, 不会撞 minimap. */
#help{
  position:absolute;
  bottom:16px;
  right:540px;
  left:auto;
  padding:8px 14px;
  font-size:11px;
  color:rgba(255,255,255,0.78);
  line-height:1.8;
  pointer-events:none;
  opacity:0.88;
  transition:opacity 200ms ease;
  max-width:360px;
  z-index:60;
  text-align:right;
}
#help .help-row{display:flex;flex-wrap:wrap;gap:12px;justify-content:flex-end;}
#help .help-row + .help-row{margin-top:3px;}
#help kbd{display:inline-block;padding:2px 6px;background:rgba(255,255,255,0.1);border-radius:4px;font-size:var(--fs-xxs);line-height:1;color:var(--t1);margin:0 2px;font-family:var(--ff-mono);border:1px solid rgba(255,255,255,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.2);}
/* P2 #8: 右栏宽度可调 */
/* DEVLOG-...-glass-system §1 T6: 同 #lp, 视觉走 .gpanel-2 */
#rp{position:absolute;top:12px;right:12px;bottom:12px;width:300px;min-width:200px;max-width:600px;display:flex;flex-direction:column;overflow:hidden;z-index:var(--z-sticky);}
#props{flex:1;overflow-y:auto;padding:0 4px;}
#props::-webkit-scrollbar{width:4px;}
#props::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:2px;}
/* DEVLOG-...-glass-system iter5: .sec (右栏 props 内的 section) — 细分隔线 + padding 微调 */
.sec{padding:14px 16px;border-bottom:1px solid var(--glass-2-stroke);}
.sec:last-child{border-bottom:none;}
.st{font-size:var(--fs-xs);font-weight:var(--fw-bold);color:var(--t3);text-transform:uppercase;letter-spacing:.5px;margin-bottom:11px;}
.pg{margin-bottom:10px;}
.pg:last-child{margin-bottom:0;}
.pl{font-size:var(--fs-xs);color:var(--t2);margin-bottom:6px;display:flex;justify-content:space-between;letter-spacing:0.5px;}
.pl span{color:var(--t3);}
.pi{width:100%;padding:10px 14px;background:var(--bg-input);border:1px solid transparent;border-radius:8px;color:var(--t1);font-size:var(--fs-md);outline:none;transition:all var(--motion-fast);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}
.pi:hover{background:var(--bg-hover);}
.pi:focus{border-color:var(--ac);background:rgba(255,255,255,0.08);box-shadow:0 0 0 3px rgba(59,130,246,0.15);}
.pi:disabled{opacity:.4;cursor:not-allowed;}
/* 颜色选择器：显示成可见色块（默认浏览器样式在暗色 UI 中只显示一条线）*/
.pi[type="color"], input[type="color"].pi, .fg input[type="color"]{
  width:100%;height:34px;padding:3px;cursor:pointer;border-radius:7px;background:var(--bg-input);
}
input[type="color"]::-webkit-color-swatch-wrapper{padding:0;border-radius:5px;overflow:hidden;}
input[type="color"]::-webkit-color-swatch{border:none;border-radius:5px;}
input[type="color"]::-moz-color-swatch{border:none;border-radius:5px;}
.pr{display:grid;gap:7px;}
.pr.c3{grid-template-columns:1fr 1fr 1fr;}
.pr.c2{grid-template-columns:1fr 1fr;}
.br{display:flex;gap:6px;flex-wrap:wrap;}
.bb{flex:1;min-width:55px;padding:9px 16px;border:1px solid transparent;border-radius:8px;background:rgba(255,255,255,0.05);color:var(--t1);font-size:var(--fs-sm);font-weight:var(--fw-medium);cursor:pointer;transition:all var(--motion-fast);text-align:center;box-shadow:0 2px 4px rgba(0,0,0,0.1);}
.bb:hover{background:rgba(255,255,255,0.08);transform:translateY(-1px);}
.bb:active{transform:translateY(0);}
.bb.red:hover{background:rgba(239,68,68,.15);color:var(--er);}
/* DEVLOG-...-glass-system iter5: .wcard (承重卡片) — 玻璃化 */
.wcard{
  background:rgba(255,255,255,0.025);
  border:1px solid var(--glass-2-stroke);
  border-radius:var(--r-md);
  padding:12px;
  margin-top:10px;
}
.wh{display:flex;justify-content:space-between;font-size:var(--fs-xs);margin-bottom:7px;}
.wh .wl{color:var(--t2);}
.wh .wv{font-weight:var(--fw-semibold);}
.wbar{height:8px;background:rgba(255,255,255,.07);border-radius:4px;overflow:hidden;}
.wfill{height:100%;border-radius:4px;transition:all var(--motion-slow);}
.wfill.ok{background:linear-gradient(90deg,#22c55e,#16a34a);}
.wfill.wn{background:linear-gradient(90deg,#f59e0b,#d97706);}
.wfill.er{background:linear-gradient(90deg,#ef4444,#dc2626);}
.ww{margin-top:8px;padding:var(--space-2);background:rgba(239,68,68,.1);border-radius:5px;font-size:var(--fs-xs);color:var(--er);}
/* DEVLOG-...-glass-system iter5: .scard (稳定卡片) — 玻璃化 */
.scard{
  background:rgba(255,255,255,0.025);
  border:1px solid var(--glass-2-stroke);
  border-radius:var(--r-md);
  padding:12px;
  margin-top:10px;
}
.ss{font-size:var(--fs-md);font-weight:var(--fw-semibold);margin-bottom:4px;}
.ss.ok{color:var(--ok);}
.ss.er{color:var(--er);}
.sd{font-size:var(--fs-xs);color:var(--t3);}
/* DEVLOG-...-glass-system §1 T11 iter4: empty-state 升级 — 给 ei (icon) 加渐变 + 入场动画, et/ed 字号微调 */
.empty-state{padding:48px var(--space-4) 56px;text-align:center;color:var(--t3);animation:empty-fade-in 600ms var(--ease-out-soft);}
.empty-state .ei{
  font-size:48px;
  margin-bottom:16px;
  opacity:.55;
  display:inline-block;
  background:linear-gradient(135deg, rgba(79,143,255,0.8), rgba(139,92,246,0.6));
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  filter:drop-shadow(0 4px 16px rgba(79,143,255,0.25));
}
.empty-state .et{
  font-size:14px;
  font-weight:var(--fw-semibold);
  color:rgba(255,255,255,0.78);
  margin-bottom:8px;
  letter-spacing:0.2px;
}
.empty-state .ed{
  font-size:12px;
  line-height:1.8;
  color:rgba(255,255,255,0.45);
}
@keyframes empty-fade-in{
  from{opacity:0; transform:translateY(8px);}
  to{opacity:1; transform:translateY(0);}
}
/* 浮动面板（替代旧弹窗）— 不挡画布，无遮罩，画布交互不被打断 */
/* R15 §T1.4 F-A3-001: #mbg 不再用 display:none 瞬切,而是 opacity+visibility fade.
   全程 display:block(.open.centered/.from-opt 改 display:flex 居中),靠 opacity 控可见性. */
#mbg{position:fixed;top:60px;left:calc(var(--lp-width,280px) + 12px);z-index:var(--z-modal);display:block;pointer-events:none;opacity:0;visibility:hidden;transition:opacity var(--motion-fast) ease, visibility 0s var(--motion-fast);}
/* MODAL-OPT-LAYOUT: modal 从最优面板打开时，居中浮在最优面板**之上**（z 比 opt-bg 高）；
   最优面板淡出 40% + 不可点，表示"等编辑结束"。视觉上是一个干净的"嵌套 modal"，不再左/右割裂。*/
#mbg.open.from-opt,
#mbg.open.centered{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.45);backdrop-filter:blur(2px);pointer-events:auto;z-index:var(--z-modal);}
/* 信息型宽 modal（如键盘快捷键） */
#mo.wide{width:880px;}
#opt-bg.with-modal>#opt-mo{opacity:.35;filter:blur(4px);pointer-events:none;transition:opacity var(--motion-fast),filter var(--motion-fast);}
#mbg.open{opacity:1;visibility:visible;transition:opacity var(--motion-fast) ease;}
/* DEVLOG-...-glass-system §1 T7: #mo 视觉走 .gpanel-1. 保留 padding + 尺寸 + flex + transform 入场 */
/* 入场用 translateY 而非 scale —— scale + backdrop-filter 会丢圆角裁剪(方角闪)+ 每帧重算模糊卡顿. */
#mo{padding:24px 24px 20px;width:380px;max-width:calc(100vw - 100px);max-height:calc(100vh - 90px);pointer-events:auto;display:flex;flex-direction:column;position:relative;transform:translateY(8px);transition:transform var(--motion-fast);}
#mbg.open #mo{transform:translateY(0);}
/* MASTER P0-M2 FIX: 右上角关闭按钮（与 cancel 按钮等价 — 触发 onCancel） */
.mo-x{position:absolute;top:12px;right:12px;width:32px;height:32px;background:rgba(255,255,255,0.04);border:none;color:var(--t2);font-size:var(--fs-icon-lg);line-height:1;cursor:pointer;border-radius:8px;display:flex;align-items:center;justify-content:center;padding:0;z-index:var(--z-raised);transition:all var(--motion-fast);}
.mo-x:hover{background:rgba(239,68,68,.15);color:var(--er);}
/* PREVIEW-CANVAS-LARGER: modal 加宽到 1080px，preview 给 640×640，让六面字 + 大箭头 + dim 标签都看得清 */
/* 2026-05-27 用户原话「确定取消按钮跑下面去了」:
   - 旧 flex-wrap 方案:form 撑满 max-height(viewport-130)后,mbtns 第二行被推到屏外/底边切.
   - 改 CSS grid:form+preview 占 row1 (1fr 高度自适应剩余空间),mbtns 占 row2 (auto高度固定),
     #mo 总 max-height=viewport-30 → row1 内部 form/preview overflow:auto 自滚,mbtns 永远贴 #mo 底部可见. */
/* 尺寸收敛(用户:编辑面板太大占满整屏):宽高都封顶成舒适对话框,绝不铺满屏幕。
   预览列也封顶,避免宽屏上 1fr 把面板撑得很大。 */
#mo.with-preview{width:min(900px, calc(100vw - 140px));display:grid;grid-template-columns:340px minmax(360px,520px);grid-template-rows:1fr auto;grid-template-areas:"form preview" "mbtns mbtns";gap:14px;max-height:min(700px, calc(100vh - 140px));}
#mo.with-preview .mo-form{grid-area:form;overflow-y:auto;padding-right:6px;min-height:0;}
#mo.with-preview .mo-preview{grid-area:preview;min-height:0;}
#mo.with-preview .mbtns{grid-area:mbtns;background:var(--bg-elevated);padding-top:8px;border-top:1px solid var(--border-default);position:sticky;bottom:0;z-index:2;}
/* PREVIEW-FIXED-SIZE: 限制 preview 高度=表单高度（align-items:stretch + min-height:0），canvas 不再撑高 modal */
/* preview 走 grid area,height auto 跟随 row1 高度;canvas flex:1 在内部占 grid cell */
#mo.with-preview .mo-preview{display:flex;flex-direction:column;gap:var(--space-2);background:#0a0e1a;border:1px solid var(--border-default);border-radius:8px;padding:var(--space-2);min-width:0;min-height:0;overflow:hidden;}
#mo:not(.with-preview) .mo-form{overflow-y:auto;max-height:calc(100vh - 130px);}
/* min-height:0 让 canvas 不抗拒被压缩，flex:1 在 column 内吃剩余高度 */
.mo-preview canvas{flex:1;width:100%;height:auto;min-height:0;background:#0a0e1a;border-radius:5px;cursor:grab;display:block;}
.mo-preview canvas:active{cursor:grabbing;}
.mo-preview .mp-hint{font-size:var(--fs-xs);color:var(--t3);text-align:center;line-height:1.5;}
/* DEVLOG-...-glass-system §1 T11 iter2: modal h2 title 升级 — 渐变文字 + 字号加大, 让用户一眼识别是哪个 modal */
#mo h2{
  font-size:20px;
  font-weight:var(--fw-bold);
  margin-bottom:20px;
  display:flex;
  align-items:center;
  gap:7px;
  letter-spacing:-0.3px;
  background:linear-gradient(135deg, #ffffff 0%, #cbd5ff 80%, #a5b4fc 100%);
  -webkit-background-clip:text;
  background-clip:text;
  -webkit-text-fill-color:transparent;
  color:transparent;
}
#mo h2::before{content:'';}
.fg{margin-bottom:16px;}
.fg label{display:block;font-size:var(--fs-sm);color:var(--t2);margin-bottom:6px;font-weight:var(--fw-medium);}
.fg input{width:100%;padding:12px 14px;background:var(--bg-input);border:1px solid transparent;border-radius:8px;color:var(--t1);font-size:var(--fs-md);outline:none;transition:all var(--motion-fast);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}
.fg input:hover{background:var(--bg-hover);}
.fg input:focus{border-color:var(--ac);background:rgba(255,255,255,0.08);box-shadow:0 0 0 3px rgba(59,130,246,0.15);}
/* MASTER P0-U2/U4 FIX: 输入校验失败的红边提示（dim<=0 / NaN / 负重量等） */
.input-invalid,input.input-invalid,.pi.input-invalid,textarea.input-invalid,select.input-invalid{border-color:var(--er) !important;background:rgba(239,68,68,.10) !important;box-shadow:0 0 0 2px rgba(239,68,68,.35) !important;}
.fr{display:grid;gap:10px;}
.fr.c3{grid-template-columns:1fr 1fr 1fr;}
.fr.c2{grid-template-columns:1fr 1fr;}
.fsec{font-size:var(--fs-xs);color:var(--t3);text-transform:uppercase;font-weight:var(--fw-bold);letter-spacing:.5px;margin:18px 0 10px;padding-bottom:7px;border-bottom:1px solid var(--border-default);}
.fhint{font-size:var(--fs-xs);color:var(--t3);margin-top:6px;line-height:1.7;}
.pbtns{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;}
.pbtn{padding:5px 11px;border:1px solid var(--border-default);border-radius:6px;background:var(--bg-input);color:var(--t2);font-size:var(--fs-xs);cursor:pointer;}
.pbtn:hover{border-color:var(--ok);color:var(--ok);}
/* MASTER P1-M12 FIX: .mbtns 移到 .mo-form 外面后，需要 flex-shrink:0 保证短屏下不被压缩；
   #mo 已是 flex-direction:column，.mo-form 设 flex:1 + min-height:0 + overflow-y:auto
   让滚动只发生在 form 内（#mo-body 的内容），按钮永远贴底。 */
#mo:not(.with-preview) .mo-form{flex:1 1 auto;min-height:0;overflow-y:auto;max-height:none;}
.mbtns{display:flex;gap:9px;margin-top:14px;flex-shrink:0;}
/* 2026-05-27 #34 用户原话「确定按钮怎么连个框都没有」:加 border + 更醒目 */
.mbtns button{flex:1;padding:var(--space-3);border-radius:8px;font-size:var(--fs-lg);font-weight:var(--fw-semibold);cursor:pointer;border:1px solid transparent;transition:filter var(--motion-fast),transform var(--motion-fast),box-shadow var(--motion-fast);}
.mbtns button:active{transform:translateY(1px);}
.m-ok{background:linear-gradient(135deg,var(--ac),var(--ac2));color:#fff;border-color:var(--ac);box-shadow:0 4px 14px rgba(79,143,255,.35);}
.m-ok:hover{filter:brightness(1.1);box-shadow:0 6px 18px rgba(79,143,255,.5);}
/* MASTER P1-M6 FIX: okDanger 切换 .red — 给删除/危险动作一个明显区别于蓝色 OK 的红色样式 */
.m-ok.red{background:linear-gradient(135deg,#ef4444,#dc2626);border-color:#dc2626;color:#fff;}
.m-ok.red:hover{background:linear-gradient(135deg,#f87171,#ef4444);}
.m-cancel{background:var(--bg-input);color:var(--t1);border-color:var(--border-default);}
.m-cancel:hover{background:var(--bg-hover);border-color:var(--ac);color:var(--ac);}
/* DEVLOG-...-glass-system §1 T7: #tip 走 .gpanel-3 (轻悬浮玻璃). 保留位置 + display + max-width */
#tip{position:fixed;color:var(--t1);padding:10px 14px;font-size:var(--fs-sm);pointer-events:none;display:none;z-index:var(--z-popup);max-width:260px;line-height:1.8;}
#tip strong{color:var(--ac);}
/* DEVLOG-...-glass-system §1 T7: #ctx 走 .gpanel-1 (强玻璃, 与 modal 视觉一致). 保留位置 + padding + display */
#ctx{position:fixed;padding:6px 0;min-width:180px;z-index:var(--z-popup);display:none;}
.ci{padding:10px var(--space-4);font-size:var(--fs-md);color:var(--t2);cursor:pointer;display:flex;align-items:center;gap:10px;transition:all var(--motion-fast);}
.ci:hover{background:rgba(79,143,255,.1);color:var(--t1);}
.ci.red:hover{background:rgba(239,68,68,.1);color:var(--er);}
.cs{height:1px;background:var(--border-default);margin:var(--space-1) 0;}
.ci .ck{margin-left:auto;font-size:var(--fs-xxs);line-height:var(--lh-base);color:var(--t3);padding:1px 5px;background:var(--bg-input);border-radius:3px;}
.sup-ctrl{display:flex;align-items:center;gap:6px;}
.sup-ctrl label{font-size:var(--fs-xs);color:var(--t2);white-space:nowrap;}
.sup-ctrl input[type=range]{width:70px;height:4px;accent-color:var(--ac);cursor:pointer;}
.sup-ctrl .sup-val{font-size:var(--fs-xs);color:var(--ac);font-weight:var(--fw-semibold);min-width:28px;text-align:center;}
.lock-icon{font-size:var(--fs-xs);cursor:pointer;padding:2px 5px;border-radius:3px;}
.lock-icon.locked{color:var(--wn);background:rgba(245,158,11,.15);}
.lock-icon.unlocked{color:var(--t3);}
.stat-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;font-size:var(--fs-sm);}
.stat-row .sl{color:var(--t2);}
.stat-row .sv{font-weight:var(--fw-semibold);}
.stat-row .sv.ok{color:var(--ok);}
.stat-row .sv.wn{color:var(--wn);}
.stat-row .sv.er{color:var(--er);}
.util-bar{height:10px;background:rgba(255,255,255,.07);border-radius:5px;overflow:hidden;margin-top:6px;position:relative;}
.util-fill{height:100%;border-radius:5px;transition:width var(--motion-slow);}
.util-fill.ok{background:linear-gradient(90deg,#22c55e,#16a34a);}
.util-fill.wn{background:linear-gradient(90deg,#f59e0b,#d97706);}
.util-fill.er{background:linear-gradient(90deg,#ef4444,#dc2626);}
/* DEVLOG-...-glass-system §1 T11 iter3: ct-item 卡片 — 圆角/spacing 加大, hover lift + accent glow */
.ct-item{
  display:flex;
  align-items:flex-start;
  gap:10px;
  padding:10px 10px;
  background:var(--bg-input);
  border:1px solid var(--glass-2-stroke);
  border-radius:var(--r-md);
  margin-bottom:7px;
  cursor:grab;
  position:relative;
  transition:
    background-color var(--motion-fade),
    border-color var(--motion-fade),
    box-shadow var(--motion-hover),
    transform var(--motion-hover);
  user-select:none;
}
.ct-item:hover{
  border-color:rgba(79,143,255,0.35);
  background:rgba(79,143,255,0.06);
  transform:translateY(-1px);
  box-shadow:var(--elev-1), 0 0 0 1px rgba(79,143,255,0.18);
}
.ct-item.empty{border-color:rgba(239,68,68,.3);opacity:.7;}
/* R20-C0f: 卡片 hover 不红边 — :hover 覆盖 .empty 红色边框,只保留 delete 按钮内的红色提示 */
.ct-item.empty:hover{border-color:var(--border-default);}
/* R20-C0e: 搜索 empty-hint — 输入有内容但 0 匹配时显示 */
.empty-hint{color:var(--t3);font-size:var(--fs-sm);padding:14px 8px;text-align:center;font-style:italic;}
.ct-item.dragging-pal{border-color:var(--ok);background:rgba(34,197,94,.08);}
/* HIGHLIGHT-LINK: 选中场景物体时，左栏对应类型卡 — 蓝色细框（不夸张，3D 物体那边的强动效保留） */
.ct-item.linked-active{
  border-color:var(--ac);
  background:rgba(79,143,255,.12);
  box-shadow:
    0 0 0 1px rgba(79,143,255,.55),
    0 0 16px rgba(79,143,255,.25),     /* outer glow */
    inset 0 0 0 1px rgba(255,255,255,.04);  /* inner highlight */
  transform:translateY(-1px);
}
.ct-dot{width:14px;height:14px;border-radius:3px;flex-shrink:0;margin-top:2px;}
.ct-info{flex:1;min-width:0;}
.ct-name{font-size:var(--fs-lg);font-weight:var(--fw-semibold);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:3px;}
.ct-desc{font-size:10.5px;color:var(--t3);line-height:1.55;word-break:break-word;}
.ct-desc span{display:inline;}
/* 名称行:名称占满,剩/共靠右,同一行不与下方 chips 重叠(用户:剩余数量与参数叠加) */
.ct-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;}
.ct-head .ct-name{flex:1;min-width:0;margin-bottom:0;}
.ct-count{text-align:right;flex-shrink:0;line-height:1.4;display:flex;align-items:baseline;gap:5px;white-space:nowrap;}
.ct-placed{font-size:var(--fs-xxs);line-height:var(--lh-base);color:var(--t3);}
.ct-remain{font-size:var(--fs-md);font-weight:var(--fw-bold);color:var(--ok);}
.ct-remain.zero{color:var(--er);}
.ct-total{font-size:var(--fs-xxs);line-height:var(--lh-base);color:var(--t3);}
#pal-ghost{position:fixed;pointer-events:none;z-index:var(--z-overlay);background:rgba(20,26,50,.93);border:1px solid rgba(79,143,255,.5);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.6);transform:translate(-50%,-110%);transition:background var(--motion-fast),border-color var(--motion-fast);}
#pal-ghost.over-canvas{background:rgba(6,30,18,.92);border-color:rgba(34,197,94,.7);}
#lp.drop-target{border-right:2px solid rgba(34,197,94,.8);background:rgba(34,197,94,.04)!important;transition:all var(--motion-fast);}
#drop-hint{position:absolute;inset:0;border:3px dashed rgba(34,197,94,.6);border-radius:4px;pointer-events:none;display:none;}
#drop-hint.show{display:block;}
.ct-edit-btn{font-size:var(--fs-xs);padding:2px 6px;border:1px solid var(--border-default);border-radius:4px;background:var(--bg-input);color:var(--t3);cursor:pointer;flex-shrink:0;}
.ct-edit-btn:hover{border-color:var(--ac);color:var(--ac);}
.ct-del-btn{font-size:var(--fs-lg);line-height:1;padding:1px 5px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;flex-shrink:0;margin-left:3px;}
.ct-del-btn:hover{border-color:#ef4444;color:#ef4444;background:rgba(239,68,68,.1);}
/* R24-T8c-19 T8c-19g (2026-07-08): 原位删除二次确认覆盖层 — 用户原话
   "二次确认框的'确认'按钮就和原始的'x（删除）'按钮在相同位置, 方便用户不用移动鼠标,
    背景雾面覆盖原始卡片信息, 保留倒计时, 超过一段时间不点击确认按钮就会消失变成原本的卡片信息"
   .ct-item 已加 position:relative, 此层 absolute 覆盖整张卡片 */
.ct-inline-confirm{
  position:absolute;
  inset:0;
  z-index:5;
  display:flex;
  flex-direction:column;
  align-items:stretch;
  justify-content:center;
  gap:6px;
  padding:8px 10px;
  background:rgba(20,26,50,0.88);
  backdrop-filter:blur(3px);
  border-radius:var(--r-md);
  cursor:default;
  animation:ct-inline-in 160ms ease-out;
}
@keyframes ct-inline-in{from{opacity:0;transform:scale(0.97);}to{opacity:1;transform:none;}}
.ct-inline-confirm .ct-inline-msg{
  font-size:var(--fs-sm);
  line-height:1.4;
  color:var(--t1);
  text-align:center;
  word-break:break-word;
}
.ct-inline-confirm .ct-inline-confirm-btn{
  align-self:center;
  padding:4px 14px;
  font-size:var(--fs-sm);
  font-weight:var(--fw-semibold);
  color:#fff;
  background:linear-gradient(180deg,#5a1a1a,#3a0e0e);
  border:1px solid #ef4444;
  border-radius:5px;
  cursor:pointer;
  min-width:120px;
}
.ct-inline-confirm .ct-inline-confirm-btn:hover{background:linear-gradient(180deg,#7a2222,#5a1a1a);}
.ct-inline-confirm .ct-inline-progress{
  height:2px;
  width:100%;
  background:rgba(255,255,255,0.12);
  border-radius:1px;
  overflow:hidden;
}
.ct-inline-confirm .ct-inline-progress-fill{
  height:100%;
  width:100%;
  background:#ef4444;
  border-radius:1px;
}
.ku-on{font-size:var(--fs-xxs);line-height:1.4;padding:2px 6px;border-radius:3px;font-weight:var(--fw-semibold);background:rgba(34,197,94,.15);color:var(--ok);}
.ku-off{font-size:var(--fs-xxs);line-height:1.4;padding:2px 6px;border-radius:3px;font-weight:var(--fw-semibold);background:rgba(79,143,255,.15);color:var(--ac);}
/* #opt-bg uses visibility/opacity (not display:none) so transitions work during exit animation.
   display:flex always; pointer-events:none when not open prevents stray clicks. */
#opt-bg{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px);opacity:0;visibility:hidden;pointer-events:none;transition:opacity 220ms cubic-bezier(.4,0,.2,1),visibility 0s 220ms;}
#opt-bg.open{opacity:1;visibility:visible;pointer-events:auto;transition:opacity 220ms cubic-bezier(.4,0,.2,1);}
/* R22-T1 Stage A: headless mode — visually hidden but logically open */
#opt-bg.open.opt-headless{visibility:hidden;opacity:0;pointer-events:none;}
/* fadeout: trigger exit — JS removes .open after 220ms */
#opt-bg.fadeout{opacity:0;visibility:visible;pointer-events:none;transition:opacity 220ms cubic-bezier(.4,0,.2,1),visibility 0s 220ms;}
/* DEVLOG-...-glass-system §1 T7: #opt-mo 视觉走 .gpanel-1. 保留尺寸 + overflow */
#opt-mo{padding:var(--space-5);width:780px;max-width:97vw;max-height:92vh;overflow-y:auto;}
/* DEVLOG-...-glass-system §1 T11 iter2: opt-mo title 同样渐变 */
#opt-mo h2{
  font-size:20px;
  font-weight:var(--fw-bold);
  margin-bottom:5px;
  letter-spacing:-0.3px;
  background:linear-gradient(135deg, #ffffff 0%, #cbd5ff 80%, #a5b4fc 100%);
  -webkit-background-clip:text;
  background-clip:text;
  -webkit-text-fill-color:transparent;
  color:transparent;
}
.opt-hint{font-size:var(--fs-sm);color:var(--t3);margin-bottom:14px;}
.opt-tbl{width:100%;border-collapse:collapse;font-size:var(--fs-sm);margin-bottom:8px;}
.opt-tbl th{padding:7px var(--space-2);background:var(--bg-hover);color:var(--t2);font-weight:var(--fw-semibold);text-align:left;border-bottom:1px solid var(--border-default);}
.opt-tbl td{padding:5px var(--space-2);border-bottom:1px solid rgba(46,52,72,.5);vertical-align:middle;}
/* OPT-PANEL-HOVER: 货物清单行 hover 蓝底 + 末尾 浮现，提示该行可点击编辑 */
.opt-tbl tbody tr{position:relative;transition:background var(--motion-fast);}
.opt-tbl tbody tr:hover{background:rgba(79,143,255,.10);box-shadow:inset 3px 0 0 var(--ac);}
.opt-tbl tbody tr:hover td:last-child::after{
  content:'编辑';position:absolute;right:10px;top:50%;transform:translateY(-50%);
  font-size:var(--fs-xs);color:var(--ac);background:rgba(15,22,42,.95);padding:2px var(--space-2);
  border:1px solid var(--ac);border-radius:4px;pointer-events:none;
}
.opt-tbl input[type=number]{width:64px;padding:3px 5px;background:var(--bg-input);border:1px solid var(--border-default);border-radius:4px;color:var(--t1);font-size:var(--fs-sm);outline:none;}
.opt-tbl input[type=number]:focus{border-color:var(--ac);}
.opt-tbl input[type=checkbox]{cursor:pointer;accent-color:var(--ac);width:14px;height:14px;}
.opt-cdot{width:10px;height:10px;border-radius:2px;display:inline-block;margin-right:4px;vertical-align:middle;}
.opt-spec-grid{display:flex;flex-direction:column;gap:5px;margin:var(--space-2) 0 var(--space-3);}
.opt-spec-btn{padding:6px 11px;border:1px solid var(--border-default);border-radius:6px;background:var(--bg-input);color:var(--t2);font-size:var(--fs-sm);cursor:pointer;transition:all var(--motion-fast);}
.opt-spec-btn.sel{border-color:var(--ac);background:rgba(79,143,255,.15);color:var(--t1);}
.opt-spec-row{display:flex;align-items:center;gap:5px;padding:5px 7px;border:1px solid var(--border-default);border-radius:6px;background:var(--bg-input);position:relative;transition:background var(--motion-fast),border-color var(--motion-fast);}
.opt-spec-row.sel{border-color:var(--ac);background:rgba(79,143,255,.10);}
/* OPT-PANEL-HOVER: 行 hover 加深 + 浮现（不与 .sel 冲突，叠加更亮） */
.opt-spec-row:hover{border-color:var(--ac);background:rgba(79,143,255,.16);}
.opt-spec-row:hover::after{
  content:'';position:absolute;right:34px;top:50%;transform:translateY(-50%);
  font-size:var(--fs-sm);color:var(--ac);pointer-events:none;
  background:rgba(15,22,42,.92);padding:1px 5px;border-radius:3px;
}
.opt-spec-pick{width:24px;height:24px;border:1px solid var(--border-default);border-radius:5px;background:var(--bg-hover);color:var(--t1);cursor:pointer;font-size:var(--fs-lg);line-height:22px;flex-shrink:0;}
.opt-spec-row.sel .opt-spec-pick{background:var(--ac);color:#fff;border-color:var(--ac);}
.opt-spec-name{flex:1;min-width:90px;background:var(--bg-hover);border:1px solid var(--border-default);border-radius:5px;padding:var(--space-1) 7px;color:var(--t1);font-size:var(--fs-sm);outline:none;}
.opt-spec-num{width:62px;background:var(--bg-hover);border:1px solid var(--border-default);border-radius:5px;padding:var(--space-1) 5px;color:var(--t1);font-size:var(--fs-sm);outline:none;text-align:center;}
.opt-spec-num:focus,.opt-spec-name:focus{border-color:var(--ac);}
.opt-spec-x{color:var(--t3);font-size:var(--fs-xs);}
.opt-spec-del{width:22px;height:22px;border:none;background:transparent;color:var(--er);font-size:var(--fs-icon-sm);cursor:pointer;border-radius:4px;}
.opt-spec-del:hover{background:rgba(239,68,68,.15);}
.opt-spec-actions{display:flex;gap:6px;margin-top:4px;}
.opt-spec-add{flex:1;padding:6px 10px;border:1px dashed var(--border-default);border-radius:6px;background:transparent;color:var(--ac);font-size:var(--fs-sm);cursor:pointer;}
.opt-spec-add:hover{border-color:var(--ac);background:rgba(79,143,255,.08);}

/* BUG-C (2026-05-06): 货物清单 row 标"不可装入" — 红底持续显示 + 红闪反馈 */
/* NB-2-T4 (2026-05-11): 闪红时长由 .5s × 3 (1.5s) 改为 .2s × 2 (400ms) — 用户 U5 directive 要求"实时预检 (闪红 200ms)"级别快反馈,避免妨碍连续操作 */
.opt-row-unfit{background:rgba(239,68,68,.10) !important;}
.opt-row-unfit td{border-top:1px solid rgba(239,68,68,.45) !important;border-bottom:1px solid rgba(239,68,68,.45) !important;}
.opt-row-unfit-flash{animation:optRowFlashRed .2s ease-in-out 2;}
@keyframes optRowFlashRed{
  0%,100%{background:rgba(239,68,68,.10);}
  50%{background:rgba(239,68,68,.45);}
}
/* NB-2-T5 (2026-05-11): 货箱卡片 unfit 视觉 — 当所选货物均装不进此 spec 时,行加红边 + 同样闪红节奏 */
.opt-spec-unfit{border-color:rgba(239,68,68,.55) !important;background:rgba(239,68,68,.10) !important;}
.opt-spec-unfit-flash{animation:optRowFlashRed .2s ease-in-out 2;}
.opt-unfit-badge{
  font-size:var(--fs-xs);color:#fff;background:var(--er,#ef4444);
  padding:1px 6px;border-radius:3px;margin-left:4px;
  font-weight:var(--fw-semibold);cursor:help;letter-spacing:.3px;
  box-shadow:0 0 0 1px rgba(255,255,255,.2) inset;
}

.seq-stats{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;padding:var(--space-2);margin:0 0 10px;background:linear-gradient(135deg,rgba(79,143,255,.08),rgba(79,143,255,.02));border:1px solid var(--border-default);border-radius:8px;}
.seq-stats .ss-cell{text-align:center;padding:var(--space-1) 2px;}
.seq-stats .ss-k{font-size:var(--fs-xs);color:var(--t3);margin-bottom:2px;}
.seq-stats .ss-v{font-size:var(--fs-icon-lg);font-weight:var(--fw-bold);color:var(--t1);line-height:1.1;}
.seq-stats .ss-v.ok{color:var(--ok);}
.seq-stats .ss-v.ac{color:var(--ac);}

.edit-mode-bar{display:flex;gap:5px;margin:0 0 10px;padding:5px;background:var(--bg-input);border:1px solid var(--border-default);border-radius:7px;}
.emb-btn{flex:1;padding:6px var(--space-2);border:1px solid transparent;border-radius:5px;background:transparent;color:var(--t2);font-size:var(--fs-sm);font-weight:var(--fw-semibold);cursor:pointer;transition:all var(--motion-fast);}
.emb-btn:hover{color:var(--t1);}
.emb-btn.on{background:var(--ac);color:#fff;border-color:var(--ac);box-shadow:0 0 0 2px rgba(79,143,255,.18);}


/* 测量模式 HUD */
/* DEVLOG-...-glass-system §1 T8: #measure-info 走 .gpanel-3 + 绿色 accent border (测量模式标志) */
#measure-info{position:absolute;top:14px;left:50%;transform:translateX(-50%);border-color:#00ff88 !important;color:#a8ffd2;padding:9px var(--space-4);font-size:var(--fs-md);line-height:1.7;pointer-events:none;z-index:var(--z-sticky);min-width:300px;max-width:min(420px, calc(100vw - var(--lp-width, 280px) - var(--rp-width, 300px) - 48px));text-align:center;box-shadow:var(--elev-2), 0 0 24px rgba(0,255,136,.14);}

/* ── 画布右下角浮层堆叠（小地图 + 切片滑块 + 测量面板）──────────────
 * 之前在 24-minimap.js / 15-measure.js 里写死 right:324px,不随右栏宽(--rp-width)
 * 或窗口大小变化 → 窄屏 / 拖宽右栏时与 #rp 重叠、互相压住(用户截图问题)。
 * 现统一在此用 calc 贴右栏外缘定位,垂直自底向上 column-reverse 堆叠,
 * 并加 max-width/max-height + overflow 防止溢出画布或压住左/右栏。
 * z-index 50:高于画布(--z-base:1),低于右栏(--z-sticky:100)与模态(--z-modal:1000)。
 * --rp-width 由 25-shortcuts-resize-share.js 拖动时实时 setProperty 维护。 */
#canvas-overlay-stack{
  position:absolute;
  /* 右缘贴 #rp 左缘外侧:#rp 占据 right:12 + rpWidth,再留 12px gap */
  right:calc(var(--rp-width, 300px) + 24px);
  bottom:14px;
  display:flex;
  flex-direction:column-reverse;   /* 先 append 的(测量面板)在底部,后 append 的(minimap)在上方 */
  gap:12px;
  align-items:flex-end;
  z-index:50;
  pointer-events:none;             /* 空白处穿透到画布;子元素显式 pointer-events:auto */
  /* 横向:绝不越过左栏右缘(留 24px 安全间距);纵向:不顶到 header */
  max-width:calc(100vw - var(--rp-width, 300px) - var(--lp-width, 280px) - 60px);
  max-height:calc(100vh - 120px);
}
#canvas-overlay-stack > *{ max-width:100%; }
/* 测量面板:列表区已自带 max-height:240 + overflow-y;此处约束整体宽不溢出 stack */
#canvas-overlay-stack #measure-panel{ width:240px; max-width:100%; }

/* 窄屏 / 移动端:右栏变全宽(<=800px,见 #rp media query),
 * 此时贴右栏定位无意义且小地图占地 → 隐藏小地图与其切换钮,只留测量面板贴底居中。 */
@media (max-width: 800px){
  #minimap-shell, #minimap-toggle{ display:none !important; }
  #canvas-overlay-stack{
    right:12px; left:12px;
    align-items:center;
    max-width:calc(100vw - 24px);
  }
}

/* ── AI 面板 ── */
/* DEVLOG-...-glass-system §1 T8: #ai-panel 视觉走 .gpanel-1. 保留位置 + 尺寸 + resize + overflow */
/* DEVLOG-2026-07-11-attr-hover-tooltip-flicker: z-index 从 --z-sticky(100) 提到 --z-aiwindow(700) ——
   用户原话"必须把AI悬浮窗图层放到最上面";之前与侧栏同档,顶栏"更多"下拉菜单(旧值 100003)一开就整个盖住它。 */
#ai-panel{position:fixed;top:80px;right:24px;width:380px;height:560px;display:none;flex-direction:column;z-index:var(--z-aiwindow,700);min-width:300px;min-height:320px;max-width:96vw;max-height:92vh;resize:none;overflow:hidden;}
/* 窗口式 8 向缩放抓手(JS 在 14-ai.js _installResizeHandles 绑定 pointer);隐形,只提供命中区 + 光标。 */
#ai-panel .ai-rz{position:absolute;z-index:6;}
#ai-panel .ai-rz-n{top:-3px;left:10px;right:10px;height:8px;cursor:ns-resize;}
#ai-panel .ai-rz-s{bottom:-3px;left:10px;right:10px;height:8px;cursor:ns-resize;}
#ai-panel .ai-rz-e{right:-3px;top:10px;bottom:10px;width:8px;cursor:ew-resize;}
#ai-panel .ai-rz-w{left:-3px;top:10px;bottom:10px;width:8px;cursor:ew-resize;}
#ai-panel .ai-rz-ne{top:-4px;right:-4px;width:16px;height:16px;cursor:nesw-resize;}
#ai-panel .ai-rz-nw{top:-4px;left:-4px;width:16px;height:16px;cursor:nwse-resize;}
#ai-panel .ai-rz-se{bottom:-4px;right:-4px;width:16px;height:16px;cursor:nwse-resize;}
#ai-panel .ai-rz-sw{bottom:-4px;left:-4px;width:16px;height:16px;cursor:nesw-resize;}
#ai-panel.ai-resizing{user-select:none;}
#ai-panel.ai-min .ai-rz{display:none;}
#ai-panel.open{display:flex;animation:panelFadeIn var(--motion-fast) ease-out;}
@keyframes panelFadeIn{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}
/* DEVLOG-...-glass-system §1 T11 iter2: AI 面板头部 — h2 渐变文字, 头部背景一致到玻璃 */
/* 单行头部:标题(可压缩省略)+ 操作按钮 + 窗口控制,全在一排,紧凑;gap 收紧、padding 减小不浪费。 */
#ai-head{padding:8px 10px;font-size:14px;font-weight:var(--fw-bold);color:var(--t1);background:transparent;border-bottom:1px solid var(--glass-2-stroke);display:flex;align-items:center;gap:5px;flex-wrap:nowrap;cursor:move;user-select:none;border-radius:16px 16px 0 0;touch-action:none;letter-spacing:0.3px;}
#ai-head h2{
  font-size:inherit;font-weight:inherit;letter-spacing:inherit;
  margin:0;flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  background:linear-gradient(135deg, #ffffff 0%, #cbd5ff 80%, #a5b4fc 100%);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
}
#ai-head.ai-head-dragging{cursor:grabbing;opacity:.92;background:rgba(255,255,255,0.02);}
#ai-head .ai-head-spacer{flex:1 1 auto;min-width:4px;}
/* 操作按钮:紧凑小药丸,一排放得下 演示/设置/导出/清空 */
#ai-head .ai-head-act{flex:0 0 auto;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.07);color:var(--t2);cursor:pointer;font-size:var(--fs-xs);letter-spacing:.02em;padding:4px 8px;border-radius:6px;white-space:nowrap;transition:background var(--motion-fast),color var(--motion-fast),border-color var(--motion-fast);}
#ai-head .ai-head-act:hover{background:rgba(255,255,255,0.1);color:var(--t1);border-color:rgba(150,180,230,.4);}
/* 窗口控制(收起/关闭)= 方形图标按钮 */
#ai-head .ai-head-icon{flex:0 0 auto;width:26px;height:26px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,0.05);border:none;color:var(--t2);cursor:pointer;font-size:17px;line-height:1;border-radius:6px;transition:background var(--motion-fast),color var(--motion-fast);}
#ai-head .ai-head-icon:hover{background:rgba(255,255,255,0.12);color:var(--t1);}
#ai-close-btn.ai-head-icon:hover{background:rgba(239,68,68,.22);color:#ff8a7a;}
/* P2: AI 状态行 — 模型 · 思考计时/演示进度/就绪 */
#ai-status{padding:5px 18px;font-size:var(--fs-xs);color:var(--t3);font-family:var(--ff-mono);border-bottom:1px solid var(--glass-2-stroke);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:24px;display:flex;align-items:center;letter-spacing:.3px;}
#ai-status:empty{display:none;}
#ai-cfg{padding:14px 18px;background:var(--bg-input);border-bottom:1px solid var(--border-default);}
.ai-cfg-row{display:flex;align-items:center;gap:10px;margin-bottom:10px;}
/* 2026-05-27 F4: label 改 min-width 避免截断中文 (API Key / 高级设置(端点/模型/协议) 等长 label) */
.ai-cfg-row label{font-size:var(--fs-sm);color:var(--t2);min-width:60px;flex-shrink:0;}
.ai-cfg-row input{flex:1;background:var(--bg-input);border:1px solid transparent;border-radius:6px;padding:6px 10px;font-size:var(--fs-sm);color:var(--t1);outline:none;transition:all var(--motion-fast);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}
.ai-cfg-row input:hover{background:var(--bg-hover);}
.ai-cfg-row input:focus{border-color:var(--ac);background:rgba(255,255,255,0.08);box-shadow:0 0 0 2px rgba(59,130,246,0.15);}
/* 2026-05-27 F4: 新增 select / checkbox / summary / status / 按钮行 / 配置点 — 之前靠 HTML inline style 兜底 */
.ai-cfg-row select,select.pi,select{appearance:none;-webkit-appearance:none;-moz-appearance:none;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="6" viewBox="0 0 10 6"><path d="M0 0l5 6 5-6z" fill="%2399a"/></svg>');background-repeat:no-repeat;background-position:right 12px center;padding-right:28px;}
.ai-cfg-row select{flex:1;background-color:var(--bg-input);border:1px solid transparent;border-radius:6px;padding:6px 28px 6px 10px;font-size:var(--fs-sm);color:var(--t1);outline:none;cursor:pointer;transition:all var(--motion-fast);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}
.ai-cfg-row select:hover{background-color:var(--bg-hover);}
.ai-cfg-row select:focus{border-color:var(--ac);background-color:rgba(255,255,255,0.08);box-shadow:0 0 0 2px rgba(59,130,246,0.15);}
.ai-cfg-row input[type="checkbox"]{flex:0 0 auto;width:16px;height:16px;margin:0;accent-color:var(--ac);cursor:pointer;}
#ai-cfg-advanced summary{font-size:var(--fs-xs);color:var(--t3);cursor:pointer;padding:6px 0;list-style:none;user-select:none;transition:color var(--motion-fast);}
#ai-cfg-advanced summary::-webkit-details-marker{display:none;}
#ai-cfg-advanced summary::before{content:'>';display:inline-block;width:14px;color:var(--t3);transition:transform var(--motion-fast);font-size:var(--fs-sm);font-family:var(--ff-mono);}
#ai-cfg-advanced[open] summary::before{transform:rotate(90deg);}
#ai-cfg-advanced summary:hover{color:var(--t1);}
#ai-cfg-status{font-size:var(--fs-sm);padding:8px 0;min-height:22px;color:var(--t2);}
#ai-cfg-status[data-state="ok"]{color:var(--ok);}
#ai-cfg-status[data-state="err"]{color:var(--er);}
#ai-cfg-status[data-state="wait"]{color:var(--ac);}
.ai-cfg-row-btns{display:flex;gap:8px;margin-top:6px;}
.ai-cfg-save{flex:1;padding:8px;background:var(--ac);border:none;border-radius:8px;color:#fff;font-size:var(--fs-sm);font-weight:var(--fw-semibold);cursor:pointer;transition:all var(--motion-fast);box-shadow:0 2px 8px rgba(59,130,246,0.3);}
.ai-cfg-save:hover{opacity:0.9;transform:translateY(-1px);box-shadow:0 4px 12px rgba(59,130,246,0.4);}
.ai-cfg-save:active{transform:translateY(0);box-shadow:0 2px 4px rgba(59,130,246,0.3);}
.ai-cfg-save:disabled{opacity:.55;cursor:wait;transform:none;}
.ai-cfg-save.ai-btn-secondary{background:linear-gradient(135deg,#3b82f6,#2563eb);}
.ai-btn-danger{background:linear-gradient(135deg,#ef4444,#b91c1c);color:#fff;box-shadow:0 2px 8px rgba(239,68,68,0.3);}
.ai-btn-danger:hover{opacity:.9;transform:translateY(-1px);box-shadow:0 4px 12px rgba(239,68,68,0.4);}
/* 配置就绪指示点 — 头部按钮旁的小绿/灰圆 */
.ai-cfg-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--t3);margin-left:8px;vertical-align:middle;flex:0 0 auto;transition:background var(--motion-fast);}
.ai-cfg-dot[data-ready="1"]{background:var(--ok);box-shadow:0 0 8px rgba(16,185,129,.6);}
/* persist-key 警告内联强调 — 取代之前用 --t2 静默颜色 */
.ai-cfg-persist-label{display:flex;align-items:center;gap:6px;font-size:var(--fs-xs);color:var(--t2);cursor:pointer;line-height:1.4;}
.ai-cfg-persist .ai-warn-inline{color:var(--wn,#f59e0b);font-weight:var(--fw-semibold);}
/* hint 文字 — 取代散落 inline style 的 4 处用法 */
.ai-cfg-hint{font-size:var(--fs-xs);color:var(--t3);line-height:1.6;padding:4px 0;}
#ai-msgs{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;background:transparent;}
#ai-msgs::-webkit-scrollbar{width:4px;}
#ai-msgs::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:2px;}
/* flex:0 0 auto —— #ai-msgs 是 flex 列,气泡默认 flex-shrink:1 会在容器填满时把【底部】新气泡压扁(用户反馈:
 *   "凡是消息生成在底部就变扁")。锁死不收缩 → 每个气泡保持自然高度,溢出由容器滚动,不再被挤扁。 */
.ai-msg{flex:0 0 auto;padding:12px 16px;border-radius:16px;font-size:var(--fs-sm);line-height:1.6;max-width:88%;white-space:pre-wrap;word-break:break-word;box-shadow:0 2px 4px rgba(0,0,0,0.1);letter-spacing:0.2px;}
.ai-msg.user{align-self:flex-end;background:linear-gradient(135deg,var(--ac),var(--ac2));color:#fff;border-bottom-right-radius:4px;}
.ai-msg.ai{align-self:flex-start;background:rgba(255,255,255,0.05);color:var(--t1);border:1px solid rgba(255,255,255,0.05);border-bottom-left-radius:4px;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);}
.ai-msg.sys{align-self:center;background:transparent;color:var(--t3);font-size:var(--fs-xs);font-style:italic;text-align:center;box-shadow:none;border:none;}
.ai-msg.act{align-self:flex-start;background:rgba(16,185,129,.1);color:var(--ok);border:1px solid rgba(16,185,129,.2);font-size:var(--fs-xs);border-bottom-left-radius:4px;}
.ai-msg.err{align-self:flex-start;background:rgba(239,68,68,.1);color:var(--er);border:1px solid rgba(239,68,68,.2);font-size:var(--fs-xs);border-bottom-left-radius:4px;}
.ai-msg-thinking{display:block;}
/* AI 思考框:固定高度的内滚区 —— 不再因空内容塌成"扁条"(min-height),也不再每个 token 把整栏往上刷
 *   (max-height + overflow 内滚 + JS 仅在用户停在底部时跟随)。用户上滚阅读时文字静止。 */
.ai-think-head{font-size:var(--fs-sm);color:var(--t1);font-weight:var(--fw-semibold,650);}
/* 像正常消息气泡一样【自然撑高】(不固定矮盒、不内滚),与第一条 AI 回复布局一致;
 *   字号同正常气泡(fs-sm)。跟随滚动交给外层 #ai-msgs(仅当用户停在底部),见 _updateThinkingStream。
 *   仅对超长推理设一个很高的上限内滚兜底,避免极端情况撑爆。 */
.ai-think-stream{margin-top:6px;max-height:60vh;overflow-y:auto;font-size:var(--fs-sm);color:var(--t1);line-height:1.6;white-space:pre-wrap;word-break:break-word;}
.ai-think-stream:empty::before{content:"正在组织思路…";color:var(--t3);font-style:italic;font-size:var(--fs-xs);}
.ai-think-stream::-webkit-scrollbar{width:4px;}
.ai-think-stream::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:2px;}
.ai-think-head{font-size:var(--fs-sm);color:var(--t2);display:flex;gap:8px;align-items:center;}
.ai-think-elapsed{font-family:var(--ff-mono);font-size:var(--fs-xs);color:var(--t3);}
/* 实时思考流:灰、限高、尾部滚动,流式增量 textContent 写入 */
/* 思考流可独立滚动(overflow-y:auto);max-height 限高,内容超出可滚 */
.ai-think-stream{margin-top:6px;font-size:var(--fs-xs);line-height:1.5;color:var(--t3);max-height:160px;overflow-y:auto;white-space:pre-wrap;word-break:break-word;opacity:.85;}
.ai-think-stream::-webkit-scrollbar{width:4px;}
.ai-think-stream::-webkit-scrollbar-thumb{background:var(--border-default);border-radius:2px;}
.ai-think-stream:empty{display:none;}
#ai-files{padding:0 16px;display:flex;flex-wrap:wrap;gap:6px;max-height:80px;overflow-y:auto;}
.ai-file-chip{display:flex;align-items:center;gap:6px;padding:4px 8px;background:rgba(255,255,255,0.05);border:1px solid var(--border-default);border-radius:6px;font-size:var(--fs-xs);color:var(--t2);transition:all var(--motion-fast);}
.ai-file-chip:hover{background:rgba(255,255,255,0.08);color:var(--t1);}
.ai-file-chip button{background:transparent;border:none;color:var(--t3);cursor:pointer;font-size:var(--fs-sm);padding:0;transition:color var(--motion-fast);}
.ai-file-chip button:hover{color:var(--er);}
#ai-input-area{padding:12px 16px 16px;border-top:1px solid var(--border-default);background:transparent;}
#ai-input{width:100%;min-height:60px;max-height:140px;background:rgba(255,255,255,0.03);border:1px solid transparent;border-radius:10px;color:var(--t1);font-size:var(--fs-sm);padding:10px 12px;outline:none;resize:vertical;font-family:inherit;transition:all var(--motion-fast);line-height:1.5;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}
#ai-input:hover{background:rgba(255,255,255,0.05);}
#ai-input:focus{border-color:var(--ac);background:rgba(255,255,255,0.06);box-shadow:0 0 0 3px rgba(59,130,246,0.15);}
.ai-input-row{display:flex;align-items:center;gap:8px;margin-top:10px;}
.ai-upload-btn{padding:7px 12px;background:rgba(255,255,255,0.04);border:1px solid transparent;border-radius:8px;color:var(--t2);cursor:pointer;font-size:var(--fs-icon-sm);transition:all var(--motion-fast);}
.ai-upload-btn:hover{background:rgba(255,255,255,0.08);color:var(--t1);}
#ai-send-btn{flex:1;padding:8px;background:var(--ac);border:none;border-radius:8px;color:#fff;font-size:var(--fs-md);font-weight:var(--fw-semibold);cursor:pointer;transition:all var(--motion-fast);box-shadow:0 2px 8px rgba(59,130,246,0.3);}
#ai-send-btn:hover{opacity:0.9;transform:translateY(-1px);box-shadow:0 4px 12px rgba(59,130,246,0.4);}
#ai-send-btn:active{transform:translateY(0);box-shadow:0 2px 4px rgba(59,130,246,0.3);}
#ai-send-btn:disabled{background:rgba(255,255,255,0.05);color:var(--t3);cursor:not-allowed;box-shadow:none;transform:none;}
.opt-result{padding:13px;background:var(--bg-input);border-radius:8px;margin-top:10px;font-size:var(--fs-sm);}
.opt-res-row{display:flex;justify-content:space-between;padding:var(--space-1) 0;border-bottom:1px solid rgba(46,52,72,.4);line-height:1.5;}
.opt-res-row:last-child{border:none;}
.opt-res-row .rv{font-weight:var(--fw-semibold);color:var(--ac);}
/* 集装箱顾问 — 推荐 + 三策略对照表 */
.opt-adv-wrap{margin-top:6px;font-size:var(--fs-sm);}
.opt-adv-rec{padding:8px 10px;border-radius:8px;background:rgba(79,143,255,.12);border:1px solid rgba(79,143,255,.3);color:var(--t1);line-height:1.55;margin-bottom:8px;}
.opt-adv-rec-tag{display:inline-block;font-size:var(--fs-xs,10px);font-weight:var(--fw-semibold);color:#fff;background:var(--ac);border-radius:4px;padding:1px 6px;margin-right:6px;}
.opt-adv-rec-mini{display:inline-block;font-size:10px;color:var(--ac);background:rgba(79,143,255,.16);border-radius:3px;padding:0 4px;margin-left:4px;}
.opt-adv-tbl{width:100%;border-collapse:collapse;font-size:var(--fs-sm);margin-bottom:8px;}
.opt-adv-tbl th{padding:5px var(--space-2);background:var(--bg-hover);color:var(--t2);font-weight:var(--fw-semibold);text-align:left;border-bottom:1px solid var(--border-default);}
.opt-adv-tbl td{padding:5px var(--space-2);border-bottom:1px solid rgba(46,52,72,.5);color:var(--t1);}
.opt-adv-tbl tr.opt-adv-row-rec td{background:rgba(79,143,255,.08);font-weight:var(--fw-semibold);}
.opt-adv-apply-row{display:flex;gap:var(--space-2);flex-wrap:wrap;margin-top:4px;}
.opt-adv-apply-row .gbtn{flex:1;min-width:120px;padding:7px 10px;font-size:var(--fs-sm);}
.opt-pal-sec{margin-top:10px;padding:8px 10px;border-radius:8px;background:rgba(138,109,59,.08);border:1px solid rgba(138,109,59,.28);}
.opt-pal-title{font-size:var(--fs-sm);color:var(--t1);margin-bottom:6px;}
.opt-pal-row{display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:flex-end;}
.opt-pal-lbl{display:flex;flex-direction:column;font-size:var(--fs-xs,10px);color:var(--t2);gap:3px;}
.opt-pal-lbl select,.opt-pal-lbl input{padding:5px 7px;border-radius:6px;border:1px solid var(--border-default);background:var(--bg-input);color:var(--t1);font-size:var(--fs-sm);}
.opt-pal-lbl input{width:120px;}
.opt-pal-run{align-self:flex-end;padding:7px 12px;font-size:var(--fs-sm);}
.opt-pal-result{margin-top:6px;font-size:var(--fs-sm);}
.opt-pal-result-line{padding:8px 10px;border-radius:8px;background:rgba(34,197,94,.12);border:1px solid rgba(34,197,94,.3);color:var(--t1);line-height:1.55;margin-bottom:8px;}
.opt-pal-result .gbtn{width:100%;padding:8px 10px;font-size:var(--fs-sm);}
.opt-mbtns{display:flex;gap:var(--space-2);margin-top:16px;}
.opt-mbtns button{flex:1;padding:10px;border-radius:8px;font-size:var(--fs-lg);font-weight:var(--fw-semibold);cursor:pointer;border:none;}
.opt-run{background:linear-gradient(135deg,var(--ac),var(--ac2));color:#fff;}
.opt-run:hover{opacity:.9;}
.opt-apply{background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;}
.opt-apply:hover{opacity:.9;}
.opt-close{background:var(--bg-input);color:var(--t2);}
.opt-close:hover{background:var(--bg-hover);}
.opt-progress{height:5px;background:rgba(255,255,255,.07);border-radius:3px;overflow:hidden;margin:var(--space-2) 0;}
.opt-pfill{height:100%;background:linear-gradient(90deg,var(--ac),var(--ac2));border-radius:3px;transition:width var(--motion-base);}
#box-sel-rect{position:absolute;pointer-events:none;display:none;border:2px solid rgba(34,197,94,0.85);background:rgba(34,197,94,0.06);border-radius:2px;z-index:var(--z-sticky);}
/* iter9 fix: 删除旧 #box-sel-del{position:absolute;display:none;...} ID 选择器,
 * 它优先级高于 .gbtn--danger class, 强制把容器内 del 按钮 display:none.
 * 现在 del 走容器 flex 布局, 不需要独立位置/display. */
/* iter8 user-feedback fix: 框选菜单统一容器 #box-sel-actions — 不分散按钮, 位置始终在 canvas 底部中央 */
#box-sel-actions{
  position:absolute;
  left:50%;
  bottom:88px;
  transform:translateX(-50%);
  display:flex;
  gap:8px;
  z-index:var(--z-dropdown);
  padding:10px 12px;
  background:var(--glass-1-bg);
  border:1px solid var(--glass-1-border);
  border-radius:var(--r-lg);
  backdrop-filter:blur(var(--glass-1-blur)) saturate(115%);
  -webkit-backdrop-filter:blur(var(--glass-1-blur)) saturate(115%);
  box-shadow:var(--elev-3);
  animation:gpanel-enter var(--motion-modal) backwards;
}
#box-sel-actions[style*="display:none"], #box-sel-actions[style*="display: none"]{display:none !important;}
#box-sel-actions .gbtn{
  flex-shrink:0;
}
/* iter9 fix: 旧的 :not(#box-sel-actions *) 写法非法, 误隐了容器内"删除N个"按钮. 删. */

/* ── 冗余尺寸面板 ── */
.pad-faces{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:8px;}
.pad-face-lbl{
  display:inline-flex;align-items:center;gap:3px;
  padding:var(--space-1) 9px;border:1px solid var(--border-default);border-radius:5px;
  background:var(--bg-input);color:var(--t2);font-size:var(--fs-xs);font-weight:var(--fw-medium);
  cursor:pointer;user-select:none;transition:all var(--motion-fast);
}
.pad-face-lbl:hover{border-color:var(--ac);color:var(--t1);}
.pad-face-lbl.sel{border-color:var(--ac);background:rgba(79,143,255,.18);color:var(--t1);}
.pad-bulk{display:flex;gap:5px;align-items:center;margin-bottom:10px;flex-wrap:wrap;}
.pad-bulk-val{width:58px;padding:5px var(--space-2);background:var(--bg-input);border:1px solid var(--border-default);border-radius:5px;color:var(--t1);font-size:var(--fs-sm);outline:none;}
.pad-bulk-val:focus{border-color:var(--ac);}
.pad-unit{font-size:var(--fs-xs);color:var(--t2);}
.pad-small-btn{padding:var(--space-1) 10px;border:1px solid var(--border-default);border-radius:5px;background:var(--bg-input);color:var(--t2);font-size:var(--fs-xs);font-weight:var(--fw-medium);cursor:pointer;white-space:nowrap;transition:all var(--motion-fast);}
.pad-small-btn:hover{border-color:var(--ac);color:var(--t1);}

/* ── 装货动画面板 ── */
/* DEVLOG-...-glass-system §1 T8: #anim-panel 视觉走 .gpanel-1. 保留位置 + padding + display */
#anim-panel{position:fixed;right:calc(var(--rp-width,300px) + 24px);bottom:24px;z-index:var(--z-sticky);width:320px;padding:16px;display:none;font-size:var(--fs-md);color:var(--t1);}
#anim-head{font-weight:var(--fw-bold);font-size:var(--fs-h1);margin-bottom:12px;background:linear-gradient(135deg,var(--t1),var(--t2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-0.5px;}
#anim-progress{height:12px;background:var(--bg-input);border-radius:6px;overflow:hidden;border:1px solid transparent;margin-bottom:8px;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}
#anim-bar{height:100%;background:linear-gradient(90deg,var(--ac),var(--ac2));transition:width var(--motion-fast) ease-out;width:0%;border-radius:6px;}
#anim-info{font-size:var(--fs-sm);color:var(--t2);margin-bottom:12px;display:flex;align-items:baseline;gap:4px;}
#anim-info #anim-cur,#anim-info #anim-total{color:var(--t1);font-weight:var(--fw-bold);}
#anim-info #anim-name{color:var(--ac);font-weight:var(--fw-semibold);margin-left:auto;}
#anim-speed-row{display:flex;align-items:center;gap:var(--space-2);margin-bottom:12px;padding:8px 12px;background:rgba(255,255,255,0.02);border-radius:8px;}
#anim-speed-row label{font-size:var(--fs-sm);color:var(--t2);min-width:60px;}
#anim-speed-row #anim-speed-val{color:var(--ac);font-weight:var(--fw-semibold);margin-left:8px;}
#anim-speed{flex:1;accent-color:var(--ac);cursor:pointer;}
#anim-ctrls{display:flex;gap:8px;margin-bottom:12px;}
#anim-ctrls button{flex:1;padding:8px 10px;border:1px solid transparent;border-radius:8px;background:rgba(255,255,255,0.05);color:var(--t1);font-size:var(--fs-sm);font-weight:var(--fw-medium);cursor:pointer;transition:all var(--motion-fast);box-shadow:0 2px 4px rgba(0,0,0,0.1);}
#anim-ctrls button:hover{background:rgba(255,255,255,0.08);transform:translateY(-1px);}
#anim-ctrls button#anim-stop:hover{background:rgba(239,68,68,0.15);color:var(--er);}
#anim-tip{font-size:var(--fs-xs);color:var(--t3);line-height:1.6;padding:8px 12px;background:rgba(255,255,255,0.02);border-radius:8px;}

/* ── 码头模式 (DOCK MODE) 控制面板 + 字幕 — 复用 .gpanel-1 玻璃, 零额外 GPU(纯 DOM) ── */
#dock-panel{position:fixed;right:calc(var(--rp-width,300px) + 24px);bottom:24px;z-index:var(--z-sticky);width:340px;padding:16px;display:none;font-size:var(--fs-md);color:var(--t1);}
#dock-head{font-weight:var(--fw-bold);font-size:var(--fs-h1);margin-bottom:12px;background:linear-gradient(135deg,var(--t1),var(--t2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-0.5px;}
#dock-progress{height:12px;background:var(--bg-input);border-radius:6px;overflow:hidden;border:1px solid transparent;margin-bottom:8px;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}
#dock-bar{height:100%;background:linear-gradient(90deg,var(--ac),var(--ac2));transition:width var(--motion-fast) ease-out;width:0%;border-radius:6px;}
#dock-info{font-size:var(--fs-sm);color:var(--t2);margin-bottom:12px;}
#dock-info #dock-cur,#dock-info #dock-total{color:var(--t1);font-weight:var(--fw-bold);}
#dock-ctrls{display:flex;gap:6px;margin-bottom:12px;}
#dock-ctrls button{flex:1;padding:8px 6px;border:1px solid transparent;border-radius:8px;background:rgba(255,255,255,0.05);color:var(--t1);font-size:var(--fs-sm);font-weight:var(--fw-medium);cursor:pointer;transition:all var(--motion-fast);box-shadow:0 2px 4px rgba(0,0,0,0.1);}
#dock-ctrls button:hover{background:rgba(255,255,255,0.08);transform:translateY(-1px);}
#dock-ctrls button#dock-playpause{background:rgba(56,189,248,0.14);color:var(--ac);}
#dock-ctrls button#dock-exit:hover{background:rgba(239,68,68,0.15);color:var(--er);}
#dock-speed-row{display:flex;align-items:center;gap:var(--space-2);padding:8px 12px;background:rgba(255,255,255,0.02);border-radius:8px;}
#dock-speed-row label{font-size:var(--fs-sm);color:var(--t2);min-width:60px;}
#dock-speed-row #dock-speed-val{color:var(--ac);font-weight:var(--fw-semibold);margin-left:8px;}
#dock-speed{flex:1;accent-color:var(--ac);cursor:pointer;}
/* 字幕浮层: 屏幕上方居中, 点击穿透(不挡画布), 进场淡入 */
#dock-caption{position:fixed;top:84px;left:50%;transform:translateX(-50%) translateY(-8px);z-index:var(--z-sticky);min-width:240px;max-width:60vw;padding:12px 22px;text-align:center;border-radius:12px;background:rgba(18,24,38,0.82);border:1px solid rgba(56,189,248,0.28);box-shadow:0 8px 28px rgba(0,0,0,0.35);color:var(--t1);pointer-events:none;opacity:0;transition:opacity var(--motion-fast) ease-out,transform var(--motion-fast) ease-out;}
#dock-caption.show{opacity:1;transform:translateX(-50%) translateY(0);}
#dock-caption #dock-cap-idx{font-size:var(--fs-xs);color:var(--ac);font-weight:var(--fw-semibold);letter-spacing:0.5px;}
#dock-caption #dock-cap-name{font-size:var(--fs-h1);font-weight:var(--fw-bold);margin:2px 0;}
#dock-caption #dock-cap-stop{font-size:var(--fs-sm);color:var(--t2);}
@media (prefers-reduced-motion: reduce){
  #dock-bar{transition:none;}
  #dock-caption{transition:opacity 0.12s linear;transform:translateX(-50%);}
  #dock-caption.show{transform:translateX(-50%);}
}

/* ── MASTER P1-5 FIX: 颜色之外的状态指示符（让 colorblind / 高对比度模式也能区分 on/off / ok/er） ── */
/* R20-T16b: 删除原 .btn.on / .ss.ok 的 U+221A 前缀(R9 白名单收回);.on/.ok 已有颜色与背景态足够表达 */
/* DEVLOG-...-glass-system §1 T6: 旧 .scene-tab.active{border-left:3px ...} 删 — underline 接管视觉指示, border-left 与 underline 双重显示反而割裂 */
.ss.er::before{content:'× ';font-weight:var(--fw-bold);}

/* ── MASTER P1-6 FIX: 响应式 / 窄视口媒体查询（之前完全没有 @media） ── */
@media (max-width: 1100px){
  #lp{ width:200px; min-width:200px; }
  #rp{ width:220px; min-width:220px; }
  /* MASTER P2-FINAL FIX: #mo.with-preview 固定 1080px 在 <1100px 视口溢出/被截 —
     折叠成 column + viewport-relative 宽度 */
  #mo.with-preview{ width: calc(100vw - 60px); flex-direction: column; }
  #mo.with-preview .mo-form{ flex: 0 0 auto; max-height: 50vh; }
  #mo.with-preview .mo-preview{ flex: 1 1 auto; height: auto; min-height: 360px; }
}
@media (max-width: 800px){
  #lp{ display:none; }
  #rp{ width:100%; min-width:0; }
  #main{ flex-direction:column; }
  /* DEVLOG-2026-07-11-attr-hover-tooltip-flicker: wrap → nowrap+overflow-x:auto,与 #hdr 基础规则
     (line ~165)和 720px 断点保持一致 —— 顶栏任何宽度都不再整排换行,过窄时横向滚动而不是跳行。 */
  #hdr{ flex-wrap:nowrap; overflow-x:auto; height:auto; min-height:50px; padding:6px 10px; }
  .dv{ display:none; }
}
/* Touch-target minimum 44×44 (iOS HIG) for primary interactive */
@media (hover: none) and (pointer: coarse){
  .btn, .scene-tab, .scene-tab-x, .scene-tab-add, .opt-spec-pick, .opt-spec-del{ min-height:44px; min-width:44px; }
}

/* P2 #8: 左/右栏可调宽度 — 4-6px 拖把手 */
/* 左右栏是 position:absolute 浮动玻璃面板 → splitter 若作为 flex item 会塌到 #main 最左挤成一团
 * (用户报"竖线跑到左栏左边,两条挤一起")。改 absolute,用拖动时已维护的 --lp-width/--rp-width
 * 把分隔条精确贴到左/右栏外缘,拖动时跟随。 */
.splitter{position:absolute;top:12px;bottom:12px;width:8px;cursor:col-resize;background:transparent;border:0;z-index:var(--z-raised);user-select:none;transition:background var(--motion-fast);}
#splitter-left{left:calc(12px + var(--lp-width, 280px));}
#splitter-right{right:calc(12px + var(--rp-width, 300px));}
.splitter:hover{background:rgba(79,143,255,.18);}
.splitter.dragging{background:var(--ac);}
/* 2026-05-29: 把手默认就清晰可见(原 1px + --border-default 暗色在深底上几乎看不见,用户报"竖条没有了")。
 *   静止 = 3px 浅蓝圆柱 + 顶/底渐隐 + 微辉光;hover/拖动 = 加高加亮发光。 */
.splitter::after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:3px;height:48px;border-radius:3px;background:linear-gradient(180deg,rgba(150,180,240,0),rgba(150,180,240,.6) 22%,rgba(150,180,240,.6) 78%,rgba(150,180,240,0));box-shadow:0 0 6px rgba(110,168,255,.25);transition:background var(--motion-fast),height var(--motion-fast),box-shadow var(--motion-fast);}
.splitter:hover::after,.splitter.dragging::after{background:var(--ac);height:72px;box-shadow:0 0 12px rgba(79,143,255,.7);}
@media (max-width: 800px){
  .splitter{ display:none; }   /* mobile: 单列布局，splitter 隐藏 */
}

/* DEVLOG-...-glass-system iter5: 键盘快捷键浮窗内卡片 — 玻璃化, 与 modal 风格一致 */
#kbd-help-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px 18px;font-size:var(--fs-md);line-height:1.7;}
@media (max-width: 540px){ #kbd-help-grid{grid-template-columns:1fr;} }
#kbd-help-grid .kh-cat{
  background:rgba(255,255,255,0.025);
  border:1px solid var(--glass-2-stroke);
  border-radius:var(--r-md);
  padding:12px 14px;
  transition:border-color var(--motion-fade), background var(--motion-fade);
  /* 每条快捷键 = 一行(.kh-row 自身 flex);类别内纵向堆叠。不再用 grid+display:contents —— 那要求每行
     单元数严格一致,纯展示行(2 单元)与可改键行(3 单元)混排时会把单元串到一起错位成一团(用户反馈混乱)。 */
  display:flex;
  flex-direction:column;
  gap:6px;
}
#kbd-help-grid .kh-cat:hover{
  border-color:rgba(79,143,255,0.25);
  background:rgba(79,143,255,0.04);
}
#kbd-help-grid .kh-cat-title{
  font-size:11px;
  font-weight:var(--fw-bold);
  background:linear-gradient(135deg, #4f8fff, #8b5cf6);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  text-transform:uppercase;letter-spacing:1.2px;
  margin-bottom:2px;padding-bottom:6px;
  border-bottom:1px solid var(--glass-2-stroke);
}
/* 每条快捷键独占一行:desc 左(可伸缩),键 + 改/重置 靠右。整齐、不依赖跨行单元对齐。 */
#kbd-help-grid .kh-row{display:flex;align-items:center;gap:10px;flex-wrap:nowrap;min-height:26px;}
#kbd-help-grid .kh-desc{flex:1 1 auto;color:rgba(255,255,255,0.72);min-width:0;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
#kbd-help-grid .kh-keys{flex:0 1 auto;display:flex;gap:var(--space-1);align-items:center;justify-content:flex-end;flex-wrap:wrap;}
#kbd-help-grid .kh-actions{flex:0 0 auto;display:flex;gap:6px;align-items:center;}
#kbd-help-grid kbd{
  display:inline-block;
  padding:3px 8px;
  background:rgba(255,255,255,0.08);
  border:1px solid rgba(255,255,255,0.12);
  border-bottom:2px solid rgba(255,255,255,0.06);
  border-radius:var(--r-sm);
  font-size:10.5px;
  font-family:var(--ff-mono);
  color:rgba(255,255,255,0.92);
  white-space:nowrap;
  box-shadow:0 1px 2px rgba(0,0,0,0.25);
}
/* 改/重置 等小按钮 (kbd 旁边的) 也玻璃化 */
#kbd-help-grid button{
  padding:3px 9px;
  border:1px solid var(--glass-2-stroke);
  border-radius:var(--r-sm);
  background:rgba(255,255,255,0.04);
  color:rgba(255,255,255,0.82);
  font-size:10.5px;
  cursor:pointer;
  transition:all var(--motion-fade);
}
#kbd-help-grid button:hover{
  background:rgba(79,143,255,0.15);
  border-color:rgba(79,143,255,0.40);
  color:#fff;
}

/* R9-19: 模板浮层 + 编辑器 */
/* DEVLOG-...-glass-system §1 T7: tpl-popup 视觉走 .gpanel-1. 保留位置 + 尺寸 + animation */
.tpl-popup{position:fixed;z-index:var(--z-toast);width:380px;max-height:480px;display:flex;flex-direction:column;font-size:var(--fs-md);animation:tplFadeIn 140ms ease-out;}
@keyframes tplFadeIn{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:translateY(0);}}
.tpl-popup .tpl-head{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) 10px;border-bottom:1px solid var(--border-default);}
.tpl-popup .tpl-title{flex:1;font-weight:var(--fw-semibold);color:var(--t1);}
.tpl-popup .tpl-new{background:var(--ac);color:#fff;border:0;border-radius:4px;padding:3px 9px;font-size:var(--fs-sm);cursor:pointer;transition:filter 120ms;}
.tpl-popup .tpl-new:hover{filter:brightness(1.15);}
.tpl-popup .tpl-close{background:transparent;border:0;color:var(--t2);font-size:var(--fs-icon-lg);line-height:1;cursor:pointer;padding:2px 6px;}
.tpl-popup .tpl-close:hover{color:var(--t1);}
.tpl-popup .tpl-search{margin:var(--space-2) 10px;padding:5px var(--space-2);background:var(--bg-input);border:1px solid var(--border-default);border-radius:4px;color:var(--t1);outline:none;}
.tpl-popup .tpl-search:focus{border-color:var(--ac);}
.tpl-popup .tpl-tabs{display:flex;flex-wrap:wrap;gap:var(--space-1);padding:0 10px 6px;border-bottom:1px solid var(--border-default);}
.tpl-popup .tpl-tab{background:var(--bg-input);border:1px solid var(--border-default);color:var(--t2);padding:3px 9px;font-size:var(--fs-sm);border-radius:12px;cursor:pointer;transition:all 120ms;}
.tpl-popup .tpl-tab:hover{color:var(--t1);}
.tpl-popup .tpl-tab.active{background:var(--ac);color:#fff;border-color:var(--ac);}
.tpl-popup .tpl-grid{flex:1;overflow-y:auto;padding:var(--space-2) 10px;display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2);}
.tpl-popup .tpl-empty{grid-column:1/-1;text-align:center;color:var(--t3);padding:20px 0;font-size:var(--fs-sm);}
/* 2026-05-27 v1.3.10: 卡片所有 height 限制清掉 + min-height:auto 确保完整内容
   显式 grid-auto-rows + .tpl-card height:auto 防 grid stretch 卡死高度 */
.tpl-popup .tpl-grid{grid-auto-rows:max-content;align-items:start;}
.tpl-popup .tpl-card{position:relative;display:flex;flex-direction:column;background:var(--bg-input);border:1px solid var(--border-default);border-radius:6px;transition:border-color 120ms,transform 80ms;min-height:auto !important;height:auto !important;max-height:none !important;gap:5px;padding:9px 11px 9px;overflow:visible !important;}
.tpl-popup .tpl-card:hover{border-color:var(--ac);transform:translateY(-1px);}
.tpl-popup .tpl-card.tpl-custom{border-color:rgba(120,200,255,.4);}
.tpl-popup .tpl-card-main{background:transparent;border:0;color:var(--t1);text-align:left;cursor:pointer;outline:none;}
.tpl-popup .tpl-card-main:focus-visible{outline:2px solid var(--ac);outline-offset:1px;border-radius:3px;}
.tpl-popup .tpl-name{font-weight:var(--fw-semibold);font-size:var(--fs-md);color:var(--t1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.tpl-popup .tpl-dims{font-size:10.5px;color:var(--t2);margin-top:2px;}
.tpl-popup .tpl-meta{font-size:var(--fs-xs);color:var(--t3);margin-top:1px;}
.tpl-popup .tpl-actions{display:flex;gap:4px;align-items:center;justify-content:flex-end;margin-top:2px;}
.tpl-popup .tpl-actions-builtin{justify-content:flex-end;color:var(--t3);font-size:9.5px;}
.tpl-popup .tpl-act-btn{background:var(--bg-hover);border:1px solid var(--border-default);color:var(--t2);font-size:var(--fs-xs);padding:2px 7px;border-radius:3px;cursor:pointer;transition:all 120ms;}
.tpl-popup .tpl-act-btn:hover{color:var(--t1);border-color:var(--ac);}
.tpl-popup .tpl-act-del:hover{color:#fff;background:var(--er);border-color:var(--er);}

/* 2026-05-27 #32: 模板卡片全数据 chips (与左栏 cargo-list 视觉对齐) */
.tpl-popup .tpl-chips{display:flex;flex-wrap:wrap;gap:3px;margin-top:4px;}
.tpl-popup .tpl-chip{display:inline-block;font-size:9.5px;line-height:1.3;padding:1px 5px;border-radius:3px;border:1px solid var(--border-subtle);color:var(--t2);background:var(--bg-hover);font-weight:var(--fw-medium);}
.tpl-popup .tpl-c-dims{color:#facc15;border-color:rgba(250,204,21,.45);background:rgba(250,204,21,.10);}
.tpl-popup .tpl-c-w{color:#fb923c;border-color:rgba(251,146,60,.45);background:rgba(251,146,60,.10);}
.tpl-popup .tpl-c-ml{color:#a78bfa;border-color:rgba(167,139,250,.45);background:rgba(167,139,250,.10);}
.tpl-popup .tpl-c-fra{color:var(--er);border-color:rgba(239,68,68,.45);background:rgba(239,68,68,.10);}
.tpl-popup .tpl-c-pre{color:var(--ac2);border-color:rgba(124,92,252,.45);background:rgba(124,92,252,.10);}
.tpl-popup .tpl-c-mp{color:#ec4899;border-color:rgba(236,72,153,.45);background:rgba(236,72,153,.10);}
.tpl-popup .tpl-c-up{color:var(--ac);border-color:rgba(79,143,255,.45);background:rgba(79,143,255,.10);}
.tpl-popup .tpl-c-pad{color:#ffae66;border-color:rgba(255,174,102,.45);background:rgba(255,174,102,.10);}
/* 2026-05-27 #32: 通用右键浮层 (卡片/标签共用) */
.tpl-ctx-menu{position:fixed;z-index:var(--z-max,99999);background:var(--bg-elevated);border:1px solid var(--border-default);border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,.55);padding:4px 0;min-width:170px;font-size:var(--fs-md);}
.tpl-ctx-item{display:block;width:100%;background:transparent;border:0;color:var(--t1);text-align:left;padding:6px 12px;font-size:var(--fs-md);cursor:pointer;}
.tpl-ctx-item:hover{background:var(--bg-hover);color:var(--ac);}
.tpl-ctx-item:disabled{color:var(--t3);cursor:not-allowed;background:transparent;}
.tpl-ctx-danger{color:var(--er);}
.tpl-ctx-danger:hover{background:rgba(239,68,68,.10);color:var(--er);}
.tpl-ctx-sep{height:1px;background:var(--border-default);margin:4px 0;}

/* DEVLOG-2026-05-27-glass-system-token-builders §1 T10:
 * R9-19 .tpl-edit / .tpl-edit-mask / .tpl-edit-* CSS (~20 行) 已删 — 整组类在 src/js/*.js 内 0 引用,
 * grep 验过: dead code. 模板编辑现走主 #mo modal (openModal 调用), 不需要独立编辑器外壳. */

/* ============================================================
 * R9-11: 全局丝滑过渡 — 杜绝瞬切
 * ============================================================
 * 原则:
 *   1. 颜色 / 边框 / 透明度 / 变换 / 阴影 / 滤镜变化都给默认 .15-.2s ease;
 *   2. 不全局对 width/height/top/left 加 transition — 那是布局抖动陷阱;
 *   3. 用户开 prefers-reduced-motion 时整体停掉(包括我们已写的关键帧);
 *   4. 仅对没有显式 transition 的元素生效 — 已有规则不会被覆盖。
 * ============================================================ */
button, .btn, .gbtn, input, select, textarea,
.oi, .scene-tab, .ph button,
.tpl-card, .tpl-tab, .tpl-act-btn, .tpl-new,
.modal-overlay, .ctx-menu, .toast {
  transition: background-color var(--motion-fast) ease, border-color var(--motion-fast) ease,
              color var(--motion-fast) ease, box-shadow var(--motion-fast) ease,
              opacity var(--motion-fast) ease, filter var(--motion-fast) ease,
              transform var(--motion-fast) ease;
}
/* T20: hover 微抖反馈已统一至下方单一规则,旧的 translateY+scale 复合规则已删除 */
/* 模态弹出统一过渡 — 显式 fade,不要 display:none 直跳 */
.modal-overlay { transition: opacity var(--motion-fast) ease, visibility 0s var(--motion-fast); }
.modal-overlay.show { transition: opacity var(--motion-fast) ease; }

/* toast 飘入 — 已在 02-state 用 anim,这里只兜底位置过渡 */
.toast { transition: transform var(--motion-base) cubic-bezier(.4,1.4,.6,1), opacity var(--motion-base) ease; }

/* DEVLOG-2026-05-27-glass-system-token-builders §1 T10:
 * R10-6 联系管理员对话面板 CSS (~33 行) 已删 — 对应 33-admin-chat.js 于 2026-05-27 v1.2.0 purge 删除,
 * CSS dead code 跟进清理. 用户决议 "不商业化、不要账号" 闭环. */

/* R10-6b: 场景标签拖动 — 限定行内 */
/* FIX(2026-05-30 幽灵标签): 拖动时源 tab 完全隐形(只保留占位宽度,避免其它 tab 跳位),
   clone 是唯一可见的拖拽体。visibility:hidden 连边框/霓虹/focus 光圈一并隐藏,彻底无"幽灵"。
   用 !important 压过 skin 的 active 高亮。 */
.scene-tab.scene-tab-source {
  visibility: hidden !important;
}
/* R22-T7 Edge 风格让位:hover 到 drop-before 的 tab 整体右移 6px 让出插入位;drop-after 左移 6px.
   transition 加 transform 让让位平滑 — 配合 box-shadow 边线提示,接近 Edge 浏览器 tab drag 视觉 */
.scene-tab { transition: background var(--motion-fast), transform var(--motion-base) cubic-bezier(.4,0,.2,1); }
.scene-tab.scene-tab-drop-before { transform: translateX(6px); }
.scene-tab.scene-tab-drop-after  { transform: translateX(-6px); }
/* clone 跟手拖拽体:挂在 <body> 上,拿不到 skin 的 #scene-tabs 作用域样式,
   故在此(无作用域)给它完整的"被拎起的标签"质感,确保它看起来才是真正在动的那个。 */
.scene-tab.scene-tab-dragging {
  pointer-events: none;
  background: linear-gradient(180deg, rgba(40,52,82,.98), rgba(24,31,50,.98));
  border: 1px solid var(--ac, #5b8cff);
  border-radius: 8px;
  color: #eaf0ff;
  box-shadow: 0 12px 30px rgba(0,0,0,.6), 0 0 14px rgba(91,140,255,.5);
  opacity: .96 !important;
  /* FIX(幽灵标签根因): 最新建的 tab 仍在播 list-item-stagger 入场动画,克隆后该动画在 clone 上重放,
     其 transform 把 clone 推离 left/top → 偏移成"幽灵"。实测 inline style.animation='none' 竟无法
     压过类规则(shorthand + nth-child delay 的诡异层叠),必须用 !important 规则强制关停。
     clone 用 position:fixed + left/top 定位,不需要 transform,故一并 transform:none。 */
  animation: none !important;
  transform: none !important;
}
.scene-tab.scene-tab-drop-before { box-shadow: -2px 0 0 0 var(--ac); }
.scene-tab.scene-tab-drop-after  { box-shadow:  2px 0 0 0 var(--ac); }

/* 减动效偏好:停掉所有过渡和动画(无障碍合规) */
/* ============================================================
 * R10-10 / R20-H1: 附加属性面板
 * ============================================================
 * 默认两行布局 — 上一行两个 checkbox(易碎 / 受压),下一行 maxPressure 数字输入整行。
 * R20-H1 删除冗余的 .attr-state "是/否"徽,checkbox 自身已表达状态。
 * 右栏标准宽 220-240px,3 列布局会挤压数字输入,故升 2 行为默认。
 * 不允许 label 折行,checkbox 与 number 行高一致。
 * ============================================================ */
.attr-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;}
.attr-cell{display:flex;align-items:center;gap:8px;padding:7px var(--space-2);background:var(--bg-input);border:1px solid var(--border-default);border-radius:5px;cursor:pointer;font-size:11.5px;color:var(--t1);min-height:32px;transition:border-color var(--motion-fast);}
.attr-cell:hover{border-color:var(--ac);}
.attr-cell .attr-chk{margin:0;cursor:pointer;flex:0 0 auto;}
.attr-cell .attr-label{flex:0 0 auto;font-weight:var(--fw-medium);}
.attr-cell.attr-cell-num{grid-column:1/-1;cursor:default;}
.attr-cell.attr-cell-num .attr-label{flex:0 0 auto;color:var(--t2);font-size:10.5px;}
.attr-cell .attr-num{flex:1;background:var(--bg-hover);border:1px solid var(--border-default);border-radius:3px;color:var(--t1);padding:3px 5px;font-size:var(--fs-sm);outline:none;min-width:0;}
.attr-cell .attr-num:focus{border-color:var(--ac);}
.attr-cell .attr-unit{flex:0 0 auto;color:var(--t3);font-size:var(--fs-xs);}

/* ============================================================
 * R10-2: 小窗 UI 响应式 (DEVLOG-2026-05-07-R10-deferred T1)
 * ============================================================
 * 三档断点:
 *   1280+   桌面默认(已有)
 *   900-1280  压缩 — 左右栏窄 + hdr 间距收缩
 *   <=900   极窄 — 右栏可折叠;hdr 文字按钮变图标按钮(高度优先)
 *   <=600   手机 / 移动 — splitter 隐藏,左右栏成抽屉(已有 .splitter 隐藏)
 * 不动 #cw,canvas 永远占满中部剩余空间。
 * ============================================================ */
/* 真实长恢复文案 + coarse 44px 按钮实测到 2059 仍需两行，2060 起单行完整。
   Chromium 在整数 max-width 边界按子像素求值，.98 保证 2059 命中而 2060 不命中。 */
@media (min-width: 1281px) and (max-width: 2059.98px){
  #hdr{padding-left:10px;padding-right:10px;gap:5px;flex-wrap:wrap;overflow-x:hidden;align-content:center;}
  #hdr .btn{padding:6px 8px;font-size:11px;}
  #hdr .dv{margin-left:2px;margin-right:2px;}
  #hdr>.sp{display:none;}
  #hdr-info{flex:0 0 100%;width:100%;max-width:100%;margin-left:0;gap:4px;}
  #hdr-info #save-ind,#hdr-info #mtag{flex-shrink:0;}
}
@media (hover: none) and (pointer: coarse) and (min-width: 1281px) and (max-width: 2059.98px){
  #hdr{gap:0;}
  #hdr .tb{gap:0;}
  #hdr .dv{margin-left:0;margin-right:0;}
  #hdr-info{gap:4px;}
}
@media (max-width: 1280px){
  #hdr{padding:5px var(--space-2);gap:var(--space-1);}
  #hdr h1{font-size:var(--fs-lg);}
  .btn{padding:5px 10px;font-size:10.5px;}
  .dv{margin:0 2px;}
  #lp{width:220px;}
  #rp{width:240px;}
  /* sup-ctrl(覆盖率阈值滑块)收紧 */
  .sup-ctrl{transform:scale(.92);transform-origin:left center;}
}
@media (max-width: 1024px){
  #hdr{padding:var(--space-1) 6px;gap:3px;}
  .btn{padding:var(--space-1) var(--space-2);font-size:var(--fs-xs);}
  /* 文字按钮极简化:保留主功能,二级隐藏 */
  #btn-screen-record,#btn-export-pdf,#btn-share-url{display:none;}
  #lp{width:200px;min-width:160px;}
  #rp{width:220px;min-width:160px;}
}
@media (max-width: 900px){
  /* 极窄 — 右栏可折叠(用户拖 splitter 自己合上),提供更小 min-width */
  #lp{width:180px;min-width:140px;}
  #rp{width:200px;min-width:140px;}
  /* R12-T1.2: 极窄屏不再整体隐藏,只保留第一行(鼠标操作),后续行收起 */
  #help{display:flex;}
  #help .help-row:nth-child(n+2){display:none;}
  /* 视图按钮组只留最常用 */
  #view-front,#view-side{display:none;}
}
@media (max-width: 720px){
  /* 手机 — 主区域改纵向 stack,左右栏变可滚抽屉 */
  #main{flex-direction:column;}
  #lp,#rp{width:auto;max-width:100%;max-height:35vh;border-right:0;border-left:0;border-bottom:1px solid var(--border-default);}
  .splitter{display:none;}
  #cw{flex:1;min-height:50vh;}
  /* 头部直接溢出滚动 */
  #hdr{flex-wrap:nowrap;overflow-x:auto;}
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── R12-T1.1: 撤销/重做按钮可视化(.disabled + 历史栈深度角标) ────────── */
.hist-badge{
  display:inline-block;
  min-width:14px;
  padding:0 var(--space-1);
  margin-left:4px;
  background:var(--ac);
  color:#0b1020;
  border-radius:8px;
  font-size:var(--fs-xs);
  font-weight:var(--fw-bold);
  line-height:14px;
  text-align:center;
  vertical-align:1px;
  pointer-events:none;
}
.hist-badge[hidden]{display:none;}
#btn-undo.disabled,
#btn-redo.disabled{
  opacity:.35;
  cursor:not-allowed;
  filter:grayscale(.6);
}
#btn-undo.disabled .hist-badge,
#btn-redo.disabled .hist-badge{
  background:var(--bg-input);
  color:var(--t2);
}

/* ── R12-T1.2: #help 帮助行布局(结构化 row + flex-wrap) ─────────────── */
#help{
  position:absolute;
  bottom:12px;
  /* 贴左栏右侧外缘(最优装载按钮右面的空地),随左栏拖宽而移动;--lp-width 由 splitter 写,未拖时回退 280 */
  left:calc(var(--lp-width, 280px) + 24px);
  right:auto;                    /* 覆盖上方旧 #help 块的 right:540px,否则 left+right 把收起态撑成 888px 宽的隐形块挡住画布底部点击 */
  width:fit-content;             /* 收起态只占"操作提示"宽度;展开时随内容长大(受 max-width 约束换行) */
  max-width:calc(100% - var(--lp-width, 280px) - 48px);
  text-align:left;
  background:rgba(0,0,0,.75);
  padding:5px 12px;
  border-radius:9px;
  font-size:var(--fs-sm);
  line-height:1.6;
  color:var(--t2);
  /* 默认收起为小药丸,需接收 hover → pointer-events:auto(收起态仅占角落一小块) */
  pointer-events:auto;
  cursor:default;
  display:flex;
  flex-direction:column;
  gap:var(--space-1);
  box-sizing:border-box;
  opacity:.55;
  transition:opacity 150ms ease;
}
/* 收起态:只露一个"操作提示"小标,消除底部常驻提示墙(用户反感的"教程 UI");
 * 鼠标移上去再完整展开。已有 快捷键 按钮承载完整键位表,这里只做轻量提示。 */
#help::before{
  content:'操作提示';
  font-size:var(--fs-xs);
  color:var(--t3);
  letter-spacing:.5px;
}
#help .help-row{display:none;}
#help:hover{opacity:1;}
#help:hover::before{display:none;}
#help:hover .help-row{
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-1) var(--space-3);
  align-items:center;
}
#help .help-row span{white-space:nowrap;}

/* R9-24 / R12-T1.5: 货物卡冗余按面 chip — 6 面分别显示,空值灰显 */
.ct-pad-faces{display:inline-flex;flex-wrap:wrap;gap:2px;margin-top:2px;}
.ct-pad-faces .pf-chip{font-size:var(--fs-xxs);line-height:1.4;padding:1px 5px;border-radius:8px;font-family:var(--ff-mono);}
.ct-pad-faces .pf-chip.chip-on{background:rgba(79,143,255,.2);color:var(--ac);}
.ct-pad-faces .pf-chip.chip-zero{background:var(--bg-input);color:var(--t3);}

/* R9-20 / R12 T1.6 — 右键按钮快速绑定快捷键浮层 */
.qbind-pop{position:fixed;z-index:var(--z-toast);background:var(--bg-elevated);border:1px solid var(--border-default);border-radius:8px;padding:10px 14px;box-shadow:0 8px 28px rgba(0,0,0,.5);font-size:var(--fs-md);display:flex;flex-direction:column;gap:var(--space-2);min-width:240px;}
.qbind-pop .qb-title{font-weight:var(--fw-semibold);color:var(--t1);}
.qbind-pop .qb-cur{color:var(--t2);font-size:var(--fs-sm);}
.qbind-pop .qb-input{background:var(--bg-input);border:1px solid var(--border-default);border-radius:4px;padding:5px 10px;color:var(--ac);text-align:center;font-family:var(--ff-mono);font-variant-numeric:tabular-nums;}
.qbind-pop .qb-input.listening{border-color:var(--ac);box-shadow:0 0 0 2px rgba(79,143,255,.18);}
.qbind-pop .qb-row{display:flex;gap:6px;justify-content:flex-end;}
.qbind-pop button{padding:var(--space-1) 10px;font-size:var(--fs-sm);border-radius:4px;border:1px solid var(--border-default);background:var(--bg-input);color:var(--t1);cursor:pointer;}
.qbind-pop button.danger{background:var(--er);border-color:var(--er);color:#fff;}
.qbind-pop button.primary{background:var(--ac);border-color:var(--ac);color:#fff;}

/* R12-T2.1 / R9-14: 左栏底部 .lp-actions —— 最优装载按钮从顶栏迁移到此
   R24-T8c-22 (2026-07-08 第3轮): 用户原话 "把左下角的设置按钮放的离最优装载近一点，贴在一起，
   留更多空间给最左边的装载可行性信息栏"
   修复: #opt-sel-counter 占满剩余空间 (flex:1), 设置+最优装载 gap:4px 紧贴右侧 */
.lp-actions{padding:var(--space-2) 10px;border-top:1px solid var(--border-default);display:flex;flex-direction:row;align-items:center;gap:4px;flex-shrink:0;flex-wrap:nowrap;}
.lp-actions .lp-action-btn{flex:0 0 auto;order:2;font-size:var(--fs-lg);font-weight:var(--fw-semibold);padding:8px 18px;}
.lp-actions #opt-sel-counter{margin-top:0;flex:1 1 auto;justify-content:flex-start;min-width:0;font-size:var(--fs-xs);order:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:auto;}
.lp-action-btn{padding:var(--space-2) var(--space-3);background:var(--ac);color:#fff;border:1px solid var(--ac);border-radius:6px;font-size:var(--fs-md);font-weight:var(--fw-medium);cursor:pointer;transition:filter var(--motion-fast);}
.lp-action-btn:hover:not(:disabled){filter:brightness(1.15);}
/* R22-T1 Stage B: 最优装载按钮禁用态(空选 / Stage C 接入不可装载判定后也用) */
.lp-action-btn:disabled,.lp-action-btn.disabled{background:var(--bg-hover);color:var(--t3);border-color:var(--border-default);cursor:not-allowed;filter:none;opacity:.6;}
/* R22-T1 Stage B: ct-item 内最优装载 checkbox(左栏货物/货箱卡片左侧) */
.ct-opt-check{display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:2px;margin-right:2px;cursor:pointer;}
.ct-opt-check input[type="checkbox"]{width:14px;height:14px;cursor:pointer;accent-color:var(--ac);margin:0;}
/* R22-T1 Stage B: 货物/货箱栏 header 全选/反选切换按钮 */
.ph-selall{font-size:var(--fs-xs);padding:2px 6px;background:transparent;color:var(--t2);border:1px solid var(--border-default);border-radius:3px;cursor:pointer;transition:all var(--motion-fast);}
.ph-selall:hover{background:var(--bg-hover);color:var(--t1);border-color:var(--ac);}
/* R22-T1 Stage B: 最优装载按钮旁的计数 / 提示文本(用户原话:"按钮旁边要展示出当前已选中的货物和货箱分别的数量") */
.opt-sel-counter,#opt-sel-counter{margin-top:4px;font-size:var(--fs-xs);color:var(--t2);text-align:center;line-height:1.5;display:flex;flex-direction:column;align-items:center;gap:4px;}
.opt-sel-counter.empty,#opt-sel-counter.empty{color:var(--wn);font-weight:var(--fw-medium);}
/* 2026-05-27 #14: counter 内三态文字 + 一键忽略按钮 */
#opt-sel-counter .osc-hint{color:var(--t2);}
#opt-sel-counter .osc-ok{color:var(--ok);font-weight:var(--fw-semibold);}
#opt-sel-counter .osc-err{color:var(--er);font-weight:var(--fw-medium);display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;}
#opt-sel-counter .osc-quickfix{font-size:var(--fs-xs);padding:3px 9px;background:linear-gradient(135deg,var(--ac),var(--ac2));color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:var(--fw-semibold);transition:opacity var(--motion-fast);}
#opt-sel-counter .osc-quickfix:hover{opacity:.85;}
#opt-sel-counter .osc-quickfix:focus-visible{outline:2px solid var(--t1);outline-offset:1px;}
/* R22-T1 Stage C: 左栏 ct-item 实时预检红框 + 角标(用户原话 #1 "直接在对应货物卡片上闪红,表述其无法装载") */
.ct-item.ct-item-unfit{border-color:var(--er) !important;background:rgba(239,68,68,.06);animation:ctItemUnfitPulse .35s ease-in-out 2;}
.ct-item.ct-item-unfit:hover{background:rgba(239,68,68,.10);border-color:var(--er) !important;}
@keyframes ctItemUnfitPulse{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,.0);}50%{box-shadow:0 0 0 3px rgba(239,68,68,.45);}}
.ct-unfit-badge{display:inline-block;font-size:var(--fs-xxs);line-height:1.4;font-weight:var(--fw-semibold);color:#fff;background:var(--er);padding:1px 5px;border-radius:3px;margin-left:4px;cursor:help;}
/* 2026-05-27 用户原话:所有卡片数据都要带不同色框,搜索后命中数据闪动 — 全数据 chip 化 + 命中闪动 */
.ct-badge-unplaced{font-size:var(--fs-xxs);line-height:1.4;color:var(--er);background:rgba(255,80,80,.15);padding:1px 5px;border-radius:3px;margin-left:4px;}
/* 模板 picker chips wrapper (.tpl-chips) — 同左栏 .ct-chips 样式但放宽 overflow,避免被 button/grid 截断 */
.tpl-popup .tpl-chips{display:flex !important;flex-wrap:wrap !important;gap:3px;overflow:visible !important;max-height:none !important;}
.ct-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px;}
.ct-data-chip{display:inline-block;font-size:var(--fs-xs);line-height:1.4;padding:2px 6px;border-radius:4px;border:1px solid var(--border-subtle);background:var(--bg-input);color:var(--t2);cursor:help;font-weight:var(--fw-medium);transition:border-color var(--motion-fast),background var(--motion-fast);}
.cdc-dims{color:#facc15;border-color:rgba(250,204,21,.45);background:rgba(250,204,21,.10);}
.cdc-weight{color:#fb923c;border-color:rgba(251,146,60,.45);background:rgba(251,146,60,.10);}
.cdc-maxload{color:#a78bfa;border-color:rgba(167,139,250,.45);background:rgba(167,139,250,.10);}
/* #9 (2026-05-29): 货箱卡片容积 chip(青色,区别于尺寸黄/载重紫) */
.cdc-vol{color:#2dd4bf;border-color:rgba(45,212,191,.45);background:rgba(45,212,191,.10);}
/* 货箱价格 chip(金色,区别于其它数据 chip) */
.cdc-price{color:#fbbf24;border-color:rgba(251,191,36,.45);background:rgba(251,191,36,.10);}
/* #16 (2026-05-29): 装载约束设置面板 — visibility/opacity approach for smooth exit */
#lr-bg{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;pointer-events:none;transition:opacity 220ms cubic-bezier(.4,0,.2,1),visibility 0s 220ms;}
#lr-bg.open{opacity:1;visibility:visible;pointer-events:auto;transition:opacity 220ms cubic-bezier(.4,0,.2,1);}
#lr-bg.lr-leaving{opacity:0;visibility:visible;pointer-events:none;transition:opacity 220ms cubic-bezier(.4,0,.2,1),visibility 0s 220ms;}
#lr-panel{width:430px;max-width:92vw;max-height:88vh;overflow:auto;background:var(--bg-panel,#161b2b);border:1px solid var(--border-default);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.6);color:var(--t1);}
.lr-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border-default);font-weight:var(--fw-semibold);font-size:15px;}
.lr-head button{background:transparent;border:0;color:var(--t3);font-size:20px;cursor:pointer;line-height:1;}
.lr-head button:hover{color:var(--t1);}
.lr-body{padding:10px 16px;}
.lr-foot{padding:8px 16px 12px;font-size:11px;color:var(--t3);line-height:1.5;border-top:1px solid var(--border-default);}
.lr-presets{display:flex;gap:6px;margin-bottom:6px;}
.lr-preset{flex:1;padding:7px 0;background:var(--bg-input);border:1px solid var(--border-subtle);border-radius:6px;color:var(--t2);cursor:pointer;font-size:13px;transition:all var(--motion-fast);}
.lr-preset:hover{color:var(--t1);border-color:var(--border-default);}
.lr-preset.on{background:linear-gradient(180deg,rgba(79,143,255,.25),rgba(79,143,255,.08));color:#fff;border-color:var(--ac);font-weight:var(--fw-semibold);}
.lr-preset-desc{font-size:11px;color:var(--t3);line-height:1.5;margin-bottom:8px;min-height:30px;}
.lr-sec{font-size:11px;color:var(--ac);margin:12px 0 4px;border-bottom:1px solid var(--border-subtle);padding-bottom:3px;}
.lr-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 0;}
.lr-row.lr-dis{opacity:.4;}
.lr-lab{font-size:13px;color:var(--t1);}
.lr-lab em{font-size:10px;color:var(--wn);font-style:normal;margin-left:4px;}
.lr-lab b{color:var(--ac);}
.lr-toggle{min-width:48px;padding:5px 12px;background:var(--bg-input);border:1px solid var(--border-subtle);border-radius:14px;color:var(--t3);cursor:pointer;font-size:12px;transition:all var(--motion-fast);}
.lr-toggle.on{background:rgba(34,197,94,.2);border-color:#22c55e;color:#86efac;}
.lr-toggle[disabled]{cursor:not-allowed;}
.lr-seg-grp{display:inline-flex;gap:4px;}
.lr-seg{padding:5px 10px;background:var(--bg-input);border:1px solid var(--border-subtle);border-radius:6px;color:var(--t3);cursor:pointer;font-size:12px;transition:all var(--motion-fast);}
.lr-seg.on{background:linear-gradient(180deg,rgba(79,143,255,.25),rgba(79,143,255,.08));color:#fff;border-color:var(--ac);}
.lr-row input[type=range]{width:160px;}
/* #16: 物体编辑栏 — 全局设置覆盖参数时的提示横幅 */
.lr-conflict-banner{background:rgba(255,184,74,.12);border:1px solid rgba(255,184,74,.5);border-radius:6px;padding:8px 10px;margin-bottom:10px;font-size:11px;color:var(--wn,#ffb84a);line-height:1.6;}
.lr-conflict-banner b{color:var(--wn,#ffb84a);}

.strength-editor{margin:14px 0 18px;padding:12px;border:1px solid var(--border-default);border-radius:8px;background:rgba(255,255,255,.025);}
.strength-face-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:8px 0 12px;}
.strength-face-tab,.strength-actions button{border:1px solid var(--border-default);border-radius:6px;background:var(--bg-input);color:var(--t2);padding:7px 8px;cursor:pointer;}
.strength-face-tab.is-selected{border-color:var(--ac);color:var(--t1);background:rgba(59,130,246,.16);box-shadow:0 0 0 1px rgba(59,130,246,.18) inset;}
.strength-face-row{display:none;}
.strength-face-row.is-selected{display:block;}
.strength-face-title{font-size:var(--fs-sm);font-weight:var(--fw-medium);color:var(--t1);margin-bottom:7px;}
.strength-pair{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.strength-pair label{font-size:var(--fs-xs);color:var(--t2);}
.strength-pair input{box-sizing:border-box;width:100%;margin-top:5px;padding:9px 10px;background:var(--bg-input);border:1px solid transparent;border-radius:6px;color:var(--t1);}
.strength-pair input[data-derived="true"]{border-style:dashed;border-color:rgba(79,143,255,.45);color:var(--ac);}
.strength-pair input:disabled{opacity:.58;cursor:not-allowed;}
.strength-actions{display:flex;gap:7px;margin-top:8px;}
.strength-actions button{font-size:var(--fs-xs);padding:5px 8px;}
.strength-status{min-height:20px;margin-top:7px;color:var(--t3);font-size:var(--fs-xs);line-height:1.55;}
.strength-warning,.strength-invalid{margin-top:10px;padding:8px 10px;border-radius:6px;font-size:var(--fs-xs);line-height:1.55;}
.strength-warning{color:var(--wn,#ffb84a);background:rgba(255,184,74,.12);border:1px solid rgba(255,184,74,.45);}
.strength-invalid{color:var(--er,#ff6b6b);background:rgba(255,80,80,.1);border:1px solid rgba(255,80,80,.42);}
.strength-warning[hidden],.strength-invalid[hidden]{display:none;}
@media (max-width:600px){.strength-pair{grid-template-columns:1fr}.strength-face-tabs{grid-template-columns:1fr}.strength-face-tab{min-height:44px}}
/* #24/#26 (2026-05-29): AI 面板最小化 — 收起为只剩标题栏的紧凑小窗(AI 后台继续运行) */
/* #28: 最小化只保留 标题(可点展开) + ×;宽度贴合内容(去掉右侧空白) */
.ai-min{height:auto!important;min-height:0!important;min-width:0!important;width:auto!important;max-width:none!important;resize:none!important;overflow:hidden!important;}
.ai-min > *:not(#ai-head){display:none!important;}
.ai-min #ai-cfg-btn,.ai-min #ai-export-conv-btn,.ai-min #ai-clear-btn,.ai-min #ai-min-btn,.ai-min #ai-demo-inline{display:none!important;}
.ai-min #ai-head{cursor:grab;border-bottom:0;padding:4px 10px!important;gap:8px!important;white-space:nowrap;width:auto!important;justify-content:flex-start!important;}
.ai-min #ai-panel-title{font-size:14px!important;margin:0!important;cursor:pointer;}
.ai-min #ai-head button{padding:2px 8px!important;font-size:14px!important;}
/* #30 (2026-05-29): 悬浮 AI 圆形启动器(FAB)— 复刻聊天气泡:可拖动、吸附边缘、点击展开 */
#ai-fab{position:fixed;width:40px;height:40px;border-radius:50%;display:none;align-items:center;justify-content:center;
  background:linear-gradient(135deg,#4f8fff,#2b6fe0);color:#fff;font-weight:700;font-size:13px;letter-spacing:.5px;
  border:0;cursor:grab;z-index:calc(var(--z-modal,9000) + 5);box-shadow:0 6px 20px rgba(0,0,0,.45),0 0 0 1px rgba(255,255,255,.12);
  touch-action:none;user-select:none;transition:box-shadow var(--motion-fast),transform var(--motion-fast),left .18s cubic-bezier(.4,0,.2,1),top .18s cubic-bezier(.4,0,.2,1);}
#ai-fab:hover{transform:scale(1.08);box-shadow:0 10px 28px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.2);}
#ai-fab:active{cursor:grabbing;transition:none;}
/* AI 运行中(运动态): 旋转光环 + 外扩脉冲,远看也能一眼分辨"在跑";空闲(停止态)= 静止圆 + 绿点。 */
#ai-fab.ai-fab-busy::after{content:"";position:absolute;inset:-4px;border-radius:50%;border:2px solid rgba(79,143,255,.7);border-top-color:transparent;animation:ai-fab-spin .9s linear infinite;}
#ai-fab.ai-fab-busy{animation:ai-fab-pulse 1.3s ease-in-out infinite;}
@keyframes ai-fab-spin{to{transform:rotate(360deg);}}
@keyframes ai-fab-pulse{
  0%,100%{box-shadow:0 6px 20px rgba(0,0,0,.45),0 0 0 1px rgba(255,255,255,.14),0 0 0 0 rgba(79,143,255,.55);}
  50%{box-shadow:0 6px 22px rgba(0,0,0,.5),0 0 0 1px rgba(255,255,255,.22),0 0 0 9px rgba(79,143,255,0);}
}
/* 空闲态: 右上角静态绿点 = "就绪待命",与运行态的旋转/脉冲形成明确对比 */
#ai-fab:not(.ai-fab-busy)::before{content:"";position:absolute;top:1px;right:1px;width:8px;height:8px;border-radius:50%;background:#34d27b;box-shadow:0 0 0 2px #16243a;}
/* #26: 拖到屏幕边缘的吸附可视反馈 — 发光描边 + "松开即最小化"提示 */
.ai-dock-hint{outline:2px dashed var(--ac,#4f8fff);outline-offset:3px;box-shadow:0 0 0 3px rgba(79,143,255,.28),0 14px 44px rgba(0,0,0,.5)!important;}
.ai-dock-hint::after{content:'松开即最小化';position:absolute;top:-26px;left:50%;transform:translateX(-50%);background:var(--ac,#4f8fff);color:#fff;font-size:11px;font-weight:600;padding:2px 10px;border-radius:10px;white-space:nowrap;pointer-events:none;z-index:20;box-shadow:0 4px 12px rgba(0,0,0,.4);}
.cdc-fragile{color:var(--er);border-color:rgba(239,68,68,.45);background:rgba(239,68,68,.10);}
.cdc-pressure{color:var(--ac2);border-color:rgba(124,92,252,.45);background:rgba(124,92,252,.10);}
.cdc-maxp{color:#ec4899;border-color:rgba(236,72,153,.45);background:rgba(236,72,153,.10);}
.cdc-upright{color:var(--ac);border-color:rgba(79,143,255,.45);background:rgba(79,143,255,.10);}
/* 搜索命中闪动: 2 圈黄色脉冲 + 设置高亮态(不动画完后保持) */
@keyframes chip-flash{0%{box-shadow:0 0 0 0 rgba(253,224,71,.0);}30%{box-shadow:0 0 0 5px rgba(253,224,71,.75);}100%{box-shadow:0 0 0 2px rgba(253,224,71,.45);}}
.ct-data-chip.match-flash{animation:chip-flash .8s ease-out 2 alternate;border-color:#fde047 !important;color:#fde047 !important;background:rgba(253,224,71,.20) !important;box-shadow:0 0 0 2px rgba(253,224,71,.45);}
/* 同样支持 padding 六面 chip 命中闪动 */
.pf-chip.match-flash{animation:chip-flash .8s ease-out 2 alternate;border-color:#fde047 !important;color:#fde047 !important;background:rgba(253,224,71,.20) !important;}

/* 2026-05-27 purge: .ai-tier-badge 已删 (license 系统下线) */

/* R12 T3.1 / R9-2 — 全局 tooltip(由 src/js/30-tooltip.js 注入 #global-tip)
   三段式结构: .tip-id (代码级唯一定义名) + .tip-desc (作用) + .tip-key (快捷键) */
.global-tip{position:fixed;z-index:var(--z-tooltip,100010);background:#0f1422;border:1px solid #2a3450;border-radius:6px;padding:0;color:#e8edff;font-size:var(--fs-md);line-height:1.5;max-width:280px;box-shadow:0 6px 22px rgba(0,0,0,.45);opacity:0;transform:translateY(-2px);transition:opacity var(--motion-fast) ease, transform var(--motion-fast) ease;pointer-events:none;}
.global-tip.visible{opacity:1;transform:translateY(0);}
.global-tip kbd{display:inline-block;padding:1px 6px;background:#1f2638;border:1px solid #3a4566;border-radius:3px;font-family:var(--ff-mono);font-variant-numeric:tabular-nums;font-size:var(--fs-sm);color:#cfd8ff;line-height:1.4;}
.global-tip .global-tip-sep{display:block;height:6px;}
.global-tip .global-tip-shortcut{font-size:var(--fs-sm);color:var(--t2);}
.global-tip .tip-id{font-family:var(--ff-mono);font-variant-numeric:tabular-nums;font-size:var(--fs-sm);color:var(--ac);padding:6px 10px;border-bottom:1px solid #2a3450;background:#141a2c;border-radius:6px 6px 0 0;letter-spacing:.2px;word-break:break-all;}
.global-tip .tip-desc{padding:6px 10px;color:#e8edff;}
/* tip-state：这个元素**此刻**为什么不能用（如"需要登录才能导出"）。
   和 tip-desc（这个按钮是干什么的）同时出现时排在它上面，并用醒目色 —— 用户
   悬停在一个点不动的按钮上时，想知道的是原因，不是再读一遍用途。 */
.global-tip .tip-state{padding:6px 10px 2px;color:#ffd479;font-weight:600;}
.global-tip .tip-desc + .tip-key,
.global-tip .tip-id + .tip-key{border-top:1px solid #2a3450;}
.global-tip .tip-key{padding:5px 10px;font-family:var(--ff-mono);font-variant-numeric:tabular-nums;font-size:var(--fs-sm);color:var(--t2);background:#101627;border-radius:0 0 6px 6px;}
.global-tip .tip-id:only-child,
.global-tip .tip-desc:only-child{border-radius:6px;border-bottom:none;}
/* 2026-05-27 用户原话:「所有按钮/信息/文字都要有统一鼠标悬浮弹窗,系统化展示可以进行什么操作」 */
.global-tip .tip-ops{padding:6px 10px;background:#0c1220;border-top:1px solid #2a3450;border-radius:0 0 6px 6px;font-size:var(--fs-sm);color:#aab5d4;line-height:1.55;}
.global-tip .tip-ops-h{font-size:var(--fs-xs);font-weight:var(--fw-bold);color:#79a3ff;letter-spacing:.5px;margin-bottom:3px;text-transform:uppercase;}
.global-tip .tip-ops-l{color:#c6d2ea;}
.global-tip .tip-ops-l b{color:#e8edff;font-weight:var(--fw-semibold);}
.global-tip .tip-id + .tip-ops,
.global-tip .tip-desc + .tip-ops,
.global-tip .tip-key + .tip-ops{border-top:1px solid #2a3450;}

/* R12 T3.2: 黑箱操作显式化指示器(#stats 同行右侧) */
/* #43 (2026-05-29): 预留固定宽度槽 —— "物理重算中"等指示器出现/消失只在本槽内变化,不再撑宽顶栏推动其它信息栏。 */
#blackbox-stack{display:inline-flex;gap:6px;align-items:center;justify-content:center;min-width:90px;flex-shrink:0;overflow:hidden;box-sizing:border-box;}
/* #42 (2026-05-29): 切片控件常驻显示,非切片态灰显+禁用(点不动) */
#inspect-ctrls.inspect-ctrls-off{opacity:.4;pointer-events:none;filter:grayscale(.4);}

/* R15 §T1.4 F-A3-002: toast 离场动画 — 02-state.js dismiss() 加 .tt-out 后 200ms 才 removeChild */
@keyframes tt-out { from { opacity:1; transform:none; } to { opacity:0; transform:translateY(-8px) scale(.95); } }
.toast-stack > .tt-out, #toast-stack > .tt-out { animation: tt-out .2s ease-out forwards; pointer-events:none; }

/* R15 §T1.4 F-A3-004: AI 思考中动效(替代静态"思考中 思考中…"硬编码) */
.thinking-dots { display:inline-block; margin-left:4px; }
.thinking-dots span { animation: thinking-dot 1.4s infinite both; display:inline-block; opacity:.3; }
.thinking-dots span:nth-child(2) { animation-delay: .2s; }
.thinking-dots span:nth-child(3) { animation-delay: .4s; }
@keyframes thinking-dot { 0%, 80%, 100% { opacity:.3; transform:translateY(0); } 40% { opacity:1; transform:translateY(-3px); } }
.blackbox-ind{font-size:var(--fs-xs);padding:3px var(--space-2);background:rgba(124,92,252,.18);border:1px solid var(--ac2);color:var(--ac2);border-radius:4px;opacity:0;transform:translateX(-4px);transition:opacity var(--motion-fast) ease, transform var(--motion-fast) ease;font-family:var(--ff-mono);letter-spacing:.5px;}
.blackbox-ind.show{opacity:1;transform:translateX(0);}

/* ============================================================
 * R16-T1.1: 全局 :focus-visible 焦点环 — 键盘用户可见
 * ============================================================
 * 不用 :focus(鼠标点击会触发,过度打扰),用 :focus-visible 仅在
 * 键盘 tab 进入时显示。
 * ============================================================ */
button:focus-visible,
.btn:focus-visible,
.bb:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
.scene-tab:focus-visible,
.ct-item:focus-visible,
.oi:focus-visible,
[role="button"]:focus-visible,
[role="tab"]:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--ac);
  outline-offset: 2px;
  border-radius: 3px;
}
/* canvas 是焦点时给一个微提示(不挡视觉) */
#cv:focus-visible {
  outline: 2px solid var(--ac);
  outline-offset: -2px;   /* 向内,避免画布周围多一圈 */
}
/* R20-T21: 删除 R17-T2.2 浅色主题 (light + prefers-color-scheme media).
 * 用户原话 (U2 N4): "把切换主题功能删去,没必要搞不必要的东西".
 * 仅保留语义层 token 的暗色定义 (R18-T1.2 :root 已含),固定深色,
 * 不再随系统/用户偏好翻转. */

/* ============================================================
 * R20-T17: Onboarding (27-onboarding.js) 覆层样式 — 此前 0 命中
 * 全屏遮罩 #ob-overlay 内含 #ob-mask(半透明背景) / #ob-spot(反向高亮)
 * / #ob-tip(浮动卡片) / 三态按钮 + 进度。pause 状态隐藏整覆层。
 * ============================================================ */
#ob-overlay{position:fixed;inset:0;z-index:var(--z-overlay);pointer-events:none;}
#ob-overlay.ob-paused{opacity:0;visibility:hidden;pointer-events:none;}
#ob-mask{position:absolute;inset:0;background:var(--bg-overlay);pointer-events:auto;}
#ob-spot{position:absolute;display:none;border-radius:6px;border:2px solid var(--ac);box-shadow:0 0 0 9999px var(--bg-overlay), 0 0 0 1px var(--ac) inset;pointer-events:none;transition:left var(--motion-base) ease, top var(--motion-base) ease, width var(--motion-base) ease, height var(--motion-base) ease;}
#ob-tip{position:fixed;width:340px;max-width:calc(100vw - 32px);background:var(--bg-elevated);border:1px solid var(--ac);border-radius:10px;padding:var(--space-3);box-shadow:0 12px 32px rgba(0,0,0,.55);font-size:var(--fs-md);color:var(--t1);pointer-events:auto;z-index:var(--z-popup);}
#ob-tip-title{font-size:var(--fs-lg);font-weight:var(--fw-bold);color:var(--ac);margin-bottom:var(--space-2);}
#ob-tip-body{font-size:var(--fs-md);color:var(--t1);line-height:1.6;margin-bottom:var(--space-2);}
#ob-tip-body b{color:var(--ac);font-weight:var(--fw-semibold);}
#ob-tip-hint{font-size:var(--fs-sm);color:var(--t2);line-height:1.5;padding:var(--space-2);background:var(--bg-input);border:1px dashed var(--border-default);border-radius:6px;margin-bottom:var(--space-3);}
#ob-tip-bar{display:flex;align-items:center;gap:var(--space-2);}
#ob-tip-prog{font-size:var(--fs-sm);color:var(--t3);font-family:monospace;}
#ob-tip-bar button{padding:var(--space-1) var(--space-3);border:1px solid var(--border-default);border-radius:6px;background:var(--bg-input);color:var(--t2);font-size:var(--fs-sm);font-weight:var(--fw-medium);cursor:pointer;transition:all var(--motion-fast);}
#ob-tip-bar button:hover{border-color:var(--ac);color:var(--t1);}
#ob-skip-btn{color:var(--t3);}
#ob-next-btn{background:linear-gradient(135deg,var(--ac),var(--ac2));color:#fff;border-color:var(--ac);font-weight:var(--fw-semibold);}
#ob-next-btn:hover{filter:brightness(1.1);color:#fff;}

/* 2026-05-27 purge: R20-T18 account modal CSS (.acct-*) 已删 (32-account.js 已删) */

/* ============================================================
 * R20-B110: HTML 散养内联 style="" 提取到 class
 * ============================================================
 * 历史:cargo3d-classic.html 内若干元素直接写 style="...",维护性差
 * (审计 agent-21 P1)。本批集中提到这里;JS 内 cssText 仍 defer。
 * ============================================================ */
.ph-actions{display:flex;flex-wrap:wrap;gap:4px;}
.lp-search{width:calc(100% - 16px);margin:4px 8px 6px;padding:5px 8px;background:#1a2030;border:1px solid #2a334d;border-radius:4px;color:#e8edff;font-size:var(--fs-sm);outline:none;}
.box-sel-action{display:none;position:absolute;background:#1f2638;color:#cfd8ff;border:1px solid #5a7ad8;padding:6px 12px;border-radius:5px;cursor:pointer;font-size:var(--fs-md);z-index:99;}

/* ============================================================
 * R20-B111: 全局 :focus-visible 兜底 + 旧浏览器 fallback
 * ============================================================
 * 上面 R16-T1.1 已给具体选择器加了焦点环,但仍有不少 input/select
 * 本身写了 outline:none(.pi/.opt-tbl input/.pad-bulk-val 等)。
 * 这里给所有交互元素一个 universal 兜底:键盘焦点必出现焦点环。
 * 同时给旧浏览器(不识别 :focus-visible)留 :focus 兜底,避免
 * 完全无视觉提示。
 * ============================================================ */
*:focus-visible{outline:2px solid var(--ac);outline-offset:2px;}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible,
[tabindex]:focus-visible{
  outline:2px solid var(--ac);
  outline-offset:2px;
}
/* 旧浏览器不识别 :focus-visible → :focus 仍触发兜底;新浏览器走上面规则 */
:focus:not(:focus-visible){outline:none;}

/* ============================================================
 * R20-B112: prefers-reduced-motion 兜底 boot loader
 * ============================================================
 * 上面 line 673 已经给 *::before/::after 全局降级动画时长到 .01ms,
 * 但 boot loader 的关键帧 bl-bounce/bl-slide 在内联 <style> 块,
 * 这里再加一道 .boot 命名空间兜底(若未来 boot 区结构变动也安全)。
 * boot loader 容器 id=#boot-loader,本规则用 [id^=boot-] 兜底。
 * ============================================================ */
@media (prefers-reduced-motion: reduce){
  #boot-loader,
  #boot-loader *,
  #boot-loader::before,
  #boot-loader::after,
  .boot,
  .boot *,
  .boot::before,
  .boot::after{
    animation:none !important;
    transition:none !important;
  }
}

/* ============================================================
 * R20-C1: 全局 prefers-reduced-motion 兜底
 * ============================================================
 * 在用户系统启用 "减少动态效果" 时,把所有 transition 时长压到 0.01ms,
 * animation 也降到 0.01ms 并仅播一次。覆盖 .15s/.18s/.25s/.3s 等所有现存值,
 * 比 B112 boot 局部规则更广(line 930)。
 * ============================================================ */
@media (prefers-reduced-motion: reduce){
  *,
  *::before,
  *::after{
    transition-duration:0.01ms !important;
    animation-duration:0.01ms !important;
    animation-iteration-count:1 !important;
    scroll-behavior:auto !important;
  }
}

/* ============================================================
 * R20-N6-1: 按钮按下微缩反馈(scale .95 + 80ms transition)
 * ============================================================
 * 用户原话(U3): "所有的操作都要有对应的反馈" — 为所有按钮类(.btn / .bb / .pbtn /
 *   .acct-btn / .opt-spec-btn / .ct-edit-btn / .ct-del-btn / .scene-tab-add /
 *   .ph button / .mbtns button / .mo-x / button / [role="button"]) 加 :active
 *   缩放反馈,确认点击命中。
 * 80ms = 用户感知阈值下限,既能看见也不拖慢操作.
 * 触屏 / 鼠标 / 键盘 Space 都会触发 :active.
 * R20-C1 prefers-reduced-motion 已自动把 transition-duration 压到 .01ms,无需重复.
 * ============================================================ */
/* T20: 全局 :active 反馈 — 单一规则,覆盖所有按钮 (旧多套规则已整合于此) */
button:not(:disabled):active{
  transform:scale(.97);
  transition-duration:var(--motion-fast);
}

/* ============================================================
 * R20-N6-4: 模态框面板 slide-in 50ms 过渡
 * ============================================================
 * #mbg.open 已有 opacity .18s fade;再叠 #mo 内部 translateY(8px → 0)
 * 50ms 微抬升,提供"面板从下方滑入"的方向性反馈,与 toast 的 tt-in
 * 关键帧风格一致。reduce-motion 走 R20-C1 兜底压到 .01ms.
 * ============================================================ */
@keyframes mo-slide-in{from{transform:translateY(8px);opacity:.7;}to{transform:none;opacity:1;}}
#mbg.open #mo,
#mbg.open #opt-mo{animation:mo-slide-in 50ms cubic-bezier(.4,0,.2,1);}

/* ============================================================
 * R20-N6-13: 通用 cursor 规则 — hover 100ms 内根据元素类型变指针
 * ============================================================
 * 大部分 .btn/.oi/.ct-item 已显式声明 cursor;此处给"漏写"的兜底,
 * 并按交互意图分组:
 *   - 可点击元素 → pointer
 *   - 可拖拽源(库存/调色板)→ grab,激活时 grabbing
 *   - 选中物体 + 主画布拖拽 → move
 *   - 不可交互装饰 → default
 * cursor 是即时的(浏览器原生 hover 触发),CSS 不需要 transition.
 * ============================================================ */
[role="button"]:not(:disabled),
[data-action]:not(:disabled),
.bbtn:not(:disabled),
.pi[type="color"],
.lock-icon,
.ph button:not(:disabled){cursor:pointer;}
.ct-item:not(.dragging){cursor:grab;}
.ct-item:active,
.ct-item.dragging{cursor:grabbing;}
.oi.sel{cursor:move;}
button:disabled,
.btn:disabled,
.bb:disabled,
.pbtn:disabled{cursor:not-allowed;}

/* ============================================================
 * R20-N6-5: 拖拽中坐标 floating chip(屏幕右下)
 * ============================================================
 * 12-events.js _showDragCoord 节流到 ~60Hz 更新 textContent.
 * pointer-events:none — 不阻挡画布交互;固定右下角避免与 #anim-panel 重叠.
 * .show 控制 opacity fade,与 reduce-motion 一致.
 * ============================================================ */
#drag-coord{
  position:fixed;
  right:18px;
  bottom:18px;
  z-index:var(--z-popup);
  background:rgba(0,0,0,.78);
  color:var(--t1);
  font-family:var(--ff-mono);font-variant-numeric:tabular-nums;
  font-size:var(--fs-sm);
  padding:6px 10px;
  border-radius:6px;
  border:1px solid var(--ac);
  pointer-events:none;
  opacity:0;
  transform:translateY(4px);
  transition:opacity 120ms ease, transform 120ms ease;
  box-shadow:0 4px 14px rgba(0,0,0,.45);
}
#drag-coord.show{opacity:1;transform:none;}

/* ============================================================
 * R20-N6-8: optimize 长任务进度卡(>300ms)
 * ============================================================
 * 11-optimize.js setTimeout 300ms 后挂 #opt-progress-card,完成时移除.
 * 浮在右下,非阻塞;有 spinner + 文本 + bar.
 * ============================================================ */
#opt-progress-card{
  position:fixed;
  right:18px;
  bottom:60px;
  z-index:var(--z-popup);
  background:var(--bg-elevated);
  border:1px solid var(--ac);
  border-radius:10px;
  padding:10px 14px;
  min-width:200px;
  box-shadow:0 8px 24px rgba(0,0,0,.55);
  font-size:var(--fs-sm);
  color:var(--t1);
  display:flex;
  flex-direction:column;
  gap:6px;
  opacity:0;
  transform:translateY(6px);
  animation:opcp-in 180ms cubic-bezier(.4,0,.2,1) forwards;
}
@keyframes opcp-in{to{opacity:1;transform:none;}}
#opt-progress-card .opcp-row{display:flex;align-items:center;gap:8px;}
#opt-progress-card .opcp-spin{
  width:14px;height:14px;border-radius:50%;
  border:2px solid var(--border-default);
  border-top-color:var(--ac);
  animation:opcp-spin 800ms linear infinite;
}
@keyframes opcp-spin{to{transform:rotate(360deg);}}
#opt-progress-card .opcp-bar{height:4px;background:var(--bg-input);border-radius:2px;overflow:hidden;}
#opt-progress-card .opcp-fill{height:100%;background:var(--ac);width:5%;transition:width 120ms ease;}

/* ============================================================
 * R20-N6-10: 顶栏全局 spinner — import / export / autoSave 时 1s 闪烁
 * ============================================================
 * 13-main.js _flashGlobalSpinner('reason') 触发,挂在 header(#save-ind 旁).
 * 1s 后自动 removeClass;reduce-motion 把 animation duration 压到 .01ms.
 * ============================================================ */
#global-spinner{
  display:inline-block;
  width:14px;height:14px;
  margin:0 6px;
  border-radius:50%;
  border:2px solid var(--border-default);
  border-top-color:var(--ac);
  vertical-align:middle;
  opacity:0;
  transition:opacity 120ms ease;
}
#global-spinner.show{
  opacity:1;
  animation:gspin 800ms linear infinite;
}
@keyframes gspin{to{transform:rotate(360deg);}}

/* ============================================================
 * R20-N6-18: confirm2 / modal 打开时背景 0 → .6 fade 200ms
 * ============================================================
 * #mbg.open.centered / .open.from-opt 既有 background:rgba(0,0,0,.45) 即时设.
 * 改为通过 CSS animation 在 200ms 内 fade 到 .6,初始为 0.让"打开"瞬间
 * 不再是硬切 — 与 #mo 内部 mo-slide-in (50ms) 形成层次感.
 * ============================================================ */
@keyframes mo-bg-fade{from{background:rgba(0,0,0,0);}to{background:rgba(0,0,0,.6);}}
#mbg.open.centered,
#mbg.open.from-opt{
  background:rgba(0,0,0,.6);
  animation:mo-bg-fade 200ms ease forwards;
}

/* ============================================================
 * DEVLOG-2026-05-27-glass-system-token-builders §1 T3:
 *   共享组件层 (R25-B 单一源码).
 *   .gpanel / .gbtn / .gmodal-shell — 所有面板与按钮的事实源.
 *   现存组件 (#lp / #rp / #mo / .btn / .bb / ...) 在 T5-T8 渐进迁移过来,
 *   迁移期内新旧选择器并存, 不立即删旧.
 * ============================================================ */

/* —— 玻璃面板基类 .gpanel —— 默认 tier-2, 三 modifier 切阶 —— */
.gpanel{
  background: var(--glass-2-bg);
  border: 1px solid var(--glass-2-border);
  border-radius: var(--r-lg);
  backdrop-filter: blur(var(--glass-2-blur)) saturate(110%);
  -webkit-backdrop-filter: blur(var(--glass-2-blur)) saturate(110%);
  box-shadow: var(--elev-3);
  /* 入场: subtle fade + lift, 与 motion library 联动 */
  animation: gpanel-enter var(--motion-modal) backwards;
}
.gpanel-1{
  background: var(--glass-1-bg);
  border-color: var(--glass-1-border);
  border-radius: var(--r-xl);
  backdrop-filter: blur(var(--glass-1-blur)) saturate(115%);
  -webkit-backdrop-filter: blur(var(--glass-1-blur)) saturate(115%);
  box-shadow: var(--elev-4);
}
.gpanel-2{
  background: var(--glass-2-bg);
  border-color: var(--glass-2-border);
  border-radius: var(--r-lg);
  backdrop-filter: blur(var(--glass-2-blur)) saturate(110%);
  -webkit-backdrop-filter: blur(var(--glass-2-blur)) saturate(110%);
  box-shadow: var(--elev-3);
}
.gpanel-3{
  background: var(--glass-3-bg);
  border-color: var(--glass-3-border);
  border-radius: var(--r-md);
  backdrop-filter: blur(var(--glass-3-blur)) saturate(108%);
  -webkit-backdrop-filter: blur(var(--glass-3-blur)) saturate(108%);
  box-shadow: var(--elev-2);
}
/* 顶栏 modifier — 不要圆角, 全宽 */
.gpanel-bar{
  border-radius: 0;
  border-left: 0;
  border-right: 0;
  border-top: 0;
  animation: none;     /* 顶栏不入场, 避免页面首屏跳动 */
}

/* 入场动画不再用 scale —— backdrop-filter + 动画 scale 会触发 Chromium 丢圆角裁剪
 * (用户报"按 Esc 时各 UI 圆角变方角")并每帧重算模糊(卡顿)。改为 opacity + 轻微 translateY,
 * 玻璃面板模糊快照只算一次,圆角恒定,既消方角闪也消卡顿。 */
@keyframes gpanel-enter{
  from{opacity:0; transform:translateY(6px);}
  to{opacity:1; transform:translateY(0);}
}

/* —— 玻璃按钮基类 .gbtn —— 默认 ghost (背景透明 hover 显), 四 modifier 切风格 —— */
.gbtn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  /* iter5: padding 收紧 6px 14px → 6px 11px, 单 btn 节省 6px, 8 tb 节省 48+px → 顶栏不再两行 */
  padding: 6px 11px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  /* DEVLOG-...-glass-system §1 T11 iter1: 默认 color 从 var(--t2) 调亮 — 浅毛玻璃背景下灰色读不清.
   * 78% 白让非 hover 也保持高对比度, hover 上 100% 给清晰反馈. */
  color: rgba(255, 255, 255, 0.78);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  font-family: inherit;
  letter-spacing: 0.2px;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  /* 过渡: 颜色/背景/border/shadow 用 fade, transform 用 spring */
  transition:
    background-color var(--motion-fade),
    border-color var(--motion-fade),
    color var(--motion-fade),
    box-shadow var(--motion-hover),
    transform var(--motion-hover);
  position: relative;
}
.gbtn:hover{
  background: var(--bg-hover);
  color: var(--t1);
  transform: translateY(-1px);
  box-shadow: var(--elev-1);
}
.gbtn:active{
  transform: translateY(0);
  box-shadow: none;
  transition-duration: 60ms;
}
.gbtn:focus-visible{
  outline: none;
  box-shadow: var(--focus-ring);
}
.gbtn:disabled,.gbtn.disabled{
  background: var(--bg-disabled);
  color: var(--t3);
  border-color: transparent;
  cursor: not-allowed;
  opacity: .5;
  transform: none;
  box-shadow: none;
}
.gbtn.loading{
  pointer-events: none;
  opacity: .8;
  color: transparent;     /* 文字让位给 spinner */
}
.gbtn.loading::after{
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 14px; height: 14px;
  border: 2px solid var(--t2);
  border-right-color: transparent;
  border-radius: 50%;
  animation: gbtn-spin .7s linear infinite;
}
@keyframes gbtn-spin{to{transform:rotate(360deg);}}

/* —— Modifier: --ghost (默认) / --accent / --danger / --success / --icon / --pill —— */
.gbtn--ghost{ /* alias 给默认态显式语义 */ }

.gbtn--accent{
  background: var(--grad-accent);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 4px 14px rgba(79,143,255,.35);
  font-weight: var(--fw-semibold);
}
.gbtn--accent:hover{
  background: var(--grad-accent);     /* 防被 .gbtn:hover 的 --bg-hover 覆盖 */
  color: #fff;
  filter: brightness(1.1);
  box-shadow: 0 6px 18px rgba(79,143,255,.5);
  transform: translateY(-1px);
}
.gbtn--accent:active{
  filter: brightness(0.95);
}

.gbtn--danger{
  background: var(--grad-danger);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 4px 14px rgba(239,68,68,.32);
  font-weight: var(--fw-semibold);
}
.gbtn--danger:hover{
  background: var(--grad-danger);
  color: #fff;
  filter: brightness(1.1);
  box-shadow: 0 6px 18px rgba(239,68,68,.5);
  transform: translateY(-1px);
}

.gbtn--success{
  background: var(--grad-success);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 4px 14px rgba(34,197,94,.32);
  font-weight: var(--fw-semibold);
}
.gbtn--success:hover{
  background: var(--grad-success);
  color: #fff;
  filter: brightness(1.08);
  transform: translateY(-1px);
}

/* 仅图标按钮 — 方形, 等宽等高 */
.gbtn--icon{
  width: 32px;
  height: 32px;
  padding: 0;
  font-size: var(--fs-icon-md);
}

/* 胶囊按钮 — 状态指示带 / toast / chip-style action */
.gbtn--pill{
  border-radius: var(--r-pill);
  padding: 5px 14px;
}

/* on 态 (toggled) — 比 :hover 更明显, 给 toolbar 切换按钮用 */
.gbtn.on{
  background: rgba(79,143,255,.14);
  color: var(--ac);
  border-color: rgba(79,143,255,.25);
  font-weight: var(--fw-semibold);
  box-shadow: 0 0 0 1px rgba(79,143,255,.18) inset;
}
.gbtn.on:hover{
  background: rgba(79,143,255,.20);
  color: var(--ac);
  border-color: rgba(79,143,255,.35);
}

/* —— Modal 外壳 .gmodal-shell —— 顶层 #mo / #opt-mo / 弹窗共用骨架 —— */
.gmodal-shell{
  /* 继承 .gpanel-1 视觉, 但 modal 默认带 padding + 出场动画 */
  background: var(--glass-1-bg);
  border: 1px solid var(--glass-1-border);
  border-radius: var(--r-xl);
  backdrop-filter: blur(var(--glass-1-blur)) saturate(115%);
  -webkit-backdrop-filter: blur(var(--glass-1-blur)) saturate(115%);
  box-shadow: var(--elev-4);
  padding: var(--space-5);
  position: relative;
  animation: gmodal-pop var(--motion-modal) var(--ease-spring) backwards;
}
@keyframes gmodal-pop{
  from{opacity:0; transform:translateY(8px);}
  to{opacity:1; transform:translateY(0);}
}

/* —— Button group 容器 — toolbar 内连续按钮的可视分组 ——
 * iter5: padding/background 收紧, 否则 header 在 1440 视口都换行 */
.gbtn-group{
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 0;
  border-radius: var(--r-sm);
  background: transparent;
  transition: background var(--motion-fade);
}

/* ============================================================
 * DEVLOG-2026-05-27-glass-system-token-builders §1 T9: 微交互层
 *   - 全局 :focus-visible accent ring (WCAG / R25-A 反黑箱)
 *   - 全局 ::-webkit-scrollbar 6px 渐变滑块
 *   - List stagger 入场 (lp/rp 内 ct-item 阶梯延迟)
 *   - boot-loader 玻璃化 (现仍在 HTML inline, 这里加共享 vars 即可)
 * ============================================================ */

/* —— 全局 focus-visible ring — 给所有可聚焦元素 (input/select/textarea/a 等) —— */
:where(input,select,textarea,a,[tabindex]):focus-visible{
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-sm);
}
/* 链接颜色不变, 仅加 ring */
a:focus-visible{ outline: 2px solid var(--ac); outline-offset: 2px; }

/* —— 全局 scrollbar (Chromium / WebKit) —— */
*::-webkit-scrollbar{ width: 8px; height: 8px; }
*::-webkit-scrollbar-track{ background: transparent; }
*::-webkit-scrollbar-thumb{
  background: linear-gradient(180deg, var(--glass-2-border), var(--glass-2-stroke));
  border-radius: var(--r-pill);
  border: 2px solid transparent;
  background-clip: padding-box;
  transition: background var(--motion-fade);
}
*::-webkit-scrollbar-thumb:hover{
  background: linear-gradient(180deg, var(--ac), rgba(79,143,255,0.6));
  background-clip: padding-box;
  border: 2px solid transparent;
}
*::-webkit-scrollbar-corner{ background: transparent; }
/* Firefox */
*{ scrollbar-color: var(--glass-2-border) transparent; scrollbar-width: thin; }

/* —— List stagger 入场: 列表项首次渲染时阶梯 fade-in (左栏 / 模板 / scene tabs) —— */
@keyframes list-item-stagger{
  from{ opacity: 0; transform: translateY(4px); }
  to{ opacity: 1; transform: translateY(0); }
}
/* 应用范围: ct-item (左栏卡片), tpl-card (模板卡片), scene-tab (场景标签) */
/* FIX(幽灵标签终极): 场景标签【移除入场动画】。它是"最新建的 tab"唯一与众不同之处 ——
   拖动时克隆体重放该动画、其 transform/opacity 让原位残留一个淡入的"背景框幽灵"。
   标签入场动画纯装饰、可舍弃;去掉后任何标签都不再有动画,幽灵无从产生(左栏卡片/模板卡仍保留)。 */
.ct-item, .tpl-popup .tpl-card{
  animation: list-item-stagger 220ms var(--ease-out-soft) backwards;
}
.scene-tab{ animation: none !important; }
/* 给前 12 项分配阶梯延迟; 12 项之后不延迟 (避免长列表满屏延迟感) */
.ct-item:nth-child(1),.tpl-popup .tpl-card:nth-child(1),.scene-tab:nth-child(1){ animation-delay: 0ms; }
.ct-item:nth-child(2),.tpl-popup .tpl-card:nth-child(2),.scene-tab:nth-child(2){ animation-delay: 22ms; }
.ct-item:nth-child(3),.tpl-popup .tpl-card:nth-child(3),.scene-tab:nth-child(3){ animation-delay: 44ms; }
.ct-item:nth-child(4),.tpl-popup .tpl-card:nth-child(4),.scene-tab:nth-child(4){ animation-delay: 66ms; }
.ct-item:nth-child(5),.tpl-popup .tpl-card:nth-child(5),.scene-tab:nth-child(5){ animation-delay: 88ms; }
.ct-item:nth-child(6),.tpl-popup .tpl-card:nth-child(6),.scene-tab:nth-child(6){ animation-delay: 110ms; }
.ct-item:nth-child(7),.tpl-popup .tpl-card:nth-child(7),.scene-tab:nth-child(7){ animation-delay: 130ms; }
.ct-item:nth-child(8),.tpl-popup .tpl-card:nth-child(8),.scene-tab:nth-child(8){ animation-delay: 150ms; }
.ct-item:nth-child(9),.tpl-popup .tpl-card:nth-child(9),.scene-tab:nth-child(9){ animation-delay: 168ms; }
.ct-item:nth-child(10),.tpl-popup .tpl-card:nth-child(10),.scene-tab:nth-child(10){ animation-delay: 184ms; }
.ct-item:nth-child(11),.tpl-popup .tpl-card:nth-child(11),.scene-tab:nth-child(11){ animation-delay: 198ms; }
.ct-item:nth-child(12),.tpl-popup .tpl-card:nth-child(12),.scene-tab:nth-child(12){ animation-delay: 210ms; }

/* —— scrollbar-fade animation (定义即可, 由其他规则间接触发) —— */
@keyframes scrollbar-fade{
  from{ opacity: 0; }
  to{ opacity: 1; }
}

/* ============================================================================
 * 深空霓虹 Cyber-Glass 设计系统 (BEAUTIFY_BLUEPRINT §2) — Wave 0 地基
 *   单一事实源:全局色彩/玻璃/霓虹辉光/运动/质感 token + 工具类 + @property + grain。
 *   追加在文件末尾 → cascade 最后生效,可扩展/微覆盖旧规则而不动 2000+ 行既有样式。
 *   用户决策(2026-05-29):"不用减少动态" → 特效默认全开,不做 prefers-reduced-motion 降级门
 *   (仅保留无 WebGL 防崩 + e2e 跳过开场);故本系统不含 reduce-motion 关闭分支。
 * ========================================================================== */
:root{
  /* —— 深空底 —— */
  --void:        #04060a;
  --deep:        #080b14;
  --abyss:       #0c1120;
  /* —— 玻璃面(深色 + rim light)—— */
  --glass-bg:    rgba(17,25,40,0.62);
  --glass-bg-2:  rgba(26,31,44,0.80);
  --glass-brd:   rgba(124,150,255,0.26);
  --glass-rim:   inset 0 1px 0 rgba(255,255,255,0.07);
  --glass-blur:  16px;
  /* —— 霓虹高光(电蓝 → 品紫 主轴)—— */
  --neon-cyan:   #00e5ff;
  --neon-blue:   #4f8fff;
  --neon-violet: #7c5cfc;
  --neon-magenta:#c44fff;
  --accent-grad:     linear-gradient(135deg, #4f8fff 0%, #7c5cfc 100%);
  --accent-grad-hot: linear-gradient(135deg, #00e5ff 0%, #7c5cfc 55%, #c44fff 100%);
  /* —— 多层辉光(focus/active/hover 统一)—— */
  --glow-soft: 0 0 5px rgba(79,143,255,0.55), 0 0 14px rgba(79,143,255,0.32), 0 0 30px rgba(124,92,252,0.22);
  --glow-hot:  0 0 6px rgba(0,229,255,0.7), 0 0 18px rgba(79,143,255,0.5), 0 0 42px rgba(124,92,252,0.4);
  /* —— 运动语言 —— */
  --ease-out-expo: cubic-bezier(0.16,1,0.3,1);
  --ease-spring:   cubic-bezier(0.34,1.56,0.64,1);
  --ease-smooth:   cubic-bezier(0.22,1,0.36,1);
  --t-micro:140ms; --t-ui:300ms; --t-cine:720ms;
  /* —— 高杠杆:把既有 accent token 轻微上移到霓虹主轴,旧组件无需逐个改即获统一电蓝-品紫 —— */
  --ac:  #4f8fff;
  --ac2: #7c5cfc;
}

/* 可动画自定义属性(霓虹流光描边 / 呼吸 hue) */
@property --fx-angle{ syntax:'<angle>'; inherits:false; initial-value:0deg; }
@property --fx-hue{ syntax:'<number>'; inherits:false; initial-value:215; }

/* —— 全局质感层:颗粒/噪声,压在 canvas 上、UI 下(#fx-grain 在 <body> 顶) —— */
#fx-grain{
  position:fixed; inset:0; z-index:2; pointer-events:none;
  display:none; /* 关闭:用户反馈满屏噪点;静态颗粒一并去掉,保持干净。display:none 彻底移出合成/渲染树,避免常驻全屏 mix-blend 合成层 */
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:200px 200px;
}

/* —— 玻璃工具类(新元素直接用;Wave 1 也可把 .gpanel 对齐到此)—— */
.glass{
  background:var(--glass-bg);
  -webkit-backdrop-filter:blur(var(--glass-blur)) saturate(160%);
  backdrop-filter:blur(var(--glass-blur)) saturate(160%);
  border:1px solid var(--glass-brd);
  box-shadow:var(--glass-rim), 0 18px 48px rgba(0,0,0,0.55);
}
.glass--solid{ background:var(--glass-bg-2); }

/* —— 霓虹辉光工具类 —— */
.glow-neon{ box-shadow:var(--glow-soft); }
.glow-neon--hot{ box-shadow:var(--glow-hot); }

/* —— 旋转 conic 霓虹描边(@property 驱动);用法:元素加 .neon-border 作 ::before —— */
.neon-border{ position:relative; }
.neon-border::before{
  content:""; position:absolute; inset:-1px; border-radius:inherit; padding:1px;
  background:conic-gradient(from var(--fx-angle), var(--neon-cyan), var(--neon-violet), var(--neon-magenta), var(--neon-cyan));
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite:exclude; pointer-events:none; opacity:0; transition:opacity var(--t-ui) var(--ease-smooth);
  animation:fx-conic-spin 6s linear infinite;
}
.neon-border:hover::before, .neon-border.is-active::before{ opacity:1; }

@keyframes fx-conic-spin{ to{ --fx-angle:360deg; } }
@keyframes fx-glow-pulse{
  0%,100%{ box-shadow:0 0 4px rgba(79,143,255,0.0), 0 0 10px rgba(124,92,252,0.0); }
  50%{ box-shadow:var(--glow-soft); }
}

/* —— 文本霓虹:解码中 / HUD 高亮 —— */
.fx-neon-text{ color:var(--neon-blue); text-shadow:0 0 8px rgba(79,143,255,0.6); }
.fx-dud{ color:var(--neon-cyan); text-shadow:0 0 8px rgba(0,229,255,0.7); opacity:0.85; }

#lp-mobile-handle{display:none;}
@media (max-width:800px){
  #lp{
    display:flex !important;
    top:0;
    bottom:0;
    left:0;
    width:min(360px,calc(100vw - 44px));
    min-width:0;
    max-width:calc(100vw - 44px);
    transform:translateX(-100%);
    pointer-events:none;
    transition:transform var(--motion-fast) ease;
    z-index:var(--z-raised);
  }
  #lp.lp-mobile-open{transform:translateX(0);pointer-events:auto;z-index:calc(var(--z-sticky) + 1);}
  #mbg.open{inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.6);pointer-events:auto;}
  #mbg.open #mo{position:relative;z-index:1;width:min(380px,calc(100vw - 32px));max-width:calc(100vw - 32px);}
  #lp-mobile-handle{
    position:fixed;
    left:0;
    top:50%;
    width:44px;
    min-height:44px;
    height:88px;
    display:flex;
    align-items:center;
    justify-content:center;
    transform:translateY(-50%);
    border:1px solid rgba(124,150,255,.6);
    border-left:0;
    border-radius:0 10px 10px 0;
    background:rgba(28,36,58,.98);
    color:#cdd9ff;
    font-size:24px;
    cursor:pointer;
    z-index:var(--z-raised);
  }
  body.lp-mobile-drawer-open #lp-mobile-handle{display:none;}
  #pcol-reopen-left{display:none !important;}
  #pcol-btn-lp{width:44px;min-width:44px;height:44px;border-radius:0 9px 9px 0;}
}

.fv-scene-dock{
  position:fixed;
  top:16px;
  right:calc(var(--rp-width,300px) + 24px);
  z-index:calc(var(--z-sticky) + 1);
  width:min(340px,calc(100% - 32px));
  max-height:calc(100vh - 32px);
  overflow:auto;
  padding:12px;
  border:1px solid rgba(86,150,255,.55);
  border-radius:12px;
  background:rgba(9,15,29,.92);
  box-shadow:0 14px 38px rgba(0,0,0,.48),0 0 20px rgba(65,133,255,.12);
  -webkit-backdrop-filter:blur(12px);
  backdrop-filter:blur(12px);
  color:var(--t1,#eef4ff);
  pointer-events:auto;
}
.fv-scene-head,.fv-scene-actions,.fv-scene-stats{display:flex;align-items:center;gap:8px;}
.fv-scene-head{justify-content:space-between;margin-bottom:8px;}
.fv-scene-head strong{font-size:13px;}
.fv-scene-live{margin-left:8px;color:var(--ok,#35d49a);font-size:11px;}
.fv-scene-close{
  width:28px;height:28px;padding:0;border:0;border-radius:7px;
  background:rgba(255,255,255,.07);color:var(--t2,#c7d2e8);font-size:20px;line-height:1;cursor:pointer;
}
.fv-scene-close:hover{background:rgba(255,255,255,.14);color:#fff;}
.fv-scene-stats{flex-wrap:wrap;color:var(--t3,#91a0bd);font-size:11px;margin-bottom:8px;}
.fv-scene-stats b{color:var(--t1,#eef4ff);}
.fv-scene-actions .gbtn{min-height:30px;padding:4px 12px;}
.fv-query-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin-top:7px;}
.fv-query-actions .gbtn{min-height:34px;padding:4px 6px;white-space:normal;line-height:1.25;}
.fv-scene-hint{margin-top:7px;color:var(--t3,#91a0bd);font-size:11px;line-height:1.55;}
.fv-region-picker{display:block;margin-top:9px;color:var(--t3,#91a0bd);font-size:11px;}
.fv-region-picker span{display:block;margin-bottom:4px;}
.fv-region-picker select{width:100%;min-height:32px;padding:5px 8px;border:1px solid var(--border-default,rgba(255,255,255,.12));border-radius:7px;background:var(--bg-input,#111a2d);color:var(--t1,#eef4ff);}
.fv-region-detail{margin-top:8px;padding:9px;border:1px solid rgba(86,150,255,.3);border-radius:8px;background:rgba(86,150,255,.07);font-size:11px;}
.fv-region-detail.is-empty{color:var(--t3,#91a0bd);line-height:1.55;}
.fv-region-detail.is-over{border-color:rgba(214,63,54,.75);background:rgba(214,63,54,.12);}
.fv-detail-title{margin-bottom:7px;color:var(--t1,#eef4ff);font-weight:600;overflow-wrap:anywhere;}
.fv-detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;}
.fv-detail-grid span{display:flex;flex-direction:column;gap:2px;color:var(--t3,#91a0bd);}
.fv-detail-grid b{color:var(--t1,#eef4ff);font-weight:600;}
.fv-detail-grid small{display:block;margin-top:1px;color:var(--t3,#91a0bd);font-size:10px;font-weight:400;}
.fv-limit-source{margin:8px 0 6px;padding-top:7px;border-top:1px solid rgba(255,255,255,.1);color:var(--t2,#c7d2e8);}
.fv-ground-na{display:flex;flex-direction:column;gap:3px;margin-top:8px;padding:7px;border-radius:6px;background:rgba(118,123,134,.14);color:var(--t3,#91a0bd);}
.fv-ground-na b{color:var(--t2,#c7d2e8);}
.fv-face-patches{display:flex;flex-direction:column;gap:6px;margin-top:8px;}
.fv-face-patch{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px 8px;padding:6px;border:1px solid rgba(255,255,255,.09);border-radius:6px;color:var(--t3,#91a0bd);}
.fv-face-patch b{overflow-wrap:anywhere;color:var(--t1,#eef4ff);}
.fv-face-patch em{grid-column:1/-1;color:var(--ok,#35d49a);font-style:normal;}
.fv-face-patch.is-over{border-color:rgba(214,63,54,.55);background:rgba(214,63,54,.09);}
.fv-face-patch.is-over em{color:var(--er,#ff6b6b);}
.fv-detail-status{margin-top:7px;color:var(--ok,#35d49a);font-weight:600;}
.fv-region-detail.is-over .fv-detail-status{color:var(--er,#ff6b6b);}
@media (max-width:800px){
  .fv-scene-dock{
    top:10px;left:50%;right:auto;transform:translateX(-50%);
    width:min(310px,calc(100% - 20px));padding:10px;
  }
}
