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
@@ -0,0 +1,452 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>DDR-0002: Two-Layer Theme System - Kitfly Docs</title>
7
+ <link rel="icon" type="image/png" sizes="32x32" href="../../assets/brand/kitfly-favicon-32.png">
8
+ <link rel="icon" type="image/png" sizes="64x64" href="../../assets/brand/kitfly-neon-256.png">
9
+ <link rel="stylesheet" href="../../styles.css">
10
+ <style id="kitfly-theme">
11
+ :root { --color-bg: #ffffff;
12
+ --color-bg-sidebar: #f5f7f8;
13
+ --color-text: #374151;
14
+ --color-text-muted: #6b7280;
15
+ --color-border: #e5e7eb;
16
+ --color-link: #007182;
17
+ --color-link-hover: #0a6172;
18
+ --color-accent: #152F46;
19
+ --color-code-bg: #f5f7f8;
20
+ --color-logo: #152F46;
21
+ --sidebar-width: 280px;
22
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
23
+ --font-headings: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
24
+ --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; }
25
+ html { font-size: 16px; }
26
+ @media (prefers-color-scheme: dark) {
27
+ :root:not([data-theme="light"]) { --color-bg: #0d1117;
28
+ --color-bg-sidebar: #152F46;
29
+ --color-text: #e5e7eb;
30
+ --color-text-muted: #9ca3af;
31
+ --color-border: #374151;
32
+ --color-link: #709EA6;
33
+ --color-link-hover: #8fb5bc;
34
+ --color-accent: #f9fafb;
35
+ --color-code-bg: #152F46;
36
+ --color-logo: #f9fafb; }
37
+ }
38
+ [data-theme="dark"] { --color-bg: #0d1117;
39
+ --color-bg-sidebar: #152F46;
40
+ --color-text: #e5e7eb;
41
+ --color-text-muted: #9ca3af;
42
+ --color-border: #374151;
43
+ --color-link: #709EA6;
44
+ --color-link-hover: #8fb5bc;
45
+ --color-accent: #f9fafb;
46
+ --color-code-bg: #152F46;
47
+ --color-logo: #f9fafb; }
48
+ [data-theme="light"] { --color-bg: #ffffff;
49
+ --color-bg-sidebar: #f5f7f8;
50
+ --color-text: #374151;
51
+ --color-text-muted: #6b7280;
52
+ --color-border: #e5e7eb;
53
+ --color-link: #007182;
54
+ --color-link-hover: #0a6172;
55
+ --color-accent: #152F46;
56
+ --color-code-bg: #f5f7f8;
57
+ --color-logo: #152F46;
58
+ --sidebar-width: 280px;
59
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
60
+ --font-headings: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
61
+ --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; }
62
+ </style>
63
+ <!-- Syntax highlighting - Prism.js -->
64
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism.min.css" id="prism-light">
65
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism-okaidia.min.css" id="prism-dark" disabled>
66
+
67
+ <script>
68
+ // Apply saved theme immediately to prevent flash
69
+ (function() {
70
+ const saved = localStorage.getItem('theme');
71
+ if (saved) {
72
+ document.documentElement.setAttribute('data-theme', saved);
73
+ }
74
+ // Set Prism theme based on saved or system preference
75
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
76
+ const isDark = saved === 'dark' || (!saved && prefersDark);
77
+ if (isDark) {
78
+ document.getElementById('prism-light')?.setAttribute('disabled', '');
79
+ document.getElementById('prism-dark')?.removeAttribute('disabled');
80
+ }
81
+ })();
82
+ </script>
83
+ </head>
84
+ <body class="mode-docs">
85
+ <div class="mobile-header">
86
+ <button class="nav-toggle" onclick="toggleNav()" aria-label="Toggle navigation">
87
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
88
+ <path d="M3 12h18M3 6h18M3 18h18"/>
89
+ </svg>
90
+ </button>
91
+ <a href="../../" class="mobile-logo" title="Home" data-initial="K">
92
+ <img src="../../assets/brand/kitfly-neon-256.png" alt="Kitfly" class="logo-img logo-icon" onerror="this.onerror=null;this.style.display='none';this.parentElement.classList.add('logo-fallback')"/>
93
+ </a>
94
+ <button class="mobile-theme-toggle" onclick="toggleTheme()" title="Toggle theme" aria-label="Toggle theme">
95
+ <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
96
+ <circle cx="12" cy="12" r="5"/>
97
+ <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
98
+ </svg>
99
+ <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
100
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
101
+ </svg>
102
+ </button>
103
+ </div>
104
+ <div class="layout">
105
+ <nav class="sidebar">
106
+ <div class="sidebar-header">
107
+ <div class="logo logo-icon">
108
+ <a href="/" class="logo-icon" data-initial="K">
109
+ <img src="../../assets/brand/kitfly-neon-256.png" alt="Kitfly" class="logo-img" onerror="this.onerror=null;this.style.display='none';this.parentElement.classList.add('logo-fallback')"/>
110
+ </a>
111
+ <span class="logo-text">
112
+ <a href="/" class="brand">Kitfly</a>
113
+ <a href="../../" class="product">Kitfly Docs</a>
114
+ </span>
115
+ </div>
116
+ <div class="header-tools">
117
+ <button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme" aria-label="Toggle theme">
118
+ <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
119
+ <circle cx="12" cy="12" r="5"/>
120
+ <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
121
+ </svg>
122
+ <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
123
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
124
+ </svg>
125
+ </button>
126
+ <div class="sidebar-meta">
127
+ <span class="meta-version">v0.2.0</span>
128
+ <span class="meta-branch">HEAD</span>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ <div class="sidebar-nav">
133
+ <ul><li><a href="../../index.html" class="nav-home">Home</a></li><li><span class="nav-section">Guide</span><ul><li><a href="../../content/guide/approaches.html">approaches</a></li><li><a href="../../content/guide/features.html">features</a></li><li><a href="../../content/guide/getting-started.html">getting-started</a></li><li><a href="../../content/guide/kitfly-overview.html">kitfly-overview</a></li></ul></li><li><span class="nav-section">Templates</span><ul><li><a href="../../content/templates/crucible.html">crucible</a></li><li><a href="../../content/templates/handbook.html">handbook</a></li><li><a href="../../content/templates/minimal.html">minimal</a></li><li><a href="../../content/templates/overview.html">overview</a></li><li><a href="../../content/templates/pipeline.html">pipeline</a></li><li><a href="../../content/templates/productbook.html">productbook</a></li><li><a href="../../content/templates/runbook.html">runbook</a></li><li><a href="../../content/templates/servicebook.html">servicebook</a></li></ul></li><li><a href="../../content/reference.html" class="nav-section">Reference</a><ul><li><a href="../../content/reference/configuration.html">configuration</a></li><li><a href="../../content/reference/design-catalog.html">design-catalog</a></li><li><a href="../../content/reference/environment-variables.html">environment-variables</a></li><li><a href="../../content/reference/glossary.html">glossary</a></li><li><a href="../../content/reference/key-concepts.html">key-concepts</a></li><li><a href="../../content/reference/plugins.html">plugins</a></li><li><a href="../../content/reference/structure.html">structure</a></li></ul></li><li><a href="../../content/deployment.html" class="nav-section">Deployment</a><ul><li><a href="../../content/deployment/preflight.html">preflight</a></li><li><details><summary class="nav-group">recipes</summary><ul><li><a href="../../content/deployment/recipes/aws-s3.html">aws-s3</a></li><li><a href="../../content/deployment/recipes/cloudflare-pages.html">cloudflare-pages</a></li><li><a href="../../content/deployment/recipes/cloudflare-r2.html">cloudflare-r2</a></li><li><a href="../../content/deployment/recipes/fly-io.html">fly-io</a></li><li><a href="../../content/deployment/recipes/github-pages.html">github-pages</a></li><li><a href="../../content/deployment/recipes/netlify.html">netlify</a></li><li><a href="../../content/deployment/recipes/vercel.html">vercel</a></li></ul></details></li><li><a href="../../content/deployment/secrets-and-env-vars.html">secrets-and-env-vars</a></li></ul></li><li><span class="nav-section">User Guide</span><ul><li><details><summary class="nav-group"><a href="../../docs/userguide/cli.html">cli</a></summary><ul><li><a href="../../docs/userguide/cli/build.html">build</a></li><li><a href="../../docs/userguide/cli/bundle.html">bundle</a></li><li><a href="../../docs/userguide/cli/dev.html">dev</a></li><li><a href="../../docs/userguide/cli/init.html">init</a></li><li><a href="../../docs/userguide/cli/servers.html">servers</a></li><li><a href="../../docs/userguide/cli/stop.html">stop</a></li><li><a href="../../docs/userguide/cli/update.html">update</a></li><li><a href="../../docs/userguide/cli/version.html">version</a></li></ul></details></li><li><a href="../../docs/userguide/sharing.html">sharing</a></li></ul></li><li><span class="nav-section">Decisions</span><ul><li><a href="../../docs/decisions/ADR-0001-minimalist-site-code.html">ADR-0001-minimalist-site-code</a></li><li><a href="../../docs/decisions/ADR-0002-ai-accessibility.html">ADR-0002-ai-accessibility</a></li><li><a href="../../docs/decisions/ADR-0003-single-file-bundle.html">ADR-0003-single-file-bundle</a></li><li><a href="../../docs/decisions/ADR-0004-bun-runtime.html">ADR-0004-bun-runtime</a></li><li><a href="../../docs/decisions/ADR-0005-plugin-contract-and-distribution.html">ADR-0005-plugin-contract-and-distribution</a></li><li><a href="../../docs/decisions/DDR-0001-viewport-locked-layout.html">DDR-0001-viewport-locked-layout</a></li><li><a href="../../docs/decisions/DDR-0002-theme-system.html" class="active">DDR-0002-theme-system</a></li><li><a href="../../docs/decisions/DDR-0003-bounded-logo-slot.html">DDR-0003-bounded-logo-slot</a></li><li><a href="../../docs/decisions/DDR-0004-slides-rendering-model.html">DDR-0004-slides-rendering-model</a></li><li><a href="../../docs/decisions/DDR-0005-deterministic-layout-boundary.html">DDR-0005-deterministic-layout-boundary</a></li></ul></li><li><a href="../../schemas.html" class="nav-section">Schemas</a><ul><li><a href="../../schemas/plugin-registry.schema.html">plugin-registry.schema</a></li><li><a href="../../schemas/plugin-schemas-notes.html">plugin-schemas-notes</a></li><li><a href="../../schemas/plugin.schema.html">plugin.schema</a></li><li><a href="../../schemas/plugins.schema.html">plugins.schema</a></li><li><details><summary class="nav-group">v0</summary><ul><li><a href="../../schemas/v0/common.schema.html">common.schema</a></li><li><a href="../../schemas/v0/plugin-registry.schema.html">plugin-registry.schema</a></li><li><a href="../../schemas/v0/plugin.schema.html">plugin.schema</a></li><li><a href="../../schemas/v0/plugins.schema.html">plugins.schema</a></li><li><a href="../../schemas/v0/site.schema.html">site.schema</a></li><li><a href="../../schemas/v0/theme.schema.html">theme.schema</a></li></ul></details></li></ul></li></ul>
134
+ </div>
135
+ </nav>
136
+ <main class="content">
137
+ <article class="prose">
138
+ <nav class="breadcrumbs"><a href="../../docs/userguide/cli/build.html">Docs</a><span class="separator">›</span><a href="../../docs/decisions/ADR-0001-minimalist-site-code.html">Decisions</a><span class="separator">›</span><span>DDR-0002-theme-system</span></nav>
139
+
140
+ <h1 id="ddr-0002-two-layer-theme-system">DDR-0002: Two-Layer Theme System</h1>
141
+ <h2 id="status">Status</h2>
142
+ <p>Accepted</p>
143
+ <h2 id="context">Context</h2>
144
+ <p>Kitfly needs a theming system that lets users customize colors and typography without editing CSS. At the same time, the shipped CSS must render correctly even without a theme file — cold-start works must work. The system must support light/dark modes, syntax highlighting themes, and user-supplied theme files alongside bundled presets.</p>
145
+ <h2 id="decision">Decision</h2>
146
+ <p>Adopt a <strong>two-layer theming model</strong> where a YAML configuration layer defines semantic design tokens and a CSS layer consumes them as custom properties, with hardcoded fallbacks.</p>
147
+ <h3 id="layer-1-themeyaml-css-variables">Layer 1: theme.yaml → CSS Variables</h3>
148
+ <p><code>theme.yaml</code> defines semantic color tokens and typography settings:</p>
149
+ <pre><code class="language-yaml">colors:
150
+ light:
151
+ background: &quot;#ffffff&quot;
152
+ surface: &quot;#f5f7f8&quot;
153
+ text: &quot;#374151&quot;
154
+ heading: &quot;#152F46&quot;
155
+ primary: &quot;#007182&quot;
156
+ accent: &quot;#D17059&quot;
157
+ border: &quot;#e5e7eb&quot;
158
+ dark:
159
+ background: &quot;#0d1117&quot;
160
+ surface: &quot;#152F46&quot;
161
+ # ...
162
+
163
+ typography:
164
+ body: &quot;system&quot;
165
+ headings: &quot;system&quot;
166
+ code: &quot;mono&quot;
167
+ baseSize: &quot;16px&quot;
168
+ scale: &quot;1.25&quot;
169
+
170
+ code:
171
+ light: &quot;default&quot;
172
+ dark: &quot;okaidia&quot;
173
+ </code></pre>
174
+ <p><code>src/theme.ts</code> loads this file, deep-merges with <code>DEFAULT_THEME</code>, and calls <code>generateThemeCSS()</code> to produce a <code>&lt;style id=&quot;kitfly-theme&quot;&gt;</code> block that sets CSS custom properties on <code>:root</code>.</p>
175
+ <h3 id="layer-2-stylescss-fallbacks">Layer 2: styles.css Fallbacks</h3>
176
+ <p><code>src/site/styles.css</code> contains hardcoded fallback values in its <code>:root</code> block:</p>
177
+ <pre><code class="language-css">:root {
178
+ --color-bg: #ffffff;
179
+ --color-bg-sidebar: #f8f9fa;
180
+ --color-text: #1a1a1a;
181
+ /* ... */
182
+ }
183
+ </code></pre>
184
+ <p>These fallbacks ensure the site renders correctly if <code>theme.yaml</code> is missing or <code>generateThemeCSS()</code> is not injected. When the theme <code>&lt;style&gt;</code> block is present, it overrides the fallbacks because it appears later in the document <code>&lt;head&gt;</code>.</p>
185
+ <h3 id="token-mapping">Token Mapping</h3>
186
+ <table>
187
+ <thead>
188
+ <tr>
189
+ <th>theme.yaml token</th>
190
+ <th>CSS variable</th>
191
+ <th>CSS usage</th>
192
+ </tr>
193
+ </thead>
194
+ <tbody><tr>
195
+ <td><code>colors.light.background</code></td>
196
+ <td><code>--color-bg</code></td>
197
+ <td>Page background</td>
198
+ </tr>
199
+ <tr>
200
+ <td><code>colors.light.surface</code></td>
201
+ <td><code>--color-bg-sidebar</code></td>
202
+ <td>Sidebar, code block backgrounds</td>
203
+ </tr>
204
+ <tr>
205
+ <td><code>colors.light.text</code></td>
206
+ <td><code>--color-text</code></td>
207
+ <td>Body text</td>
208
+ </tr>
209
+ <tr>
210
+ <td><code>colors.light.textMuted</code></td>
211
+ <td><code>--color-text-muted</code></td>
212
+ <td>Secondary text, metadata</td>
213
+ </tr>
214
+ <tr>
215
+ <td><code>colors.light.heading</code></td>
216
+ <td><code>--color-accent</code></td>
217
+ <td>Headings, logo color</td>
218
+ </tr>
219
+ <tr>
220
+ <td><code>colors.light.primary</code></td>
221
+ <td><code>--color-link</code></td>
222
+ <td>Links, active nav</td>
223
+ </tr>
224
+ <tr>
225
+ <td><code>colors.light.primaryHover</code></td>
226
+ <td><code>--color-link-hover</code></td>
227
+ <td>Link hover state</td>
228
+ </tr>
229
+ <tr>
230
+ <td><code>colors.light.border</code></td>
231
+ <td><code>--color-border</code></td>
232
+ <td>Borders, dividers</td>
233
+ </tr>
234
+ </tbody></table>
235
+ <p>Dark mode uses the same mapping under <code>@media (prefers-color-scheme: dark)</code> and <code>[data-theme=&quot;dark&quot;]</code>.</p>
236
+ <h3 id="dark-mode-strategy">Dark Mode Strategy</h3>
237
+ <p>Three selectors handle dark mode:</p>
238
+ <ol>
239
+ <li><code>@media (prefers-color-scheme: dark)</code> — OS-level automatic</li>
240
+ <li><code>[data-theme=&quot;dark&quot;]</code> — explicit user toggle</li>
241
+ <li><code>:root:not([data-theme=&quot;light&quot;])</code> — prevents OS dark overriding an explicit light choice</li>
242
+ </ol>
243
+ <h3 id="theme-presets">Theme Presets</h3>
244
+ <p>Bundled themes in <code>themes/</code> (<code>github.yaml</code>, <code>paper.yaml</code>, <code>terminal.yaml</code>) can be copied and customized. Users place their <code>theme.yaml</code> in the project root alongside <code>site.yaml</code>.</p>
245
+ <h2 id="consequences">Consequences</h2>
246
+ <h3 id="positive">Positive</h3>
247
+ <ul>
248
+ <li>Users customize appearance via YAML without touching CSS</li>
249
+ <li>CSS works standalone — no build step required to see something</li>
250
+ <li>Dark mode works automatically via OS preference with manual override</li>
251
+ <li>Theme presets provide starting points for customization</li>
252
+ <li>Syntax highlighting themes are decoupled (Prism CDN URLs per light/dark)</li>
253
+ </ul>
254
+ <h3 id="negative">Negative</h3>
255
+ <ul>
256
+ <li>CSS fallback values can drift from <code>DEFAULT_THEME</code> if one is updated without the other (mitigated: both live in the codebase and tests can catch drift)</li>
257
+ <li>Token mapping is implicit — <code>surface</code> → <code>--color-bg-sidebar</code> isn&#39;t obvious without reading theme.ts</li>
258
+ </ul>
259
+ <h3 id="neutral">Neutral</h3>
260
+ <ul>
261
+ <li>Typography uses named presets (<code>system</code>, <code>serif</code>, <code>readable</code>) rather than raw font stacks — simple but limits advanced control</li>
262
+ </ul>
263
+ <h2 id="alternatives-considered">Alternatives Considered</h2>
264
+ <h3 id="css-only-theming-no-yaml">CSS-only theming (no YAML)</h3>
265
+ <p>Users edit CSS directly. Rejected because YAML is more accessible for non-developers and integrates with the existing <code>site.yaml</code> configuration model.</p>
266
+ <h3 id="css-modules-postcss-pipeline">CSS Modules / PostCSS pipeline</h3>
267
+ <p>Build-time CSS transformation. Rejected because it contradicts ADR-0001 (minimalist site code) and adds build dependencies.</p>
268
+ <h3 id="single-source-of-truth-no-css-fallbacks">Single source of truth (no CSS fallbacks)</h3>
269
+ <p>Only generate CSS from theme.ts, no hardcoded <code>:root</code>. Rejected because it breaks cold-start: opening <code>styles.css</code> directly or loading before JS runs would show unstyled content.</p>
270
+
271
+ </article>
272
+ <aside class="toc"><span class="toc-title">On this page</span><ul><li><a href="#status">Status</a></li><li><a href="#context">Context</a></li><li><a href="#decision">Decision</a></li><li class="toc-h3"><a href="#layer-1-themeyaml-css-variables">Layer 1: theme.yaml → CSS Variables</a></li><li class="toc-h3"><a href="#layer-2-stylescss-fallbacks">Layer 2: styles.css Fallbacks</a></li><li class="toc-h3"><a href="#token-mapping">Token Mapping</a></li><li class="toc-h3"><a href="#dark-mode-strategy">Dark Mode Strategy</a></li><li class="toc-h3"><a href="#theme-presets">Theme Presets</a></li><li><a href="#consequences">Consequences</a></li><li class="toc-h3"><a href="#positive">Positive</a></li><li class="toc-h3"><a href="#negative">Negative</a></li><li class="toc-h3"><a href="#neutral">Neutral</a></li><li><a href="#alternatives-considered">Alternatives Considered</a></li><li class="toc-h3"><a href="#css-only-theming-no-yaml">CSS-only theming (no YAML)</a></li><li class="toc-h3"><a href="#css-modules-postcss-pipeline">CSS Modules / PostCSS pipeline</a></li><li class="toc-h3"><a href="#single-source-of-truth-no-css-fallbacks">Single source of truth (no CSS fallbacks)</a></li></ul></aside>
273
+ </main>
274
+ </div>
275
+
276
+ <footer class="site-footer">
277
+ <div class="footer-content">
278
+ <div class="footer-left">
279
+ <span class="footer-version">v0.2.0</span>
280
+ <span class="footer-separator">·</span>
281
+ <span class="footer-commit" title="Commit: 33ccd68">Published 2026-02-15</span>
282
+ </div>
283
+ <div class="footer-center">
284
+ <span class="footer-copyright"><a href="https://3leaps.net" class="footer-link">© 2026 3 Leaps, LLC</a></span>
285
+ <span class="footer-separator">·</span><a href="/" class="footer-link">Kitfly</a>
286
+ </div>
287
+ <div class="footer-right">
288
+ <a href="https://kitfly.dev" class="footer-link">Built with Kitfly</a>
289
+ </div>
290
+ </div>
291
+ </footer>
292
+ <!-- Syntax highlighting - Prism.js -->
293
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js"></script>
294
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js"></script>
295
+ <!-- Mermaid diagram support -->
296
+ <script type="module">
297
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
298
+
299
+ function getMermaidTheme() {
300
+ const theme = document.documentElement.getAttribute('data-theme');
301
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
302
+ const isDark = theme === 'dark' || (!theme && prefersDark);
303
+ return isDark ? 'dark' : 'neutral';
304
+ }
305
+
306
+ mermaid.initialize({
307
+ startOnLoad: true,
308
+ theme: getMermaidTheme()
309
+ });
310
+
311
+ // Re-render mermaid diagrams when theme changes
312
+ window.reinitMermaid = async function() {
313
+ mermaid.initialize({ startOnLoad: false, theme: getMermaidTheme() });
314
+ const diagrams = document.querySelectorAll('.mermaid');
315
+ for (const el of diagrams) {
316
+ const code = el.getAttribute('data-mermaid-source');
317
+ if (code) {
318
+ el.innerHTML = code;
319
+ el.removeAttribute('data-processed');
320
+ }
321
+ }
322
+ await mermaid.run({ nodes: diagrams });
323
+ };
324
+ </script>
325
+
326
+ <script>
327
+ function toggleTheme() {
328
+ const html = document.documentElement;
329
+ const current = html.getAttribute('data-theme');
330
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
331
+
332
+ let next;
333
+ if (current === 'dark') {
334
+ next = 'light';
335
+ } else if (current === 'light') {
336
+ next = 'dark';
337
+ } else {
338
+ // No explicit theme set, toggle from system preference
339
+ next = prefersDark ? 'light' : 'dark';
340
+ }
341
+
342
+ html.setAttribute('data-theme', next);
343
+ localStorage.setItem('theme', next);
344
+
345
+ // Switch Prism theme
346
+ const prismLight = document.getElementById('prism-light');
347
+ const prismDark = document.getElementById('prism-dark');
348
+ if (next === 'dark') {
349
+ prismLight?.setAttribute('disabled', '');
350
+ prismDark?.removeAttribute('disabled');
351
+ } else {
352
+ prismLight?.removeAttribute('disabled');
353
+ prismDark?.setAttribute('disabled', '');
354
+ }
355
+
356
+ // Re-render mermaid diagrams with new theme
357
+ if (window.reinitMermaid) {
358
+ window.reinitMermaid();
359
+ }
360
+ }
361
+
362
+ // Slides mode hash routing
363
+ (function initSlidesMode() {
364
+ const shell = document.querySelector('.slides-shell');
365
+ if (!shell) return;
366
+
367
+ const slides = Array.from(document.querySelectorAll('.slide'));
368
+ if (!slides.length) return;
369
+
370
+ const prevBtn = document.querySelector('.slide-prev');
371
+ const nextBtn = document.querySelector('.slide-next');
372
+ const counter = document.querySelector('.slide-counter');
373
+ const progressBar = document.querySelector('.slide-progress-bar');
374
+ const navLinks = Array.from(document.querySelectorAll('.sidebar-nav a[href^="#slide-"]'));
375
+ let current = 0;
376
+
377
+ function setActive(n) {
378
+ current = Math.max(0, Math.min(n, slides.length - 1));
379
+ slides.forEach((slide, idx) => slide.classList.toggle('active', idx === current));
380
+ navLinks.forEach((link) => {
381
+ const active = link.getAttribute('href') === '#' + slides[current].id;
382
+ link.classList.toggle('active', active);
383
+ });
384
+ if (counter) counter.textContent = (current + 1) + ' / ' + slides.length;
385
+ if (progressBar) progressBar.style.width = (((current + 1) / slides.length) * 100) + '%';
386
+ if (prevBtn) prevBtn.disabled = current === 0;
387
+ if (nextBtn) nextBtn.disabled = current === slides.length - 1;
388
+ history.replaceState(null, '', '#' + slides[current].id);
389
+ }
390
+
391
+ function setFromHash() {
392
+ const hash = window.location.hash || '';
393
+ const idx = slides.findIndex((s) => '#' + s.id === hash);
394
+ if (idx >= 0) setActive(idx);
395
+ else setActive(0);
396
+ }
397
+
398
+ prevBtn?.addEventListener('click', () => setActive(current - 1));
399
+ nextBtn?.addEventListener('click', () => setActive(current + 1));
400
+
401
+ document.addEventListener('keydown', (e) => {
402
+ if (e.key === 'ArrowRight' || e.key === ' ') {
403
+ e.preventDefault();
404
+ setActive(current + 1);
405
+ } else if (e.key === 'ArrowLeft') {
406
+ e.preventDefault();
407
+ setActive(current - 1);
408
+ } else if (e.key === 'Home') {
409
+ e.preventDefault();
410
+ setActive(0);
411
+ } else if (e.key === 'End') {
412
+ e.preventDefault();
413
+ setActive(slides.length - 1);
414
+ }
415
+ });
416
+
417
+ window.addEventListener('hashchange', setFromHash);
418
+ setFromHash();
419
+ })();
420
+
421
+ // Copy code button
422
+ document.querySelectorAll('.prose pre code').forEach(block => {
423
+ const button = document.createElement('button');
424
+ button.className = 'copy-button';
425
+ button.textContent = 'Copy';
426
+ button.onclick = async () => {
427
+ await navigator.clipboard.writeText(block.textContent);
428
+ button.textContent = 'Copied!';
429
+ setTimeout(() => button.textContent = 'Copy', 2000);
430
+ };
431
+ block.parentElement.appendChild(button);
432
+ });
433
+
434
+ // Mobile nav toggle
435
+ function toggleNav() {
436
+ document.querySelector('.sidebar').classList.toggle('open');
437
+ }
438
+
439
+ // Close nav when clicking outside on mobile
440
+ document.addEventListener('click', (e) => {
441
+ const sidebar = document.querySelector('.sidebar');
442
+ const toggle = document.querySelector('.nav-toggle');
443
+ if (sidebar.classList.contains('open') &&
444
+ !sidebar.contains(e.target) &&
445
+ !toggle.contains(e.target)) {
446
+ sidebar.classList.remove('open');
447
+ }
448
+ });
449
+ </script>
450
+
451
+ </body>
452
+ </html>