Eink Deck logo

Eink Deck

Community
Mercury-vivis
eink-deck

把「大纲 Markdown」渲染成单文件横向翻页 HTML PPT(浏览器放映、B 键静态、WebGL 水墨背景、5 套主题)。在任何目录放大纲都能渲染,无需依赖具体工作区。支持 M05–M14 元件三控(字号/垂直/水平)、L01–L27 + L12b 共 28 个版式(含 L19 条形图/L20 KPI 塔/L21 闭环图/L22 系统同心圆/L23 多图证据墙/L24 论断陈述/L25 横向时间轴/L26 微卡矩阵/L27 图幅主导)、M12 图片 auto 原图比例+contain 不裁+四向调整、M10 ||| 分步揭示、强调锚点、M16 参考链接框(新标签打开,协议白名单防 XSS)、SVG 双通道注入(通用 `svg:` 内联 / 侧车 JSON)。

Overview

PublisherMercury-vivis
Repositoryeink-deck
Skill nameeink-deck
Stars
6
Forks
1
Bundled files
37
LicenseAGPL-3.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 37 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Mercury-vivis on GitHub. Read the source before you install it.

Installation

Install the Eink Deck AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/Mercury-vivis/eink-deck/tree/main
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Mercury-vivis/eink-deck.git \
  .claude/skills/eink-deck
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Eink Deck in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Eink Deck on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Eink Deck is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

eink-deck · 通用 eink 网页 PPT 引擎

这是什么

把「大纲 Markdown」渲染成单文件横向翻页 HTML PPT(浏览器放映、B 键静态、WebGL 水墨背景、主题可配)。引擎/模板/示例全部随本 skill 自带,在任何目录、任何专题都能用——不依赖任何具体工作区。

  • 默认主题 ink-classic(墨水经典);内置 5 套预设含 kraft 牛皮纸(深墨 #2a1e13 / 纸 #eedfc7 / 朱砂 #a8331f,三色体系,禁用 emoji——风格约定、引擎 validate 只计数不拦截;图标一律用 Lucide <ico=name>),见下「主题与配色」。
  • 产物:<输出目录>/index.html(单文件)

大纲目录怎么定(三级回退 · engine/_defaults.py)

golden_check.py / render_baseline.py 要找「16 讲实纲目录」时才用到,三处按序取第一个命中的:

  1. 命令行:--outlines <目录>(优先级最高,端到端已实测可切换);
  2. 环境变量:EINK_OUTLINES_DIR;
  3. 内置默认:engine/_defaults.py 的 DEFAULT_OUTLINES_DIR。

该默认值是唯一源,两个脚本都从这里读,不各写一遍——历史上两边各写同一条本机绝对路径,属于「复制即漂移」。_tools/check_drift.py --strict 的 D2 项看守「不得再把资料库绝对路径写进脚本」,D1 项看守默认主题。

主题与配色(核心能力 · 5 套预设 + 默认 ink-classic)

本技能把"背景/字体配色"全部抽成主题,不再写死任何专题色。换主题只改一行大纲元数据或一条命令行参数,版式逻辑零改动。

主题键名称墨(ink)纸(paper)强调(accent)字体基调
ink-classic墨水经典(默认)#0a0a0b#f1efea#b23a2e 编辑红衬线
indigo靛蓝瓷#0a1f3d#f1f3f5#d98324 暖琥珀无衬线
forest森林墨#1a2e1f#f5f1e8#b8842f 金衬线
kraft牛皮纸#2a1e13#eedfc7#a8331f 朱砂红衬线
dune沙丘#1f1a14#f0e6d2#b5654a 陶土衬线

怎么选主题(优先级:命令行 > 大纲字段 > 默认):

  1. 大纲元数据(推荐):顶部写 - 主题: indigo(键也认 theme:);- 字体: sans 可单独切字体基调(serif/sans),不写则用主题默认。
  2. 命令行覆盖:render_outline.py 大纲.md 输出 --theme=forest 会覆盖大纲里的主题。
  3. 不写 → 默认取 engine/_theme_default.py 的 DEFAULT_THEME(现为 ink-classic,最通用安全)。该值是 eink 引擎与 eink2pptx 的唯一源,两侧都从这里读,不许各自再写一遍字面量。
  • 背景(WebGL 水墨双背景)与字体(衬线 / 无衬线)随主题自动着色;衬线切换用大纲顶部元数据 - 字体: serif|sans(--serif-zh 是引擎内部 CSS 变量名,不是命令行参数)。
  • 新增主题:改 engine/_eink_template.py 顶部 THEMES 字典(唯一源)。
  • 改默认主题:只改 engine/_theme_default.py 的 DEFAULT_THEME;eink2pptx 侧会跟随(它按目录结构定位该文件读取,无本机绝对路径)。两侧若出现不同值,_tools/check_drift.py --strict 会报 D1。

⚠️ 单一真相源(2026-08-19 立):本技能包(engine/ + templates/)是引擎与模板文档的唯一源。
各工作区不再保留 render_outline.py/_eink_template.py/模板文档副本。
改引擎或模板:直接改本技能包,改完即生效(OneDrive 双机同步)。
各工作区只放:大纲 .md、images/ 配图、可选的 _svg_overrides.json 设计侧车(放各自生成目录,见「示意图注入(SVG 协议)」)、输出目录。

来源与许可

  • 创作主体(© 2026 老陈 / 帮帮):整套 Python 渲染引擎(大纲 Markdown → 自包含 HTML 的渲染管线、契约系统、版式分发器)、版式体系 L01–L27(28 个)+ 通用元件、bat / tests / golden 自检等工具链,均为本技能从零创作(上游基础版为 HTML 模板 + mjs,不含任何 Python)。
  • 风格来源(致谢):「电子墨水」视觉主题、WebGL 双背景(light/dark)、版式语法体系与字体栈(Playfair Display / Source Serif 4 / IBM Plex Mono / Noto Serif SC / Noto Sans SC)源自 guizang-ppt-skill(作者 op7418,GitHub),为本技能提供了视觉与模板地基;版式在其约 10 个布局基础上扩展为 28 个。迭代基础为 guizang-ppt-skill 的 WorkBuddy 市场版 v1.0.0(技能内部版本 1.0.1,约 2026-07-22 安装)。
  • 许可:因包含 guizang-ppt-skill 的 AGPL-3.0 代码(原作 © 2026 op7418),本技能整包以 AGPL-3.0 发布并保留原作者版权;原创部分的著作权归属不受影响。完整文本见 LICENSE,第三方资源(Motion One MIT、Lucide ISC)见 THIRD_PARTY.md。

资源文件导览(本 skill 自带)

eink-deck/
├── SKILL.md                     # 本文件:工作流 + 资源导览 + 指针(版本唯一源)
├── engine/                      # 渲染引擎(工具本体,直接可跑)
│   ├── render_outline.py        # 解析层:大纲 md → lecture dict(命令行入口)
│   ├── lint_outline.py          # 大纲守护:渲染前体检(只报告不阻断,工作流 1b)
│   ├── package_deck.py          # 两步打包器:产物目录 → 单文件交付稿(工作流 4b)
│   ├── prep_images.py           # 配图预处理(渲染前可选):压体积/缩长边/统一 WebP(工作流 0)
│   ├── _eink_template.py        # 引擎:lecture dict → index.html(CSS/JS 内联固化)
│   ├── syntax_registry.py       # 语法注册表(机器层:lint/guard/生成器的输入)
│   ├── check_syntax_anchors.py  # 锚点自检:注册表 ↔ 引擎无漂移
│   ├── check_docs_consistency.py# 文档一致性 guard:文档 ↔ 注册表
│   ├── gen_syntax_docs.py       # 生成器:注册表 → generated/ 人读文档
│   ├── golden_check.py          # golden 门禁:示例大纲 + 16 讲 lint --strict
│   ├── generated/               # 人读派生文档(机器生成 · 勿手改)
│   │   ├── 语法速查.md          # ★ 唯一人读语法文档(键字典 + 版式挂载)
│   │   └── 示例大纲.md          # ★ golden 基线(28 版式每版式一页可抄)
│   └── eink_assets/
│       ├── motion.min.js        # 入场动效依赖(classic build,本地,引擎自动引用,勿删)
│       ├── motion.source.esm.js # 动效库 ESM 原版——只给 build_motion_classic.py 当源,勿手改
│       ├── build_motion_classic.py # 由 ESM 源生成经典版 motion.min.js(升级 Motion One 时重跑)
│       ├── lucide.umd.min.js    # 图标依赖(本地,引擎自动引用,勿删)
│       ├── get_fonts.py         # 离线字体源下载
│       └── README_fonts.md      # 字体缓存/子集说明
├── bat/                         # 通用拖拽 bat(可拷到任意工作目录用,位置无关)
├── references/
│   ├── CHANGELOG.md             # ★ 变更日志(版本唯一记录处,凡修改落日志)
│   └── 二期重构论证.md          # 引擎注册表驱动重构论证(阶段3 方案)
├── templates/
│   └── eink_模板模块规范.md    # 视觉结构(一页长什么样;不含语法)

与上游技能的分工(2026-09-15):本技能只管「大纲 → 画面」,不管内容怎么想出来。 内容层(核心思想/切幕/记忆钉/讲稿取舍)与架构层(选型/骨架/锚点/三道闸)归 lecture-outline-design 技能; 语法(字段怎么写/挂载)见 engine/generated/语法速查.md;视觉见 templates/eink_模板模块规范.md。 改语法或版式能力 → 改引擎 + 注册表 → 重跑生成器 → 落 references/CHANGELOG.md。

工作流(用户怎么用)

0. 压缩配图(可选 · 渲染前 · 2026-09-09 起)

配图会被 base64 内嵌进交付稿,图越大单文件越臃肿。渲染前可先压一轮:

bash
# 建议先干跑看收益(不写任何文件)
python "<skill路径>/engine/prep_images.py" "images/某图.png" --dry-run
# 确认后真跑:同目录同名、只换 .webp 扩展名,原文件保留
python "<skill路径>/engine/prep_images.py" "images"        # 整个目录

人工拖拽:bat/02压缩配图-eink.bat(拖图片文件或文件夹都行)——先干跑、再要你确认才写,找不到带 Pillow 的 python 会直接报错而不是静默失败。

  • 做三件事:长边缩到 ≤1920(只缩不放、不裁内容)、可选 --trim 去纯色白边、统一转 WebP(照片类 q80;颜色 ≤256 的线条/地图类自动走无损,若更大则自动退回 q80);
  • 原文件一律保留,只新增 .webp;动图 GIF 跳过(转静图会丢动画);
  • 脚本绝不改大纲——报告末尾会列出需同步修改的 M12 文件: 行,你改完再重渲;
  • ⚠️ 拖文件夹会处理里面所有图——干跑报告首行有「共 N 张」,确认前先看清楚数量。

1. 建大纲

  • 复制 engine/generated/示例大纲.md 为你的《XX讲大纲.md》(golden 骨架,28 版式逐式一页可抄);
  • 或照 engine/generated/示例大纲.md 的骨架写:### Pxx · 简述 起头,下面 - 键: 值 逐行;
  • 顶部元数据(必填 4 项):- 讲次: / - 讲稿题目(M02): / - 系列名: / - 总讲次数:(纯元信息:引擎当前仅落元数据、未参与渲染;右下角页码分母恒为本讲实际页数)。
  • 顶部元数据(可选):- 主题: / - 字体: / - 讲次类型:(自由文本,仅作标注,引擎不解释)/ - 块词表:。
  • 配图放大纲目录的 images/ 子目录(如 images/示意图.png)。
  • 从讲稿开始(而不是从空白大纲开始):先用 lecture-outline-design 技能由讲稿产出内容层大纲,本技能只负责渲染;接口见 engine/generated/语法速查.md 与上游 lecture 技能。
- 块词表:(可选 · M03 前段受控校验)
yaml
- 块词表: 概述 / 方法 / 案例 / 小结

给 M03 前段(块名)做受控校验:写了之后,前段不在表内的页会打印 [块词表警告]——只警告、不阻断,渲染照常完成。不写则不校验(块名自由填写)。

反污染铁律:引擎不内置任何词表。某个系列若需固定板块名(例如五段式结构),一律写进该系列的 --profile JSON,由调用方注入,绝不写死进通用引擎。通用技能里的示例词表(概述/方法/案例/小结)只是占位示范,照抄前请换成你自己专题的分块名。

1b. 大纲体检(lint · 必跑 · 2026-09-09 起)

bash
python "<skill路径>/engine/lint_outline.py" "<大纲.md>"
  • 为什么必跑:语法文档写得再全,作者(含 AI)照样会笔误——第9讲实测 7 处「强调: 裸短语」整行被静默丢弃。lint 是对大纲的守护(护栏脚本守的是技能本身)。
  • AI 流程(硬纪律):生成大纲 → 跑 lint → 按提示自改 → 0 错误后才渲染。
  • 人工手写:不指望主动跑——Windows 拖拽渲染 bat 已内置:渲染前自动 lint、只报告、不阻断。
  • 检查项(全部来自本文件已文档化的坑 + 真实事故):页头不合式、页号重复、强调: 裸短语缺 [ ](错误)、||| 写在不支持分步的字段(警告)、M06/M08/M10 字号三控(警告)、同页多条 M12 文件:(警告)、未知版式/模块码(错误)、M12 文件 指向的图不存在(警告)、贴字斜杠疑似想分行(提示)、emoji(警告)、侧车 JSON 非法(警告)。
  • 纯标准库,任何 python 直接跑;exit 恒为 0(报告不拦截,处理权在人/AI)。

2. 渲染

bash
python "<skill路径>/engine/render_outline.py" "<大纲.md>" "<输出目录>"

例:

bash
python engine/render_outline.py "第05讲大纲.md" "第05讲_输出"   # 在 eink-deck 技能根目录下运行
  • 引擎自动:解析大纲 → 拷贝 images/ 到输出 → 渲染 index.html → 打印校验(页数/浅深交替/emoji=0/figure 平衡/accent 数)。
  • 输出目录不存在会自动创建。
  • --theme=xxx(覆盖主题)与 --profile=<json 路径>(覆盖大纲顶部元数据,供派生技能注入固化项;profile 的键覆盖大纲同名元数据)可放在任意位置——引擎解析前先抽走 -- 选项,不占位置参数。

2b. Windows 拖拽渲染 bat(可选)

技能自带两个通用 bat(bat/ 文件夹,推荐直接用):00拖拽渲染-eink.bat(拖大纲 md → 渲染,内置 lint 体检不阻断)与 01交付打包-eink.bat(拖产物文件夹 → 单文件交付稿)。两者位置无关:按共享目录 + 账户 junction 兜底定位引擎,拷到任何工作目录都能用;不绑任何系列 profile,主题/字体全按大纲自身元数据(系列 preset 见各系列自己的 bat,如武脉在 PPT制作 文件夹)。

另含 03自检-eink.bat:双击即跑 tests/test_smoke.py + scripts/golden_check.py 两套回归门禁(纯新增文件,不改 00/01/02 任何行为)。改完引擎后双击它即可确认没回归——比记命令行省事,且与你现有工作流一致。

⚠️ ARC-6·bat 定位逻辑(双机必读):00/01/02/03 先按 bat 自身所在目录的上一级定位 eink-deck 根(即 bat/ 的父目录),失败时的兜底路径是 %USERPROFILE%\.workbuddy\skills\eink-deck。该路径必须是 junction(指向技能共享目录的 eink-deck),不能是独立副本——否则兜底会静默用到一份过期副本,渲染/自检结果都不可信。

不想用 bat、每次敲命令时,最小 bat 如下(把大纲 md 拖到它图标上即可渲染):

bat
@echo off
chcp 65001 >nul
if "%~1"=="" ( echo drag your outline .md onto this bat & pause & exit /b )
set "OUT_DIR=%~dpn1_eink"
python "<skill路径>\engine\render_outline.py" "%~1" "%OUT_DIR%" --profile="<profile.json>"
echo Done: %OUT_DIR%
pause
explorer "%OUT_DIR%"

⚠️ bat 编码纪律(2026-09-02 实测,务必遵守):Windows bat 一律 纯 ASCII + CRLF + chcp 65001,不写中文、不加 UTF-8 BOM。

  • 机理:chcp 65001 切换代码页后 cmd 会重新定位文件指针。若文件含多字节中文(尤其 LF 换行时),行边界错位、把注释行切成"命令",stderr 冒出 'xxx' is not recognized as an internal or external command——偶发,同一文件首跑报错、后几次正常,极难定位。加 BOM 则必定报 '锘?' 不是内部或外部命令。
  • 实测:同一内容的中文 bat 连跑 5 次,第 1 次报 'file' / '两处:eink-deck' 不是内部或外部命令、第 2–3 次不报;改纯 ASCII + CRLF 后连跑 5 次 stderr 全空、中文(引擎侧输出)正常。
  • 做法:bat 里只写英文提示,中文信息交给 Python 引擎输出(UTF-8 代码页下正常显示)。不要用 set PYTHONIOENCODING=gbk 替代——引擎输出若含 GBK 无法编码的字符会抛 UnicodeEncodeError 直接中断渲染。
  • ⚠️ 括号块内 echo 禁裸括号(2026-09-09 事故):cmd 对 if ... ( ... ) 是整块先解析再判断(条件真假都解析)。块内 echo 行若含未转义的 ( 或 ),会把块提前闭合,报 not was unexpected at this time、窗口一闪即退零输出。写法:改措辞避开,或转义 ^( ^)。

3. 放映与调整

  • 浏览器打开 <输出目录>/index.html:←/→ 翻页、空格前进、B 键静态、Home/End 首末页。
  • 觉得"上一点/下一点/大一点"→ 改大纲的 Mxx 垂直/水平/字号 数值,重新渲染(引擎无状态,改完重跑即可)。

4. 交互功能(放映时)

  • 分步揭示:大纲 M10 正文: A ||| B → 先 A、按右出 B; ||| A ||| B(前导空)→ 初始空白逐段揭示。左键回退一段。M10 notes: 同样支持 |||(2026-09-09 起,见 §分步揭示)。
  • pipeline 步进(L13):空格/→ 手动步进流程。

4b. 打包交付(两步方案 · 单文件可迁移 · 2026-09-09 起)

4b-0 · python 探测(AI / 命令行执行打包前必做)

直接 python package_deck.py 时,PATH 首发 python 常是 WorkBuddy 托管版(无 fonttools)→ 字体静默降级为 CDN 链接、产物不自包含。AI 或命令行执行打包前必须先探测:

powershell
# 探测当前 python 是否可用;失败则显式改用你自己装有 fontTools 的系统 python
python -c "import fontTools, sys; print(sys.executable)"
# 若上面的 python 缺 fontTools,把下面占位换成你本机装有 fontTools 的 python 绝对路径
# & "C:/your/python/path/python.exe" -c "import fontTools; print('fonttools OK')"

探测成功才用该 python 打包;人工拖拽可直接用 01交付打包-eink.bat(内置探测,首行打印 python [fonttools OK] : <路径>,看到此行才代表字体会内嵌)。找不到带 fonttools 的 python 时 先 pip install fonttools brotli 并跑 engine/eink_assets/get_fonts.py,再打包。

演示文稿多数不在自己电脑上放——交付稿必须拷一个文件就能用。两步方案:

步骤谁执行命令 / 操作产出
1. 渲染AI 或人工作流 2(照旧,完全不动)<输出目录>/index.html + assets/(工作稿,可反复重渲)
2. 打包AI 或人python "<skill路径>/engine/package_deck.py" "<输出目录>";人工拖拽:通用 bat/01交付打包-eink.bat(系列版如武脉在 PPT制作 文件夹)<输出目录>/<去修饰词名>.html(目录名去 _eink,再去「大纲」)单文件

打包器做什么(不改 build()、不动渲染流程):

  1. 内联 motion.min.js + lucide.umd.min.js:两者都是 classic <script>,内联后交付稿拷到任何电脑双击都有动效与图标——这是「拷去别的电脑双击」成立的根基(2026-09-27:motion 已从 ESM 动态 import() 改为 classic script,file:// 下不再静默失效,内联与内联逻辑都更简单);
  2. localStorage 守卫:存储被禁时用内存桩,B 键不静默失效(代价:静态/动态偏好不跨刷新持久);
  3. 字体子集内嵌:按本 deck 实际用字切 Noto Serif/Sans SC + Playfair Display + Source Serif 4 的 woff2 子集 base64 内嵌、剥掉 Google Fonts <link>——断网观感与在线一致。两个前提:① 能 import fontTools 的 python;② 本地字体缓存(~/.workbuddy/eink-font-cache/,先跑 engine/eink_assets/get_fonts.py)。任一不满足 → 保留 CDN 链接并告警(降级不阻断)。⚠️ PATH 上第一个 python 常是 WorkBuddy 托管版(没装 fonttools),直接 python package_deck.py 会静默降级成"字体没内嵌"——交付 bat 会探测常见安装路径挑一个有 fonttools 的 python,并在首行打印 python [fonttools OK] : <路径>;看到这行才算字体会内嵌;
  4. 本地配图内联:产物 images/ 下的真实配图(M12 文件: 引入)全部转成 data URI 内嵌,覆盖 <img src="images/…"> 与 CSS url(images/…) 两种写法。渲染器只把图拷贝到 images/ 并在 HTML 里写相对路径,不内联则交付稿一旦拷出产物目录,图片全开天窗(2026-09-09 老陈实测第2讲发现的 P0)。图缺失/路径越界 → 告警并保留原路径(不阻断,但交付稿会缺图)。base64 会使体积膨胀约 33%,单张 >1.5MB 会告警建议先压缩;
  5. 锚点校验:产物结构不合预期(引擎升级改了装配)→ 大声报错退出,绝不静默出坏包。

动效故障怎么复现(2026-09-27 教训,别再走弯路):

  • 用「页面加载后覆盖 window.requestAnimationFrame」模拟 rAF 停摆无效——animate() 走 WAAPI(浏览器合成器驱动),不吃这个覆盖,新旧版本读数一致,会得出"缺陷不存在"的错误结论。
  • 要复现动画冻结,必须用 CDP 真实冻结标签页:Page.setWebLifecycleState {state:'frozen'} → 等 → {state:'active'}。
  • 兜底代码写 inline style 前必须先 el.getAnimations().forEach(a=>a.cancel()),否则 WAAPI 动画的 fill 状态继续压着 opacity,inline 写了也是 0。

交付稿命名:目录名去 _eink 再去「大纲」修饰词(老陈 2026-09-09 定)——第2讲_大纲_eink → 第2讲.html;第9讲-李靖-大纲_eink → 第9讲-李靖.html。名字会删空则回退保留修饰词。交付稿名跟随大纲文件名,想要更有信息量就改大纲文件命名(如 第2讲-车战与妇好钺_大纲.md → 第2讲-车战与妇好钺.html),别手改产物。

工作稿 vs 交付稿:index.html 是可反复重渲的工作稿;<去修饰词名>.html 是一次性快照交付稿。两者都不要手改——改大纲、重渲、重新打包。工作稿目录含 assets/(有真实配图时还有 images/),交付稿是纯单文件(配图已 base64 内嵌)。

5. 各讲自备(不随 skill 分发)

  • images/ 配图(各专题自己的图)
  • _svg_overrides.json(可选):侧车——需要不改大纲临时覆盖某页图时用。放该讲生成目录(输出目录),引擎渲染前自动加载。
    • 💡 常规不用它:图的常规载体是写进大纲该页 - svg: <svg …>(任意版式通用、页号随动、零外部文件,见「通用内联 SVG」)。
    • ⚠️ 侧车为 JSON 纯字符串字典。引擎 json.load 只读,绝不 import/exec 任何 Python,不存在"侧车被篡改即可任意代码执行(RCE)"的风险。

6. SVG 示意图:两种载体 ★

载体 A(常规):写进大纲页面 - svg: <svg …>(单行)——页号随动、零外部文件、单一真相源。完整语法见「通用内联 SVG」。

载体 B(可选):侧车 _svg_overrides.json——需要不改大纲做临时覆盖、或同一页反复调图时用;放该讲生成目录(输出目录),引擎渲染前自动加载。

json
{
  "第1讲/P11": "<svg viewBox=\"0 0 640 360\" xmlns=\"http://www.w3.org/2000/svg\"><text x=\"20\" y=\"40\" fill=\"currentColor\" font-family=\"Noto Serif SC\">示例</text></svg>",
  "第1讲/P20": "<svg viewBox=\"0 0 640 360\" xmlns=\"http://www.w3.org/2000/svg\"><rect x=\"20\" y=\"20\" width=\"120\" height=\"40\" fill=\"none\" stroke=\"currentColor\"/></svg>"
}
  • ⚠️ 值 = 原始 SVG 字符串(直接以 <svg 开头),不是 base64、不是 ![svg](data:…) 包装。引擎链路:_load_svg_overrides() 取到值后 sanitize_svg(str(v)) 直接注入 svg_inline,模板原样内嵌进 <figure class="fig-svg">——全程不做 base64 解码,也没有任何 data URI 解析分支;按 base64 写,页面上会直接显示那串乱码文本。
  • 键 = 「讲次/页号」复合键(多讲同目录也不串页);也兼容纯页号(如 "P11")。⚠️ 键名铁律:讲次与页号之间只有 /、无空格(必须 "第10讲/P21",不可 "第 10 讲 / P21");且 SVG 字符串内所有双引号必须转义为 \"(不可直接写 "),否则整份 JSON 非法、引擎 json.load 失败、该讲全部侧车页一起消失(见「侧车 SVG」)。
  • 加载顺序:生成目录(输出目录)→ 大纲所在目录 → 引擎目录(后两者为兼容部署的回退)。
  • 🟥 侧车一律放该讲自己的生成目录——多讲同目录共用一份侧车会互相覆盖,后渲染的覆盖先渲染的,SVG 全丢。与页面 - svg: 同页会直接报错,二选一。
  • 🟥 侧车零执行:JSON 纯字符串字典,引擎 json.load 只读,绝不 import/exec 任何 Python。

画 SVG 的通用铁律(两种载体都适用):

  • 配色自适应:墨色文字/线条用 stroke="currentColor" / fill="currentColor"(继承页面深浅色,深色页自动转纸色);强调色用 var(--accent)(随主题,不一定是红);严禁写死 #000/#fff。
  • 字体 font-family="Noto Serif SC";画布一般为 640×360(16:9),加 style="width:100%;height:auto";🟥 viewBox 按实际内容包围盒收紧——内容只占画布一部分时,把 viewBox 裁到内容实际范围(如内容仅 420×240 就写 viewBox="0 0 420 240"),不留大段透明边浪费空间;示意类图配 - svg_cap: 概念示意 · 非精确。
  • 重渲即替换:改大纲重渲时图自动保留注入,不会丢。

安全与离线(2026-08-21 全面加固)

本引擎面向"渲染他人/网络来源大纲"的对外分发场景,已对所有外部输入做统一安全闸,渲染产物不执行任何外部代码:

  • 侧车零执行:设计侧车是 JSON 纯字符串字典(_svg_overrides.json),引擎 json.load 只读,绝不 import/exec 任何 Python。
  • 数值字段白名单:垂直/水平/maxh/ratio 等只能填 数字+(vh|vw|px|%) 或 NxM/auto,非法值(含引号、脚本片段)直接忽略,杜绝 style/class 属性逃逸引发的 XSS。
  • SVG 注入前清洗:- svg: 内联、侧车 svg_inline、items 的 ic SVG 片段,注入前经 sanitize_svg() 剥离 <script>/<foreignObject>/<iframe>/<embed>/<link>/<meta>、!ENTITY 与全部 on*= 事件属性,封死 javascript:/data:(非图片)协议。
  • 图片路径穿越防护:M12 文件: 拒绝绝对路径与 .. 穿越,解析后必须落在大纲目录内,否则退回占位——借配图读出任意本地文件不可行。
  • 脚本依赖全本地化:lucide / motion 随引擎内置(eink_assets/ → <输出>/assets/),JS 侧不再引用任何外部 CDN(unpkg / jsdelivr 已移除),消除供应链投 JS 风险;本地加载失败仅优雅降级为静态,不发任何外部 JS 请求。字体不在此列(见下条)。
  • 唯一网络项 = 字体:Google Fonts <link> 仍保留(Playfair Display / Source Serif 4 / IBM Plex Mono / Noto Serif SC / Noto Sans SC 共 5 族,字重 300–900 由 CSS 决定,非作者自选),离线时自动回落系统衬线(source-han-serif-sc/serif/Microsoft YaHei),不影响内容与安全,仅字形略变。⚠️ 这不是"小请求":字体 CSS 约 1.24 MB(1193 个 @font-face 段)会整份下载——Google 的按需加载只对字体文件生效、对 CSS 不生效;实测一篇讲稿真正触发的切片仅约 220 KB。彻底离线现已支持:大纲写 字体源: 本地(或 CLI --offline-fonts / profile 字体源: 本地),引擎在构建期把本 deck 实际用字从本地 Noto SC 源切出子集、base64 内嵌进 index.html,产物自包含、断网也不豆腐块(详见下「字体离线开关」)。未开启离线时仍是上述 CDN 行为。
  • 自己写侧车/大纲也建议遵守:即便只渲染自己的大纲,按 JSON 侧车 + 白名单取值写,能避免"顺手粘贴一段带 <script> 的 SVG"之类误伤。

字体离线开关(子集内嵌)

默认字体走 Google Fonts CDN(见上「唯一网络项 = 字体」)。开启离线开关后,引擎在构建期把本 deck 实际用到的中文字符从本地 Noto SC 源切出子集、base64 内嵌进 index.html,产物自包含、断网也不豆腐块。

触发方式(任选其一,优先级:CLI > 大纲/profiler)

方式写法说明
大纲元数据(规范键)字体源: 本地推荐;与 字体: serif/sans 基调互不干扰
大纲元数据(兼容快捷)字体: 本地同时触发离线且基调回落默认衬线
命令行--offline-fonts(或 --offline)优先级最高,覆盖大纲
派生 profile字体源: 本地武脉等系列可一次性固化默认离线

字体: serif/sans 仍表示衬线/无衬线基调(旧语义不变)。离线时若想另指定基调,用 字体基调: sans 等独立键,不与 字体: 本地 冲突。

一次性准备(构建机只需一次)

  1. 取本地字体源(完整可变 TTF,不进产物,只用于切子集):
    bash
    python engine/eink_assets/get_fonts.py        # 默认缓存 ~/.workbuddy/eink-font-cache/
    EINK_FONT_DIR=/your/cache python engine/eink_assets/get_fonts.py   # 或自定义目录
  2. 构建期依赖 fonttools(含 brotli 才能读写 woff2):pip install fonttools brotli。
  3. 详细见 engine/eink_assets/README_fonts.md。

优雅降级(有就生效、无就安全退网):若 fonttools 未装、或本地源 woff2/ttf 缺失,引擎会打印明确告警并回退到 Google Fonts CDN 链接(产物仍需联网,但不会报错、不会豆腐块)。离线开关不会因缺料而崩。

产物行为:离线成功时 <head> 去掉 Google Fonts <link>,改内联 @font-face(base64 子集);中文衬线/无衬线保证离线可用;西文字体(Playfair / Source Serif / IBM Plex Mono)仍走系统回退(Georgia 等),不影响中文离线。子集大小随去重字数线性增长——以第9讲(约 1165 去重字)为例,内嵌约 1.16 MB(整库 25 MB 仅取 3.8%),与 CDN 版体积相当,但产物自包含。

重要约束:子集是「冻结」的:子集只在构建那一刻按当时字集切好并内嵌。正常改稿必须回到大纲重渲(这点与 CDN 模式一样,两者都要重渲);但离线产物是「密封成品」——不要手改产物 index.html 往里加字(新字不在子集里 → 豆腐块 □),要加字回到大纲重渲(重渲会自动重新切子集)。推荐工作流:撰写期用 CDN(全量字体、零摩擦),交付期加 字体源: 本地 重渲一次冻结子集。

语法唯一源(指针)

语法事实只此一处:引擎代码(行为)→ engine/syntax_registry.py(机器注册表)→ engine/generated/语法速查.md(人读)。

  • 字段怎么写 / 取值 / 挂载:见 engine/generated/语法速查.md(机器生成 · 勿手改)
  • 完整可抄样例:见 engine/generated/示例大纲.md(golden 基线,28 版式每版式一页)
  • 一页长什么样 / 视觉结构:见 templates/eink_模板模块规范.md
  • 架构与方法(选型 / 骨架 / 锚点 / 三道闸):见上游 lecture-outline-design 技能 文档「字段用法 / 组件参考」段只许含注册表 ACTIVE 键;DEAD 键只存在于注册表(lint 锚)与 CHANGELOG。教 DEAD 键 = 漂移,删除。

示意图注入(SVG 协议)

写大纲时直接内联。协议只此一处定义,其他文档(lecture 技能 / 示例大纲)引用本处,不重复定义。

  • 🟥 常规唯一通道 A 内联:大纲该页 - svg: <svg …>(任意版式通用,L12 亦通);页号随动、零外部文件。
  • 🟨 通道 B 侧车(不改大纲的临时覆盖):_svg_overrides.json 放该讲自己的输出目录,键 "讲次/页号";与页面 - svg: 同页会报错。
  • 🟥 一页只允许一个图源:侧车 / svg: 任意两个 → 引擎直接报错退出(SystemExit);L12b items: 与 svg:/侧车同页 → 引擎不报错但静默丢弃 svg,仅 lint 报错误级拦截。
  • 🟥 - svg: 值必须压成单行——引擎逐行解析,只取第一行;多行写法会让 </svg> 及全部内容被静默丢弃。
  • 🟥 值 = 原始 SVG 字符串(以 <svg …> 开头),不是 base64、不是 ![svg](data:…) 包装;md 里双引号照常写 ",不转义。
  • 🟨 页面内不得有 - 设计: 占位行;需图注加 - svg_cap:(所有版式都渲染)。
  • 配色:文字线条 currentColor(继承深浅色),强调 var(--accent),严禁写死 #000/#fff;示意类配 svg_cap: 概念示意 · 非精确。画布上限 640×360(16:9);🟥 viewBox 按内容包围盒收紧(不留大段透明边)。

维护者红线:领域污染审计(改引擎/改模板/改示例前后必做)

本技能是通用引擎。任何"只在某一个具体专题下成立"的约定混进来,都是污染。
⚠️ 关键认知:靠关键词词表检索做审计必然漏——领域词列不完("府兵制""关陇集团""投鞭断流"这类词,词表里没有也能污染)。必须按下面三层做语义级通读,而不是 grep 几个专名。

三层污染模型

层表现危害典型实例(已清理)
① 行为层领域约定硬编码进引擎、实际改变输出最高:通用场景被强制套用错误规则内置 M03 固定五词表;按「讲次类型=人物传记」强制校验
② 示例层文档/示例里把具体专题内容当范本高:AI 写新讲时照抄即中招示例大纲 8 页沿用某讲真实素材;语法速查的 M06/M08/M10 示例句
③ 措辞层把某主题下的视觉结果当成通用事实中:误导维护者注释写"朱砂红 accent"(accent 随主题变,非恒定红)

审计清单(逐项过,不要挑着做)

  1. 引擎:grep 引擎 py 内是否出现任何词表 / 分类枚举 / 内容级默认值。有 → 外置为可选字段(如 - 块词表:),引擎不内置。
  2. 引擎 docstring / 注释:是否出现具体专题名、人名、事件名、专属标语(如"脉续千年")、用户称呼。有 → 中性化。
  3. 自检样例(__main__):整段是否来自某个真实讲次。是 → 重写为中性;样例内 SVG 是否写死某主题的 hex(违反"配色自适应铁律")。
  4. 示例大纲:通读每一页的内容文本(不是 grep),问一句"这一页在讲谁的什么事?"——若答案是某个具体专题,就是污染。
  5. 语法速查 / 模板规范:所有「示例」列、代码块里的示例值。
  6. 措辞:朱砂红 → accent 强调色;第X讲/第16讲 → 泛化;老陈 → 用户。
  7. changelog:引用「第N讲」这类验证对象编号可保留(有可追溯价值);但具体历史人名/事件实例必须中性化。
  8. 收尾:删 __pycache__(陈旧二进制会污染 grep 结果)→ py_compile → 用示例大纲端到端渲染 → grep 产物确认领域词为 0。

判定口诀

"这句话,换到另一个完全不同的专题上还成立吗?"
不成立 → 污染。要么外置成配置(写进该系列的 --profile),要么换成中性表述。

引擎侧的唯一正确形态:引擎只认字段名,不认任何具体内容值。

文档纪律(2026-09-08 立,2026-09-15 精简)

  1. 新增语法条目默认进「语法速查」(engine/generated/语法速查.md,从注册表生成),按正确写法陈述("要这样写"),不写"别这样错"。
  2. 「字段用法 / 组件参考」段只许含注册表 ACTIVE 键;DEAD 键只存在于注册表(lint 锚)与 CHANGELOG。教 DEAD 键 = 漂移,默认删除而非标注。
  3. 已解决的坑、已修复的问题、已过时的表述一律删除,不留档(版本记录只在 references/CHANGELOG.md)。

配套:文档内交叉引用一律用章节名(「见『语法唯一源』」),不用编号。编号会随重构漂移,名称不会。


引擎维护纪律(改 engine/*.py 前必读 · 2026-09-04 立)

前五轮收敛踩出来的坑,每条都实际发生过。按序做,不要跳步。

1. 先写黄金单测锁行为,再动手改

写测试 → 跑出红(证明测试真的能抓住问题)→ 改引擎 → 跑绿 → 端到端回归。
禁止"改完再说"或"凭印象认为没影响"。护栏脚本放 .workbuddy/tmp/,可重复跑。

2. 查 CSS 必须读常量值,禁止行首锚定 + 位置切片(★ 已踩两次)

_CSS 是单行超长字符串,_CSS_EXTRA 是三引号块。两个错误叠加会得出完全错误的结论:

  • ❌ grep "^\.chrome\{" —— 行首锚定对单行内的规则必然零命中 → 误判"规则不存在/评审误报";
  • ❌ src[src.index("_CSS ="):src.index("_CSS_EXTRA")] —— index 可能命中 docstring 里提到的同名串,
    位置早于真实定义 → 切片反向为空。

✅ 正确做法:import 模块后取 T._CSS + T._CSS_EXTRA(已求值常量),或至少去掉锚定、不用位置切片。

3. _CSS 是单引号字符串:源码里的换行必须是字面 \n 两字符

往 CSS 里插注释时若用 Python 真实换行 → SyntaxError: unterminated string literal。
修法:字符扫描精确界定字面量区间(遇 \\ 跳过下一字符、遇 ' 结束),区间内真实换行统一改回字面 \n。

4. 合并 CSS 重复规则必须保留"后者胜"的层叠语义

同一选择器在两处定义时,后出现的属性覆盖先出现的。合并成一条后若取值弄反,会静默改掉全系列版式。
合并前后必须逐属性比对「层叠终值」是否一致(有 verify_css_cascade.py 可参考)。

注意:有些"重复"其实是服务于不同 DOM 结构的选择器,不能删。
例:figure.fig-svg > svg(svg 直挂 figure)与 .fig-svg svg(figure>div>svg 孙元素)都要保留,
只应统一其上限值,让页面通过内联 M12 maxh 覆盖。

5. 启发式阈值必须按实测分布校准,不能拍脑袋

新增告警类机制后,"零告警"不等于机制有效——要统计真实分布验证阈值落在合理位置。
实例:正文溢出阈值初版定 320 字,全系列 199 个正文页(中位 110 / 最大 242)跑出 0 条,等于没加;
按分布改 200 后触发 2 页(正是最长的两页)才真正可用。

6. 改动后做「分区比对」,证明正文零改动

整文件 diff 会因 CSS/强调包裹变化而全红,看不出问题。应按 <style> 切分:
CSS 区允许变、正文区(去掉 accent 包裹后)必须逐字节一致。同时把残留的疑似硬编码色
(如 #2a1e13)逐条查上下文,确认是 :root 主题兜底(合法)还是真残留。

6b. 无 git 时的「新旧引擎 A/B 副本法」(2026-09-10 立)

技能目录不纳入版本控制,改完引擎没有"改前版本"可比。要证明"零回归"须自建对照组:

  1. 改完后把 engine/render_outline.py 与 engine/_eink_template.py 各复制一份到临时目录,精确还原改动点(把新增逻辑改回旧行为)→ 得到"旧引擎";
  2. 复制出的 render_outline.py 要把 from _eink_template import 改成指向副本名,否则仍导入现行模板;
  3. 用全量真实大纲跑两边(新版引擎 vs 旧引擎),输出到两个目录;
  4. 比对必须带侧车——把各讲的 _svg_overrides.json 复制进临时输出目录,否则 fig-svg 数全为 0,比对假绿;
  5. 判据:<style>/<script> 剥掉后逐字节比。差异必须只在被改动的功能上(如"仅新增图注"= 去掉 div.svg-cap 后两边完全一致),其余必须字节级一致。

判据要用精确 DOM 串,别搜类名:.svg-cap / .fig-svg 这些词在 CSS 规则里也出现,只 count("svg-cap") 会得到每页恒定的假阳性。要 re.findall(r'<div class="svg-cap"', body)。 测试标记要完全区分:FIGSVG图 含子串 SVG图,会造成假阳性——用 AAA_ / BBB_ / CCC_ 这类互不包含的标记。

7. 写测试大纲时的两个硬约束(不然页数为 0 / 字段不生效)

  • 分页标记必须是 ## P01 · 标题(正则 ^#{2,4}\s+(P\d+[A-Za-z]?)),写 ## 页1… 解析出 0 页;
  • svg / svg_cap / meta-row 是无 M 前缀的版式字段,写成 - M12 svg: 不生效。

版本

版本唯一源:本文件 frontmatter version(其他文件不再维护版本号)。 全部改动记录:references/CHANGELOG.md(凡修改必落日志;已解决的坑、已过时的表述一律删除,不留档)。


维护与贡献

本技能以独立仓库发布。开发约定:

  • 编码:统一 UTF-8 编码与 LF 换行;bat 文件保持纯 ASCII 与 CRLF(其首部已声明)。
  • 运行:需要 Python 3.8+(推荐 3.13.x 并装 fontTools,以自包含字体)。bat 优先用系统 PATH 中的 python,缺失时回退;引擎根通过 %~dp0.. 相对定位,并以 %USERPROFILE%\.workbuddy\skills\eink-deck 兜底。
  • 验证:python tests/test_smoke.py(覆盖 28 版式渲染断言);python engine/golden_check.py --skip-render 做 lint 门禁;设 EINK_OUTLINES_DIR 指向实纲目录可跑完整 golden 自检。
  • 版本号:SKILL.md frontmatter 的 version 为唯一真相源(本仓库不另设版本号副本)。兼容性不变时升次版本,破坏性改动升主版本。
  • 欢迎通过 Issue / PR 反馈与贡献。

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Eink Deck AI skill do?

把「大纲 Markdown」渲染成单文件横向翻页 HTML PPT(浏览器放映、B 键静态、WebGL 水墨背景、5 套主题)。在任何目录放大纲都能渲染,无需依赖具体工作区。支持 M05–M14 元件三控(字号/垂直/水平)、L01–L27 + L12b 共 28 个版式(含 L19 条形图/L20 KPI 塔/L21 闭环图/L22 系统同心圆/L23 多图证据墙/L24 论断陈述/L25 横向时间轴/L26 微卡矩阵/L27 图幅主导)、M12 图片 auto 原图比例+contain 不裁+四向调整、M10 ||| 分步揭示、强调锚点、M16 参考链接框(新标签打开,协议白名单防 XSS)、SVG 双通道注入(通用 `svg:` 内联 / 侧车 JSON)。

Why use Eink Deck on TypingMind?

Because you install it once and use it with any model. Eink Deck is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Eink Deck in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Mercury-vivis/eink-deck/tree/main. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Eink Deck?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Eink Deck?

As many as you like. As long as a model supports skills, you can use Eink Deck with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Eink Deck AI skill free?

Yes. It is published on GitHub by Mercury-vivis under the AGPL-3.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇