# Trace 视觉规格 · spec

> 配套：`tokens.css`（全部取值）、`icons.svg`（72 个原创线性图标：i16 描边 1.25 / i20 描边 1.5，currentColor）、`screens/*.html`（15 组屏幕 · 82 帧）、`prototype.html`（可点击流程）、`gallery.html`（全部屏幕浅/深色）。
> 本文件只写**为什么**、**多少**、**怎么落到 AppKit**。数值以 `tokens.css` 为准；两者不一致时以 tokens.css 为准，并修正本文。

设计原则只有一条：**PDF 永远是画面主体，Trace 只在你点下去的那一刻出现；你点过 ☆ 的地方，像铅笔读过的书一样留下来。**

---

## 0. 与当前实现的差异（盘点）

| 项 | 当前实现 | 本规格 | 理由 |
|---|---|---|---|
| 浮层宽度 | 326 固定 | 340（320–380，随内容与窗口收缩） | 中文释义 13pt 在 326 下常出现单字孤行；语法页签 380 |
| 浮层圆角 | 8 | 10 | 与 macOS 26 菜单/弹出面板圆角一致，窗口 12 |
| 浮层阴影 | 单层 0.12 | 三层：0.5px 描边 + 2pt 近影 + 22pt 远影（见 `--trace-overlay-shadow`） | 实色浮层在白纸上需要清晰边缘，远影更轻 |
| 词痕迹 | 2.25pt 实线下划线 | 1.25pt 圆头点线，点距 3，基线下 2 | 实线像超链接/拼写错误；点线像铅笔，几十条仍干净 |
| 句/段记号 | 页边 11pt 竖条，内缩 8 | 2pt 竖线，栏外 10pt；段落带 4pt 端帽；并列车道 5pt | 远离正文，不挤压行首字母 |
| Hover | 150 / 100 | 150 进 / 100 出（保持） | — |
| 适合宽度容差 | 2.5% | 2.5%（保持） | — |
| 选区遮罩 | `#0A84FF` α .58 | accent α .24，`multiply`（深色 .30 / 正常混合） | .58 让黑字变灰蓝、难读；multiply 保留字形对比 |
| 侧栏宽 | 244 | 244（保持），1024 宽时自动收起 | — |
| 工具栏高 | 52 | 52（保持） | — |

---

## 1. Token

### 1.1 颜色（浅色 / 深色）

| Token | 浅色 | 深色 | 用途 |
|---|---|---|---|
| `--trace-window` | `#f4f4f3` | `#1e1e1f` | windowBackgroundColor |
| `--trace-toolbar` | `#f8f8f7` | `#262627` | 统一工具栏 |
| `--trace-sidebar` | `#ececea` | `#232324` | 侧栏（不透明近似，实机用 `NSVisualEffectView .sidebar`） |
| `--trace-content` | `#ffffff` | `#1b1b1c` | 列表、表单 |
| `--trace-canvas` | `#e4e3df` | `#141414` | PDFView 背景：略暖的“书桌灰” |
| `--trace-surface` / `--trace-overlay-bg` | `#ffffff` | `#2b2b2d` | Reader 浮层：**不透明实色** |
| `--trace-surface-sunken` | `#f5f5f3` | `#232325` | 浮层内注释框 |
| `--trace-label` | `rgba(0,0,0,.86)` | `rgba(255,255,255,.88)` | 主文字 |
| `--trace-label-secondary` | `.56` | `.56` | 次要 |
| `--trace-label-tertiary` | `.34` | `.34` | 辅助 |
| `--trace-separator` | `rgba(0,0,0,.10)` | `rgba(255,255,255,.10)` | 细分隔线 |
| `--trace-accent` | `#0a70e8` | `#2f8cff` | 跟随系统强调色（controlAccentColor） |
| `--trace-selection` | accent α .24 · multiply | accent α .30 | 选区遮罩、查词激活遮罩（二者共用） |
| `--trace-positive / caution / negative` | `#3d8b5a / #a8741f / #bf4a3c` | `#6cbf88 / #d8a24e / #eb7c6c` | 连接状态点、OCR 失败 |
| `--trace-scrim` | `rgba(0,0,0,.12)` | `.30` | ⌘K、引导 |
| `--trace-focus-dim` | `rgba(20,18,14,.15)` | `rgba(0,0,0,.15)` | 专注阅读压暗 15% |

**增强对比度**（`prefers-contrast: more` / `accessibilityDisplayShouldIncreaseContrast`）：次要/辅助文字提到 .72/.55，分隔线 .32，浮层描边 .55，墨色不透明度 1，点线加粗到 1.75pt，重逢点 .6。

### 1.2 墨色（跟随**纸面**而非 App 外观）

| Token | 纸面浅 | 纸面暗（压暗/反色） |
|---|---|---|
| `--trace-graphite`（默认） | `#5b6778` | `#a4b0c2` |
| `--trace-ochre` 赭石 | `#9a6a37` | `#d3a672` |
| `--trace-indigo` 靛青 | `#47588b` | `#9eadde` |
| `--trace-moss` 苔绿 | `#5b7351` | `#a3b993` |
| `--trace-vermilion` 朱砂 | `#a2513e` | `#de9480` |
| 点线不透明度 | .78 | .82 |
| 页边记号不透明度 | .72 | .78 |
| 高亮 citron / mint / rose / sky | α .46/.42/.42/.45 · multiply | α .30/.28/.28/.30 · screen |

深色 App + 原色纸面时，墨色仍用浅纸值——墨是画在纸上的，不是画在窗口上的。

### 1.3 字体与字号

| Token | 值 | 用途 |
|---|---|---|
| `--trace-type-headword` | 17 · New York Semibold | 查词词头（衬线 17 与 SF 14 视觉等重） |
| `--trace-type-title` | **14** | 浮层标题、页签标题 |
| `--trace-type-body` | **13** / 行高 20 | 中文释义、译文 |
| `--trace-type-context` | 12 / 行高 18 | 语境补充（次要色），段前 7 |
| `--trace-type-meta` | **11** | 词性、页签、日期 |
| `--trace-type-caption` | 10 | 角标、计数 |
| `--trace-type-ipa` | 12.5 · Charis SIL → Times | 音标 |

中文：PingFang SC；等宽：SF Mono（快捷键、导出预览）。

### 1.4 间距 · 尺寸

| Token | 值 |
|---|---|
| 网格 | 4 / 8 / 12 / 16 / 20 / 24 / 32 |
| 浮层水平内边距 | **16** |
| 浮层底部内边距 | **12** |
| 浮层分区间距 | **8** |
| 浮层顶部 grip 区 | 14（grip 32×3） |
| 页签栏 | 30，指示条 20×2 |
| 浮层与触发词距离 | 8；距 viewport 边 ≥ 8 |
| 正文不出滚动条行数 | **≤ 8 行** |
| 圆角 | 浮层 10 · 控件 6 · 缩略图 3 · 窗口 12 |
| 点线 | 线宽 1.25 · 距基线 2 · 点距 3 · 嵌套下移 3.25 |
| 页边记号 | 线宽 2 · 栏外 10 · 车道 5 · 端帽 4 |
| 重逢点 | 3pt 静止圆点，词尾右上，不透明度 .35（深色 .45，增强对比度 .6） |
| 选区把手 | 圆点 10 · 竖杆 2 · 命中区 union + 12 |

### 1.5 窗口

| 窗口 | 尺寸 | 最小 |
|---|---|---|
| 阅读 | 1440×900 | 1024×640（< 1100 自动收起侧栏） |
| 书库 | 1280×800 | 820×560 |
| 足迹 | 1200×780 | 900×560 |
| 设置 | 760×560 | 固定 |

---

## 2. 交互常量（继承 PRD，不改）

- 双击判定窗口 **350ms**：按下立即出现遮罩（0ms），350ms 内无第二击才发起查词。
- 浮层全部**不透明**；不抢 First Responder；整块实色区域吃鼠标。
- 滚动累计 **120pt** 关闭浮层（150ms 淡出）；Hover **150ms** 延迟出现 / **100ms** 消失。
- 触发点在 viewport **上部三分之一** → 浮层固定向下展开；其余优先上方，放不下再向下。
- 重叠 Anchor：**只取最小 Anchor**（面积最小，其次字符数最少）。
- 字号 14 / 13 / 11；内边距 水平 16 · 底 12 · 分区 8；8 行以内无滚动条。

---

## 3. 动效

总则：**100–260ms，无回弹**（所有曲线超调 ≤ 2%）。出现用 standard，消失用 exit，且消失比出现快约 20ms。

### 3.1 曲线

| 名称 | cubic-bezier | AppKit |
|---|---|---|
| standard | `(0.2, 0, 0, 1)` | `CAMediaTimingFunction(controlPoints: 0.2, 0, 0, 1)` |
| exit | `(0.4, 0, 1, 1)` | 同上 |
| pencil | `(0.45, 0.05, 0.3, 1)` | 起笔略慢，收笔轻 |
| settle | `(0.25, 0, 0.15, 1)` | 星标回落 |
| linear | — | 骨架、流式淡入 |

### 3.2 时长

| 动作 | 时长 | 曲线 | 描述 |
|---|---|---|---|
| 按下遮罩 | 0 | — | mouseDown 立即绘制 |
| 浮层出现 | **140** | standard | opacity 0→1，scale **0.97→1**，原点在朝向触发词的边 |
| 浮层消失 | 120 | exit | 仅 opacity |
| 流式段落 | **80** | linear | 每个已解析字段整段淡入，不逐字 |
| 高度变化 | **120** | standard | 保持朝向触发词的那条边不动 |
| 页签指示条 | **160** | standard | 位移 + 宽度；内容 120ms 交叉淡化 |
| 星标 | **180** | settle | scale **1.12→1**，☆ 变 ★ 同帧 |
| 铅笔点线 | **220 / 行** | pencil | 从左到右 clip 展开；多行依次，行间无间隙 |
| 页边记号 | **200** | standard | 从竖向中点向两端生长 |
| 取消保存 | **150** | exit | 点线/记号淡出 |
| 拖动浮层阴影 | 100 | standard | 切换到 `--trace-overlay-shadow-drag` |
| 回到原文闪烁 | **480** | linear | 不透明度 **1→.4→1** 两次，随后 520ms 打开旧内容 |
| 命令面板 | 140 | standard | 淡入 + 下移 4pt |
| Toast | 160 进 / 1200 停 / 200 出 | standard / exit | “适合宽度”等 |
| 侧注栏展开 | 220 | standard | 宽 0→280 |
| 骨架扫光 | 1400 循环 | linear | |
| OCR 扫描光带 | 1600 循环 | linear | |
| 示例页呼吸提示 | 2400 循环 | ease-in-out | 不透明度 .25↔.85；点过一次永久消失 |

### 3.3 减少动态效果

`NSWorkspace.shared.accessibilityDisplayShouldReduceMotion` 为真时：

- 所有位移、缩放 → **150ms 纯淡入淡出**；
- 铅笔点线、页边生长、回到原文闪烁 → **0ms**，直接出现终态（闪烁改为 1 次 300ms 的淡底）；
- 骨架扫光停止，保留静态骨架；星标不缩放。

---

## 4. 键盘

| 键 | 作用 |
|---|---|
| Esc | 关闭命令面板 → 关闭浮层/操作条 → 清除选区 → 停止朗读（逐级） |
| ⌘S | 保存 / 取消保存当前浮层（星标） |
| ⌘1–⌘5 | 查词 · 翻译 · 详解 · 语法 · 注释（浮层内；不可用的页签灰显） |
| ⌘0 | 适合宽度 |
| ⌘K | 命令面板：跳页、找痕迹、搜正文、执行操作；输入数字直接跳页 |
| ⌘⇧F | 足迹窗口 |
| ⌘⇧R | 朗读选区（再按停止） |
| ⌘⇧L | 专注阅读（阅读标尺） |
| ⌥⌘P | 段落对照侧注栏 |
| 按住 ⌥ | OCR 模式下显示识别词框 |

全部菜单项同时出现在菜单栏，保证 VoiceOver 与键盘用户可达。

---

## 5. AppKit 映射

| 设计元素 | AppKit 实现 | 备注 |
|---|---|---|
| 阅读窗口 | `NSWindow` + `NSSplitViewController`（侧栏 item `.sidebar`） | 外层用 frame/autoresizing 隔离 fitting（AGENTS §6） |
| 工具栏 | `NSToolbar` unified，`NSToolbarItemGroup` 分段 | OCR 状态为 4 态 item：关闭/处理中/开启/失败 |
| PDF 主体 | `InteractivePDFView: PDFView`，`backgroundColor = canvas` | 页面阴影用 `pageShadowsEnabled`，不自绘 |
| 浮层宿主 | `ReaderOverlayLayerView`（裁剪到 viewport） | 所有浮层都挂在这里，禁止 `NSPopover`/`NSPanel` |
| 助手浮层 | `NSView` + `CALayer`（`backgroundColor` 实色，`cornerRadius 10`，`cornerCurve .continuous`） | 阴影用两个 sublayer，`shadowPath` 预设避免离屏渲染 |
| 页签 | 自绘 5 个 `NSButton(.inline)` + 指示条 `CALayer` | 指示条 `CABasicAnimation position/bounds` 160ms |
| 流式文本 | `NSTextField(wrappingLabel:)` 每段一个；新增段 `alphaValue` 动画 | 不用 `NSTextView`，避免抢焦点 |
| 星标 | `NSButton` + `CAKeyframeAnimation transform.scale [1.12, 1]` | |
| 铅笔点线 | `ReadingMarkRenderer` → 每页一个 `CAShapeLayer`（`lineDashPattern [0, 3]`，`lineCap .round`） | 保存动画：`strokeEnd` 0→1，多行 `beginTime` 依次偏移 220ms |
| 页边记号 | `CAShapeLayer`，`strokeStart/End` 从 .5 → 0/1 | |
| 高亮 | `CALayer` `compositingFilter = "multiplyBlendMode"`（暗纸 `screenBlendMode`） | 不写 PDF annotation 为事实来源 |
| 选区遮罩/把手 | 自定义 layer，共用 `PDFSelection.bounds(for:)` | 放大镜：`NSView` 以 `PDFPage.draw(with:to:)` 2× 绘制，非截屏 |
| Hover 预览 | 同浮层 view，`hitTest` 返回 nil | 不接受鼠标 |
| 操作条 | 自绘 `NSView`，presenter frame 为 authoritative hit region | AGENTS §6 |
| 缩放 HUD | `NSView` 实色 116×52，radius 12，`ignoresMouseEvents` 语义 | |
| 命令面板 | 挂在 overlay 宿主的 `NSView` + `NSTextField`；`NSTableView` 结果 | 打开时临时成为 First Responder，Esc 后还给 PDFView |
| 侧注栏（段落对照） | Split 右侧第三列 `NSSplitViewItem(inspectorWithViewController:)` 宽 280 | 可折叠 |
| 专注阅读 | overlay 底层一个 `CAShapeLayer` 偶奇填充（整页压暗减去标尺） | |
| 朗读 | `AVSpeechSynthesizer` + `willSpeakRangeOfSpeechString` 驱动当前词淡底 | 迷你播放条挂 overlay 底部 |
| 书库 | `NSCollectionView`（网格）/ `NSTableView`（列表） | 封面 `PDFPage.thumbnail(of:for:)` 后台生成缓存 |
| 足迹窗口 | 独立 `NSWindow` + 三栏 `NSSplitViewController` | 原页裁图同放大镜方式绘制 |
| 设置 | `NSTabViewController(.toolbar)` 5 页 | 快捷方式矩阵为 `NSGridView` |
| 夜间纸面 | 压暗：页面上 `CALayer` 黑 α .28；反色：`CIColorInvert` + `CIHueAdjust(π)` 作 `PDFView` layer filter，图片区域再反一次 | 见 §8 高代价效果 |
| SwiftUI | **只允许**用于设置里的“标记与外观实时预览”小组件（`NSHostingView`），其余一律 AppKit | 遵守固定技术边界 |

---

## 6. 新功能与优先级

标签：**核心**（P0 视觉升级，直接服务连续阅读 / 留痕）、**增强**（P1）、**探索**（P2，需要单独立项）。

| 功能 | 级别 | 服务哪条铁律 | 屏幕 |
|---|---|---|---|
| 铅笔点线痕迹 + 页边记号 + 保存动效 | 核心 | 更快留下值得回看的痕迹，几十条仍干净 | traces, assistant |
| 浮层五页签（查词/翻译/详解/语法/注释）+ ⌘1–5 | 核心 | 同一浮层内完成，不离开页面 | assistant |
| 首次启动三步引导 + 示例页呼吸提示 | 核心 | 第一分钟就读起来 | onboarding |
| OCR 四态控件 + 扫描光带 | 核心 | 扫描 PDF 也能连续读 | ocr |
| 快捷方式矩阵 / 模型服务卡片 / 阅读助手按动作配置 | 核心 | — | settings |
| 书库分组、拖入导入、进度线 | 核心/增强 | 回到上次读到的地方 | library |
| 语法可视化（主干/一层/全部） | 增强 | 长句读不下去时不离开 | grammar |
| 足迹窗口 + 回到原文闪烁 + 随手重访 | 增强 | 回看痕迹；不计分、不排任务、不提醒 | footprints |
| 导出 Markdown / Anki CSV | 增强 | 痕迹带走（只导出，不做 SRS） | footprints |
| ⌘K 命令面板 | 增强 | 键盘不离手 | command |
| 夜间纸面：压暗 / 反色 | 增强 | 夜读时长 | night |
| 专注阅读标尺、朗读选区 | 增强 | 长时间阅读 | focus |
| 段落对照侧注栏 | 增强 | 难段落不打断 | parallel |
| 重逢点：在别的文档遇到保存过的词 | 增强 | 痕迹跨文档复现，不打扰 | traces/reunion |
| 引用 / 图 Hover 预览、页眉页脚识别 | 探索 | 论文连续读 | paper |

**明确不做**：生词自动标记、词汇量、SRS/FSRS、学习任务、Dashboard、账号、同步、统计图表。重访卡片只随机抽一条，没有“今日任务”。

---

## 7. 数据模型改动

现有：`documents / anchors / anchor_rects / records(type ∈ lookup, translation, explanation, grammar, note)`，最新迁移 `v4_library_folders`。以下按功能分批，都走 `RecordRepository`，一次迁移一个批次。

### 7.1 `v5_reading_marks` — 高亮（核心）

高亮是“内容重要”，不是一次查询，不放进 `records`：

```sql
CREATE TABLE highlights (
  id TEXT PRIMARY KEY,
  anchor_id TEXT NOT NULL REFERENCES anchors(id) ON DELETE CASCADE,
  color TEXT NOT NULL,            -- citron | mint | rose | sky
  created_at DATETIME NOT NULL
);
CREATE UNIQUE INDEX highlights_anchor ON highlights(anchor_id);
```

`anchors` 增加 `granularity TEXT NOT NULL DEFAULT 'phrase'`（word | phrase | sentence | paragraph），决定渲染成点线还是页边记号；旧数据按字符数与是否跨句回填。

### 7.2 语法 / 详解 / 注释（核心 → 增强）

复用已存在的 `RecordType.grammar / explanation / note`，不新增枚举。`grammar` 的 `content` 存 JSON：

```json
{ "v": 1, "spans": [{"role": "subject|predicate|object|complement|clause|modifier", "from": 0, "to": 3, "level": 0, "label": "原因状语从句", "head": 5}], "skeleton": "…" }
```

token 下标相对 Anchor 的 `selectedText`，渲染时重新映射到 PDF page rect，不存屏幕坐标。

### 7.3 段落对照（增强）

译文默认临时，不落库；点 ☆ 保存时写成普通 `translation` record，新增列 `records.origin TEXT NOT NULL DEFAULT 'overlay'`（overlay | sidecar），仅用于足迹里的来源图标。

### 7.4 重逢（增强）

不建新表。由 `records(type = lookup)` 的 `original_text` 规范化（小写、词形还原）得到 lemma，内存中建 `ReunionIndex: [lemma: [anchorID]]`，文档打开时对当前页 PDFKit 文本做一次匹配。
`records` 增加 `lemma TEXT NULL` + 索引 `(lemma)` 以避免每次全表扫描。**只匹配用户保存过的词，不做全文生词分析。**

### 7.5 足迹 · 随手重访（增强）

```sql
CREATE TABLE revisit_log (
  anchor_id TEXT NOT NULL REFERENCES anchors(id) ON DELETE CASCADE,
  shown_at DATETIME NOT NULL
);
```

仅用于“最近 7 天展示过的不再抽”，不计分、不排期。

### 7.6 文档偏好（增强）

`documents` 增加：

- `page_appearance TEXT NOT NULL DEFAULT 'original'`（original | dim | invert），按文档记忆；
- `sidecar_open INTEGER NOT NULL DEFAULT 0`。

### 7.7 引用索引（探索）

`CitationIndex` 首版只放内存：打开文档时解析 References 段落，缓存 `[label: (pageIndex, rect)]`；需要持久化时再按 `document_id + pipeline_version` 建表，同 OCR cache 语义。

### 7.8 UserDefaults 新键

| Key | 类型 | 默认 |
|---|---|---|
| `trace.mark.ink` | String | `graphite` |
| `trace.mark.weight` | Double | `1.25` |
| `trace.mark.style` | String | `dotted`（dotted / solid） |
| `trace.highlight.defaultColor` | String | `citron` |
| `trace.onboarding.completed` | Bool | false |
| `trace.onboarding.sampleHintDismissed` | Bool | false |
| `trace.reader.focusRuler` | Bool | false |
| `trace.reader.reunionDots` | Bool | true |
| `trace.speech.voice` / `trace.speech.rate` | String / Double | 系统默认 / 0.5 |
| `trace.library.grouping` | String | `none` |

---

## 8. 高代价效果与替代方案

| 效果 | 代价 | 首选实现 | 退路 |
|---|---|---|---|
| 反色纸面（图片不反色） | 每页滤镜 + 图片区域检测 | `PDFView` layer filter `CIColorInvert + CIHueAdjust(π)`；图片区域来自 `PDFPage` 的 image XObject bbox，叠加一层原图 | 只提供“压暗”（黑 α .28 覆盖层），反色标为实验 |
| 拖选放大镜 | 每次 drag 重绘 | 只画 120×40 区域，2× 光栅化，节流到 60Hz | 不显示放大镜，仅把手放大反馈 |
| 铅笔多行动画 | 多条 shape layer 同时动画 | 每行一个 `CAShapeLayer`，结束后合并进页面静态 layer | 减少动态效果时直接出现终态 |
| 30+ 痕迹常驻 | 每次滚动/缩放换算 rect | 每页一个 layer，只在 page 进入可见区域时生成；scale 变化后重建 path，不做逐帧换算 | 缩放手势进行中隐藏痕迹，`.ended` 后一次性重绘 |
| 语法弧线与括号 | 文本测量 + 布局 | 以 `PDFSelection` 每个 token 的 bounds 为输入，在浮层内自绘 | 仅显示句子骨架 + 成分色块，不画弧线 |
| 段落对照按需翻译 | 网络与费用 | 只翻译可见段落，滚动停止 300ms 后补齐，latest-wins | 手动“翻译此段” |
| 重逢点匹配 | 每页文本扫描 | 只在页面首次可见时，对已保存 lemma 集合做哈希查找 | 设置里默认关闭 |
| OCR 扫描光带 | 动画常驻 | 只在“处理中”显示，单层渐变 `CAGradientLayer` 位移 | 静态进度环 |
| 骨架扫光 | 动画常驻 | 一个共享 `CAGradientLayer`，浮层关闭即移除 | 静态骨架 |
| 背景模糊（工具栏/侧栏） | 合成开销 | 系统 `NSVisualEffectView` | 实色（本规格浮层本来就不透明） |

---

## 9. 可访问性与适配

- **浅色 / 深色 / 增强对比度**：三套取值见 §1；gallery 每帧浅深并排，`screens/*.html?contrast=more` 预览增强对比度。
- **1024 宽**：侧栏自动收起为工具栏按钮；浮层宽降到 320；操作条收成图标。
- **灰度**：痕迹类型不依赖颜色区分——点线（词/词组）、实竖线（句）、带端帽括号（段）、色块（高亮）；灰度检查帧见 `traces?solo=grey`。
- **VoiceOver**（帧 `reader?solo=voiceover`）：
  - 浮层为 `NSAccessibilityElement` role `.popover`，标题读“查词：bewildered”；流式完成时发 `.announcementRequested`，不在每个增量发。
  - 痕迹作为 PDF 页面的自定义 accessibility children，读“已保存查词：bewildered，第 214 页”。
  - 星标按钮 label 在“保存 / 取消保存”间切换。
  - 快捷方式矩阵每格读“单击：查词”。
  - 转子新增“痕迹”：VO-U 后逐条跳转本页痕迹。
- **减少动态效果**：见 §3.3。
- **键盘**：所有功能有菜单项；浮层可 Tab 在页签与按钮间循环，Esc 返回 PDF。

---

## 10. 三个值得记住的细节

1. **铅笔，而不是荧光笔。** 保存那一刻，☆ 以 1.12→1 落定，随后一条 1.25pt 圆头点线从词首划到词尾——220ms/行，起笔略慢、收笔轻。满页 39 条痕迹仍像一本被认真读过的书。
2. **重逢。** 你在另一本书里再遇到曾保存过的词，词尾右上会有一个 3pt、透明度 .35 的静止小点；Hover 告诉你“在《Walden》第 88 页保存过”。数据来自本地词形索引，不调用模型、不提醒、不计数。
3. **回到原文。** 在足迹里点“回到原文”，阅读窗口滚到那一页，那条痕迹以 1→.4→1 闪两次，然后打开你当时的查词——像手指在书页上点了一下“就是这儿”。

---

## 11. 文件清单

```
design/
  tokens.css            全部 token（浅/深/增强对比度/减少动态效果）
  icons.svg             72 个图标 symbol（i16 / i20 两套网格）
  gallery.html          全部屏幕，浅色 + 深色
  prototype.html        可点击流程（启动 → 书库 → 查词 → 保存 → 拖选翻译 → 语法 → Hover → 足迹 → 回到原文）
  spec.md / spec.html   本文件
  screens/*.html        15 组屏幕，82 帧；?solo=<帧>&theme=dark&contrast=more&anno=0
  assets/               渲染 kit、原型状态机、示例数据
```
