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/bundle.ts CHANGED
@@ -4,13 +4,13 @@
4
4
  * Usage: bun run bundle [folder] [options]
5
5
  *
6
6
  * Options:
7
- * -o, --out <dir> Output directory [env: KITFLY_BUILD_OUT] [default: dist]
7
+ * -o, --out <dir> Output directory [env: KITFLY_BUNDLE_OUT] [default: bundles]
8
8
  * -n, --name <file> Bundle filename [env: KITFLY_BUNDLE_NAME] [default: bundle.html]
9
- * --raw Include raw markdown in bundle [env: KITFLY_BUILD_RAW] [default: true]
9
+ * --raw Include raw markdown in bundle [env: KITFLY_BUNDLE_RAW] [default: true]
10
10
  * --no-raw Don't include raw markdown
11
11
  * --help Show help message
12
12
  *
13
- * Creates dist/bundle.html - a single file containing all content,
13
+ * Creates bundles/bundle.html - a single file containing all content,
14
14
  * styles, and scripts for offline viewing.
15
15
  */
16
16
 
@@ -18,32 +18,38 @@ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
18
18
  import { basename, extname, join, resolve } from "node:path";
19
19
  import { marked, Renderer } from "marked";
20
20
  import { ENGINE_ASSETS_DIR } from "../src/engine.ts";
21
+ import { loadPluginInjections } from "../src/plugin-loader.ts";
21
22
  import {
22
23
  buildBundleFooter,
23
- // Navigation/template building
24
24
  buildSectionNav,
25
+ // Navigation/template building
26
+ buildSlideNav,
25
27
  // Types
26
28
  type ContentFile,
27
29
  collectFiles,
30
+ collectSlides,
28
31
  envBool,
29
32
  // Config helpers
30
33
  envString,
31
34
  // Formatting
32
35
  escapeHtml,
36
+ filterUnknownSlidesVisualsTypeDiagnostics,
33
37
  // YAML/Config parsing
34
38
  loadSiteConfig,
35
39
  // Markdown utilities
36
40
  parseFrontmatter,
41
+ parseYaml,
37
42
  resolveSiteVersion,
38
43
  resolveStylesPath,
39
44
  type SiteConfig,
40
45
  slugify,
41
46
  validatePath,
47
+ validateSlidesVisualsFences,
42
48
  } from "../src/shared.ts";
43
49
  import { generateThemeCSS, getPrismUrls, loadTheme } from "../src/theme.ts";
44
50
 
45
51
  // Defaults
46
- const DEFAULT_OUT = "dist";
52
+ const DEFAULT_OUT = "bundles";
47
53
  const DEFAULT_NAME = "bundle.html";
48
54
 
49
55
  let ROOT = process.cwd();
@@ -91,11 +97,15 @@ function getConfig(): {
91
97
  raw: boolean;
92
98
  } {
93
99
  const args = parseArgs(process.argv.slice(2));
100
+ const legacyOut = envString("KITFLY_BUILD_OUT", DEFAULT_OUT);
101
+ const out = args.out ?? envString("KITFLY_BUNDLE_OUT", legacyOut);
102
+ const legacyRaw = envBool("KITFLY_BUILD_RAW", true);
103
+ const raw = args.raw ?? envBool("KITFLY_BUNDLE_RAW", legacyRaw);
94
104
  return {
95
105
  folder: args.folder,
96
- out: args.out ?? envString("KITFLY_BUILD_OUT", DEFAULT_OUT),
106
+ out,
97
107
  name: args.name ?? envString("KITFLY_BUNDLE_NAME", DEFAULT_NAME),
98
- raw: args.raw ?? envBool("KITFLY_BUILD_RAW", true),
108
+ raw,
99
109
  };
100
110
  }
101
111
 
@@ -295,6 +305,73 @@ function buildBundleNav(files: ContentFile[], config: SiteConfig): string {
295
305
  return html;
296
306
  }
297
307
 
308
+ async function buildSlidesBundleContent(files: ContentFile[], config: SiteConfig): Promise<string> {
309
+ const slides = await collectSlides(files);
310
+ let validateFences = false;
311
+ try {
312
+ const raw = await readFile(join(ROOT, "kitfly.plugins.yaml"), "utf-8");
313
+ const parsed = parseYaml(raw) as unknown as Record<string, unknown>;
314
+ const enabled = Array.isArray(parsed?.plugins) ? (parsed.plugins as unknown[]) : [];
315
+ validateFences = enabled.some((p) => typeof p === "string" && p.startsWith("slides-visuals@"));
316
+ } catch {
317
+ // no config, skip
318
+ }
319
+ const renderedSlides = await Promise.all(
320
+ slides.map(async (slide, i) => {
321
+ let inner = "";
322
+ if (slide.kind === "markdown") {
323
+ if (validateFences) {
324
+ const diagnostics = filterUnknownSlidesVisualsTypeDiagnostics(
325
+ validateSlidesVisualsFences(slide.body),
326
+ );
327
+ if (diagnostics.length) {
328
+ const msg = diagnostics
329
+ .slice(0, 12)
330
+ .map((d) => ` - ${slide.sourcePath}:${d.line} ${d.message}`)
331
+ .join("\n");
332
+ throw new Error(`slides-visuals fence contract violations:\n${msg}`);
333
+ }
334
+ }
335
+ inner = marked.parse(slide.body) as string;
336
+ } else if (slide.kind === "yaml") {
337
+ inner = `<pre><code class="language-yaml">${escapeHtml(slide.body)}</code></pre>`;
338
+ } else {
339
+ let prettyJson = slide.body;
340
+ try {
341
+ prettyJson = JSON.stringify(JSON.parse(slide.body), null, 2);
342
+ } catch {
343
+ // Keep original text
344
+ }
345
+ inner = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
346
+ }
347
+
348
+ inner = await inlineLocalImages(inner, config);
349
+ inner = rewriteContentLinks(inner, files, slide.sourceUrlPath, config.docroot);
350
+
351
+ const activeClass = i === 0 ? " active" : "";
352
+ const classToken = slide.className ? ` ${slide.className}` : "";
353
+ return `<section id="${slide.id}" class="slide${classToken}${activeClass}" data-slide-index="${i}">${inner}</section>`;
354
+ }),
355
+ );
356
+
357
+ return `
358
+ <div class="slides-shell" style="--slide-aspect: ${config.aspect || "16/9"}">
359
+ <div class="slide-viewport">
360
+ <div class="slide-frame">
361
+ ${renderedSlides.join("\n")}
362
+ </div>
363
+ </div>
364
+ <div class="slide-nav" aria-label="Slide navigation">
365
+ <button class="slide-prev" type="button" aria-label="Previous slide">Prev</button>
366
+ <span class="slide-counter">1 / ${slides.length}</span>
367
+ <button class="slide-next" type="button" aria-label="Next slide">Next</button>
368
+ <div class="slide-progress" role="presentation">
369
+ <span class="slide-progress-bar" style="width: ${(1 / slides.length) * 100}%"></span>
370
+ </div>
371
+ </div>
372
+ </div>`;
373
+ }
374
+
298
375
  function buildBundleSidebarHeader(
299
376
  config: SiteConfig,
300
377
  version: string | undefined,
@@ -304,12 +381,13 @@ function buildBundleSidebarHeader(
304
381
  const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
305
382
  const productHref = config.home ? "#home" : "#";
306
383
  const versionLabel = version ? `v${version}` : "unversioned";
384
+ const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
307
385
 
308
386
  return `
309
387
  <div class="sidebar-header">
310
388
  <div class="logo ${logoClass}">
311
- <a href="${config.brand.url}" class="logo-icon"${brandTarget}>
312
- <img src="${brandLogo}" alt="${config.brand.name}" class="logo-img">
389
+ <a href="${config.brand.url}" class="logo-icon" data-initial="${brandInitial}"${brandTarget}>
390
+ <img src="${brandLogo}" alt="${config.brand.name}" class="logo-img" onerror="this.onerror=null;this.style.display='none';this.parentElement.classList.add('logo-fallback')">
313
391
  </a>
314
392
  <span class="logo-text">
315
393
  <a href="${config.brand.url}" class="brand"${brandTarget}>${config.brand.name}</a>
@@ -410,29 +488,6 @@ async function bundle() {
410
488
  // Resolve site version (site.yaml version, then git tag)
411
489
  const version = await resolveSiteVersion(ROOT, config.version);
412
490
 
413
- // Build navigation and content sections
414
- const sections: Map<string, { id: string; title: string; html: string }[]> = new Map();
415
-
416
- // Add home page as first item if specified
417
- if (config.home) {
418
- const homePath = validatePath(ROOT, config.docroot, config.home);
419
- if (homePath) {
420
- try {
421
- await stat(homePath);
422
- const content = await readFile(homePath, "utf-8");
423
- const { frontmatter, body } = parseFrontmatter(content);
424
- const title = (frontmatter.title as string) || "Home";
425
- let htmlContent = marked.parse(body) as string;
426
- htmlContent = await inlineLocalImages(htmlContent, config);
427
- htmlContent = rewriteContentLinks(htmlContent, files, undefined, config.docroot);
428
- sections.set("Home", [{ id: "home", title, html: htmlContent }]);
429
- console.log(` ✓ Added home page: ${config.home}`);
430
- } catch {
431
- console.warn(` ⚠ Home page ${config.home} not found`);
432
- }
433
- }
434
- }
435
-
436
491
  // Collect page metadata and raw content for AI accessibility
437
492
  const pageIndex: {
438
493
  path: string;
@@ -441,77 +496,124 @@ async function bundle() {
441
496
  description?: string;
442
497
  }[] = [];
443
498
  const rawMarkdown: { path: string; content: string }[] = [];
499
+ let navHtml = "";
500
+ let contentHtml = "";
444
501
 
445
- for (const file of files) {
446
- const content = await readFile(file.path, "utf-8");
447
- let title = basename(file.path).replace(/\.(md|yaml|json)$/, "");
448
- let description: string | undefined;
449
- let htmlContent: string;
450
-
451
- if (file.path.endsWith(".yaml")) {
452
- htmlContent = `<pre><code class="language-yaml">${escapeHtml(content)}</code></pre>`;
453
- } else if (file.path.endsWith(".json")) {
454
- // Render JSON as code block (pretty-printed)
455
- let prettyJson = content;
456
- try {
457
- prettyJson = JSON.stringify(JSON.parse(content), null, 2);
458
- } catch {
459
- // Use original if not valid JSON
460
- }
461
- htmlContent = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
462
- } else {
463
- const { frontmatter, body } = parseFrontmatter(content);
464
- if (frontmatter.title) {
465
- title = frontmatter.title as string;
502
+ if (config.mode === "slides") {
503
+ const slides = await collectSlides(files);
504
+ for (const file of files) {
505
+ const content = await readFile(file.path, "utf-8");
506
+ if (INCLUDE_RAW && file.path.endsWith(".md")) {
507
+ rawMarkdown.push({ path: file.urlPath, content });
466
508
  }
467
- if (frontmatter.description) {
468
- description = frontmatter.description as string;
509
+ }
510
+ for (const slide of slides) {
511
+ pageIndex.push({
512
+ path: slide.id,
513
+ title: slide.title,
514
+ section: slide.section,
515
+ });
516
+ }
517
+ navHtml = buildSlideNav(slides, config, "slide-1");
518
+ contentHtml = await buildSlidesBundleContent(files, config);
519
+ } else {
520
+ // Build navigation and content sections
521
+ const sections: Map<string, { id: string; title: string; html: string }[]> = new Map();
522
+
523
+ // Add home page as first item if specified
524
+ if (config.home) {
525
+ const homePath = validatePath(ROOT, config.docroot, config.home);
526
+ if (homePath) {
527
+ try {
528
+ await stat(homePath);
529
+ const content = await readFile(homePath, "utf-8");
530
+ const { frontmatter, body } = parseFrontmatter(content);
531
+ const title = (frontmatter.title as string) || "Home";
532
+ let htmlContent = marked.parse(body) as string;
533
+ htmlContent = await inlineLocalImages(htmlContent, config);
534
+ htmlContent = rewriteContentLinks(htmlContent, files, undefined, config.docroot);
535
+ sections.set("Home", [{ id: "home", title, html: htmlContent }]);
536
+ console.log(` ✓ Added home page: ${config.home}`);
537
+ } catch {
538
+ console.warn(` ⚠ Home page ${config.home} not found`);
539
+ }
469
540
  }
470
- htmlContent = marked.parse(body) as string;
541
+ }
471
542
 
472
- // Collect raw markdown for AI accessibility
473
- if (INCLUDE_RAW) {
474
- rawMarkdown.push({ path: file.urlPath, content });
543
+ for (const file of files) {
544
+ const content = await readFile(file.path, "utf-8");
545
+ let title = basename(file.path).replace(/\.(md|yaml|json)$/, "");
546
+ let description: string | undefined;
547
+ let htmlContent: string;
548
+
549
+ if (file.path.endsWith(".yaml")) {
550
+ htmlContent = `<pre><code class="language-yaml">${escapeHtml(content)}</code></pre>`;
551
+ } else if (file.path.endsWith(".json")) {
552
+ // Render JSON as code block (pretty-printed)
553
+ let prettyJson = content;
554
+ try {
555
+ prettyJson = JSON.stringify(JSON.parse(content), null, 2);
556
+ } catch {
557
+ // Use original if not valid JSON
558
+ }
559
+ htmlContent = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
560
+ } else {
561
+ const { frontmatter, body } = parseFrontmatter(content);
562
+ if (frontmatter.title) {
563
+ title = frontmatter.title as string;
564
+ }
565
+ if (frontmatter.description) {
566
+ description = frontmatter.description as string;
567
+ }
568
+ htmlContent = marked.parse(body) as string;
569
+
570
+ // Collect raw markdown for AI accessibility
571
+ if (INCLUDE_RAW) {
572
+ rawMarkdown.push({ path: file.urlPath, content });
573
+ }
475
574
  }
476
- }
477
575
 
478
- // Collect page metadata for content index
479
- pageIndex.push({
480
- path: file.urlPath,
481
- title,
482
- section: file.section,
483
- description,
484
- });
576
+ // Collect page metadata for content index
577
+ pageIndex.push({
578
+ path: file.urlPath,
579
+ title,
580
+ section: file.section,
581
+ description,
582
+ });
485
583
 
486
- // Inline any SVG references
487
- htmlContent = await inlineLocalImages(htmlContent, config);
488
- htmlContent = rewriteContentLinks(htmlContent, files, file.urlPath, config.docroot);
584
+ // Inline any SVG references
585
+ htmlContent = await inlineLocalImages(htmlContent, config);
586
+ htmlContent = rewriteContentLinks(htmlContent, files, file.urlPath, config.docroot);
489
587
 
490
- const sectionId = slugify(file.urlPath);
588
+ const sectionId = slugify(file.urlPath);
491
589
 
492
- if (!sections.has(file.section)) {
493
- sections.set(file.section, []);
590
+ if (!sections.has(file.section)) {
591
+ sections.set(file.section, []);
592
+ }
593
+ sections.get(file.section)?.push({ id: sectionId, title, html: htmlContent });
494
594
  }
495
- sections.get(file.section)?.push({ id: sectionId, title, html: htmlContent });
496
- }
497
595
 
498
- // Build navigation HTML from shared hierarchical nav tree
499
- const navHtml = buildBundleNav(files, config);
596
+ // Build navigation HTML from shared hierarchical nav tree
597
+ navHtml = buildBundleNav(files, config);
500
598
 
501
- // Build content HTML
502
- let contentHtml = "";
503
- for (const [, items] of sections) {
504
- for (const item of items) {
505
- contentHtml += `
599
+ // Build content HTML
600
+ for (const [, items] of sections) {
601
+ for (const item of items) {
602
+ contentHtml += `
506
603
  <section id="${item.id}" class="bundle-section">
507
604
  <h1 class="section-title">${item.title}</h1>
508
605
  ${item.html}
509
606
  </section>
510
607
  `;
608
+ }
511
609
  }
512
610
  }
513
611
 
514
612
  const themeCSS = generateThemeCSS(theme);
613
+ const plugins = await loadPluginInjections({
614
+ root: ROOT,
615
+ mode: config.mode === "slides" ? "slides" : "docs",
616
+ });
515
617
 
516
618
  // Inline brand assets for self-contained bundle
517
619
  const brandLogo = await inlineBrandAsset(config.brand.logo || "assets/brand/logo.png");
@@ -551,6 +653,7 @@ ${assets.prismCss}
551
653
  <style id="prism-dark" disabled>
552
654
  ${assets.prismCssDark}
553
655
  </style>
656
+ ${plugins.head}
554
657
  <script>
555
658
  (function() {
556
659
  const saved = localStorage.getItem('theme');
@@ -566,7 +669,7 @@ ${assets.prismCssDark}
566
669
  })();
567
670
  </script>
568
671
  </head>
569
- <body>
672
+ <body class="${config.mode === "slides" ? "mode-slides" : "mode-docs"}">
570
673
  <div class="layout">
571
674
  <nav class="sidebar">
572
675
  ${buildBundleSidebarHeader(config, version, brandLogo)}
@@ -590,6 +693,7 @@ ${assets.prismAutoloader}
590
693
  <script>
591
694
  ${assets.mermaid}
592
695
  </script>
696
+ ${plugins.bodyEnd}
593
697
  <script>
594
698
  // Initialize Mermaid
595
699
  function getMermaidTheme() {
@@ -647,17 +751,78 @@ ${assets.mermaid}
647
751
  }
648
752
  }
649
753
 
650
- // Smooth scroll for anchor links
651
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
652
- anchor.addEventListener('click', function (e) {
653
- e.preventDefault();
654
- const target = document.querySelector(this.getAttribute('href'));
655
- if (target) {
656
- target.scrollIntoView({ behavior: 'smooth' });
657
- history.pushState(null, '', this.getAttribute('href'));
754
+ // Slides mode hash routing
755
+ (function initSlidesMode() {
756
+ const shell = document.querySelector('.slides-shell');
757
+ if (!shell) {
758
+ // Docs mode: retain smooth in-page anchor scrolling.
759
+ document.querySelectorAll('a[href^="#"]').forEach((link) => {
760
+ link.addEventListener('click', (e) => {
761
+ const href = link.getAttribute('href') || '';
762
+ if (href.length <= 1) return;
763
+ const target = document.querySelector(href);
764
+ if (!target) return;
765
+ e.preventDefault();
766
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
767
+ history.replaceState(null, '', href);
768
+ });
769
+ });
770
+ return;
771
+ }
772
+
773
+ const slides = Array.from(document.querySelectorAll('.slide'));
774
+ if (!slides.length) return;
775
+
776
+ const prevBtn = document.querySelector('.slide-prev');
777
+ const nextBtn = document.querySelector('.slide-next');
778
+ const counter = document.querySelector('.slide-counter');
779
+ const progressBar = document.querySelector('.slide-progress-bar');
780
+ const navLinks = Array.from(document.querySelectorAll('.sidebar-nav a[href^="#slide-"]'));
781
+ let current = 0;
782
+
783
+ function setActive(n) {
784
+ current = Math.max(0, Math.min(n, slides.length - 1));
785
+ slides.forEach((slide, idx) => slide.classList.toggle('active', idx === current));
786
+ navLinks.forEach((link) => {
787
+ const active = link.getAttribute('href') === '#' + slides[current].id;
788
+ link.classList.toggle('active', active);
789
+ });
790
+ if (counter) counter.textContent = (current + 1) + ' / ' + slides.length;
791
+ if (progressBar) progressBar.style.width = (((current + 1) / slides.length) * 100) + '%';
792
+ if (prevBtn) prevBtn.disabled = current === 0;
793
+ if (nextBtn) nextBtn.disabled = current === slides.length - 1;
794
+ history.replaceState(null, '', '#'+slides[current].id);
795
+ }
796
+
797
+ function setFromHash() {
798
+ const hash = window.location.hash || '';
799
+ const idx = slides.findIndex((s) => '#'+s.id === hash);
800
+ if (idx >= 0) setActive(idx);
801
+ else setActive(0);
802
+ }
803
+
804
+ prevBtn?.addEventListener('click', () => setActive(current - 1));
805
+ nextBtn?.addEventListener('click', () => setActive(current + 1));
806
+
807
+ document.addEventListener('keydown', (e) => {
808
+ if (e.key === 'ArrowRight' || e.key === ' ') {
809
+ e.preventDefault();
810
+ setActive(current + 1);
811
+ } else if (e.key === 'ArrowLeft') {
812
+ e.preventDefault();
813
+ setActive(current - 1);
814
+ } else if (e.key === 'Home') {
815
+ e.preventDefault();
816
+ setActive(0);
817
+ } else if (e.key === 'End') {
818
+ e.preventDefault();
819
+ setActive(slides.length - 1);
658
820
  }
659
821
  });
660
- });
822
+
823
+ window.addEventListener('hashchange', setFromHash);
824
+ setFromHash();
825
+ })();
661
826
  </script>
662
827
  <!-- AI Accessibility: Content Index -->
663
828
  <script type="application/json" id="kitfly-content-index">
@@ -755,9 +920,9 @@ if (import.meta.main) {
755
920
  Usage: bun run bundle [folder] [options]
756
921
 
757
922
  Options:
758
- -o, --out <dir> Output directory [env: KITFLY_BUILD_OUT] [default: ${DEFAULT_OUT}]
923
+ -o, --out <dir> Output directory [env: KITFLY_BUNDLE_OUT] [default: ${DEFAULT_OUT}]
759
924
  -n, --name <file> Bundle filename [env: KITFLY_BUNDLE_NAME] [default: ${DEFAULT_NAME}]
760
- --raw Include raw markdown in bundle [env: KITFLY_BUILD_RAW] [default: true]
925
+ --raw Include raw markdown in bundle [env: KITFLY_BUNDLE_RAW] [default: true]
761
926
  --no-raw Don't include raw markdown
762
927
  --help Show this help message
763
928
 
@@ -765,8 +930,9 @@ Examples:
765
930
  bun run bundle
766
931
  bun run bundle ./docs
767
932
  bun run bundle --name docs.html
768
- bun run bundle ./docs --out ./public --name handbook.html
933
+ bun run bundle ./docs --out ./bundles --name handbook.html
769
934
  KITFLY_BUNDLE_NAME=docs.html bun run bundle
935
+ KITFLY_BUNDLE_OUT=release bun run bundle
770
936
  `);
771
937
  process.exit(0);
772
938
  }