@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.
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +57 -0
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/SECURITY.md +53 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +58 -0
- package/dist/css-generator.d.ts +17 -0
- package/dist/css-generator.js +59 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +62 -0
- package/dist/plugin.d.ts +30 -0
- package/dist/plugin.js +40 -0
- package/dist/postinstall.d.ts +1 -0
- package/dist/postinstall.js +48 -0
- package/dist/resolver.d.ts +11 -0
- package/dist/resolver.js +184 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +1 -0
- package/docs/kdf-doc.md +653 -0
- package/docs/kdf-skill.md +374 -0
- package/example/bootstrap/globals.css +12 -0
- package/example/bootstrap/hero-section.tsx +38 -0
- package/example/bootstrap/kdf/homepage.json +35 -0
- package/example/bootstrap/kdf/shared/button.json +10 -0
- package/example/bootstrap/kdf/shared/card.json +4 -0
- package/example/bootstrap/kdf/shared/color.json +17 -0
- package/example/bootstrap/kdf/shared/layout.json +7 -0
- package/example/bootstrap/kdf/shared/typography.json +8 -0
- package/example/bootstrap/konde-server.css +3 -0
- package/example/bootstrap/konde.css +3 -0
- package/example/preview.ts +428 -0
- package/example/pure-css/hero-section.tsx +40 -0
- package/example/pure-css/kdf/homepage.json +20 -0
- package/example/pure-css/kdf/shared/button.json +10 -0
- package/example/pure-css/kdf/shared/card.json +4 -0
- package/example/pure-css/kdf/shared/color.json +11 -0
- package/example/pure-css/kdf/shared/typography.json +8 -0
- package/example/pure-css/konde-server.css +3 -0
- package/example/pure-css/konde.css +3 -0
- package/example/pure-css/styles.css +34 -0
- package/example/sample-pages/about.json +30 -0
- package/example/sample-pages/dashboard.json +12 -0
- package/example/sample-pages/pricing.json +28 -0
- package/example/shadcn/globals.css +15 -0
- package/example/shadcn/hero-section.tsx +34 -0
- package/example/shadcn/konde-server.css +3 -0
- package/example/shadcn/konde.css +3 -0
- package/example/tailwind/globals.css +5 -0
- package/example/tailwind/hero-section.tsx +34 -0
- package/example/tailwind/konde-server.css +3 -0
- package/example/tailwind/konde.css +3 -0
- package/kdf/homepage.json +25 -0
- package/kdf/shared/button.json +10 -0
- package/kdf/shared/card.json +4 -0
- package/kdf/shared/color.json +17 -0
- package/kdf/shared/layout.json +7 -0
- package/kdf/shared/typography.json +8 -0
- 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 <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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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,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. */
|