@kamishibai/sdk 0.1.1
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.
- package/README.md +192 -0
- package/package.json +54 -0
- package/src/blocks/board.js +210 -0
- package/src/blocks/callout.js +63 -0
- package/src/blocks/code.js +28 -0
- package/src/blocks/deck.js +76 -0
- package/src/blocks/diagram.js +265 -0
- package/src/blocks/element.js +51 -0
- package/src/blocks/graph.js +264 -0
- package/src/blocks/grid.js +156 -0
- package/src/blocks/index.js +106 -0
- package/src/blocks/list.js +50 -0
- package/src/blocks/placement.js +119 -0
- package/src/blocks/prose.js +28 -0
- package/src/blocks/quote.js +25 -0
- package/src/blocks/raw.js +47 -0
- package/src/blocks/registry.js +158 -0
- package/src/blocks/schema-parts.js +19 -0
- package/src/blocks/section.js +53 -0
- package/src/blocks/slide.js +104 -0
- package/src/blocks/stat.js +83 -0
- package/src/blocks/table.js +50 -0
- package/src/blocks/timeline.js +80 -0
- package/src/cli/commands/close.js +51 -0
- package/src/cli/commands/comments.js +73 -0
- package/src/cli/commands/debug.js +34 -0
- package/src/cli/commands/example.js +22 -0
- package/src/cli/commands/export.js +10 -0
- package/src/cli/commands/init.js +53 -0
- package/src/cli/commands/lint.js +93 -0
- package/src/cli/commands/list.js +54 -0
- package/src/cli/commands/open.js +31 -0
- package/src/cli/commands/promote.js +38 -0
- package/src/cli/commands/render.js +31 -0
- package/src/cli/commands/replay.js +49 -0
- package/src/cli/commands/schema.js +10 -0
- package/src/cli/commands/serve.js +178 -0
- package/src/cli/commands/setup.js +64 -0
- package/src/cli/commands/snapshot.js +29 -0
- package/src/cli/commands/templates.js +75 -0
- package/src/cli/deliver.js +54 -0
- package/src/cli/emit.js +29 -0
- package/src/cli/format.js +153 -0
- package/src/cli/index.js +365 -0
- package/src/cli/registry.js +18 -0
- package/src/core/blocks.js +147 -0
- package/src/core/diagram.js +282 -0
- package/src/core/errors.js +156 -0
- package/src/core/example.js +109 -0
- package/src/core/ir.js +62 -0
- package/src/core/lint-gates.js +427 -0
- package/src/core/lint.js +281 -0
- package/src/core/scan.js +84 -0
- package/src/core/schema.js +88 -0
- package/src/core/spec-check.js +33 -0
- package/src/core/validate.js +44 -0
- package/src/core/version.js +18 -0
- package/src/core/vocabulary.js +140 -0
- package/src/delivery/atomic.js +71 -0
- package/src/delivery/comments.js +180 -0
- package/src/delivery/home.js +70 -0
- package/src/delivery/open.js +31 -0
- package/src/delivery/project.js +141 -0
- package/src/delivery/read.js +109 -0
- package/src/delivery/run.js +95 -0
- package/src/delivery/scaffold-blueprints.js +728 -0
- package/src/delivery/store.js +219 -0
- package/src/delivery/template-extensions.js +183 -0
- package/src/delivery/template-format.js +112 -0
- package/src/delivery/template-package.js +376 -0
- package/src/delivery/template-promote.js +240 -0
- package/src/delivery/template-scaffold.js +181 -0
- package/src/delivery/templates.js +192 -0
- package/src/delivery/toml.js +195 -0
- package/src/delivery/write.js +35 -0
- package/src/export/browser.js +130 -0
- package/src/export/index.js +96 -0
- package/src/export/pdf.js +25 -0
- package/src/export/png.js +40 -0
- package/src/export/pptx.js +48 -0
- package/src/export/slides.js +33 -0
- package/src/export/snapshot.js +33 -0
- package/src/layouts/article.js +103 -0
- package/src/layouts/canvas.js +144 -0
- package/src/layouts/card.js +128 -0
- package/src/layouts/deck.js +88 -0
- package/src/layouts/index.js +90 -0
- package/src/layouts/one-page.js +161 -0
- package/src/layouts/registry.js +251 -0
- package/src/layouts/resume.js +172 -0
- package/src/layouts/template-index.js +78 -0
- package/src/parser/artifact.js +38 -0
- package/src/parser/container.js +103 -0
- package/src/parser/index.js +223 -0
- package/src/parser/tokens.js +265 -0
- package/src/render/board-filter.client.js +80 -0
- package/src/render/compile.js +29 -0
- package/src/render/context.js +98 -0
- package/src/render/element.js +32 -0
- package/src/render/fonts.js +129 -0
- package/src/render/graph-hover.client.js +148 -0
- package/src/render/html.js +52 -0
- package/src/render/index.js +241 -0
- package/src/render/measure.js +60 -0
- package/src/render/placement.js +136 -0
- package/src/render/playback.client.js +74 -0
- package/src/render/scale-to-fit.client.js +136 -0
- package/src/render/scale.js +41 -0
- package/src/render/skeleton.js +131 -0
- package/src/render/ssr.js +24 -0
- package/src/render/styles.js +56 -0
- package/src/render/templates.js +191 -0
- package/src/serve/daemon.js +117 -0
- package/src/serve/overlay.js +213 -0
- package/src/serve/protocol.js +36 -0
- package/src/serve/server.js +264 -0
- package/templates/kami/cards/components.js +40 -0
- package/templates/kami/cards/index.js +25 -0
- package/templates/kami/cards/manifest.js +67 -0
- package/templates/kami/cards/styles.css +389 -0
- package/templates/kami/long-form/components.js +102 -0
- package/templates/kami/long-form/index.js +25 -0
- package/templates/kami/long-form/manifest.js +87 -0
- package/templates/kami/long-form/styles.css +481 -0
- package/templates/kami/one-page/components.js +48 -0
- package/templates/kami/one-page/index.js +27 -0
- package/templates/kami/one-page/manifest.js +65 -0
- package/templates/kami/one-page/styles.css +375 -0
- package/templates/kami/resume/components.js +51 -0
- package/templates/kami/resume/index.js +27 -0
- package/templates/kami/resume/manifest.js +65 -0
- package/templates/kami/resume/styles.css +424 -0
- package/templates/kami/slides/components.js +41 -0
- package/templates/kami/slides/index.js +26 -0
- package/templates/kami/slides/manifest.js +64 -0
- package/templates/kami/slides/styles.css +406 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--paper: #f5f4ed;
|
|
3
|
+
--surface: #faf9f5;
|
|
4
|
+
--accent: #1B365D;
|
|
5
|
+
--accent-soft: #dbe2ec;
|
|
6
|
+
--rule: #ddd9cc;
|
|
7
|
+
--text-primary: #23201a;
|
|
8
|
+
--text-secondary: #5c574c;
|
|
9
|
+
--text-muted: #8a8371;
|
|
10
|
+
--code-bg: #efece1;
|
|
11
|
+
--warn: #8a5a12;
|
|
12
|
+
/* ksb:measure --measure */
|
|
13
|
+
/* ksb:maxWidth --max-width */
|
|
14
|
+
/* Decoration, not geometry: the grid is 24 tracks because that is this SDK's
|
|
15
|
+
column vocabulary, and how many of them one card takes is read by this
|
|
16
|
+
stylesheet and by nothing else. Numbers only CSS asks about belong in the
|
|
17
|
+
skin, never in the layout. */
|
|
18
|
+
--card-columns: 24;
|
|
19
|
+
--card-span: 8;
|
|
20
|
+
--card-gap: 24px;
|
|
21
|
+
/* Declared means embedded (CONTRACT A4): only families that really ship with
|
|
22
|
+
the artifact are named here; the rest fall back to generic keywords. */
|
|
23
|
+
--serif: 'Noto Serif TC', serif;
|
|
24
|
+
--mono: monospace;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
* {
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
html,
|
|
32
|
+
body {
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
body {
|
|
38
|
+
background: var(--paper);
|
|
39
|
+
color: var(--text-primary);
|
|
40
|
+
font-family: var(--serif);
|
|
41
|
+
font-size: 17px;
|
|
42
|
+
line-height: 1.55;
|
|
43
|
+
-webkit-font-smoothing: antialiased;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* The board: the `card` layout's outer frame. It is as wide as --max-width,
|
|
47
|
+
which is a page width and not a type ruler -- three cards across is nothing
|
|
48
|
+
like a column of prose, which is exactly why the two numbers are two. */
|
|
49
|
+
.cards {
|
|
50
|
+
max-width: var(--max-width);
|
|
51
|
+
margin: 0 auto;
|
|
52
|
+
padding: 72px 28px 96px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* The lead-in: whatever sat at the top level and was not a chapter. It keeps
|
|
56
|
+
the measure, because it is ordinary running text. */
|
|
57
|
+
.cards-intro {
|
|
58
|
+
max-width: var(--measure);
|
|
59
|
+
margin: 0 0 40px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.cards-intro > * + * {
|
|
63
|
+
margin-top: 18px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* The card grid: 24 real column tracks, laid on the board itself. A card spans
|
|
67
|
+
--card-span of them, so the same stylesheet is a three-across board of
|
|
68
|
+
knowledge cards or a two-across dashboard by changing one number. */
|
|
69
|
+
.card-grid {
|
|
70
|
+
display: grid;
|
|
71
|
+
grid-template-columns: repeat(var(--card-columns), minmax(0, 1fr));
|
|
72
|
+
column-gap: var(--card-gap);
|
|
73
|
+
row-gap: var(--card-gap);
|
|
74
|
+
align-items: start;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.card {
|
|
78
|
+
grid-column: span var(--card-span);
|
|
79
|
+
/* min-width: 0 -- a grid item defaults to min-width: auto, so one long
|
|
80
|
+
unbroken word (a URL, a code span) widens its track and silently narrows
|
|
81
|
+
every neighbour on the row. */
|
|
82
|
+
min-width: 0;
|
|
83
|
+
padding: 24px 26px 26px;
|
|
84
|
+
background: var(--surface);
|
|
85
|
+
border: 1px solid var(--rule);
|
|
86
|
+
border-radius: 6px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* A card is a whole chapter, so its own outer margins would fight the grid gap.
|
|
90
|
+
The section keeps its inner rhythm and gives up its outer one. */
|
|
91
|
+
.card > .section {
|
|
92
|
+
margin: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.card > .section > .section-title {
|
|
96
|
+
margin-top: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.masthead {
|
|
100
|
+
border-bottom: 2px solid var(--accent);
|
|
101
|
+
padding-bottom: 20px;
|
|
102
|
+
margin-bottom: 40px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.masthead .kicker {
|
|
106
|
+
font-size: 12px;
|
|
107
|
+
letter-spacing: 0.22em;
|
|
108
|
+
text-transform: uppercase;
|
|
109
|
+
color: var(--accent);
|
|
110
|
+
margin: 0 0 10px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.masthead .doc-title {
|
|
114
|
+
font-size: 34px;
|
|
115
|
+
line-height: 1.3;
|
|
116
|
+
font-weight: 700;
|
|
117
|
+
margin: 0;
|
|
118
|
+
color: var(--text-primary);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.masthead .byline {
|
|
122
|
+
margin: 12px 0 0;
|
|
123
|
+
font-size: 14px;
|
|
124
|
+
color: var(--text-secondary);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.section {
|
|
128
|
+
margin: 0 0 24px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.section > .section-title {
|
|
132
|
+
font-weight: 700;
|
|
133
|
+
color: var(--accent);
|
|
134
|
+
line-height: 1.35;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.section-l1 > .section-title {
|
|
138
|
+
font-size: 21px;
|
|
139
|
+
margin: 0 0 16px;
|
|
140
|
+
padding-bottom: 8px;
|
|
141
|
+
border-bottom: 1px solid var(--rule);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.section-l2 > .section-title {
|
|
145
|
+
font-size: 17px;
|
|
146
|
+
margin: 24px 0 10px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.section-l3 > .section-title,
|
|
150
|
+
.section-l4 > .section-title,
|
|
151
|
+
.section-l5 > .section-title,
|
|
152
|
+
.section-l6 > .section-title {
|
|
153
|
+
font-size: 15px;
|
|
154
|
+
margin: 20px 0 8px;
|
|
155
|
+
color: var(--text-primary);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.prose {
|
|
159
|
+
margin: 0 0 14px;
|
|
160
|
+
line-height: 1.55;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.prose:last-child {
|
|
164
|
+
margin-bottom: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.prose a {
|
|
168
|
+
color: var(--accent);
|
|
169
|
+
text-underline-offset: 3px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Lists arrive two ways -- a list block, and list markup pushed into prose by a
|
|
173
|
+
raw island -- and both get the same rules, or the same source would look
|
|
174
|
+
different depending on which door it came through. */
|
|
175
|
+
.prose ul,
|
|
176
|
+
.prose ol,
|
|
177
|
+
.list {
|
|
178
|
+
margin: 0 0 14px;
|
|
179
|
+
padding-left: 1.4em;
|
|
180
|
+
line-height: 1.55;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.prose li,
|
|
184
|
+
.list li {
|
|
185
|
+
margin: 0 0 6px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.prose li:last-child,
|
|
189
|
+
.list li:last-child {
|
|
190
|
+
margin-bottom: 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.prose li::marker,
|
|
194
|
+
.list li::marker {
|
|
195
|
+
color: var(--text-muted);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.list .list {
|
|
199
|
+
margin: 6px 0 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.list li > .prose {
|
|
203
|
+
margin: 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.list li > .prose + * {
|
|
207
|
+
margin-top: 8px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.prose code,
|
|
211
|
+
.callout code,
|
|
212
|
+
.quote code,
|
|
213
|
+
.table code {
|
|
214
|
+
font-family: var(--mono);
|
|
215
|
+
font-size: 0.9em;
|
|
216
|
+
background: var(--code-bg);
|
|
217
|
+
border-radius: 3px;
|
|
218
|
+
padding: 1px 5px;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.quote {
|
|
222
|
+
margin: 0 0 16px;
|
|
223
|
+
padding: 4px 0 4px 16px;
|
|
224
|
+
border-left: 3px solid var(--accent-soft);
|
|
225
|
+
color: var(--text-secondary);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.quote > .prose:last-child {
|
|
229
|
+
margin-bottom: 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.callout {
|
|
233
|
+
margin: 0 0 16px;
|
|
234
|
+
padding: 14px 16px;
|
|
235
|
+
background: var(--paper);
|
|
236
|
+
border-left: 4px solid var(--accent);
|
|
237
|
+
border-radius: 2px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.callout-warn {
|
|
241
|
+
border-left-color: var(--warn);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.callout .callout-label {
|
|
245
|
+
display: block;
|
|
246
|
+
font-size: 11px;
|
|
247
|
+
letter-spacing: 0.18em;
|
|
248
|
+
text-transform: uppercase;
|
|
249
|
+
color: var(--accent);
|
|
250
|
+
margin-bottom: 6px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.callout-warn .callout-label {
|
|
254
|
+
color: var(--warn);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.callout > .prose:last-child {
|
|
258
|
+
margin-bottom: 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.code {
|
|
262
|
+
margin: 0 0 16px;
|
|
263
|
+
padding: 14px 16px;
|
|
264
|
+
background: var(--code-bg);
|
|
265
|
+
border: 1px solid var(--rule);
|
|
266
|
+
border-radius: 4px;
|
|
267
|
+
overflow-x: auto;
|
|
268
|
+
font-family: var(--mono);
|
|
269
|
+
font-size: 13px;
|
|
270
|
+
line-height: 1.5;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.code code {
|
|
274
|
+
font-family: inherit;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.table-wrap {
|
|
278
|
+
margin: 0 0 16px;
|
|
279
|
+
overflow-x: auto;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.table {
|
|
283
|
+
width: 100%;
|
|
284
|
+
border-collapse: collapse;
|
|
285
|
+
font-size: 14px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.table th,
|
|
289
|
+
.table td {
|
|
290
|
+
border-bottom: 1px solid var(--rule);
|
|
291
|
+
padding: 8px 10px;
|
|
292
|
+
text-align: left;
|
|
293
|
+
vertical-align: top;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.table th {
|
|
297
|
+
color: var(--accent);
|
|
298
|
+
font-weight: 700;
|
|
299
|
+
border-bottom: 2px solid var(--accent-soft);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.island {
|
|
303
|
+
margin: 0 0 16px;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* diagram (CONTRACT D1) -- the geometry is computed by core/diagram.js and the
|
|
307
|
+
skin only colours it, with variables both Kami skins agree on, so one spec
|
|
308
|
+
draws one picture rather than two that look alike. */
|
|
309
|
+
.diagram {
|
|
310
|
+
margin: 0 0 16px;
|
|
311
|
+
overflow-x: auto;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.diagram-svg {
|
|
315
|
+
display: block;
|
|
316
|
+
max-width: 100%;
|
|
317
|
+
height: auto;
|
|
318
|
+
font-family: var(--serif);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.diagram-node-box {
|
|
322
|
+
fill: var(--accent-soft);
|
|
323
|
+
stroke: var(--accent);
|
|
324
|
+
stroke-width: 1.5;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.diagram-node-label {
|
|
328
|
+
fill: var(--text-primary);
|
|
329
|
+
font-size: 15px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.diagram-edge-line {
|
|
333
|
+
fill: none;
|
|
334
|
+
stroke: var(--accent);
|
|
335
|
+
stroke-width: 1.5;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.diagram-arrow {
|
|
339
|
+
fill: var(--accent);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.diagram-edge-label {
|
|
343
|
+
fill: var(--text-secondary);
|
|
344
|
+
font-size: 12px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.colophon {
|
|
348
|
+
margin-top: 56px;
|
|
349
|
+
padding-top: 16px;
|
|
350
|
+
border-top: 1px solid var(--rule);
|
|
351
|
+
font-size: 12px;
|
|
352
|
+
color: var(--text-muted);
|
|
353
|
+
letter-spacing: 0.06em;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Medium window: two across. The tracks stay 24 -- only the span changes, so
|
|
357
|
+
the cards keep landing on the same column vocabulary at every width. */
|
|
358
|
+
@media (max-width: 1024px) {
|
|
359
|
+
.card {
|
|
360
|
+
grid-column: span 12;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Narrow window: one card per row. A card squeezed to a third of a phone is a
|
|
365
|
+
card nobody can read, and the flowing genre stays flowing -- no fixed canvas,
|
|
366
|
+
no scaling, the measure intact. */
|
|
367
|
+
@media (max-width: 680px) {
|
|
368
|
+
.card {
|
|
369
|
+
grid-column: 1 / -1;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@media print {
|
|
374
|
+
body {
|
|
375
|
+
background: #fff;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* On paper the board goes back to one column: a printed card grid breaks
|
|
379
|
+
across pages in the middle of whichever card the page ended on. */
|
|
380
|
+
.card-grid {
|
|
381
|
+
display: block;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.card {
|
|
385
|
+
break-inside: avoid;
|
|
386
|
+
margin: 0 0 var(--card-gap);
|
|
387
|
+
border-color: #ccc;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { el } from '../../../src/blocks/element.js'
|
|
2
|
+
import { manifest } from './manifest.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Chrome of the Kami long-form template.
|
|
6
|
+
*
|
|
7
|
+
* The root form is no longer written here: the shape of a long-form page is the
|
|
8
|
+
* `article` 文體's, and every template on that layout gets the same frame and
|
|
9
|
+
* the same two slots (CONTRACT C1). What is left is exactly this template's own
|
|
10
|
+
* business — the masthead, the colophon, the table of contents in the rail, and
|
|
11
|
+
* every word of them, which is the half a layout is forbidden to have an
|
|
12
|
+
* opinion about.
|
|
13
|
+
*
|
|
14
|
+
* Nothing here is a component: each slot returns the same neutral element tree
|
|
15
|
+
* a block module returns, so this file is data with a little arithmetic in it
|
|
16
|
+
* and imports no rendering library at all (CONTRACT A1).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const TEMPLATE_LABEL = `${manifest.namespace}/${manifest.name}`
|
|
20
|
+
|
|
21
|
+
/** Verbatim — this template's word for the rail's contents. */
|
|
22
|
+
const TOC_LABEL = '目次'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The section tree, flattened into entries this template can draw.
|
|
26
|
+
*
|
|
27
|
+
* A section with no `id` cannot be linked to — the anchor would point at
|
|
28
|
+
* nothing — so it contributes its children in its own place rather than taking
|
|
29
|
+
* a whole subtree down with it. Ids come from the pipeline (`withIds`), so this
|
|
30
|
+
* is the defensive branch, not the normal one.
|
|
31
|
+
*/
|
|
32
|
+
const tocEntries = (blocks) =>
|
|
33
|
+
(blocks ?? []).flatMap((block) => {
|
|
34
|
+
if (block?.type !== 'section') return []
|
|
35
|
+
const children = tocEntries(block.children)
|
|
36
|
+
return typeof block.id === 'string' && block.id.length > 0
|
|
37
|
+
? [{ id: block.id, title: block.title, children }]
|
|
38
|
+
: children
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const tocList = (entries) =>
|
|
42
|
+
el(
|
|
43
|
+
'ol',
|
|
44
|
+
{ class: 'toc-list' },
|
|
45
|
+
entries.map((entry) =>
|
|
46
|
+
// Spread rather than `… : null`: a null child is a real empty comment
|
|
47
|
+
// node in the artifact, and a leaf-heavy table of contents would pay for
|
|
48
|
+
// one per entry, forever, to say nothing.
|
|
49
|
+
el('li', { class: 'toc-item' }, [
|
|
50
|
+
el('a', { class: 'toc-link', href: `#${entry.id}` }, [entry.title]),
|
|
51
|
+
...(entry.children.length > 0 ? [tocList(entry.children)] : []),
|
|
52
|
+
]),
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The table of contents — **chrome this template computes**, not content.
|
|
58
|
+
*
|
|
59
|
+
* The author never writes it: it is derived from the section tree the document
|
|
60
|
+
* already has, exactly the way the masthead is derived from `meta` (wayfinder
|
|
61
|
+
* 14 號票, 量測報告 §5.4). Two consequences follow, and both are the point:
|
|
62
|
+
* it never enters the IR, so `replay` under another template gets that
|
|
63
|
+
* template's chrome instead of this one's; and the anchors are the section ids
|
|
64
|
+
* that have been in the DOM since F1, so jumping works with no script at all.
|
|
65
|
+
*
|
|
66
|
+
* A document with no sections yields **nothing**, and the empty rail region it
|
|
67
|
+
* leaves behind is collapsed by `styles.css` (`.doc-rail:empty`). Answering it
|
|
68
|
+
* here, in markup, would have made the page's shape depend on the document
|
|
69
|
+
* rather than on the template's applied state.
|
|
70
|
+
*/
|
|
71
|
+
const toc = (doc) => {
|
|
72
|
+
const entries = tocEntries(doc?.children)
|
|
73
|
+
if (entries.length === 0) return []
|
|
74
|
+
return [
|
|
75
|
+
el('nav', { class: 'doc-toc', 'aria-label': TOC_LABEL }, [
|
|
76
|
+
el('p', { class: 'toc-heading' }, [TOC_LABEL]),
|
|
77
|
+
tocList(entries),
|
|
78
|
+
]),
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const masthead = (meta) =>
|
|
83
|
+
el('header', { class: 'masthead' }, [
|
|
84
|
+
meta.kicker ? el('p', { class: 'kicker' }, [meta.kicker]) : null,
|
|
85
|
+
el('h1', { class: 'doc-title' }, [meta.title]),
|
|
86
|
+
meta.author || meta.date
|
|
87
|
+
? el('p', { class: 'byline' }, [[meta.author, meta.date].filter(Boolean).join(' · ')])
|
|
88
|
+
: null,
|
|
89
|
+
])
|
|
90
|
+
|
|
91
|
+
export const chrome = Object.freeze({
|
|
92
|
+
/** Above the body: who wrote this and what it is called. */
|
|
93
|
+
'doc-lead': (meta) => [masthead(meta)],
|
|
94
|
+
|
|
95
|
+
/** Beside it: where the reader is, derived from the document's own headings. */
|
|
96
|
+
'doc-rail': (doc) => toc(doc),
|
|
97
|
+
|
|
98
|
+
/** Below it: the engine and skin that drew the page, in this template's words. */
|
|
99
|
+
'doc-foot': () => [
|
|
100
|
+
el('footer', { class: 'colophon' }, [`kamishibai · ${TEMPLATE_LABEL}@${manifest.version}`]),
|
|
101
|
+
],
|
|
102
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { manifest, templateKey } from './manifest.js'
|
|
4
|
+
import { chrome } from './components.js'
|
|
5
|
+
|
|
6
|
+
const STYLES_PATH = fileURLToPath(new URL('./styles.css', import.meta.url))
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Template package as consumed by the render layer.
|
|
10
|
+
*
|
|
11
|
+
* No `root`: the shape of the page belongs to the `article` layout this
|
|
12
|
+
* manifest names, and this package supplies only skin (`styles`), config
|
|
13
|
+
* (`manifest`) and chrome (CONTRACT C1).
|
|
14
|
+
*/
|
|
15
|
+
export default Object.freeze({
|
|
16
|
+
manifest,
|
|
17
|
+
key: templateKey,
|
|
18
|
+
chrome,
|
|
19
|
+
language: manifest.language,
|
|
20
|
+
/** Fonts subset into the artifact (SPEC §7.2 出廠字體堆疊). */
|
|
21
|
+
fonts: Object.freeze([
|
|
22
|
+
Object.freeze({ package: '@fontsource/noto-serif-tc', weights: Object.freeze(['400', '700']) }),
|
|
23
|
+
]),
|
|
24
|
+
styles: () => readFileSync(STYLES_PATH, 'utf8'),
|
|
25
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template package descriptor (SPEC §5.2 `[template]` section).
|
|
3
|
+
* S1 ships it as a module; the TOML manifest lands with the template
|
|
4
|
+
* authoring slice (S7).
|
|
5
|
+
*/
|
|
6
|
+
export const manifest = Object.freeze({
|
|
7
|
+
namespace: 'kami',
|
|
8
|
+
name: 'long-form',
|
|
9
|
+
version: '0.1.0',
|
|
10
|
+
description: 'Kami 長文模板 — 紙感單欄閱讀版面',
|
|
11
|
+
language: 'zh-TW',
|
|
12
|
+
/**
|
|
13
|
+
* The 文體 this skin is a skin *of* (CONTRACT C1). Everything about the shape
|
|
14
|
+
* of the page — the frame, the body region, the two chrome slots, the flowing
|
|
15
|
+
* canvas — comes from there; this package only decides what it looks like.
|
|
16
|
+
*/
|
|
17
|
+
layout: 'article',
|
|
18
|
+
/** No required root form: this template renders whatever sits under `doc`. */
|
|
19
|
+
root: null,
|
|
20
|
+
blocks: Object.freeze([
|
|
21
|
+
'doc',
|
|
22
|
+
'section',
|
|
23
|
+
'prose',
|
|
24
|
+
'list',
|
|
25
|
+
'quote',
|
|
26
|
+
'callout',
|
|
27
|
+
'code',
|
|
28
|
+
'table',
|
|
29
|
+
'raw',
|
|
30
|
+
'diagram',
|
|
31
|
+
// F6a 四型資料 block(11 號票 P1「資料」)。五張 Kami 皮一起宣告:
|
|
32
|
+
// 詞彙表是准入規則,一張皮少宣告一種,同一份文件在它底下就會被
|
|
33
|
+
// KSB_TEMPLATE_BLOCK_UNSUPPORTED 擋掉——而那不是守門,是缺角。
|
|
34
|
+
'stat',
|
|
35
|
+
'timeline',
|
|
36
|
+
'board',
|
|
37
|
+
'graph',
|
|
38
|
+
]),
|
|
39
|
+
/**
|
|
40
|
+
* 轉子/<型別>/config — the template's settings for a block type.
|
|
41
|
+
*
|
|
42
|
+
* The callout labels live here because they are *wording*, and wording is
|
|
43
|
+
* decoration: freezing `NOTE`/`WARNING` inside the shared conversion path
|
|
44
|
+
* meant a template that wanted 「注意」 or an icon had to fork the renderer
|
|
45
|
+
* (CONTRACT A7). These two strings are this template's answer, not the SDK's.
|
|
46
|
+
*/
|
|
47
|
+
blockConfig: Object.freeze({
|
|
48
|
+
callout: Object.freeze({ labels: Object.freeze({ note: 'NOTE', warn: 'WARNING' }) }),
|
|
49
|
+
}),
|
|
50
|
+
/**
|
|
51
|
+
* The applied state of the `article` layout's geometry knobs, in CSS pixels.
|
|
52
|
+
*
|
|
53
|
+
* `maxWidth` is how wide the page may get; `measure` is how wide a line of
|
|
54
|
+
* text may get. They are two numbers on purpose (CONTRACT C3): collapsing
|
|
55
|
+
* them is what made `--measure: 740px` simultaneously a page width and a type
|
|
56
|
+
* ruler, so a diagram in a narrow column had nothing to be measured against
|
|
57
|
+
* (量測報告 §4.2). They coincided here until F2b, because the page *was* one
|
|
58
|
+
* column; a page with a rail is precisely the case that pulls them apart —
|
|
59
|
+
* 1080px of paper carrying a 740px column of text, 「一般網站的玩法」
|
|
60
|
+
* (issue 14 裁決 3).
|
|
61
|
+
*
|
|
62
|
+
* These values are the *template's*, not the layout's — the layout only says
|
|
63
|
+
* which knobs exist and what they fall back to. `styles.css` no longer holds
|
|
64
|
+
* either number: it declares where they go, and the render layer writes them
|
|
65
|
+
* in (`src/render/styles.js`).
|
|
66
|
+
*/
|
|
67
|
+
maxWidth: 1080,
|
|
68
|
+
measure: 740,
|
|
69
|
+
/**
|
|
70
|
+
* The rail knobs of the `article` 文體, turned on — this *is* what a Kami
|
|
71
|
+
* long-form page looks like (CONTRACT F2b P3): 左目錄、右內文, the layout
|
|
72
|
+
* every reader already knows from a documentation site.
|
|
73
|
+
*
|
|
74
|
+
* The three numbers are one sentence of arithmetic, not three tastes:
|
|
75
|
+
* `maxWidth` 1080 = `railWidth` 240 + the 44px column gap + `measure` 740 +
|
|
76
|
+
* the 56px the paper already reserves as left/right padding. Pick any other
|
|
77
|
+
* page width and the column of text stops being 740px wide, which would
|
|
78
|
+
* silently detune the type scale against a measure it never actually gets
|
|
79
|
+
* (量測報告 §4.2 の同じ罠). The gap lives in `styles.css` because it is the
|
|
80
|
+
* one part of the sum that is pure decoration.
|
|
81
|
+
*/
|
|
82
|
+
rail: true,
|
|
83
|
+
railWidth: 240,
|
|
84
|
+
railSide: 'start',
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export const templateKey = `${manifest.namespace}/${manifest.name}`
|