kitfly 0.1.2 → 0.2.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 (194) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +63 -16
  3. package/VERSION +1 -1
  4. package/dist/_raw/content/deployment/preflight.md +134 -0
  5. package/dist/_raw/content/deployment/recipes/aws-s3.md +128 -0
  6. package/dist/_raw/content/deployment/recipes/cloudflare-pages.md +73 -0
  7. package/dist/_raw/content/deployment/recipes/cloudflare-r2.md +156 -0
  8. package/dist/_raw/content/deployment/recipes/fly-io.md +57 -0
  9. package/dist/_raw/content/deployment/recipes/github-pages.md +112 -0
  10. package/dist/_raw/content/deployment/recipes/netlify.md +99 -0
  11. package/dist/_raw/content/deployment/recipes/vercel.md +88 -0
  12. package/dist/_raw/content/deployment/secrets-and-env-vars.md +75 -0
  13. package/dist/_raw/content/deployment.md +128 -0
  14. package/dist/_raw/content/guide/approaches.md +182 -0
  15. package/dist/_raw/content/guide/features.md +121 -0
  16. package/dist/_raw/content/guide/getting-started.md +112 -0
  17. package/dist/_raw/content/guide/kitfly-overview.md +209 -0
  18. package/dist/_raw/content/reference/configuration.md +259 -0
  19. package/dist/_raw/content/reference/design-catalog.md +167 -0
  20. package/dist/_raw/content/reference/environment-variables.md +66 -0
  21. package/dist/_raw/content/reference/glossary.md +92 -0
  22. package/dist/_raw/content/reference/key-concepts.md +118 -0
  23. package/dist/_raw/content/reference/plugins.md +220 -0
  24. package/dist/_raw/content/reference/structure.md +166 -0
  25. package/dist/_raw/content/reference.md +19 -0
  26. package/dist/_raw/content/templates/crucible.md +192 -0
  27. package/dist/_raw/content/templates/handbook.md +83 -0
  28. package/dist/_raw/content/templates/minimal.md +138 -0
  29. package/dist/_raw/content/templates/overview.md +187 -0
  30. package/dist/_raw/content/templates/pipeline.md +151 -0
  31. package/dist/_raw/content/templates/productbook.md +187 -0
  32. package/dist/_raw/content/templates/runbook.md +193 -0
  33. package/dist/_raw/content/templates/servicebook.md +163 -0
  34. package/dist/_raw/docs/decisions/ADR-0001-minimalist-site-code.md +118 -0
  35. package/dist/_raw/docs/decisions/ADR-0002-ai-accessibility.md +153 -0
  36. package/dist/_raw/docs/decisions/ADR-0003-single-file-bundle.md +93 -0
  37. package/dist/_raw/docs/decisions/ADR-0004-bun-runtime.md +98 -0
  38. package/dist/_raw/docs/decisions/ADR-0005-plugin-contract-and-distribution.md +110 -0
  39. package/dist/_raw/docs/decisions/DDR-0001-viewport-locked-layout.md +111 -0
  40. package/dist/_raw/docs/decisions/DDR-0002-theme-system.md +131 -0
  41. package/dist/_raw/docs/decisions/DDR-0003-bounded-logo-slot.md +106 -0
  42. package/dist/_raw/docs/decisions/DDR-0004-slides-rendering-model.md +113 -0
  43. package/dist/_raw/docs/decisions/DDR-0005-deterministic-layout-boundary.md +107 -0
  44. package/dist/_raw/docs/userguide/cli/build.md +85 -0
  45. package/dist/_raw/docs/userguide/cli/bundle.md +81 -0
  46. package/dist/_raw/docs/userguide/cli/dev.md +92 -0
  47. package/dist/_raw/docs/userguide/cli/init.md +116 -0
  48. package/dist/_raw/docs/userguide/cli/servers.md +69 -0
  49. package/dist/_raw/docs/userguide/cli/stop.md +76 -0
  50. package/dist/_raw/docs/userguide/cli/update.md +78 -0
  51. package/dist/_raw/docs/userguide/cli/version.md +65 -0
  52. package/dist/_raw/docs/userguide/cli.md +34 -0
  53. package/dist/_raw/docs/userguide/sharing.md +94 -0
  54. package/dist/_raw/schemas/plugin-schemas-notes.md +71 -0
  55. package/dist/_raw/schemas.md +42 -0
  56. package/dist/assets/brand/kitfly-favicon-32.png +0 -0
  57. package/dist/assets/brand/kitfly-icon-64.png +0 -0
  58. package/dist/assets/brand/kitfly-logo-128.png +0 -0
  59. package/dist/assets/brand/kitfly-logo-512.png +0 -0
  60. package/dist/assets/brand/kitfly-logo.svg +12132 -0
  61. package/dist/assets/brand/kitfly-neon-128.png +0 -0
  62. package/dist/assets/brand/kitfly-neon-192.png +0 -0
  63. package/dist/assets/brand/kitfly-neon-256.png +0 -0
  64. package/dist/assets/brand/kitfly-neon.png +0 -0
  65. package/dist/assets/brand/palette.md +75 -0
  66. package/dist/content/deployment/index.html +11 -0
  67. package/dist/content/deployment/preflight.html +418 -0
  68. package/dist/content/deployment/recipes/aws-s3.html +421 -0
  69. package/dist/content/deployment/recipes/cloudflare-pages.html +372 -0
  70. package/dist/content/deployment/recipes/cloudflare-r2.html +443 -0
  71. package/dist/content/deployment/recipes/fly-io.html +356 -0
  72. package/dist/content/deployment/recipes/github-pages.html +414 -0
  73. package/dist/content/deployment/recipes/index.html +11 -0
  74. package/dist/content/deployment/recipes/netlify.html +394 -0
  75. package/dist/content/deployment/recipes/vercel.html +382 -0
  76. package/dist/content/deployment/secrets-and-env-vars.html +380 -0
  77. package/dist/content/deployment.html +426 -0
  78. package/dist/content/guide/approaches.html +501 -0
  79. package/dist/content/guide/features.html +436 -0
  80. package/dist/content/guide/getting-started.html +403 -0
  81. package/dist/content/guide/index.html +11 -0
  82. package/dist/content/guide/kitfly-overview.html +544 -0
  83. package/dist/content/index.html +11 -0
  84. package/dist/content/reference/configuration.html +580 -0
  85. package/dist/content/reference/design-catalog.html +449 -0
  86. package/dist/content/reference/environment-variables.html +367 -0
  87. package/dist/content/reference/glossary.html +368 -0
  88. package/dist/content/reference/index.html +11 -0
  89. package/dist/content/reference/key-concepts.html +399 -0
  90. package/dist/content/reference/plugins.html +491 -0
  91. package/dist/content/reference/structure.html +463 -0
  92. package/dist/content/reference.html +334 -0
  93. package/dist/content/templates/crucible.html +546 -0
  94. package/dist/content/templates/handbook.html +405 -0
  95. package/dist/content/templates/index.html +11 -0
  96. package/dist/content/templates/minimal.html +447 -0
  97. package/dist/content/templates/overview.html +558 -0
  98. package/dist/content/templates/pipeline.html +494 -0
  99. package/dist/content/templates/productbook.html +540 -0
  100. package/dist/content/templates/runbook.html +543 -0
  101. package/dist/content/templates/servicebook.html +523 -0
  102. package/dist/content-index.json +540 -0
  103. package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +491 -0
  104. package/dist/docs/decisions/ADR-0002-ai-accessibility.html +434 -0
  105. package/dist/docs/decisions/ADR-0003-single-file-bundle.html +412 -0
  106. package/dist/docs/decisions/ADR-0004-bun-runtime.html +409 -0
  107. package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +402 -0
  108. package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +459 -0
  109. package/dist/docs/decisions/DDR-0002-theme-system.html +452 -0
  110. package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +423 -0
  111. package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +399 -0
  112. package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +422 -0
  113. package/dist/docs/decisions/index.html +11 -0
  114. package/dist/docs/userguide/cli/build.html +408 -0
  115. package/dist/docs/userguide/cli/bundle.html +419 -0
  116. package/dist/docs/userguide/cli/dev.html +428 -0
  117. package/dist/docs/userguide/cli/index.html +11 -0
  118. package/dist/docs/userguide/cli/init.html +436 -0
  119. package/dist/docs/userguide/cli/servers.html +393 -0
  120. package/dist/docs/userguide/cli/stop.html +408 -0
  121. package/dist/docs/userguide/cli/update.html +406 -0
  122. package/dist/docs/userguide/cli/version.html +406 -0
  123. package/dist/docs/userguide/cli.html +386 -0
  124. package/dist/docs/userguide/index.html +11 -0
  125. package/dist/docs/userguide/sharing.html +465 -0
  126. package/dist/index.html +387 -0
  127. package/dist/llms.txt +18 -0
  128. package/dist/provenance.json +7 -0
  129. package/dist/schemas/index.html +11 -0
  130. package/dist/schemas/plugin-registry.schema.html +327 -0
  131. package/dist/schemas/plugin-schemas-notes.html +364 -0
  132. package/dist/schemas/plugin.schema.html +327 -0
  133. package/dist/schemas/plugins.schema.html +327 -0
  134. package/dist/schemas/v0/common.schema.html +386 -0
  135. package/dist/schemas/v0/index.html +11 -0
  136. package/dist/schemas/v0/plugin-registry.schema.html +547 -0
  137. package/dist/schemas/v0/plugin.schema.html +497 -0
  138. package/dist/schemas/v0/plugins.schema.html +406 -0
  139. package/dist/schemas/v0/site.schema.html +541 -0
  140. package/dist/schemas/v0/theme.schema.html +615 -0
  141. package/dist/schemas.html +351 -0
  142. package/dist/styles.css +1262 -0
  143. package/package.json +4 -2
  144. package/plugins-dist/callouts.css +32 -0
  145. package/plugins-dist/callouts.js +46 -0
  146. package/plugins-dist/slides-visuals.css +224 -0
  147. package/plugins-dist/slides-visuals.js +598 -0
  148. package/registry/plugins.yaml +35 -0
  149. package/schemas/README.md +10 -0
  150. package/schemas/plugin-registry.schema.json +5 -0
  151. package/schemas/plugin-schemas-notes.md +71 -0
  152. package/schemas/plugin.schema.json +5 -0
  153. package/schemas/plugins.schema.json +5 -0
  154. package/schemas/v0/common.schema.json +64 -0
  155. package/schemas/v0/plugin-registry.schema.json +225 -0
  156. package/schemas/v0/plugin.schema.json +175 -0
  157. package/schemas/v0/plugins.schema.json +84 -0
  158. package/schemas/v0/site.schema.json +56 -9
  159. package/schemas/v0/theme.schema.json +105 -22
  160. package/scripts/build.ts +155 -3
  161. package/scripts/bundle.ts +258 -95
  162. package/scripts/dev.ts +203 -1
  163. package/src/__tests__/build.test.ts +158 -1
  164. package/src/__tests__/bundle.test.ts +31 -0
  165. package/src/__tests__/cli.test.ts +14 -3
  166. package/src/__tests__/fixtures/fences/slides-visuals/invalid/bad-list-indent.md +5 -0
  167. package/src/__tests__/fixtures/fences/slides-visuals/invalid/blank-line.md +5 -0
  168. package/src/__tests__/fixtures/fences/slides-visuals/invalid/compare-object-items.md +9 -0
  169. package/src/__tests__/fixtures/fences/slides-visuals/invalid/indented-fence.md +4 -0
  170. package/src/__tests__/fixtures/fences/slides-visuals/invalid/stat-grid-missing-fields.md +5 -0
  171. package/src/__tests__/fixtures/fences/slides-visuals/invalid/unknown-type.md +3 -0
  172. package/src/__tests__/fixtures/fences/slides-visuals/valid/compare.md +10 -0
  173. package/src/__tests__/fixtures/fences/slides-visuals/valid/comparison-table.md +14 -0
  174. package/src/__tests__/fixtures/fences/slides-visuals/valid/funnel.md +7 -0
  175. package/src/__tests__/fixtures/fences/slides-visuals/valid/kpi.md +5 -0
  176. package/src/__tests__/fixtures/fences/slides-visuals/valid/layer-cake.md +6 -0
  177. package/src/__tests__/fixtures/fences/slides-visuals/valid/pyramid.md +6 -0
  178. package/src/__tests__/fixtures/fences/slides-visuals/valid/quadrant-grid.md +8 -0
  179. package/src/__tests__/fixtures/fences/slides-visuals/valid/scorecard.md +13 -0
  180. package/src/__tests__/fixtures/fences/slides-visuals/valid/stat-grid.md +8 -0
  181. package/src/__tests__/init.test.ts +35 -0
  182. package/src/__tests__/plugin-loader.test.ts +221 -0
  183. package/src/__tests__/shared.test.ts +428 -0
  184. package/src/__tests__/slides-visuals-fence-contract.test.ts +28 -0
  185. package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +114 -0
  186. package/src/__tests__/styles.test.ts +35 -0
  187. package/src/cli.ts +9 -4
  188. package/src/plugin-loader.ts +245 -0
  189. package/src/shared.ts +614 -7
  190. package/src/site/styles.css +331 -0
  191. package/src/site/template.html +66 -5
  192. package/src/templates/deck.ts +186 -0
  193. package/src/templates/driver.ts +11 -1
  194. package/src/templates/minimal.ts +1 -0
@@ -5,7 +5,11 @@
5
5
  "title": "Site Configuration",
6
6
  "description": "Configuration for Kitfly static site generator",
7
7
  "type": "object",
8
- "required": ["title", "brand", "sections"],
8
+ "required": [
9
+ "title",
10
+ "brand",
11
+ "sections"
12
+ ],
9
13
  "properties": {
10
14
  "schemaVersion": {
11
15
  "type": "string",
@@ -25,8 +29,35 @@
25
29
  },
26
30
  "version": {
27
31
  "type": "string",
28
- "description": "Site version displayed in footer provenance",
29
- "examples": ["1.0.0", "2024.03", "draft"]
32
+ "description": "Site version shown in footer provenance. Supports literal values, auto (read VERSION in site root), or file:<path> (read from relative file).",
33
+ "examples": [
34
+ "1.0.0",
35
+ "2024.03",
36
+ "draft",
37
+ "auto",
38
+ "file:VERSION",
39
+ "file:./meta/site version.txt"
40
+ ]
41
+ },
42
+ "mode": {
43
+ "type": "string",
44
+ "description": "Site layout mode",
45
+ "enum": [
46
+ "docs",
47
+ "slides"
48
+ ],
49
+ "default": "docs"
50
+ },
51
+ "aspect": {
52
+ "type": "string",
53
+ "description": "Slide aspect ratio (only applies when mode is slides)",
54
+ "enum": [
55
+ "16/9",
56
+ "4/3",
57
+ "3/2",
58
+ "16/10"
59
+ ],
60
+ "default": "16/9"
30
61
  },
31
62
  "home": {
32
63
  "type": "string",
@@ -35,7 +66,10 @@
35
66
  "brand": {
36
67
  "type": "object",
37
68
  "description": "Brand configuration for logo and links",
38
- "required": ["name", "url"],
69
+ "required": [
70
+ "name",
71
+ "url"
72
+ ],
39
73
  "properties": {
40
74
  "name": {
41
75
  "type": "string",
@@ -64,7 +98,10 @@
64
98
  "logoType": {
65
99
  "type": "string",
66
100
  "description": "Logo type: icon (square) or wordmark (wide)",
67
- "enum": ["icon", "wordmark"],
101
+ "enum": [
102
+ "icon",
103
+ "wordmark"
104
+ ],
68
105
  "default": "icon"
69
106
  }
70
107
  },
@@ -76,7 +113,10 @@
76
113
  "minItems": 1,
77
114
  "items": {
78
115
  "type": "object",
79
- "required": ["name", "path"],
116
+ "required": [
117
+ "name",
118
+ "path"
119
+ ],
80
120
  "properties": {
81
121
  "name": {
82
122
  "type": "string",
@@ -90,7 +130,9 @@
90
130
  "files": {
91
131
  "type": "array",
92
132
  "description": "Specific files to include (for root-level sections)",
93
- "items": { "type": "string" }
133
+ "items": {
134
+ "type": "string"
135
+ }
94
136
  },
95
137
  "maxDepth": {
96
138
  "type": "integer",
@@ -102,7 +144,9 @@
102
144
  "exclude": {
103
145
  "type": "array",
104
146
  "description": "Glob patterns to exclude from auto-discovery",
105
- "items": { "type": "string" }
147
+ "items": {
148
+ "type": "string"
149
+ }
106
150
  }
107
151
  },
108
152
  "additionalProperties": false
@@ -126,7 +170,10 @@
126
170
  "maxItems": 10,
127
171
  "items": {
128
172
  "type": "object",
129
- "required": ["text", "url"],
173
+ "required": [
174
+ "text",
175
+ "url"
176
+ ],
130
177
  "properties": {
131
178
  "text": {
132
179
  "type": "string",
@@ -14,7 +14,11 @@
14
14
  "type": "string",
15
15
  "description": "Theme name for identification",
16
16
  "maxLength": 50,
17
- "examples": ["Kitfly Default", "GitHub", "Paper"]
17
+ "examples": [
18
+ "Kitfly Default",
19
+ "GitHub",
20
+ "Paper"
21
+ ]
18
22
  },
19
23
  "layout": {
20
24
  "type": "object",
@@ -25,7 +29,11 @@
25
29
  "description": "Sidebar width (CSS length value)",
26
30
  "pattern": "^[0-9]+(px|rem|em|%)$",
27
31
  "default": "280px",
28
- "examples": ["280px", "320px", "18rem"]
32
+ "examples": [
33
+ "280px",
34
+ "320px",
35
+ "18rem"
36
+ ]
29
37
  }
30
38
  },
31
39
  "additionalProperties": false
@@ -33,10 +41,17 @@
33
41
  "colors": {
34
42
  "type": "object",
35
43
  "description": "Color palettes for light and dark modes",
36
- "required": ["light", "dark"],
44
+ "required": [
45
+ "light",
46
+ "dark"
47
+ ],
37
48
  "properties": {
38
- "light": { "$ref": "#/$defs/colorPalette" },
39
- "dark": { "$ref": "#/$defs/colorPalette" }
49
+ "light": {
50
+ "$ref": "#/$defs/colorPalette"
51
+ },
52
+ "dark": {
53
+ "$ref": "#/$defs/colorPalette"
54
+ }
40
55
  },
41
56
  "additionalProperties": false
42
57
  },
@@ -47,13 +62,25 @@
47
62
  "light": {
48
63
  "type": "string",
49
64
  "description": "Prism theme for light mode",
50
- "enum": ["default", "coy", "solarized-light", "tomorrow"],
65
+ "enum": [
66
+ "default",
67
+ "coy",
68
+ "solarized-light",
69
+ "tomorrow"
70
+ ],
51
71
  "default": "default"
52
72
  },
53
73
  "dark": {
54
74
  "type": "string",
55
75
  "description": "Prism theme for dark mode",
56
- "enum": ["okaidia", "tomorrow-night", "nord", "dracula", "one-dark", "synthwave84"],
76
+ "enum": [
77
+ "okaidia",
78
+ "tomorrow-night",
79
+ "nord",
80
+ "dracula",
81
+ "one-dark",
82
+ "synthwave84"
83
+ ],
57
84
  "default": "okaidia"
58
85
  }
59
86
  },
@@ -66,19 +93,31 @@
66
93
  "body": {
67
94
  "type": "string",
68
95
  "description": "Font preset for body text",
69
- "enum": ["system", "mono", "serif", "readable"],
96
+ "enum": [
97
+ "system",
98
+ "mono",
99
+ "serif",
100
+ "readable"
101
+ ],
70
102
  "default": "system"
71
103
  },
72
104
  "headings": {
73
105
  "type": "string",
74
106
  "description": "Font preset for headings",
75
- "enum": ["system", "mono", "serif", "readable"],
107
+ "enum": [
108
+ "system",
109
+ "mono",
110
+ "serif",
111
+ "readable"
112
+ ],
76
113
  "default": "system"
77
114
  },
78
115
  "code": {
79
116
  "type": "string",
80
117
  "description": "Font preset for code blocks",
81
- "enum": ["mono"],
118
+ "enum": [
119
+ "mono"
120
+ ],
82
121
  "default": "mono"
83
122
  },
84
123
  "baseSize": {
@@ -86,12 +125,22 @@
86
125
  "description": "Base font size",
87
126
  "pattern": "^[0-9]+(px|rem|em)$",
88
127
  "default": "16px",
89
- "examples": ["16px", "17px", "1rem"]
128
+ "examples": [
129
+ "16px",
130
+ "17px",
131
+ "1rem"
132
+ ]
90
133
  },
91
134
  "scale": {
92
135
  "type": "string",
93
136
  "description": "Type scale ratio for heading sizes",
94
- "enum": ["1.125", "1.2", "1.25", "1.333", "1.5"],
137
+ "enum": [
138
+ "1.125",
139
+ "1.2",
140
+ "1.25",
141
+ "1.333",
142
+ "1.5"
143
+ ],
95
144
  "default": "1.25"
96
145
  }
97
146
  },
@@ -103,61 +152,95 @@
103
152
  "colorPalette": {
104
153
  "type": "object",
105
154
  "description": "Color palette for a single mode (light or dark)",
106
- "required": ["background", "surface", "text", "heading", "primary", "border"],
155
+ "required": [
156
+ "background",
157
+ "surface",
158
+ "text",
159
+ "heading",
160
+ "primary",
161
+ "border"
162
+ ],
107
163
  "properties": {
108
164
  "background": {
109
165
  "type": "string",
110
166
  "description": "Page background color",
111
167
  "pattern": "^#[0-9a-fA-F]{6}$",
112
- "examples": ["#ffffff", "#0f1e2b"]
168
+ "examples": [
169
+ "#ffffff",
170
+ "#0f1e2b"
171
+ ]
113
172
  },
114
173
  "surface": {
115
174
  "type": "string",
116
175
  "description": "Elevated surface color (cards, code blocks)",
117
176
  "pattern": "^#[0-9a-fA-F]{6}$",
118
- "examples": ["#f5f7f8", "#152F46"]
177
+ "examples": [
178
+ "#f5f7f8",
179
+ "#152F46"
180
+ ]
119
181
  },
120
182
  "text": {
121
183
  "type": "string",
122
184
  "description": "Primary body text color",
123
185
  "pattern": "^#[0-9a-fA-F]{6}$",
124
- "examples": ["#374151", "#e5e7eb"]
186
+ "examples": [
187
+ "#374151",
188
+ "#e5e7eb"
189
+ ]
125
190
  },
126
191
  "textMuted": {
127
192
  "type": "string",
128
193
  "description": "Secondary/muted text color",
129
194
  "pattern": "^#[0-9a-fA-F]{6}$",
130
- "examples": ["#6b7280", "#9ca3af"]
195
+ "examples": [
196
+ "#6b7280",
197
+ "#9ca3af"
198
+ ]
131
199
  },
132
200
  "heading": {
133
201
  "type": "string",
134
202
  "description": "Heading text color",
135
203
  "pattern": "^#[0-9a-fA-F]{6}$",
136
- "examples": ["#152F46", "#f9fafb"]
204
+ "examples": [
205
+ "#152F46",
206
+ "#f9fafb"
207
+ ]
137
208
  },
138
209
  "primary": {
139
210
  "type": "string",
140
211
  "description": "Primary accent color (links, buttons)",
141
212
  "pattern": "^#[0-9a-fA-F]{6}$",
142
- "examples": ["#007182", "#4db8c7"]
213
+ "examples": [
214
+ "#007182",
215
+ "#4db8c7"
216
+ ]
143
217
  },
144
218
  "primaryHover": {
145
219
  "type": "string",
146
220
  "description": "Primary color hover state",
147
221
  "pattern": "^#[0-9a-fA-F]{6}$",
148
- "examples": ["#005c6a", "#6cc9d6"]
222
+ "examples": [
223
+ "#005c6a",
224
+ "#6cc9d6"
225
+ ]
149
226
  },
150
227
  "accent": {
151
228
  "type": "string",
152
229
  "description": "Secondary accent color (highlights, CTAs)",
153
230
  "pattern": "^#[0-9a-fA-F]{6}$",
154
- "examples": ["#D17059", "#e8947f"]
231
+ "examples": [
232
+ "#D17059",
233
+ "#e8947f"
234
+ ]
155
235
  },
156
236
  "border": {
157
237
  "type": "string",
158
238
  "description": "Border and divider color",
159
239
  "pattern": "^#[0-9a-fA-F]{6}$",
160
- "examples": ["#e5e7eb", "#374151"]
240
+ "examples": [
241
+ "#e5e7eb",
242
+ "#374151"
243
+ ]
161
244
  }
162
245
  },
163
246
  "additionalProperties": false
package/scripts/build.ts CHANGED
@@ -16,15 +16,18 @@ import { copyFile, cp, mkdir, readdir, readFile, stat, writeFile } from "node:fs
16
16
  import { basename, dirname, extname, join, resolve } from "node:path";
17
17
  import { marked, Renderer } from "marked";
18
18
  import { ENGINE_ASSETS_DIR } from "../src/engine.ts";
19
+ import { loadPluginInjections, type PluginInjections } from "../src/plugin-loader.ts";
19
20
  import {
20
21
  buildBreadcrumbsStatic,
21
22
  buildFooter,
22
23
  buildNavStatic,
23
24
  buildPageMeta,
25
+ buildSlideNav,
24
26
  buildToc,
25
27
  type ContentFile,
26
- // Navigation/template building
27
28
  collectFiles,
29
+ // Navigation/template building
30
+ collectSlides,
28
31
  envBool,
29
32
  // Config helpers
30
33
  envString,
@@ -39,12 +42,15 @@ import {
39
42
  type Provenance,
40
43
  // Markdown utilities
41
44
  parseFrontmatter,
45
+ parseYaml,
42
46
  resolveStylesPath,
43
47
  resolveTemplatePath,
48
+ rewriteRelativeAssetUrls,
44
49
  // Types
45
50
  type SiteConfig,
46
51
  slugify,
47
52
  validatePath,
53
+ validateSlidesVisualsFences,
48
54
  } from "../src/shared.ts";
49
55
  import { generateThemeCSS, getPrismUrls, loadTheme, type Theme } from "../src/theme.ts";
50
56
 
@@ -161,6 +167,7 @@ async function renderFile(
161
167
  provenance: Provenance,
162
168
  config: SiteConfig,
163
169
  theme: Theme,
170
+ plugins: PluginInjections,
164
171
  ): Promise<string> {
165
172
  const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
166
173
  const content = await readFile(filePath, "utf-8");
@@ -198,12 +205,15 @@ async function renderFile(
198
205
  const themeCSS = generateThemeCSS(theme);
199
206
  const prismUrls = getPrismUrls(theme);
200
207
  const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
208
+ const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
201
209
 
202
210
  return template
211
+ .replace("{{BODY_CLASS}}", "mode-docs")
203
212
  .replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
204
213
  .replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
205
214
  .replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
206
215
  .replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
216
+ .replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
207
217
  .replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
208
218
  .replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
209
219
  .replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
@@ -218,6 +228,8 @@ async function renderFile(
218
228
  .replace("{{TOC}}", toc)
219
229
  .replace("{{FOOTER}}", footer)
220
230
  .replace("{{THEME_CSS}}", themeCSS)
231
+ .replace("{{PLUGIN_HEAD}}", plugins.head)
232
+ .replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
221
233
  .replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
222
234
  .replace("{{PRISM_DARK_URL}}", prismUrls.dark)
223
235
  .replace("{{HOT_RELOAD_SCRIPT}}", "");
@@ -229,6 +241,7 @@ function renderGettingStarted(
229
241
  provenance: Provenance,
230
242
  config: SiteConfig,
231
243
  theme: Theme,
244
+ plugins: PluginInjections,
232
245
  ): string {
233
246
  const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
234
247
  const htmlContent = `
@@ -259,12 +272,15 @@ sections:
259
272
  const prismUrls = getPrismUrls(theme);
260
273
  const pathPrefix = "./";
261
274
  const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
275
+ const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
262
276
 
263
277
  return template
278
+ .replace("{{BODY_CLASS}}", "mode-docs")
264
279
  .replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
265
280
  .replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
266
281
  .replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
267
282
  .replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
283
+ .replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
268
284
  .replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
269
285
  .replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
270
286
  .replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
@@ -279,6 +295,114 @@ sections:
279
295
  .replace("{{TOC}}", "")
280
296
  .replace("{{FOOTER}}", buildFooter(provenance, config))
281
297
  .replace("{{THEME_CSS}}", themeCSS)
298
+ .replace("{{PLUGIN_HEAD}}", plugins.head)
299
+ .replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
300
+ .replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
301
+ .replace("{{PRISM_DARK_URL}}", prismUrls.dark)
302
+ .replace("{{HOT_RELOAD_SCRIPT}}", "");
303
+ }
304
+
305
+ async function renderSlidesIndex(
306
+ template: string,
307
+ files: ContentFile[],
308
+ provenance: Provenance,
309
+ config: SiteConfig,
310
+ theme: Theme,
311
+ plugins: PluginInjections,
312
+ ): Promise<string> {
313
+ const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
314
+ const pathPrefix = "./";
315
+ const slides = await collectSlides(files);
316
+ let validateFences = false;
317
+ try {
318
+ const raw = await readFile(join(ROOT, "kitfly.plugins.yaml"), "utf-8");
319
+ const parsed = parseYaml(raw) as unknown as Record<string, unknown>;
320
+ const enabled = Array.isArray(parsed?.plugins) ? (parsed.plugins as unknown[]) : [];
321
+ validateFences = enabled.some((p) => typeof p === "string" && p.startsWith("slides-visuals@"));
322
+ } catch {
323
+ // no config, skip
324
+ }
325
+ const renderedSlides = await Promise.all(
326
+ slides.map(async (slide, i) => {
327
+ let inner = "";
328
+ if (slide.kind === "markdown") {
329
+ if (validateFences) {
330
+ const diagnostics = validateSlidesVisualsFences(slide.body);
331
+ if (diagnostics.length) {
332
+ const msg = diagnostics
333
+ .slice(0, 12)
334
+ .map((d) => ` - ${slide.sourcePath}:${d.line} ${d.message}`)
335
+ .join("\n");
336
+ throw new Error(`slides-visuals fence contract violations:\n${msg}`);
337
+ }
338
+ }
339
+ inner = marked.parse(slide.body) as string;
340
+ } else if (slide.kind === "yaml") {
341
+ inner = `<pre><code class="language-yaml">${escapeHtml(slide.body)}</code></pre>`;
342
+ } else {
343
+ let prettyJson = slide.body;
344
+ try {
345
+ prettyJson = JSON.stringify(JSON.parse(slide.body), null, 2);
346
+ } catch {
347
+ // Use original if not valid JSON
348
+ }
349
+ inner = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
350
+ }
351
+ inner = rewriteRelativeAssetUrls(inner, slide.sourceUrlPath, pathPrefix);
352
+
353
+ const activeClass = i === 0 ? " active" : "";
354
+ const classToken = slide.className ? ` ${slide.className}` : "";
355
+ return `<section id="${slide.id}" class="slide${classToken}${activeClass}" data-slide-index="${i}">${inner}</section>`;
356
+ }),
357
+ );
358
+
359
+ const htmlContent = `
360
+ <div class="slides-shell" style="--slide-aspect: ${config.aspect || "16/9"}">
361
+ <div class="slide-viewport">
362
+ <div class="slide-frame">
363
+ ${renderedSlides.join("\n")}
364
+ </div>
365
+ </div>
366
+ <div class="slide-nav" aria-label="Slide navigation">
367
+ <button class="slide-prev" type="button" aria-label="Previous slide">Prev</button>
368
+ <span class="slide-counter">1 / ${slides.length}</span>
369
+ <button class="slide-next" type="button" aria-label="Next slide">Next</button>
370
+ <div class="slide-progress" role="presentation">
371
+ <span class="slide-progress-bar" style="width: ${(1 / slides.length) * 100}%"></span>
372
+ </div>
373
+ </div>
374
+ </div>`;
375
+
376
+ const nav = buildSlideNav(slides, config, "slide-1");
377
+ const brandTarget = config.brand.external ? ' target="_blank" rel="noopener"' : "";
378
+ const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
379
+ const themeCSS = generateThemeCSS(theme);
380
+ const prismUrls = getPrismUrls(theme);
381
+ const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
382
+
383
+ return template
384
+ .replace("{{BODY_CLASS}}", "mode-slides")
385
+ .replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
386
+ .replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
387
+ .replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
388
+ .replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
389
+ .replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
390
+ .replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
391
+ .replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
392
+ .replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
393
+ .replace(/\{\{SITE_TITLE\}\}/g, config.title)
394
+ .replace("{{TITLE}}", config.title)
395
+ .replace("{{VERSION}}", uiVersion)
396
+ .replace("{{BRANCH}}", provenance.gitBranch)
397
+ .replace("{{BREADCRUMBS}}", "")
398
+ .replace("{{PAGE_META}}", "")
399
+ .replace("{{NAV}}", nav)
400
+ .replace("{{CONTENT}}", htmlContent)
401
+ .replace("{{TOC}}", "")
402
+ .replace("{{FOOTER}}", buildFooter(provenance, config))
403
+ .replace("{{THEME_CSS}}", themeCSS)
404
+ .replace("{{PLUGIN_HEAD}}", plugins.head)
405
+ .replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
282
406
  .replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
283
407
  .replace("{{PRISM_DARK_URL}}", prismUrls.dark)
284
408
  .replace("{{HOT_RELOAD_SCRIPT}}", "");
@@ -333,6 +457,12 @@ async function buildSite() {
333
457
  // Read template
334
458
  const template = await readFile(await resolveTemplatePath(ROOT), "utf-8");
335
459
 
460
+ // Load plugin injections (optional; no-op when kitfly.plugins.yaml is absent)
461
+ const plugins = await loadPluginInjections({
462
+ root: ROOT,
463
+ mode: config.mode === "slides" ? "slides" : "docs",
464
+ });
465
+
336
466
  // Copy CSS
337
467
  const css = await readFile(await resolveStylesPath(ROOT), "utf-8");
338
468
  await writeFile(join(DIST, "styles.css"), css);
@@ -370,13 +500,23 @@ async function buildSite() {
370
500
 
371
501
  if (files.length === 0) {
372
502
  // No content - render Getting Started page
373
- const html = renderGettingStarted(template, provenance, config, theme);
503
+ const html = renderGettingStarted(template, provenance, config, theme, plugins);
374
504
  await writeFile(join(DIST, "index.html"), html);
375
505
  console.log(" ✓ index.html (Getting Started)");
376
506
  console.log(`\n\x1b[33mNo content found. Create site.yaml or content/ directory.\x1b[0m`);
377
507
  return;
378
508
  }
379
509
 
510
+ if (config.mode === "slides") {
511
+ const html = await renderSlidesIndex(template, files, provenance, config, theme, plugins);
512
+ await writeFile(join(DIST, "index.html"), html);
513
+ console.log(` ✓ index.html (slides mode, ${files.length} source files)`);
514
+ await generateAIAccessibility(DIST, files, config, provenance);
515
+ console.log(`\n\x1b[32mBuild complete! Output in ${OUT_DIR}/\x1b[0m`);
516
+ console.log(`\nTo view locally: open ${OUT_DIR}/index.html`);
517
+ return;
518
+ }
519
+
380
520
  for (const file of files) {
381
521
  const html = await renderFile(
382
522
  file.path,
@@ -386,6 +526,7 @@ async function buildSite() {
386
526
  provenance,
387
527
  config,
388
528
  theme,
529
+ plugins,
389
530
  );
390
531
 
391
532
  // Create output path
@@ -403,7 +544,16 @@ async function buildSite() {
403
544
  if (homePath) {
404
545
  try {
405
546
  await stat(homePath);
406
- const homeHtml = await renderFile(homePath, "", template, files, provenance, config, theme);
547
+ const homeHtml = await renderFile(
548
+ homePath,
549
+ "",
550
+ template,
551
+ files,
552
+ provenance,
553
+ config,
554
+ theme,
555
+ plugins,
556
+ );
407
557
  await writeFile(join(DIST, "index.html"), homeHtml);
408
558
  console.log(` ✓ index.html (from ${config.home})`);
409
559
  } catch {
@@ -417,6 +567,7 @@ async function buildSite() {
417
567
  provenance,
418
568
  config,
419
569
  theme,
570
+ plugins,
420
571
  );
421
572
  await writeFile(join(DIST, "index.html"), indexHtml);
422
573
  console.log(" ✓ index.html");
@@ -433,6 +584,7 @@ async function buildSite() {
433
584
  provenance,
434
585
  config,
435
586
  theme,
587
+ plugins,
436
588
  );
437
589
  await writeFile(join(DIST, "index.html"), indexHtml);
438
590
  console.log(" ✓ index.html");