@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.
Files changed (136) hide show
  1. package/README.md +192 -0
  2. package/package.json +54 -0
  3. package/src/blocks/board.js +210 -0
  4. package/src/blocks/callout.js +63 -0
  5. package/src/blocks/code.js +28 -0
  6. package/src/blocks/deck.js +76 -0
  7. package/src/blocks/diagram.js +265 -0
  8. package/src/blocks/element.js +51 -0
  9. package/src/blocks/graph.js +264 -0
  10. package/src/blocks/grid.js +156 -0
  11. package/src/blocks/index.js +106 -0
  12. package/src/blocks/list.js +50 -0
  13. package/src/blocks/placement.js +119 -0
  14. package/src/blocks/prose.js +28 -0
  15. package/src/blocks/quote.js +25 -0
  16. package/src/blocks/raw.js +47 -0
  17. package/src/blocks/registry.js +158 -0
  18. package/src/blocks/schema-parts.js +19 -0
  19. package/src/blocks/section.js +53 -0
  20. package/src/blocks/slide.js +104 -0
  21. package/src/blocks/stat.js +83 -0
  22. package/src/blocks/table.js +50 -0
  23. package/src/blocks/timeline.js +80 -0
  24. package/src/cli/commands/close.js +51 -0
  25. package/src/cli/commands/comments.js +73 -0
  26. package/src/cli/commands/debug.js +34 -0
  27. package/src/cli/commands/example.js +22 -0
  28. package/src/cli/commands/export.js +10 -0
  29. package/src/cli/commands/init.js +53 -0
  30. package/src/cli/commands/lint.js +93 -0
  31. package/src/cli/commands/list.js +54 -0
  32. package/src/cli/commands/open.js +31 -0
  33. package/src/cli/commands/promote.js +38 -0
  34. package/src/cli/commands/render.js +31 -0
  35. package/src/cli/commands/replay.js +49 -0
  36. package/src/cli/commands/schema.js +10 -0
  37. package/src/cli/commands/serve.js +178 -0
  38. package/src/cli/commands/setup.js +64 -0
  39. package/src/cli/commands/snapshot.js +29 -0
  40. package/src/cli/commands/templates.js +75 -0
  41. package/src/cli/deliver.js +54 -0
  42. package/src/cli/emit.js +29 -0
  43. package/src/cli/format.js +153 -0
  44. package/src/cli/index.js +365 -0
  45. package/src/cli/registry.js +18 -0
  46. package/src/core/blocks.js +147 -0
  47. package/src/core/diagram.js +282 -0
  48. package/src/core/errors.js +156 -0
  49. package/src/core/example.js +109 -0
  50. package/src/core/ir.js +62 -0
  51. package/src/core/lint-gates.js +427 -0
  52. package/src/core/lint.js +281 -0
  53. package/src/core/scan.js +84 -0
  54. package/src/core/schema.js +88 -0
  55. package/src/core/spec-check.js +33 -0
  56. package/src/core/validate.js +44 -0
  57. package/src/core/version.js +18 -0
  58. package/src/core/vocabulary.js +140 -0
  59. package/src/delivery/atomic.js +71 -0
  60. package/src/delivery/comments.js +180 -0
  61. package/src/delivery/home.js +70 -0
  62. package/src/delivery/open.js +31 -0
  63. package/src/delivery/project.js +141 -0
  64. package/src/delivery/read.js +109 -0
  65. package/src/delivery/run.js +95 -0
  66. package/src/delivery/scaffold-blueprints.js +728 -0
  67. package/src/delivery/store.js +219 -0
  68. package/src/delivery/template-extensions.js +183 -0
  69. package/src/delivery/template-format.js +112 -0
  70. package/src/delivery/template-package.js +376 -0
  71. package/src/delivery/template-promote.js +240 -0
  72. package/src/delivery/template-scaffold.js +181 -0
  73. package/src/delivery/templates.js +192 -0
  74. package/src/delivery/toml.js +195 -0
  75. package/src/delivery/write.js +35 -0
  76. package/src/export/browser.js +130 -0
  77. package/src/export/index.js +96 -0
  78. package/src/export/pdf.js +25 -0
  79. package/src/export/png.js +40 -0
  80. package/src/export/pptx.js +48 -0
  81. package/src/export/slides.js +33 -0
  82. package/src/export/snapshot.js +33 -0
  83. package/src/layouts/article.js +103 -0
  84. package/src/layouts/canvas.js +144 -0
  85. package/src/layouts/card.js +128 -0
  86. package/src/layouts/deck.js +88 -0
  87. package/src/layouts/index.js +90 -0
  88. package/src/layouts/one-page.js +161 -0
  89. package/src/layouts/registry.js +251 -0
  90. package/src/layouts/resume.js +172 -0
  91. package/src/layouts/template-index.js +78 -0
  92. package/src/parser/artifact.js +38 -0
  93. package/src/parser/container.js +103 -0
  94. package/src/parser/index.js +223 -0
  95. package/src/parser/tokens.js +265 -0
  96. package/src/render/board-filter.client.js +80 -0
  97. package/src/render/compile.js +29 -0
  98. package/src/render/context.js +98 -0
  99. package/src/render/element.js +32 -0
  100. package/src/render/fonts.js +129 -0
  101. package/src/render/graph-hover.client.js +148 -0
  102. package/src/render/html.js +52 -0
  103. package/src/render/index.js +241 -0
  104. package/src/render/measure.js +60 -0
  105. package/src/render/placement.js +136 -0
  106. package/src/render/playback.client.js +74 -0
  107. package/src/render/scale-to-fit.client.js +136 -0
  108. package/src/render/scale.js +41 -0
  109. package/src/render/skeleton.js +131 -0
  110. package/src/render/ssr.js +24 -0
  111. package/src/render/styles.js +56 -0
  112. package/src/render/templates.js +191 -0
  113. package/src/serve/daemon.js +117 -0
  114. package/src/serve/overlay.js +213 -0
  115. package/src/serve/protocol.js +36 -0
  116. package/src/serve/server.js +264 -0
  117. package/templates/kami/cards/components.js +40 -0
  118. package/templates/kami/cards/index.js +25 -0
  119. package/templates/kami/cards/manifest.js +67 -0
  120. package/templates/kami/cards/styles.css +389 -0
  121. package/templates/kami/long-form/components.js +102 -0
  122. package/templates/kami/long-form/index.js +25 -0
  123. package/templates/kami/long-form/manifest.js +87 -0
  124. package/templates/kami/long-form/styles.css +481 -0
  125. package/templates/kami/one-page/components.js +48 -0
  126. package/templates/kami/one-page/index.js +27 -0
  127. package/templates/kami/one-page/manifest.js +65 -0
  128. package/templates/kami/one-page/styles.css +375 -0
  129. package/templates/kami/resume/components.js +51 -0
  130. package/templates/kami/resume/index.js +27 -0
  131. package/templates/kami/resume/manifest.js +65 -0
  132. package/templates/kami/resume/styles.css +424 -0
  133. package/templates/kami/slides/components.js +41 -0
  134. package/templates/kami/slides/index.js +26 -0
  135. package/templates/kami/slides/manifest.js +64 -0
  136. 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}`