kitfly 0.1.2 → 0.2.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 (209) hide show
  1. package/CHANGELOG.md +46 -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/slides-authoring-guidelines.md +129 -0
  25. package/dist/_raw/content/reference/structure.md +166 -0
  26. package/dist/_raw/content/reference.md +20 -0
  27. package/dist/_raw/content/templates/crucible.md +192 -0
  28. package/dist/_raw/content/templates/handbook.md +83 -0
  29. package/dist/_raw/content/templates/minimal.md +138 -0
  30. package/dist/_raw/content/templates/overview.md +187 -0
  31. package/dist/_raw/content/templates/pipeline.md +151 -0
  32. package/dist/_raw/content/templates/productbook.md +187 -0
  33. package/dist/_raw/content/templates/runbook.md +193 -0
  34. package/dist/_raw/content/templates/servicebook.md +163 -0
  35. package/dist/_raw/docs/decisions/ADR-0001-minimalist-site-code.md +118 -0
  36. package/dist/_raw/docs/decisions/ADR-0002-ai-accessibility.md +153 -0
  37. package/dist/_raw/docs/decisions/ADR-0003-single-file-bundle.md +93 -0
  38. package/dist/_raw/docs/decisions/ADR-0004-bun-runtime.md +98 -0
  39. package/dist/_raw/docs/decisions/ADR-0005-plugin-contract-and-distribution.md +110 -0
  40. package/dist/_raw/docs/decisions/DDR-0001-viewport-locked-layout.md +111 -0
  41. package/dist/_raw/docs/decisions/DDR-0002-theme-system.md +131 -0
  42. package/dist/_raw/docs/decisions/DDR-0003-bounded-logo-slot.md +106 -0
  43. package/dist/_raw/docs/decisions/DDR-0004-slides-rendering-model.md +113 -0
  44. package/dist/_raw/docs/decisions/DDR-0005-deterministic-layout-boundary.md +107 -0
  45. package/dist/_raw/docs/userguide/cli/build.md +85 -0
  46. package/dist/_raw/docs/userguide/cli/bundle.md +81 -0
  47. package/dist/_raw/docs/userguide/cli/dev.md +92 -0
  48. package/dist/_raw/docs/userguide/cli/init.md +116 -0
  49. package/dist/_raw/docs/userguide/cli/servers.md +69 -0
  50. package/dist/_raw/docs/userguide/cli/stop.md +76 -0
  51. package/dist/_raw/docs/userguide/cli/update.md +78 -0
  52. package/dist/_raw/docs/userguide/cli/version.md +65 -0
  53. package/dist/_raw/docs/userguide/cli.md +34 -0
  54. package/dist/_raw/docs/userguide/sharing.md +94 -0
  55. package/dist/_raw/schemas/plugin-schemas-notes.md +71 -0
  56. package/dist/_raw/schemas.md +42 -0
  57. package/dist/assets/brand/kitfly-favicon-32.png +0 -0
  58. package/dist/assets/brand/kitfly-icon-64.png +0 -0
  59. package/dist/assets/brand/kitfly-logo-128.png +0 -0
  60. package/dist/assets/brand/kitfly-logo-512.png +0 -0
  61. package/dist/assets/brand/kitfly-logo.svg +12132 -0
  62. package/dist/assets/brand/kitfly-neon-128.png +0 -0
  63. package/dist/assets/brand/kitfly-neon-192.png +0 -0
  64. package/dist/assets/brand/kitfly-neon-256.png +0 -0
  65. package/dist/assets/brand/kitfly-neon.png +0 -0
  66. package/dist/assets/brand/palette.md +75 -0
  67. package/dist/content/deployment/index.html +11 -0
  68. package/dist/content/deployment/preflight.html +418 -0
  69. package/dist/content/deployment/recipes/aws-s3.html +421 -0
  70. package/dist/content/deployment/recipes/cloudflare-pages.html +372 -0
  71. package/dist/content/deployment/recipes/cloudflare-r2.html +443 -0
  72. package/dist/content/deployment/recipes/fly-io.html +356 -0
  73. package/dist/content/deployment/recipes/github-pages.html +414 -0
  74. package/dist/content/deployment/recipes/index.html +11 -0
  75. package/dist/content/deployment/recipes/netlify.html +394 -0
  76. package/dist/content/deployment/recipes/vercel.html +382 -0
  77. package/dist/content/deployment/secrets-and-env-vars.html +380 -0
  78. package/dist/content/deployment.html +426 -0
  79. package/dist/content/guide/approaches.html +501 -0
  80. package/dist/content/guide/features.html +436 -0
  81. package/dist/content/guide/getting-started.html +403 -0
  82. package/dist/content/guide/index.html +11 -0
  83. package/dist/content/guide/kitfly-overview.html +544 -0
  84. package/dist/content/index.html +11 -0
  85. package/dist/content/reference/configuration.html +580 -0
  86. package/dist/content/reference/design-catalog.html +449 -0
  87. package/dist/content/reference/environment-variables.html +367 -0
  88. package/dist/content/reference/glossary.html +368 -0
  89. package/dist/content/reference/index.html +11 -0
  90. package/dist/content/reference/key-concepts.html +399 -0
  91. package/dist/content/reference/plugins.html +491 -0
  92. package/dist/content/reference/slides-authoring-guidelines.html +418 -0
  93. package/dist/content/reference/structure.html +463 -0
  94. package/dist/content/reference.html +335 -0
  95. package/dist/content/templates/crucible.html +546 -0
  96. package/dist/content/templates/handbook.html +405 -0
  97. package/dist/content/templates/index.html +11 -0
  98. package/dist/content/templates/minimal.html +447 -0
  99. package/dist/content/templates/overview.html +558 -0
  100. package/dist/content/templates/pipeline.html +494 -0
  101. package/dist/content/templates/productbook.html +540 -0
  102. package/dist/content/templates/runbook.html +543 -0
  103. package/dist/content/templates/servicebook.html +523 -0
  104. package/dist/content-index.json +549 -0
  105. package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +491 -0
  106. package/dist/docs/decisions/ADR-0002-ai-accessibility.html +434 -0
  107. package/dist/docs/decisions/ADR-0003-single-file-bundle.html +412 -0
  108. package/dist/docs/decisions/ADR-0004-bun-runtime.html +409 -0
  109. package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +402 -0
  110. package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +459 -0
  111. package/dist/docs/decisions/DDR-0002-theme-system.html +452 -0
  112. package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +423 -0
  113. package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +399 -0
  114. package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +422 -0
  115. package/dist/docs/decisions/index.html +11 -0
  116. package/dist/docs/userguide/cli/build.html +408 -0
  117. package/dist/docs/userguide/cli/bundle.html +419 -0
  118. package/dist/docs/userguide/cli/dev.html +428 -0
  119. package/dist/docs/userguide/cli/index.html +11 -0
  120. package/dist/docs/userguide/cli/init.html +436 -0
  121. package/dist/docs/userguide/cli/servers.html +393 -0
  122. package/dist/docs/userguide/cli/stop.html +408 -0
  123. package/dist/docs/userguide/cli/update.html +406 -0
  124. package/dist/docs/userguide/cli/version.html +406 -0
  125. package/dist/docs/userguide/cli.html +386 -0
  126. package/dist/docs/userguide/index.html +11 -0
  127. package/dist/docs/userguide/sharing.html +465 -0
  128. package/dist/index.html +387 -0
  129. package/dist/llms.txt +18 -0
  130. package/dist/provenance.json +7 -0
  131. package/dist/schemas/index.html +11 -0
  132. package/dist/schemas/plugin-registry.schema.html +327 -0
  133. package/dist/schemas/plugin-schemas-notes.html +364 -0
  134. package/dist/schemas/plugin.schema.html +327 -0
  135. package/dist/schemas/plugins.schema.html +327 -0
  136. package/dist/schemas/v0/common.schema.html +386 -0
  137. package/dist/schemas/v0/index.html +11 -0
  138. package/dist/schemas/v0/plugin-registry.schema.html +547 -0
  139. package/dist/schemas/v0/plugin.schema.html +497 -0
  140. package/dist/schemas/v0/plugins.schema.html +406 -0
  141. package/dist/schemas/v0/site.schema.html +541 -0
  142. package/dist/schemas/v0/theme.schema.html +615 -0
  143. package/dist/schemas.html +351 -0
  144. package/dist/styles.css +1262 -0
  145. package/package.json +4 -2
  146. package/plugins-dist/callouts.css +32 -0
  147. package/plugins-dist/callouts.js +46 -0
  148. package/plugins-dist/slides-visuals.css +390 -0
  149. package/plugins-dist/slides-visuals.js +689 -0
  150. package/registry/plugins.yaml +35 -0
  151. package/schemas/README.md +10 -0
  152. package/schemas/plugin-registry.schema.json +5 -0
  153. package/schemas/plugin-schemas-notes.md +71 -0
  154. package/schemas/plugin.schema.json +5 -0
  155. package/schemas/plugins.schema.json +5 -0
  156. package/schemas/v0/common.schema.json +64 -0
  157. package/schemas/v0/plugin-registry.schema.json +225 -0
  158. package/schemas/v0/plugin.schema.json +175 -0
  159. package/schemas/v0/plugins.schema.json +84 -0
  160. package/schemas/v0/site.schema.json +56 -9
  161. package/schemas/v0/theme.schema.json +105 -22
  162. package/scripts/build.ts +158 -3
  163. package/scripts/bundle.ts +261 -95
  164. package/scripts/dev.ts +301 -11
  165. package/src/__tests__/build.test.ts +220 -1
  166. package/src/__tests__/bundle.test.ts +31 -0
  167. package/src/__tests__/cli.test.ts +14 -3
  168. package/src/__tests__/dev-plugin-errors.test.ts +20 -0
  169. package/src/__tests__/fixtures/fences/slides-visuals/invalid/bad-list-indent.md +5 -0
  170. package/src/__tests__/fixtures/fences/slides-visuals/invalid/blank-line.md +5 -0
  171. package/src/__tests__/fixtures/fences/slides-visuals/invalid/compare-object-items.md +9 -0
  172. package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-branching-no-source.md +5 -0
  173. package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-converging-no-target.md +6 -0
  174. package/src/__tests__/fixtures/fences/slides-visuals/invalid/indented-fence.md +4 -0
  175. package/src/__tests__/fixtures/fences/slides-visuals/invalid/staircase-empty-steps.md +3 -0
  176. package/src/__tests__/fixtures/fences/slides-visuals/invalid/stat-grid-missing-fields.md +5 -0
  177. package/src/__tests__/fixtures/fences/slides-visuals/invalid/timeline-horizontal-no-events.md +2 -0
  178. package/src/__tests__/fixtures/fences/slides-visuals/invalid/unknown-type.md +3 -0
  179. package/src/__tests__/fixtures/fences/slides-visuals/valid/compare.md +10 -0
  180. package/src/__tests__/fixtures/fences/slides-visuals/valid/comparison-table.md +14 -0
  181. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching-no-split.md +7 -0
  182. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching.md +8 -0
  183. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging-no-merge.md +7 -0
  184. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging.md +8 -0
  185. package/src/__tests__/fixtures/fences/slides-visuals/valid/funnel.md +7 -0
  186. package/src/__tests__/fixtures/fences/slides-visuals/valid/kpi.md +5 -0
  187. package/src/__tests__/fixtures/fences/slides-visuals/valid/layer-cake.md +6 -0
  188. package/src/__tests__/fixtures/fences/slides-visuals/valid/pyramid.md +6 -0
  189. package/src/__tests__/fixtures/fences/slides-visuals/valid/quadrant-grid.md +8 -0
  190. package/src/__tests__/fixtures/fences/slides-visuals/valid/scorecard.md +13 -0
  191. package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase-down.md +7 -0
  192. package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase.md +8 -0
  193. package/src/__tests__/fixtures/fences/slides-visuals/valid/stat-grid.md +8 -0
  194. package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-horizontal.md +9 -0
  195. package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-vertical.md +10 -0
  196. package/src/__tests__/init.test.ts +35 -0
  197. package/src/__tests__/plugin-loader.test.ts +221 -0
  198. package/src/__tests__/shared.test.ts +451 -0
  199. package/src/__tests__/slides-visuals-fence-contract.test.ts +28 -0
  200. package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +147 -0
  201. package/src/__tests__/styles.test.ts +35 -0
  202. package/src/cli.ts +9 -4
  203. package/src/plugin-loader.ts +245 -0
  204. package/src/shared.ts +650 -7
  205. package/src/site/styles.css +331 -0
  206. package/src/site/template.html +66 -5
  207. package/src/templates/deck.ts +186 -0
  208. package/src/templates/driver.ts +11 -1
  209. package/src/templates/minimal.ts +1 -0
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,
@@ -32,6 +35,7 @@ import {
32
35
  escapeHtml,
33
36
  // File utilities
34
37
  exists,
38
+ filterUnknownSlidesVisualsTypeDiagnostics,
35
39
  // Provenance
36
40
  generateProvenance,
37
41
  // YAML/Config parsing
@@ -39,12 +43,15 @@ import {
39
43
  type Provenance,
40
44
  // Markdown utilities
41
45
  parseFrontmatter,
46
+ parseYaml,
42
47
  resolveStylesPath,
43
48
  resolveTemplatePath,
49
+ rewriteRelativeAssetUrls,
44
50
  // Types
45
51
  type SiteConfig,
46
52
  slugify,
47
53
  validatePath,
54
+ validateSlidesVisualsFences,
48
55
  } from "../src/shared.ts";
49
56
  import { generateThemeCSS, getPrismUrls, loadTheme, type Theme } from "../src/theme.ts";
50
57
 
@@ -161,6 +168,7 @@ async function renderFile(
161
168
  provenance: Provenance,
162
169
  config: SiteConfig,
163
170
  theme: Theme,
171
+ plugins: PluginInjections,
164
172
  ): Promise<string> {
165
173
  const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
166
174
  const content = await readFile(filePath, "utf-8");
@@ -198,12 +206,15 @@ async function renderFile(
198
206
  const themeCSS = generateThemeCSS(theme);
199
207
  const prismUrls = getPrismUrls(theme);
200
208
  const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
209
+ const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
201
210
 
202
211
  return template
212
+ .replace("{{BODY_CLASS}}", "mode-docs")
203
213
  .replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
204
214
  .replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
205
215
  .replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
206
216
  .replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
217
+ .replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
207
218
  .replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
208
219
  .replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
209
220
  .replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
@@ -218,6 +229,8 @@ async function renderFile(
218
229
  .replace("{{TOC}}", toc)
219
230
  .replace("{{FOOTER}}", footer)
220
231
  .replace("{{THEME_CSS}}", themeCSS)
232
+ .replace("{{PLUGIN_HEAD}}", plugins.head)
233
+ .replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
221
234
  .replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
222
235
  .replace("{{PRISM_DARK_URL}}", prismUrls.dark)
223
236
  .replace("{{HOT_RELOAD_SCRIPT}}", "");
@@ -229,6 +242,7 @@ function renderGettingStarted(
229
242
  provenance: Provenance,
230
243
  config: SiteConfig,
231
244
  theme: Theme,
245
+ plugins: PluginInjections,
232
246
  ): string {
233
247
  const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
234
248
  const htmlContent = `
@@ -259,12 +273,15 @@ sections:
259
273
  const prismUrls = getPrismUrls(theme);
260
274
  const pathPrefix = "./";
261
275
  const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
276
+ const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
262
277
 
263
278
  return template
279
+ .replace("{{BODY_CLASS}}", "mode-docs")
264
280
  .replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
265
281
  .replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
266
282
  .replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
267
283
  .replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
284
+ .replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
268
285
  .replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
269
286
  .replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
270
287
  .replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
@@ -279,6 +296,116 @@ sections:
279
296
  .replace("{{TOC}}", "")
280
297
  .replace("{{FOOTER}}", buildFooter(provenance, config))
281
298
  .replace("{{THEME_CSS}}", themeCSS)
299
+ .replace("{{PLUGIN_HEAD}}", plugins.head)
300
+ .replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
301
+ .replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
302
+ .replace("{{PRISM_DARK_URL}}", prismUrls.dark)
303
+ .replace("{{HOT_RELOAD_SCRIPT}}", "");
304
+ }
305
+
306
+ async function renderSlidesIndex(
307
+ template: string,
308
+ files: ContentFile[],
309
+ provenance: Provenance,
310
+ config: SiteConfig,
311
+ theme: Theme,
312
+ plugins: PluginInjections,
313
+ ): Promise<string> {
314
+ const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
315
+ const pathPrefix = "./";
316
+ const slides = await collectSlides(files);
317
+ let validateFences = false;
318
+ try {
319
+ const raw = await readFile(join(ROOT, "kitfly.plugins.yaml"), "utf-8");
320
+ const parsed = parseYaml(raw) as unknown as Record<string, unknown>;
321
+ const enabled = Array.isArray(parsed?.plugins) ? (parsed.plugins as unknown[]) : [];
322
+ validateFences = enabled.some((p) => typeof p === "string" && p.startsWith("slides-visuals@"));
323
+ } catch {
324
+ // no config, skip
325
+ }
326
+ const renderedSlides = await Promise.all(
327
+ slides.map(async (slide, i) => {
328
+ let inner = "";
329
+ if (slide.kind === "markdown") {
330
+ if (validateFences) {
331
+ const diagnostics = filterUnknownSlidesVisualsTypeDiagnostics(
332
+ validateSlidesVisualsFences(slide.body),
333
+ );
334
+ if (diagnostics.length) {
335
+ const msg = diagnostics
336
+ .slice(0, 12)
337
+ .map((d) => ` - ${slide.sourcePath}:${d.line} ${d.message}`)
338
+ .join("\n");
339
+ throw new Error(`slides-visuals fence contract violations:\n${msg}`);
340
+ }
341
+ }
342
+ inner = marked.parse(slide.body) as string;
343
+ } else if (slide.kind === "yaml") {
344
+ inner = `<pre><code class="language-yaml">${escapeHtml(slide.body)}</code></pre>`;
345
+ } else {
346
+ let prettyJson = slide.body;
347
+ try {
348
+ prettyJson = JSON.stringify(JSON.parse(slide.body), null, 2);
349
+ } catch {
350
+ // Use original if not valid JSON
351
+ }
352
+ inner = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
353
+ }
354
+ inner = rewriteRelativeAssetUrls(inner, slide.sourceUrlPath, pathPrefix);
355
+
356
+ const activeClass = i === 0 ? " active" : "";
357
+ const classToken = slide.className ? ` ${slide.className}` : "";
358
+ return `<section id="${slide.id}" class="slide${classToken}${activeClass}" data-slide-index="${i}">${inner}</section>`;
359
+ }),
360
+ );
361
+
362
+ const htmlContent = `
363
+ <div class="slides-shell" style="--slide-aspect: ${config.aspect || "16/9"}">
364
+ <div class="slide-viewport">
365
+ <div class="slide-frame">
366
+ ${renderedSlides.join("\n")}
367
+ </div>
368
+ </div>
369
+ <div class="slide-nav" aria-label="Slide navigation">
370
+ <button class="slide-prev" type="button" aria-label="Previous slide">Prev</button>
371
+ <span class="slide-counter">1 / ${slides.length}</span>
372
+ <button class="slide-next" type="button" aria-label="Next slide">Next</button>
373
+ <div class="slide-progress" role="presentation">
374
+ <span class="slide-progress-bar" style="width: ${(1 / slides.length) * 100}%"></span>
375
+ </div>
376
+ </div>
377
+ </div>`;
378
+
379
+ const nav = buildSlideNav(slides, config, "slide-1");
380
+ const brandTarget = config.brand.external ? ' target="_blank" rel="noopener"' : "";
381
+ const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
382
+ const themeCSS = generateThemeCSS(theme);
383
+ const prismUrls = getPrismUrls(theme);
384
+ const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
385
+
386
+ return template
387
+ .replace("{{BODY_CLASS}}", "mode-slides")
388
+ .replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
389
+ .replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
390
+ .replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
391
+ .replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
392
+ .replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
393
+ .replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
394
+ .replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
395
+ .replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
396
+ .replace(/\{\{SITE_TITLE\}\}/g, config.title)
397
+ .replace("{{TITLE}}", config.title)
398
+ .replace("{{VERSION}}", uiVersion)
399
+ .replace("{{BRANCH}}", provenance.gitBranch)
400
+ .replace("{{BREADCRUMBS}}", "")
401
+ .replace("{{PAGE_META}}", "")
402
+ .replace("{{NAV}}", nav)
403
+ .replace("{{CONTENT}}", htmlContent)
404
+ .replace("{{TOC}}", "")
405
+ .replace("{{FOOTER}}", buildFooter(provenance, config))
406
+ .replace("{{THEME_CSS}}", themeCSS)
407
+ .replace("{{PLUGIN_HEAD}}", plugins.head)
408
+ .replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
282
409
  .replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
283
410
  .replace("{{PRISM_DARK_URL}}", prismUrls.dark)
284
411
  .replace("{{HOT_RELOAD_SCRIPT}}", "");
@@ -333,6 +460,12 @@ async function buildSite() {
333
460
  // Read template
334
461
  const template = await readFile(await resolveTemplatePath(ROOT), "utf-8");
335
462
 
463
+ // Load plugin injections (optional; no-op when kitfly.plugins.yaml is absent)
464
+ const plugins = await loadPluginInjections({
465
+ root: ROOT,
466
+ mode: config.mode === "slides" ? "slides" : "docs",
467
+ });
468
+
336
469
  // Copy CSS
337
470
  const css = await readFile(await resolveStylesPath(ROOT), "utf-8");
338
471
  await writeFile(join(DIST, "styles.css"), css);
@@ -370,13 +503,23 @@ async function buildSite() {
370
503
 
371
504
  if (files.length === 0) {
372
505
  // No content - render Getting Started page
373
- const html = renderGettingStarted(template, provenance, config, theme);
506
+ const html = renderGettingStarted(template, provenance, config, theme, plugins);
374
507
  await writeFile(join(DIST, "index.html"), html);
375
508
  console.log(" ✓ index.html (Getting Started)");
376
509
  console.log(`\n\x1b[33mNo content found. Create site.yaml or content/ directory.\x1b[0m`);
377
510
  return;
378
511
  }
379
512
 
513
+ if (config.mode === "slides") {
514
+ const html = await renderSlidesIndex(template, files, provenance, config, theme, plugins);
515
+ await writeFile(join(DIST, "index.html"), html);
516
+ console.log(` ✓ index.html (slides mode, ${files.length} source files)`);
517
+ await generateAIAccessibility(DIST, files, config, provenance);
518
+ console.log(`\n\x1b[32mBuild complete! Output in ${OUT_DIR}/\x1b[0m`);
519
+ console.log(`\nTo view locally: open ${OUT_DIR}/index.html`);
520
+ return;
521
+ }
522
+
380
523
  for (const file of files) {
381
524
  const html = await renderFile(
382
525
  file.path,
@@ -386,6 +529,7 @@ async function buildSite() {
386
529
  provenance,
387
530
  config,
388
531
  theme,
532
+ plugins,
389
533
  );
390
534
 
391
535
  // Create output path
@@ -403,7 +547,16 @@ async function buildSite() {
403
547
  if (homePath) {
404
548
  try {
405
549
  await stat(homePath);
406
- const homeHtml = await renderFile(homePath, "", template, files, provenance, config, theme);
550
+ const homeHtml = await renderFile(
551
+ homePath,
552
+ "",
553
+ template,
554
+ files,
555
+ provenance,
556
+ config,
557
+ theme,
558
+ plugins,
559
+ );
407
560
  await writeFile(join(DIST, "index.html"), homeHtml);
408
561
  console.log(` ✓ index.html (from ${config.home})`);
409
562
  } catch {
@@ -417,6 +570,7 @@ async function buildSite() {
417
570
  provenance,
418
571
  config,
419
572
  theme,
573
+ plugins,
420
574
  );
421
575
  await writeFile(join(DIST, "index.html"), indexHtml);
422
576
  console.log(" ✓ index.html");
@@ -433,6 +587,7 @@ async function buildSite() {
433
587
  provenance,
434
588
  config,
435
589
  theme,
590
+ plugins,
436
591
  );
437
592
  await writeFile(join(DIST, "index.html"), indexHtml);
438
593
  console.log(" ✓ index.html");