---
name: pdf-triptych
description: |
  把一份标准、规范、白皮书或长技术 PDF 拆解成一页三联图 HTML：第一张讲骨架（这份文档的结构是什么），第二张讲细节（概念之间的准确关系），第三张举例子（换成具体场景后每样东西落在哪）。三张图共用同一根横轴和同一套颜色，既有递进也有共同标尺。用于把读不动的长文档变成一页能讲、能评审、能贴进汇报的图。当用户说 /pdf-triptych、把这份 PDF 画成图、标准拆解、三联图、一张图讲清这份标准、文档骨架图、把 PDF 做成可视化 时使用。不用于普通 PDF 转文字、摘要，也不用于纯数据图表（那是 lieflat-charts）。
---

# pdf-triptych

把一份长文档拆成三张有递进、有共同标尺的图。

## 这套流程解决什么

长标准文档读不动，不是因为字多，是因为**结构藏在字里**：哪些是主线、哪些是横切、哪些只是用法，原文往往不说。直接"把 PDF 转成图"会得到一堆罗列的方框；正确顺序是先把结构挖出来，再决定用什么形状承载，最后才写代码。

三张图的分工与联系：

| 图 | 讲什么 | 与上一张的关系 |
|---|---|---|
| 一 · 骨架 | 这份文档的层级结构：从抽象到具体怎么展开，哪些定义横跨若干层，有没有第三维度 | — |
| 二 · 细节 | 概念之间的准确关系，通常是把原文里的几张 figure 揉成一张 | 与图一**同轴叠放**：图二的每个框落在图一的哪一列，就属于哪一层 |
| 三 · 例子 | 换成一个具体场景，每样东西落在哪一列 | 与图一**同列**：沿竖虚线往上看就知道它对应结构的哪一层 |

共同标尺有两样，缺一不可：**贯穿全图的竖向虚线（列）** 和 **全文统一的三类颜色**。

**张数由文档结构决定，不是写死三张。** 判断标准：

- 原文的几张 figure **之间有主从关系**（一张是骨架，另几张是它某个节点的放大）→ 骨架与细节**分成两张**，细节那张标明谁是谁的放大。
- 原文的 figure **互不相交、谁也不属于谁** → 骨架与细节**合成一张**，把几张切面并排，并明说"它们不是一条线上的几段"。这时总共两张图。
- 例子层始终独立成图。

第二次执行（ISO/IEC 22989）走的是第二条：那份标准的三张图互不兼容，原文从未说过谁属于谁，合成一张并列展示是对的。

## 视觉语法从哪来：站在 lieflat-charts 上

这套图的视觉语法不是新发明的，继承自 **lieflat-charts**（由「躺在废墟里」开发，https://moxt.ai ）。但概念结构图不在它的图库里（它的 63 张全是数据图表），所以走的是它的**库外图型翻译流程**：先回答"这个图型编码了什么、每个视觉通道对应哪个维度"，再找最近的亲戚，再用它的 token 造句。本 skill 的阶段 2 就是这个流程的展开。

直接继承，不要改：

| 继承项 | 内容 |
|---|---|
| 色板哲学 | 纸灰 `#F0EFEB` + 炭黑两极；**一律实心**：不透明材质、不发光、不渐变滤镜、无阴影。质感全靠明度对比和形状 |
| 卡片四件套 | 结论式标题 `h2` + 副标题（图例、单位、范围，用 ` · ` 分隔）+ 图 + 来源行（全大写、加字距） |
| 标题规则 | **写结论不写图型名**。"从一个概念展开到每一条可落地的指标" 好过 "扇出树" |
| 形状 | 卡片圆角 24px，无边框无阴影，靠留白分卡 |
| 字体 | Inter 全家；标题 700 / 数值 800 / 轴标签 600 |
| 动画性格 | 快进快停 `quarticOut`，不弹跳；必须带 `prefers-reduced-motion` 降级 |
| 交互三问 | 这根线背后有没有真实记录？没有（纯示意的发丝线）→ **禁止加交互**，给没有内容的元素加 hover 是欺骗 |
| 演示数据 | 确定性伪随机，禁用 `Math.random()`——刷新必须长一样，否则截图和回归对比全失效 |
| 说不的底线 | 发光 / 玻璃拟态 / 3D → 拒绝；断轴 → 拒绝 |

必须改的两条，因为概念结构图和数据图表的目的不同：

**一、强调色规则要反转。** lieflat 说"强调色只给一个主角，第二个主角会消解强调"——那是对的，因为**数据图表有一个要讲的结论**（哪个最大、哪个异常），强调色指向它。但**概念结构图要讲的是整体结构**，没有哪个分支比别的更重要。

> **失败模式**：本流程首次执行时，我按 lieflat 的规则把唯一的橙色给了一个分支（它确实是原文里最特别的一支）。用户的反馈是："整个页面唯一的颜色是橙色，给了这一支，那一般人会觉得这是所有体系最重要的一点，但实际上我们想要表达的是整体逻辑。"
>
> 改法：换成**三类分类色**——颜色表达"这个东西属于哪一类"，不表达"它有多重要"。具体见 `references/visual-grammar.md` 的颜色一节。

**二、最小字号要抬高。** lieflat 的下限是半宽卡 6.5px、通栏 5.5px，那是给英文和数字定的。**中文笔画密，9px 才读得清**，8.4px 是绝对下限（检查脚本按这个报警）。装不下就换行或缩短文案，不要缩字号。

还有一条要知道但不用改：本 skill 的图属于 lieflat 的 **Lupi 系**（发丝线、密度来自单位、需要凑近读 30 秒以上），不是 Glance 系。但比 Lupi 更"工程"一些——不用手绘感的 `blob`，因为结构图要的是精确，不是氛围。

**lieflat-charts 是可选依赖，不装也能用**：需要的色值、字号和规则都已内化在 `references/visual-grammar.md` 里。装了的好处是能查风格正本、取更多色值起点——它的 `palm` 预设有现成的分类色，但注意**预设的分类色是给色块用的，画成 1px 线会看不出颜色**，线色要自己调亮。

## 阶段 0 · 提取

```bash
pdftotext -layout input.pdf out.txt        # 正文
pdfinfo input.pdf | grep Pages             # 页数
```

**图必须用 Read 工具直接看，不能只读 pdftotext 的输出。** 原文的 figure 在文本里只剩一行图题，框名、箭头方向、箭头上的文字、图例分组全都丢了。这个 skill 的图二基本就是在重画原文的 figure，看不到图就只能瞎编。

```
Read(file_path="input.pdf", pages="11-12")   # 直接看图所在的页
```

## 阶段 1 · 结构探针（必须派 subagent，不可跳过）

派一个 opus subagent 通读全文。**不要自己只读目录和几节就动手**——这是本流程最容易翻车的地方：只读局部会把并列关系误认成父子关系，把"可以"读成"必须"。

提问模板见 `references/probe-template.md`，六个问题一个都不能少。重点是：

- 层与层之间用**原文的英文短句**连接，不是你的概括
- 每张 figure 的**每个框名、每条箭头的方向与文字、图例的分组**
- **易误读点**：全文有没有 shall、某条规则是不是带限定词、某个说法是不是只针对某一类对象

subagent 返回后，把它纠正你的地方单独记下来。这些是后面写图注的素材，也是最能体现"真读了"的证据。

**失败模式**：首次执行（ISO/IEC 25002）时，我只读了其中五六节就开画，把上位的两个分类当成了下位四个模型的父节点。subagent 通读后指出：原文是并列的分类句，全文没有任何 is-a 措辞。若不纠正，整张骨架图的第 2 层就是错的——**层级关系是这类图最核心的断言，也最容易在只读局部时读反。**

## 阶段 2 · 视觉逻辑推导（不碰代码）

把结构翻译成**表达需求**，每个需求配一条**独立的视觉通道**。通道撞车，图就乱。

典型的四个需求与可用通道：

| 表达需求 | 可用通道 | 不要用 |
|---|---|---|
| 抽象 → 具体，数量逐层变多 | 横向分叉扇出、同心环周长、分层板 | 金字塔（它表达漏斗与转化，不是展开） |
| 同层内部再分类 | 分支归属、扇区归属、上下分区 | 颜色（颜色要留给跨图的类别） |
| 某些定义横跨若干层，长短不一 | 与层共用同一根轴的**长短条**，条的起止位置即跨度 | 一视同仁的并列方框 |
| 第三维度（谁用、在哪些过程里用、适用范围） | 罩在整体之外的横括号或外圈 | 混进层级里 |

产出一段文字 + 一张通道分配表，先给用户看。**这一步不写任何代码。**

## 阶段 3 · 方案并置

做 3–4 个方案放在同一页，每个配一句话标题、一段副标题、一张图、一行"长处 / 短处"。不要替用户选。

常用的四种形：

- **扇出树 + 跨度条**：层级是横轴，越往右分得越细；贯穿性定义是下方同轴的长短条。信息密度最高，最适合当主图。
- **同心环 + 辐条**：圆心最抽象，外圈周长天然表达"越具体越多"。造型强，但径向文字难排。
- **原文 figure 合一**：把文档里的几张图揉成一张，标明谁是谁的放大。忠实度最高，但层次感弱。
- **分层剖面（可拖动）**：每层一块板，贯穿定义是穿板的立柱。最直观，但信息密度最低。

## 阶段 4 · 融合与对齐

用户通常会选两个：一个讲结构清楚，一个讲关系准确。

**融合不是提炼，是同轴叠放。** 两者完整保留，上下放，重排列宽让它们共用同一根横轴。

**失败模式**：本流程首次执行时，我说"扇出树的第 3 列就是原文的图 3"，于是只保留了"哪个模型对应哪个实体"，把图 3 的嵌套结构、四要素框、模型钉在哪个框上全丢了。用户一眼看出"丢了很多细节"。**任何以"其实已经包含了"开头的融合理由，都要先回去数一遍对方有几个框。**

对齐的做法：

1. 定一组列 `X[]`，是图一的抽象层
2. 把图二里最宽的那块（通常是原文 figure 的主体）测出需要多宽，反过来调整 `X[]` 的间距
3. 竖向虚线从图一顶部画到**最后一块按列读的内容**为止——不要无脑画到画布底部
4. 图三用同一组 `X[]`

### 横向区块一律走 place()，列归属必须显式声明

**共同标尺是这套图的核心契约：任何元素的横向跨度都在向读者宣称"我属于这些列"。** 跨度与真实归属不符就是撒谎，和柱状图断轴是同一类错误。

`references/skeleton.html` 提供了 `place(x, y, w, h, cols)`，`cols` 不给会直接抛错：

| `cols` | 含义 | 自动做什么 |
|---|---|---|
| `[2,5]` | 跨第 2–5 列，按列读 | 什么都不做 |
| `5` | 整块只属于第 5 列，但要更宽才放得下 | 画归属线回第 5 列列头 + 一句说明 |
| `null` | 不按列读：并列切面、全局词表、容器框 | 不透明底色盖住虚线 |

检查脚本会从竖虚线本身推断列位置（只认长度超过画布 30% 的长虚线，短的是刻度或连接线），扫出所有横跨 ≥1.2 列却没有 `data-cols` 的矩形，列为"待确认"。它不影响退出码，但每一条都要逐个看过。

> **为什么这条要做成机器检查，而不是写在文档里就算了**：第二次执行时，同一份产物里一条四步链横跨七列却只属于其中一列（错），另一处三张并列切面却用底色盖住了虚线并明说"不是一条线上的三段"（对）。同一个问题一处解决一处没有——**靠文档里的规则拦不住**。回头看第一次执行的产物也有两处未声明（跨度条的起止是手工算的、一个容器框横跨全宽却不按列读），只是当时没人发现。

## 阶段 5 · 视觉打磨

### 5.1 先做一次纯视觉 review

改文案之前，先当作没读过内容，只看形：

```bash
python3 ~/.claude/skills/pdf-triptych/scripts/render_check.py out.html --scale 2 --slice --height 4800
```

脚本会做五件事：**自动测量页面真实高度**（不用手猜 `--height`）、`node --check` 语法、抓 Chrome console 的运行时错误、在页面里自检（文字溢出画布 / 同行重叠 / 字号低于 8.4px）、按真实高度截图并分块。**分块截图必须用 Read 工具逐张看**——脚本只能抓到机械问题，"这一块读起来别扭"只有眼睛能发现。本流程首次执行时这一步抓到四个问题：列虚线太淡看不见、标签被分叉线压住、扇出起点因线叠线形成黑楔子、左下角一大块空地没用。

看的时候重点查三件脚本查不出来的事：

1. **有没有"不按列读"的区块被虚线穿过**——见 `visual-grammar.md` 同名小节，这是第二次执行时唯一的真实错误。
2. **一张图的视觉分区有没有超过 5 块**。超了不一定要拆，但要检查是不是靠留白和小标题分开了；挤在一起就该拆或该合并同类项。
3. **某张图是不是八成以上一个颜色**。例子层常见（全是"过程"类），可以接受，但要在副标题里说一句。

> **不要自己猜窗口高度。** 第二次 review 时我手动传了 `--height 5200`，截出来 70% 是空白，差点把它误判成产物的 viewBox 设错了——实测 viewBox 只空余 15px，完全正确。现在脚本默认自动测量，不要再手传，除非你明确要看某个高度下的表现。

### 5.2 颜色

- **不超过三类**，一类内容一个颜色，全文统一。类别按"这个东西属于什么"分，不按"它重不重要"分。
- **黑灰只给结构**：层的分界线、箭头、跨度条、外部维度。
- **不要单一强调色**。给某一支单独上色，读者会理解成"这是全文最重要的一点"，而你想表达的是整体逻辑。
- **细线上的颜色要够亮**。低明度低饱和的色画成 1px 线，和黑色分不出来。色块能认出是绿，不代表线也能。
- 色值起点可以借 lieflat-charts 的 `color-presets.js`，但预设的分类色是给色块用的，画线要自己调亮。

### 5.3 去掉决策过程

页面上不留只有作者知道的编号：

| 不要 | 改成 |
|---|---|
| 方案 A、风格 B | 直接写这张图讲什么 |
| 图 2 = 把这一段展开 | 这一段的完整展开 · §7.1 |
| 上层是全貌（方案 A） | 上层是全貌：概念怎样一层层展开 |

**条款号保留**（§7.2 这种），它能回查原文，是可信度的一部分。

### 5.4 每次改动后验证

```bash
python3 ~/.claude/skills/pdf-triptych/scripts/render_check.py out.html --height 4800
```

**语法检查过不代表能跑。** 本流程首次执行时出过一次变量先用后定义，`node --check` 通过，但整张图画不出来——因为 `const` 的暂时性死区是运行时才触发的。脚本因此同时抓 console 错误。

脚本自身经过对抗测试：人造一个带重叠、溢出、6px 小字的页面，三类问题都能抓到；真实产物全绿。

## 什么该固化成规则，什么不该

每次执行完都会有新发现，但不是所有发现都该写进 skill。判断标准只有一条：**它是这套图型的固有契约，还是这份文档的特殊情况？**

| 该固化 | 不该固化 |
|---|---|
| 列归属必须声明——共同标尺是图型契约，任何文档都适用 | "生命周期适合当横轴"——那是某一份标准的结论，别的文档未必有生命周期 |
| 图的张数由 figure 之间有无主从关系决定——这是通用判据 | "要画三张切面并排"——那是某份文档的 figure 恰好互不相交 |
| 列线要用长度过滤识别——任何图都可能有装饰性短虚线 | "117 条术语画成 barcode"——那是一次形式创意，换份文档可能完全不适用 |
| 派 subagent 通读、figure 必须看图——这是方法 | "全文 0 个 shall"——那是某一份标准的事实 |

判断不了的时候问自己：**换一份完全不同的 PDF，这条还成立吗？** 不成立就别写进来，写进 `references/` 当案例可以，但不要写成规则。

规则宁可少而硬，不要多而软。多一条软规则，执行者就多一分"看过了但没照做"的空间。

## 阶段 6 · 例子层

用同一组列换成一个具体场景。例子要**自下而上读**：最底下是"这个场景里有什么"，往上是"怎么评价它"，最上面是"拿来做什么"。

每一行是一条可追溯的链：挂在哪个实体上 → 从哪来 → 用哪个标准概念 → 量什么 → 实际多少。

## 交付物

```
<name>.html                单文件，无构建，双击可开
<name>.html.bak            首次改动前的备份
```

HTML 骨架见 `references/skeleton.html`，视觉语法细则见 `references/visual-grammar.md`。

## 检查清单

交付前逐条过：

- [ ] 派过 subagent 通读全文，不是只读了目录和几节
- [ ] 原文的每张 figure 都用 Read 直接看过
- [ ] subagent 指出的易误读点，图上有体现（比如"全文规则均为 should"）
- [ ] 三张图共用同一组列，竖虚线贯穿
- [ ] 颜色不超过三类，全文统一，黑灰只给结构；**没有单一强调色**
- [ ] 卡片四件套齐全（结论式标题 / 副标题说清图例 / 图 / 来源行）
- [ ] 没有"方案 A""图 2"这类只有作者知道的编号
- [ ] 条款号保留，能回查
- [ ] 做过一次纯视觉 review（2 倍截图分块看），且截图高度是脚本自动测的
- [ ] 横向区块都走了 `place()`，`cols` 逐个想清楚；检查脚本报的"待确认"逐条看过
- [ ] 图例只画一次，第二张起写"颜色含义与上图相同"
- [ ] `node --check` 过，且 Chrome console 无 uncaught
- [ ] 最小字号 ≥ 9px（中文），无文字重叠、无超出画布
- [ ] 数据来源行写明了哪些内容不来自这份 PDF
