@kondeio/kdf 0.1.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 (59) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CONTRIBUTING.md +57 -0
  3. package/LICENSE +21 -0
  4. package/README.md +374 -0
  5. package/SECURITY.md +53 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +58 -0
  8. package/dist/css-generator.d.ts +17 -0
  9. package/dist/css-generator.js +59 -0
  10. package/dist/index.d.ts +48 -0
  11. package/dist/index.js +62 -0
  12. package/dist/plugin.d.ts +30 -0
  13. package/dist/plugin.js +40 -0
  14. package/dist/postinstall.d.ts +1 -0
  15. package/dist/postinstall.js +48 -0
  16. package/dist/resolver.d.ts +11 -0
  17. package/dist/resolver.js +184 -0
  18. package/dist/types.d.ts +44 -0
  19. package/dist/types.js +1 -0
  20. package/docs/kdf-doc.md +653 -0
  21. package/docs/kdf-skill.md +374 -0
  22. package/example/bootstrap/globals.css +12 -0
  23. package/example/bootstrap/hero-section.tsx +38 -0
  24. package/example/bootstrap/kdf/homepage.json +35 -0
  25. package/example/bootstrap/kdf/shared/button.json +10 -0
  26. package/example/bootstrap/kdf/shared/card.json +4 -0
  27. package/example/bootstrap/kdf/shared/color.json +17 -0
  28. package/example/bootstrap/kdf/shared/layout.json +7 -0
  29. package/example/bootstrap/kdf/shared/typography.json +8 -0
  30. package/example/bootstrap/konde-server.css +3 -0
  31. package/example/bootstrap/konde.css +3 -0
  32. package/example/preview.ts +428 -0
  33. package/example/pure-css/hero-section.tsx +40 -0
  34. package/example/pure-css/kdf/homepage.json +20 -0
  35. package/example/pure-css/kdf/shared/button.json +10 -0
  36. package/example/pure-css/kdf/shared/card.json +4 -0
  37. package/example/pure-css/kdf/shared/color.json +11 -0
  38. package/example/pure-css/kdf/shared/typography.json +8 -0
  39. package/example/pure-css/konde-server.css +3 -0
  40. package/example/pure-css/konde.css +3 -0
  41. package/example/pure-css/styles.css +34 -0
  42. package/example/sample-pages/about.json +30 -0
  43. package/example/sample-pages/dashboard.json +12 -0
  44. package/example/sample-pages/pricing.json +28 -0
  45. package/example/shadcn/globals.css +15 -0
  46. package/example/shadcn/hero-section.tsx +34 -0
  47. package/example/shadcn/konde-server.css +3 -0
  48. package/example/shadcn/konde.css +3 -0
  49. package/example/tailwind/globals.css +5 -0
  50. package/example/tailwind/hero-section.tsx +34 -0
  51. package/example/tailwind/konde-server.css +3 -0
  52. package/example/tailwind/konde.css +3 -0
  53. package/kdf/homepage.json +25 -0
  54. package/kdf/shared/button.json +10 -0
  55. package/kdf/shared/card.json +4 -0
  56. package/kdf/shared/color.json +17 -0
  57. package/kdf/shared/layout.json +7 -0
  58. package/kdf/shared/typography.json +8 -0
  59. package/package.json +77 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * KDF Visual Preview — Static HTML Generator
3
+ *
4
+ * Run: bun example/preview.ts
5
+ * Auto-opens in browser. Edit JSON → re-run → see changes.
6
+ */
7
+ import { getDesign } from "../src/index";
8
+ // konde.css is user-managed, not auto-generated
9
+ import { readFileSync, existsSync, readdirSync } from "fs";
10
+ import { join } from "path";
11
+ import { $ } from "bun";
12
+
13
+ /**
14
+ * Usage:
15
+ * bun example/preview.ts → tailwind (default)
16
+ * bun example/preview.ts bootstrap → bootstrap
17
+ * PORT=4410 KDF_PREVIEW_OPEN=0 bun example/preview.ts pure-css
18
+ */
19
+ const FRAMEWORK = process.argv[2] || "tailwind";
20
+ const FRAMEWORK_DIRS: Record<string, string> = {
21
+ tailwind: "kdf",
22
+ shadcn: "kdf",
23
+ bootstrap: "example/bootstrap/kdf",
24
+ "pure-css": "example/pure-css/kdf",
25
+ };
26
+
27
+ // Override KDF_DIR so resolver reads from the right folder
28
+ const kdfDir = FRAMEWORK_DIRS[FRAMEWORK] || "kdf";
29
+ process.env.KDF_DIR = kdfDir;
30
+
31
+ const KDF_ROOT = join(process.cwd(), kdfDir);
32
+ const OUTPUT = join(process.cwd(), "example/preview.html");
33
+
34
+ const FRAMEWORK_CDN: Record<string, string> = {
35
+ tailwind: `<script src="https://cdn.tailwindcss.com"></script>`,
36
+ shadcn: `<script src="https://cdn.tailwindcss.com"></script>`,
37
+ bootstrap: `<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">`,
38
+ "pure-css": "",
39
+ };
40
+
41
+ function getPages(): string[] {
42
+ if (!existsSync(KDF_ROOT)) return [];
43
+ return readdirSync(KDF_ROOT)
44
+ .filter((f) => f.endsWith(".json"))
45
+ .map((f) => f.replace(".json", ""));
46
+ }
47
+
48
+ /** Detect element type from token key name */
49
+ function detectElement(key: string): { tag: string; content: string; selfClosing?: boolean } {
50
+ const k = key.toLowerCase();
51
+
52
+ // Headings
53
+ if (k === "title" || k.endsWith("-title") || k.includes("title"))
54
+ return { tag: "h2", content: "The quick brown fox jumps" };
55
+ if (k.includes("h1")) return { tag: "h1", content: "Main Headline" };
56
+ if (k.includes("h2")) return { tag: "h2", content: "Section Title" };
57
+ if (k.includes("h3")) return { tag: "h3", content: "Subsection" };
58
+
59
+ // Buttons / CTAs
60
+ if (k.includes("cta") || k.includes("button") || k.includes("signup") || k.includes("login"))
61
+ return { tag: "button", content: k.includes("primary") || k.includes("cta") ? "Get Started" : "Learn More" };
62
+ if (k === "active") return { tag: "button", content: "1" };
63
+
64
+ // Images
65
+ if (k.includes("image") || k.includes("avatar") || k.includes("logo"))
66
+ return { tag: "img", content: "", selfClosing: true };
67
+
68
+ // Text / descriptions
69
+ if (k.includes("description") || k.includes("content") || k.includes("answer"))
70
+ return { tag: "p", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." };
71
+ if (k.includes("label") || k.includes("muted") || k.includes("small") || k.includes("category") || k.includes("role") || k.includes("period"))
72
+ return { tag: "span", content: "Subtitle text" };
73
+ if (k.includes("name") || k.includes("plan-name"))
74
+ return { tag: "span", content: "Item Name" };
75
+ if (k.includes("price") || k.includes("value"))
76
+ return { tag: "span", content: "$49" };
77
+ if (k.includes("badge"))
78
+ return { tag: "span", content: "Popular" };
79
+ if (k.includes("feature-item"))
80
+ return { tag: "span", content: "Unlimited projects" };
81
+ if (k.includes("question"))
82
+ return { tag: "span", content: "How does KDF work?" };
83
+
84
+ // Navigation
85
+ if (k.includes("nav-item") || k.includes("menu-item") || k.includes("footer-item"))
86
+ return { tag: "a", content: "Nav Link" };
87
+
88
+ // Wrappers / containers
89
+ if (k.includes("wrapper") || k.includes("actions") || k.includes("grid") || k.includes("list") || k.includes("nav") || k.includes("menu") || k.includes("footer") || k.includes("header") || k.includes("body") || k.includes("features"))
90
+ return { tag: "div", content: "" };
91
+
92
+ // Cards
93
+ if (k.includes("card") && !k.includes("-"))
94
+ return { tag: "div", content: "" };
95
+
96
+ return { tag: "div", content: key };
97
+ }
98
+
99
+ /** Build a contextual preview section showing tokens grouped by their JSON structure */
100
+ function renderPagePreview(page: string, d: ReturnType<typeof getDesign>, raw: Record<string, unknown>): string {
101
+ let html = "";
102
+
103
+ for (const [sectionKey, sectionValue] of Object.entries(raw)) {
104
+ if (sectionKey === "$layout") continue;
105
+ if (!sectionValue || typeof sectionValue !== "object") continue;
106
+
107
+ const section = sectionValue as Record<string, unknown>;
108
+ const sectionTokens: string[] = [];
109
+
110
+ for (const [key, value] of Object.entries(section)) {
111
+ if (typeof value !== "string" && !(typeof value === "object" && value && "className" in value)) continue;
112
+
113
+ const fullPath = `${sectionKey}.${key}`;
114
+ const resolved = d(fullPath);
115
+ const cssProps = d.css(fullPath);
116
+ const rawVal = typeof value === "string" ? value : JSON.stringify(value);
117
+ const el = detectElement(key);
118
+
119
+ // Build inline style from d.css() + any base styles
120
+ const cssStyle = Object.entries(cssProps).map(([k, v]) => `${k}:${v}`).join(";");
121
+
122
+ let rendered: string;
123
+ if (el.selfClosing) {
124
+ rendered = `<${el.tag} data-kdf="${fullPath}" class="${resolved}" style="width:120px;height:80px;background:#e5e7eb;border-radius:8px;${cssStyle}" />`;
125
+ } else if (el.tag === "a") {
126
+ rendered = `<${el.tag} href="#" data-kdf="${fullPath}" class="${resolved}" style="${cssStyle}">${el.content}</${el.tag}>`;
127
+ } else if (el.content === "" && el.tag === "div") {
128
+ rendered = `<div data-kdf="${fullPath}" class="${resolved}" style="min-height:2.5rem;border:1px dashed #d1d5db;border-radius:6px;padding:0.5rem;display:flex;align-items:center;justify-content:center;${cssStyle}"><span style="font-size:0.75rem;color:#9ca3af">${key} (container)</span></div>`;
129
+ } else {
130
+ rendered = `<${el.tag} data-kdf="${fullPath}" class="${resolved}" style="${cssStyle}">${el.content}</${el.tag}>`;
131
+ }
132
+
133
+ sectionTokens.push(`
134
+ <div class="kdf-token">
135
+ <div style="margin-bottom:0.375rem">${rendered}</div>
136
+ <div class="kdf-token-label">d("${fullPath}")${rawVal.startsWith("@") ? `<span class="kdf-token-ref">${escape(rawVal)}</span>` : ""}</div>
137
+ <div class="kdf-token-resolved">${escape(resolved || "(empty)")}${cssStyle ? ` | style: ${cssStyle}` : ""}</div>
138
+ </div>`);
139
+ }
140
+
141
+ if (sectionTokens.length === 0) continue;
142
+
143
+ html += `
144
+ <div style="margin-bottom:2rem">
145
+ <h3 class="kdf-section-title" style="font-size:0.875rem">
146
+ <span class="kdf-dot"></span>
147
+ ${sectionKey}
148
+ </h3>
149
+ <div class="kdf-token-group">
150
+ ${sectionTokens.join("")}
151
+ </div>
152
+ </div>`;
153
+ }
154
+
155
+ return html;
156
+ }
157
+
158
+ function renderAllPages(): string {
159
+ const pages = getPages();
160
+ let allSections = "";
161
+
162
+ for (const page of pages) {
163
+ const d = getDesign(page);
164
+ const raw = JSON.parse(readFileSync(join(KDF_ROOT, `${page}.json`), "utf-8"));
165
+
166
+ const preview = renderPagePreview(page, d, raw);
167
+ const jsonPreview = JSON.stringify(raw, null, 2);
168
+
169
+ // Count tokens
170
+ let tokenCount = 0;
171
+ function count(obj: Record<string, unknown>) {
172
+ for (const [key, value] of Object.entries(obj)) {
173
+ if (key === "$layout") continue;
174
+ if (typeof value === "string") tokenCount++;
175
+ else if (value && typeof value === "object" && "className" in value) tokenCount++;
176
+ else if (value && typeof value === "object" && !Array.isArray(value)) count(value as Record<string, unknown>);
177
+ }
178
+ }
179
+ count(raw);
180
+
181
+ allSections += `
182
+ <section id="${page}" class="kdf-section">
183
+ <div class="kdf-section-title">
184
+ <h2 style="margin:0">${page}</h2>
185
+ <span class="kdf-badge">${tokenCount} tokens</span>
186
+ <span style="font-size:0.75rem;color:#d1d5db">kdf/${page}.json</span>
187
+ </div>
188
+ <div class="kdf-grid">
189
+ <div>
190
+ ${preview}
191
+ </div>
192
+ <div>
193
+ <div class="kdf-sticky">
194
+ <h3 style="font-size:0.875rem;color:#6b7280;margin:0 0 0.5rem">Source</h3>
195
+ <pre class="kdf-pre">${escape(jsonPreview)}</pre>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </section>`;
200
+ }
201
+
202
+ const nav = [
203
+ ...pages.map((p) => `<a href="#${p}" class="px-3 py-1.5 rounded text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900 transition-colors">${p}</a>`),
204
+ `<a href="#konde-css" class="px-3 py-1.5 rounded text-sm text-amber-600 hover:bg-amber-50 transition-colors">konde.css</a>`,
205
+ ].join("\n ");
206
+
207
+ // Read konde.css content for preview section
208
+ const serverCssContent = existsSync(join(process.cwd(), `example/${FRAMEWORK}/konde-server.css`))
209
+ ? readFileSync(join(process.cwd(), `example/${FRAMEWORK}/konde-server.css`), "utf-8")
210
+ : "/* not found */";
211
+ const clientCssContent = existsSync(join(process.cwd(), `example/${FRAMEWORK}/konde.css`))
212
+ ? readFileSync(join(process.cwd(), `example/${FRAMEWORK}/konde.css`), "utf-8")
213
+ : "/* not found */";
214
+
215
+ const cdn = FRAMEWORK_CDN[FRAMEWORK] || "";
216
+ // konde-server.css — server-rendered (simulated here as <link> early in <head>)
217
+ const serverCssFile = join(process.cwd(), `example/${FRAMEWORK}/konde-server.css`);
218
+ const serverCssLink = existsSync(serverCssFile)
219
+ ? `<link rel="stylesheet" href="konde-server.css">`
220
+ : `<!-- konde-server.css not found — skipped -->`;
221
+
222
+ // konde.css — client-side, LAST in <head>
223
+ const clientCssFile = join(process.cwd(), `example/${FRAMEWORK}/konde.css`);
224
+ const clientCssLink = existsSync(clientCssFile)
225
+ ? `<link rel="stylesheet" href="konde.css">`
226
+ : `<!-- konde.css not found — skipped -->`;
227
+
228
+ return `<!DOCTYPE html>
229
+ <html lang="en">
230
+ <head>
231
+ <meta charset="UTF-8">
232
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
233
+ <title>KDF Preview — ${FRAMEWORK}</title>
234
+
235
+ <!-- 1. konde-server.css — critical overrides (server-rendered in real Next.js) -->
236
+ ${serverCssLink}
237
+
238
+ <!-- 2. Framework CSS -->
239
+ ${cdn}
240
+ ${FRAMEWORK !== "pure-css" ? `<link rel="stylesheet" href="globals.css">` : `<link rel="stylesheet" href="styles.css">`}
241
+
242
+ <!-- 3. Preview shell styles -->
243
+ <style>
244
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; }
245
+ .kdf-shell { max-width: 1200px; margin: 0 auto; padding: 2rem 1.5rem; }
246
+ .kdf-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid #e5e7eb; }
247
+ .kdf-header h1 { font-size: 1.5rem; font-weight: 700; margin: 0; }
248
+ .kdf-header p { font-size: 0.875rem; color: #6b7280; margin: 0.25rem 0 0; }
249
+ .kdf-nav { display: flex; gap: 0.25rem; background: #fff; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 0.25rem; }
250
+ .kdf-nav a { padding: 0.375rem 0.75rem; border-radius: 0.375rem; font-size: 0.875rem; color: #4b5563; text-decoration: none; }
251
+ .kdf-nav a:hover { background: #f3f4f6; }
252
+ .kdf-badge { font-size: 0.75rem; background: #f3f4f6; color: #6b7280; padding: 0.125rem 0.5rem; border-radius: 0.25rem; }
253
+ .kdf-badge-fw { font-size: 0.75rem; background: #dbeafe; color: #1d4ed8; padding: 0.125rem 0.5rem; border-radius: 0.25rem; }
254
+ .kdf-grid { display: grid; grid-template-columns: 3fr 2fr; gap: 2rem; }
255
+ .kdf-section { margin-bottom: 3rem; }
256
+ .kdf-section-title { font-size: 1.125rem; font-weight: 700; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
257
+ .kdf-dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: #3b82f6; }
258
+ .kdf-token-group { background: #fff; border: 1px solid #e5e7eb; border-radius: 0.5rem; overflow: hidden; }
259
+ .kdf-token { padding: 0.75rem 1rem; border-bottom: 1px solid #f3f4f6; }
260
+ .kdf-token:last-child { border-bottom: none; }
261
+ .kdf-token-label { font-size: 0.75rem; color: #9ca3af; margin-top: 0.375rem; font-family: monospace; }
262
+ .kdf-token-ref { font-size: 0.75rem; color: #3b82f6; margin-left: 0.5rem; }
263
+ .kdf-token-resolved { font-size: 0.625rem; color: #d1d5db; margin-top: 0.25rem; word-break: break-all; font-family: monospace; }
264
+ .kdf-pre { padding: 1rem; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 0.5rem; font-size: 0.75rem; overflow: auto; max-height: 70vh; line-height: 1.6; }
265
+ .kdf-sticky { position: sticky; top: 2rem; }
266
+ .kdf-footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #e5e7eb; font-size: 0.75rem; color: #9ca3af; }
267
+ </style>
268
+
269
+ <script>
270
+ const es = new EventSource("/__reload");
271
+ es.onmessage = (e) => { if (e.data === "reload") location.reload(); };
272
+ </script>
273
+
274
+ <!-- 4. konde.css — LAST before </head>, highest specificity -->
275
+ ${clientCssLink}
276
+ </head>
277
+ <body>
278
+ <div class="kdf-shell">
279
+
280
+ <div class="kdf-header">
281
+ <div>
282
+ <h1>KDF Preview</h1>
283
+ <p>Edit JSON → auto-reload &nbsp; <span class="kdf-badge-fw">${FRAMEWORK}</span></p>
284
+ </div>
285
+ <div class="kdf-nav">
286
+ ${nav}
287
+ </div>
288
+ </div>
289
+
290
+ ${allSections}
291
+
292
+ <section id="konde-css" class="kdf-section">
293
+ <div class="kdf-section-title">
294
+ <h2 style="margin:0">konde CSS</h2>
295
+ <span class="kdf-badge">user-managed</span>
296
+ </div>
297
+ <div class="kdf-grid" style="grid-template-columns:1fr 1fr">
298
+ <div>
299
+ <h3 style="font-size:0.875rem;color:#6b7280;margin:0 0 0.5rem">konde-server.css <span class="kdf-badge">server</span></h3>
300
+ <pre class="kdf-pre">${escape(serverCssContent)}</pre>
301
+ </div>
302
+ <div>
303
+ <h3 style="font-size:0.875rem;color:#6b7280;margin:0 0 0.5rem">konde.css <span class="kdf-badge">client</span></h3>
304
+ <pre class="kdf-pre">${escape(clientCssContent)}</pre>
305
+ </div>
306
+ </div>
307
+ </section>
308
+
309
+ <div class="kdf-footer">
310
+ @kondeio/kdf v0.1.0 — ${FRAMEWORK} — all styles resolved from kdf/*.json via getDesign()
311
+ </div>
312
+ </div>
313
+ </body>
314
+ </html>`;
315
+ }
316
+
317
+ function escape(s: string): string {
318
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
319
+ }
320
+
321
+ // Live server with file watching
322
+ const portFromEnv = Number(process.env.PORT);
323
+ const PORT = Number.isInteger(portFromEnv) && portFromEnv > 0 ? portFromEnv : 4400;
324
+ const SHOULD_OPEN_BROWSER = process.env.KDF_PREVIEW_OPEN !== "0";
325
+ let clients: ReadableStreamDefaultController[] = [];
326
+
327
+ // SSE endpoint for live reload
328
+ function handleSSE(): Response {
329
+ const stream = new ReadableStream({
330
+ start(controller) {
331
+ clients.push(controller);
332
+ controller.enqueue("data: connected\n\n");
333
+ },
334
+ cancel() {
335
+ clients = clients.filter((c) => c !== undefined);
336
+ },
337
+ });
338
+ return new Response(stream, {
339
+ headers: {
340
+ "Content-Type": "text/event-stream",
341
+ "Cache-Control": "no-cache",
342
+ Connection: "keep-alive",
343
+ },
344
+ });
345
+ }
346
+
347
+ function notifyClients() {
348
+ for (const client of clients) {
349
+ try {
350
+ client.enqueue("data: reload\n\n");
351
+ } catch {
352
+ /* client disconnected */
353
+ }
354
+ }
355
+ }
356
+
357
+ // Watch kdf/ directory for changes
358
+ import { watch } from "fs";
359
+ function watchDir(dir: string) {
360
+ if (!existsSync(dir)) return;
361
+ watch(dir, { recursive: true }, (event, filename) => {
362
+ if (!filename?.endsWith(".json")) return;
363
+ console.log(` changed: kdf/${filename} → reloading`);
364
+ // Don't overwrite konde.css — user may have manual edits
365
+ // To regenerate from JSON: delete konde.css → auto-generated on next request
366
+ notifyClients();
367
+ });
368
+ }
369
+ watchDir(KDF_ROOT);
370
+
371
+ // Watch konde CSS files for manual edits
372
+ for (const cssFile of ["konde-server.css", "konde.css"]) {
373
+ const cssPath = join(process.cwd(), `example/${FRAMEWORK}/${cssFile}`);
374
+ if (existsSync(cssPath)) {
375
+ watch(cssPath, () => {
376
+ console.log(` changed: ${cssFile} (manual edit) → reloading`);
377
+ notifyClients();
378
+ });
379
+ }
380
+ }
381
+
382
+ // Serve
383
+ const server = Bun.serve({
384
+ port: PORT,
385
+ fetch(req) {
386
+ const url = new URL(req.url);
387
+
388
+ if (url.pathname === "/__reload") return handleSSE();
389
+
390
+ if (url.pathname === "/konde-server.css" || url.pathname === "/konde.css") {
391
+ const file = join(process.cwd(), `example/${FRAMEWORK}${url.pathname}`);
392
+ if (!existsSync(file)) {
393
+ return new Response(`/* ${url.pathname.slice(1)} not found — skipped */`, {
394
+ headers: { "Content-Type": "text/css" },
395
+ });
396
+ }
397
+ return new Response(readFileSync(file, "utf-8"), {
398
+ headers: { "Content-Type": "text/css", "Cache-Control": "no-cache" },
399
+ });
400
+ }
401
+
402
+ if (url.pathname === "/globals.css" || url.pathname === "/styles.css") {
403
+ const file = join(process.cwd(), `example/${FRAMEWORK}${url.pathname}`);
404
+ if (!existsSync(file)) {
405
+ return new Response("/* file not found */", {
406
+ headers: { "Content-Type": "text/css" },
407
+ });
408
+ }
409
+ return new Response(readFileSync(file, "utf-8"), {
410
+ headers: { "Content-Type": "text/css", "Cache-Control": "no-cache" },
411
+ });
412
+ }
413
+
414
+ // Render fresh on every request (no cache — instant changes)
415
+ const html = renderAllPages();
416
+ return new Response(html, {
417
+ headers: { "Content-Type": "text/html; charset=utf-8" },
418
+ });
419
+ },
420
+ });
421
+
422
+ console.log(`KDF Preview: http://localhost:${PORT}`);
423
+ console.log(`Pages: ${getPages().join(", ")}`);
424
+ console.log(`Watching kdf/ for changes — auto-reload enabled\n`);
425
+
426
+ if (SHOULD_OPEN_BROWSER) {
427
+ await $`open http://localhost:${PORT}`.quiet();
428
+ }
@@ -0,0 +1,40 @@
1
+ import { getDesign } from "@kondeio/kdf";
2
+
3
+ export function HeroSection() {
4
+ const d = getDesign("homepage");
5
+
6
+ return (
7
+ <section data-kdf="hero.wrapper" className={d("hero.wrapper")}>
8
+ <div data-kdf="hero.content" className={d("hero.content")}>
9
+ <h1 data-kdf="hero.title" className={d("hero.title")}>
10
+ Build websites with AI
11
+ </h1>
12
+ <p data-kdf="hero.description" className={d("hero.description")}>
13
+ The AI orchestration platform for web development teams.
14
+ </p>
15
+ <div data-kdf="hero.actions" className={d("hero.actions")}>
16
+ <a href="/about" data-kdf="hero.cta-secondary" className={d("hero.cta-secondary")}>
17
+ Learn more
18
+ </a>
19
+ <a href="/pricing" data-kdf="hero.cta-primary" className={d("hero.cta-primary")}>
20
+ Get started
21
+ </a>
22
+ </div>
23
+ </div>
24
+ </section>
25
+ );
26
+ }
27
+
28
+ /*
29
+ kdf/shared/button.json (plain CSS version):
30
+ "base": "kdf-btn",
31
+ "cta": "kdf-btn kdf-btn--primary kdf-btn--lg",
32
+ "outline": "kdf-btn kdf-btn--outline"
33
+
34
+ styles.css:
35
+ .kdf-btn { display: inline-flex; padding: 0.5rem 1rem; border-radius: 4px; }
36
+ .kdf-btn--primary { background: #4F46E5; color: white; }
37
+ .kdf-btn--lg { padding: 0.75rem 1.5rem; font-size: 1.125rem; }
38
+
39
+ Same d() API, same data-kdf, same JSON — different CSS underneath.
40
+ */
@@ -0,0 +1,20 @@
1
+ {
2
+ "hero": {
3
+ "wrapper": "kdf-hero",
4
+ "content": "kdf-hero__content",
5
+ "title": "@typography.h1",
6
+ "description": "kdf-hero__desc @typography.body",
7
+ "actions": "kdf-hero__actions",
8
+ "cta-primary": "@button.base @button.cta @button.lg",
9
+ "cta-secondary": "@button.base @button.outline",
10
+ "slider": "kdf-hero__slider"
11
+ },
12
+ "template-grid": {
13
+ "wrapper": "kdf-grid",
14
+ "card": "@card.base @card.hover",
15
+ "card-image": "kdf-card__img",
16
+ "card-body": "kdf-card__body",
17
+ "card-title": "kdf-card__title",
18
+ "card-category": "@typography.muted"
19
+ }
20
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "base": "kdf-btn",
3
+ "primary": "kdf-btn--primary",
4
+ "outline": "kdf-btn--outline",
5
+ "ghost": "kdf-btn--ghost",
6
+ "cta": "kdf-btn--primary",
7
+ "sm": "",
8
+ "md": "",
9
+ "lg": "kdf-btn--lg"
10
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "base": "kdf-card",
3
+ "hover": "kdf-card--hover"
4
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "primary": {
3
+ "css": { "--kdf-primary": "#4F46E5" }
4
+ },
5
+ "danger": {
6
+ "css": { "--kdf-danger": "#DC2626" }
7
+ },
8
+ "success": {
9
+ "css": { "--kdf-success": "#16A34A" }
10
+ }
11
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "h1": "kdf-h1",
3
+ "h2": "kdf-h2",
4
+ "h3": "kdf-h3",
5
+ "body": "kdf-body",
6
+ "muted": "kdf-muted",
7
+ "small": "kdf-muted"
8
+ }
@@ -0,0 +1,3 @@
1
+ /* konde-server.css — Critical design overrides (server-rendered) */
2
+ /* Imported in layout.tsx — inlined in HTML, no FOUC */
3
+ /* Edit freely — KDF will never overwrite this file */
@@ -0,0 +1,3 @@
1
+ /* konde.css — Custom design overrides */
2
+ /* Loaded LAST in <head> for highest specificity */
3
+ /* Edit freely — KDF will never overwrite this file */
@@ -0,0 +1,34 @@
1
+ /* Plain CSS — class definitions for KDF tokens */
2
+
3
+ /* Layout */
4
+ .kdf-hero { display: flex; flex-wrap: wrap; gap: 2rem; align-items: center; }
5
+ .kdf-hero__content { flex: 1; min-width: 300px; }
6
+ .kdf-hero__desc { margin-top: 1rem; }
7
+ .kdf-hero__actions { margin-top: 1.5rem; display: flex; gap: 0.75rem; }
8
+ .kdf-hero__slider { flex: 1; min-width: 300px; }
9
+ .kdf-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1.5rem; margin-top: 2.5rem; }
10
+
11
+ /* Typography */
12
+ .kdf-h1 { font-size: 2.25rem; font-weight: 700; line-height: 1.2; color: #111827; margin: 0; }
13
+ .kdf-h2 { font-size: 1.5rem; font-weight: 700; color: #111827; margin: 0; }
14
+ .kdf-h3 { font-size: 1.25rem; font-weight: 600; color: #111827; margin: 0; }
15
+ .kdf-body { font-size: 1rem; line-height: 1.6; color: #6b7280; }
16
+ .kdf-muted { font-size: 0.875rem; color: #9ca3af; }
17
+
18
+ /* Buttons */
19
+ .kdf-btn { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; border-radius: 6px; font-weight: 500; font-size: 0.875rem; border: none; cursor: pointer; transition: all 0.2s; text-decoration: none; }
20
+ .kdf-btn--primary { background: #4F46E5; color: white; }
21
+ .kdf-btn--primary:hover { background: #4338CA; }
22
+ .kdf-btn--outline { background: transparent; border: 1px solid #d1d5db; color: #374151; }
23
+ .kdf-btn--outline:hover { background: #f3f4f6; }
24
+ .kdf-btn--ghost { background: transparent; color: #6b7280; }
25
+ .kdf-btn--ghost:hover { background: #f3f4f6; color: #111827; }
26
+ .kdf-btn--lg { padding: 0.75rem 1.5rem; font-size: 1rem; }
27
+
28
+ /* Cards */
29
+ .kdf-card { background: white; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden; }
30
+ .kdf-card--hover { transition: box-shadow 0.2s; }
31
+ .kdf-card--hover:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
32
+ .kdf-card__img { width: 100%; aspect-ratio: 16/9; object-fit: cover; background: #f3f4f6; }
33
+ .kdf-card__body { padding: 1rem; }
34
+ .kdf-card__title { font-weight: 500; color: #111827; font-size: 0.875rem; }
@@ -0,0 +1,30 @@
1
+ {
2
+ "hero": {
3
+ "wrapper": "max-w-3xl",
4
+ "title": "@typography.h1",
5
+ "description": "mt-4 @typography.body max-w-xl"
6
+ },
7
+ "story": {
8
+ "wrapper": "mt-16",
9
+ "title": "@typography.h2",
10
+ "content": "mt-4 @typography.body max-w-2xl",
11
+ "image": "mt-8 w-full rounded-lg object-cover aspect-video"
12
+ },
13
+ "team": {
14
+ "wrapper": "mt-16",
15
+ "title": "@typography.h2",
16
+ "grid": "mt-8 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3",
17
+ "card": "@card.base p-6 text-center",
18
+ "avatar": "mx-auto size-20 rounded-full object-cover",
19
+ "name": "mt-4 font-medium text-gray-900",
20
+ "role": "@typography.muted"
21
+ },
22
+ "values": {
23
+ "wrapper": "mt-16",
24
+ "title": "@typography.h2",
25
+ "grid": "mt-8 grid grid-cols-1 gap-6 sm:grid-cols-2",
26
+ "card": "@card.base p-6",
27
+ "card-title": "@typography.h3",
28
+ "card-description": "mt-2 @typography.body"
29
+ }
30
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "header": {
3
+ "wrapper": "flex items-center justify-between p-4",
4
+ "title": "@typography.h2",
5
+ "badge": "bg-[var(--kdf-accent)] text-white rounded-full px-3 py-1 text-xs"
6
+ },
7
+ "stats": {
8
+ "card": "@card.base p-6",
9
+ "value": "text-[var(--kdf-primary)] text-4xl font-bold",
10
+ "label": "@typography.muted"
11
+ }
12
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "hero": {
3
+ "wrapper": "text-center max-w-3xl mx-auto",
4
+ "title": "@typography.h1",
5
+ "description": "mt-4 @typography.body max-w-xl mx-auto"
6
+ },
7
+ "plans": {
8
+ "wrapper": "mt-12 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3",
9
+ "card": "@card.base p-6",
10
+ "card-featured": "@card.base p-6 border-blue-600 ring-1 ring-blue-600",
11
+ "badge": "inline-block rounded-full bg-blue-100 px-3 py-1 text-xs font-medium text-blue-700",
12
+ "plan-name": "@typography.h3",
13
+ "price": "mt-4 text-4xl font-bold tracking-tight text-gray-900",
14
+ "period": "text-sm font-normal text-gray-500",
15
+ "description": "mt-2 @typography.muted",
16
+ "features": "mt-6 space-y-3",
17
+ "feature-item": "flex items-start gap-2 text-sm text-gray-600",
18
+ "cta": "@button.base @button.primary @button.md w-full justify-center",
19
+ "cta-featured": "@button.base @button.cta @button.md w-full justify-center"
20
+ },
21
+ "faq": {
22
+ "wrapper": "mt-16 max-w-2xl mx-auto",
23
+ "title": "@typography.h2 text-center",
24
+ "list": "mt-8 space-y-4",
25
+ "question": "font-medium text-gray-900",
26
+ "answer": "mt-1 @typography.body"
27
+ }
28
+ }
@@ -0,0 +1,15 @@
1
+ /* Tailwind CSS + shadcn/ui theme */
2
+ /* CDN (dev only): add <script src="https://cdn.tailwindcss.com"></script> to HTML */
3
+ /* Build (production): @tailwind base; @tailwind components; @tailwind utilities; */
4
+
5
+ /* shadcn theme variables — these come from shadcn init */
6
+ @layer base {
7
+ :root {
8
+ --background: 0 0% 100%;
9
+ --foreground: 240 10% 3.9%;
10
+ --primary: 240 5.9% 10%;
11
+ --primary-foreground: 0 0% 98%;
12
+ }
13
+ }
14
+
15
+ /* Load konde.css after framework CSS in the host app. withKDF() only exposes paths. */