# 站位报告的设计约定

报告由 `scripts/novel-staging.mjs render --html` 生成，样式和页面脚本全部内联在 `renderHtml()` 里。**改样式只改那一处。**

跟其余几份报告同一套视觉语言：冷灰印张 + 铁锈红印记，1600 宽，零外部依赖，离线双击能开。跟它们不一样的是**这份报告能调整**：每一场一张俯视平面，人可以拖动，改完导出就是新的 staging.json。

## 结构

```
页眉      剧名 · 开场站位 · 改动状态 · 质量门徽章 ·（有改动才出现）还原 · 导出 JSON
KPI 带    场次（几场有人开场不在）/ 地点（几个按图认的）/ 开场在场人次 / 群演
（病灶横幅）质量门有未过项才出现
01 各场开场站位   每场一张大卡（报告的主体）
02 地点与认出来的东西   每个地点一张卡：场景图 + 东西清单
03 质量门     render 那一刻的 ✓/✗ 清单
```

## 每场一张卡

左边是**俯视平面**（16:10，上北下南、左西右东，方位字在平面外面一圈），右边从上到下：这个地点的场景图（小图）→ 选中的人 → 群演 → 开场走位句（带复制按钮）→ 为什么这么排 → 这一场的实时检查。

- **东西**按 `plan` 画成框，能坐能站的框是铁锈色；圆的画成圆。名字贴在框的左上角、不折行：人站在东西中间时不挡名字。
- **人**是圆点，点上写名字的第一个字，下面写全名；坐着的颜色浅一点；一个小三角指着他面朝的方向。点的位置由「东西 + 哪一侧 + 沿着多远」算出来（`placePoints`），跟走位句永远是一回事。
- **拖动**：按住一个人拖到平面上别处，松手时落在最近那件东西最近的一侧（`nearestPlace`）；松在能坐的东西里面就坐在它上面。拖的时候目标那件东西亮起来，右边场景图上同一件东西的框也亮。换了东西或换了一侧，人自动转过去面朝它；从托盘拖进来的人站着，落在能坐的东西上就坐下（`moveTo`）。
- **托盘**在平面下面：开场不在场的人。拖进平面就是在场，拖回托盘就是开场不在。
- **点一下**一个人：右边出现坐着／站着、朝向（八个方位的罗盘）、移出。
- **群演**：加减按钮 + 「在哪」一行字；人数变成 0 时「在哪」自动清空。
- **实时检查**：每改一次，这一场按跟 `validate` 同一份规则（`sceneProblems`）重算，有问题用铁锈红列出来。

页面上的算法跟命令行是同一份：`stagingCore()` 整个用 `toString()` 嵌进页面。所以 `stagingCore` 的函数体里不许引用外面的任何东西，自测会单独拿它的源码重建一份来核对。

## 改动、保存、导出

- 每次改动存进这台电脑的浏览器（`localStorage`，按文件内容区分），刷新不丢；文件重新 render 过、内容变了，就不再套用旧的改动。隐私模式下存不了，照样能改能导出。
- 页眉显示「页面上改过，还没导出」，导出按钮变成铁锈红实底。
- **导出 JSON** 下载的是改过的 staging.json，喂回 `validate` / `render` / `blocking` 照样能用。两个坑照旧：嵌入的数据里 `<` 转义防 `</script` 截断；`revokeObjectURL` 延后调防 Safari 存出空文件。
- **还原到文件里的版本** 要点两次（第一次变成「再点一次还原」，三秒内再点才还原），防手滑；不使用浏览器的确认弹窗。

## 地点卡

一排两张。场景图（有图才有）上按 `box` 画出认出来的每件东西，鼠标移到清单的某一行或平面上的某件东西，图上对应的框亮起并标出名字。没有图时是占位，写明图应该放在 `images/<地点名>-sheet.png`。`readFrom` 是 `prompt` 的地点标「没有图，按设定提示词推算」。

## 质量底线

- 模型生成的数据一律 `esc()` 转义
- 中文文案不用单字词（坐着／站着、正北／正东，不写单字）
- `@media print`：隐藏按钮、托盘和选中面板
- `prefers-reduced-motion` 关动效
