@chengzhao-dev/dsh-quarto-book 0.4.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.en.md +3 -3
  2. package/README.md +3 -3
  3. package/lib/tools/render-check.js +5 -2
  4. package/package.json +5 -2
  5. package/skills/designing-theme/SKILL.md +3 -2
  6. package/skills/designing-theme/assets/theme/css/base.css +216 -55
  7. package/skills/designing-theme/assets/theme/css/callouts.css +32 -23
  8. package/skills/designing-theme/assets/theme/css/code.css +84 -2
  9. package/skills/designing-theme/assets/theme/css/content.css +152 -36
  10. package/skills/designing-theme/assets/theme/css/landing.css +175 -54
  11. package/skills/designing-theme/assets/theme/css/nav.css +462 -35
  12. package/skills/designing-theme/assets/theme/css/sidebar.css +76 -31
  13. package/skills/designing-theme/assets/theme/css/tokens.css +34 -16
  14. package/skills/designing-theme/assets/theme/includes/fonts.html +1 -1
  15. package/skills/designing-theme/assets/theme/includes/footer.html +16 -7
  16. package/skills/designing-theme/assets/theme/includes/palette.html +420 -0
  17. package/skills/designing-theme/assets/theme/palettes/flexoki/brand-mark.svg +3 -0
  18. package/skills/designing-theme/assets/theme/palettes/flexoki/favicon-dark.svg +5 -0
  19. package/skills/designing-theme/assets/theme/palettes/flexoki/favicon-light.svg +5 -0
  20. package/skills/designing-theme/assets/theme/palettes/flexoki/highlight-dark.theme +214 -0
  21. package/skills/designing-theme/assets/theme/palettes/flexoki/highlight-light.theme +214 -0
  22. package/skills/designing-theme/assets/theme/palettes/flexoki/meta.md +53 -0
  23. package/skills/designing-theme/assets/theme/palettes/flexoki/theme-dark.scss +43 -0
  24. package/skills/designing-theme/assets/theme/palettes/flexoki/theme-light.scss +40 -0
  25. package/skills/designing-theme/assets/theme/palettes/flexoki/tokens.css +156 -0
  26. package/skills/designing-theme/assets/theme/palettes/hill-light/brand-mark.svg +3 -0
  27. package/skills/designing-theme/assets/theme/palettes/hill-light/favicon-dark.svg +5 -0
  28. package/skills/designing-theme/assets/theme/palettes/hill-light/favicon-light.svg +5 -0
  29. package/skills/designing-theme/assets/theme/palettes/hill-light/meta.md +78 -0
  30. package/skills/designing-theme/assets/theme/palettes/hill-light/syntax.css +150 -0
  31. package/skills/designing-theme/assets/theme/palettes/hill-light/tokens.css +322 -0
  32. package/skills/designing-theme/assets/theme/palettes/solarized/brand-mark.svg +3 -0
  33. package/skills/designing-theme/assets/theme/palettes/solarized/favicon-dark.svg +5 -0
  34. package/skills/designing-theme/assets/theme/palettes/solarized/favicon-light.svg +5 -0
  35. package/skills/designing-theme/assets/theme/palettes/solarized/highlight-dark.theme +214 -0
  36. package/skills/designing-theme/assets/theme/palettes/solarized/highlight-light.theme +214 -0
  37. package/skills/designing-theme/assets/theme/palettes/solarized/meta.md +66 -0
  38. package/skills/designing-theme/assets/theme/palettes/solarized/syntax.css +67 -0
  39. package/skills/designing-theme/assets/theme/palettes/solarized/theme-dark.scss +43 -0
  40. package/skills/designing-theme/assets/theme/palettes/solarized/theme-light.scss +41 -0
  41. package/skills/designing-theme/assets/theme/palettes/solarized/tokens.css +154 -0
  42. package/skills/designing-theme/knowledge/reading-surface.md +27 -15
  43. package/skills/designing-theme/references/theme-spine.md +2 -2
  44. package/skills/designing-theme/references/theme-system.md +82 -36
  45. package/skills/designing-theme/scripts/checking-layout.py +306 -48
  46. package/skills/designing-theme/scripts/measuring-pages.mjs +40 -3
  47. package/skills/writing-quarto/examples/01-language-scope.md +3 -3
  48. package/skills/writing-quarto/examples/02-structure-shape.md +5 -5
  49. package/skills/writing-quarto/examples/03-block-after.md +2 -2
  50. package/skills/writing-quarto/examples/03-excerpt-lead.md +6 -6
  51. package/skills/writing-quarto/examples/03-layout-list.md +15 -15
  52. package/skills/writing-quarto/examples/03-qmd-elements.md +4 -4
  53. package/skills/writing-quarto/examples/04-paragraph-cases.md +5 -5
  54. package/skills/writing-quarto/examples/04-paragraph-opening.md +3 -3
  55. package/skills/writing-quarto/examples/05-sentence-brevity.md +5 -5
  56. package/skills/writing-quarto/examples/05-sentence-frame.md +6 -6
  57. package/skills/writing-quarto/examples/05-sentence-reference.md +5 -5
  58. package/skills/writing-quarto/examples/06-wording-cases.md +8 -8
  59. package/skills/writing-quarto/examples/08-appendix.md +4 -4
  60. package/skills/writing-quarto/quarto/basics.md +24 -2
  61. package/skills/writing-quarto/quarto/comments.md +43 -0
  62. package/skills/writing-quarto/quarto/coords.md +4 -4
  63. package/skills/writing-quarto/quarto/encoding.md +35 -0
  64. package/skills/writing-quarto/quarto/mermaid.md +1 -1
  65. package/skills/writing-quarto/quarto/prose.md +86 -0
  66. package/skills/writing-quarto/quarto/render.md +17 -16
  67. package/skills/writing-quarto/quarto/terminal.md +3 -3
  68. package/skills/writing-quarto/scripts/checking-dom.py +20 -8
  69. package/skills/writing-quarto/scripts/verifying-content.py +12 -3
  70. package/skills/writing-quarto/steps/01-task.md +6 -6
  71. package/skills/writing-quarto/steps/02-structure.md +19 -19
  72. package/skills/writing-quarto/steps/03-blocks.md +40 -40
  73. package/skills/writing-quarto/steps/04-paragraphs.md +55 -55
  74. package/skills/writing-quarto/steps/05-sentences.md +42 -43
  75. package/skills/writing-quarto/steps/06-wording.md +9 -11
  76. package/skills/writing-quarto/steps/07-layout.md +21 -17
  77. package/skills/writing-quarto/steps/08-reread.md +9 -13
  78. package/templates/quarto-book-project/SKILL.md +1 -1
  79. package/skills/designing-theme/assets/theme/assets/brand-mark.svg +0 -4
  80. package/skills/designing-theme/assets/theme/assets/favicon.svg +0 -9
  81. package/skills/designing-theme/assets/theme/palettes/github/highlight-dark.theme +0 -213
  82. package/skills/designing-theme/assets/theme/palettes/github/highlight-light.theme +0 -213
  83. package/skills/designing-theme/assets/theme/palettes/github/meta.md +0 -38
  84. package/skills/designing-theme/assets/theme/palettes/github/theme-dark.scss +0 -35
  85. package/skills/designing-theme/assets/theme/palettes/github/theme-light.scss +0 -34
  86. package/skills/designing-theme/assets/theme/palettes/github/tokens.css +0 -160
package/README.en.md CHANGED
@@ -26,10 +26,10 @@ Both skills are discovered from the package's `skills/` directory and loaded on
26
26
  ## Install
27
27
 
28
28
  ```bash
29
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.4.3
29
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.0
30
30
  ```
31
31
 
32
- Replace `--profile` with the profile you actually run. The desktop app always runs the `desktop` profile, and that profile is owned by the app, so `dsh --profile desktop` is rejected; install or upgrade a plugin with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin with `npm:@chengzhao-dev/dsh-quarto-book@0.4.3` only if the app refuses that write. To follow a GitHub tag instead, use `github:chengzhao-dev/dsh-quarto-book#v0.4.3`.
32
+ Replace `--profile` with the profile you actually run. The desktop app always runs the `desktop` profile, and that profile is owned by the app, so `dsh --profile desktop` is rejected; install or upgrade a plugin with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin with `npm:@chengzhao-dev/dsh-quarto-book@0.6.0` only if the app refuses that write. To follow a GitHub tag instead, use `github:chengzhao-dev/dsh-quarto-book#v0.6.0`.
33
33
 
34
34
  Upgrading from the old unscoped package `dsh-quarto-book` (removed from npm with 0.3.4, so this only affects profiles that installed it earlier): remove it from the same profile first with `dsh plugin --profile <profile> remove dsh-quarto-book`, then add the scoped package. Keeping both installed registers `writing-quarto` and `designing-theme` twice at the same rank 550.
35
35
 
@@ -43,7 +43,7 @@ Load `writing-quarto` or `designing-theme` inside a `type: book` repository and
43
43
 
44
44
  A project supplies exactly one skill of its own: `<book>/.agents/skills/quarto-book-project/`, starting from `templates/quarto-book-project/`. When it exists, both general skills read `SKILL.md` and then the `writing/` files it points to; when it does not, they skip it and do not guess project conventions. **Do not embed same-name skills into a book repository**: DSH picks one skill per name per layer by rank, lowest wins, and `.agents/skills` (rank 200) outranks the packaged provider (rank 550); a copy of `writing-quarto` or `designing-theme` in the book disables the plugin's text.
45
45
 
46
- Theme assets live in `skills/designing-theme/assets/theme/`. The package copy is authoritative; the book's `theme/` is the render copy. When a copy exists, `designing-theme` edits only that copy, and only when the user explicitly asks to install the plugin theme into the book and the target directory does not exist yet does the skill copy from the package and write the `filters/answering-disclosure.lua` filter. `_quarto.yml` references only the book's copy, never an npm package or the plugin checkout.
46
+ Theme assets live in `skills/designing-theme/assets/theme/`. The package copy is authoritative; the book's `theme/` is the render copy. When a copy exists, `designing-theme` edits only that copy, and only when the user explicitly asks to install the plugin theme into the book and the target directory does not exist yet does the skill copy from the package and place `skills/writing-quarto/scripts/answering-disclosure.lua` at the book's `filters/answering-disclosure.lua`. `_quarto.yml` references only the book's copy, never an npm package or the plugin checkout.
47
47
 
48
48
  Run the content and link checks from the `writing-quarto` skill directory:
49
49
 
package/README.md CHANGED
@@ -26,10 +26,10 @@
26
26
  ## 安装
27
27
 
28
28
  ```bash
29
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.4.3
29
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.6.0
30
30
  ```
31
31
 
32
- `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级插件可用 `dsh plugin --profile desktop add <包>`,如果应用拒绝写入,再走「设置 → 插件 → 添加插件」,地址填 `npm:@chengzhao-dev/dsh-quarto-book@0.4.3`。想跟 GitHub 标签,也可以写 `github:chengzhao-dev/dsh-quarto-book#v0.4.3`。
32
+ `--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级插件可用 `dsh plugin --profile desktop add <包>`,如果应用拒绝写入,再走「设置 → 插件 → 添加插件」,地址填 `npm:@chengzhao-dev/dsh-quarto-book@0.6.0`。想跟 GitHub 标签,也可以写 `github:chengzhao-dev/dsh-quarto-book#v0.6.0`。
33
33
 
34
34
  从旧的无作用域包 `dsh-quarto-book` 升级时(该包已随 0.3.4 从 npm 删除,只影响之前装过它的 profile),先在同一个 profile 里 `dsh plugin --profile <profile> remove dsh-quarto-book` 再装新包。两个包同时留着会以相同的 rank 550 各注册一遍 `writing-quarto` 和 `designing-theme`。
35
35
 
@@ -43,7 +43,7 @@ dsh plugin --profile web add npm:@chengzhao-dev/dsh-quarto-book@0.4.3
43
43
 
44
44
  项目只放一份自己的约定技能:`<book>/.agents/skills/quarto-book-project/`,模板见 `templates/quarto-book-project/`。存在时两个通用技能先读它的 `SKILL.md`,再按它指向的 `writing/` 文件读章节表;不存在就跳过,不猜项目约定。**不要把同名技能嵌进书仓库**:DSH 在同一技能层按 rank 取一个,`.agents/skills`(rank 200)优先于插件(rank 550),书里再放 `writing-quarto` 或 `designing-theme` 会让插件正文失效。
45
45
 
46
- 主题资产在 `skills/designing-theme/assets/theme/`,包内是权威来源,书内 `theme/` 是渲染副本;已有副本时 `designing-theme` 只改副本,只有用户明确要求「把插件主题装进这本书」且目标目录不存在时才从包复制,并写入过滤器 `filters/answering-disclosure.lua`。`_quarto.yml` 只引用书内副本,不指向 npm 包或插件检出目录。
46
+ 主题资产在 `skills/designing-theme/assets/theme/`,包内是权威来源,书内 `theme/` 是渲染副本;已有副本时 `designing-theme` 只改副本,只有用户明确要求「把插件主题装进这本书」且目标目录不存在时才从包复制,并把 `skills/writing-quarto/scripts/answering-disclosure.lua` 放到书内 `filters/answering-disclosure.lua`。`_quarto.yml` 只引用书内副本,不指向 npm 包或插件检出目录。
47
47
 
48
48
  内容与链接检查在 `writing-quarto` 技能目录下运行:
49
49
 
@@ -12,7 +12,9 @@ const QUARTO_TIMEOUT_MS = 120_000;
12
12
  const PYTHON_TIMEOUT_MS = 60_000;
13
13
  const MAX_OUTPUT_FILES = 30;
14
14
  const MAX_CAPTURED_OUTPUT = 12_000;
15
- const MOJIBAKE_MARKERS = ['锟', '鍂', '鐜', '绔', '鎴', '璇', '浣', '鍙', '熨', '闃'];
15
+ // 只收多字乱码序列。单字(如「璇」「浣」「熨」)在正常中文里也会出现,
16
+ // 拿它们当特征会把合法页面判成乱码。
17
+ const MOJIBAKE_MARKERS = ['锟斤拷', '烫烫烫', '屯屯屯', 'â€', 'ä', 'é', 'ï¼', '锘匡'];
16
18
  export function stripQuotes(value) {
17
19
  const trimmed = value.trim();
18
20
  if (trimmed.length >= 2) {
@@ -333,7 +335,8 @@ export async function inspectHtml(input) {
333
335
  else {
334
336
  const domScript = path.join(SKILL_DIR, 'scripts', 'checking-dom.py');
335
337
  const dom = await runPython(domScript, ['--root', input.root, '--book-dir', input.outputDir], input.root, input.python, spawnFn);
336
- const selected = parseDomLines(dom.output).filter((item) => ['C1', 'C2', 'C3', 'C5', 'C6', 'C7', 'C8'].includes(item.id));
338
+ // C9 只在产物里出现 details.answer-disclosure 时判 fail,没有答案时记 N/A。
339
+ const selected = parseDomLines(dom.output).filter((item) => ['C1', 'C2', 'C3', 'C5', 'C6', 'C7', 'C8', 'C9'].includes(item.id));
337
340
  const failed = selected.filter((item) => item.status === 'fail');
338
341
  checks.push({
339
342
  id: 'dom',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chengzhao-dev/dsh-quarto-book",
3
- "version": "0.4.3",
3
+ "version": "0.6.0",
4
4
  "description": "为 Quarto Book 写章节、检查结构并维护主题。 · Write Quarto Book chapters, check their structure, and maintain the theme.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -35,7 +35,10 @@
35
35
  "zh": "为 Quarto Book 写章节、检查结构并维护主题。",
36
36
  "en": "Write Quarto Book chapters, check their structure, and maintain the theme."
37
37
  },
38
- "capabilities": ["skills", "tools"]
38
+ "capabilities": [
39
+ "skills",
40
+ "tools"
41
+ ]
39
42
  }
40
43
  },
41
44
  "icon": "./icon.svg",
@@ -15,6 +15,7 @@ metadata:
15
15
  - 新增 palette 或页面组件样式。
16
16
  - **不适用**:`.qmd` 正文写法(转 `writing-quarto`);书里项目的业务领域内容(由项目自己的技能负责)。
17
17
  - 项目带有 `.agents/skills/quarto-book-project/SKILL.md` 时,先读它的「主题与过滤器路径」,只改 `_quarto.yml` 实际引用的书内副本;没有就跳过。书内副本默认在 `theme/`,过滤器在 `filters/`。
18
+ - 编码与行尾跟 `writing-quarto` 的 `quarto/encoding.md`:UTF-8 无 BOM、样式文件用 LF;`.bat`/`.cmd` 才用 CRLF。
18
19
 
19
20
  ## 任务路由
20
21
 
@@ -23,14 +24,14 @@ metadata:
23
24
  | 任务 | 读取 |
24
25
  | --- | --- |
25
26
  | 主题的分层、令牌与组件边界 | `references/theme-system.md` |
26
- | 色板接线与暗色分层 | `assets/theme/palettes/github/meta.md` |
27
+ | 色板接线与暗色分层 | `assets/theme/palettes/flexoki/meta.md` |
27
28
  | 结构尺度令牌 | `assets/theme/css/tokens.css` 头注释 |
28
29
  | 字体、栏宽与阅读表面的取舍理由 | `knowledge/reading-surface.md` |
29
30
 
30
31
  ## P0 硬约束
31
32
 
32
33
  1. 主题资产被书根 `_quarto.yml` 以相对路径直接消费;移动或改名必须同步 `_quarto.yml`。
33
- 2. `css/` 组件规则不写色值,语义颜色只来自 `palettes/github/tokens.css`;`css/tokens.css` 只放结构尺度令牌,明暗共用。
34
+ 2. `css/` 组件规则不写色值,语义颜色只来自 `palettes/<name>/tokens.css`(默认 Flexoki;Solarized 为运行时覆盖层);`css/tokens.css` 只放结构尺度令牌,明暗共用。
34
35
  3. 这套资产是本插件主题的唯一来源。项目内若存在副本,先确认哪一份被 `_quarto.yml` 引用,再只改被引用的那一份,不假定两处自动同步。
35
36
  4. 修改主题或 `_quarto.yml` 后在仓库根目录整本 `quarto render`,验证明暗两套主题;布局调整优先修改 grid 与共享节奏,不用页面局部样式堆叠间距。
36
37
  5. 不新增字体文件、不引入新颜色来源:字体与颜色决定的理由在 `knowledge/reading-surface.md`,改决定先改知识再动资产。
@@ -12,7 +12,7 @@
12
12
  · 章节页 标题→首段引言:由本规则的 line-height / margin-bottom 控制(见下方 #quarto-document-content > p:first-of-type)
13
13
  · 首页 标题→描述:由下方 #title-block-header .description 的 margin-top 控制 */
14
14
  #title-block-header.quarto-title-block.default .quarto-title .title {
15
- font-size: 2rem; /* 32px,GitHub Markdown h1 */
15
+ font-size: 2rem; /* 32px,按正文比例 */
16
16
  font-weight: 600;
17
17
  letter-spacing: 0;
18
18
  line-height: 1.2;
@@ -20,7 +20,7 @@
20
20
  text-align: left; /* 覆盖默认 theme 居中,与左对齐正文/引言成组 */
21
21
  margin-bottom: 0; /* 章节页标题→首段进一步收紧;首页间距交由 .description 控制 */
22
22
  }
23
- /* hero eyebrow(首页眉标,gitcn「GITHUB SKILLS」同款:英文大写标签 + 绿点在文字后)。
23
+ /* hero eyebrow(首页眉标:英文小标签 + 一条短横线)。
24
24
  .hero-eyebrow 与标题块同为 #quarto-document-content 直接子级,
25
25
  用 flex + order 将其提到标题(h1+description)之前。 */
26
26
  #quarto-document-content:has(.hero-eyebrow) {
@@ -35,7 +35,7 @@
35
35
  #quarto-document-content:has(.hero-eyebrow) #title-block-header {
36
36
  order: 0;
37
37
  }
38
- /* 首页标题 36px(gitcn getting-started hero text-4xl),覆盖内容页默认 30px */
38
+ /* 首页标题 36px(hero 大标题),覆盖内容页默认 32px */
39
39
  #quarto-document-content:has(.hero-eyebrow) #title-block-header .quarto-title .title {
40
40
  font-size: 2.25rem;
41
41
  }
@@ -44,21 +44,18 @@
44
44
  align-items: center;
45
45
  gap: 0.5rem;
46
46
  margin: 0;
47
- font-size: 0.875rem;
47
+ font-size: 0.8125rem;
48
48
  font-weight: 400;
49
- letter-spacing: normal;
49
+ letter-spacing: 0;
50
50
  text-transform: none;
51
51
  color: var(--text-secondary);
52
52
  }
53
53
  .hero-eyebrow p {
54
54
  margin: 0;
55
55
  }
56
- .hero-eyebrow::after {
57
- content: "";
58
- width: 0.5rem;
59
- height: 0.625rem;
60
- background-color: var(--dot-accent);
61
- }
56
+ /* 眉标后面原本有一段 1.75rem 的强调色短横。它只是装饰:与标题之间已有 0.9rem
57
+ 的间距,短横既不能分组也不能指示层级,反而在首页第一屏抢走标题的注意力。
58
+ 留空后眉标靠字号与颜色(13px、次要色)退到标题之前,不再需要额外线条。 */
62
59
  #title-block-header .description {
63
60
  color: var(--text-secondary);
64
61
  font-size: 1.125rem;
@@ -80,26 +77,43 @@
80
77
  display: none;
81
78
  }
82
79
 
80
+ /* C++ 的 ++ 不做逐符号位移。fontTools 实测:Bright 的 + 墨迹中心在 0.23em,
81
+ 汉字在 0.357em,整套西文符号(数字、括号、比较符)都落在同一高度轴上;
82
+ 只抬 ++ 会让它脱离同一行的 × ÷ = 与括号。字形越界才是真问题——缺字会回落到
83
+ CJK 字体,墨迹中心一下跳到 0.5em,那才是「++ 偏上」的来源,按码位补进子集解决。 */
84
+
83
85
 
84
86
 
85
- /* ---------- 标题:用尺寸区分阅读层级(单字面字体下层级只靠字号) ----------
86
- h2 通栏底线显式关闭:Bootstrap 主题(cosmo)自带 h2 border-bottom,
87
- 不写会被回落默认值重新画上。章节之间靠 margin-top: 3rem 的节奏分隔。 */
87
+ /* ---------- 标题:黑体标题 + 楷体正文的双字面 ----------
88
+ 标题走 --heading-font(系统黑体,有真 600 字重),正文走 --ui-font(Bright 楷体):
89
+ 字重与字面同时分层,不再只靠字号。h2 通栏底线显式关闭:Bootstrap 主题(cosmo)
90
+ 自带 h2 border-bottom,不写会被回落默认值重新画上。 */
91
+ h1,
92
+ h2,
93
+ h3,
94
+ h4,
95
+ h5,
96
+ h6 {
97
+ font-family: var(--heading-font);
98
+ }
88
99
  h1,
89
100
  h2 {
90
101
  font-size: 1.5rem;
91
102
  font-weight: 600;
92
- line-height: 1.25;
103
+ line-height: 1.3;
93
104
  letter-spacing: 0;
94
105
  text-wrap: balance;
95
106
  border-bottom: none;
96
107
  padding-bottom: 0;
97
108
  }
98
109
  h1 { font-size: 2rem; }
110
+ /* 二级标题下不再画短线:全书唯一的横线是通栏 1px 卡片发丝线(顶栏底边、表格线、
111
+ 引用块上下边)。标题自己不带线,章节节奏只靠 margin-top 与字号台阶区分——
112
+ 一条 2.25rem 的短线和通栏发丝线不是同一套语言,反而多出一个「第三种线」。 */
99
113
  h3 {
100
- font-size: 1.25rem; /* 20px:标题台阶 32/24/20/18,h3 与 h2 拉开一档 */
114
+ font-size: 1.1875rem; /* 19px:标题台阶 32/24/19/18,h3 与 h2 拉开一档 */
101
115
  font-weight: 600;
102
- line-height: 1.5;
116
+ line-height: 1.55;
103
117
  letter-spacing: 0;
104
118
  text-wrap: balance;
105
119
  }
@@ -115,18 +129,24 @@ h5 {
115
129
  line-height: 1.2;
116
130
  letter-spacing: 0;
117
131
  }
132
+ h6 {
133
+ font-size: 0.85rem;
134
+ font-weight: 600;
135
+ line-height: 1.2;
136
+ letter-spacing: 0;
137
+ }
118
138
 
119
139
  /* h2 通栏底线已移除(2026-10):章节之间靠 margin-top: 3rem 的节奏分隔,
120
140
  不再用横线;原首页/索引页关闭底线的覆盖规则随之删除。 */
121
141
 
122
- /* ---------- 垂直节奏(上边距节奏,参考 mdBook / React / Vue 的 prose 模式) ----------
142
+ /* ---------- 垂直节奏(上边距节奏,书籍阅读的段落呼吸) ----------
123
143
  Quarto 把每个 h2 章节内容包在 <section class="level2"> 里,因此对主容器与各 section
124
144
  的直属块级子元素统一重置 margin,用上边距节奏控制间距
125
145
  —— 正文间距收成四档,档位按「两块内容是不是同一件事」取:
126
146
  粘连 0.5rem(标题→首段/列表、标题→callout、caption→其代码块)
127
- 相关 1rem(代码块↔caption、代码块↔callout、列表内代码块→验证句)
128
- 换段 1.5rem(段落/列表/代码块之间的默认距离,下方 * + * 是唯一来源,对 content 与首页一视同仁)
129
- 换节 3rem(h2 之前;h3/h4/h5 之前 2rem) */
147
+ 相关 0.9rem(代码块↔caption、代码块↔callout、列表内代码块→验证句)
148
+ 换段 1.4rem(段落/列表/代码块之间的默认距离,下方 * + * 是唯一来源)
149
+ 换节 3.25rem(h2 之前;h3/h4/h5 之前 2.25rem) */
130
150
  #quarto-document-content > *,
131
151
  #quarto-document-content section > * {
132
152
  margin-top: 0;
@@ -135,11 +155,11 @@ h5 {
135
155
  /* 换段档:段落、列表和代码块之间的默认距离。 */
136
156
  #quarto-document-content > * + *,
137
157
  #quarto-document-content section > * + * {
138
- margin-top: 1.5rem;
158
+ margin-top: 1.4rem;
139
159
  }
140
160
  #quarto-document-content p,
141
161
  #quarto-document-content li {
142
- line-height: 1.75;
162
+ line-height: 1.85;
143
163
  }
144
164
  /* 代码块上方/下方的说明(caption):次要灰 + 略小,贴近其描述的代码块;
145
165
  代码块本身保持高对比正文色(切勿把灰色涂到 pre 上) */
@@ -151,17 +171,17 @@ h5 {
151
171
  /* 代码块 → 下方段落:换段档 */
152
172
  #quarto-document-content > :is(.code-copy-outer-scaffold, pre) + p,
153
173
  #quarto-document-content section > :is(.code-copy-outer-scaffold, pre) + p {
154
- margin-top: 1.5rem;
174
+ margin-top: 1.4rem;
155
175
  }
156
176
  /* 代码块 → 下方 caption:相关档,caption 是代码块的附属说明 */
157
177
  #quarto-document-content > :is(.code-copy-outer-scaffold, pre) + .code-caption,
158
178
  #quarto-document-content section > :is(.code-copy-outer-scaffold, pre) + .code-caption {
159
- margin-top: 1rem;
179
+ margin-top: 0.9rem;
160
180
  }
161
181
  /* 正文 → 代码块:换段档 */
162
182
  #quarto-document-content > :is(p, .code-caption) + :is(.code-copy-outer-scaffold, pre),
163
183
  #quarto-document-content section > :is(p, .code-caption) + :is(.code-copy-outer-scaffold, pre) {
164
- margin-top: 1.5rem;
184
+ margin-top: 1.4rem;
165
185
  }
166
186
  /* 列表项由 li 自身承担块间距,上面的 section 直属子元素规则覆盖不到代码块后的验证句。 */
167
187
  #quarto-document-content li > :is(.code-copy-outer-scaffold, .code-with-filename, pre, div.sourceCode) + p {
@@ -179,27 +199,27 @@ h5 {
179
199
  #quarto-document-content .callout + :is(.code-copy-outer-scaffold, pre) {
180
200
  margin-top: 1rem;
181
201
  }
182
- /* h2 间距:换节档 3rem;margin-bottom 与相邻 margin-top 折叠取大者,
202
+ /* h2 间距:换节档 3.25rem;margin-bottom 与相邻 margin-top 折叠取大者,
183
203
  设 0.5rem 让标题与首段保持粘连档,不被折叠抬高一倍 */
184
204
  #quarto-document-content > h2,
185
205
  #quarto-document-content section > h2 {
186
- margin-top: 3rem;
206
+ margin-top: 3.25rem;
187
207
  margin-bottom: 0.5rem;
188
208
  }
189
209
  /* 章节页首段引言:进一步拉近与标题的间距(标题块 margin-bottom 已为 0,这里再上拉一点)
190
210
  —— 间距控制点:标题块 line-height/margin-bottom(上方 #title-block-header… .title)+ 此处首段 margin-top */
191
211
  #quarto-document-content > p:first-of-type {
192
- margin-top: 0.5rem; /* 标题→首段:干净正间距(取代负 margin hack) */
212
+ margin-top: 0.6rem; /* 标题→首段:干净正间距(取代负 margin hack) */
193
213
  }
194
214
  #quarto-document-content > h3,
195
215
  #quarto-document-content section > h3 {
196
- margin-top: 2rem;
216
+ margin-top: 2.25rem;
197
217
  }
198
218
  #quarto-document-content > h4,
199
219
  #quarto-document-content > h5,
200
220
  #quarto-document-content section > h4,
201
221
  #quarto-document-content section > h5 {
202
- margin-top: 2rem;
222
+ margin-top: 2.25rem;
203
223
  }
204
224
  /* 标题紧接的首段/列表收紧到粘连档,让「标题—首段」成组(段→标题仍保留上方疏朗留白) */
205
225
  #quarto-document-content :is(h2, h3, h4) + :is(p, ul, ol) {
@@ -232,17 +252,53 @@ h5 {
232
252
  /* ---------- 链接 ---------- */
233
253
  a {
234
254
  color: var(--link-color);
255
+ text-underline-offset: 0.18em;
235
256
  }
236
257
  a:hover {
237
- text-decoration: underline;
258
+ color: var(--link-hover-color);
259
+ text-decoration-color: var(--action-color);
238
260
  }
239
- /* r4ds:代码里的链接用细下划线 */
261
+ /* 代码里的链接用细下划线 */
240
262
  code a:any-link {
241
263
  text-decoration: underline;
242
264
  text-decoration-color: var(--link-underline);
243
265
  text-decoration-thickness: 1px;
244
266
  }
245
267
 
268
+ /* ---------- 源码模态(code-tools 打开的面板) ----------
269
+ Quarto 用 Bootstrap 模态显示 .qmd 源文件:默认白底黑字、圆角 0.5rem、
270
+ 遮罩纯黑 50%。这里把遮罩接到 --overlay-scrim,面板接卡片层令牌,
271
+ 标题用黑体,正文区用代码底色(它展示的是代码)。 */
272
+ .modal-backdrop {
273
+ background-color: var(--overlay-scrim);
274
+ }
275
+ .modal-backdrop.show {
276
+ opacity: 1;
277
+ }
278
+ .modal-content {
279
+ background: var(--surface-raised);
280
+ border: 1px solid var(--card-border);
281
+ border-radius: var(--radius-lg);
282
+ box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
283
+ }
284
+ .modal-header {
285
+ border-bottom: 1px solid var(--card-border);
286
+ padding: 0.8rem 1.1rem;
287
+ }
288
+ .modal-title {
289
+ color: var(--body-color);
290
+ font-family: var(--heading-font);
291
+ font-size: 1.0625rem;
292
+ font-weight: 600;
293
+ }
294
+ .modal-body {
295
+ background: var(--code-bg);
296
+ padding: 0.9rem 1.1rem;
297
+ }
298
+ [data-palette][data-mode="night"] .modal-content .btn-close {
299
+ filter: invert(1) grayscale(1) brightness(1.6);
300
+ }
301
+
246
302
  /* ---------- 杂项(原 misc.css:过渡 / 小屏 / 页脚 / 返回顶部 / 打印) ---------- */
247
303
  #quarto-sidebar .sidebar-item-text.sidebar-link,
248
304
  .sidebar.margin-sidebar nav[role="doc-toc"] a,
@@ -266,19 +322,102 @@ code a:any-link {
266
322
  }
267
323
  }
268
324
 
269
- .footer {
325
+ /* 页脚跟随正文结束,不被 Quarto 的 inline min-height 推到长屏底部留下大片空白。 */
326
+ body #quarto-content {
327
+ min-height: 0 !important;
328
+ }
329
+
330
+ /* ---------- 页脚贴底(短页时 colophon 落在视口底部,不在其下留背景带) ---------- */
331
+ body {
332
+ display: flex;
333
+ flex-direction: column;
334
+ min-height: 100vh;
335
+ }
336
+
337
+ body > #quarto-content {
338
+ flex: 1 0 auto;
339
+ }
340
+
341
+ /* Quarto 的三行网格(正文 / 章节翻页 / 页脚)把剩余高度留给页脚行,
342
+ 短页时 colophon 落在视口底部,正文与翻页仍靠在一起。 */
343
+ #quarto-content {
344
+ grid-template-rows:
345
+ [content-top] auto
346
+ [content-bottom] auto
347
+ [page-bottom] 1fr
348
+ [footer-bottom];
349
+ }
350
+
351
+ /* ---------- 页脚:书籍 colophon ---------- */
352
+ .book-footer {
270
353
  grid-column: 1 / -1;
354
+ grid-row: page-bottom / footer-bottom;
355
+ align-self: end;
356
+ justify-self: center;
357
+ width: 100%;
358
+ max-width: 54rem;
271
359
  color: var(--text-secondary);
272
- background-color: var(--code-bg);
360
+ background-color: transparent;
273
361
  font-family: var(--ui-font);
274
362
  font-weight: 400;
275
- font-size: 0.75rem;
363
+ font-size: 0.8125rem;
364
+ line-height: 1.7;
365
+ border-top: 1px solid var(--card-border);
276
366
  border-radius: 0;
277
- padding: 1.25rem 1rem 1rem;
278
- margin-top: 3rem;
367
+ padding: 1.1rem 1rem 1.75rem;
368
+ margin: 2.5rem 0 0;
279
369
  text-align: center;
280
370
  }
281
371
 
372
+ .book-footer p {
373
+ margin: 0;
374
+ }
375
+
376
+ .book-footer .book-footer-note {
377
+ color: var(--text-secondary);
378
+ font-size: 0.75rem;
379
+ }
380
+
381
+ /* Quarto 自动生成的 footer 只承担窄屏的源码/编辑入口;
382
+ 桌面端空 footer 不再继承 .footer 的 3rem 上边距与背景色带。 */
383
+ footer.footer {
384
+ display: none;
385
+ }
386
+
387
+ @media (max-width: 991.98px) {
388
+ footer.footer:has(.toc-actions) {
389
+ display: block;
390
+ grid-column: 1 / -1;
391
+ padding: 0;
392
+ margin: 0;
393
+ background: transparent;
394
+ border: 0;
395
+ }
396
+
397
+ /* Quarto 的 nav-footer 自带上/下大边距;手机上只留一行「查看代码」。 */
398
+ footer.footer .nav-footer {
399
+ padding: 0.25rem 1rem 0.5rem;
400
+ }
401
+
402
+ footer.footer .nav-footer-center {
403
+ margin: 0;
404
+ }
405
+
406
+ footer.footer .toc-actions {
407
+ padding: 0;
408
+ }
409
+
410
+ footer.footer .toc-actions ul {
411
+ justify-content: center;
412
+ padding: 0;
413
+ margin: 0;
414
+ }
415
+
416
+ footer.footer .toc-actions li {
417
+ margin: 0;
418
+ }
419
+ }
420
+
282
421
  body.quarto-light .navbar,
283
422
  body.quarto-dark .navbar {
284
423
  --bs-navbar-color: var(--navbar-fg);
@@ -288,35 +427,36 @@ body.quarto-dark .navbar {
288
427
  box-shadow: none;
289
428
  }
290
429
 
291
- .footer a {
430
+ .book-footer a {
292
431
  color: var(--text-link);
293
432
  }
294
- .footer a:hover {
295
- text-decoration: underline;
433
+ .book-footer a:hover {
434
+ color: var(--link-hover-color);
435
+ text-decoration-color: var(--action-color);
296
436
  }
297
437
 
298
438
  #back-to-top {
299
439
  position: fixed;
300
440
  right: 1.25rem;
301
441
  bottom: 1.25rem;
302
- width: 2.5rem;
303
- height: 2.5rem;
442
+ width: 2.25rem;
443
+ height: 2.25rem;
304
444
  display: flex;
305
445
  align-items: center;
306
446
  justify-content: center;
307
- border-radius: 50%;
447
+ border-radius: var(--radius-sm);
308
448
  border: 1px solid var(--card-border);
309
449
  background-color: var(--card-bg);
310
- color: var(--accent);
311
- font-size: 1.1rem;
450
+ color: var(--text-secondary);
451
+ font-size: 1rem;
312
452
  line-height: 1;
313
453
  cursor: pointer;
314
454
  opacity: 0;
315
455
  visibility: hidden;
316
- transform: translateY(0.5rem);
456
+ transform: translateY(0.4rem);
317
457
  transition: opacity var(--transition), transform var(--transition),
318
- background-color var(--transition);
319
- box-shadow: var(--card-shadow);
458
+ color var(--transition), border-color var(--transition);
459
+ box-shadow: none;
320
460
  z-index: 1000;
321
461
  }
322
462
  #back-to-top.show {
@@ -325,7 +465,8 @@ body.quarto-dark .navbar {
325
465
  transform: translateY(0);
326
466
  }
327
467
  #back-to-top:hover {
328
- background-color: var(--sidebar-hover-bg);
468
+ color: var(--link-hover-color);
469
+ border-color: var(--text-secondary);
329
470
  }
330
471
  @media (prefers-reduced-motion: reduce) {
331
472
  #back-to-top {
@@ -343,11 +484,23 @@ blockquote {
343
484
  }
344
485
 
345
486
  @media print {
487
+ /* 打印恢复普通文档流:页脚贴底与三行网格只服务屏幕。 */
488
+ body {
489
+ display: block;
490
+ min-height: 0;
491
+ }
492
+ #quarto-content {
493
+ grid-template-rows: none;
494
+ }
495
+ .book-footer {
496
+ grid-row: auto;
497
+ }
346
498
  /* 只隐藏复制按钮:Quarto 1.10 起 .code-copy-outer-scaffold 包裹着代码本身,
347
499
  把它一起 display:none 会导致打印/导出 PDF 时所有代码块消失。 */
348
500
  #quarto-sidebar,
349
501
  #quarto-margin-sidebar,
350
502
  .nav-page,
503
+ .page-navigation,
351
504
  .code-copy-button,
352
505
  #back-to-top {
353
506
  display: none;
@@ -357,12 +510,20 @@ blockquote {
357
510
  h4 {
358
511
  break-after: avoid;
359
512
  }
360
- pre {
513
+ pre,
514
+ figure,
515
+ .callout,
516
+ blockquote {
361
517
  break-inside: avoid;
362
518
  }
363
- #quarto-document-content a[href^="http"]::after {
364
- content: " (" attr(href) ")";
365
- font-size: 0.85em;
366
- word-break: break-all;
519
+ /* 宽表跨页时表头跟着重复,否则第二页读者不知道列名。 */
520
+ thead {
521
+ display: table-header-group;
522
+ }
523
+ @page {
524
+ size: A4;
525
+ margin: 18mm 16mm;
367
526
  }
527
+ /* 不再打印外链 URL:840px 正文列里长 URL 必然断行成灰字长串,
528
+ 脚注/参考文献里的链接读者可自行点开电子版。 */
368
529
  }