@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
@@ -0,0 +1,420 @@
1
+ <!-- 色板与时辰选择:Flexoki 是编译默认;Solarized 与 Hill Light(hill-light)只切
2
+ html[data-palette],Hill Light 另有 html[data-mode] 的四种时辰(晓雾/午荫/暮烟/夜月)。
3
+ 首屏在 head 内恢复选择,避免先画 Flexoki 再跳色;控件在导航栏工具区末尾。
4
+ favicon 是外部 SVG,读不到 CSS 变量和 data-palette,所以按色板与明暗改 href。 -->
5
+ <script>
6
+ (function () {
7
+ var PALETTE_KEY = 'book-palette';
8
+ var MODE_KEY = 'book-hill-mode';
9
+ var PALETTES = ['flexoki', 'solarized', 'hill-light'];
10
+ var MODES = ['dawn', 'noon', 'dusk', 'night'];
11
+ var MODE_LABELS = {
12
+ dawn: ['晓雾', '溪山晓雾 · 清晨'],
13
+ noon: ['午荫', '溪山午荫 · 正午'],
14
+ dusk: ['暮烟', '溪山暮烟 · 黄昏'],
15
+ night: ['夜月', '溪山夜月 · 夜晚']
16
+ };
17
+ var PALETTE_LABELS = { flexoki: 'Flexoki', solarized: 'Solarized', 'hill-light': 'Hill Light' };
18
+ var root = document.documentElement;
19
+
20
+ function read(key) {
21
+ try {
22
+ return window.localStorage.getItem(key);
23
+ } catch (e) {
24
+ return null;
25
+ }
26
+ }
27
+ function write(key, value) {
28
+ try {
29
+ window.localStorage.setItem(key, value);
30
+ } catch (e) {}
31
+ }
32
+ function isPalette(value) {
33
+ return PALETTES.indexOf(value) !== -1;
34
+ }
35
+ function isMode(value) {
36
+ return MODES.indexOf(value) !== -1;
37
+ }
38
+ // 山光的默认时辰按本地时间落点(与 hill-light 官方 head 脚本一致):
39
+ // 06–11 晓雾、11–17 午荫、17–20 暮烟,其余夜月。
40
+ function byHour() {
41
+ var hour = new Date().getHours();
42
+ if (hour >= 6 && hour < 11) return 'dawn';
43
+ if (hour >= 11 && hour < 17) return 'noon';
44
+ if (hour >= 17 && hour < 20) return 'dusk';
45
+ return 'night';
46
+ }
47
+
48
+ var initialPalette = isPalette(read(PALETTE_KEY)) ? read(PALETTE_KEY) : 'flexoki';
49
+ var initialMode = isMode(read(MODE_KEY)) ? read(MODE_KEY) : byHour();
50
+ if (initialPalette !== 'flexoki') root.setAttribute('data-palette', initialPalette);
51
+ if (initialPalette === 'hill-light') root.setAttribute('data-mode', initialMode);
52
+
53
+ // 基座镜像:Quarto 只编译两套 Bootstrap(light/dark),靠 localStorage 的
54
+ // quarto-color-scheme 决定首帧用哪套。山光的明暗由时辰决定,所以 head 里先把
55
+ // 时辰翻成 sentinel(夜月 alternate,其余 default),Quarto 的 before-body 脚本
56
+ // 读到的就是山光要的基座;只镜像山光,Flexoki/Solarized 的明暗仍由读者与圆钮决定。
57
+ // file:// 下 Quarto 不写 localStorage,镜像也会被浏览器拒绝(write 已 try/catch),
58
+ // 此时由 mount 后的 syncBase 对账兜底,可能闪一帧。
59
+ function mirrorQuartoScheme() {
60
+ if (initialPalette !== 'hill-light') return;
61
+ if (location.protocol !== 'http:' && location.protocol !== 'https:') return;
62
+ write('quarto-color-scheme', initialMode === 'night' ? 'alternate' : 'default');
63
+ }
64
+ mirrorQuartoScheme();
65
+
66
+ function palette() {
67
+ var value = root.getAttribute('data-palette');
68
+ return isPalette(value) ? value : 'flexoki';
69
+ }
70
+ function mode() {
71
+ var value = root.getAttribute('data-mode');
72
+ return isMode(value) ? value : initialMode;
73
+ }
74
+
75
+ // 明暗先看 Quarto 已写好的 body class,再看它存的偏好;都没有时退回系统偏好。
76
+ // file:// 下 Quarto 不写 localStorage,页脚观察器会在 body class 变化后再同步一次。
77
+ function quartoIsDark() {
78
+ if (document.body) {
79
+ if (document.body.classList.contains('quarto-dark')) return true;
80
+ if (document.body.classList.contains('quarto-light')) return false;
81
+ }
82
+ var scheme = read('quarto-color-scheme');
83
+ if (scheme === 'alternate' || scheme === 'dark') return true;
84
+ if (scheme === 'default' || scheme === 'light') return false;
85
+ return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
86
+ }
87
+ // 山光的明暗由时辰决定:夜月是唯一的暗色时辰。
88
+ function isDark() {
89
+ return palette() === 'hill-light' ? mode() === 'night' : quartoIsDark();
90
+ }
91
+
92
+ // 只替换 Quarto 写好的相对路径里的色板与明暗两段,保留 ./ 或 ../../ 前缀。
93
+ function syncFavicon() {
94
+ var link = document.querySelector('link[rel~="icon"]');
95
+ if (!link) return;
96
+ var href = link.getAttribute('href') || '';
97
+ var next = href.replace(
98
+ /(palettes\/)(flexoki|solarized|hill-light)(\/favicon-)(light|dark)(\.svg)/,
99
+ '$1' + palette() + '$3' + (isDark() ? 'dark' : 'light') + '$5'
100
+ );
101
+ if (next !== href) link.setAttribute('href', next);
102
+ }
103
+ window.__bookSyncFavicon = syncFavicon;
104
+ syncFavicon();
105
+
106
+ // 基座对账:山光夜月必须落在 Quarto 的 dark 基座上(body.quarto-dark 的规则
107
+ // 才生效),其余时辰落在 light 基座。只在状态不一致时调用 QuartoToggleColorScheme,
108
+ // 避免把读者在其他色板的明暗偏好翻掉;切走山光时不动基座(保持读者当前的明暗)。
109
+ function syncBase() {
110
+ if (palette() !== 'hill-light') return;
111
+ if (typeof window.quartoToggleColorScheme !== 'function') return;
112
+ if (!document.body) return;
113
+ var cls = document.body.classList;
114
+ if (!cls.contains('quarto-dark') && !cls.contains('quarto-light')) return;
115
+ if ((mode() === 'night') !== cls.contains('quarto-dark')) {
116
+ window.quartoToggleColorScheme();
117
+ }
118
+ }
119
+ window.__bookSyncBase = syncBase;
120
+
121
+ function applyPalette(value) {
122
+ if (value === 'flexoki') root.removeAttribute('data-palette');
123
+ else root.setAttribute('data-palette', value);
124
+ // 山光需要 data-mode;切走时清掉,避免其他色板误吃四时令牌。
125
+ if (value === 'hill-light') root.setAttribute('data-mode', mode());
126
+ else root.removeAttribute('data-mode');
127
+ write(PALETTE_KEY, value);
128
+ syncFavicon();
129
+ syncBase();
130
+ document.dispatchEvent(new CustomEvent('book-palette-change', { detail: value }));
131
+ }
132
+ function applyMode(value) {
133
+ root.setAttribute('data-mode', value);
134
+ write(MODE_KEY, value);
135
+ syncFavicon();
136
+ syncBase();
137
+ document.dispatchEvent(new CustomEvent('book-palette-change', { detail: 'hill-light' }));
138
+ }
139
+
140
+ // 单选组键盘语义:方向键在段间移动并即时选中,Home/End 到两端;
141
+ // 只有选中段进 Tab 序列(roving tabindex),与原生 radio 一致。
142
+ function bindKeys(wrap, buttons, current, activate) {
143
+ wrap.addEventListener('keydown', function (event) {
144
+ var index = buttons.findIndex(function (button) {
145
+ return button.dataset.key === current();
146
+ });
147
+ if (index === -1) index = 0;
148
+ var next = null;
149
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
150
+ next = (index + 1) % buttons.length;
151
+ } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
152
+ next = (index - 1 + buttons.length) % buttons.length;
153
+ } else if (event.key === 'Home') {
154
+ next = 0;
155
+ } else if (event.key === 'End') {
156
+ next = buttons.length - 1;
157
+ }
158
+ if (next === null) return;
159
+ event.preventDefault();
160
+ activate(buttons[next].dataset.key);
161
+ buttons[next].focus();
162
+ });
163
+ }
164
+
165
+ function makeSegment(label) {
166
+ var wrap = document.createElement('div');
167
+ wrap.className = 'palette-picker';
168
+ wrap.setAttribute('role', 'radiogroup');
169
+ wrap.setAttribute('aria-label', label);
170
+ var segment = document.createElement('div');
171
+ segment.className = 'palette-segment';
172
+ wrap.appendChild(segment);
173
+ return { wrap: wrap, segment: segment };
174
+ }
175
+
176
+ // 色板下拉:三段文字胶囊在 Hill Light 出现后还要并排多一段四时辰,顶栏右端被挤到
177
+ // 近 190px,窄屏只能把名字全裁掉。改成「当前色板」按钮 + 列表:闭合态只占色点与名字,
178
+ // 展开后三套色板一次看全。
179
+ // 语义取 select-only combobox:按钮带着当前值(这是菜单按钮做不到的),面板是 listbox,
180
+ // 焦点留在按钮上、用 aria-activedescendant 指当前项——APG 的原生键位(方向键、Home/End、
181
+ // 首字母、Enter 提交、Esc 无提交关闭)就是照这个模型写的。
182
+ function makeMenu(label) {
183
+ var wrap = document.createElement('div');
184
+ wrap.className = 'palette-picker palette-menu';
185
+ var trigger = document.createElement('button');
186
+ trigger.type = 'button';
187
+ trigger.className = 'palette-trigger';
188
+ trigger.setAttribute('role', 'combobox');
189
+ trigger.setAttribute('aria-haspopup', 'listbox');
190
+ trigger.setAttribute('aria-controls', 'book-palette-list');
191
+ trigger.setAttribute('aria-expanded', 'false');
192
+ trigger.setAttribute('aria-autocomplete', 'none');
193
+ trigger.title = label;
194
+ trigger.innerHTML =
195
+ '<span class="palette-swatch" aria-hidden="true"></span>' +
196
+ '<span class="palette-trigger-label"></span>' +
197
+ '<svg class="palette-caret" viewBox="0 0 10 10" aria-hidden="true">' +
198
+ '<path d="M2 3.6 5 6.6 8 3.6" fill="none" stroke="currentColor" stroke-width="1.4" ' +
199
+ 'stroke-linecap="round" stroke-linejoin="round"/></svg>';
200
+ var list = document.createElement('div');
201
+ list.className = 'palette-menu-list';
202
+ list.id = 'book-palette-list';
203
+ list.setAttribute('role', 'listbox');
204
+ list.setAttribute('aria-label', label);
205
+ list.hidden = true;
206
+ wrap.appendChild(trigger);
207
+ wrap.appendChild(list);
208
+ return { wrap: wrap, trigger: trigger, list: list };
209
+ }
210
+
211
+ // 四时辰图标:12px 格内的极简线性图。晓雾/午荫/暮烟/夜月是气氛词,圆点或首字都
212
+ // 传达不了差别,所以缺口用图形补:破晓的日头、正午的烈日、擦山的低日、夜月的弯月。
213
+ // 颜色一律 currentColor,跟分段控件的选中态一起变。
214
+ var MODE_ICONS = {
215
+ dawn:
216
+ '<svg viewBox="0 0 12 12" aria-hidden="true">' +
217
+ '<path d="M1.6 8.6h8.8" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>' +
218
+ '<path d="M3.3 8.6a2.7 2.7 0 0 1 5.4 0" fill="none" stroke="currentColor" stroke-width="1.2"/>' +
219
+ '<path d="M6 2.3v1.2M3.5 4.1l.85.85M8.5 4.1l-.85.85" stroke="currentColor" ' +
220
+ 'stroke-width="1.2" stroke-linecap="round"/></svg>',
221
+ noon:
222
+ '<svg viewBox="0 0 12 12" aria-hidden="true">' +
223
+ '<circle cx="6" cy="6" r="2.1" fill="none" stroke="currentColor" stroke-width="1.2"/>' +
224
+ '<path d="M6 1.4v1.1M6 9.5v1.1M1.4 6h1.1M9.5 6h1.1M2.75 2.75l.8.8M8.45 8.45l.8.8' +
225
+ 'M9.25 2.75l-.8.8M3.55 8.45l-.8.8" stroke="currentColor" stroke-width="1.2" ' +
226
+ 'stroke-linecap="round"/></svg>',
227
+ dusk:
228
+ '<svg viewBox="0 0 12 12" aria-hidden="true">' +
229
+ '<path d="M1.6 8.6h8.8" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>' +
230
+ '<path d="M3.3 8.6a2.7 2.7 0 0 1 5.4 0" fill="none" stroke="currentColor" stroke-width="1.2"/>' +
231
+ '<path d="M2.4 4.9h1.7M7.9 4.9h1.7" stroke="currentColor" stroke-width="1.2" ' +
232
+ 'stroke-linecap="round"/></svg>',
233
+ night:
234
+ '<svg viewBox="0 0 12 12" aria-hidden="true">' +
235
+ '<path d="M8.4 2.5a3.9 3.9 0 1 0 1.2 5.6 4.8 4.8 0 0 1-1.2-5.6Z" fill="currentColor"/></svg>'
236
+ };
237
+
238
+ function mount() {
239
+ var tools = document.querySelector('.quarto-navbar-tools.tools-end');
240
+ if (!tools || tools.querySelector('.palette-picker')) return;
241
+
242
+ var paletteGroup = makeMenu('选择色板');
243
+ var paletteButtons = PALETTES.map(function (key) {
244
+ var button = document.createElement('div');
245
+ button.className = 'palette-menu-item';
246
+ button.id = 'book-palette-opt-' + key;
247
+ button.setAttribute('role', 'option');
248
+ button.setAttribute('aria-selected', 'false');
249
+ button.dataset.key = key;
250
+ button.innerHTML =
251
+ '<span class="palette-swatch" aria-hidden="true"></span>' +
252
+ '<span class="palette-menu-label">' + PALETTE_LABELS[key] + '</span>' +
253
+ '<svg class="palette-check" viewBox="0 0 12 12" aria-hidden="true">' +
254
+ '<path d="M2.4 6.3 4.7 8.6 9.6 3.7" fill="none" stroke="currentColor" stroke-width="1.6" ' +
255
+ 'stroke-linecap="round" stroke-linejoin="round"/></svg>';
256
+ button.addEventListener('click', function () {
257
+ applyPalette(key);
258
+ setMenu(false, true);
259
+ });
260
+ paletteGroup.list.appendChild(button);
261
+ return button;
262
+ });
263
+
264
+ // 时辰仍用分段控件:四项是同一维度上的并列值,一眼看全比再开一层菜单快。
265
+ var modeGroup = makeSegment('选择时辰');
266
+ modeGroup.wrap.classList.add('mode-picker');
267
+ modeGroup.wrap.title = '选择时辰';
268
+ var modeButtons = MODES.map(function (key) {
269
+ var button = document.createElement('button');
270
+ button.type = 'button';
271
+ button.className = 'palette-segment-option';
272
+ button.setAttribute('role', 'radio');
273
+ button.dataset.key = key;
274
+ button.innerHTML = MODE_ICONS[key] + '<span class="palette-mode-label">' + MODE_LABELS[key][0] + '</span>';
275
+ button.setAttribute('aria-label', MODE_LABELS[key][1]);
276
+ button.title = MODE_LABELS[key][1];
277
+ button.addEventListener('click', function () {
278
+ applyMode(key);
279
+ });
280
+ modeGroup.segment.appendChild(button);
281
+ return button;
282
+ });
283
+
284
+ var activeIndex = -1;
285
+
286
+ function menuOpen() {
287
+ return !paletteGroup.list.hidden;
288
+ }
289
+ function currentIndex() {
290
+ var value = palette();
291
+ for (var i = 0; i < paletteButtons.length; i++) {
292
+ if (paletteButtons[i].dataset.key === value) return i;
293
+ }
294
+ return 0;
295
+ }
296
+ function setActive(index) {
297
+ activeIndex = (index + paletteButtons.length) % paletteButtons.length;
298
+ paletteButtons.forEach(function (button, i) {
299
+ button.classList.toggle('is-active', i === activeIndex);
300
+ });
301
+ paletteGroup.trigger.setAttribute('aria-activedescendant', paletteButtons[activeIndex].id);
302
+ }
303
+ function clearActive() {
304
+ activeIndex = -1;
305
+ paletteButtons.forEach(function (button) {
306
+ button.classList.remove('is-active');
307
+ });
308
+ paletteGroup.trigger.removeAttribute('aria-activedescendant');
309
+ }
310
+ function setMenu(open, refocus) {
311
+ paletteGroup.list.hidden = !open;
312
+ paletteGroup.trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
313
+ if (open) {
314
+ setActive(currentIndex());
315
+ } else {
316
+ clearActive();
317
+ if (refocus) paletteGroup.trigger.focus();
318
+ }
319
+ }
320
+
321
+ paletteGroup.trigger.addEventListener('click', function () {
322
+ setMenu(!menuOpen(), false);
323
+ });
324
+ paletteGroup.trigger.addEventListener('keydown', function (event) {
325
+ var key = event.key;
326
+ if (!menuOpen()) {
327
+ if (key === 'ArrowDown' || key === 'ArrowUp' || key === 'Enter' || key === ' ') {
328
+ event.preventDefault();
329
+ setMenu(true, false);
330
+ }
331
+ return;
332
+ }
333
+ if (key === 'ArrowDown') {
334
+ event.preventDefault();
335
+ setActive(activeIndex + 1);
336
+ } else if (key === 'ArrowUp') {
337
+ event.preventDefault();
338
+ setActive(activeIndex - 1);
339
+ } else if (key === 'Home') {
340
+ event.preventDefault();
341
+ setActive(0);
342
+ } else if (key === 'End') {
343
+ event.preventDefault();
344
+ setActive(paletteButtons.length - 1);
345
+ } else if (key === 'Enter' || key === ' ') {
346
+ event.preventDefault();
347
+ if (activeIndex >= 0) applyPalette(paletteButtons[activeIndex].dataset.key);
348
+ setMenu(false, true);
349
+ } else if (key === 'Escape') {
350
+ event.preventDefault();
351
+ setMenu(false, true);
352
+ } else if (key === 'Tab') {
353
+ setMenu(false, false);
354
+ } else if (key.length === 1) {
355
+ // 首字母跳转:三套色板的首字母正好是 f / s / h。
356
+ var letter = key.toLowerCase();
357
+ var hit = paletteButtons.filter(function (button) {
358
+ return PALETTE_LABELS[button.dataset.key].toLowerCase().charAt(0) === letter;
359
+ })[0];
360
+ if (hit) {
361
+ event.preventDefault();
362
+ setActive(paletteButtons.indexOf(hit));
363
+ }
364
+ }
365
+ });
366
+ document.addEventListener('click', function (event) {
367
+ if (menuOpen() && !paletteGroup.wrap.contains(event.target)) setMenu(false, false);
368
+ });
369
+
370
+ function syncPalette() {
371
+ var value = palette();
372
+ var name = PALETTE_LABELS[value];
373
+ paletteGroup.trigger.dataset.key = value;
374
+ paletteGroup.trigger.querySelector('.palette-trigger-label').textContent = name;
375
+ // 无障碍名带上可见的当前色板名,满足 2.5.3(Label in Name)。
376
+ paletteGroup.trigger.setAttribute('aria-label', '选择色板,当前 ' + name);
377
+ paletteButtons.forEach(function (button) {
378
+ button.setAttribute('aria-selected', button.dataset.key === value ? 'true' : 'false');
379
+ });
380
+ modeGroup.wrap.hidden = value !== 'hill-light';
381
+ }
382
+ function syncMode() {
383
+ var value = mode();
384
+ modeButtons.forEach(function (button) {
385
+ var checked = button.dataset.key === value;
386
+ button.setAttribute('aria-checked', checked ? 'true' : 'false');
387
+ button.tabIndex = checked ? 0 : -1;
388
+ });
389
+ }
390
+
391
+ bindKeys(modeGroup.wrap, modeButtons, mode, applyMode);
392
+
393
+ syncPalette();
394
+ syncMode();
395
+ tools.appendChild(paletteGroup.wrap);
396
+ tools.appendChild(modeGroup.wrap);
397
+
398
+ document.addEventListener('book-palette-change', function () {
399
+ syncPalette();
400
+ syncMode();
401
+ syncBase();
402
+ });
403
+ // 返回页(bfcache)时 body class 可能被 Quarto 复原,再对账一次。
404
+ window.addEventListener('pageshow', function (event) {
405
+ if (event.persisted) syncBase();
406
+ });
407
+ syncBase();
408
+ }
409
+
410
+ if (document.readyState === 'loading') {
411
+ document.addEventListener('DOMContentLoaded', function () {
412
+ syncFavicon();
413
+ mount();
414
+ });
415
+ } else {
416
+ syncFavicon();
417
+ mount();
418
+ }
419
+ })();
420
+ </script>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="书">
2
+ <path fill="#000000" fill-rule="evenodd" d="M32 18c-6-5-15-7-24-6v36c9-1 18 1 24 6 6-5 15-7 24-6V12c-9-1-18 1-24 6Zm-2 3v29c-5-3-11-4-17-4V16c6 0 12 2 17 5Zm4 0c5-3 11-5 17-5v30c-6 0-12 1-17 4V21Z"/>
3
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="书">
2
+ <rect width="64" height="64" rx="14" fill="#100F0F"/>
3
+ <path fill="#FFFCF0" fill-rule="evenodd" d="M32 18c-6-5-15-7-24-6v36c9-1 18 1 24 6 6-5 15-7 24-6V12c-9-1-18 1-24 6Zm-2 3v29c-5-3-11-4-17-4V16c6 0 12 2 17 5Zm4 0c5-3 11-5 17-5v30c-6 0-12 1-17 4V21Z"/>
4
+ <rect x="30" y="20" width="4" height="32" fill="#3AA99F"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="书">
2
+ <rect width="64" height="64" rx="14" fill="#FFFCF0"/>
3
+ <path fill="#100F0F" fill-rule="evenodd" d="M32 18c-6-5-15-7-24-6v36c9-1 18 1 24 6 6-5 15-7 24-6V12c-9-1-18 1-24 6Zm-2 3v29c-5-3-11-4-17-4V16c6 0 12 2 17 5Zm4 0c5-3 11-5 17-5v30c-6 0-12 1-17 4V21Z"/>
4
+ <rect x="30" y="20" width="4" height="32" fill="#24837B"/>
5
+ </svg>
@@ -0,0 +1,214 @@
1
+ {
2
+ "metadata": {
3
+ "name": "Quarto Book Flexoki Dark",
4
+ "revision": 1,
5
+ "license": "SPDX-License-Identifier: MIT",
6
+ "copyright": [
7
+ "SPDX-FileCopyrightText: Steph Ango (Flexoki, MIT)",
8
+ "SPDX-FileCopyrightText: 2020 Waqar Ahmed <waqar.17a@gmail.com> (skylighting 结构基础)"
9
+ ]
10
+ },
11
+ "background-color": "#1c1b1a",
12
+ "editor-colors": {
13
+ "BackgroundColor": "#1c1b1a",
14
+ "BracketMatching": "#343331",
15
+ "CodeFolding": "#1c1b1a",
16
+ "CurrentLine": "#1c1b1a",
17
+ "CurrentLineNumber": "#cecdc3",
18
+ "IconBorder": "#1c1b1a",
19
+ "IndentationLine": "#343331",
20
+ "LineNumbers": "#878580",
21
+ "MarkBookmark": "#4385be",
22
+ "MarkBreakpointActive": "#d14d41",
23
+ "MarkBreakpointDisabled": "#8b7ec8",
24
+ "MarkBreakpointReached": "#d0a215",
25
+ "MarkError": "#d14d41",
26
+ "MarkExecution": "#8b7ec8",
27
+ "MarkWarning": "#da702c",
28
+ "ModifiedLines": "#d14d41",
29
+ "ReplaceHighlight": "#879a39",
30
+ "SavedLines": "#879a39",
31
+ "SearchHighlight": "#d0a215",
32
+ "Separator": "#343331",
33
+ "SpellChecking": "#d14d41",
34
+ "TabMarker": "#575653",
35
+ "TextSelection": "#343331",
36
+ "WordWrapMarker": "#343331"
37
+ },
38
+ "text-styles": {
39
+ "Alert": {
40
+ "bold": true,
41
+ "selected-text-color": "#d14d41",
42
+ "text-color": "#d14d41"
43
+ },
44
+ "Annotation": {
45
+ "selected-text-color": "#878580",
46
+ "text-color": "#878580"
47
+ },
48
+ "Attribute": {
49
+ "selected-text-color": "#d0a215",
50
+ "text-color": "#d0a215"
51
+ },
52
+ "BaseN": {
53
+ "selected-text-color": "#8b7ec8",
54
+ "text-color": "#8b7ec8"
55
+ },
56
+ "BuiltIn": {
57
+ "selected-text-color": "#ce5d97",
58
+ "text-color": "#ce5d97"
59
+ },
60
+ "Char": {
61
+ "selected-text-color": "#3aa99f",
62
+ "text-color": "#3aa99f"
63
+ },
64
+ "Comment": {
65
+ "selected-text-color": "#878580",
66
+ "text-color": "#878580"
67
+ },
68
+ "CommentVar": {
69
+ "selected-text-color": "#878580",
70
+ "text-color": "#878580"
71
+ },
72
+ "Constant": {
73
+ "selected-text-color": "#8b7ec8",
74
+ "text-color": "#8b7ec8"
75
+ },
76
+ "ControlFlow": {
77
+ "selected-text-color": "#879a39",
78
+ "text-color": "#879a39"
79
+ },
80
+ "DataType": {
81
+ "selected-text-color": "#d0a215",
82
+ "text-color": "#d0a215"
83
+ },
84
+ "DecVal": {
85
+ "selected-text-color": "#8b7ec8",
86
+ "text-color": "#8b7ec8"
87
+ },
88
+ "Documentation": {
89
+ "selected-text-color": "#878580",
90
+ "text-color": "#878580"
91
+ },
92
+ "Error": {
93
+ "selected-text-color": "#d14d41",
94
+ "text-color": "#d14d41",
95
+ "underline": true
96
+ },
97
+ "Extension": {
98
+ "bold": true,
99
+ "selected-text-color": "#d14d41",
100
+ "text-color": "#d14d41"
101
+ },
102
+ "Float": {
103
+ "selected-text-color": "#8b7ec8",
104
+ "text-color": "#8b7ec8"
105
+ },
106
+ "Function": {
107
+ "selected-text-color": "#da702c",
108
+ "text-color": "#da702c",
109
+ "bold": true
110
+ },
111
+ "Import": {
112
+ "selected-text-color": "#d14d41",
113
+ "text-color": "#d14d41"
114
+ },
115
+ "Information": {
116
+ "selected-text-color": "#878580",
117
+ "text-color": "#878580"
118
+ },
119
+ "Keyword": {
120
+ "selected-text-color": "#879a39",
121
+ "text-color": "#879a39"
122
+ },
123
+ "Normal": {
124
+ "selected-text-color": "#cecdc3",
125
+ "text-color": "#cecdc3"
126
+ },
127
+ "Operator": {
128
+ "selected-text-color": "#d14d41",
129
+ "text-color": "#d14d41"
130
+ },
131
+ "Others": {
132
+ "selected-text-color": "#8b7ec8",
133
+ "text-color": "#8b7ec8"
134
+ },
135
+ "Preprocessor": {
136
+ "selected-text-color": "#4385be",
137
+ "text-color": "#4385be"
138
+ },
139
+ "RegionMarker": {
140
+ "selected-text-color": "#878580",
141
+ "text-color": "#878580"
142
+ },
143
+ "SpecialChar": {
144
+ "selected-text-color": "#8b7ec8",
145
+ "text-color": "#8b7ec8"
146
+ },
147
+ "SpecialString": {
148
+ "selected-text-color": "#3aa99f",
149
+ "text-color": "#3aa99f"
150
+ },
151
+ "String": {
152
+ "selected-text-color": "#3aa99f",
153
+ "text-color": "#3aa99f"
154
+ },
155
+ "Variable": {
156
+ "selected-text-color": "#4385be",
157
+ "text-color": "#4385be"
158
+ },
159
+ "VerbatimString": {
160
+ "selected-text-color": "#3aa99f",
161
+ "text-color": "#3aa99f"
162
+ },
163
+ "Warning": {
164
+ "selected-text-color": "#da702c",
165
+ "text-color": "#da702c"
166
+ }
167
+ },
168
+ "custom-styles": {
169
+ "INI Files": {
170
+ "Section": {
171
+ "selected-text-color": "#8b7ec8",
172
+ "text-color": "#8b7ec8"
173
+ }
174
+ },
175
+ "Python": {
176
+ "Builtin Function": {
177
+ "selected-text-color": "#ce5d97",
178
+ "text-color": "#ce5d97"
179
+ },
180
+ "Import": {
181
+ "selected-text-color": "#d14d41",
182
+ "text-color": "#d14d41"
183
+ },
184
+ "Special Variable": {
185
+ "selected-text-color": "#4385be",
186
+ "text-color": "#4385be"
187
+ }
188
+ },
189
+ "Rust": {
190
+ "Attribute": {
191
+ "selected-text-color": "#d0a215",
192
+ "text-color": "#d0a215"
193
+ },
194
+ "Macro": {
195
+ "selected-text-color": "#4385be",
196
+ "text-color": "#4385be"
197
+ },
198
+ "Self": {
199
+ "selected-text-color": "#ce5d97",
200
+ "text-color": "#ce5d97"
201
+ },
202
+ "Trait": {
203
+ "selected-text-color": "#da702c",
204
+ "text-color": "#da702c"
205
+ }
206
+ },
207
+ "XML": {
208
+ "Element": {
209
+ "selected-text-color": "#4385be",
210
+ "text-color": "#4385be"
211
+ }
212
+ }
213
+ }
214
+ }