kitfly 0.2.0 → 0.2.3

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 (126) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +25 -10
  3. package/VERSION +1 -1
  4. package/dist/_raw/content/guide/branding.md +146 -0
  5. package/dist/_raw/content/guide/data-driven-content.md +204 -0
  6. package/dist/_raw/content/reference/configuration.md +145 -7
  7. package/dist/_raw/content/reference/environment-variables.md +26 -1
  8. package/dist/_raw/content/reference/glossary.md +25 -1
  9. package/dist/_raw/content/reference/key-concepts.md +30 -2
  10. package/dist/_raw/content/reference/plugins.md +14 -0
  11. package/dist/_raw/content/reference/slides-authoring-guidelines.md +129 -0
  12. package/dist/_raw/content/reference.md +1 -0
  13. package/dist/_raw/docs/decisions/ADR-0006-data-driven-content.md +350 -0
  14. package/dist/content/deployment/preflight.html +10 -6
  15. package/dist/content/deployment/recipes/aws-s3.html +10 -6
  16. package/dist/content/deployment/recipes/cloudflare-pages.html +10 -6
  17. package/dist/content/deployment/recipes/cloudflare-r2.html +10 -6
  18. package/dist/content/deployment/recipes/fly-io.html +10 -6
  19. package/dist/content/deployment/recipes/github-pages.html +10 -6
  20. package/dist/content/deployment/recipes/netlify.html +10 -6
  21. package/dist/content/deployment/recipes/vercel.html +10 -6
  22. package/dist/content/deployment/secrets-and-env-vars.html +10 -6
  23. package/dist/content/deployment.html +10 -6
  24. package/dist/content/guide/approaches.html +10 -6
  25. package/dist/content/guide/branding.html +510 -0
  26. package/dist/content/guide/data-driven-content.html +543 -0
  27. package/dist/content/guide/features.html +10 -6
  28. package/dist/content/guide/getting-started.html +10 -6
  29. package/dist/content/guide/kitfly-overview.html +10 -6
  30. package/dist/content/reference/configuration.html +135 -9
  31. package/dist/content/reference/design-catalog.html +10 -6
  32. package/dist/content/reference/environment-variables.html +50 -8
  33. package/dist/content/reference/glossary.html +24 -8
  34. package/dist/content/reference/key-concepts.html +33 -9
  35. package/dist/content/reference/plugins.html +22 -7
  36. package/dist/content/reference/slides-authoring-guidelines.html +422 -0
  37. package/dist/content/reference/structure.html +10 -6
  38. package/dist/content/reference.html +11 -6
  39. package/dist/content/templates/crucible.html +10 -6
  40. package/dist/content/templates/handbook.html +10 -6
  41. package/dist/content/templates/minimal.html +10 -6
  42. package/dist/content/templates/overview.html +10 -6
  43. package/dist/content/templates/pipeline.html +10 -6
  44. package/dist/content/templates/productbook.html +10 -6
  45. package/dist/content/templates/runbook.html +10 -6
  46. package/dist/content/templates/servicebook.html +10 -6
  47. package/dist/content-index.json +38 -2
  48. package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +10 -6
  49. package/dist/docs/decisions/ADR-0002-ai-accessibility.html +10 -6
  50. package/dist/docs/decisions/ADR-0003-single-file-bundle.html +10 -6
  51. package/dist/docs/decisions/ADR-0004-bun-runtime.html +10 -6
  52. package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +10 -6
  53. package/dist/docs/decisions/ADR-0006-data-driven-content.html +752 -0
  54. package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +10 -6
  55. package/dist/docs/decisions/DDR-0002-theme-system.html +10 -6
  56. package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +10 -6
  57. package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +10 -6
  58. package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +10 -6
  59. package/dist/docs/userguide/cli/build.html +10 -6
  60. package/dist/docs/userguide/cli/bundle.html +10 -6
  61. package/dist/docs/userguide/cli/dev.html +10 -6
  62. package/dist/docs/userguide/cli/init.html +10 -6
  63. package/dist/docs/userguide/cli/servers.html +10 -6
  64. package/dist/docs/userguide/cli/stop.html +10 -6
  65. package/dist/docs/userguide/cli/update.html +10 -6
  66. package/dist/docs/userguide/cli/version.html +10 -6
  67. package/dist/docs/userguide/cli.html +10 -6
  68. package/dist/docs/userguide/sharing.html +10 -6
  69. package/dist/index.html +10 -6
  70. package/dist/llms.txt +3 -3
  71. package/dist/provenance.json +4 -4
  72. package/dist/schemas/plugin-registry.schema.html +10 -6
  73. package/dist/schemas/plugin-schemas-notes.html +10 -6
  74. package/dist/schemas/plugin.schema.html +10 -6
  75. package/dist/schemas/plugins.schema.html +10 -6
  76. package/dist/schemas/v0/common.schema.html +14 -10
  77. package/dist/schemas/v0/plugin-registry.schema.html +13 -9
  78. package/dist/schemas/v0/plugin.schema.html +13 -9
  79. package/dist/schemas/v0/plugins.schema.html +13 -9
  80. package/dist/schemas/v0/site.schema.html +67 -7
  81. package/dist/schemas/v0/theme.schema.html +21 -17
  82. package/dist/schemas.html +10 -6
  83. package/dist/styles.css +39 -4
  84. package/package.json +1 -1
  85. package/plugins-dist/latex-runtime.js +140 -0
  86. package/plugins-dist/latex.js +178 -0
  87. package/plugins-dist/slides-charts-lite-runtime.js +179 -0
  88. package/plugins-dist/slides-charts-lite.js +198 -0
  89. package/plugins-dist/slides-visuals.css +166 -0
  90. package/plugins-dist/slides-visuals.js +124 -33
  91. package/registry/plugins.yaml +30 -5
  92. package/schemas/v0/site.schema.json +56 -0
  93. package/scripts/build.ts +195 -70
  94. package/scripts/bundle.ts +122 -11
  95. package/scripts/dev.ts +345 -178
  96. package/src/__tests__/brief.test.ts +151 -0
  97. package/src/__tests__/build.test.ts +234 -4
  98. package/src/__tests__/bundle.test.ts +134 -0
  99. package/src/__tests__/dev-plugin-errors.test.ts +20 -0
  100. package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-branching-no-source.md +5 -0
  101. package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-converging-no-target.md +6 -0
  102. package/src/__tests__/fixtures/fences/slides-visuals/invalid/staircase-empty-steps.md +3 -0
  103. package/src/__tests__/fixtures/fences/slides-visuals/invalid/timeline-horizontal-no-events.md +2 -0
  104. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching-no-split.md +7 -0
  105. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching.md +8 -0
  106. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging-no-merge.md +7 -0
  107. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging.md +8 -0
  108. package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase-down.md +7 -0
  109. package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase.md +8 -0
  110. package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-horizontal.md +9 -0
  111. package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-vertical.md +10 -0
  112. package/src/__tests__/init.test.ts +51 -2
  113. package/src/__tests__/latex-runtime.bun.test.ts +35 -0
  114. package/src/__tests__/shared.test.ts +621 -1
  115. package/src/__tests__/slides-charts-lite-runtime.bun.test.ts +45 -0
  116. package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +33 -0
  117. package/src/cli.ts +11 -4
  118. package/src/commands/init.ts +1 -1
  119. package/src/shared.ts +761 -18
  120. package/src/site/styles.css +39 -4
  121. package/src/site/template.html +5 -2
  122. package/src/templates/brief.ts +486 -0
  123. package/src/templates/deck.ts +59 -0
  124. package/src/templates/driver.ts +46 -13
  125. package/src/templates/handbook.ts +32 -0
  126. package/src/templates/runbook.ts +32 -0
@@ -0,0 +1,140 @@
1
+ (() => {
2
+ function isCurrency(expr) {
3
+ return /^\d[\d,]*(?:\.\d+)?(?:\s*(?:to|-)\s*\d[\d,]*(?:\.\d+)?)?$/.test(expr.trim());
4
+ }
5
+
6
+ function shouldTreatAsLiteralInline(expr, text, closingIndex) {
7
+ if (/^\s|\s$/.test(expr) || /\n/.test(expr) || isCurrency(expr)) return true;
8
+ // Guard common currency range form "$5-$10" where expr becomes "5-".
9
+ if (/-\s*$/.test(expr)) {
10
+ const tail = text.slice(closingIndex + 1);
11
+ if (/^\d/.test(tail)) return true;
12
+ }
13
+ return false;
14
+ }
15
+
16
+ function findClosing(text, start, display) {
17
+ for (let i = start; i < text.length; i++) {
18
+ if (text[i] === "\\" && text[i + 1] === "$") {
19
+ i++;
20
+ continue;
21
+ }
22
+ if (display) {
23
+ if (text[i] === "$" && text[i + 1] === "$") return i;
24
+ } else if (text[i] === "$" && text[i - 1] !== "$" && text[i + 1] !== "$") {
25
+ return i;
26
+ }
27
+ }
28
+ return -1;
29
+ }
30
+
31
+ function splitMath(text) {
32
+ const out = [];
33
+ let buf = "";
34
+ let i = 0;
35
+
36
+ while (i < text.length) {
37
+ if (text[i] === "\\" && text[i + 1] === "$") {
38
+ buf += "$";
39
+ i += 2;
40
+ continue;
41
+ }
42
+ if (text[i] !== "$") {
43
+ buf += text[i++];
44
+ continue;
45
+ }
46
+
47
+ const display = text[i + 1] === "$";
48
+ const openLen = display ? 2 : 1;
49
+ const end = findClosing(text, i + openLen, display);
50
+ if (end < 0) {
51
+ buf += display ? "$$" : "$";
52
+ i += openLen;
53
+ continue;
54
+ }
55
+
56
+ const expr = text.slice(i + openLen, end);
57
+ if (!display && shouldTreatAsLiteralInline(expr, text, end)) {
58
+ buf += `$${expr}$`;
59
+ i = end + 1;
60
+ continue;
61
+ }
62
+
63
+ if (buf) out.push({ text: buf });
64
+ out.push({ math: expr, display });
65
+ buf = "";
66
+ i = end + openLen;
67
+ }
68
+
69
+ if (buf) out.push({ text: buf });
70
+ return out;
71
+ }
72
+
73
+ function renderFencedMath() {
74
+ document.querySelectorAll("pre > code.language-math").forEach((code) => {
75
+ const pre = code.parentElement;
76
+ if (!pre || pre.tagName !== "PRE") return;
77
+ const host = document.createElement("div");
78
+ host.className = "kitfly-katex-display";
79
+ window.katex.render(code.textContent || "", host, { displayMode: true, throwOnError: false });
80
+ pre.replaceWith(host);
81
+ });
82
+ }
83
+
84
+ function renderDelimitedMath() {
85
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
86
+ const nodes = [];
87
+ while (walker.nextNode()) {
88
+ const node = walker.currentNode;
89
+ const parent = node.parentElement;
90
+ if (!parent || parent.closest("pre, code, script, style, textarea, .katex")) continue;
91
+ if (!(node.textContent || "").includes("$")) continue;
92
+ nodes.push(node);
93
+ }
94
+
95
+ for (const node of nodes) {
96
+ const parts = splitMath(node.textContent || "");
97
+ if (!parts.some((p) => p.math)) continue;
98
+ const frag = document.createDocumentFragment();
99
+ for (const p of parts) {
100
+ if (p.text != null) {
101
+ frag.appendChild(document.createTextNode(p.text));
102
+ } else if (p.math != null) {
103
+ const host = document.createElement("span");
104
+ host.className = p.display ? "kitfly-katex-display" : "kitfly-katex-inline";
105
+ window.katex.render(p.math, host, { displayMode: Boolean(p.display), throwOnError: false });
106
+ frag.appendChild(host);
107
+ }
108
+ }
109
+ node.replaceWith(frag);
110
+ }
111
+ }
112
+
113
+ function apply() {
114
+ const styleId = "kitfly-latex-style";
115
+ if (!document.getElementById(styleId)) {
116
+ const style = document.createElement("style");
117
+ style.id = styleId;
118
+ style.textContent = ".kitfly-katex-display{display:block;text-align:center;margin:1em 0;overflow-x:auto}";
119
+ document.head.appendChild(style);
120
+ }
121
+
122
+ if (!window.katex?.render) {
123
+ console.warn("[kitfly:latex] KaTeX unavailable");
124
+ return;
125
+ }
126
+
127
+ renderFencedMath();
128
+ renderDelimitedMath();
129
+ }
130
+
131
+ if (typeof document === "undefined") {
132
+ globalThis.__kitflyLatexTest = { splitMath, isCurrency, shouldTreatAsLiteralInline };
133
+ return;
134
+ }
135
+ if (document.readyState === "loading") {
136
+ document.addEventListener("DOMContentLoaded", apply);
137
+ } else {
138
+ apply();
139
+ }
140
+ })();
@@ -0,0 +1,178 @@
1
+ (() => {
2
+ const KATEX_CSS_URL = "https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css";
3
+ const KATEX_CSS_SRI = "sha384-zh0CIslj+VczCZtlzBcjt5ppRcsAmDnRem7ESsYwWwg3m/OaJ2l4x7YBZl9Kxxib";
4
+ const KATEX_JS_URL = "https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js";
5
+ const KATEX_JS_SRI = "sha384-Rma6DA2IPUwhNxmrB/7S3Tno0YY7sFu9WSYMCuulLhIqYSGZ2gKCJWIqhBWqMQfh";
6
+
7
+ function isCurrency(expr) {
8
+ return /^\d[\d,]*(?:\.\d+)?(?:\s*(?:to|-)\s*\d[\d,]*(?:\.\d+)?)?$/.test(expr.trim());
9
+ }
10
+
11
+ function shouldTreatAsLiteralInline(expr, text, closingIndex) {
12
+ if (/^\s|\s$/.test(expr) || /\n/.test(expr) || isCurrency(expr)) return true;
13
+ // Guard common currency range form "$5-$10" where expr becomes "5-".
14
+ if (/-\s*$/.test(expr)) {
15
+ const tail = text.slice(closingIndex + 1);
16
+ if (/^\d/.test(tail)) return true;
17
+ }
18
+ return false;
19
+ }
20
+
21
+ function findClosing(text, start, display) {
22
+ for (let i = start; i < text.length; i++) {
23
+ if (text[i] === "\\" && text[i + 1] === "$") {
24
+ i++;
25
+ continue;
26
+ }
27
+ if (display) {
28
+ if (text[i] === "$" && text[i + 1] === "$") return i;
29
+ } else if (text[i] === "$" && text[i - 1] !== "$" && text[i + 1] !== "$") {
30
+ return i;
31
+ }
32
+ }
33
+ return -1;
34
+ }
35
+
36
+ function splitMath(text) {
37
+ const out = [];
38
+ let buf = "";
39
+ let i = 0;
40
+
41
+ while (i < text.length) {
42
+ if (text[i] === "\\" && text[i + 1] === "$") {
43
+ buf += "$";
44
+ i += 2;
45
+ continue;
46
+ }
47
+ if (text[i] !== "$") {
48
+ buf += text[i++];
49
+ continue;
50
+ }
51
+
52
+ const display = text[i + 1] === "$";
53
+ const openLen = display ? 2 : 1;
54
+ const end = findClosing(text, i + openLen, display);
55
+ if (end < 0) {
56
+ buf += display ? "$$" : "$";
57
+ i += openLen;
58
+ continue;
59
+ }
60
+
61
+ const expr = text.slice(i + openLen, end);
62
+ if (!display && shouldTreatAsLiteralInline(expr, text, end)) {
63
+ buf += `$${expr}$`;
64
+ i = end + 1;
65
+ continue;
66
+ }
67
+
68
+ if (buf) out.push({ text: buf });
69
+ out.push({ math: expr, display });
70
+ buf = "";
71
+ i = end + openLen;
72
+ }
73
+
74
+ if (buf) out.push({ text: buf });
75
+ return out;
76
+ }
77
+
78
+ function ensureTag(tag, attrs) {
79
+ const id = attrs["data-kitfly-latex"];
80
+ const existing = document.querySelector(`${tag}[data-kitfly-latex="${id}"]`);
81
+ if (existing) return Promise.resolve(existing);
82
+ return new Promise((resolve, reject) => {
83
+ const el = document.createElement(tag);
84
+ Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v));
85
+ el.addEventListener("load", () => resolve(el), { once: true });
86
+ el.addEventListener("error", () => reject(new Error(`Failed to load ${attrs.href || attrs.src}`)), {
87
+ once: true,
88
+ });
89
+ document.head.appendChild(el);
90
+ });
91
+ }
92
+
93
+ function renderFencedMath() {
94
+ document.querySelectorAll("pre > code.language-math").forEach((code) => {
95
+ const pre = code.parentElement;
96
+ if (!pre || pre.tagName !== "PRE") return;
97
+ const host = document.createElement("div");
98
+ host.className = "kitfly-katex-display";
99
+ window.katex.render(code.textContent || "", host, { displayMode: true, throwOnError: false });
100
+ pre.replaceWith(host);
101
+ });
102
+ }
103
+
104
+ function renderDelimitedMath() {
105
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
106
+ const nodes = [];
107
+ while (walker.nextNode()) {
108
+ const node = walker.currentNode;
109
+ const parent = node.parentElement;
110
+ if (!parent || parent.closest("pre, code, script, style, textarea, .katex")) continue;
111
+ if (!(node.textContent || "").includes("$")) continue;
112
+ nodes.push(node);
113
+ }
114
+
115
+ for (const node of nodes) {
116
+ const parts = splitMath(node.textContent || "");
117
+ if (!parts.some((p) => p.math)) continue;
118
+ const frag = document.createDocumentFragment();
119
+ for (const p of parts) {
120
+ if (p.text != null) {
121
+ frag.appendChild(document.createTextNode(p.text));
122
+ } else if (p.math != null) {
123
+ const host = document.createElement("span");
124
+ host.className = p.display ? "kitfly-katex-display" : "kitfly-katex-inline";
125
+ window.katex.render(p.math, host, { displayMode: Boolean(p.display), throwOnError: false });
126
+ frag.appendChild(host);
127
+ }
128
+ }
129
+ node.replaceWith(frag);
130
+ }
131
+ }
132
+
133
+ function apply() {
134
+ const styleId = "kitfly-latex-style";
135
+ if (!document.getElementById(styleId)) {
136
+ const style = document.createElement("style");
137
+ style.id = styleId;
138
+ style.textContent = ".kitfly-katex-display{display:block;text-align:center;margin:1em 0;overflow-x:auto}";
139
+ document.head.appendChild(style);
140
+ }
141
+
142
+ Promise.all([
143
+ ensureTag("link", {
144
+ rel: "stylesheet",
145
+ href: KATEX_CSS_URL,
146
+ integrity: KATEX_CSS_SRI,
147
+ crossorigin: "anonymous",
148
+ "data-kitfly-latex": "css",
149
+ }),
150
+ ensureTag("script", {
151
+ src: KATEX_JS_URL,
152
+ integrity: KATEX_JS_SRI,
153
+ crossorigin: "anonymous",
154
+ defer: "",
155
+ "data-kitfly-latex": "js",
156
+ }),
157
+ ])
158
+ .then(() => {
159
+ if (!window.katex?.render) {
160
+ console.warn("[kitfly:latex] KaTeX unavailable");
161
+ return;
162
+ }
163
+ renderFencedMath();
164
+ renderDelimitedMath();
165
+ })
166
+ .catch((e) => console.warn("[kitfly:latex]", e instanceof Error ? e.message : String(e)));
167
+ }
168
+
169
+ if (typeof document === "undefined") {
170
+ globalThis.__kitflyLatexTest = { splitMath, isCurrency, shouldTreatAsLiteralInline };
171
+ return;
172
+ }
173
+ if (document.readyState === "loading") {
174
+ document.addEventListener("DOMContentLoaded", apply);
175
+ } else {
176
+ apply();
177
+ }
178
+ })();
@@ -0,0 +1,179 @@
1
+ (() => {
2
+ const KIND_SET = new Set(["bar", "line", "pie"]);
3
+
4
+ function parseValue(raw) {
5
+ const v = String(raw ?? "").trim();
6
+ if (!v) return "";
7
+ if ((v.startsWith("[") && v.endsWith("]")) || (v.startsWith("{") && v.endsWith("}"))) {
8
+ try {
9
+ return JSON.parse(v);
10
+ } catch {
11
+ return v;
12
+ }
13
+ }
14
+ if (/^-?\d+(?:\.\d+)?$/.test(v)) return Number(v);
15
+ const m = v.match(/^"(.*)"$/) || v.match(/^'(.*)'$/);
16
+ return m ? m[1] : v;
17
+ }
18
+
19
+ function parseSpec(text) {
20
+ const out = {};
21
+ const lines = String(text ?? "").split(/\r?\n/);
22
+ for (const raw of lines) {
23
+ const line = raw.trim();
24
+ if (!line || line.startsWith("#")) continue;
25
+ const kv = line.match(/^([a-z0-9_-]+)\s*:\s*(.*)$/i);
26
+ if (!kv) continue;
27
+ out[kv[1]] = parseValue(kv[2]);
28
+ }
29
+
30
+ const kind = String(out.kind || "").toLowerCase();
31
+ const labels = Array.isArray(out.labels) ? out.labels.map((x) => String(x ?? "")) : [];
32
+ const data = Array.isArray(out.data)
33
+ ? out.data
34
+ .map((x) => Number(x))
35
+ .filter((n) => Number.isFinite(n))
36
+ : [];
37
+
38
+ if (!KIND_SET.has(kind)) return null;
39
+ if (!labels.length || !data.length || labels.length !== data.length) return null;
40
+
41
+ const height = typeof out.height === "number" && Number.isFinite(out.height) ? out.height : 300;
42
+ const color = typeof out.color === "string" ? out.color.trim().toLowerCase() : "primary";
43
+ const title = typeof out.title === "string" ? out.title : "";
44
+
45
+ return {
46
+ kind,
47
+ labels,
48
+ data,
49
+ title,
50
+ color,
51
+ height: Math.max(160, Math.min(640, Math.round(height))),
52
+ };
53
+ }
54
+
55
+ function pickThemeColor(hint) {
56
+ const style = getComputedStyle(document.documentElement);
57
+ const map = {
58
+ primary: style.getPropertyValue("--color-primary").trim() || "#2563eb",
59
+ accent: style.getPropertyValue("--color-accent").trim() || "#f59e0b",
60
+ muted: style.getPropertyValue("--color-text-muted")?.trim() || "#6b7280",
61
+ text: style.getPropertyValue("--color-text")?.trim() || "#111827",
62
+ border: style.getPropertyValue("--color-border")?.trim() || "#d1d5db",
63
+ surface: style.getPropertyValue("--color-surface")?.trim() || "#f9fafb",
64
+ };
65
+ return map[hint] || map.primary;
66
+ }
67
+
68
+ function configFromSpec(spec) {
69
+ const base = pickThemeColor(spec.color);
70
+ const isPie = spec.kind === "pie";
71
+ const bg = isPie
72
+ ? spec.labels.map((_, i) => `color-mix(in srgb, ${base} ${Math.max(20, 90 - i * 12)}%, white)`)
73
+ : base;
74
+
75
+ return {
76
+ type: spec.kind,
77
+ data: {
78
+ labels: spec.labels,
79
+ datasets: [
80
+ {
81
+ label: spec.title || "Series",
82
+ data: spec.data,
83
+ backgroundColor: bg,
84
+ borderColor: base,
85
+ borderWidth: 2,
86
+ tension: spec.kind === "line" ? 0.25 : 0,
87
+ fill: spec.kind === "line" ? false : true,
88
+ },
89
+ ],
90
+ },
91
+ options: {
92
+ responsive: true,
93
+ maintainAspectRatio: false,
94
+ plugins: {
95
+ legend: { display: spec.kind === "pie", labels: { color: pickThemeColor("text") } },
96
+ title: { display: Boolean(spec.title), text: spec.title, color: pickThemeColor("text") },
97
+ },
98
+ scales:
99
+ spec.kind === "pie"
100
+ ? undefined
101
+ : {
102
+ x: { ticks: { color: pickThemeColor("text") }, grid: { color: pickThemeColor("border") } },
103
+ y: { ticks: { color: pickThemeColor("text") }, grid: { color: pickThemeColor("border") } },
104
+ },
105
+ },
106
+ };
107
+ }
108
+
109
+ function renderWrapper(wrapper, spec) {
110
+ let canvas = wrapper.querySelector("canvas");
111
+ if (!canvas) {
112
+ canvas = document.createElement("canvas");
113
+ wrapper.appendChild(canvas);
114
+ }
115
+ wrapper.style.height = `${spec.height}px`;
116
+ if (wrapper.__kitflyChart && typeof wrapper.__kitflyChart.destroy === "function") {
117
+ wrapper.__kitflyChart.destroy();
118
+ }
119
+ wrapper.__kitflyChart = new window.Chart(canvas, configFromSpec(spec));
120
+ }
121
+
122
+ function transformCodeBlock(code) {
123
+ const pre = code.parentElement;
124
+ if (!pre || pre.tagName !== "PRE") return;
125
+ const spec = parseSpec(code.textContent || "");
126
+ if (!spec) {
127
+ console.warn("[kitfly:slides-charts-lite] Invalid chart block, leaving as code");
128
+ return;
129
+ }
130
+
131
+ const wrapper = document.createElement("div");
132
+ wrapper.className = "kitfly-chart-wrapper";
133
+ wrapper.setAttribute("data-kitfly-chart-spec", JSON.stringify(spec));
134
+ pre.replaceWith(wrapper);
135
+ renderWrapper(wrapper, spec);
136
+ }
137
+
138
+ function renderAll() {
139
+ if (!window.Chart) {
140
+ console.warn("[kitfly:slides-charts-lite] Chart.js unavailable");
141
+ return;
142
+ }
143
+
144
+ const styleId = "kitfly-charts-lite-style";
145
+ if (!document.getElementById(styleId)) {
146
+ const style = document.createElement("style");
147
+ style.id = styleId;
148
+ style.textContent = ".kitfly-chart-wrapper{position:relative;width:100%;max-width:100%;margin:1rem 0}";
149
+ document.head.appendChild(style);
150
+ }
151
+
152
+ document.querySelectorAll("pre > code.language-chart").forEach(transformCodeBlock);
153
+ }
154
+
155
+ function reinitCharts() {
156
+ document.querySelectorAll(".kitfly-chart-wrapper[data-kitfly-chart-spec]").forEach((wrapper) => {
157
+ const raw = wrapper.getAttribute("data-kitfly-chart-spec") || "";
158
+ try {
159
+ const spec = JSON.parse(raw);
160
+ renderWrapper(wrapper, spec);
161
+ } catch {
162
+ // ignore broken metadata
163
+ }
164
+ });
165
+ }
166
+
167
+ if (typeof document === "undefined") {
168
+ globalThis.__kitflyChartsLiteTest = { parseSpec };
169
+ return;
170
+ }
171
+
172
+ window.reinitCharts = reinitCharts;
173
+
174
+ if (document.readyState === "loading") {
175
+ document.addEventListener("DOMContentLoaded", renderAll);
176
+ } else {
177
+ renderAll();
178
+ }
179
+ })();