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,459 @@
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-0001: Viewport-Locked Layout with Fixed Footer Ribbon - 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" class="active">DDR-0001-viewport-locked-layout</a></li><li><a href="../../docs/decisions/DDR-0002-theme-system.html">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-0001-viewport-locked-layout</span></nav>
139
+
140
+ <h1 id="ddr-0001-viewport-locked-layout-with-fixed-footer-ribbon">DDR-0001: Viewport-Locked Layout with Fixed Footer Ribbon</h1>
141
+ <h2 id="status">Status</h2>
142
+ <p>Accepted</p>
143
+ <h2 id="context">Context</h2>
144
+ <p>Kitfly sites use a three-column layout: sidebar navigation, main content, and a table-of-contents panel. The original footer was appended to the content flow with <code>margin-left</code> to offset past the sidebar, which caused several problems:</p>
145
+ <ul>
146
+ <li>Footer only covered the content pane, not the full viewport width</li>
147
+ <li>Sidebar navigation extended below the footer, breaking visual hierarchy</li>
148
+ <li>Footer position depended on content length — short pages had it mid-screen, long pages required scrolling to find it</li>
149
+ <li>On mobile the footer needed separate margin overrides per breakpoint</li>
150
+ </ul>
151
+ <h2 id="decision">Decision</h2>
152
+ <p>Adopt a <strong>viewport-locked layout</strong> where fixed chrome (footer ribbon, and mobile header) occupies known height at viewport edges, and the middle band (sidebar + content + TOC) fills the remaining space between them.</p>
153
+ <h3 id="layout-model">Layout Model</h3>
154
+ <pre><code>┌──────────────────────────────────────────────────┐
155
+ │ (mobile header — 768px and below only) │
156
+ ├────────────┬─────────────────────────┬───────────┤
157
+ │ │ │ │
158
+ │ SIDEBAR │ CONTENT │ TOC │
159
+ │ fixed │ scrollable │ fixed │
160
+ │ left │ center │ right │
161
+ │ 280px │ flex: 1 │ 200px │
162
+ │ │ │ │
163
+ │ top: 0 │ margin-left: sidebar │ top: 6r │
164
+ │ bottom: │ padding-bottom: │ │
165
+ │ footer │ footer + 1rem │ │
166
+ │ │ │ │
167
+ ├────────────┴─────────────────────────┴───────────┤
168
+ │ FOOTER RIBBON │
169
+ │ position: fixed; bottom: 0; left: 0; right: 0 │
170
+ │ height: var(--footer-height) z-index: 300 │
171
+ │ full-width — spans all columns │
172
+ └──────────────────────────────────────────────────┘
173
+ </code></pre>
174
+ <h3 id="css-variables">CSS Variables</h3>
175
+ <table>
176
+ <thead>
177
+ <tr>
178
+ <th>Variable</th>
179
+ <th>Value</th>
180
+ <th>Purpose</th>
181
+ </tr>
182
+ </thead>
183
+ <tbody><tr>
184
+ <td><code>--sidebar-width</code></td>
185
+ <td><code>280px</code></td>
186
+ <td>Sidebar column width</td>
187
+ </tr>
188
+ <tr>
189
+ <td><code>--footer-height</code></td>
190
+ <td><code>2.25rem</code></td>
191
+ <td>Footer ribbon height</td>
192
+ </tr>
193
+ </tbody></table>
194
+ <h3 id="z-index-stack">Z-Index Stack</h3>
195
+ <table>
196
+ <thead>
197
+ <tr>
198
+ <th>Layer</th>
199
+ <th>Z-Index</th>
200
+ <th>Element</th>
201
+ </tr>
202
+ </thead>
203
+ <tbody><tr>
204
+ <td>Footer ribbon</td>
205
+ <td>300</td>
206
+ <td><code>.site-footer</code></td>
207
+ </tr>
208
+ <tr>
209
+ <td>Mobile header</td>
210
+ <td>200</td>
211
+ <td><code>.mobile-header</code></td>
212
+ </tr>
213
+ <tr>
214
+ <td>Mobile sidebar</td>
215
+ <td>100</td>
216
+ <td><code>.sidebar</code> (mobile overlay)</td>
217
+ </tr>
218
+ </tbody></table>
219
+ <h3 id="key-rules">Key Rules</h3>
220
+ <ol>
221
+ <li><strong>Footer is viewport-fixed</strong>, not content-appended. It uses <code>position: fixed; bottom: 0</code> and spans full width.</li>
222
+ <li><strong>Sidebar stops above footer</strong> via <code>bottom: var(--footer-height)</code> — no overlap, sidebar scrolls independently within its band.</li>
223
+ <li><strong>Content has bottom padding</strong> of <code>calc(var(--footer-height) + 1rem)</code> so the last content line is never hidden behind the footer.</li>
224
+ <li><strong>Layout min-height</strong> is <code>calc(100vh - var(--footer-height))</code> to prevent the document from extending behind the footer.</li>
225
+ <li><strong>Mobile breakpoints</strong> inherit the same <code>--footer-height</code> variable — no per-breakpoint footer overrides needed.</li>
226
+ <li><strong>Print styles</strong> hide the footer entirely (<code>.site-footer { display: none !important }</code>).</li>
227
+ </ol>
228
+ <h3 id="responsive-behavior">Responsive Behavior</h3>
229
+ <table>
230
+ <thead>
231
+ <tr>
232
+ <th>Breakpoint</th>
233
+ <th>Sidebar</th>
234
+ <th>TOC</th>
235
+ <th>Footer</th>
236
+ </tr>
237
+ </thead>
238
+ <tbody><tr>
239
+ <td>&gt; 1200px</td>
240
+ <td>Fixed left</td>
241
+ <td>Fixed right</td>
242
+ <td>Fixed bottom, full-width</td>
243
+ </tr>
244
+ <tr>
245
+ <td>769–1200px</td>
246
+ <td>Fixed left (240px)</td>
247
+ <td>Hidden</td>
248
+ <td>Fixed bottom, full-width</td>
249
+ </tr>
250
+ <tr>
251
+ <td>≤ 768px</td>
252
+ <td>Overlay (toggle)</td>
253
+ <td>Hidden</td>
254
+ <td>Fixed bottom, full-width</td>
255
+ </tr>
256
+ </tbody></table>
257
+ <p>The footer ribbon is consistent across all breakpoints — it never changes position or width.</p>
258
+ <h2 id="consequences">Consequences</h2>
259
+ <h3 id="positive">Positive</h3>
260
+ <ul>
261
+ <li>Footer is always visible as a status/provenance ribbon without scrolling</li>
262
+ <li>Sidebar navigation never extends below footer</li>
263
+ <li>Single <code>--footer-height</code> variable controls all spacing — easy to adjust</li>
264
+ <li>No per-breakpoint footer margin overrides</li>
265
+ <li>Clean visual hierarchy: chrome frames content</li>
266
+ </ul>
267
+ <h3 id="negative">Negative</h3>
268
+ <ul>
269
+ <li>Content needs <code>padding-bottom</code> to avoid being hidden — if <code>--footer-height</code> changes, padding must match (mitigated by using the CSS variable)</li>
270
+ <li>Fixed footer consumes ~36px of viewport permanently — acceptable for the information density it provides (version, date, copyright, brand link)</li>
271
+ </ul>
272
+ <h2 id="alternatives-considered">Alternatives Considered</h2>
273
+ <h3 id="sticky-footer-content-appended">Sticky footer (content-appended)</h3>
274
+ <p>The original approach. Footer stays in document flow but sticks to bottom on short pages. Rejected because it doesn&#39;t span the sidebar and creates layout inconsistencies across page lengths.</p>
275
+ <h3 id="css-grid-viewport-layout">CSS Grid viewport layout</h3>
276
+ <p>Use <code>grid-template-rows: 1fr auto</code> on the body. Would work but requires restructuring the HTML template (moving sidebar inside a grid container). More invasive than necessary for the current fix.</p>
277
+
278
+ </article>
279
+ <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="#layout-model">Layout Model</a></li><li class="toc-h3"><a href="#css-variables">CSS Variables</a></li><li class="toc-h3"><a href="#z-index-stack">Z-Index Stack</a></li><li class="toc-h3"><a href="#key-rules">Key Rules</a></li><li class="toc-h3"><a href="#responsive-behavior">Responsive Behavior</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><a href="#alternatives-considered">Alternatives Considered</a></li><li class="toc-h3"><a href="#sticky-footer-content-appended">Sticky footer (content-appended)</a></li><li class="toc-h3"><a href="#css-grid-viewport-layout">CSS Grid viewport layout</a></li></ul></aside>
280
+ </main>
281
+ </div>
282
+
283
+ <footer class="site-footer">
284
+ <div class="footer-content">
285
+ <div class="footer-left">
286
+ <span class="footer-version">v0.2.0</span>
287
+ <span class="footer-separator">·</span>
288
+ <span class="footer-commit" title="Commit: 33ccd68">Published 2026-02-15</span>
289
+ </div>
290
+ <div class="footer-center">
291
+ <span class="footer-copyright"><a href="https://3leaps.net" class="footer-link">© 2026 3 Leaps, LLC</a></span>
292
+ <span class="footer-separator">·</span><a href="/" class="footer-link">Kitfly</a>
293
+ </div>
294
+ <div class="footer-right">
295
+ <a href="https://kitfly.dev" class="footer-link">Built with Kitfly</a>
296
+ </div>
297
+ </div>
298
+ </footer>
299
+ <!-- Syntax highlighting - Prism.js -->
300
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js"></script>
301
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js"></script>
302
+ <!-- Mermaid diagram support -->
303
+ <script type="module">
304
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
305
+
306
+ function getMermaidTheme() {
307
+ const theme = document.documentElement.getAttribute('data-theme');
308
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
309
+ const isDark = theme === 'dark' || (!theme && prefersDark);
310
+ return isDark ? 'dark' : 'neutral';
311
+ }
312
+
313
+ mermaid.initialize({
314
+ startOnLoad: true,
315
+ theme: getMermaidTheme()
316
+ });
317
+
318
+ // Re-render mermaid diagrams when theme changes
319
+ window.reinitMermaid = async function() {
320
+ mermaid.initialize({ startOnLoad: false, theme: getMermaidTheme() });
321
+ const diagrams = document.querySelectorAll('.mermaid');
322
+ for (const el of diagrams) {
323
+ const code = el.getAttribute('data-mermaid-source');
324
+ if (code) {
325
+ el.innerHTML = code;
326
+ el.removeAttribute('data-processed');
327
+ }
328
+ }
329
+ await mermaid.run({ nodes: diagrams });
330
+ };
331
+ </script>
332
+
333
+ <script>
334
+ function toggleTheme() {
335
+ const html = document.documentElement;
336
+ const current = html.getAttribute('data-theme');
337
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
338
+
339
+ let next;
340
+ if (current === 'dark') {
341
+ next = 'light';
342
+ } else if (current === 'light') {
343
+ next = 'dark';
344
+ } else {
345
+ // No explicit theme set, toggle from system preference
346
+ next = prefersDark ? 'light' : 'dark';
347
+ }
348
+
349
+ html.setAttribute('data-theme', next);
350
+ localStorage.setItem('theme', next);
351
+
352
+ // Switch Prism theme
353
+ const prismLight = document.getElementById('prism-light');
354
+ const prismDark = document.getElementById('prism-dark');
355
+ if (next === 'dark') {
356
+ prismLight?.setAttribute('disabled', '');
357
+ prismDark?.removeAttribute('disabled');
358
+ } else {
359
+ prismLight?.removeAttribute('disabled');
360
+ prismDark?.setAttribute('disabled', '');
361
+ }
362
+
363
+ // Re-render mermaid diagrams with new theme
364
+ if (window.reinitMermaid) {
365
+ window.reinitMermaid();
366
+ }
367
+ }
368
+
369
+ // Slides mode hash routing
370
+ (function initSlidesMode() {
371
+ const shell = document.querySelector('.slides-shell');
372
+ if (!shell) return;
373
+
374
+ const slides = Array.from(document.querySelectorAll('.slide'));
375
+ if (!slides.length) return;
376
+
377
+ const prevBtn = document.querySelector('.slide-prev');
378
+ const nextBtn = document.querySelector('.slide-next');
379
+ const counter = document.querySelector('.slide-counter');
380
+ const progressBar = document.querySelector('.slide-progress-bar');
381
+ const navLinks = Array.from(document.querySelectorAll('.sidebar-nav a[href^="#slide-"]'));
382
+ let current = 0;
383
+
384
+ function setActive(n) {
385
+ current = Math.max(0, Math.min(n, slides.length - 1));
386
+ slides.forEach((slide, idx) => slide.classList.toggle('active', idx === current));
387
+ navLinks.forEach((link) => {
388
+ const active = link.getAttribute('href') === '#' + slides[current].id;
389
+ link.classList.toggle('active', active);
390
+ });
391
+ if (counter) counter.textContent = (current + 1) + ' / ' + slides.length;
392
+ if (progressBar) progressBar.style.width = (((current + 1) / slides.length) * 100) + '%';
393
+ if (prevBtn) prevBtn.disabled = current === 0;
394
+ if (nextBtn) nextBtn.disabled = current === slides.length - 1;
395
+ history.replaceState(null, '', '#' + slides[current].id);
396
+ }
397
+
398
+ function setFromHash() {
399
+ const hash = window.location.hash || '';
400
+ const idx = slides.findIndex((s) => '#' + s.id === hash);
401
+ if (idx >= 0) setActive(idx);
402
+ else setActive(0);
403
+ }
404
+
405
+ prevBtn?.addEventListener('click', () => setActive(current - 1));
406
+ nextBtn?.addEventListener('click', () => setActive(current + 1));
407
+
408
+ document.addEventListener('keydown', (e) => {
409
+ if (e.key === 'ArrowRight' || e.key === ' ') {
410
+ e.preventDefault();
411
+ setActive(current + 1);
412
+ } else if (e.key === 'ArrowLeft') {
413
+ e.preventDefault();
414
+ setActive(current - 1);
415
+ } else if (e.key === 'Home') {
416
+ e.preventDefault();
417
+ setActive(0);
418
+ } else if (e.key === 'End') {
419
+ e.preventDefault();
420
+ setActive(slides.length - 1);
421
+ }
422
+ });
423
+
424
+ window.addEventListener('hashchange', setFromHash);
425
+ setFromHash();
426
+ })();
427
+
428
+ // Copy code button
429
+ document.querySelectorAll('.prose pre code').forEach(block => {
430
+ const button = document.createElement('button');
431
+ button.className = 'copy-button';
432
+ button.textContent = 'Copy';
433
+ button.onclick = async () => {
434
+ await navigator.clipboard.writeText(block.textContent);
435
+ button.textContent = 'Copied!';
436
+ setTimeout(() => button.textContent = 'Copy', 2000);
437
+ };
438
+ block.parentElement.appendChild(button);
439
+ });
440
+
441
+ // Mobile nav toggle
442
+ function toggleNav() {
443
+ document.querySelector('.sidebar').classList.toggle('open');
444
+ }
445
+
446
+ // Close nav when clicking outside on mobile
447
+ document.addEventListener('click', (e) => {
448
+ const sidebar = document.querySelector('.sidebar');
449
+ const toggle = document.querySelector('.nav-toggle');
450
+ if (sidebar.classList.contains('open') &&
451
+ !sidebar.contains(e.target) &&
452
+ !toggle.contains(e.target)) {
453
+ sidebar.classList.remove('open');
454
+ }
455
+ });
456
+ </script>
457
+
458
+ </body>
459
+ </html>