Cairn Stack 用于构建自包含的 Cairn 文档栈。一个 Cairn 栈就是一个 .cairn.html 文件,内部包含:阅读器软件、可选的视觉编辑器、以纯 JSON 存储的卡片式文档、所需的全部图片与声音,以及读者的阅读进度。从文件夹打开即可工作——无需服务器、无需安装、无需网络。按 Edit 让读者变为作者,按 Save As 让浏览器写出携带全部内容的新文件。
这一约束是核心,并塑造了创作方式:内容即数据,绝非代码,没有作者 CSS、没有作者 JavaScript;颜色来自八个主题令牌,行为来自一组小型封闭的声明式动作。一切都在文件内部,优先使用完全无法访问网络的 sealed 模式。布局是绝对的:卡片是固定画布,默认 1280×800,每个元素都是定位盒子。
如何构建:编写 *.stack.json 规格(文档加资源来源列表),捆绑脚本生成 .cairn.html,处理 < 转义、标记拼接、规范 CSP、排序资源块、精确解码字节计数等机械工作。命令示例:
S=<skill-dir>/scripts/cairn.mjs # 无依赖,无需 npm install
node $S new field-guide.stack.json --title "A field guide to lichen" node $S build field-guide.stack.json -o field-guide.cairn.html node $S check field-guide.cairn.html node $S preview field-guide.cairn.html --out shots # 渲染并测量 node $S extract someone-elses.cairn.html -o edit.stack.json # 修订他人文件
new 会复制一个可用的六卡起步模板并生成新的 UUID v4 docId。build 在文档有错误时拒绝写入,并打印不阻塞的设计警告。check 对已构建文件重新运行全部检查,并证明四个受管区域之外的每个字节仍与来源外壳匹配。preview 在无头 Chromium 中渲染每张流程卡,报告静态验证无法得知的问题:来自真实 DOM 的文本溢出测量、渲染期间的控制台与页面错误、每卡 PNG。preview 需要 Playwright(从项目或全局安装解析),它从不安装任何东西;若报告 skipped,可安装 Playwright(npm i -D playwright && npx playwright install chromium)或自行打开文件用方向键翻页(Esc 显示外壳,O 显示概览)。
编辑已有栈:extract 恢复可编辑规格及其资源文件。保留 docId,它是文档身份,世界各处副本中的阅读进度都以它为键;仅在真正分叉时才生成新 ID。卡片、元素与资源 ID 也应保持稳定,因为处理器、保存状态与导航都引用 ID 而非标签。
网格:每张优秀 Cairn 卡共享同一几何。画布 1280×800;边距 x=76,内容宽 1128;页眉 kicker y 140、heading y 174、rule y 116;正文内容位于 y 268 与 y 620 之间;页脚导航按钮 y 636、进度 y 700;列布局为整宽 1128、分栏 600+464(间距 64)、侧栏 520+548(间距 60)。右边缘落在 1204(=1280−76)。两栏卡为 x:76,w:600 与 x:740,w:464。每侧至少留 32px 空白。把规则、页码、页脚导航、进度读数等家具放在背景上而非每张卡上。
按设备选择画布:阅读器将整张卡按 min(w/W, h/H) 等比缩放以适应窗口,加黑边、绝不裁剪。1280×800 适合笔记本、桌面与投影仪;在竖屏手机上缩放到约 30%,19px 正文渲染约 6px,不可读。420×860 适合在野外手机上阅读的栈,在手机上约 1:1 渲染,在桌面上显示为窄的居中列。若用户说“在手机上”“在野外”“通勤时”,需有意识地选择画布并告知选择与原因,不要折中。references/layouts.md 载有两种画布的完整网格。
字体:运行时将 h1 渲染为元素自身字号的 2.3 倍,因此按期望外观尺寸除以 2.3 来创作。封面标题看起来 62px,作者写 <h1> fontSize:27,行高 1.08;卡片标题 42px,写 <h1> fontSize:18;导语 21px,写 <p> fontSize:21,行高 1.5;正文 19px,写 <p> fontSize:19,行高 1.55;小正文 17–18px;说明与脚注 14–15px,color:"muted";kicker 13px,fontWeight:700,color:"accent",大写。标题框高度约 size×1.08×行数 + size×0.45。表格无单元格内边距,键值列表用成对行(<p><strong>Term</strong><br>Definition</p>)比 <table> 更好读。
颜色:元素样式使用令牌名而非十六进制,如 color:"accent" 而非 color:"#1C554C";十六进制只出现在 doc.theme.colors 中。八个令牌为 bg、surface、text、muted、accent、accentText、success、danger,需全部定义并定义 radius。对比度是真实约束:bg 上的 text 与 accent 上的 accentText 都需 4.5:1。references/layouts.md 有五个已验证比率的调色板。两个习惯承载大部分视觉重量:一个强调色,克制使用;形状是家具而非装饰,给它们负 z 使内容位于上方。
组合卡片:每张卡应是一个想法,以读者到第三张卡就能识别的形状呈现。可靠节奏是 kicker → heading → 一栏散文 → 一个可看或可做的东西。不要填满画布。references/layouts.md 有带真实框架数字的原型:cover、statement、prose-and-figure、two-up comparison、gate、reveal、gallery、closing,以及调色板与安全 SVG 模式。
行为:状态是声明式且有类型的,四种类型(string、number、boolean、stringList),各有匹配的初始值,任何地方都不做强制转换。动作值与守卫是表达式字符串而非 JSON 值,这是最常见的错误:
{ "act": "set", "var": "done", "to": "true" } ✓ 表达式 true { "act": "set", "var": "done", "to": true } ✗ 原始 JSON 布尔 { "act": "set", "var": "who", "to": ""Ada"" } ✓ 带引号字符串字面量 { "act": "set", "var": "who", "to": "Ada" } ✗ 将 Ada 读作变量 { "act": "set", "var": "label", "to": "concat(a, b)" } ✓ 用 concat 连接,而非 +
处理器可位于元素、卡片、背景或栈上,按此顺序解析。用 {{ expression }} 将实时值插入显示文本,如 "{{ card }} of {{ cards }}"、"Ready? {{ round(confidence * 100) }}%"。references/recipes.md 有可复用模式的工作 JSON:带提示的受守卫继续、按需揭示、测验评分、键盘导航、菜单卡、清单、外部链接。
“完成”的含义:交付前 check 与 preview 都应安静,应亲自看过渲染的卡片,并满足:每张卡都说了些什么;读者始终知道位置与如何移动;交互状态诚实,阻止继续的守卫也解释原因;图片有 alt 文本或 "decorative": true;除非真正需要网络,否则为 sealed。若需求要求全面或全功能,外壳定义了可衡量标准——20 张卡、两个背景、全部四种变量类型、十种元素类型、八种动作类型、四个位置的处理器、一个守卫、一个键处理器、插值、内嵌 SVG 与完全可达性,用 node $S check file.cairn.html --featured 检查;普通栈不要追求该标准。
参考文件:references/format.md 涵盖每种元素、变量、处理器、动作、表达式内置与资源规则的每个字段,以及构建脚本接受的规格格式;references/layouts.md 有带框架的卡片原型、五个已验证调色板、图形与 SVG 模式;references/recipes.md 有常见交互的工作处理器 JSON;assets/starter.stack.json 是六卡起步模板;assets/cairn-shell.cairn.html 是每个栈构建入的外壳,其前 12 KB 的 #cairn-agent-reference 内也载有完整字段参考。