# 演讲者模式与演讲备注契约

制作任何需要现场讲述的 deck 时加载本文件。目标不是把页面文案复制到备注区，而是让页面、讲述、转场、时间和现场控制形成一条可以排练和可以恢复的叙事链。

## 1. 页面计划同时规划“看什么”和“说什么”

在写 HTML 前，先做一张表。只填用户大纲、素材或已确认的现场信息能够支持的列：

| 页码 | `data-slide-id` | 章节 | 页面目的 | 观众屏可见信息 | 演讲者补充 | 建议时长 | 转场 | 可选现场信息 |
|---|---|---|---|---|---|---:|---|---|

- **观众屏可见信息**：观众此刻必须读到的结论、证据、结构或图像。
- **演讲者补充**：背景、例子、判断依据、语气和细节，不要逐字复述页面。
- **转场**：解释下一页为什么紧接着出现，不要只写“下一页”。
- **可选现场信息**：可包含停顿、提问、Demo、视频、敏感信息、翻页时机、备用方案或读音。

默认生成“提词卡”，不是逐字稿。只有用户明确要求逐字稿时，才写完整口播。

## 2. 模型如何把大纲信息带进演讲模式

对用户的大纲、项目记录和补充说明做结构化提取，不要自行增加现场事实。

- 已给出章节名或可靠推断的连续章节：写入 `section`。
- 已给出总时长或逐页节奏：可给出 `minutes`，总建议时长不超过现场时长的 90%。
- 大纲明确写了停顿、提问、Demo、视频、语气、翻页点或备用路径：分别写入对应的可选字段。
- 用户明确要求某页自动停留多少秒：才写 `autoAdvanceSeconds`。
- 没有提供的信息不猜测、不写“待补充”到演讲界面，直接省略字段。界面对缺失时长显示横杠，其他可选模块整段隐藏。
- 如果页面在未运行校验的情况下临时新增，且没有对应备注，演讲者界面只显示中性横杠 `—`，不显示“待补充”或虚构内容；校验器仍应报告备注与页面不一致。

`minutes` 是讲述计划，`autoAdvanceSeconds` 是播放行为，二者必须分开。不得因为一页建议讲 1 分钟，就默认 60 秒后自动翻页。

## 3. 稳定的页面 ID

每一页都必须有唯一、语义化且稳定的 ID：

```html
<section class="slide ..." data-slide-id="codepilot-capabilities">
```

- 使用小写英文 slug：`cover`、`codepilot-capabilities`、`demo-workflow`、`closing`。
- 页面重排时保留原 ID；只有页面语义彻底改变时才更换。
- 不要用页码作为 ID。演讲者在浏览器里修改的备注按 ID 保存，稳定 ID 可避免重排后串页。

## 4. `SPEAKER_NOTES` 数据结构

在 deck 中保留一条与每页 ID 对应的记录，顺序必须与 slide 完全一致：

```js
const SPEAKER_NOTES = [
  {
    id: 'codepilot-capabilities',
    title: 'CodePilot 能做什么',
    section: 'CodePilot',
    minutes: 0.9,
    purpose: '先让观众建立完整产品认知，再进入实现过程',
    talk: [
      '从用户动作讲能力，不先讲底层 Harness',
      '用首页、多模型、Skills 和侧边栏四张截图建立产品全貌',
      '强调模型、工具和上下文可以在同一工作流里协作'
    ],
    transition: '观众知道产品长什么样后，再回答为什么要这样实现',
    cue: '按截图顺序指向四个能力区',
    advance: '讲完“同一工作流”后翻页'
  }
];
window.__SPEAKER_NOTES__ = SPEAKER_NOTES;
```

必填字段：

- `id`、`title`、`purpose`、`talk`、`transition`。
- `talk` 默认 3–5 条，每条只表达一个意思；封面、章节页和纯过渡页可以更短。

可选字段：

| 字段 | 用途 | 界面缺失时 |
|---|---|---|
| `section` | 章节名和章节进度 | 隐藏章节行 |
| `minutes` | 本页建议讲述分钟 | 显示 `—` |
| `cue` | 停顿、Demo、视频、敏感信息提醒 | 隐藏 |
| `interaction` | 现场提问、举手、投票或 Q&A | 隐藏 |
| `delivery` | 语气、重音、语速或停顿 | 隐藏 |
| `advance` | 人工翻页的句子或动作时机 | 隐藏 |
| `fallback` | Demo / 视频 / 网络失败时的备用说法 | 隐藏 |
| `pronunciation` | 人名、缩写和外语读音 | 隐藏 |
| `autoAdvanceSeconds` | 本页自动停留秒数，优先于全局间隔 | 使用全局间隔或不自动翻页 |

`cue`、`interaction`、`delivery`、`advance`、`fallback`、`pronunciation` 可以是字符串，也可以是字符串数组。

## 5. 演讲者界面行为

- 普通 deck 的右下角控制区显示 `P 演讲模式`，不单独悬浮一个抢眼按钮。
- 点击后当前窗口进入演讲者视图，并打开独立观众屏。
- 主体保持两栏：左侧预览、右侧备注；左侧当前页在上、下一页在下。
- 当前页与下一页 iframe 始终严格保持 `16:9`。空间不足时留边并整体等比缩小，不得裁切、压扁或让页面内部文字重新排版。
- 预览 iframe 只在初次挂载时加载 HTML，翻页时通过 `postMessage` 发送页码，不反复改写 `src` 重载整份演示稿。
- 小屏优先缩小下一页预览，把空间留给当前页。
- 底栏分成三段：左侧是 `已进行 / 本页 / 剩余或超时` 三组时间，中间是两行控制（第一行 `首页 / 上一页 / 下一页 / 尾页`，第二行 `开始计时或暂停 / 重置计时 / 排练`），右侧只显示 `第几页 / 总页数` 和完成百分比。不要在底栏重复当前页标题。计时按钮必须明确写成“开始计时 / 继续计时 / 重置计时”，避免让用户误以为是重置整场演示。
- `自动翻页` 放在右上角状态栏，不占用底部翻页区。
- `宫格` 放在“当前页”标题旁。打开后直接用宫格替换当前页/下一页预览区，不要弹出突兀的全屏层；卡片显示页码、标题、章节和进度，点击页面后立即回到当前页/下一页预览。`ESC` 切换宫格。
- 右侧卡片依次显示 `标题 / 本页目的 / 草稿（备注）`，避免把标题或页面目的混进草稿正文。
- 演讲者可从“均衡 / 页面优先 / 备注优先”三种布局选择。
- 备注编辑按 `data-slide-id` 存入 `localStorage`，显示保存状态；切页后备注滚动回顶部，并允许调整字号。
- `Home` / `End`、方向键、PageUp/PageDown 与可见按钮保持一致；光标在备注编辑器中时不得触发翻页。
- 对话框已打开时，`?` 不得覆盖当前对话框；只有 `Escape` 关闭当前对话框。尾页的“下一页”预览显示“演示结束”，不重复当前页。
- 设置面板使用可辨识的卡片层级；开关使用胶囊 switch，数值间隔使用带减号、数值和加号的 stepper，不直接暴露浏览器原生复选框或粗糙数字输入框。

## 6. 时间控制与排练

- 计时器由用户显式开始，可暂停、继续和重置；不要进入模式就自动计时。
- 同时显示总实际时长、本页实际时长、本页计划、剩余或超时。
- 只有每页都有 `minutes` 时，才显示总计划和预计结束时间；部分页缺失时显示横杠。
- 有 `section` 时显示章节位置；该章节每页都有计划时长时，再显示章节剩余时间。
- 排练模式记录每页实际时长和总时长，本地保留最近 5 次。
- 排练结果只做数据汇总，不生成“AI 教练”式评判。

## 7. 自动翻页

- 默认关闭，由用户显式开启。
- 可设置全局间隔；页面存在 `autoAdvanceSeconds` 时优先使用页面值。
- 进入总览、正在圈选、打开设置、浏览器页面不可见、观众屏黑屏/白屏/冻结或观众屏失去同步时，自动倒计暂停。
- 离开暂停状态后从原剩余时间继续，不重头计时。
- 到尾页后停止，不自动回到首页。

## 8. 激光笔、圈选与观众屏控制

- `L` 切换激光笔，红点短暂淡出，不保存。
- `C` 切换圈选，圈选保留在当前页；翻页时清除。
- `X` 清除当前页标注。
- 所有坐标归一化后传给观众屏，不受演讲者屏和观众屏尺寸差异影响。
- `B` 切换观众屏黑屏，`W` 切换白屏，`F` 冻结/恢复观众屏。
- 冻结时演讲者可以继续翻页，观众屏保留原页；恢复时立即追平演讲者当前页。

## 9. 观众屏同步与恢复

模板同时使用窗口 `postMessage`、`BroadcastChannel` 和 `storage` 事件同步。观众页在加载和每次翻页后回传确认，演讲者端显示：

- **连接中**：观众窗口已打开，尚未收到确认。
- **已同步**：观众页确认的页码与演讲者页相同，心跳有效。
- **未同步**：观众页心跳仍有效，但两端页码或序列号不同。
- **已冻结**：观众屏故意保留原页，不是同步故障。
- **未连接**：观众窗口未打开、已关闭，或心跳已超时。即使某些内置浏览器的 `window.closed` 代理不可靠，心跳超时后也必须落到“未连接”。
- **弹窗被拦截**：浏览器阻止打开观众窗口。

“重新打开观众屏”必须始终可用，并在恢复后立即发送当前页。

演讲者退出演讲模式时必须发送 `bye`。由脚本打开的观众窗口尝试自动关闭；如果浏览器不允许自关，则保留深色“演示已结束”遮罩，不得继续停留在最后一页假装演示仍在进行。

浏览器只能确认观众**页面/窗口**的软件同步状态，不能检测 HDMI、转接器或投影仪线缆是否物理断开。现场仍应目视确认外接屏。

## 10. 演前检查和快捷键

“检查”面板检查观众屏同步、弹窗权限、全屏、字体、图片、视频和当前页 16:9。检查结果不得宣称物理外接屏已连接。

| 键 | 行为 |
|---|---|
| `L` | 激光笔 |
| `C` | 圈选 |
| `X` | 清除标记 |
| `B` / `W` | 观众屏黑屏 / 白屏 |
| `F` | 冻结 / 恢复观众屏 |
| `A` | 开启 / 关闭自动翻页 |
| `R` | 开始 / 结束排练 |
| `?` | 打开快捷键说明 |

## 11. 能力边界

这个演讲模式只依赖当前 HTML 和浏览器本地能力，不需要账号或在线服务。不要默认加入：

- 实时字幕、语音转文字或外部语音模型。
- AI 排练教练或对演讲者的评分。
- 扫码提问、在线投票或云端互动后台。
- 手机遥控或需要服务端中继的跨设备控制。

## 12. 生成后验证

```bash
node <SKILL_ROOT>/scripts/validate-presenter-mode.mjs path/to/index.html
node <SKILL_ROOT>/scripts/validate-presenter-mode.mjs path/to/index.html --target-minutes 30
```

浏览器实测至少包含：进入演讲模式、弹窗允许/拦截、前后翻页且预览 iframe 不重载、内嵌宫格替换预览与选页返回、首页/尾页、尾页预览结束态、尾页重新开始、观众窗口关闭后显示“未连接”、重新打开后恢复同步、退出演讲后观众屏关闭或显示结束遮罩、备注保存、计时、排练记录、自动翻页暂停/恢复、激光笔、圈选、清除、黑屏、白屏、冻结、设置面板与演前检查。

至少在一组常用尺寸和一组小屏尺寸下检查：当前页/下一页上下排列，两个 iframe 宽高比均为 `16:9`，且没有超出各自容器。
