/* ============================================================
 * WebSurf-viewer — 入口页样式（单文件）。
 *
 * 主题「导演工作台」（2026-09-29 v3，向 game 的菱角语彙靠拢但**配色自立**）：
 * 配色取导演版语彙——中性灰阶为主体，黑白条（场记板/遮幅）为结构装饰，
 * 调色卡条（黑/灰/白/时间码黄四段）为媒体标识，时间码黄为唯一彩色强调
 * （时间码数字 / pane 编号 / 嵌片 / 调色卡末段），REC 红仅用于错误——
 * 不沿用 game 的琥珀色板。--warn 与 --accent 同值属刻意（时间码黄兼任
 * 片场警示惯例色，note-warn 的黄边黄字读作「片场警示」而非「强调」）。
 * 三工程区分：apps/debug 石墨青「仪表」、apps/game 深青「莱茵精工」、
 * apps/viewer 中性灰阶「导演工作台」。
 *   · 菱角分明：全文件 border-radius 一律 0，仅服务孔（1 处，位于侧栏左下）
 *     与螺钉保留 50% 圆；轨迹色点 .track-dot / .meta-dot 方形化；
 *   · 切角系统：**已撤销**（owner 2026-10-16：右上切角"莫名其妙"，与 game 同口径）。
 *     最后两处（帮助浮层 `#help`、致命错误卡 `#fatal .card`）本轮一并删除；
 *     侧栏与主按钮更早前已是矩形。菱角语彙改由「零圆角 + 1px/2px 直线」表达。
 *   · 线宽系统：2px 结构线（HUD 铭牌左缘）/ 1px 发丝分隔 / 1px 点线
 *     （小节标题右端 3px 短竖刻度，同 game h4）；
 *   · 档案元素成套：两个 tabpane 档案编号（右上，VW-01 / VW-02，时间码黄）+
 *     页脚署名（右下）；硬件件计数：服务孔 1（侧栏左下）+ 引导卡铰链片的 3 颗螺钉
 *     （上片 1 + 下片 2，见下方引导卡段）——viewer 为全屏画布 + 浮层布局，硬件件按「每浮层
 *     至多 1 件」适配，不套用 game 单窗口的 ≥2 口径；
 *   · **导演版条纹特色（owner 指定保留并强化，v3 扩展）**：时间轴上下缘
 *     胶片齿孔条（硬停重复条纹）、引导卡顶部场记板**黑白斜纹**顶条（上下两根拍板条各走
 *     135°/45° 镜像斜纹、逐条等宽，硬停色块；见下方引导卡段），
 *     拖拽态上下缘黑白遮幅条、录像信息条左端**调色卡条**（黑/灰/白/
 *     时间码黄四段）、每个小节标题左端**调色卡竖条**（黑/白/黄三段）、
 *     （激活 = 场记板白底黑字）；
 *   · **影视语彙泛化（v4/v6）**：播放中时间码前缀 REC 红点、时间轴底部秒刻度尺、白竖线播放头、SMPTE 彩条、速度电平表、场记板黑白标签。
 *   · **引导卡「Scene Slate」（owner 2026-10-16 定版）**：直角铰链片（内联 SVG 画形状：四个 10px 硬切角 + 右上 45° 直斜边，
 *     1px 描边随轮廓走）压住上下两根拍板条的左端 + RGB/CMYK 色标条（7 格逐格等宽）+
 *     标题行（大字 WebSurf / SCENE SLATE + PRODUCTION / VERSION 两格印字）+
 *     四格（STEP 01~03 引导 + ACTIONS 两枚按钮）+ 底部 FORMAT 行（BSP / REC / REPLAY / DEM）。
 *     拍板条 = 上片抬起 −10°、两组 45° 斜纹方向镜像（硬停色块，无受光高光 / 无材质渐变）。
 *     零圆角，与全站口径一致）；色标条的 6 个彩色只在卡内用，
 *     不进 :root。它整块替换了上一版引导卡（那版是人字黑白条 + 表格化板身）—— 相关类名
 *     （`slate-stick` / `slate-stripes` / `slate-screws` / `slate-row` / `gstep` / `glabel` /
 *     `gvalue` / `gtext` / `gbtns`）在本文件与 index.html 里均已 0 命中。
 *
 * 与 JS 的接线（本文件只负责外观；显隐与取值由类名驱动）：
 *   · 可见性钩子：.hidden、#help.hidden、#guideError.show、#dropzone.active、
 *     #fatal.show、.tabpane.active、.tab.active、.session-pane.active、#dock.full、.telemetry.full、
 *     .track-btn.off/.active/.danger、.track-off.invalid、.field-input.invalid、
 *     .btn.busy、.filebtn.busy（首访卡两枚按钮）、.tl-range.active；
 *   · .track-dot 与 .meta-dot 的底色、.tl-zone-run / .tl-zone-ab / .tl-zone-active / .tl-zone-hl 的
 *     left/width 由 apps/viewer/src 运行时写行内样式，这里只给静态外观。
 * ============================================================ */

:root {
  color-scheme: dark;

  /* ── 「导演工作台」令牌（中性灰阶主体 + 时间码黄单强调） ── */
  --bg: #101012;                          /* 影调黑（监视器黑，中性不偏色） */
  --panel: #161619;                       /* 面板炭灰 */
  --panel-trans: rgba(22, 22, 25, 0.92);
  --overlay: rgba(8, 8, 10, 0.66);
  --border: #333338;                      /* 1px 发丝线 */
  --border-strong: #56565c;               /* 分隔线 */
  --text: #e8e8e4;                        /* 中性白（场记板白） */
  --muted: #a2a2a4;                       /* 中性灰 */
  --accent: #e6c35c;                      /* 时间码黄：唯一彩色强调 */
  --accent-hover: #f0d47e;
  --accent-weak: rgba(230, 195, 92, 0.12);
  --on-accent: #1a1505;
  --ok: #8cc98f;                         /* 预留令牌：本工程当前无 success 态消费点 */
  /* --accent-hover / --on-accent 为预留令牌：主按钮走场记板白底黑字（--strip-*），无 hover/前景消费点 */
  --err: #e05548;                         /* REC 红：仅错误/危险 */
  --warn: #e0955c;                       /* 警示橙：与时间码黄分离 */                       /* 刻意与 --accent 同值：时间码黄兼任片场警示色 */

  /* 黑白条（场记板 / 遮幅）：亮段 = 场记板白，暗段 = 影调黑 */
  --strip-light: #e8e8e4;
  --strip-dark: #101012;

  --sidebar-w: 348px;
  --mono: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
}
@media (max-width: 1000px) { :root { --sidebar-w: 320px; } }

/* ── 基底 ─────────────────────────────────────────────── */
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body {
  font-family: system-ui, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
  font-size: 13px; color: var(--text); background: var(--bg);
}
::selection { background: rgba(232, 232, 228, 0.12); }
:focus-visible { outline: 1px solid var(--text); outline-offset: 1px; }
#app { position: fixed; inset: 0; }
#game { display: block; width: 100%; height: 100%; background: var(--bg); cursor: crosshair; }
.hidden { display: none !important; }
kbd {
  font-family: inherit; font-size: 11px; color: var(--muted);
  border: 1px solid var(--border-strong);
  padding: 0 4px; margin: 0 1px;
}
code { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

/* ── HUD #hud（左上角）：三行只读状态铭牌（#pose / #bspStatus / #replayStatus）；
   左缘 2px 白结构线（灰阶区不用彩）；容器不挡点击；
   后两行内容为空时由 :empty 隐藏 ── */
#hud {
  position: absolute; top: 12px; left: 12px; z-index: 5;
  padding: 6px 10px 6px 9px;
  background: var(--panel-trans); border: 1px solid var(--border);
  border-left: 2px solid var(--text);
  pointer-events: none; user-select: none; max-width: 620px;
}
.hud-line { line-height: 1.7; font-size: 11.5px; }
#pose { font-family: var(--mono); font-size: 11px; color: var(--text); font-variant-numeric: tabular-nums; }
#bspStatus, #replayStatus { color: var(--muted); }
#bspStatus:empty, #replayStatus:empty { display: none; }

/* ── 右上角工具条 #topbar：帮助与面板两个 .iconbtn；两个隐藏文件框（#bspFile 地图、
      #importFile 引导层的记录 / 录像导入，去向由内容判定）都挂在这里 ── */
#topbar {
  position: absolute; top: 12px; right: 12px; z-index: 6;
  display: flex; align-items: center; gap: 6px;
}
#bspFile, #importFile { display: none; }
.filebtn, .iconbtn, .btn {
  font-family: inherit; font-size: 12px; color: var(--text); padding: 4px 11px;
  background: transparent; border: 1px solid var(--border-strong);
  cursor: pointer; white-space: nowrap;
}
.filebtn:hover, .iconbtn:hover, .btn:hover {
  background: rgba(232, 232, 228, 0.06);
  box-shadow: inset 2px 0 0 var(--border-strong);
}
.filebtn.busy, .btn.busy, .btn:disabled { opacity: 0.45; pointer-events: none; }
.btn.active, .iconbtn.active {
  background: rgba(232, 232, 228, 0.08); border-color: var(--text); color: var(--text);
  box-shadow: inset 2px 0 0 var(--text);
}
#topbar .iconbtn:first-child { font-weight: 700; }

/* ── 帮助浮层 #help（非模态）：完整矩形 + 单层发丝压槽（原右上 10px 切角已按
   owner 2026-10-16「莫名其妙」的裁决撤销，与 game 同口径）；
   点击 #helpClose 或按 Esc 关闭，均由 apps/viewer/src/ui/hud.ts 处理 ── */
#help {
  position: absolute; top: 46px; right: 12px; z-index: 7;
  width: 400px; max-width: calc(100vw - 24px); max-height: calc(100% - 62px);
  overflow-y: auto; overscroll-behavior: contain;
  padding: 10px 12px;
  background: var(--panel); border: 1px solid var(--border-strong);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 6px 20px rgba(0, 0, 0, 0.35);
  font-size: 12px; line-height: 1.7;
  /* 滚动条自绘见文件尾 webkit 块（勿设标准 scrollbar-width，Chromium 121+ 会抢占） */
}
#help .help-title {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 12px; font-weight: 600; letter-spacing: 0.06em;
  padding-bottom: 5px; margin-bottom: 5px; border-bottom: 1px solid var(--border-strong);
}
#help .help-close {
  cursor: pointer; color: var(--muted); font-size: 15px; line-height: 1;
  padding: 1px 6px;
}
#help .help-close:hover { color: var(--text); background: rgba(232, 232, 228, 0.06); }
#help .help-sec { margin: 7px 0; }
#help .help-h { font-weight: 600; margin-bottom: 2px; }
#help p { color: var(--muted); }

/* ── 侧栏面板 #sidebar（右侧，宽 --sidebar-w）：**矩形无切角** + 顶缘
   时间码黄嵌片；#tabs 三个标签按钮（地图 / 录像 / 演示）+ .tabpane（带 active 的那个显示）──
   切角已按 owner 要求去掉：这种方正的矩形观感更好，装饰性的切角元素不再需要。
   服务孔保留（它不与任何切角成对，只作件计数）。 */
#sidebar {
  position: absolute; top: 56px; right: 12px; bottom: 12px; z-index: 6;
  width: var(--sidebar-w); display: flex; flex-direction: column;
  background: var(--panel-trans); border: 1px solid var(--border-strong);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  overflow: hidden;
}
/* 顶缘嵌片（**SMPTE 七色条**，与 `#dropzone` / 场记板同一套影视测试信号语彙）：
   本工程的彩色只出现在这类**细嵌片**上 —— 一行像素宽、硬停分段，不铺面、不做渐变过渡，
   所以它读起来是"校准条"而不是装饰色块。 */
#sidebar::before {
  content: ""; position: absolute; top: 0; left: 18px;
  width: 56px; height: 3px; z-index: 1;
  background: linear-gradient(90deg,
    #c8c8c8 0 14.28%, #e6c35c 14.28% 28.56%, #4fb3c4 28.56% 42.84%, #58c25c 42.84% 57.12%,
    #b06fc4 57.12% 71.4%, #d9564a 71.4% 85.68%, #4a6fd9 85.68% 100%);
}
/* 服务孔（侧栏左下 5px 圆；件计数之一） */
#sidebar::after {
  content: ""; position: absolute; bottom: 13px; left: 12px;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--bg); border: 1px solid var(--border-strong);
  z-index: 1; pointer-events: none;
}
#tabs { display: flex; gap: 8px; padding: 10px 12px; }
/* 标签 = 状态标签条形态（同 game 标签条：无圆角 1px 边；激活 = 场记板白底黑字） */
.tab {
  font-family: inherit; font-size: 12.5px; color: var(--muted);
  padding: 5px 14px; background: none;
  border: 1px solid var(--border-strong);
  cursor: pointer;
}
.tab:hover { color: var(--text); border-color: var(--text); }
.tab.active {
  color: var(--strip-dark); background: var(--strip-light);
  border-color: var(--strip-light);
}
/* 激活 tab 的聚焦指示：白底上外描边不显眼，改内嵌 1px 暗线（与全站「不外加描边」一致） */
.tab.active:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 1px var(--border-strong);
}
.tabpane {
  position: relative;
  display: none; flex: 1; overflow-y: auto; overscroll-behavior: contain;
  padding: 10px 12px 14px;
  /* 槽位只给会滚动的 pane：地图 pane 内容短，常驻槽位是一根不对称死条（owner 实测右侧偏宽）；
     会滚动的 #pane-replay / #pane-demo 在下方单独保留槽位。自绘滚动条见文件尾 webkit 块。 */
}
#pane-replay, #pane-demo { scrollbar-gutter: stable; }
.tabpane.active { display: flex; flex-direction: column; }

/* 档案编号（tabpane 右上，时间码黄等宽 12px）与页脚署名（右下，中性；
   margin-top:auto 在内容不足时收尾填补空白）——编号/署名不占文档流 */
#pane-map::before { content: "VW-01"; }
#pane-replay::before { content: "VW-02"; }
#pane-demo::before { content: "VW-03"; }
.tabpane::before {
  position: absolute; top: 8px; right: 12px;
  font-family: var(--mono); font-size: 12px; color: var(--muted);
  letter-spacing: 0.04em;
}
.tabpane::after {
  content: ""; display: block; margin-top: auto; padding-top: 10px;
  border-top: 1px solid var(--border);
  text-align: right; font-family: var(--mono); font-size: 11px;
  color: var(--muted); letter-spacing: 0.04em;
}
#pane-map::after { content: "地图面板 · v0.1"; }
#pane-replay::after { content: "记录面板 · v0.1"; }
#pane-demo::after { content: "录像面板 · v0.1"; }

/* ── .map-reload：地图页顶部的「更换地图」行（label.filebtn，点击转发到 #bspFile；构造点见 apps/viewer/src/ui/mapinfo.ts）── */
.map-reload {
  display: block; text-align: center; margin-bottom: 10px;
  background: var(--strip-light); border-color: var(--strip-light); color: var(--strip-dark);
  font-weight: 600;
}
.map-reload:hover { background: #ffffff; }

/* ── 面板通用元件：.sec 分段（.sec-title = 小节刻度尺：12px/600 + 0.06em +
   下分隔线 + 右端 3px 短竖刻度，同 game h4）、.field 行、.note 提示条、
   .mono 等宽块、.kv 键值行 ── */
.sec { margin-bottom: 12px; }
.sec-title {
  position: relative;
  font-size: 12px; font-weight: 600; letter-spacing: 0.06em;
  padding: 2px 0 5px 14px; border-bottom: 1px solid var(--border-strong); margin-bottom: 8px;
}
/* 左端调色卡竖条（影调黑/白/时间码黄三段，调色盘语彙扩展到每个小节；
   右端仍为 3px 短竖刻度） */
/* 分区标记条：3×15px 竖嵌片，**硬停三段取 RGB 三原色**（红 / 绿 / 蓝）。
   竖条只有 3px 宽，所以彩色在这里是"刻度"而不是"色块"—— 与嵌片语彙一致。
   **取色与三块面板的编号标签同一套**（`#pane-map` 红 / `#pane-replay` 绿 / `#pane-demo` 蓝）：
   全界面的彩色收成两套原色 —— 屏幕侧用 RGB（加色）、回放信息条用 CMYK（减色），
   不再有第三种随手配的色组。（这里早先是黄/青/珊瑚三色，既非 RGB 也非 CMYK，
   在本体系里是孤立的第三套，owner 指出后已并入 RGB。） */
.sec-title::before {
  content: ""; position: absolute; left: 0; top: 3px;
  width: 3px; height: 15px;
  background: linear-gradient(180deg,
    #e5484d 0 33.3%, #46c05a 33.3% 66.6%, #4a7fe0 66.6% 100%);
}
.sec-title::after {
  content: ""; position: absolute; right: 0; bottom: -3px;
  width: 1px; height: 3px; background: var(--border-strong);
}
.sec-body { display: flex; flex-direction: column; gap: 6px; }
.field { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.field-label { color: var(--muted); min-width: 76px; flex: 0 0 auto; }
.field-input {
  flex: 1; min-width: 0; font-family: inherit; font-size: 12px; color: var(--text);
  background: var(--bg); border: 1px solid var(--border);
  padding: 3px 6px;
}
.field-input:focus { outline: none; border-color: var(--text); }
.field-input.invalid { border-color: var(--err); }
/* select.field-select（mapinfo.ts 构造）：自绘形态对齐 select.tl-select——
   细线框 + muted 色 data-URI 刻度箭头 + 聚焦琥珀边 */
select.field-select {
  font-family: inherit; font-size: 12px; color: var(--text);
  background-color: var(--panel);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23a2a2a4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center;
  border: 1px solid var(--border); padding: 3px 22px 3px 6px; cursor: pointer;
  appearance: none; -webkit-appearance: none;
}
select.field-select:focus { border-color: var(--text); }
.field-num { max-width: 96px; flex: 0 0 auto; }
.field-check { gap: 6px; cursor: pointer; }
.field-check .field-label { min-width: 0; }
.field-check input {
  cursor: pointer; appearance: none; -webkit-appearance: none;
  width: 14px; height: 14px;
  background: var(--bg); border: 1px solid var(--border-strong);
}
.field-check input:checked {
  border-color: var(--text);
  box-shadow: inset 0 0 0 1px var(--text);
}
.field-check input:hover { border-color: var(--text); }
.btn-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
.btn.small { padding: 3px 8px; font-size: 11.5px; }

.note { font-size: 11.5px; line-height: 1.6; padding: 5px 8px; word-break: break-all; }
.note-info { color: var(--muted); border: 1px solid var(--border); }
.note-warn { color: var(--warn); background: rgba(224, 149, 92, 0.1); border: 1px solid var(--warn); }
.note-error { color: var(--err); background: rgba(224, 85, 72, 0.1); border: 1px solid var(--err); }

.mono { font-family: var(--mono); font-size: 11px; line-height: 1.55; }
.kv { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; line-height: 1.7; }
.kv .k { color: var(--muted); flex: 0 0 auto; }
.kv .v { color: var(--text); text-align: right; word-break: break-all; }

/* ── 可折叠分组 .fold：details/summary 结构（apps/viewer/src/core/dom.ts 构造 fold-title/fold-name/fold-body）── */
.fold {
  border: 1px solid var(--border);
  margin-bottom: 12px;
}
.fold-title {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; cursor: pointer; user-select: none;
  list-style: none;
}
.fold-title::-webkit-details-marker { display: none; }
.fold-title::marker { content: ''; }
.fold-title::before {
  content: '▸'; flex: 0 0 auto; color: var(--muted); font-size: 10px;
}
.fold[open] > .fold-title::before { content: '▾'; }
.fold[open] > .fold-title { color: var(--text); }
.fold-name { font-size: 12.5px; font-weight: 600; }
.fold-body { padding: 2px 10px 8px; }

/* 轨迹列表 .track-list / .track-row：每条轨道一行，卡内两行 .track-line（构造点见 apps/viewer/src/replay/trackpanel.ts） */
.track-list { display: flex; flex-direction: column; gap: 5px; }
.track-row {
  display: flex; flex-direction: column; gap: 3px;
  padding: 5px 8px;
  border: 1px solid var(--border);
}
.track-line { display: flex; align-items: center; gap: 5px; min-width: 0; }
/* .track-line-ops（trackpanel.ts 的操作行变体）走本基础规则，无独立规则 */
.track-name {
  flex: 1; min-width: 0; font-family: inherit; font-size: 12px; color: var(--text);
  background: transparent; border: 1px solid transparent; padding: 1px 4px;
}
.track-name:hover { border-color: var(--border); }
.track-name:focus { outline: none; border-color: var(--text); }
.track-meta { font-size: 10.5px; color: var(--muted); flex: 0 0 auto; }
.track-off-label { font-size: 11px; color: var(--muted); flex: 0 0 auto; }
/* 轨迹调色板（trackpanel.ts 的 type=color）：16×16 细线方框，色板原生弹层 */
.track-color {
  width: 16px; height: 16px; flex: 0 0 auto; padding: 0;
  background: var(--bg); border: 1px solid var(--border-strong); cursor: pointer;
}
.track-color::-webkit-color-swatch-wrapper { padding: 1px; }
.track-color::-webkit-color-swatch { border: none; }
.track-off {
  width: 58px; flex: 0 0 auto; font-family: inherit; font-size: 11.5px; color: var(--text);
  background: var(--bg); border: 1px solid var(--border);
  padding: 1px 4px;
}
.track-off:focus { outline: none; border-color: var(--text); }
.track-off.invalid { border-color: var(--err); }
.track-off-unit { font-size: 10.5px; color: var(--muted); flex: 0 0 auto; margin-right: 2px; }
.track-btn {
  flex: 0 0 auto; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
  font-family: inherit; font-size: 12px; line-height: 1; color: var(--muted);
  background: transparent; border: 1px solid var(--border);
  padding: 0; cursor: pointer;
}
.track-btn:hover { color: var(--text); background: rgba(232, 232, 228, 0.06); }
.track-btn.off { opacity: 0.45; }
.track-btn.active { color: var(--text); border-color: var(--text); background: rgba(232, 232, 228, 0.08); }
.track-btn.danger:hover { color: var(--err); border-color: var(--err); background: rgba(224, 85, 72, 0.12); }

/* 出生点列表 .spawn-list / .spawn-item（单行：类名 .cls + 跳转按钮；推荐项加 .primary，构造点见 apps/viewer/src/ui/mapinfo.ts） */
.spawn-list { display: flex; flex-direction: column; gap: 4px; max-height: 260px; overflow-y: auto; }
.spawn-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 11.5px; padding: 3px 8px;
  border: 1px solid transparent;
}
.spawn-item:hover { background: rgba(232, 232, 228, 0.06); }
.spawn-item.primary { border-color: var(--text); }
.spawn-item .cls { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spawn-item button {
  flex: 0 0 auto; font-family: inherit; font-size: 11px; color: var(--text); padding: 1px 8px;
  background: transparent; border: 1px solid var(--border-strong);
  cursor: pointer;
}
.spawn-item button:hover { background: rgba(232, 232, 228, 0.06); }

/* ── 底部 dock #dock：两个回放会话各占一层（`.session-pane`），同一时刻只有 `.active` 那层可见。
   右边界是 calc(--sidebar-w + 24px)，面板收起时加 .full 回到 12px；
   容器不挡点击、层内直属元素恢复；两条芯片带浮层软投影。
   两层各自的播放状态互不相干（见 apps/viewer/src/replay/session.ts）── */
#dock {
  position: absolute; left: 12px; right: calc(var(--sidebar-w) + 24px); bottom: 12px; z-index: 6;
  pointer-events: none;
}
#dock.full { right: 12px; }
.session-pane { display: none; flex-direction: column; gap: 6px; pointer-events: none; }
.session-pane.active { display: flex; }
.session-pane > * { pointer-events: auto; }

/* 信息条（底部 dock 上层，**两条链路各一条**）：记录条 `#replayMeta` 放 .replay 头部元信息
   （apps/viewer/src/ui/replaymeta.ts 按 .meta-name / .mi / .mk / .mv 组装，成绩一项加 .headline）；
   录像条 `#demoInfo` 放 .dem 独有事实（apps/viewer/src/ui/demometa.ts 同一套类名契约 —— 服务器 /
   天空盒 / 协议 / 事件 / 实体流 / 字符串表 / 包走读）。两条**各挂自己会话的容器**，互不写对方。
   左端调色卡条（::before：黑/灰/白/时间码黄四段，clapper 色卡语彙）为两条共用。 */
.info-strip {
  position: relative;
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 18px;
  padding: 5px 10px 5px 14px;
  background: var(--panel-trans); border: 1px solid var(--border);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
.info-strip::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 5px;
  /* **四原色 · 印刷（CMYK）**：青 / 品红 / 黄 / 黑，硬停四段。
     与三块面板的**RGB 三原色**（屏幕）刻意区分：这里是"印出来的一册"，那边是"屏幕上的三块"。
     两套减色/加色原色在同一界面上对位出现，是本面板的识记点。 */
  background: linear-gradient(180deg,
    #00b7c2 0 25%, #e0218a 25% 50%, #f2e14c 50% 75%, #131316 75% 100%);
}
.meta-name {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.meta-dot { width: 9px; height: 9px; flex: 0 0 auto; }
.mi { display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap; }
/* .mk 字段名 = 场记板标签栏（黑底白字小标签，Scene/Take 栏语彙） */
.mk {
  font-size: 10px; color: var(--strip-light);
  background: var(--strip-dark); padding: 1px 5px; letter-spacing: 0.06em;
}
.mv { font-family: var(--mono); font-size: 11.5px; font-variant-numeric: tabular-nums; }
.mi.headline .mv { color: var(--accent); font-size: 14px; font-weight: 600; }

/* ── 时间轴 .timeline（记录会话 #timeline / 录像会话 #timelineDemo 各一条）：
   左列三行（.tl-slider-row 进度 / .tl-controls 主控制 / .tl-opts 显示开关），
   右列为八键按键簇 .tm-keys（跨 3 行，只挂在记录会话那条上）；内容由
   apps/viewer/src/replay/timeline.ts 按构造期能力档构建 ── */
.timeline {
  position: relative;
  /* 左列 1fr：滑杆随 dock 全宽贯通（owner 定稿——试过 680px 上限 + space-between，
     中段空缺被读作「轨道断了」，撤）。两端与边界 / 相邻组件的距离由 padding
     13px 10px 保证；wrap 不再外扩（见 .tl-sliderwrap 注释）。 */
  /* `column-gap: 0` 是 2026-10-16 的修复：原先这里写 `column-gap: 14px`，而第二列只在
     记录会话那条上才有内容（`.tm-keys` 按键簇，`apps/viewer/src/ui/telemetry.ts` 只挂进
     `#timeline`）——录像会话的 `#timelineDemo` **没有按键簇**，但 14px 的列间距照样占位，
     表现就是滑杆走到离右端 14px 就「少了一段」。间距改由按键簇自己带（`margin-left`），
     没有按键簇 ⇒ 不占位。 */
  display: grid; grid-template-columns: 1fr auto; column-gap: 0;
  padding: 13px 10px;
  background: var(--panel-trans); border: 1px solid var(--border);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
/* 胶片齿孔：时间轴上下缘的输片孔条（硬停重复条纹，无渐变）——
   审片室「看录像」的媒体语彙，导演版特色保留项；孔距 13px、孔宽 5px，
   影调黑底上镂空。
   **已改回深灰镂空**（owner 裁决：早先那版读起来就是胶片，很好；我中途把它改成
   七色循环孔条，反而失了胶片感 —— 彩色点缀归嵌片，孔条保持影调）。 */
.timeline::before, .timeline::after {
  content: ""; position: absolute; left: 10px; right: 10px; height: 5px;
  background: repeating-linear-gradient(90deg,
    var(--strip-dark) 0 5px, transparent 5px 13px);
}
.timeline::before { top: 4px; }
.timeline::after { bottom: 4px; }
.tl-slider-row, .tl-controls, .tl-opts { grid-column: 1; }
.tl-slider-row { display: flex; align-items: center; gap: 10px; }
/* **两条影带的高度都定死 106px**（owner 定稿：记录那条本来就是 106，录像那条也拉到同一个数）。
   定死是为了不再出现高度抖动 —— 这一栏此前完全由内容决定高度，控件一折行就变高、换个窗口又变。
   `align-content: space-between` 让三行在这个固定高度里**均匀分布**，余量摊到行与行之间，
   而不是全堆在底部。两条共用 `.timeline` 一个类，所以这一条规则同时作用于它们（owner 要的正是「都」）。
   注：数值取自 owner 的窗口宽度；若窗口窄到控件折行更多，内容会高过这个定值（那时把 106 调大）。 */
/* 三行高度也定死：两条时间轴同一套行高（记录 22/29/27 是它内容自然高度，录像那条此前
   由内容算出 22.66/29.67/25.67，同高同结构却看起来'差一点'）。 */
.timeline { height: 106px; align-content: space-between; grid-template-rows: 22px 29px 27px; }

/* 滑杆外观与两条人物叠加带（活跃区间 / 悬停高亮）共用同一个坐标系，见下滑杆块。 */
.tl-sliderwrap {
  position: relative; flex: 1; min-width: 160px; height: 18px;
  display: flex; align-items: center;
  /* 秒刻度尺：底部短刻度（每 10% 一格） */
  background-image: repeating-linear-gradient(90deg,
    var(--border) 0 1px, transparent 1px 10%);
  background-size: calc(100% - 1px) 5px;
  background-position: left bottom;
  background-repeat: no-repeat;
}
/* 播放头：自绘白竖线 thumb（剪辑 playhead 语彙）；webkit/moz 双轨 */
.tl-slider {
  flex: 1; width: 100%; position: relative; z-index: 2; background: transparent;
  -webkit-appearance: none; appearance: none;
  height: 18px; cursor: pointer;
}
.tl-slider::-webkit-slider-runnable-track {
  height: 2px; background: var(--border-strong);
}
.tl-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 3px; height: 16px; margin-top: -7px;
  background: var(--strip-light); border: none;
}
.tl-slider::-moz-range-track {
  height: 2px; background: var(--border-strong);
}
.tl-slider::-moz-range-thumb {
  width: 3px; height: 16px; background: var(--strip-light); border: none; border-radius: 0;
}
.tl-zone { position: absolute; top: 2px; bottom: 2px; pointer-events: none; }
/* 正式跑段带 = **斜纹灰白带 + 两端刻线**（剪辑软件「片段」语彙：斜纹 = 记录中，
   端线 = 入点/出点）；A-B 区间带 = 白框——两种带形态/纹理刻意区分，无需文字即可分辨；
   灰阶区间语彙（时间码黄只给数字，不给区间面），改色须同步帮助文本 */
.tl-zone-run {
  /* **与人物活跃带同高**：.tl-zone 基类是 top/bottom 2px（14px 高），而人物带是 3px（12px 高），
     两条斜纹带并排时录像那条会显得更高。这里覆写为 3px，与 #timelineDemo 的活跃带一致。 */
  top: 3px; bottom: 3px;
  background:
    repeating-linear-gradient(-45deg, rgba(232, 232, 228, 0.2) 0 3px, transparent 3px 8px),
    rgba(232, 232, 228, 0.13);
  border-left: 1px solid var(--border-strong);
  border-right: 1px solid var(--border-strong);
}
/* 当前视角人物的**活跃区间**：沿用「录像」页**正式跑段**的同族样式（斜纹灰白带 + 两端刻线），
   与「录像」tab 的语彙一致，不再自造一条淡黄带。owner 定稿：hover 才用橙色。 */
/* 人物叠加带 = **容器 + 若干段**（`apps/viewer/src/replay/timeline.ts` 按会话给的**区间列表**
   逐段建 `.tl-seg` 子元素并写行内 left/width）：
   · 一个人中途退出又进来 ⇒ 这里是**两段**，中间留断口（owner 实测：原先合并成一段连贯的在场，看不出他退过服）；
   · 记录会话不建这两条带（能力档 `personZones`），故 `.tl-seg` 只出现在录像档。
   容器本身不吃指针事件（滑杆拖动要从带上起手）。
   **容器必须撑满滑杆**（`left: 0; right: 0`）：它是绝对定位、子元素又全是绝对定位 ⇒ 不给左右边界时
   宽度算成 0，子段的百分比全按 0 宽解析、整条带**看着就是没有**（owner 实测过这一版）。 */
.tl-zone-active { position: absolute; left: 0; right: 0; top: 3px; bottom: 3px; pointer-events: none; }
.tl-zone-active > .tl-seg {
  position: absolute; top: 0; bottom: 0;
  background:
    repeating-linear-gradient(-45deg, rgba(232, 232, 228, 0.2) 0 3px, transparent 3px 8px),
    rgba(232, 232, 228, 0.13);
  border-left: 1px solid var(--border-strong);
  border-right: 1px solid var(--border-strong);
}
.tl-zone-hl { position: absolute; left: 0; right: 0; top: 0; bottom: 0; pointer-events: none; }
.tl-zone-hl > .tl-seg { position: absolute; top: 0; bottom: 0; background: rgba(230, 195, 92, 0.22); }
/* **一次性「跳到这一跑」带**（owner 要求）：点过关记录的跳转按钮后，在滑杆上画出「你即将看到的
   那一段」（落点 → 播报时刻），**播放头走过它就消失**（清除在 app 的帧循环里，见 `setJumpSpan`）。
   与上面两条带刻意分开：**橙色实心描边 + 左端一道竖线**（起点一眼可见），带内用极淡的橙填充 ——
   「人物活跃区间」是灰白斜纹、「悬停高亮」是橙色淡填充，三者叠在一起也分得清。
   它单独一层、自己撑满整条（left/right: 0），**不参与** `.tl-zone-active` 的段池。 */
.tl-zone-jump {
  position: absolute; left: 0; right: 0; top: 0; bottom: 0; pointer-events: none;
  background: rgba(230, 195, 92, 0.14);
  border: 1px solid var(--accent);
  border-left-width: 3px;
}
/* **悬停高亮**：鼠标停在右侧看板某一行上时，把该行对应的区间画到条上；移开即清除。
   故意用**橙色**（`--accent`）与上面那条灰白斜纹带区分：一个表示"正在看的人"，一个表示"鼠标指的人"。 */
.tl-zone-ab {
  background: rgba(232, 232, 228, 0.26);
  border-left: 1px solid var(--text);
  border-right: 1px solid var(--text);
}
.tl-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* 影带的行距与行高**不做任何额外规定**（owner 复核后撤销）：
   记录那条（`#timeline`）本来就被认可，录像那条（`#timelineDemo`）**要的是同样小小的间隔**，
   而我上一版给录像那条钉了 `min-height: 54px / 78px`（照抄记录那条因折行而变高的行高）⇒
   录像那条单行内容被撑出大片空白，owner 当场否掉（「你错的太夸张了……放这么大的间隔」）。
   现在两条都不动：行高就是内容自己的高度，行距就是网格默认的 0。 */
.btn.playbtn { min-width: 70px; font-weight: 600; padding: 4px 13px; }
/* 时间码：监视器黄（本行唯一彩色，与 .tl-zone 灰阶刻意分离） */
/* 时间码 = 时间码黄的核心消费位；播放中前缀 REC 红点（录制指示语彙） */
.tl-time { font-family: var(--mono); font-size: 12px; color: var(--accent); white-space: nowrap; font-variant-numeric: tabular-nums; }
.tl-time::before {
  content: ""; display: none; width: 7px; height: 7px; margin-right: 6px;
  background: var(--err); vertical-align: middle;
}
.tl-time.rec::before { display: inline-block; }
.tl-frame { font-size: 11px; color: var(--muted); font-family: var(--mono); white-space: nowrap; }
.tl-speed-select { flex: 0 0 auto; }
.tl-opts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
select.tl-select {
  font-family: inherit; font-size: 11.5px; color: var(--text);
  background-color: var(--panel);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23a2a2a4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center;
  border: 1px solid var(--border); padding: 2px 22px 2px 5px; cursor: pointer;
  appearance: none; -webkit-appearance: none;
}
select.tl-select:focus { border-color: var(--text); }
.tl-opt {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; color: var(--muted); cursor: pointer; white-space: nowrap;
}
.tl-opt input {
  cursor: pointer;
  appearance: none; -webkit-appearance: none;
  width: 13px; height: 13px;
  background: var(--bg); border: 1px solid var(--border-strong);
}
.tl-opt input:checked {
  border-color: var(--text);
  box-shadow: inset 0 0 0 1px var(--text);
}
.tl-range { font-size: 11.5px; color: var(--muted); font-family: var(--mono); min-width: 150px; }
.tl-range.active { color: var(--text); }

/* 右列按键簇 .tm-keys：上排 Q/W/E · 中排 A/S/D · 下排 蹲（1 格）+ 跳（2 格）；
   细线键帽（无圆角），高亮加 .on（白底黑字，键帽即黑白条语彙），
   构造点见 apps/viewer/src/ui/telemetry.ts */
.tm-keys {
  grid-column: 2; grid-row: 1 / span 3; align-self: center;
  /* 与左列的间距由自己带（见 `.timeline` 的 `column-gap: 0` 注释）：这样没有按键簇的
     那条时间轴（录像会话 `#timelineDemo`）不会被凭空占掉 14px。 */
  margin-left: 14px;
  display: grid; grid-template-columns: repeat(3, 24px); grid-auto-rows: 24px; gap: 3px;
}
.tm-key {
  display: flex; align-items: center; justify-content: center;
  font-size: 10.5px; color: var(--muted);
  background: var(--bg); border: 1px solid var(--border-strong);
}
.tm-key.on {
  color: var(--strip-dark); background: var(--strip-light); border-color: var(--strip-light);
}
.tm-key-q { grid-area: 1 / 1; }
.tm-key-w { grid-area: 1 / 2; }
.tm-key-e { grid-area: 1 / 3; }
.tm-key-a { grid-area: 2 / 1; }
.tm-key-s { grid-area: 2 / 2; }
.tm-key-d { grid-area: 2 / 3; }
.tm-key-duck { grid-area: 3 / 1; }
.tm-key-jump { grid-area: 3 / 2 / 4 / 4; }

/* ── 回放遥测 HUD .telemetry：单行裸数字，距底 24%；水平锚点按「可视区域」居中——
   左边界扣除 --sidebar-w + 24px（与 #dock 的右边界同参），面板收起（.full）后改 left: 50% 全屏居中；
   单层 1px 暗影只为在亮色场景下可读（与 game #stats 同构） ── */
.telemetry {
  position: absolute; bottom: 24%;
  left: calc((100% - (var(--sidebar-w) + 24px)) / 2);
  transform: translateX(-50%);
  z-index: 5; pointer-events: none; user-select: none;
  font-variant-numeric: tabular-nums;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  font-size: 20px; font-weight: 600; color: #ececec;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
/* 速度电平表（telemetry.ts：12 段，水平速度 400u/s 满格；末 2 段点亮 = 高速过载红） */
.telemetry-meter {
  display: flex; gap: 2px;
}
.meter-seg {
  width: 9px; height: 4px; background: var(--border);
}
.meter-seg.on { background: var(--muted); }
.meter-seg.on.hot { background: var(--err); }
.telemetry.full { left: 50%; }
.telemetry .vsep { color: var(--muted); margin: 0 7px; font-weight: 400; }
.telemetry .tm-vert { color: var(--muted); }
/* .tm-horiz（telemetry.ts 构造的分隔横杠）刻意走容器继承，无独立规则 */

/* ── 引导层 #guide（首访卡片 + #guideError.show）· 拖拽高亮 #dropzone.active ·
   致命错误面板 #fatal.show（由 apps/viewer/src/ui/hud.ts 切换）── */
#guide {
  position: absolute; inset: 0; z-index: 8;
  display: flex; align-items: center; justify-content: center;
  /* 卡片在**视口**里居中（owner 2026-10-16：不要整体左置）。注意 `#sidebar` 是 `#app`
     的**兄弟**（z-index 6）且压在引导层之上 —— 卡面伸进侧栏那一条就会被盖住，所以
     不再用 padding 让位，改由下面 `.card` 的宽度上限把「缩放后的可视宽度」限制在
     侧栏左侧。 */
  padding: 0 12px;
  background: var(--overlay);
}
/* ── 引导卡 = owner 2026-10-16 定的「Scene Slate」场记板（样张 test/slate.html 逐条落进来）：
   圆角铰链片（内联 SVG，1px 描边随轮廓走）压住两根拍板条的左端 + RGB/CMYK 色标条（7 格等宽）+
   标题行（大字 + PRODUCTION / VERSION）+ 四格（STEP 01~03 / ACTIONS）+ 底部 FORMAT 行。
   ⚠ 唯一偏离全站口径之处（owner 指定）：色标条引入 6 个彩色，只出现在卡内，不进 :root。
   圆角一律 0（owner 2026-10-16 追加），拍板条的斜纹仍是硬停色块（无受光高光、无材质渐变）。── */
#guide .card {
  /* 板面比旧卡宽：四格引导 + 色标条需要横向空间。上限 = min(想要宽度, 不压到侧栏的可视上限)：
     整块按 0.75 缩放后可视半宽 = 0.375 × 本宽度，居中时它不得超过 50vw − 侧栏宽
     ⇒ 本宽度 ≤ (50vw − 侧栏宽) / 0.375。窄视口再兜一个 320px 下限，避免卡片被压没。 */
  width: min(760px, max(320px, calc((50vw - var(--sidebar-w)) / 0.375)));
  position: relative; text-align: left;
  /* 给抬起的上片与铰链片留位：整块按 0.75 缩放后（见 `.slate` 的 transform），拍板条最右端
     只需 ~84px 净空（未缩放时是 ~212px，缩放把外伸部分一并收小了） */
  padding-top: clamp(96px, 13vh, 140px);
}
/* `.slate` 自己**不带 padding**：它就是拍板条与铰链片的包含块，带 padding 只会把在流的板身
   推下去、条子却留在原处（实测两者之间裂开 48px、片顶还被切）。留位交给外层 `.card`。
   **整块 0.75 缩放**（owner 2026-10-16）：用 transform 而不是逐个改尺寸 —— 卡片、条子、
   铰链片、螺钉、字号一次性同比缩到 75%，且矢量与字体仍是原生清晰度；
   `transform-origin: center` 让视觉中心留在原位（flex 居中的仍是未缩放的布局盒）。 */
#guide .slate {
  position: relative; isolation: isolate; width: 100%;
  transform: scale(0.75); transform-origin: center;
  filter: drop-shadow(0 20px 26px rgba(0, 0, 0, 0.34));
}
/* 上下拍板条：上片抬起并向右上方倾斜；两组斜纹方向镜像，视觉上形成 >>>>（硬停色块） */
#guide .clapstick {
  position: absolute; left: -12px; right: -9px; overflow: hidden;
  background: #0b0c0f; border: 1px solid #3c4049;
}
#guide .clapstick-top {
  z-index: 4; top: -84px; height: 74px;
  transform: rotate(-10deg); transform-origin: 7% 100%;
}
#guide .clapstick-bottom { z-index: 3; top: -1px; height: 73px; }
#guide .stripes { position: absolute; inset: 0; background-color: var(--strip-dark); }
#guide .clapstick-top .stripes {
  background-image: repeating-linear-gradient(135deg, var(--strip-light) 0 57px, var(--strip-dark) 57px 114px);
}
#guide .clapstick-bottom .stripes {
  background-image: repeating-linear-gradient(45deg, var(--strip-light) 0 57px, var(--strip-dark) 57px 114px);
}
/* 条子下缘的一道发丝高光：让两层条子之间有条可读的界 */
#guide .clapstick::after {
  content: ""; position: absolute; inset: auto 0 0; height: 1px; background: rgba(255, 255, 255, 0.17);
}
/* 铰链片：压住两根条子的左端。形状由内联 SVG 画（`.hinge-plate`），这里只管盒子与螺钉定位。
   盒子仍是 197×188 @ left:-25/top:-105 —— 螺钉坐标就写在这套局部坐标里，别改盒子。 */
#guide .hinge { position: absolute; z-index: 6; left: -25px; top: -105px; width: 197px; height: 188px; }
#guide .hinge-plate { position: absolute; inset: 0; display: block; width: 100%; height: 100%; overflow: visible; }
/* 三颗螺钉：**上片 1 + 下片 2** —— 上下两片因此能相对转动、板才打得响（4 颗同时钉死两片是错的） */
#guide .screw {
  position: absolute; width: 24px; height: 24px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #d8dce4 0 17%, #8e95a3 28%, #555b67 65%, #292c34 72%);
  border: 1px solid #17191e; box-shadow: 0 1px 2px #050608;
}
#guide .screw.one { left: 44px; top: 50px; }
#guide .screw.two { left: 44px; top: 126px; }
#guide .screw.three { left: 129px; top: 126px; }
/* 板身 = 卡片本体 */
#guide .slate-body {
  position: relative; z-index: 1;
  padding: 88px 21px 0;
  background: var(--panel-trans); border: 1px solid var(--border-strong);
}
/* 色标条：RGB 三格 + CMYK 四格，**7 格逐格等宽**（各 1/7）。K 与板面底色同为黑，不加 1px
   内描边就看不见 —— 那样第 7 格读不出来，7 格等宽也白搭 */
#guide .colorbar {
  display: flex; height: 46px; margin: 0 24px 18px; overflow: hidden; background: var(--strip-dark);
}
#guide .colorbar span { flex: 1; min-width: 0; }
#guide .colorbar .r { background: #a92d31; }
#guide .colorbar .g { background: #258638; }
#guide .colorbar .b { background: #1761c9; }
#guide .colorbar .c { background: #2aaac3; }
#guide .colorbar .m { background: #a92f80; }
#guide .colorbar .y { background: #d6ad25; }
#guide .colorbar .k { background: #101115; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16); }
/* 标题行：左大字（产品名 + 副标），右侧两格印字（工程名 / 版本） */
#guide .title-row {
  display: grid; grid-template-columns: 0.95fr 1.05fr; min-height: 136px;
  border-top: 1px solid var(--border-strong); border-bottom: 1px solid var(--border-strong);
}
#guide .slate-name {
  display: flex; flex-direction: column; justify-content: center; min-width: 0;
  padding: 16px 18px; border-right: 1px solid var(--border-strong);
}
#guide .slate-name strong {
  font-size: clamp(35.25px, 5.2vw, 55.25px); font-weight: 600; letter-spacing: -0.045em;
  line-height: 1.08; overflow-wrap: anywhere;
}
#guide .slate-name span {
  margin-top: 13px; color: var(--muted); font-family: var(--mono); font-size: 21.25px;
  font-weight: 500; letter-spacing: 0.27em;
}
#guide .production { display: grid; grid-template-rows: 1fr 1fr; min-width: 0; }
#guide .production div {
  padding: 12px 26px; color: var(--muted); font-family: var(--mono); font-size: 16.25px; letter-spacing: 0.13em;
}
#guide .production div + div { border-top: 1px solid var(--border-strong); }
#guide .production b { display: block; margin-top: 4px; color: var(--text); font-family: system-ui, sans-serif; font-size: 20.25px; font-weight: 500; }
/* 四格：前三格等宽、操作格略宽（两枚按钮的文案比「01」长，不然会折成三行）。
   实物板上这四格是 SCENE / TAKE / ROLL / DATE，这里换成三步引导 + 操作 */
#guide .fields {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) 1.3fr;
  border-bottom: 1px solid var(--border-strong);
}
/* display:block 是**必须**的：应用里另有一条全局 .field{display:flex}（侧栏表单行，见本文件
   表单段），不压掉会把卡内标签与正文摆成左右两列 */
#guide .field { display: block; min-width: 0; padding: 20px 22px 20px; }
#guide .field + .field { border-left: 1px solid var(--border-strong); }
#guide .field > label {
  display: block; margin-bottom: 9px; color: var(--muted);
  font-family: var(--mono); font-size: 17.25px; letter-spacing: 0.1em;
}
/* 值：实物板上是短码（01 / 001），这张卡里换成整句话 —— 字号行高按正文调，仍是同一格 */
#guide .field > span {
  display: block; color: var(--muted); font-size: 16.25px; line-height: 1.75;
}
#guide .field code { font-family: var(--mono); font-size: 15.25px; font-weight: 500; color: var(--text); }
/* 第四格 = 操作：两枚按钮是 `label[for]` 原生转发到隐藏 input；出错行也留在这格里。
   全局 `.filebtn` 带 `white-space: nowrap`，这里必须放开，否则长标签会撑破格子 */
#guide .field .filebtn {
  display: block; width: 100%; margin-bottom: 9px; padding: 11px 12px; text-align: center;
  color: var(--text); background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border-strong);
  font-family: var(--mono); font-size: 15.25px; font-weight: 500; letter-spacing: 0.05em; white-space: normal;
}
#guide .field .filebtn:hover { background: rgba(255, 255, 255, 0.07); border-color: var(--text); }
#guide .field .filebtn.busy { opacity: 0.45; pointer-events: none; }
/* 错误行 #guideError：默认不占位，`.show` 时出现在操作格里（REC 红 + `.raw` 原文） */
#guide #guideError { display: none; }
#guide #guideError.show {
  display: block; margin-top: 10px;
  color: var(--err); font-size: 14.25px; line-height: 1.6;
}
#guide #guideError .raw {
  display: block; margin-top: 6px;
  font-family: var(--mono); font-size: 12.75px; font-weight: 500; color: var(--muted);
  word-break: break-all;
}
/* 底行：支持的格式（地图 + 三种记录 / 录像）；右侧斜杠是原设计的装饰 */
#guide .format-row {
  display: flex; align-items: center; gap: 46px; padding: 20px 25px 24px;
  color: var(--muted); font-family: var(--mono); font-size: 16.25px; letter-spacing: 0.13em;
}
#guide .format-row strong { font-weight: 500; color: var(--text); }
#guide .slashes {
  margin-left: auto; color: var(--muted); font-size: 37.25px; letter-spacing: 0.12em; white-space: nowrap;
}
/* 窄视口：四格改竖排一列、铰链片与螺钉等比缩小、斜纹周期减半。
   断点取 1080px：自由区宽 = 视口 − 右侧栏（348）− 两侧让位（24），四格并排约需 700px 自由区 ⇒
   视口低于 ~1080px 时并排会挤到换行，不如直接竖排（也不至于把卡片撑高到溢出视口） */
@media (max-width: 1080px) {
  #guide .card { padding-top: 112px; }
  #guide .clapstick { left: -7px; right: -6px; }
  #guide .clapstick-top { top: -54px; height: 44px; }
  #guide .clapstick-bottom { top: -1px; height: 43px; }
  #guide .clapstick-top .stripes {
    background-image: repeating-linear-gradient(135deg, var(--strip-light) 0 28px, var(--strip-dark) 28px 56px);
  }
  #guide .clapstick-bottom .stripes {
    background-image: repeating-linear-gradient(45deg, var(--strip-light) 0 28px, var(--strip-dark) 28px 56px);
  }
  #guide .hinge { left: 0; top: -51px; width: 104px; height: 96px; }
  #guide .screw { width: 14px; height: 14px; }
  #guide .screw.one { left: 23px; top: 26px; }
  #guide .screw.two { left: 23px; top: 64px; }
  #guide .screw.three { left: 68px; top: 64px; }
  #guide .slate-body { padding: 57px 10px 17px; }
  #guide .colorbar { height: 34px; margin: 0 11px 14px; }
  #guide .title-row { min-height: 116px; }
  #guide .slate-name { padding: 12px 10px; }
  #guide .slate-name strong { font-size: clamp(26.25px, 7.5vw, 36.25px); }
  #guide .slate-name span { margin-top: 8px; font-size: 10.25px; letter-spacing: 0.16em; }
  #guide .production div { padding: 10px 10px; font-size: 10.25px; }
  #guide .production b { margin-top: 4px; font-size: 17.25px; }
  #guide .fields { grid-template-columns: 1fr; }
  #guide .field { padding: 14px 11px 16px; }
  #guide .field + .field { border-left: 0; border-top: 1px solid var(--border-strong); }
  #guide .field > label { margin-bottom: 7px; font-size: 11.25px; letter-spacing: 0.06em; }
  #guide .field > span { font-size: 14.25px; line-height: 1.65; }
  #guide .field .filebtn { padding: 10px 12px; font-size: 13.75px; }
  #guide .format-row { gap: 9px; padding: 17px 11px 15px; font-size: 10.25px; letter-spacing: 0.07em; }
  #guide .slashes { font-size: 17.25px; }
}
/* 矮视口（≤820px 高）：卡片再压一档 —— 抬起的上片本身就占 ~210px，不压就会顶到视口外 */
@media (max-height: 820px) {
  #guide .card { padding-top: 96px; }
  #guide .slate-body { padding-top: 78px; }
  #guide .colorbar { height: 38px; margin: 0 24px 14px; }
  #guide .title-row { min-height: 108px; }
  #guide .slate-name { padding: 12px 18px; }
  #guide .slate-name strong { font-size: clamp(31.25px, 4.6vw, 47.25px); }
  #guide .production div { padding: 10px 26px; }
  #guide .field { padding: 14px 22px; }
  #guide .field > label { margin-bottom: 6px; }
  #guide .format-row { padding: 14px 25px 16px; }
}
#dropzone {
  position: absolute; inset: 0; z-index: 9; display: none;
  align-items: center; justify-content: center;
  /* **遮罩明显压暗**：早先只有 6% 的亮色薄雾，拖文件进来时几乎看不出进入了拖拽态。
     改成暗色重遮罩（配合时间码黄文字），后面板与画面整体退到背景里。 */
  background: rgba(9, 9, 11, 0.78);
  font-family: var(--mono); font-size: 14px; letter-spacing: 0.08em; color: var(--accent);
  pointer-events: none;
}
#dropzone::before, #dropzone::after {
  content: ""; position: absolute; left: 0; right: 0; height: 8px;
  /* SMPTE 七色彩条（白 75/黄/青/绿/紫/红/蓝，硬停分段）——影视测试信号语彙 */
  background: linear-gradient(90deg,
    #c8c8c8 0 14.28%, #e6c35c 14.28% 28.56%, #4fb3c4 28.56% 42.84%, #58c25c 42.84% 57.12%,
    #b06fc4 57.12% 71.4%, #d9564a 71.4% 85.68%, #4a6fd9 85.68% 100%);
}
#dropzone::before { top: 12px; }
#dropzone::after { bottom: 12px; }
#dropzone.active { display: flex; }

#fatal {
  position: absolute; inset: 0; z-index: 20; display: none;
  align-items: center; justify-content: center; background: var(--overlay);
}
#fatal.show { display: flex; }
#fatal .card {
  max-width: 480px; margin: 0 16px; padding: 22px 26px;
  position: relative;
  background: var(--panel); border: 1px solid var(--border-strong);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 6px 20px rgba(0, 0, 0, 0.35);
}
#fatal .ftitle { font-size: 15px; font-weight: 600; color: var(--err); margin-bottom: 10px; }
#fatal .fdetail { font-size: 12px; color: var(--muted); line-height: 1.8; white-space: pre-wrap; word-break: break-all; }
#fatal .fdetail code { color: var(--muted); }

/* ── 演示页（Source `.dem`，`DemoPanel`） ─────────────────────────────
   与「录像」页刻意不同：`.dem` 没有定长位姿，看板是「时间线 + 玩家标注」而非轨迹列表。
   时间线一行一位玩家：有采到位姿则画强调色区间条，否则显示「位姿不可用」的弱化标记。 */
/* ── 「载入与看板」（owner 要求重新设计：排版与数据展示都不够好；不必再省高度）──────
   设计取向：**一页规格表**。上面一行是「这份文件是什么」（文件名 + 大小 + 一句话摘要），
   中间 `dl.dmp-facts` 是逐项事实（左标签右值，值用等宽字，数值带单位/副读数），
   底下一行是唯一**逐帧在变**的「在服」名单（强调色标签 + 可换行名单 + 人数角标）。
   与底部那条录像信息条的分工：信息条是**冷读数**（12 项、可悬停查诊断），
   这里是**看得懂的摘要**（分组、带单位、带副读数），两者都保留、互不替代。 */
.dmp-load { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 12px 0; }
.dmp-load input { display: none; }
.dmp-drop { font-size: 11px; color: var(--muted); }
.dmp-meta, .dmp-detail { padding: 10px 0; border-top: 1px solid var(--border); overflow-x: auto; }
.dmp-board { border: 1px solid var(--border); background: rgba(0, 0, 0, 0.18); }
.dmp-title { color: var(--text); font-weight: 600; }
.dmp-title-row {
  display: flex; flex-direction: column; gap: 3px;
  padding: 9px 11px; border-bottom: 1px solid var(--border);
}
.dmp-file { font-family: var(--mono); font-size: 12px; overflow-wrap: anywhere; }
.dmp-size { font-family: var(--mono); font-size: 11px; color: var(--muted); white-space: nowrap; }
.dmp-sum { font-weight: 400; font-size: 11px; color: var(--muted); line-height: 1.5; }
/* 规格表：`auto 1fr` 两列，行间发丝线；值右对齐不要（长文本要能自然折行） */
.dmp-facts { display: flex; flex-direction: column; margin: 0; }
.dmp-facts > div {
  display: grid; grid-template-columns: 52px 1fr; gap: 10px; align-items: baseline;
  padding: 6px 11px; border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.dmp-facts > div:first-child { border-top: 0; }
.dmp-facts dt { color: var(--muted); font-size: 11px; }
.dmp-facts dd { margin: 0; color: var(--text); font-size: 12px; line-height: 1.5; word-break: break-word; }
.dmp-mono { font-family: var(--mono); }
/* 副读数（tick 数 / 轨迹条数 / 解析耗时）：跟在主值后面的一小撮弱化说明，换行也不缩进 */
.dmp-sub2 { margin-left: 8px; font-size: 11px; color: var(--muted); font-style: normal; }
/* 「在服」行：唯一逐帧更新的那一格，强调色标签 + 可换行名单 + 人数角标 */
.dmp-live-row {
  display: grid; grid-template-columns: 52px 1fr auto; gap: 10px; align-items: baseline;
  padding: 7px 11px; border-top: 1px solid var(--border); background: rgba(255, 255, 255, 0.02);
}
.dmp-live-tag { font-size: 11px; color: var(--accent); }
.dmp-live-n { font-family: var(--mono); font-size: 11px; color: var(--muted); white-space: nowrap; }
.dmp-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; font-size: 12px; }
.dmp-grid span { color: var(--muted); }
.dmp-grid b { color: var(--text); font-weight: 500; word-break: break-all; }
.dmp-roster-list { padding: 8px 0; border-top: 1px solid var(--border); }
.dmp-axis { display: flex; justify-content: space-between; font-size: 11px; color: var(--muted); margin-bottom: 6px; }
.dmp-lane { flex: 0 0 auto;
  display: grid; grid-template-columns: 34% 1fr; align-items: center; gap: 8px;
  width: 100%; padding: 3px 0; background: none; border: 0; cursor: pointer; text-align: left;
}
.dmp-lane:hover .dmp-name { color: var(--text); }
.dmp-lane.on .dmp-name { color: var(--accent); }
.dmp-name { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dmp-track { position: relative; display: block; height: 10px; background: var(--panel); border: 1px solid var(--border); }
.dmp-bar { position: absolute; top: 0; bottom: 0; background: var(--accent); }
.dmp-nobar { font-size: 10px; color: var(--muted); line-height: 10px; padding-left: 4px; }
.dmp-note { padding: 8px 0 0; font-size: 11px; color: var(--muted); line-height: 1.5; }
.dmp-empty { color: var(--muted); font-size: 12px; padding: 10px 0; }.dmp-sub { padding: 10px 0 4px; font-size: 11px; color: var(--muted); border-top: 1px solid var(--border); margin-top: 6px; }
.dmp-lane.dmp-ent { cursor: default; }
.dmp-bar.dmp-bar-ent { background: var(--muted); }.dmp-lane.dmp-ent:hover .dmp-name { color: var(--text); }
.dmp-lane:active { background: rgba(232, 232, 228, 0.05); }.dmp-ratio { margin-left: 6px; font-weight: 400; color: var(--ok); font-size: 10px; }.dmp-jsonlist, .dmp-events { max-height: 260px; overflow-y: auto; }
.dmp-events { font-size: 11px; color: var(--muted); line-height: 1.6; }
.dmp-ev b { color: var(--accent); font-family: var(--mono); margin-right: 6px; }

/* 侧栏的「在服名单」抬头：只在播放时逐帧更新文本（轮换），不放进度条本身 ——
   进度与人物叠加带统一在底部时间轴滑杆上（见 .tl-zone-active / .tl-zone-hl）。 */
.dmp-live { font-size: 12px; color: var(--text); line-height: 1.5; }
.dmp-grid .dmp-live { margin: 0; }
/* 注：早先这里有一条 `.tl-demo .tl-only-replay { display: none }` —— 靠一个类名在运行期
   收起「只服务于记录」的控件。两条链路拆成各自的时间轴实例后，控件在**构造期**就按
   `TimelineProfile` 决定建不建（见 apps/viewer/src/replay/timeline.ts），该规则已删。 */
/* ── 右侧看板的**花名册**（所有活跃过的人）──────────────────────────────────
   每行：名字 + 一根按整场时长换算的活跃条 + `加入–退场` 时间。
   **当前时刻在线** ⇒ 全不透明；**已离线 / 还没加入** ⇒ 半透明（owner 的要求）。
   悬停某行 ⇒ 由 app 调 `Timeline.setHighlight` 把该行区间画到底部进度条上。 */
.dmp-roster { display: flex; flex-direction: column; gap: 1px; max-height: 260px; overflow-y: auto; }
.dmp-who { display: grid; grid-template-columns: 1fr 92px 84px; align-items: center; gap: 8px; width: 100%; padding: 4px 6px; border: 0; background: none; text-align: left; font: inherit; font-size: 12px; color: var(--text); cursor: pointer; }
.dmp-who:hover { background: var(--accent-weak); }
.dmp-who.off { opacity: 0.4; }
.dmp-whoname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 队伍标签（PlayerSample.team → 观察者 / T / CT）：贴在名字后的小方框，与人物名牌的 `.dmp-who-tag` 同语彙 */
/* 观察标记（T-702）：过半时间在观察 —— 这段位姿是镜头跟着别人，不是本人走位 */
.dmp-who-observe { margin-left: 4px; padding: 0 4px; border-radius: 3px; font-size: 10px; font-style: normal; color: var(--bg); background: var(--muted); }
/* 死亡标记（T-714）：过半时间已死 —— 死亡期间镜头自由观察，同样不是本人走位（与「观」并列） */
.dmp-who-dead { margin-left: 4px; padding: 0 4px; border-radius: 3px; font-size: 10px; font-style: normal; color: var(--muted); border: 1px solid var(--muted); }
.dmp-who-team { margin-left: 6px; padding: 0 4px; font-style: normal; font-size: 10px; line-height: 1.5; color: var(--muted); border: 1px solid var(--border-strong); }
.dmp-whobar { position: relative; display: block; height: 6px; background: rgba(255, 255, 255, 0.06); }
.dmp-whobar i { position: absolute; top: 0; bottom: 0; background: var(--accent); }
.dmp-whotime { font-size: 10px; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
/* ── 滑杆与区间带共坐标系 ────────────────────────────────────────────
   区间带（`.tl-zone-*`）按**父容器 `.tl-sliderwrap` 宽度**算 `left/width` 百分比，
   滑杆在 wrap 内 100% 宽 ⇒ 两者共享同一坐标系。曾按「两端更易点中」给 wrap 加过
   `0 -8px` 外扩，实测滑杆两端距容器边框只剩 2px、且比相邻行凸出（owner 2026-09-30
   裁决撤外扩）；两端可点范围回到 thumb 半径内属已知代价。 */
.tl-sliderwrap { margin: 0; }
.tl-slider { height: 20px; }
/* ── 演示看板：人物名牌（与「录像」页同一套语彙）──────────────────────────────
   原本这里是一个**裸复选框**，与看板其余部分（色点 / 标签 / 发丝线）不是一个语言。
   现在拆成三件成套元素：**轨迹色点**（与 3D 里那条轨迹同色）+ **名字** + **角色标签**，
   开关另起一行、沿用本工程的方框刻线样式，不再是浏览器默认控件的样子。 */
.dmp-who-head { display: flex; align-items: center; gap: 7px; margin: 2px 0 8px; }
.dmp-who-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.dmp-who-name { font-size: 13px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dmp-who-tag { margin-left: auto; flex: 0 0 auto; padding: 1px 6px; font-size: 10px; letter-spacing: 0.06em; color: var(--muted); border: 1px solid var(--border-strong); }
.dmp-who-tag.human { color: var(--accent); border-color: var(--accent); }

/* ── 滚动条自绘（::-webkit-scrollbar，Edge/Chromium）：方角 + thumb=分隔色 +
   1px 轨道沿，hover 转铜金（瞬时态）。勿与标准 scrollbar-width 并设，
   否则 Chromium 121+ 走标准属性、忽略这里的全部自绘 ── */
#help::-webkit-scrollbar, .tabpane::-webkit-scrollbar, .spawn-list::-webkit-scrollbar,
.dmp-jsonlist::-webkit-scrollbar, .dmp-events::-webkit-scrollbar, .dmp-roster::-webkit-scrollbar,
.dmp-meta::-webkit-scrollbar, .dmp-detail::-webkit-scrollbar { width: 10px; height: 10px; }
#help::-webkit-scrollbar-track, .tabpane::-webkit-scrollbar-track, .spawn-list::-webkit-scrollbar-track,
.dmp-jsonlist::-webkit-scrollbar-track, .dmp-events::-webkit-scrollbar-track, .dmp-roster::-webkit-scrollbar-track,
.dmp-meta::-webkit-scrollbar-track, .dmp-detail::-webkit-scrollbar-track {
  background: var(--bg); border-left: 1px solid var(--border); }
#help::-webkit-scrollbar-thumb, .tabpane::-webkit-scrollbar-thumb, .spawn-list::-webkit-scrollbar-thumb,
.dmp-jsonlist::-webkit-scrollbar-thumb, .dmp-events::-webkit-scrollbar-thumb, .dmp-roster::-webkit-scrollbar-thumb,
.dmp-meta::-webkit-scrollbar-thumb, .dmp-detail::-webkit-scrollbar-thumb { background: var(--border-strong); }
#help::-webkit-scrollbar-thumb:hover, .tabpane::-webkit-scrollbar-thumb:hover, .spawn-list::-webkit-scrollbar-thumb:hover,
.dmp-jsonlist::-webkit-scrollbar-thumb:hover, .dmp-events::-webkit-scrollbar-thumb:hover, .dmp-roster::-webkit-scrollbar-thumb:hover,
.dmp-meta::-webkit-scrollbar-thumb:hover, .dmp-detail::-webkit-scrollbar-thumb:hover { background: var(--accent); }
#help::-webkit-scrollbar-button, .tabpane::-webkit-scrollbar-button, .spawn-list::-webkit-scrollbar-button,
.dmp-jsonlist::-webkit-scrollbar-button, .dmp-events::-webkit-scrollbar-button, .dmp-roster::-webkit-scrollbar-button,
.dmp-meta::-webkit-scrollbar-button, .dmp-detail::-webkit-scrollbar-button { display: none; }
#help::-webkit-scrollbar-corner, .tabpane::-webkit-scrollbar-corner, .spawn-list::-webkit-scrollbar-corner,
.dmp-jsonlist::-webkit-scrollbar-corner, .dmp-events::-webkit-scrollbar-corner, .dmp-roster::-webkit-scrollbar-corner,
.dmp-meta::-webkit-scrollbar-corner, .dmp-detail::-webkit-scrollbar-corner { background: var(--bg); }

/* ── 录像看板：对话区（本仓现有那份实测 0 条，空态要写清原因）─────────────────
   与看板其余部分同一套语彙：等宽小字、行间发丝线、不铺色块、不加圆角。 */
/* 录像面板：**回到「整列滚动」**（不再做网格分高）—— owner 复核后改为：面板里的对话
   **只留明暗**（不设滚动条、不自动滚动），「当前这一刻说了什么」交给画面左下角的浮层
   （`.chat-overlay`，见下方浮层段）。三节的类名（`sec-load` / `sec-roster` / `sec-chat`）
   保留作**结构标记**，布局不再依赖它们。 */
.dmp-chat { display: flex; flex-direction: column; }
/* ── 消息过滤（四个类别各一个勾选框，**勾上 = 这一类不显示**）────────────────────
   类别判据见 `apps/viewer/src/replay/demo/chatkind.ts`：进服 / 过关 / 服务器公告 / 玩家对话。
   每个框后面跟该类共几条（`×N`），勾上后标签加删除线并压暗 —— 让「我滤掉的是不是我以为的那类」
   一眼可核对。整行允许折行（四个类别 + 读数在窄侧栏里放不下一行）。 */
/* 档案里连不到账号的名字（T-717）：只说明，不需要用户操作 */
.dmp-alias-hint { margin: 4px 0 0; font-size: 10px; line-height: 1.5; color: var(--muted); }
.dmp-alias-hint:empty { display: none; }

.dmp-chat-filter {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px;
  padding: 6px 0 7px; border-bottom: 1px solid var(--border);
}
.dmp-cf-title { font-size: 11px; color: var(--muted); }
.dmp-cf { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text); cursor: pointer; }
.dmp-cf input { appearance: none; -webkit-appearance: none; width: 12px; height: 12px; margin: 0;
  border: 1px solid var(--border); background: var(--panel); cursor: pointer; }
.dmp-cf input:checked { background: var(--accent); border-color: var(--accent); }
.dmp-cf-n { font-family: var(--mono); font-size: 10px; color: var(--muted); }
/* 兜底读数：只在「有消息没命中任何具体判据」时出现 —— 换个没见过的服/语言时它会涨，
   提示有新写法要补判据（悬停写明）。用警示色，但不抢四类勾选框的视线。 */
.dmp-cf-fb {
  padding: 0 5px; font-family: var(--mono); font-size: 10px; line-height: 1.7;
  color: var(--warn, var(--accent)); border: 1px dashed var(--warn, var(--accent));
}
/* 勾上的那一类：标签加删除线 + 压暗，读数保持可读（用户还要看「滤掉了几条」） */
/* 勾选制（owner 2026-10-16）：勾上 = 只看这一类（高亮），没勾 = 不参与过滤（压暗，但不算"划掉"）。
   全勾与全不勾都等于默认全显示 —— 换算见 DemoPanel.chatAllow()。 */
.dmp-cf.on .dmp-cf-name { color: var(--accent); font-weight: 600; }
.dmp-cf.off .dmp-cf-name { color: var(--muted); }
.dmp-cf.off .dmp-cf-n { opacity: 0.7; }
/* 已发生的对话 = 正常亮度；未发生的压暗（透明度作用于整行，时间前缀更弱一档） */
.dmp-chat-line { padding: 4px 0; font-family: var(--mono); font-size: 11px; line-height: 1.6; color: var(--text); border-bottom: 1px solid var(--border); overflow-wrap: anywhere; opacity: 0.32; }
.dmp-chat-line.dmp-chat-on { opacity: 1; }
/* 过关记录：左侧一条强调色发丝线把它与播报区分开（可点击跳转的那一类） */
.dmp-chat-line.dmp-chat-rec { border-left: 2px solid var(--accent); padding-left: 6px; }
/* 「跳到这一跑」按钮：贴在该行正文后，等宽小字 —— 它显示**算好的目标时刻**（m:ss） */
.dmp-chat-jump,
.dmp-chat-deduct {
  margin-left: 6px; padding: 0 5px; font-family: var(--mono); font-size: 10px; line-height: 1.7;
  color: var(--accent); background: none; border: 1px solid var(--accent); cursor: pointer;
}
.dmp-chat-jump:hover,
.dmp-chat-deduct:hover { background: var(--accent); color: var(--bg); }
.dmp-chat-t { color: var(--muted); margin-right: 6px; font-variant-numeric: tabular-nums; }
/* 行首灰色时间：过关记录那几行可点 —— 点了就跳到这一跑的起点（跳转与扣减分开，见 demopanel） */
button.dmp-chat-t { appearance: none; background: none; border: 0; padding: 0; font: inherit;
  border-bottom: 1px dotted currentColor; cursor: pointer; }
button.dmp-chat-t:hover { color: var(--accent); border-bottom-style: solid; }
.dmp-chat-x { color: inherit; }
.dmp-chat-empty { padding: 6px 0; font-size: 12px; color: var(--muted); line-height: 1.7; }
.dmp-chat-hint { display: block; margin-top: 4px; font-size: 11px; color: var(--muted); opacity: 0.75; }

/* ── 画面左下角的**对话浮层** `#chatOverlay`（`apps/viewer/src/ui/chatoverlay.ts`）──────────
   只显示「当前这一刻」附近的几条聊天（owner 定稿）：出现 15 秒后淡出、同屏最多 5 条、
   短时间内超过 5 条就把最早那条快速丢出去。**顺序自早到晚、最底下是最晚的** ——
   容器贴着左下角、按 `bottom` 定位（`bottom` 由 ChatOverlay 按 `#dock` 高度动态让位，
   否则会被底部那条时间轴压住），所以内容只会**向上长**，新的一条从底部顶上来。
   `pointer-events: none`：浮层不许挡住画面上的鼠标操作（点击画面要能锁定鼠标）。
   这里**刻意用了 transition**：owner 要的「渐渐消失」只能这样表达；时长与
   `apps/viewer/src/ui/chatoverlay.ts` 的常量一一对应（420ms 正常淡出 / 140ms 被挤掉）。 */
.chat-overlay {
  position: absolute; left: 18px; z-index: 5;
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  max-width: min(58ch, 46vw); pointer-events: none;
}
.co-line {
  padding: 2px 9px 3px; border-left: 2px solid var(--border-strong);
  background: rgba(10, 12, 12, 0.66);
  font-family: var(--mono); font-size: 12px; line-height: 1.5; color: var(--text);
  opacity: 0; transition: opacity 420ms linear;
  overflow-wrap: anywhere;
}
.co-line.co-on { opacity: 1; }
/* ── 显示页签的渲染设置行（rp-*）：沿用本页既有元件（.sec / .sec-title / .sec-body / .field /
      .field-label / .field-input.field-num / .note.note-info / .btn.small，见 styles.css:249-313），
      只补本页原先没有的三样：滑块、复位按钮、数值单位。标注列定宽 96px、长标注列内换行，不挤压滑块。── */
.rp-mapstate { font-size: 11.5px; color: var(--muted); line-height: 1.5; margin: 0 0 8px; }
.rp-field .field-label { flex: 0 0 96px; line-height: 1.35; }
.rp-field input[type="range"].field-range {
  flex: 1; min-width: 0; height: 15px;
  appearance: none; -webkit-appearance: none; background: transparent; cursor: pointer;
}
.rp-field input[type="range"].field-range::-webkit-slider-runnable-track { height: 1px; background: var(--border-strong); }
.rp-field input[type="range"].field-range::-webkit-slider-thumb {
  -webkit-appearance: none; width: 3px; height: 13px; margin-top: -6px;
  background: var(--text); border: 0; border-radius: 0;
}
.rp-field input[type="range"].field-range::-moz-range-track { height: 1px; background: var(--border-strong); }
.rp-field input[type="range"].field-range::-moz-range-thumb {
  width: 3px; height: 13px; background: var(--text); border: 0; border-radius: 0;
}
.rp-field .rp-value { display: flex; align-items: baseline; gap: 3px; flex: 0 0 auto; }
.rp-field .rp-value input { width: 64px; }
.rp-reset.btn.small { flex: none; padding: 2px 6px; font-size: 11px; }
/* 输入中的非法值（越界/非数字）：沿用本页 .invalid 的语义，只标色不改值 */
.rp-field .field-input.rp-invalid { border-color: var(--err); }
.rp-unit { color: var(--muted); font-size: 11.5px; flex: none; }

/* 标签式输入（P2-③）：多名字的材质项用它，值仍是逗号分隔串 */
.rp-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.rp-tags .rp-tag { display: inline-flex; align-items: center; gap: 2px; padding: 1px 4px 1px 6px;
  border: 1px solid var(--border); border-radius: 3px; font-size: 11px; color: var(--text); background: var(--panel); }
.rp-tags .rp-tag-x { padding: 0 2px; font: inherit; line-height: 1; cursor: pointer;
  color: var(--muted); background: none; border: 0; }
.rp-tags .rp-tag-x:hover { color: var(--accent); }
.rp-tags input { flex: 1 1 90px; min-width: 60px; }
