# 多主题统一管理整合方案
> 基于对 template.html / template-swiss.html 两份模板与全部 references 的逐行对比分析(2026-07)。
> 本文档是**方案建议**,尚未实施;按阶段推进,每个阶段独立可交付、可回滚。
## 现状诊断
两个主题是"复制粘贴后各自演化"的关系,同一份基础设施存在多份副本,并已实际漂移:
### 运行时 JS 漂移(约 250 行 × 每主题一份)
| 模块 | A 杂志风 | B 瑞士风 |
|---|---|---|
| 低功耗事件名 | `ppt-low-power-change` | `swiss-low-power-change` |
| localStorage key | `guizang-ppt-low-power`(共用!) | 同左 |
| ESC 索引缩略图可见性修复 | ✅(本次补齐) | ✅ |
| `?slide=N` 直达参数 | ✅(本次补齐) | ✅ |
| Windows 字重补偿 `is-win` | 缺失 | ✅ |
| prefers-reduced-motion 自动降级 | ✅(本次补齐) | ✅ |
| 翻页/滚轮/触屏/键盘核心 | 两份逐字符几乎相同 | 同左 |
### Token 体系成熟度不一
- **B 最成熟**:主题色 + Carbon 文字角色 token + `--sp-3~13` 8px 间距阶梯 + motion token + `--nav-safe-bottom`
- **A 最简陋**:只有 6 个颜色变量,层级靠 opacity 表达,存在 10-11px 小字(投屏不可读),`.lead/.chrome/.foot` 在模板内部重复定义(v1 与 v2 API 叠加)
### 已知具体坏味道(可立即修的小项)
1. B 模板 `#hint` 引用了未定义变量 `--ink-tint`(靠 fallback 兜底)
2. B 模板 pipeline 死代码:`__pipeAdvance` 恒返 false,但 43 行 pipeline CSS 和动效 CSS 还在
3. B 的 motion token 双维护:CSS 定义了 `--ease-prod`,JS 里又硬编码 `EASE_PROD=[.2,0,.38,.9]`
4. A 是唯一没有校验器的主题,但 validate-swiss-deck.mjs 的标题选择器已经包含 A 的类名(`.display/.h1-zh` 等),说明测量层本来就通用
## 整合原则
**不建议**把两个模板合并成一个"大一统模板"——两种风格的美学规则互斥(直角 vs 圆角、衬线 vs 无衬线),强行合并会让类名前缀和条件分支爆炸,也违背"每份 deck 单文件交付"的定位。
**建议**的整合层次:**共享的是"基建"和"契约",不共享"皮肤"**。
## 分阶段方案
### Phase 1 · 共享运行时(收益最大)
把各主题漂移的 JS 收敛为一份 `assets/runtime.js`(构建时内联进各模板,保持单文件交付):
- 翻页核心 go() / 键盘 / 滚轮 / 触屏 / nav 圆点 / ESC 索引(含缩略图可见性修复)
- 低功耗 IIFE:统一事件名为 `deck-low-power-change`,统一 localStorage key,保留各主题旧事件名一个版本作为 alias
- `?slide=N`、prefers-reduced-motion、Motion One 加载器
- 主题差异通过配置对象注入:`initDeck({ darkClass:'dark', onSlideChange(){...} })`
实施方式二选一:
- **a. 构建脚本**(推荐):`scripts/build-templates.mjs` 把 `src/runtime.js` + 各主题 `src/style-*.css` + 皮肤 HTML 组装成各 template*.html。模板仍是完整单文件,但源头唯一。
- **b. 文档约定**:不引入构建,把 runtime 段落用 `` 注释标记,改动时用脚本校验各份一致。成本低但漂移风险仍在。
### Phase 2 · 公共 Token 契约
在各模板间统一**命名和语义**(值可以不同):
- 间距:全部采用 B 的 `--sp-*` 8px 阶梯(A 补齐)
- 文字角色:`--text-primary/secondary/helper/on-color` + `--border-subtle/strong` 各主题同名(A 需要从 opacity 方案迁移)
- 安全区:`--nav-safe-bottom` + `.nav-safe-bottom(-tight)` 各主题同名(已完成:B 有,A 待补)
- Motion:`--ease-*/--dur-*` 统一命名,JS 从 CSS var 读取,消除双维护
- 最小字号地板:A 对齐 B 的"正文 ≥16px、caption ≥14px"投屏标准(A 现存 10-11px 小字需要清理)
### Phase 3 · 校验器公共核
抽 `scripts/lib/validate-core.mjs`:
- slide 解析、Playwright 加载(双根解析 + try/finally + swiftshader)、isMeaningful 过滤器、M1 溢出/底部空白/nav 安全线、M2 标题间距、overflowFix 分级建议
- 各主题瘦身为 config + 专属静态规则:`validate-swiss-deck.mjs`(版式锁定)、**新增 `validate-magazine-deck.mjs`**(A 目前裸奔,公共核直接就能给它 M1/M2 测量)
### Phase 4 · References 去重
- `themes*.md` 各份结构完全同构 → 统一"主题卡片 schema",每主题只留数据
- 图片规则目前散在 4+ 处且 A/B 规则有分叉(A:信息图必须 fit-contain;B:重生成图禁止 fit-contain)→ 合并为单一来源 `references/images.md`,分叉处显式按主题参数化
- 动效说明多处重复 → 收敛到各 layouts 文件,组件文档只放链接
- 清理 golden source 绝对路径:改为相对于 `` 的路径或删除
### 顺手修复清单(可与任一阶段同批)
- [ ] A:`.lead/.chrome/.foot` 双定义合并
- [ ] A:补齐 `--nav-safe-bottom` 安全区 token
- [ ] B:删 pipeline 死 CSS 或恢复 `__pipeAdvance`
- [ ] B:`--ink-tint` 未定义引用
- [ ] B:JS 缓动改读 CSS var
- [ ] 文档:去除 `/Users/guohao/...` 绝对路径(已完成)
- [ ] 各模板共有:`file://` 直开时 module 动态 import 本地 `./assets/motion.min.js` 被 Chrome CORS 拦截,实际总是走 jsDelivr CDN;离线 + 直开的组合只能拿到静态降级。共享 runtime 时考虑把 motion 关键函数直接内联进模板,或文档明确"离线演示请起本地 server"
## 建议排序
1. **Phase 1a(构建脚本 + 共享 runtime)** —— 一次性消灭最大的漂移面
2. **顺手修复清单** —— 全部是小改动,跟 Phase 1 同一个 PR
3. **Phase 3(校验器公共核 + A 校验器)** —— A 从此也有质量护栏
4. Phase 2、4 可以慢慢来,不阻塞新主题
完成 Phase 1+3 后,再增加"新主题"的边际成本会从"复制 2000 行再改"降到"写一份皮肤 CSS + 一份 layouts 文档 + 一个 validator config"。