@getvitops/generator 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 (75) hide show
  1. package/README.md +35 -0
  2. package/assets/bricks/elements/carousel.php +89 -0
  3. package/assets/bricks/elements/centered.php +128 -0
  4. package/assets/bricks/elements/color-scheme-toggle.php +61 -0
  5. package/assets/bricks/elements/copy-button.php +65 -0
  6. package/assets/bricks/elements/dismissable.php +93 -0
  7. package/assets/bricks/elements/entries.php +96 -0
  8. package/assets/bricks/elements/image-compare.php +132 -0
  9. package/assets/bricks/elements/multi-field.php +148 -0
  10. package/assets/bricks/elements/sitenav.php +298 -0
  11. package/assets/bricks/elements/split-link.php +170 -0
  12. package/assets/bricks/elements/split-panel.php +155 -0
  13. package/assets/bricks/elements/split.php +83 -0
  14. package/assets/bricks/load.php +129 -0
  15. package/assets/css/animation.css +526 -0
  16. package/assets/css/global.css +17 -0
  17. package/assets/css/index.css +58 -0
  18. package/assets/css/layout.css +723 -0
  19. package/assets/css/patterns/anchor-link.css +66 -0
  20. package/assets/css/patterns/banner.css +64 -0
  21. package/assets/css/patterns/carousel.css +269 -0
  22. package/assets/css/patterns/cluster.css +64 -0
  23. package/assets/css/patterns/code.css +17 -0
  24. package/assets/css/patterns/combobox.css +97 -0
  25. package/assets/css/patterns/comment.css +88 -0
  26. package/assets/css/patterns/copy.css +82 -0
  27. package/assets/css/patterns/details.css +87 -0
  28. package/assets/css/patterns/dialog.css +99 -0
  29. package/assets/css/patterns/drawer.css +108 -0
  30. package/assets/css/patterns/dropdown.css +45 -0
  31. package/assets/css/patterns/forms.css +335 -0
  32. package/assets/css/patterns/grouped.css +86 -0
  33. package/assets/css/patterns/horizontal-scroll.css +40 -0
  34. package/assets/css/patterns/icon.css +72 -0
  35. package/assets/css/patterns/lightbox.css +156 -0
  36. package/assets/css/patterns/list.css +272 -0
  37. package/assets/css/patterns/marquee.css +68 -0
  38. package/assets/css/patterns/masonry.css +40 -0
  39. package/assets/css/patterns/media.css +70 -0
  40. package/assets/css/patterns/nav.css +241 -0
  41. package/assets/css/patterns/notification.css +114 -0
  42. package/assets/css/patterns/overlay.css +28 -0
  43. package/assets/css/patterns/popover.css +68 -0
  44. package/assets/css/patterns/pull-quote.css +71 -0
  45. package/assets/css/patterns/scroll-based.css +227 -0
  46. package/assets/css/patterns/scroll-target.css +124 -0
  47. package/assets/css/patterns/separator.css +130 -0
  48. package/assets/css/patterns/sitenav.css +582 -0
  49. package/assets/css/patterns/split-link.css +113 -0
  50. package/assets/css/patterns/splitter.css +212 -0
  51. package/assets/css/patterns/stack.css +67 -0
  52. package/assets/css/patterns/sticky.css +127 -0
  53. package/assets/css/patterns/subgrid.css +178 -0
  54. package/assets/css/patterns/svg.css +166 -0
  55. package/assets/css/patterns/table.css +106 -0
  56. package/assets/css/patterns/tag.css +75 -0
  57. package/assets/css/patterns/text-effects.css +317 -0
  58. package/assets/css/patterns/tooltip.css +68 -0
  59. package/assets/css/patterns/tree.css +126 -0
  60. package/assets/css/utilities.css +351 -0
  61. package/assets/js/deferred.js +1 -0
  62. package/assets/js/elements.js +887 -0
  63. package/assets/js/polyfills/adoptedStyleSheets.js +1 -0
  64. package/assets/js/polyfills/css-anchor-positioning-fn.js +17 -0
  65. package/assets/js/polyfills/global.esm.js +1 -0
  66. package/assets/js/polyfills/popover.js +63 -0
  67. package/assets/js/polyfills/template-shadowroot.js +24 -0
  68. package/assets/js/polyfills/webcomponents-bundle.js +27 -0
  69. package/assets/js/polyfills.js +1 -0
  70. package/dist/index.d.mts +1425 -0
  71. package/dist/index.d.mts.map +1 -0
  72. package/dist/index.mjs +2611 -0
  73. package/dist/index.mjs.map +1 -0
  74. package/package.json +33 -0
  75. package/schema.json +508 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,2611 @@
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { createHash } from "node:crypto";
5
+ import { transform } from "lightningcss";
6
+ import * as z from "zod/mini";
7
+
8
+ //#region src/schema.ts
9
+ /**
10
+ * Canonical `design-system.json` schema — the single source of truth.
11
+ *
12
+ * Authored with `zod/mini` (tree-shakeable Zod v4). Everything else derives from it:
13
+ * • the `DesignSystem` TS type (`z.infer`), used throughout the generator,
14
+ * • the published JSON Schema (`toJSONSchema`), emitted to `schema.json` for `$schema` refs,
15
+ * • runtime validation (`validate`) for the CLI and to gate `generate`.
16
+ */
17
+ const CssDecls = z.record(z.string(), z.string());
18
+ const Scalar = z.union([z.string(), z.number()]);
19
+ const VarMap = z.record(z.string(), Scalar);
20
+ const Ramp = z.object({
21
+ xxd: z.optional(z.string()),
22
+ xd: z.optional(z.string()),
23
+ d: z.optional(z.string()),
24
+ base: z.optional(z.string()),
25
+ l: z.optional(z.string()),
26
+ xl: z.optional(z.string()),
27
+ xxl: z.optional(z.string())
28
+ });
29
+ const RoleSpec = z.union([z.string(), z.object({
30
+ ramp: z.optional(z.string()),
31
+ invert: z.optional(z.boolean()),
32
+ shift: z.optional(z.number()),
33
+ steps: z.optional(z.record(z.string(), z.string())),
34
+ value: z.optional(z.record(z.string(), z.string()))
35
+ })]);
36
+ const Scheme = z.object({
37
+ appearance: z.enum(["light", "dark"]),
38
+ semantic: z.record(z.string(), RoleSpec)
39
+ });
40
+ const UtilityName = z.enum([
41
+ "bg",
42
+ "text",
43
+ "border",
44
+ "outline",
45
+ "fill",
46
+ "stroke"
47
+ ]);
48
+ const Colors = z.object({
49
+ palette: z.record(z.string(), Ramp),
50
+ utilities: z.optional(z.array(UtilityName)),
51
+ schemes: z.record(z.string(), Scheme)
52
+ });
53
+ const Scale = z.object({
54
+ base: z.string(),
55
+ ratio: z.number(),
56
+ steps: z.optional(z.number()),
57
+ names: z.optional(z.array(z.string())),
58
+ baseStep: z.optional(z.number()),
59
+ baseline: z.optional(z.string()),
60
+ fluid: z.optional(z.object({
61
+ minVw: z.string(),
62
+ maxVw: z.string(),
63
+ minRatio: z.number()
64
+ }))
65
+ });
66
+ const Pattern = z.object({
67
+ group: z.optional(z.string()),
68
+ overrides: z.optional(CssDecls),
69
+ element: z.optional(z.string()),
70
+ class: z.optional(z.string()),
71
+ default_role: z.optional(z.string()),
72
+ base: z.optional(CssDecls),
73
+ states: z.optional(z.record(z.string(), z.record(z.string(), z.unknown()))),
74
+ roles: z.optional(z.array(z.string()))
75
+ });
76
+ const Patterns = z.object({
77
+ defaults: z.optional(CssDecls),
78
+ radii: z.optional(CssDecls),
79
+ groups: z.optional(z.record(z.string(), CssDecls)),
80
+ z: z.optional(z.record(z.string(), z.number())),
81
+ items: z.optional(z.record(z.string(), Pattern))
82
+ });
83
+ const TypographyRole = z.record(z.string(), Scalar);
84
+ const Typography = z.object({
85
+ families: z.optional(z.record(z.string(), z.string())),
86
+ roles: z.optional(z.record(z.string(), TypographyRole)),
87
+ headings: z.optional(z.record(z.string(), z.string()))
88
+ });
89
+ const AnimEffect = z.object({
90
+ kf: z.string(),
91
+ css: z.optional(VarMap),
92
+ vars: z.optional(VarMap)
93
+ });
94
+ const Animations = z.object({
95
+ effects: z.optional(z.record(z.string(), AnimEffect)),
96
+ journeys: z.optional(z.object({
97
+ base: z.optional(z.record(z.string(), VarMap)),
98
+ compose: z.optional(z.array(z.array(z.string())))
99
+ }))
100
+ });
101
+ const DesignSystemSchema = z.object({
102
+ $schema: z.optional(z.string()),
103
+ colors: Colors,
104
+ shadows: z.optional(z.record(z.string(), z.string())),
105
+ fonts: z.optional(z.record(z.string(), z.string())),
106
+ typeScale: z.optional(Scale),
107
+ spaceScale: z.optional(Scale),
108
+ patterns: z.optional(Patterns),
109
+ typography: z.optional(Typography),
110
+ animations: z.optional(Animations)
111
+ });
112
+ const ColorsPatch = z.object({
113
+ palette: z.optional(z.record(z.string(), Ramp)),
114
+ utilities: z.optional(z.array(UtilityName)),
115
+ schemes: z.optional(z.record(z.string(), z.object({
116
+ appearance: z.optional(z.enum(["light", "dark"])),
117
+ semantic: z.optional(z.record(z.string(), RoleSpec))
118
+ })))
119
+ });
120
+ const DesignSystemPatchSchema = z.extend(z.partial(DesignSystemSchema), { colors: z.optional(ColorsPatch) });
121
+ /**
122
+ * Stable URL of the published JSON Schema. Resolves once `@getvitops/generator` is on
123
+ * npm (unpkg serves the packaged `schema.json`); `vitops init` stamps this into a
124
+ * scaffolded config's `$schema` so editors light up autocomplete + validation.
125
+ */
126
+ const SCHEMA_URL = "https://unpkg.com/@getvitops/generator/schema.json";
127
+ /** The published JSON Schema (draft-2020-12), derived from the zod schema. */
128
+ const jsonSchema = {
129
+ $id: SCHEMA_URL,
130
+ ...z.toJSONSchema(DesignSystemSchema, { target: "draft-2020-12" })
131
+ };
132
+ /** Validate an unknown value against the design-system schema. */
133
+ function validate(input) {
134
+ const result = z.safeParse(DesignSystemSchema, input);
135
+ if (!result.success) return {
136
+ ok: false,
137
+ data: void 0,
138
+ errors: result.error.issues
139
+ };
140
+ if (result.data.colors.schemes.default == null) return {
141
+ ok: false,
142
+ data: void 0,
143
+ errors: [{
144
+ code: "custom",
145
+ path: [
146
+ "colors",
147
+ "schemes",
148
+ "default"
149
+ ],
150
+ message: "colors.schemes must include a \"default\" (base) scheme"
151
+ }]
152
+ };
153
+ return {
154
+ ok: true,
155
+ data: result.data,
156
+ errors: []
157
+ };
158
+ }
159
+
160
+ //#endregion
161
+ //#region src/docs.ts
162
+ /**
163
+ * OKF documentation bundle generator (ported from the repo's `generate-docs.ts`).
164
+ *
165
+ * Pure: `generateDocs(ds, assetsDir)` returns a `{ relPath: markdown }` map for the
166
+ * Open Knowledge Format tree (top index, css/, bricks/), reading the Bricks element
167
+ * PHP from `<assetsDir>/bricks/elements`. No filesystem writes — the caller decides
168
+ * where the bundle lands (e.g. `<outDir>/docs`).
169
+ */
170
+ const DS_PATH = "design-system.json";
171
+ const ORDER = [
172
+ "split",
173
+ "centered",
174
+ "carousel",
175
+ "color-scheme-toggle",
176
+ "copy-button",
177
+ "dismissable",
178
+ "entries",
179
+ "image-compare",
180
+ "sitenav",
181
+ "multi-field",
182
+ "split-link",
183
+ "split-panel"
184
+ ];
185
+ function tokenize(src) {
186
+ const toks = [];
187
+ let i = 0;
188
+ const n = src.length;
189
+ while (i < n) {
190
+ const c = src[i];
191
+ if (c === " " || c === " " || c === "\n" || c === "\r") {
192
+ i++;
193
+ continue;
194
+ }
195
+ if (c === "/" && src[i + 1] === "/") {
196
+ while (i < n && src[i] !== "\n") i++;
197
+ continue;
198
+ }
199
+ if (c === "#") {
200
+ while (i < n && src[i] !== "\n") i++;
201
+ continue;
202
+ }
203
+ if (c === "/" && src[i + 1] === "*") {
204
+ i += 2;
205
+ while (i < n && !(src[i] === "*" && src[i + 1] === "/")) i++;
206
+ i += 2;
207
+ continue;
208
+ }
209
+ if (c === "'" || c === "\"") {
210
+ const quote = c;
211
+ i++;
212
+ let s = "";
213
+ while (i < n && src[i] !== quote) if (src[i] === "\\" && i + 1 < n) {
214
+ const next = src[i + 1];
215
+ s += next === "n" ? "\n" : next === "t" ? " " : next;
216
+ i += 2;
217
+ } else s += src[i++];
218
+ i++;
219
+ toks.push({
220
+ t: "str",
221
+ v: s
222
+ });
223
+ continue;
224
+ }
225
+ if (c === "=" && src[i + 1] === ">") {
226
+ toks.push({
227
+ t: "arrow",
228
+ v: "=>"
229
+ });
230
+ i += 2;
231
+ continue;
232
+ }
233
+ if ("[](){},".includes(c)) {
234
+ toks.push({
235
+ t: "punc",
236
+ v: c
237
+ });
238
+ i++;
239
+ continue;
240
+ }
241
+ if (c >= "0" && c <= "9" || c === "-" && src[i + 1] >= "0" && src[i + 1] <= "9") {
242
+ let s = c;
243
+ i++;
244
+ while (i < n && src[i] >= "0" && src[i] <= "9") s += src[i++];
245
+ toks.push({
246
+ t: "num",
247
+ v: s
248
+ });
249
+ continue;
250
+ }
251
+ if (/[A-Za-z_\\]/.test(c)) {
252
+ let s = c;
253
+ i++;
254
+ while (i < n && /[A-Za-z0-9_\\]/.test(src[i])) s += src[i++];
255
+ toks.push({
256
+ t: "id",
257
+ v: s
258
+ });
259
+ continue;
260
+ }
261
+ i++;
262
+ }
263
+ return toks;
264
+ }
265
+ var Parser = class {
266
+ p = 0;
267
+ toks;
268
+ constructor(toks) {
269
+ this.toks = toks;
270
+ }
271
+ peek() {
272
+ return this.toks[this.p];
273
+ }
274
+ next() {
275
+ return this.toks[this.p++];
276
+ }
277
+ parseValue() {
278
+ const t = this.peek();
279
+ if (!t) return null;
280
+ if (t.t === "str") {
281
+ this.next();
282
+ return t.v;
283
+ }
284
+ if (t.t === "num") {
285
+ this.next();
286
+ return Number(t.v);
287
+ }
288
+ if (t.t === "id") {
289
+ this.next();
290
+ if (t.v === "true") return true;
291
+ if (t.v === "false") return false;
292
+ if (t.v === "null") return null;
293
+ if (this.peek()?.t === "punc" && this.peek()?.v === "(") {
294
+ this.next();
295
+ if (t.v === "array") return this.parseBody(")");
296
+ const args = this.parseCallArgs();
297
+ return args.find((a) => typeof a === "string") ?? args[0] ?? "";
298
+ }
299
+ return t.v;
300
+ }
301
+ if (t.t === "punc" && t.v === "[") {
302
+ this.next();
303
+ return this.parseBody("]");
304
+ }
305
+ if (t.t === "punc" && t.v === "(") {
306
+ this.next();
307
+ return this.parseBody(")");
308
+ }
309
+ this.next();
310
+ return null;
311
+ }
312
+ parseCallArgs() {
313
+ const args = [];
314
+ while (this.peek() && !(this.peek().t === "punc" && this.peek().v === ")")) {
315
+ args.push(this.parseValue());
316
+ if (this.peek()?.t === "punc" && this.peek()?.v === ",") this.next();
317
+ }
318
+ this.next();
319
+ return args;
320
+ }
321
+ parseBody(close) {
322
+ const list = [];
323
+ const map = {};
324
+ let keyed = false;
325
+ while (this.peek() && !(this.peek().t === "punc" && this.peek().v === close)) {
326
+ const first = this.parseValue();
327
+ if (this.peek()?.t === "arrow") {
328
+ this.next();
329
+ map[String(first)] = this.parseValue();
330
+ keyed = true;
331
+ } else list.push(first);
332
+ if (this.peek()?.t === "punc" && this.peek()?.v === ",") this.next();
333
+ }
334
+ this.next();
335
+ return keyed ? map : list;
336
+ }
337
+ };
338
+ function parsePhpLiteral(text) {
339
+ return new Parser(tokenize(text)).parseValue();
340
+ }
341
+ function matchBalanced(src, open) {
342
+ const pairs = {
343
+ "(": ")",
344
+ "[": "]",
345
+ "{": "}"
346
+ };
347
+ const stack = [];
348
+ let i = open;
349
+ const n = src.length;
350
+ while (i < n) {
351
+ const c = src[i];
352
+ if (c === "/" && src[i + 1] === "/") {
353
+ while (i < n && src[i] !== "\n") i++;
354
+ continue;
355
+ }
356
+ if (c === "/" && src[i + 1] === "*") {
357
+ i += 2;
358
+ while (i < n && !(src[i] === "*" && src[i + 1] === "/")) i++;
359
+ i += 2;
360
+ continue;
361
+ }
362
+ if (c === "'" || c === "\"") {
363
+ i++;
364
+ while (i < n && src[i] !== c) {
365
+ if (src[i] === "\\") i++;
366
+ i++;
367
+ }
368
+ i++;
369
+ continue;
370
+ }
371
+ if (c in pairs) stack.push(pairs[c]);
372
+ else if (c === ")" || c === "]" || c === "}") {
373
+ if (stack.pop() !== c) {}
374
+ if (stack.length === 0) return i + 1;
375
+ }
376
+ i++;
377
+ }
378
+ return n;
379
+ }
380
+ function methodBody(src, name) {
381
+ const m = new RegExp(`function\\s+${name}\\s*\\([^)]*\\)`).exec(src);
382
+ if (!m) return null;
383
+ const brace = src.indexOf("{", m.index + m[0].length);
384
+ if (brace < 0) return null;
385
+ const end = matchBalanced(src, brace);
386
+ return src.slice(brace + 1, end - 1);
387
+ }
388
+ function literalAt(src, fromIdx) {
389
+ let i = fromIdx;
390
+ while (i < src.length && /\s/.test(src[i])) i++;
391
+ let open;
392
+ if (src[i] === "[") open = i;
393
+ else if (src.startsWith("array", i)) open = src.indexOf("(", i);
394
+ else return null;
395
+ const end = matchBalanced(src, open);
396
+ return {
397
+ text: src.slice(i, end),
398
+ end
399
+ };
400
+ }
401
+ function firstString(re, src) {
402
+ const m = re.exec(src);
403
+ return m ? m[1] ?? null : null;
404
+ }
405
+ function extractDocblock(src) {
406
+ const m = /\/\*\*([\s\S]*?)\*\//.exec(src);
407
+ if (!m) return "";
408
+ const lines = m[1].split("\n").map((l) => l.replace(/^\s*\*?\s?/, "").replace(/\s+$/, ""));
409
+ while (lines.length && lines[0] === "") lines.shift();
410
+ if (lines.length && /Bricks Builder\)\./.test(lines[0])) lines.shift();
411
+ const owned = lines.findIndex((l) => l.startsWith("Owned by the framework repo"));
412
+ const kept = (owned >= 0 ? lines.slice(0, owned) : lines).map((l) => l.replace(/^•\s*/, "- "));
413
+ while (kept.length && kept[kept.length - 1] === "") kept.pop();
414
+ while (kept.length && kept[0] === "") kept.shift();
415
+ return kept.join("\n");
416
+ }
417
+ function extractControls(src) {
418
+ const body = methodBody(src, "set_controls");
419
+ if (!body) return [];
420
+ const controls = [];
421
+ const re = /\$this->controls\[\s*'([^']+)'\s*\](\[\s*'([^']+)'\s*\])?\s*=\s*/g;
422
+ let m;
423
+ while (m = re.exec(body)) {
424
+ const key = m[1];
425
+ if (m[2]) continue;
426
+ const lit = literalAt(body, m.index + m[0].length);
427
+ if (!lit) continue;
428
+ const def = parsePhpLiteral(lit.text);
429
+ if (def && typeof def === "object" && !Array.isArray(def)) controls.push({
430
+ key,
431
+ def
432
+ });
433
+ re.lastIndex = lit.end;
434
+ }
435
+ return controls;
436
+ }
437
+ function extractChildren(src) {
438
+ const body = methodBody(src, "get_nestable_children");
439
+ if (!body) return [];
440
+ const labels = [];
441
+ const re = /'label'\s*=>\s*esc_html__\(\s*'([^']*)'/g;
442
+ let m;
443
+ while (m = re.exec(body)) labels.push(m[1]);
444
+ return labels;
445
+ }
446
+ function extractKeywords(src) {
447
+ const body = methodBody(src, "get_keywords");
448
+ if (!body) return [];
449
+ const ret = /return\s+/.exec(body);
450
+ if (!ret) return [];
451
+ const lit = literalAt(body, ret.index + ret[0].length);
452
+ if (!lit) return [];
453
+ const arr = parsePhpLiteral(lit.text);
454
+ return Array.isArray(arr) ? arr.filter((x) => typeof x === "string") : [];
455
+ }
456
+ function parseElement(src) {
457
+ const name = firstString(/public\s+\$name\s*=\s*'vitops-([^']+)'/, src);
458
+ if (!name) return null;
459
+ return {
460
+ name,
461
+ label: firstString(/function get_label\(\)[\s\S]*?return\s+esc_html__\(\s*'([^']*)'/, src) ?? name,
462
+ nestable: /public\s+\$nestable\s*=\s*true/.test(src),
463
+ docblock: extractDocblock(src),
464
+ controls: extractControls(src),
465
+ cssClass: firstString(/\$this->controls\[\s*'_cssClasses'\s*\]\[\s*'default'\s*\]\s*=\s*'([^']*)'/, src),
466
+ children: extractChildren(src),
467
+ keywords: extractKeywords(src)
468
+ };
469
+ }
470
+ function controlLine(c) {
471
+ const def = c.def;
472
+ if (def.type === "info") return `- _Note:_ ${def.content ?? ""}`;
473
+ const label = def.label || c.key;
474
+ const options = def.options && typeof def.options === "object" && !Array.isArray(def.options) ? def.options : null;
475
+ const meta = [];
476
+ let type = def.type || "text";
477
+ if (def.units === true) type += "+units";
478
+ meta.push(type);
479
+ if (def.default !== void 0) {
480
+ const raw = String(def.default);
481
+ const shown = options && options[raw] ? options[raw] : raw;
482
+ meta.push(`default \`${shown}\``);
483
+ }
484
+ if (def.placeholder !== void 0 && def.placeholder !== "") meta.push(`placeholder \`${def.placeholder}\``);
485
+ if (def.min !== void 0 || def.max !== void 0) meta.push(`range ${def.min ?? "–"}–${def.max ?? "∞"}`);
486
+ const tail = [];
487
+ if (def.description) tail.push(String(def.description));
488
+ if (options) tail.push(`Options: ${Object.values(options).map((o) => `\`${o}\``).join(", ")}.`);
489
+ if (Array.isArray(def.css) && def.css.length) {
490
+ const first = def.css[0];
491
+ if (first && typeof first === "object" && !Array.isArray(first) && "property" in first) tail.push(`Bound to \`${first.property}\`.`);
492
+ }
493
+ if (def.type === "repeater" && def.fields && typeof def.fields === "object") {
494
+ const fields = Object.values(def.fields).map((f) => f && typeof f === "object" && !Array.isArray(f) ? f.label : null).filter(Boolean);
495
+ if (fields.length) tail.push(`Fields: ${fields.join(", ")}.`);
496
+ }
497
+ const head = `- **${label}** (${meta.join(", ")})`;
498
+ return tail.length ? `${head} — ${tail.join(" ")}` : head;
499
+ }
500
+ function renderElement(el) {
501
+ const out = [];
502
+ const nest = el.nestable ? "nestable" : "not nestable";
503
+ out.push(`## ${el.label} — \`vitops-${el.name}\` · ${nest}`);
504
+ out.push("");
505
+ if (el.docblock) {
506
+ out.push(el.docblock);
507
+ out.push("");
508
+ }
509
+ if (el.controls.filter((c) => c.def.type !== "info").length) {
510
+ out.push("**Controls**");
511
+ out.push("");
512
+ for (const c of el.controls) out.push(controlLine(c));
513
+ out.push("");
514
+ } else {
515
+ const notes = el.controls.filter((c) => c.def.type === "info");
516
+ if (notes.length) {
517
+ for (const c of notes) out.push(controlLine(c));
518
+ out.push("");
519
+ }
520
+ }
521
+ if (el.cssClass) {
522
+ out.push(`**Base CSS class:** \`${el.cssClass}\` (applied automatically; add ratio/modifier classes alongside it in the element's "CSS classes" field).`);
523
+ out.push("");
524
+ }
525
+ if (el.children.length) {
526
+ out.push(`**Seeded children:** ${el.children.join(", ")}.`);
527
+ out.push("");
528
+ }
529
+ if (el.keywords.length) {
530
+ out.push(`**Keywords:** ${el.keywords.filter((k) => k !== "vitops").join(", ")}.`);
531
+ out.push("");
532
+ }
533
+ return out.join("\n");
534
+ }
535
+ function frontmatter(fm) {
536
+ const q = (s) => `"${s.replace(/"/g, "\\\"")}"`;
537
+ const lines = [
538
+ `type: ${q(fm.type)}`,
539
+ `title: ${q(fm.title)}`,
540
+ `description: ${q(fm.description)}`
541
+ ];
542
+ if (fm.resource) lines.push(`resource: ${q(fm.resource)}`);
543
+ if (fm.tags) lines.push(`tags: [${fm.tags.join(", ")}]`);
544
+ lines.push("generator: \"@getvitops/generator\"");
545
+ return `---\n${lines.join("\n")}\n---`;
546
+ }
547
+ const INDEX_NOTE = `<!-- GENERATED by @getvitops/generator — do not edit; regenerate with \`vitops generate\`. -->`;
548
+ const ELEMENTS_PREAMBLE = `${frontmatter({
549
+ type: "Bricks Elements Reference",
550
+ title: "Vitops — Bricks Builder elements reference",
551
+ description: "Every repo-owned Bricks element, its controls, defaults, seeded children and keywords, for driving the Bricks Builder UI.",
552
+ resource: "bricks/elements",
553
+ tags: [
554
+ "bricks",
555
+ "wordpress",
556
+ "elements",
557
+ "design-system"
558
+ ]
559
+ })}
560
+
561
+ # Vitops — Bricks Builder elements reference
562
+
563
+ Context for driving the Bricks Builder UI (e.g. Claude for Chrome). Every section below
564
+ is generated from the element's own source, so this reference always matches the elements
565
+ currently deployed. See also the CSS class vocabulary in [/css/classes.md](../css/classes.md)
566
+ and the integration guidance in [/bricks/index.md](index.md).
567
+
568
+ ## How they appear in the builder
569
+
570
+ - The elements register from \`dist/bricks/load.php\` and group under a **"Vitops"** category
571
+ pinned to the **top** of the element panel. Insert one by searching its **label** (e.g.
572
+ "Split", "Carousel") or any of its **keywords** (listed per element).
573
+ - Two rendering families:
574
+ - **Web-component elements** render a Lit custom element (\`<wc-*>\`, \`<copy-button>\`, …)
575
+ from \`dist/elements.js\` and progressively enhance. A few (Image Compare, Split Panel)
576
+ show their children stacked in the builder canvas until the client-side upgrade runs —
577
+ that is expected, not broken.
578
+ - **CSS-pattern elements** (Split, Centered, Menu, Split Link) render plain markup styled
579
+ by the framework CSS — no JS dependency.
580
+ - **Nestable** elements accept child elements dropped into them; some seed starter children
581
+ (listed as "Seeded children"). **Non-nestable** elements are configured entirely through
582
+ their controls.
583
+
584
+ ## Configuring layout
585
+
586
+ Some layout is set through **controls**; some through **CSS classes typed into the element's
587
+ built-in "CSS classes" field** (called out per element as modifier classes). Responsive
588
+ suffixes on those classes engage from a container breakpoint: \`-sm\` = 30rem, \`-md\` = 48rem,
589
+ \`-lg\` = 64rem, \`-xl\` = 80rem.
590
+
591
+ Each control below lists its type, default/placeholder, any select options, and the CSS
592
+ custom property it writes (when it drives one). Info notes flagged _Note:_ carry the
593
+ modifier-class and structural guidance from the element itself.
594
+
595
+ ---
596
+ `;
597
+ function renderElementsDoc(elementsDir) {
598
+ const files = existsSync(elementsDir) ? readdirSync(elementsDir).filter((f) => f.endsWith(".php")) : [];
599
+ const elements = [];
600
+ for (const f of files) try {
601
+ const el = parseElement(readFileSync(join(elementsDir, f), "utf8"));
602
+ if (el) elements.push(el);
603
+ } catch {}
604
+ elements.sort((a, b) => {
605
+ const ia = ORDER.indexOf(a.name);
606
+ const ib = ORDER.indexOf(b.name);
607
+ if (ia >= 0 && ib >= 0) return ia - ib;
608
+ if (ia >= 0) return -1;
609
+ if (ib >= 0) return 1;
610
+ return a.name.localeCompare(b.name);
611
+ });
612
+ return `${ELEMENTS_PREAMBLE}\n${elements.map(renderElement).join("\n---\n\n")}`;
613
+ }
614
+ const code = (xs) => xs.map((x) => `\`${x}\``).join(", ");
615
+ function renderCssClasses(ds) {
616
+ const palette = ds.colors.palette;
617
+ const ramps = Object.keys(palette);
618
+ const firstRamp = ramps[0];
619
+ const nonBase = (firstRamp ? Object.keys(palette[firstRamp] ?? {}) : []).filter((s) => s !== "base");
620
+ const roles = Object.keys(ds.colors.schemes.default?.semantic ?? {});
621
+ const utils = ds.colors.utilities ?? [
622
+ "bg",
623
+ "text",
624
+ "border"
625
+ ];
626
+ const typeRoles = Object.keys(ds.typography?.roles ?? {});
627
+ const shadows = Object.keys(ds.shadows ?? {});
628
+ const effects = Object.keys(ds.animations?.effects ?? {});
629
+ const items = ds.patterns?.items ?? {};
630
+ const patterns = Object.keys(items);
631
+ const patternRoles = Array.from(new Set(Object.values(items).flatMap((p) => [p.default_role, ...p.roles ?? []].filter(Boolean))));
632
+ const spaceNames = ds.spaceScale?.names ?? [];
633
+ const typeNames = ds.typeScale?.names ?? [];
634
+ const families = Object.keys(ds.typography?.families ?? {});
635
+ const radii = Object.keys(ds.patterns?.radii ?? {});
636
+ return `${frontmatter({
637
+ type: "CSS Framework Reference",
638
+ title: "Vitops CSS framework — class vocabulary",
639
+ description: "Every utility and component class in the Vitops CSS framework, stated as a naming rule over the design tokens it expands.",
640
+ resource: DS_PATH,
641
+ tags: [
642
+ "css",
643
+ "utilities",
644
+ "patterns",
645
+ "design-system"
646
+ ]
647
+ })}
648
+
649
+ # Vitops CSS framework — class vocabulary (LLM reference)
650
+
651
+ A variable-driven CSS framework. Classes encode the design system's **tokens** (colour,
652
+ type, space, shadow), **responsive grammar**, and **interaction states** — so styling with
653
+ these classes stays consistent and theme-/dark-mode-aware. Prefer them over hand-written
654
+ CSS or ad-hoc property values.
655
+
656
+ This is a **rule reference**, not an exhaustive list: each family below is a naming rule
657
+ plus the set of tokens it expands over. Applying a rule to any listed token yields a valid
658
+ class (e.g. rule \`bg-<color>\` + colour \`pine-xl\` → \`bg-pine-xl\`).
659
+
660
+ ## Responsive & state variant grammar
661
+
662
+ Every utility accepts a **container-breakpoint prefix**; animation utilities also accept a
663
+ **state prefix**. In CSS/Bricks the separator is \`-\`; in Tailwind it is \`:\` / \`@\`.
664
+
665
+ | Intent | CSS / Bricks | Tailwind |
666
+ | ----------------- | -------------------------- | ----------------------------------------- |
667
+ | responsive split | \`md-split-1-2\` | \`@md:split-1-2\` |
668
+ | responsive align | \`md-items-center\` | \`@md:items-center\` |
669
+ | hover effect | \`transition hover-fade-in\` | \`transition fade-in hover:flip-fade-in\` |
670
+
671
+ - Breakpoint prefixes (bare): \`sm-\` = 30rem, \`md-\` = 48rem, \`lg-\` = 64rem, \`xl-\` = 80rem.
672
+ - State prefixes (animation effects only): \`hover-\`, \`active-\`, \`focus-\`, and \`flip-<effect>\`
673
+ (plays the effect in reverse on toggle). Effects require \`transition\` on the element.
674
+
675
+ ## Layout & structure
676
+
677
+ - **\`centered\`** — named-track grid centering content in the reading \`measure\` track.
678
+ Widen a **direct child** by adding \`breakout\`, \`spotlight\`, or \`fullbleed\` (each
679
+ breakpoint-prefixable) to that child. Track widths are set via \`--width-measure\` /
680
+ \`--width-breakout\` / \`--width-spotlight\` and \`--gutter\`.
681
+ - **\`rhythm\`** — relationship-based vertical spacing (margins between headings, paragraphs,
682
+ lists, media) driven by the space scale. Usually paired with \`centered\`.
683
+ - **\`split\`** — equal flex columns. Ratio rule: **\`split-<a>-<b>\`** where \`<a>-<b>\` ∈
684
+ \`1-2\`, \`2-1\`, \`1-3\`, \`3-1\`, \`1-4\`, \`4-1\`, \`2-3\`, \`3-2\` (breakpoint-prefixable).
685
+ - **Flex** — \`flex\`, \`flex-row\`, \`flex-col\`; \`g\` for gap (space-scale token).
686
+ - **Alignment** — \`items-{start,center,end,stretch}\`, \`justify-{start,center,end,between}\`,
687
+ text align \`text-{start,center,end}\` (all breakpoint-prefixable).
688
+ - **Display** — \`block\`, \`inline\`, \`inline-block\`, \`flex\`, \`grid\`, \`hidden\`
689
+ (breakpoint-prefixable, e.g. \`md-hidden\`).
690
+ - **Accessibility** — \`sr-only\` / \`not-sr-only\` (breakpoint-prefixable).
691
+ - **State hooks** — \`is-active\`, \`is-open\` (styling flags toggled by JS / native state).
692
+
693
+ ## Spacing
694
+
695
+ The space scale is ${code(spaceNames)} exposed as \`--space-<name>\` tokens.
696
+ Gap (\`g\`) and \`rhythm\` margins consume these tokens; prefer \`rhythm\` for vertical flow
697
+ rather than per-element margins.
698
+
699
+ ## Typography
700
+
701
+ Rule: **\`font-<role>\`** — role ∈ ${code(typeRoles)}. Each role carries its own family,
702
+ size (from the type scale ${code(typeNames)}), tracking, transform, and weight.
703
+ Families: ${code(families)} (\`--font-*\`).
704
+
705
+ ## Colour
706
+
707
+ Rule: **\`<util>-<color>\`** — util ∈ ${code(utils)}; \`<color>\` is either a **named ramp**
708
+ step or a **semantic role**.
709
+
710
+ - Named ramps: ${code(ramps)}. Steps: base (no suffix) + ${code(nonBase)} — e.g. \`bg-pine\`
711
+ (base), \`text-navy-xl\`, \`border-grey-d\`.
712
+ - Semantic roles: ${code(roles)} — e.g. \`bg-brand-primary\`, \`text-danger\`. Roles remap
713
+ automatically under \`:root[data-brx-theme="dark"]\`.
714
+
715
+ ## Shadows
716
+
717
+ Rule: **\`drop-shadow-<size>\`** — size ∈ ${code(shadows)} (applied as a \`filter\`, so it
718
+ follows non-rectangular shapes).
719
+
720
+ ## Animation
721
+
722
+ Rule: **\`<effect>\`** (with the state/flip prefixes above) — effect ∈ ${code(effects)}.
723
+ Composed **journeys** chain multiple effects into one entrance: rule \`<parts>-journey\`
724
+ (e.g. \`fade-slide-journey\`, \`fade-scale-blur-journey\`). All require \`transition\` on the
725
+ element; scroll-linked entrances resolve within the first portion of the element's scroll.
726
+
727
+ ## Component patterns
728
+
729
+ Each pattern is a base class \`<pattern>\` with interaction states (hover/active/focus-visible)
730
+ baked in; coloured patterns add role variants via rule **\`<pattern>-<role>\`**.
731
+
732
+ - Patterns: ${code(patterns)}.
733
+ - Roles (for coloured patterns like \`badge\`, \`tag\`, \`status\`, \`button\`):
734
+ ${code(patternRoles)} — e.g. \`badge-success\`, \`button-danger\`. The default (unsuffixed)
735
+ variant uses the brand-primary role.
736
+ - Shape primitives: \`--br-<name>\` radii — ${code(radii)}.
737
+ `;
738
+ }
739
+ function renderTopIndex() {
740
+ return `${INDEX_NOTE}
741
+
742
+ # Vitops design system
743
+
744
+ LLM-oriented documentation bundle (Open Knowledge Format) for the Vitops design system — a
745
+ variable-driven CSS framework plus progressively-enhanced web components, generated from
746
+ \`design-system.json\` and published alongside the deployed theme.
747
+
748
+ # Contents
749
+
750
+ * [CSS framework](css/) - the class vocabulary (colour, type, space, layout, animation, component patterns), stated as naming rules
751
+ * [Bricks Builder](bricks/) - custom elements and how to style them
752
+ `;
753
+ }
754
+ function renderCssIndex() {
755
+ return `${INDEX_NOTE}
756
+
757
+ # CSS framework
758
+
759
+ # Contents
760
+
761
+ * [Class vocabulary](classes.md) - every utility and component class, summarized by naming rule
762
+ `;
763
+ }
764
+ function renderBricksIndex() {
765
+ return `${INDEX_NOTE}
766
+
767
+ # Bricks Builder integration
768
+
769
+ # Styling
770
+
771
+ Prefer adding the framework **CSS classes** — see [/css/classes.md](../css/classes.md) — in a
772
+ Bricks element's "CSS classes" field over hand-setting spacing, colour, or typography through
773
+ the Bricks Builder property panels. The classes encode the design system's tokens, responsive
774
+ grammar, and interaction states, so they stay consistent, respond to theme / dark mode, and
775
+ update with the system. Values typed directly into Bricks' property controls are one-off,
776
+ drift from the system, and don't react to theme changes — reserve them for genuinely bespoke,
777
+ one-element cases.
778
+
779
+ # Contents
780
+
781
+ * [Elements reference](elements.md) - every custom Vitops element, its controls, defaults, seeded children and keywords
782
+ * [Class vocabulary](../css/classes.md) - the framework CSS classes to apply to elements
783
+ `;
784
+ }
785
+ /**
786
+ * Build the OKF docs bundle for a config. Returns a `{ relPath: content }` map;
787
+ * the caller writes it wherever the bundle should live (e.g. `<outDir>/docs`).
788
+ */
789
+ function generateDocs(ds, assetsDir) {
790
+ const elementsDir = join(assetsDir, "bricks", "elements");
791
+ return {
792
+ "index.md": renderTopIndex(),
793
+ "css/index.md": renderCssIndex(),
794
+ "css/classes.md": renderCssClasses(ds),
795
+ "bricks/index.md": renderBricksIndex(),
796
+ "bricks/elements.md": renderElementsDoc(elementsDir)
797
+ };
798
+ }
799
+
800
+ //#endregion
801
+ //#region src/generate.ts
802
+ /**
803
+ * Pure, parameterized port of the Vitops design-system generator.
804
+ *
805
+ * `generate({ input, format, outDir })` reads a consumer's `design-system.json`,
806
+ * validates it, and emits consumer-ready output for the chosen format into
807
+ * `outDir`. Unlike the original CWD-bound script it mutates no shared state: every
808
+ * generated token layer is built in memory and either bundled (css/bricks) or
809
+ * inlined (tailwind) before writing.
810
+ *
811
+ * Framework-static assets (the hand-written CSS partials, the Bricks PHP + loader,
812
+ * and the pre-built JS bundles) live under `assets/` in this package and are read /
813
+ * copied from there — they are the same for every consumer.
814
+ */
815
+ const DEFAULT_ASSETS = join(dirname(fileURLToPath(import.meta.url)), "..", "assets");
816
+ const STEP_ORDER = [
817
+ "xxd",
818
+ "xd",
819
+ "d",
820
+ "base",
821
+ "l",
822
+ "xl",
823
+ "xxl"
824
+ ];
825
+ const suffix = (step) => step === "base" ? "" : `-${step}`;
826
+ const MIRROR = {
827
+ xxd: "xxl",
828
+ xd: "xl",
829
+ d: "l",
830
+ base: "base",
831
+ l: "d",
832
+ xl: "xd",
833
+ xxl: "xxd"
834
+ };
835
+ const STEP_INDEX = Object.fromEntries(STEP_ORDER.map((s, i) => [s, i]));
836
+ const shiftStep = (step, by) => {
837
+ const i = STEP_INDEX[step] ?? STEP_ORDER.indexOf("base");
838
+ return STEP_ORDER[Math.max(0, Math.min(STEP_ORDER.length - 1, i - by))];
839
+ };
840
+ const BASE_STEP = "d";
841
+ const UTILITY_PROPS = {
842
+ bg: "background-color",
843
+ text: "color",
844
+ border: "border-color",
845
+ outline: "outline-color",
846
+ fill: "fill",
847
+ stroke: "stroke"
848
+ };
849
+ const roleRamp = (spec, inherited = "") => typeof spec === "string" ? spec : spec.ramp ?? inherited;
850
+ const resolveSlot = (spec, step, inherited) => {
851
+ if (typeof spec === "string") return {
852
+ ramp: spec,
853
+ step
854
+ };
855
+ if (spec.value?.[step] != null) return { value: spec.value[step] };
856
+ return {
857
+ ramp: spec.ramp ?? inherited,
858
+ step: spec.steps?.[step] ?? shiftStep(spec.invert ? MIRROR[step] : step, spec.shift ?? 0)
859
+ };
860
+ };
861
+ const srcVar = (s) => "value" in s ? s.value : `var(--color-${s.ramp}${suffix(s.step)})`;
862
+ const srcEq = (a, b) => "value" in a || "value" in b ? "value" in a && "value" in b && a.value === b.value : a.ramp === b.ramp && a.step === b.step;
863
+ const decls = (obj, indent = " ") => Object.entries(obj).map(([k, v]) => `${indent}${k}: ${v};`).join("\n");
864
+ const round = (n) => Number(n.toFixed(4)).toString();
865
+ const rem = (v) => parseFloat(v);
866
+ const buildScale = (s) => {
867
+ const names = s.names ?? Array.from({ length: s.steps ?? 0 }, (_, i) => String(i + 1));
868
+ const base = rem(s.base);
869
+ const baseStep = s.baseStep ?? Math.ceil(names.length / 2);
870
+ const baselineIdx = s.baseline ? names.indexOf(s.baseline) : baseStep - 1;
871
+ return names.map((name, i) => {
872
+ const k = i - (baseStep - 1);
873
+ const vMax = base * Math.pow(s.ratio, k);
874
+ const max = `${round(vMax)}rem`;
875
+ let value = max;
876
+ if (s.fluid) {
877
+ const { minVw, maxVw, minRatio } = s.fluid;
878
+ const vMin = base * Math.pow(minRatio, k);
879
+ const lo = Math.min(vMin, vMax);
880
+ const hi = Math.max(vMin, vMax);
881
+ if (lo !== hi) {
882
+ const slope = (vMax - vMin) / (rem(maxVw) - rem(minVw));
883
+ const intercept = vMin - slope * rem(minVw);
884
+ const sign = slope < 0 ? "-" : "+";
885
+ const pref = `calc(${round(intercept)}rem ${sign} ${round(Math.abs(slope * 100))}vw)`;
886
+ value = `clamp(${round(lo)}rem, ${pref}, ${round(hi)}rem)`;
887
+ }
888
+ }
889
+ return {
890
+ name,
891
+ value,
892
+ max,
893
+ offset: i - baselineIdx
894
+ };
895
+ });
896
+ };
897
+ const TYPO_KEYMAP = {
898
+ family: ["font-family", "ff"],
899
+ size: ["font-size", "fs"],
900
+ weight: ["font-weight", "fw"],
901
+ style: ["font-style", "fst"],
902
+ "line-height": ["line-height", "lh"],
903
+ tracking: ["letter-spacing", "ls"],
904
+ "text-decoration": ["text-decoration", "td"],
905
+ "text-transform": ["text-transform", "tt"],
906
+ "text-wrap": ["text-wrap", "tw"],
907
+ color: ["color", "color"]
908
+ };
909
+ const TYPO_IDENTITY = {
910
+ "font-style": "normal",
911
+ "letter-spacing": "normal",
912
+ "text-decoration": "none",
913
+ "text-transform": "none",
914
+ "text-wrap": "wrap"
915
+ };
916
+ const STATE_DEFAULT = {
917
+ opacity: "1",
918
+ "translate-x": "0",
919
+ "translate-y": "0",
920
+ "scale-x": "1",
921
+ "scale-y": "1",
922
+ rotate: "0deg",
923
+ blur: "0px",
924
+ shadow: "0 0 0 transparent",
925
+ brightness: "1",
926
+ grayscale: "0",
927
+ contrast: "1",
928
+ saturate: "1",
929
+ sepia: "0",
930
+ "hue-rotate": "0deg",
931
+ clip: "none"
932
+ };
933
+ const BASE_HOOK = {
934
+ padding: "p",
935
+ "border-radius": "br",
936
+ border: "b",
937
+ "box-shadow": "ds",
938
+ "font-size": "fs"
939
+ };
940
+ const id = (key) => createHash("sha256").update(key).digest("hex").slice(0, 6);
941
+ function build(ds, format, assetsDir) {
942
+ const BRICKS = format === "bricks";
943
+ const palette = ds.colors.palette;
944
+ const schemes = ds.colors.schemes;
945
+ const base = schemes.default;
946
+ if (base == null) throw new Error("colors.schemes must include a \"default\" scheme");
947
+ const defaultSemantic = base.semantic;
948
+ const altSchemes = Object.entries(schemes).filter(([k]) => k !== "default");
949
+ const darkScheme = altSchemes.map(([, s]) => s).find((s) => s.appearance === "dark");
950
+ const patterns = ds.patterns;
951
+ const shadows = ds.shadows;
952
+ const typography = ds.typography;
953
+ const UTILITIES = (ds.colors.utilities ?? [
954
+ "bg",
955
+ "text",
956
+ "border"
957
+ ]).filter((u) => u in UTILITY_PROPS);
958
+ const generated = {};
959
+ if (BRICKS) generated["color.css"] = "/* Colours provided by Bricks (palette import generates tokens + utilities). */\n";
960
+ else {
961
+ let css = `/* GENERATED from design-system.json — do not edit by hand. */\n:root {\n`;
962
+ css += ` color-scheme: ${base.appearance};\n`;
963
+ css += ` /* Palette ramps */\n`;
964
+ for (const [name, steps] of Object.entries(palette)) for (const step of STEP_ORDER) if (steps[step] != null) css += ` --color-${name}${suffix(step)}: ${steps[step]};\n`;
965
+ css += `\n /* Semantic roles → ramps (default: ${base.appearance}) */\n`;
966
+ for (const [role, spec] of Object.entries(defaultSemantic)) {
967
+ const ramp = roleRamp(spec);
968
+ for (const step of STEP_ORDER) if (palette[ramp]?.[step] != null) css += ` --color-${role}${suffix(step)}: ${srcVar(resolveSlot(spec, step, ramp))};\n`;
969
+ }
970
+ css += `}\n\n`;
971
+ for (const [key, scheme] of altSchemes) {
972
+ let block = "";
973
+ for (const [role, spec] of Object.entries(scheme.semantic)) {
974
+ const baseSpec = defaultSemantic[role];
975
+ if (baseSpec == null) continue;
976
+ const ramp = roleRamp(baseSpec);
977
+ for (const step of STEP_ORDER) {
978
+ if (palette[ramp]?.[step] == null) continue;
979
+ const alt = resolveSlot(spec, step, ramp);
980
+ if (srcEq(alt, resolveSlot(baseSpec, step, ramp))) continue;
981
+ block += ` --color-${role}${suffix(step)}: ${srcVar(alt)};\n`;
982
+ }
983
+ }
984
+ if (block) {
985
+ css += `/* Semantic roles → ramps (${key}: ${scheme.appearance}) */\n`;
986
+ css += `:root[data-brx-theme="${scheme.appearance}"] {\n color-scheme: ${scheme.appearance};\n${block}}\n\n`;
987
+ }
988
+ }
989
+ const allTokens = [];
990
+ for (const [name, steps] of Object.entries(palette)) for (const step of STEP_ORDER) if (steps[step] != null) allTokens.push(`${name}${suffix(step)}`);
991
+ for (const [role, spec] of Object.entries(defaultSemantic)) {
992
+ const ramp = roleRamp(spec);
993
+ for (const step of STEP_ORDER) if (palette[ramp]?.[step] != null) allTokens.push(`${role}${suffix(step)}`);
994
+ }
995
+ css += `/* Colour utilities — ${UTILITIES.join(", ")} */\n`;
996
+ for (const token of allTokens) css += UTILITIES.map((cls) => `.${cls}-${token} { ${UTILITY_PROPS[cls]}: var(--color-${token}); }`).join("\n") + "\n";
997
+ generated["color.css"] = css;
998
+ }
999
+ {
1000
+ const entries = Object.entries(shadows ?? {});
1001
+ let sh = `/* GENERATED drop-shadow tokens + utilities — do not edit by hand. */\n`;
1002
+ if (entries.length) {
1003
+ sh += `:root {\n`;
1004
+ for (const [name, value] of entries) sh += ` --shadow-${name}: ${value};\n`;
1005
+ sh += `}\n\n`;
1006
+ for (const [name] of entries) sh += `.drop-shadow-${name} { filter: drop-shadow(var(--shadow-${name})); }\n`;
1007
+ }
1008
+ generated["shadows.css"] = sh;
1009
+ }
1010
+ const typeSteps = ds.typeScale ? buildScale(ds.typeScale) : [];
1011
+ const spaceSteps = ds.spaceScale ? buildScale(ds.spaceScale) : [];
1012
+ const textMax = Object.fromEntries(typeSteps.map((s) => [`text-${s.name}`, s.max]));
1013
+ const typographyFamilies = typography?.families ?? {};
1014
+ const withScaleFallback = (v) => v.replace(/var\(\s*(--text-[\w-]+)\s*\)/g, (m, name) => textMax[name.slice(2)] ? `var(${name}, ${textMax[name.slice(2)]})` : m);
1015
+ const roleDecls = (role, spec) => {
1016
+ const out = {};
1017
+ for (const [key, [prop, sfx]] of Object.entries(TYPO_KEYMAP)) {
1018
+ const identity = TYPO_IDENTITY[prop];
1019
+ if (spec[key] == null && identity == null) continue;
1020
+ out[prop] = `var(--${role}-${sfx}, ${spec[key] == null ? identity : key === "family" ? typographyFamilies[spec.family] ?? String(spec.family) : key === "size" ? withScaleFallback(String(spec[key])) : String(spec[key])})`;
1021
+ }
1022
+ return out;
1023
+ };
1024
+ let twTypeTokensCss = "";
1025
+ if (BRICKS) generated["type-tokens.css"] = "/* Fonts + type/space scales provided by Bricks (Font + Variables managers). */\n";
1026
+ else {
1027
+ const root = {};
1028
+ for (const [name, stack] of Object.entries(ds.fonts ?? {})) root[`--font-${name}`] = stack;
1029
+ for (const s of typeSteps) root[`--text-${s.name}`] = s.value;
1030
+ for (const s of spaceSteps) root[`--space-${s.name}`] = s.value;
1031
+ const css = `/* GENERATED font families + fluid type/space scales — do not edit by hand. */\n:root {\n${decls(root)}\n}\n`;
1032
+ twTypeTokensCss = css;
1033
+ generated["type-tokens.css"] = css;
1034
+ }
1035
+ let twPatternTokensCss = "";
1036
+ {
1037
+ const tDefaults = patterns?.defaults ?? {};
1038
+ const tRadii = patterns?.radii ?? {};
1039
+ const tGroups = patterns?.groups ?? {};
1040
+ const tZ = patterns?.z ?? {};
1041
+ const tItems = patterns?.items ?? {};
1042
+ const root = {};
1043
+ for (const [prop, val] of Object.entries(tDefaults)) root[`--${prop}-default`] = val;
1044
+ if (!BRICKS) for (const [name, val] of Object.entries(tRadii)) root[`--br-${name}`] = val;
1045
+ for (const [group, props] of Object.entries(tGroups)) for (const [prop, val] of Object.entries(props)) root[`--${prop}-${group}`] = val;
1046
+ for (const [name, n] of Object.entries(tZ)) root[`--z-tier-${name}`] = String(n);
1047
+ const aliases = {};
1048
+ for (const [name, p] of Object.entries(tItems)) {
1049
+ if (!p.group) continue;
1050
+ for (const prop of Object.keys(tGroups[p.group] ?? {})) aliases[`--${prop}-${name}-group`] = `var(--${prop}-${p.group})`;
1051
+ for (const [prop, val] of Object.entries(p.overrides ?? {})) aliases[`--${prop}-${name}-group`] = val;
1052
+ }
1053
+ let tok = `/* GENERATED pattern token cascade — do not edit by hand. */\n:root {\n`;
1054
+ tok += decls(root) + "\n";
1055
+ if (Object.keys(aliases).length) tok += "\n" + decls(aliases) + "\n";
1056
+ tok += `}\n`;
1057
+ twPatternTokensCss = tok;
1058
+ generated["tokens.css"] = tok;
1059
+ }
1060
+ {
1061
+ const roles = typography?.roles ?? {};
1062
+ const headings = typography?.headings ?? {};
1063
+ let typo = `/* GENERATED typography roles — do not edit by hand. */\n`;
1064
+ for (const [role, spec] of Object.entries(roles)) typo += `.font-${role} {\n${decls(roleDecls(role, spec))}\n}\n`;
1065
+ for (const [tag, role] of Object.entries(headings)) if (roles[role]) typo += `${tag} {\n${decls(roleDecls(role, roles[role]))}\n}\n`;
1066
+ generated["typography.css"] = typo;
1067
+ }
1068
+ const stateRules = (sel, role, states, colorProp = "background-color") => {
1069
+ let out = "";
1070
+ const roleVar = (step) => role ? `var(--color-${role}-${step})` : "";
1071
+ for (const [state, spec] of Object.entries(states)) {
1072
+ const body = [];
1073
+ if (typeof spec.step === "number" && role) body.push(`${colorProp}: ${roleVar(shiftStep(BASE_STEP, spec.step))};`);
1074
+ if (typeof spec.scale === "number") body.push(`scale: ${spec.scale};`);
1075
+ if (typeof spec.lift === "string") body.push(`translate: 0 calc(-1 * ${spec.lift});`);
1076
+ if (typeof spec.shadow === "string" && shadows?.[spec.shadow]) body.push(`filter: drop-shadow(var(--shadow-${spec.shadow}));`);
1077
+ else if (spec.shadow === true) body.push(`box-shadow: var(--lift-shadow, 0 8px 20px -6px rgb(0 0 0 / 0.25));`);
1078
+ if (spec.ring === true) {
1079
+ const ringColor = role ? `var(--color-${role}-l)` : `var(--color-brand-primary-l)`;
1080
+ body.push(`outline: none;`, `box-shadow: 0 0 0 3px ${ringColor};`);
1081
+ }
1082
+ if (spec.css && typeof spec.css === "object") for (const [k, v] of Object.entries(spec.css)) body.push(`${k}: ${v};`);
1083
+ if (!body.length) continue;
1084
+ const rule = `${sel}${state === "hover" ? ":hover" : state === "active" ? ":active" : state === "focus-visible" ? ":focus-visible" : `:${state}`} {\n ${body.join("\n ")}\n}\n`;
1085
+ out += state === "hover" ? `@media (hover: hover) {\n${rule}}\n` : rule;
1086
+ }
1087
+ return out;
1088
+ };
1089
+ let pat = `/* GENERATED component patterns — do not edit by hand. */\n`;
1090
+ pat += `/* Interaction transitions for patterned elements (independent props compose). */\n`;
1091
+ for (const [pname, p] of Object.entries(patterns?.items ?? {})) {
1092
+ if (!p.base) continue;
1093
+ const states = p.states ?? {};
1094
+ const isElement = !!p.element;
1095
+ const defaultSel = isElement ? `:where(${p.element})` : `.${p.class ?? pname}`;
1096
+ const defaultRole = p.default_role ?? null;
1097
+ const base = { ...p.base };
1098
+ const fills = pname === "button" || pname === "badge" || base["background-color"] != null || base["background"] != null;
1099
+ const colorProp = fills ? "background-color" : "color";
1100
+ pat += `${defaultSel} {\n`;
1101
+ if (Object.keys(states).length > 0) {
1102
+ pat += ` transition-property: translate, scale, rotate, filter, box-shadow, background-color, color;\n`;
1103
+ pat += ` transition-duration: var(--interact-duration, 200ms);\n`;
1104
+ pat += ` transition-timing-function: var(--interact-easing, ease);\n`;
1105
+ pat += ` translate: 0 0; scale: 1; rotate: 0deg;\n`;
1106
+ }
1107
+ if (defaultRole && fills && base["background-color"] == null && base["background"] == null) base["background-color"] = `var(--color-${defaultRole}-${BASE_STEP})`;
1108
+ const wrappedBase = {};
1109
+ for (const [prop, val] of Object.entries(base)) {
1110
+ const sfx = BASE_HOOK[prop];
1111
+ wrappedBase[prop] = sfx ? `var(--${sfx}-${pname}, ${val})` : String(val);
1112
+ }
1113
+ pat += decls(wrappedBase) + "\n}\n";
1114
+ pat += stateRules(defaultSel, defaultRole, states, colorProp);
1115
+ for (const role of p.roles ?? []) {
1116
+ const variantSel = isElement ? `${p.element}.${role}` : `.${p.class ?? pname}-${role}`;
1117
+ const variantColorDecl = fills ? `background-color: var(--color-${role}-${BASE_STEP})` : `color: var(--color-${role}-${BASE_STEP})`;
1118
+ pat += `${variantSel} { ${variantColorDecl}; }\n`;
1119
+ pat += stateRules(variantSel, role, states, colorProp);
1120
+ }
1121
+ pat += "\n";
1122
+ }
1123
+ generated["patterns.css"] = pat;
1124
+ {
1125
+ const anim = ds.animations ?? {};
1126
+ const asStr = (v) => String(v);
1127
+ const block = (sel, d) => `.${sel} {\n${decls(d)}\n}\n`;
1128
+ let out = `/* GENERATED animation effects + journeys — do not edit by hand. */\n`;
1129
+ for (const [name, e] of Object.entries(anim.effects ?? {})) {
1130
+ const d = { "--_anim": e.kf };
1131
+ for (const [k, v] of Object.entries(e.css ?? {})) d[k] = asStr(v);
1132
+ for (const [k, v] of Object.entries(e.vars ?? {})) d[`--${k}`] = asStr(v);
1133
+ out += block(name, d);
1134
+ }
1135
+ const base = anim.journeys?.base ?? {};
1136
+ for (const parts of anim.journeys?.compose ?? []) {
1137
+ const d = {
1138
+ "--_anim": parts.map((p) => `${p}-journey`).join(", "),
1139
+ "animation-range": "entry exit"
1140
+ };
1141
+ for (const p of parts) for (const [k, v] of Object.entries(base[p] ?? {})) d[`--${k}`] = asStr(v);
1142
+ out += block(`${parts.join("-")}-journey`, d);
1143
+ }
1144
+ const STATES = [
1145
+ ["hover", (s) => `.${s}:hover`],
1146
+ ["focus", (s) => `.${s}:focus-visible`],
1147
+ ["active", (s) => `.${s}.is-active, .${s}[data-active]`]
1148
+ ];
1149
+ out += `\n/* State-flip variants — compose with .transition (hover-/focus-/active-<fx>). */\n`;
1150
+ for (const [name, e] of Object.entries(anim.effects ?? {})) {
1151
+ if (e.kf !== "composite" && e.kf !== "paint") continue;
1152
+ const props = {};
1153
+ for (const [k, v] of Object.entries(e.vars ?? {})) {
1154
+ const m = /^(.*)-(from|to)$/.exec(k);
1155
+ if (m) (props[m[1]] ??= {})[m[2]] = asStr(v);
1156
+ }
1157
+ const rest = {};
1158
+ for (const [p, ft] of Object.entries(props)) {
1159
+ if (ft.from != null && ft.from !== STATE_DEFAULT[p]) rest[`--${p}-from`] = ft.from;
1160
+ if (ft.to != null && ft.to !== STATE_DEFAULT[p]) rest[`--${p}-to`] = ft.to;
1161
+ }
1162
+ const flip = Object.fromEntries(Object.keys(props).map((p) => [`--t-${p}`, `var(--${p}-to, ${STATE_DEFAULT[p]})`]));
1163
+ if (Object.keys(rest).length) out += `${STATES.map(([s]) => `.${s}-${name}`).join(", ")} {\n${decls(rest)}\n}\n`;
1164
+ for (const [s, sel] of STATES) out += `${sel(`${s}-${name}`)} {\n${decls(flip)}\n}\n`;
1165
+ }
1166
+ generated["animation-effects.css"] = out;
1167
+ }
1168
+ const namedColors = [];
1169
+ for (const [name, steps] of Object.entries(palette)) for (const step of STEP_ORDER) {
1170
+ const hex = steps[step];
1171
+ if (hex == null) continue;
1172
+ const token = `${name}${suffix(step)}`;
1173
+ namedColors.push({
1174
+ raw: `var(--color-${token})`,
1175
+ light: hex,
1176
+ id: id(`named:${token}`),
1177
+ utilityClasses: UTILITIES
1178
+ });
1179
+ }
1180
+ const semanticColors = [];
1181
+ for (const [role, spec] of Object.entries(defaultSemantic)) {
1182
+ const ramp = roleRamp(spec);
1183
+ const altSpec = darkScheme?.semantic[role];
1184
+ for (const step of STEP_ORDER) {
1185
+ if (palette[ramp]?.[step] == null) continue;
1186
+ const lightSrc = resolveSlot(spec, step, ramp);
1187
+ const darkSrc = altSpec != null ? resolveSlot(altSpec, step, ramp) : lightSrc;
1188
+ const hasDark = !srcEq(darkSrc, lightSrc);
1189
+ semanticColors.push({
1190
+ raw: `var(--color-${role}${suffix(step)})`,
1191
+ light: srcVar(lightSrc),
1192
+ darkModeEnabled: hasDark,
1193
+ dark: srcVar(darkSrc),
1194
+ id: id(`semantic:${role}${suffix(step)}`),
1195
+ utilityClasses: UTILITIES
1196
+ });
1197
+ }
1198
+ }
1199
+ return {
1200
+ generated,
1201
+ bricksColorsNamed: JSON.stringify({
1202
+ id: id("palette:named"),
1203
+ name: "Named",
1204
+ colors: namedColors
1205
+ }, null, 2),
1206
+ bricksColorsSemantic: JSON.stringify({
1207
+ id: id("palette:semantic"),
1208
+ name: "Semantic",
1209
+ colors: semanticColors
1210
+ }, null, 2),
1211
+ bricksVariables: (() => {
1212
+ const scaleMeta = (scope, prefix, s, names) => {
1213
+ const px = Math.round(rem(s.base) * 16);
1214
+ const lo = s.fluid?.minRatio ?? s.ratio;
1215
+ return {
1216
+ scaleScope: scope,
1217
+ scaleType: "tshirt",
1218
+ scaleNames: names,
1219
+ prefix,
1220
+ minFontSize: px,
1221
+ minScaleRatio: lo,
1222
+ minScaleRatioSelect: lo,
1223
+ maxFontSize: px,
1224
+ maxScaleRatio: s.ratio,
1225
+ maxScaleRatioSelect: s.ratio,
1226
+ baseline: s.baseline ?? names[Math.floor((names.length - 1) / 2)]
1227
+ };
1228
+ };
1229
+ const categories = [];
1230
+ const variables = [];
1231
+ const addScale = (label, scope, prefix, cfg, steps) => {
1232
+ if (!cfg || !steps.length) return;
1233
+ const names = steps.map((s) => s.name);
1234
+ const catId = id(`varcat:${scope}`);
1235
+ categories.push({
1236
+ id: catId,
1237
+ name: label,
1238
+ scale: scaleMeta(scope, prefix, cfg, names),
1239
+ utilityClasses: []
1240
+ });
1241
+ for (const st of steps) variables.push({
1242
+ id: id(`var:${prefix}${st.name}`),
1243
+ name: `${prefix}${st.name}`,
1244
+ value: st.value,
1245
+ category: catId,
1246
+ scale: {
1247
+ scale: st.offset,
1248
+ scaleName: st.name
1249
+ }
1250
+ });
1251
+ };
1252
+ addScale("Typography", "typography", "text-", ds.typeScale, typeSteps);
1253
+ addScale("Spacing", "spacing", "space-", ds.spaceScale, spaceSteps);
1254
+ for (const [name, stack] of Object.entries(ds.fonts ?? {})) variables.push({
1255
+ id: id(`var:font-${name}`),
1256
+ name: `font-${name}`,
1257
+ value: stack
1258
+ });
1259
+ for (const [name, val] of Object.entries(ds.patterns?.radii ?? {})) variables.push({
1260
+ id: id(`var:br-${name}`),
1261
+ name: `br-${name}`,
1262
+ value: val
1263
+ });
1264
+ return JSON.stringify({
1265
+ variables,
1266
+ categories
1267
+ }, null, 2);
1268
+ })(),
1269
+ tokensJson: JSON.stringify({
1270
+ colors: {
1271
+ palette,
1272
+ semantic: Object.fromEntries(Object.entries(defaultSemantic).map(([role, spec]) => [role, roleRamp(spec)]))
1273
+ },
1274
+ fonts: ds.fonts ?? {},
1275
+ text: Object.fromEntries(typeSteps.map((s) => [s.name, s.value])),
1276
+ spacing: Object.fromEntries(spaceSteps.map((s) => [s.name, s.value])),
1277
+ shadows: shadows ?? {},
1278
+ radii: ds.patterns?.radii ?? {}
1279
+ }, null, 2),
1280
+ designManifest: (() => {
1281
+ const reverseIndex = {};
1282
+ for (const [name, steps] of Object.entries(palette)) for (const step of STEP_ORDER) if (steps[step] != null) reverseIndex[`--color-${name}${suffix(step)}`] = `colors.palette.${name}.${step}`;
1283
+ const colorRoles = Object.entries(defaultSemantic).map(([name, spec]) => ({
1284
+ name,
1285
+ ramp: roleRamp(spec)
1286
+ }));
1287
+ const typoRoles = typography?.roles ?? {};
1288
+ const hooks = {};
1289
+ for (const [key, [prop, sfx]] of Object.entries(TYPO_KEYMAP)) hooks[sfx] = {
1290
+ prop,
1291
+ key
1292
+ };
1293
+ for (const [role, spec] of Object.entries(typoRoles)) for (const [key, [, sfx]] of Object.entries(TYPO_KEYMAP)) if (spec[key] != null) reverseIndex[`--${role}-${sfx}`] = `typography.roles.${role}.${key}`;
1294
+ const pDefaults = patterns?.defaults ?? {};
1295
+ const pRadii = patterns?.radii ?? {};
1296
+ const pGroups = patterns?.groups ?? {};
1297
+ const pItems = patterns?.items ?? {};
1298
+ for (const prop of Object.keys(pDefaults)) reverseIndex[`--${prop}-default`] = `patterns.defaults.${prop}`;
1299
+ for (const name of Object.keys(pRadii)) reverseIndex[`--br-${name}`] = `patterns.radii.${name}`;
1300
+ for (const [group, props] of Object.entries(pGroups)) for (const prop of Object.keys(props)) reverseIndex[`--${prop}-${group}`] = `patterns.groups.${group}.${prop}`;
1301
+ const items = Object.entries(pItems).map(([name, p]) => {
1302
+ const base = p.base ?? {};
1303
+ const wrappable = Object.keys(base).filter((prop) => prop in BASE_HOOK).map((prop) => ({
1304
+ sfx: BASE_HOOK[prop],
1305
+ prop
1306
+ }));
1307
+ for (const { sfx, prop } of wrappable) reverseIndex[`--${sfx}-${name}`] = `patterns.items.${name}.base.${prop}`;
1308
+ return {
1309
+ name,
1310
+ group: p.group ?? null,
1311
+ base,
1312
+ wrappable,
1313
+ roles: p.roles ?? []
1314
+ };
1315
+ });
1316
+ return JSON.stringify({
1317
+ colors: {
1318
+ ramps: Object.keys(palette),
1319
+ steps: STEP_ORDER,
1320
+ palette,
1321
+ roles: colorRoles
1322
+ },
1323
+ typography: {
1324
+ roles: Object.keys(typoRoles),
1325
+ hooks,
1326
+ families: typographyFamilies,
1327
+ specs: typoRoles
1328
+ },
1329
+ scales: {
1330
+ text: typeSteps.map((s) => ({
1331
+ name: s.name,
1332
+ value: s.value,
1333
+ max: s.max
1334
+ })),
1335
+ space: spaceSteps.map((s) => ({
1336
+ name: s.name,
1337
+ value: s.value,
1338
+ max: s.max
1339
+ }))
1340
+ },
1341
+ fonts: ds.fonts ?? {},
1342
+ patterns: {
1343
+ defaults: pDefaults,
1344
+ radii: pRadii,
1345
+ groups: pGroups,
1346
+ items
1347
+ },
1348
+ shadows: shadows ?? {},
1349
+ interaction: {
1350
+ duration: "200ms",
1351
+ easing: "ease"
1352
+ },
1353
+ reverseIndex
1354
+ }, null, 2);
1355
+ })(),
1356
+ tailwind: emitTailwind({
1357
+ ds,
1358
+ palette,
1359
+ defaultSemantic,
1360
+ darkScheme,
1361
+ shadows,
1362
+ typeSteps,
1363
+ spaceSteps,
1364
+ typography,
1365
+ roleDecls,
1366
+ twTypeTokensCss,
1367
+ twPatternTokensCss,
1368
+ patternsCss: pat,
1369
+ assetsDir
1370
+ }),
1371
+ twTypeTokensCss,
1372
+ twPatternTokensCss,
1373
+ patternsCss: pat
1374
+ };
1375
+ }
1376
+ function emitTailwind(ctx) {
1377
+ const { ds, palette, defaultSemantic, darkScheme, shadows, typeSteps, spaceSteps, typography, roleDecls, twTypeTokensCss, twPatternTokensCss, patternsCss, assetsDir } = ctx;
1378
+ const cssDir = join(assetsDir, "css");
1379
+ const nl = (arr) => arr.filter(Boolean).join("\n");
1380
+ const parts = [`/* GENERATED for Tailwind v4 (Astro) — do not edit by hand. */`];
1381
+ parts.push(`@import "tailwindcss";\n`);
1382
+ const theme = [];
1383
+ theme.push(` /* Colour ramps → bg-* text-* border-* */`);
1384
+ for (const [name, steps] of Object.entries(palette)) for (const step of STEP_ORDER) if (steps[step] != null) theme.push(` --color-${name}${suffix(step)}: ${steps[step]};`);
1385
+ theme.push(`\n /* Semantic roles → ramps (default scheme) */`);
1386
+ for (const [role, spec] of Object.entries(defaultSemantic)) {
1387
+ const ramp = roleRamp(spec);
1388
+ for (const step of STEP_ORDER) if (palette[ramp]?.[step] != null) theme.push(` --color-${role}${suffix(step)}: ${srcVar(resolveSlot(spec, step, ramp))};`);
1389
+ }
1390
+ theme.push(`\n /* Fonts → font-* */`);
1391
+ for (const [name, stack] of Object.entries(ds.fonts ?? {})) theme.push(` --font-${name}: ${stack};`);
1392
+ theme.push(`\n /* Fluid type scale → text-* */`);
1393
+ for (const s of typeSteps) theme.push(` --text-${s.name}: ${s.value};`);
1394
+ theme.push(`\n /* Fluid spacing scale → p-* m-* gap-* */`);
1395
+ for (const s of spaceSteps) theme.push(` --spacing-${s.name}: ${s.value};`);
1396
+ theme.push(`\n /* Drop-shadows → shadow-* */`);
1397
+ for (const [name, value] of Object.entries(shadows ?? {})) theme.push(` --shadow-${name}: ${value};`);
1398
+ theme.push(`\n /* Container breakpoints → @sm:/@md:/@lg:/@xl: */`);
1399
+ for (const [name, val] of [
1400
+ ["sm", "30rem"],
1401
+ ["md", "48rem"],
1402
+ ["lg", "64rem"],
1403
+ ["xl", "80rem"]
1404
+ ]) theme.push(` --container-${name}: ${val};`);
1405
+ parts.push(`@theme {\n${theme.join("\n")}\n}\n`);
1406
+ parts.push(`/* ── Framework tokens (consumed by the inlined components) ── */\n${twTypeTokensCss}\n${twPatternTokensCss}`);
1407
+ let darkBlock = "";
1408
+ if (darkScheme) for (const [role, spec] of Object.entries(darkScheme.semantic)) {
1409
+ const baseSpec = defaultSemantic[role];
1410
+ if (baseSpec == null) continue;
1411
+ const ramp = roleRamp(baseSpec);
1412
+ for (const step of STEP_ORDER) {
1413
+ if (palette[ramp]?.[step] == null) continue;
1414
+ const alt = resolveSlot(spec, step, ramp);
1415
+ if (srcEq(alt, resolveSlot(baseSpec, step, ramp))) continue;
1416
+ darkBlock += ` --color-${role}${suffix(step)}: ${srcVar(alt)};\n`;
1417
+ }
1418
+ }
1419
+ if (darkBlock && darkScheme) parts.push(`/* Dark-mode semantic overrides */\n:root[data-brx-theme="${darkScheme.appearance}"] {\n${darkBlock}}\n`);
1420
+ parts.push("/* State variant for the active flip (hover/focus-visible are built in). */\n@custom-variant is-active (&.is-active, &[data-active]);\n");
1421
+ const util = [];
1422
+ util.push(`/* Typography roles */`);
1423
+ for (const [role, spec] of Object.entries(typography?.roles ?? {})) util.push(`@utility font-${role} {\n${decls(roleDecls(role, spec))}\n}`);
1424
+ util.push(`\n/* Animation effects (pair with .transition or a driver) */`);
1425
+ const asStr = (v) => String(v);
1426
+ for (const [name, e] of Object.entries(ds.animations?.effects ?? {})) {
1427
+ const d = { "--_anim": e.kf };
1428
+ for (const [k, v] of Object.entries(e.css ?? {})) d[k] = asStr(v);
1429
+ for (const [k, v] of Object.entries(e.vars ?? {})) d[`--${k}`] = asStr(v);
1430
+ util.push(`@utility ${name} {\n${decls(d)}\n}`);
1431
+ if (e.kf !== "composite" && e.kf !== "paint") continue;
1432
+ const props = /* @__PURE__ */ new Set();
1433
+ for (const k of Object.keys(e.vars ?? {})) {
1434
+ const m = /^(.*)-(from|to)$/.exec(k);
1435
+ if (m) props.add(m[1]);
1436
+ }
1437
+ const flip = Object.fromEntries([...props].map((p) => [`--t-${p}`, `var(--${p}-to, ${STATE_DEFAULT[p]})`]));
1438
+ util.push(`@utility flip-${name} {\n${decls(flip)}\n}`);
1439
+ }
1440
+ util.push(`\n/* Split — internal fractions (pair split with a ratio) */`);
1441
+ util.push("@utility split {\n display: flex;\n & > * { flex: 1; }\n & > :first-child { flex: var(--_split-a, 1); }\n & > :last-child { flex: var(--_split-b, 1); }\n}");
1442
+ for (const [a, b] of [
1443
+ [1, 2],
1444
+ [2, 1],
1445
+ [1, 3],
1446
+ [3, 1],
1447
+ [1, 4],
1448
+ [4, 1],
1449
+ [2, 3],
1450
+ [3, 2]
1451
+ ]) util.push(`@utility split-${a}-${b} {\n --_split-a: ${a};\n --_split-b: ${b};\n}`);
1452
+ util.push(`\n/* Track placement (inside .centered) */`);
1453
+ for (const track of [
1454
+ "measure",
1455
+ "breakout",
1456
+ "spotlight",
1457
+ "fullbleed"
1458
+ ]) util.push(`@utility ${track} {\n grid-column: ${track};\n}`);
1459
+ parts.push(util.join("\n") + "\n");
1460
+ parts.push(`/* ── Structure (mirrors src/css/layout.css) ── */
1461
+ :root {
1462
+ --rhythm-scale: 1;
1463
+ --rhythm-base: calc(1rlh * var(--rhythm-scale));
1464
+ --rhythm-h-p: 0.4;
1465
+ --rhythm-p-p: 0.9;
1466
+ --rhythm-p-h: 1.6;
1467
+ --rhythm-h-h: 0.6;
1468
+ --rhythm-p-list: 0.5;
1469
+ --rhythm-list-p: 0.9;
1470
+ --rhythm-li-li: 0.3;
1471
+ --rhythm-text-media: 1.2;
1472
+ --rhythm-media-text: 1.2;
1473
+ --width-measure: 65ch;
1474
+ --width-breakout: 90ch;
1475
+ --width-spotlight: 120ch;
1476
+ --gutter: clamp(1rem, 4cqi, 3rem);
1477
+ }
1478
+
1479
+ body {
1480
+ container-type: inline-size;
1481
+ }
1482
+
1483
+ .rhythm {
1484
+ display: flow-root;
1485
+ }
1486
+ .rhythm > * + * {
1487
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-p-p));
1488
+ }
1489
+ .rhythm > :where(h1, h2, h3, h4, h5, h6) + p {
1490
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-h-p));
1491
+ }
1492
+ .rhythm > p + :where(h1, h2, h3, h4, h5, h6) {
1493
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-p-h));
1494
+ }
1495
+ .rhythm > :where(h1, h2, h3, h4, h5, h6) + :where(h1, h2, h3, h4, h5, h6) {
1496
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-h-h));
1497
+ }
1498
+ .rhythm > p + :where(ul, ol) {
1499
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-p-list));
1500
+ }
1501
+ .rhythm > :where(ul, ol) + p {
1502
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-list-p));
1503
+ }
1504
+ .rhythm :where(ul, ol) > li + li {
1505
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-li-li));
1506
+ }
1507
+ .rhythm > :where(p, h1, h2, h3, h4, h5, h6, ul, ol) + :where(figure, img, picture, video) {
1508
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-text-media));
1509
+ }
1510
+ .rhythm > :where(figure, img, picture, video) + :where(p, h1, h2, h3, h4, h5, h6, ul, ol) {
1511
+ margin-top: calc(var(--rhythm-base) * var(--rhythm-media-text));
1512
+ }
1513
+
1514
+ .centered {
1515
+ --_measure: min(var(--width-measure), 100% - var(--gutter) * 2);
1516
+ --_breakout: minmax(0, calc((var(--width-breakout) - var(--width-measure)) / 2));
1517
+ --_spotlight: minmax(0, calc((var(--width-spotlight) - var(--width-breakout)) / 2));
1518
+ position: relative;
1519
+ display: grid;
1520
+ grid-template-columns:
1521
+ [fullbleed-start] minmax(var(--gutter), 1fr)
1522
+ [spotlight-start] var(--_spotlight)
1523
+ [breakout-start] var(--_breakout)
1524
+ [measure-start] var(--_measure) [measure-end]
1525
+ var(--_breakout) [breakout-end]
1526
+ var(--_spotlight) [spotlight-end]
1527
+ minmax(var(--gutter), 1fr) [fullbleed-end];
1528
+ }
1529
+ .centered > * {
1530
+ grid-column: measure;
1531
+ }
1532
+
1533
+ .region {
1534
+ padding-block: clamp(
1535
+ var(--region-space-min, 3rem),
1536
+ var(--region-space, 8vh),
1537
+ var(--region-space-max, 8rem)
1538
+ );
1539
+ }
1540
+ `);
1541
+ const animEngine = readFileSync(join(cssDir, "animation.css"), "utf8");
1542
+ parts.push(`/* ── Animation engine (animation.css) ── */\n${animEngine}`);
1543
+ parts.push(`/* ── Component patterns ── */\n${patternsCss}`);
1544
+ const TW_CLASH = new Set([
1545
+ "block",
1546
+ "inline",
1547
+ "inline-block",
1548
+ "flow-root",
1549
+ "flex",
1550
+ "inline-flex",
1551
+ "grid",
1552
+ "inline-grid",
1553
+ "table",
1554
+ "inline-table",
1555
+ "table-caption",
1556
+ "table-cell",
1557
+ "table-row",
1558
+ "contents",
1559
+ "hidden",
1560
+ "list-item",
1561
+ "static",
1562
+ "fixed",
1563
+ "absolute",
1564
+ "relative",
1565
+ "sticky",
1566
+ "isolate",
1567
+ "visible",
1568
+ "invisible",
1569
+ "collapse",
1570
+ "sr-only",
1571
+ "not-sr-only",
1572
+ "flex-row",
1573
+ "flex-row-reverse",
1574
+ "flex-col",
1575
+ "flex-col-reverse",
1576
+ "flex-wrap",
1577
+ "flex-nowrap",
1578
+ "items-start",
1579
+ "items-end",
1580
+ "items-center",
1581
+ "items-baseline",
1582
+ "items-stretch",
1583
+ "justify-start",
1584
+ "justify-end",
1585
+ "justify-center",
1586
+ "justify-between",
1587
+ "justify-around",
1588
+ "justify-evenly",
1589
+ "content-start",
1590
+ "content-end",
1591
+ "content-center",
1592
+ "content-between",
1593
+ "content-around",
1594
+ "content-evenly",
1595
+ "text-left",
1596
+ "text-center",
1597
+ "text-right",
1598
+ "text-justify",
1599
+ "text-start",
1600
+ "text-end"
1601
+ ]);
1602
+ const droppedClashes = /* @__PURE__ */ new Set();
1603
+ let droppedVariantBlocks = 0;
1604
+ const stripForTailwind = (css) => {
1605
+ const kept = [];
1606
+ let depth = 0;
1607
+ let start = 0;
1608
+ let preludeEnd = -1;
1609
+ for (let i = 0; i < css.length; i++) {
1610
+ const ch = css[i];
1611
+ if (ch === "/" && css[i + 1] === "*") {
1612
+ const end = css.indexOf("*/", i + 2);
1613
+ i = end === -1 ? css.length : end + 1;
1614
+ continue;
1615
+ }
1616
+ if (ch === "\"" || ch === "'") {
1617
+ i += 1;
1618
+ while (i < css.length && css[i] !== ch) i += css[i] === "\\" ? 2 : 1;
1619
+ continue;
1620
+ }
1621
+ if (ch === "{") {
1622
+ if (depth === 0) preludeEnd = i;
1623
+ depth++;
1624
+ } else if (ch === "}") {
1625
+ depth--;
1626
+ if (depth === 0) {
1627
+ const raw = css.slice(start, i + 1).trim();
1628
+ const prelude = css.slice(start, preludeEnd).replace(/\/\*[\s\S]*?\*\//g, "").trim();
1629
+ const clash = /^\.([A-Za-z0-9_-]+)/.exec(prelude);
1630
+ if (/^@container\s*\(\s*min-width:/.test(prelude)) droppedVariantBlocks++;
1631
+ else if (clash && TW_CLASH.has(clash[1])) droppedClashes.add(clash[1]);
1632
+ else if (raw) kept.push(raw);
1633
+ start = i + 1;
1634
+ }
1635
+ }
1636
+ }
1637
+ return kept.join("\n\n");
1638
+ };
1639
+ const componentCss = componentPartialOrder(cssDir).map((rel) => {
1640
+ return `/* ── ${rel} ── */\n${stripForTailwind(readFileSync(join(cssDir, rel), "utf8"))}`;
1641
+ }).join("\n\n");
1642
+ parts.push(`/* ── Component + structural partials (Tailwind-clashing utilities dropped) ── */\n${componentCss}`);
1643
+ return nl(parts) + "\n";
1644
+ }
1645
+ /**
1646
+ * The component/structural CSS partials to inline into the Tailwind bundle, in
1647
+ * index.css cascade order. Everything the aggregator imports EXCEPT the engine
1648
+ * (global/animation), the layout utilities (Tailwind owns them), and the generated
1649
+ * token layer (Tailwind builds its own via @theme).
1650
+ */
1651
+ function componentPartialOrder(cssDir) {
1652
+ const indexPath = join(cssDir, "index.css");
1653
+ const skip = new Set([
1654
+ "global.css",
1655
+ "animation.css",
1656
+ "layout.css"
1657
+ ]);
1658
+ const order = [];
1659
+ const seen = /* @__PURE__ */ new Set();
1660
+ const text = existsSync(indexPath) ? readFileSync(indexPath, "utf8") : "";
1661
+ for (const m of text.matchAll(/@import\s+['"]\.\/([^'"]+)['"]/g)) {
1662
+ const rel = m[1];
1663
+ if (rel.startsWith("generated/")) continue;
1664
+ if (skip.has(rel)) continue;
1665
+ if (!seen.has(rel) && existsSync(join(cssDir, rel))) {
1666
+ seen.add(rel);
1667
+ order.push(rel);
1668
+ }
1669
+ }
1670
+ if (!order.length) {
1671
+ if (existsSync(join(cssDir, "utilities.css"))) order.push("utilities.css");
1672
+ const pdir = join(cssDir, "patterns");
1673
+ if (existsSync(pdir)) {
1674
+ for (const f of readdirSync(pdir).sort()) if (f.endsWith(".css")) order.push(`patterns/${f}`);
1675
+ }
1676
+ }
1677
+ return order;
1678
+ }
1679
+ /**
1680
+ * Assemble the full stylesheet by resolving index.css's @import order against the
1681
+ * static partials (from assets) and the freshly generated token partials (in
1682
+ * memory), then minify with lightningcss. No @import survives, so no disk
1683
+ * resolution / shared `generated/` dir is needed.
1684
+ */
1685
+ function bundleCss(generated, assetsDir, minify) {
1686
+ const cssDir = join(assetsDir, "css");
1687
+ const index = readFileSync(join(cssDir, "index.css"), "utf8");
1688
+ const chunks = [];
1689
+ for (const line of index.split("\n")) {
1690
+ const m = /@import\s+['"]\.\/([^'"]+)['"]/.exec(line);
1691
+ if (!m) continue;
1692
+ const rel = m[1];
1693
+ if (rel.startsWith("generated/")) {
1694
+ const name = rel.slice(10);
1695
+ chunks.push(generated[name] ?? "");
1696
+ } else chunks.push(readFileSync(join(cssDir, rel), "utf8"));
1697
+ }
1698
+ const merged = chunks.join("\n");
1699
+ const res = transform({
1700
+ filename: "styles.css",
1701
+ code: Buffer.from(merged),
1702
+ minify,
1703
+ sourceMap: minify
1704
+ });
1705
+ return {
1706
+ code: res.code.toString(),
1707
+ map: res.map ? res.map.toString() : void 0
1708
+ };
1709
+ }
1710
+ function loadConfig(input) {
1711
+ const result = validate(typeof input === "string" ? JSON.parse(readFileSync(input, "utf8")) : input);
1712
+ if (!result.ok) {
1713
+ const lines = result.errors.slice(0, 12).map((e) => ` • ${e.path.join(".") || "(root)"}: ${e.message}`).join("\n");
1714
+ throw new Error(`Invalid design-system.json:\n${lines}`);
1715
+ }
1716
+ return result.data;
1717
+ }
1718
+ async function generate(options) {
1719
+ const format = options.format ?? "bricks";
1720
+ const outDir = options.outDir ?? "dist";
1721
+ const assetsDir = options.assetsDir ?? DEFAULT_ASSETS;
1722
+ const ds = loadConfig(options.input);
1723
+ const built = build(ds, format, assetsDir);
1724
+ mkdirSync(outDir, { recursive: true });
1725
+ const written = [];
1726
+ const put = (rel, content) => {
1727
+ const p = join(outDir, rel);
1728
+ mkdirSync(dirname(p), { recursive: true });
1729
+ writeFileSync(p, content);
1730
+ written.push(p);
1731
+ };
1732
+ if (format === "tailwind") {
1733
+ put("tailwind.css", built.tailwind);
1734
+ put("tokens.json", built.tokensJson);
1735
+ } else if (format === "css") {
1736
+ const { code } = bundleCss(built.generated, assetsDir, true);
1737
+ put("styles.css", code);
1738
+ put("tokens.json", built.tokensJson);
1739
+ put("design-manifest.json", built.designManifest);
1740
+ } else {
1741
+ const { code, map } = bundleCss(built.generated, assetsDir, true);
1742
+ put("styles.min.css", map ? code + `\n/*# sourceMappingURL=styles.min.css.map */` : code);
1743
+ if (map) put("styles.min.css.map", map);
1744
+ put("bricks-colors-named.json", built.bricksColorsNamed);
1745
+ put("bricks-colors-semantic.json", built.bricksColorsSemantic);
1746
+ put("bricks-variables.json", built.bricksVariables);
1747
+ put("tokens.json", built.tokensJson);
1748
+ const jsDir = join(assetsDir, "js");
1749
+ if (existsSync(jsDir)) {
1750
+ cpSync(jsDir, outDir, { recursive: true });
1751
+ for (const f of readdirSync(jsDir)) written.push(join(outDir, f));
1752
+ }
1753
+ const bricksSrc = join(assetsDir, "bricks");
1754
+ if (existsSync(bricksSrc)) {
1755
+ cpSync(bricksSrc, join(outDir, "bricks"), { recursive: true });
1756
+ written.push(join(outDir, "bricks"));
1757
+ }
1758
+ const docs = generateDocs(ds, assetsDir);
1759
+ for (const [rel, content] of Object.entries(docs)) put(join("docs", rel), content);
1760
+ }
1761
+ return {
1762
+ format,
1763
+ outDir,
1764
+ written
1765
+ };
1766
+ }
1767
+
1768
+ //#endregion
1769
+ //#region src/site.ts
1770
+ /**
1771
+ * Company / site-level configuration schema — the umbrella around a
1772
+ * `design-system.json`-compliant `designSystem`.
1773
+ *
1774
+ * Authored with `zod/mini` (same instance the design-system schema uses), so the
1775
+ * two compose: `DesignSystemSchema` is embedded as `designSystem.<theme>` and the
1776
+ * whole thing serialises to one JSON Schema via `z.toJSONSchema`. Everything a
1777
+ * generator needs derives from here: the `SiteConfig` type (`z.infer`), the
1778
+ * published JSON Schema (`siteJsonSchema`), and runtime validation (`validateSite`).
1779
+ *
1780
+ * Pure: no YAML/env/fs. `resolveSiteConfig` takes an already-parsed object; a
1781
+ * thin loader (reading YAML, injecting `SITE_ENV`) lives outside this module.
1782
+ */
1783
+ /** A string, or a per-locale map of strings (`{ en: "…", fr: "…" }`). */
1784
+ const LocalizableSchema = z.union([z.string(), z.record(z.string(), z.string())]);
1785
+ const ImageRefSchema = z.union([z.string(), z.object({
1786
+ src: z.string(),
1787
+ alt: z.string(),
1788
+ width: z.optional(z.number()),
1789
+ height: z.optional(z.number())
1790
+ })]);
1791
+ const AttrValue = z.union([
1792
+ z.string(),
1793
+ z.number(),
1794
+ z.boolean()
1795
+ ]);
1796
+ const AttrsSchema = z.optional(z.record(z.string(), AttrValue));
1797
+ const IsoDate = z.string().check(z.regex(/^\d{4}-\d{2}-\d{2}$/));
1798
+ const Iso4217 = z.string().check(z.regex(/^[A-Z]{3}$/));
1799
+ const DayOfWeek = z.enum([
1800
+ "Monday",
1801
+ "Tuesday",
1802
+ "Wednesday",
1803
+ "Thursday",
1804
+ "Friday",
1805
+ "Saturday",
1806
+ "Sunday"
1807
+ ]);
1808
+ const DesignSystemEntry = z.extend(DesignSystemPatchSchema, {
1809
+ displayName: z.optional(LocalizableSchema),
1810
+ extends: z.optional(z.string())
1811
+ });
1812
+ const LocaleSchema = z.object({
1813
+ name: z.string(),
1814
+ tagline: z.optional(z.string()),
1815
+ basePath: z.optional(z.string())
1816
+ });
1817
+ const HttpRedirect = z.union([
1818
+ z.literal(301),
1819
+ z.literal(302),
1820
+ z.literal(307),
1821
+ z.literal(308)
1822
+ ]);
1823
+ const DomainAliasSchema = z.object({
1824
+ domain: z.string(),
1825
+ redirectType: HttpRedirect,
1826
+ redirectTo: z.string(),
1827
+ environment: z.optional(z.string())
1828
+ });
1829
+ /** schema.org `PostalAddress` field names, so JSON-LD generation is lossless. */
1830
+ const PostalAddressSchema = z.object({
1831
+ streetAddress: z.string(),
1832
+ addressLocality: z.string(),
1833
+ addressRegion: z.optional(z.string()),
1834
+ postalCode: z.optional(z.string()),
1835
+ addressCountry: z.string()
1836
+ });
1837
+ const ContactObjectSchema = z.object({
1838
+ name: z.optional(z.string()),
1839
+ email: z.optional(z.email()),
1840
+ phone: z.optional(z.string()),
1841
+ address: z.optional(PostalAddressSchema)
1842
+ });
1843
+ const ContactConfigSchema = z.union([z.string(), ContactObjectSchema]);
1844
+ const GeoSchema = z.object({
1845
+ latitude: z.number().check(z.gte(-90), z.lte(90)),
1846
+ longitude: z.number().check(z.gte(-180), z.lte(180))
1847
+ });
1848
+ /** schema.org `OpeningHoursSpecification` — per-day open/close (24h `HH:MM`). */
1849
+ const OpeningHoursSchema = z.object({
1850
+ dayOfWeek: z.array(DayOfWeek),
1851
+ opens: z.string().check(z.regex(/^\d{2}:\d{2}$/)),
1852
+ closes: z.string().check(z.regex(/^\d{2}:\d{2}$/))
1853
+ });
1854
+ const LocalBusinessType = z.enum([
1855
+ "LocalBusiness",
1856
+ "Store",
1857
+ "Restaurant",
1858
+ "CafeOrCoffeeShop",
1859
+ "ProfessionalService",
1860
+ "MedicalBusiness",
1861
+ "HealthAndBeautyBusiness",
1862
+ "HomeAndConstructionBusiness",
1863
+ "AutomotiveBusiness",
1864
+ "FinancialService",
1865
+ "LegalService",
1866
+ "RealEstateAgent",
1867
+ "Organization"
1868
+ ]);
1869
+ const LocationSchema = z.object({
1870
+ slug: z.optional(LocalizableSchema),
1871
+ name: LocalizableSchema,
1872
+ email: z.optional(z.email()),
1873
+ phone: z.optional(z.string()),
1874
+ address: z.optional(PostalAddressSchema),
1875
+ geo: z.optional(GeoSchema),
1876
+ hours: z.optional(z.array(OpeningHoursSchema)),
1877
+ type: z.optional(LocalBusinessType),
1878
+ description: z.optional(LocalizableSchema),
1879
+ areaServed: z.optional(z.array(z.string())),
1880
+ priceRange: z.optional(z.string()),
1881
+ paymentAccepted: z.optional(z.array(z.string())),
1882
+ currenciesAccepted: z.optional(Iso4217),
1883
+ knowsLanguage: z.optional(z.array(z.string()))
1884
+ });
1885
+ const OrganizationSchema = z.object({
1886
+ name: z.optional(LocalizableSchema),
1887
+ legalName: z.optional(z.string()),
1888
+ foundingDate: z.optional(IsoDate),
1889
+ logo: z.optional(ImageRefSchema),
1890
+ email: z.optional(z.email()),
1891
+ phone: z.optional(z.string()),
1892
+ address: z.optional(PostalAddressSchema),
1893
+ taxID: z.optional(z.string()),
1894
+ vatID: z.optional(z.string()),
1895
+ /** Profile URLs → JSON-LD `sameAs` (mirrors `links`). */
1896
+ sameAs: z.optional(z.array(z.url()))
1897
+ });
1898
+ const DnsRecordType = z.enum([
1899
+ "A",
1900
+ "AAAA",
1901
+ "CNAME",
1902
+ "TXT",
1903
+ "MX",
1904
+ "NS",
1905
+ "SRV",
1906
+ "CAA",
1907
+ "PTR",
1908
+ "SOA"
1909
+ ]);
1910
+ const DnsRecordSchema = z.object({
1911
+ type: DnsRecordType,
1912
+ name: z.optional(z.string()),
1913
+ value: z.string(),
1914
+ ttl: z.optional(z.number().check(z.int(), z.positive())),
1915
+ proxied: z.optional(z.boolean())
1916
+ });
1917
+ const DnsDomainSchema = z.object({
1918
+ provider: z.optional(z.string()),
1919
+ nameservers: z.optional(z.array(z.string())),
1920
+ records: z.optional(z.array(DnsRecordSchema))
1921
+ });
1922
+ const RobotsSchema = z.string();
1923
+ const EnvironmentSchema = z.object({
1924
+ url: z.url(),
1925
+ api: z.optional(z.url()),
1926
+ analytics: z.optional(z.boolean()),
1927
+ robots: z.optional(RobotsSchema),
1928
+ variant: z.optional(z.string())
1929
+ });
1930
+ const AbVariantSchema = z.object({
1931
+ environment: z.string(),
1932
+ description: z.optional(z.string()),
1933
+ overrides: z.optional(z.record(z.string(), z.unknown()))
1934
+ });
1935
+ const FontProvider = z.enum([
1936
+ "google",
1937
+ "fontsource",
1938
+ "adobe",
1939
+ "bunny",
1940
+ "fontshare",
1941
+ "googleicons",
1942
+ "npm",
1943
+ "local"
1944
+ ]);
1945
+ const FontStyle = z.enum(["normal", "italic"]);
1946
+ const FontDisplay = z.enum([
1947
+ "auto",
1948
+ "block",
1949
+ "swap",
1950
+ "fallback",
1951
+ "optional"
1952
+ ]);
1953
+ const FontWeight = z.union([z.number(), z.string()]);
1954
+ /** A local-font face (`provider: 'local'`). */
1955
+ const FontVariantSchema = z.object({
1956
+ src: z.array(z.string()),
1957
+ weight: z.optional(FontWeight),
1958
+ style: z.optional(FontStyle),
1959
+ unicodeRange: z.optional(z.array(z.string())),
1960
+ display: z.optional(FontDisplay)
1961
+ });
1962
+ const FontSchema = z.object({
1963
+ name: z.string(),
1964
+ provider: FontProvider,
1965
+ cssVariable: z.string().check(z.regex(/^--/)),
1966
+ weights: z.optional(z.array(FontWeight)),
1967
+ styles: z.optional(z.array(FontStyle)),
1968
+ subsets: z.optional(z.array(z.string())),
1969
+ fallbacks: z.optional(z.array(z.string())),
1970
+ optimizedFallbacks: z.optional(z.boolean()),
1971
+ display: z.optional(FontDisplay),
1972
+ formats: z.optional(z.array(z.string())),
1973
+ unicodeRange: z.optional(z.array(z.string())),
1974
+ variants: z.optional(z.array(FontVariantSchema)),
1975
+ /** Emit `<Font preload />` for this family. */
1976
+ preload: z.optional(z.union([z.boolean(), z.array(z.object({
1977
+ weight: FontWeight,
1978
+ style: z.optional(FontStyle)
1979
+ }))]))
1980
+ });
1981
+ const PresentSchema = z.optional(z.record(z.string(), z.unknown()));
1982
+ const NavItemSchema = z.object({
1983
+ label: z.optional(LocalizableSchema),
1984
+ image: z.optional(ImageRefSchema),
1985
+ href: z.optional(z.string()),
1986
+ icon: z.optional(z.string()),
1987
+ attrs: AttrsSchema,
1988
+ itemAttrs: AttrsSchema,
1989
+ submenu: z.optional(z.object({
1990
+ present: PresentSchema,
1991
+ get items() {
1992
+ return z.optional(z.array(NavItemSchema));
1993
+ }
1994
+ }))
1995
+ });
1996
+ const FormFieldType = z.enum([
1997
+ "text",
1998
+ "email",
1999
+ "tel",
2000
+ "url",
2001
+ "number",
2002
+ "password",
2003
+ "textarea",
2004
+ "select",
2005
+ "checkbox",
2006
+ "radio",
2007
+ "date",
2008
+ "file",
2009
+ "hidden"
2010
+ ]);
2011
+ const FormFieldSchema = z.object({
2012
+ name: z.string(),
2013
+ type: FormFieldType,
2014
+ label: z.optional(LocalizableSchema),
2015
+ required: z.optional(z.boolean()),
2016
+ validation: z.optional(z.string()),
2017
+ options: z.optional(z.union([z.string(), z.array(z.object({
2018
+ label: z.string(),
2019
+ value: z.string()
2020
+ }))])),
2021
+ attrs: AttrsSchema
2022
+ });
2023
+ const ContentNodeSchema = z.object({
2024
+ tag: z.string(),
2025
+ attrs: AttrsSchema,
2026
+ text: z.optional(z.string()),
2027
+ get children() {
2028
+ return z.optional(z.array(ContentNodeSchema));
2029
+ }
2030
+ });
2031
+ const TemplateSchema = z.union([
2032
+ z.object({
2033
+ type: z.literal("nav"),
2034
+ tags: z.optional(z.array(z.string())),
2035
+ present: PresentSchema,
2036
+ items: z.optional(z.array(NavItemSchema))
2037
+ }),
2038
+ z.object({
2039
+ type: z.literal("form"),
2040
+ tags: z.optional(z.array(z.string())),
2041
+ present: PresentSchema,
2042
+ action: z.optional(z.string()),
2043
+ method: z.optional(z.enum(["get", "post"])),
2044
+ fields: z.optional(z.array(FormFieldSchema)),
2045
+ submit: z.optional(z.record(z.string(), AttrValue)),
2046
+ honeypot: z.optional(z.boolean())
2047
+ }),
2048
+ z.object({
2049
+ type: z.literal("nodes"),
2050
+ tags: z.optional(z.array(z.string())),
2051
+ nodes: z.optional(z.array(ContentNodeSchema))
2052
+ })
2053
+ ]);
2054
+ const GalleryImageSchema = z.object({
2055
+ src: z.string(),
2056
+ alt: LocalizableSchema,
2057
+ caption: z.optional(LocalizableSchema)
2058
+ });
2059
+ const GallerySchema = z.object({
2060
+ title: z.optional(LocalizableSchema),
2061
+ tags: z.optional(z.array(z.string())),
2062
+ images: z.array(GalleryImageSchema)
2063
+ });
2064
+ const TestimonialItemSchema = z.object({
2065
+ quote: LocalizableSchema,
2066
+ name: z.string(),
2067
+ role: z.optional(z.string()),
2068
+ rating: z.optional(z.number().check(z.gte(0), z.lte(5))),
2069
+ date: z.optional(IsoDate)
2070
+ });
2071
+ const TestimonialGroupSchema = z.object({
2072
+ title: z.optional(LocalizableSchema),
2073
+ items: z.array(TestimonialItemSchema)
2074
+ });
2075
+ const OfferSchema = z.object({
2076
+ name: z.optional(LocalizableSchema),
2077
+ price: z.optional(z.union([z.string(), z.number()])),
2078
+ priceCurrency: z.optional(Iso4217),
2079
+ availability: z.optional(z.string()),
2080
+ url: z.optional(z.url()),
2081
+ validFrom: z.optional(IsoDate),
2082
+ priceValidUntil: z.optional(IsoDate)
2083
+ });
2084
+ const ServiceSchema = z.object({
2085
+ name: LocalizableSchema,
2086
+ description: z.optional(LocalizableSchema),
2087
+ slug: z.optional(LocalizableSchema),
2088
+ offers: z.optional(z.array(OfferSchema))
2089
+ });
2090
+ const OpenGraphSchema = z.object({
2091
+ type: z.optional(z.string()),
2092
+ siteName: z.optional(z.string()),
2093
+ title: z.optional(LocalizableSchema),
2094
+ description: z.optional(LocalizableSchema),
2095
+ image: z.optional(ImageRefSchema),
2096
+ locale: z.optional(z.string())
2097
+ });
2098
+ const TwitterSchema = z.object({
2099
+ card: z.optional(z.enum([
2100
+ "summary",
2101
+ "summary_large_image",
2102
+ "app",
2103
+ "player"
2104
+ ])),
2105
+ site: z.optional(z.string()),
2106
+ creator: z.optional(z.string()),
2107
+ title: z.optional(LocalizableSchema),
2108
+ description: z.optional(LocalizableSchema),
2109
+ image: z.optional(ImageRefSchema)
2110
+ });
2111
+ const SiteConfigSchema = z.object({
2112
+ defaultLocale: z.string(),
2113
+ locales: z.record(z.string(), LocaleSchema),
2114
+ organization: z.optional(OrganizationSchema),
2115
+ contact: z.optional(ContactConfigSchema),
2116
+ domains: z.optional(z.object({
2117
+ canonical: z.url(),
2118
+ aliases: z.optional(z.array(DomainAliasSchema))
2119
+ })),
2120
+ primaryLocation: z.optional(z.string()),
2121
+ locations: z.optional(z.record(z.string(), LocationSchema)),
2122
+ services: z.optional(z.record(z.string(), ServiceSchema)),
2123
+ links: z.optional(z.object({
2124
+ googleMaps: z.optional(z.url()),
2125
+ instagram: z.optional(z.url()),
2126
+ facebook: z.optional(z.url()),
2127
+ x: z.optional(z.url()),
2128
+ linkedin: z.optional(z.url()),
2129
+ youtube: z.optional(z.url()),
2130
+ github: z.optional(z.url())
2131
+ })),
2132
+ dns: z.optional(z.record(z.string(), DnsDomainSchema)),
2133
+ cloudflare: z.optional(z.record(z.string(), z.unknown())),
2134
+ environments: z.record(z.string(), EnvironmentSchema),
2135
+ abTesting: z.optional(z.object({
2136
+ enabled: z.boolean(),
2137
+ cookieName: z.optional(z.string()),
2138
+ cookieMaxAge: z.optional(z.number().check(z.int(), z.positive())),
2139
+ splitRatio: z.optional(z.number().check(z.gte(0), z.lte(1))),
2140
+ variants: z.optional(z.record(z.string(), AbVariantSchema))
2141
+ })),
2142
+ designSystem: z.record(z.string(), DesignSystemEntry),
2143
+ defaultTheme: z.optional(z.string()),
2144
+ defaultColorScheme: z.optional(z.enum(["light", "dark"])),
2145
+ respectSystemPreference: z.optional(z.boolean()),
2146
+ fonts: z.optional(z.array(FontSchema)),
2147
+ tags: z.optional(z.record(z.string(), z.unknown())),
2148
+ postTypes: z.optional(z.record(z.string(), z.unknown())),
2149
+ galleries: z.optional(z.record(z.string(), GallerySchema)),
2150
+ testimonials: z.optional(z.record(z.string(), TestimonialGroupSchema)),
2151
+ templates: z.optional(z.record(z.string(), TemplateSchema)),
2152
+ navigation: z.optional(z.object({ activeTemplate: z.optional(z.object({
2153
+ default: z.optional(z.string()),
2154
+ breakpoints: z.optional(z.record(z.string(), z.string()))
2155
+ })) })),
2156
+ seo: z.optional(z.object({
2157
+ titleTemplate: z.optional(z.string()),
2158
+ descriptionTemplate: z.optional(z.string()),
2159
+ robots: z.optional(RobotsSchema),
2160
+ googleSiteVerification: z.optional(z.string()),
2161
+ bingSiteVerification: z.optional(z.string()),
2162
+ openGraph: z.optional(OpenGraphSchema),
2163
+ twitter: z.optional(TwitterSchema)
2164
+ })),
2165
+ analytics: z.optional(z.object({
2166
+ googleAnalyticsId: z.optional(z.string()),
2167
+ plausibleDomain: z.optional(z.string()),
2168
+ googleTagManagerId: z.optional(z.string())
2169
+ })),
2170
+ notifications: z.optional(z.object({ email: z.optional(z.email()) })),
2171
+ tracking: z.optional(z.object({
2172
+ enabled: z.optional(z.boolean()),
2173
+ platforms: z.optional(z.array(z.string()))
2174
+ })),
2175
+ security: z.optional(z.object({ turnstile: z.optional(z.object({ siteKey: z.optional(z.string()) })) })),
2176
+ legal: z.optional(z.object({
2177
+ privacyPolicy: z.optional(z.object({
2178
+ enabled: z.boolean(),
2179
+ url: z.optional(z.url()),
2180
+ lastUpdated: z.optional(IsoDate)
2181
+ })),
2182
+ termsOfService: z.optional(z.object({
2183
+ enabled: z.boolean(),
2184
+ url: z.optional(z.url()),
2185
+ lastUpdated: z.optional(z.nullable(IsoDate))
2186
+ })),
2187
+ cookieConsent: z.optional(z.object({
2188
+ enabled: z.boolean(),
2189
+ type: z.optional(z.enum(["opt-in", "opt-out"])),
2190
+ position: z.optional(z.enum([
2191
+ "top",
2192
+ "bottom",
2193
+ "center"
2194
+ ])),
2195
+ categories: z.optional(z.array(z.string()))
2196
+ }))
2197
+ })),
2198
+ icons: z.optional(z.object({
2199
+ ui: z.optional(z.string()),
2200
+ brand: z.optional(z.string()),
2201
+ semantic: z.optional(z.array(z.string())),
2202
+ "fa7-solid": z.optional(z.array(z.string())),
2203
+ "fa7-regular": z.optional(z.array(z.string())),
2204
+ "fa7-light": z.optional(z.array(z.string())),
2205
+ "fa7-thin": z.optional(z.array(z.string())),
2206
+ "fa7-brands": z.optional(z.array(z.string())),
2207
+ "simple-icons": z.optional(z.array(z.string())),
2208
+ "material-symbols": z.optional(z.array(z.string())),
2209
+ lucide: z.optional(z.array(z.string()))
2210
+ })),
2211
+ favicon: z.optional(z.object({
2212
+ source: z.string(),
2213
+ lowResSource: z.optional(z.string())
2214
+ })),
2215
+ deployment: z.optional(z.object({
2216
+ platform: z.optional(z.string()),
2217
+ buildCommand: z.optional(z.string()),
2218
+ deployCommand: z.optional(z.string()),
2219
+ outputDirectory: z.optional(z.string()),
2220
+ type: z.optional(z.string())
2221
+ }))
2222
+ });
2223
+ const SITE_SCHEMA_URL = "https://unpkg.com/@getvitops/generator/site.schema.json";
2224
+ /** The published JSON Schema (draft-2020-12), derived from the zod schema. */
2225
+ const siteJsonSchema = {
2226
+ $id: SITE_SCHEMA_URL,
2227
+ ...z.toJSONSchema(SiteConfigSchema, { target: "draft-2020-12" })
2228
+ };
2229
+ const issue = (path, message) => ({
2230
+ code: "custom",
2231
+ path,
2232
+ message
2233
+ });
2234
+ /**
2235
+ * Validate a raw (already parsed) config: shape via zod, then the cross-field
2236
+ * integrity that JSON Schema can't express (referential keys, extends acyclicity).
2237
+ * These live only here — the published JSON Schema enforces shape, not integrity.
2238
+ */
2239
+ function validateSite(input) {
2240
+ const parsed = z.safeParse(SiteConfigSchema, input);
2241
+ if (!parsed.success) return {
2242
+ ok: false,
2243
+ data: void 0,
2244
+ errors: parsed.error.issues
2245
+ };
2246
+ const cfg = parsed.data;
2247
+ const errors = [];
2248
+ const has = (m, k) => !!m && k in m;
2249
+ if (!has(cfg.locales, cfg.defaultLocale)) errors.push(issue(["defaultLocale"], `defaultLocale "${cfg.defaultLocale}" is not in locales`));
2250
+ if (!has(cfg.designSystem, "default") && cfg.defaultTheme == null) errors.push(issue(["designSystem"], "designSystem must include a \"default\" theme"));
2251
+ if (cfg.defaultTheme != null && !has(cfg.designSystem, cfg.defaultTheme)) errors.push(issue(["defaultTheme"], `defaultTheme "${cfg.defaultTheme}" is not in designSystem`));
2252
+ for (const [key, entry] of Object.entries(cfg.designSystem)) if (entry.extends != null && !has(cfg.designSystem, entry.extends)) errors.push(issue([
2253
+ "designSystem",
2254
+ key,
2255
+ "extends"
2256
+ ], `extends "${entry.extends}" is not a designSystem key`));
2257
+ for (const key of Object.keys(cfg.designSystem)) try {
2258
+ resolveTheme(cfg.designSystem, key);
2259
+ } catch (e) {
2260
+ errors.push(issue(["designSystem", key], e.message));
2261
+ }
2262
+ if (cfg.primaryLocation != null && !has(cfg.locations, cfg.primaryLocation)) errors.push(issue(["primaryLocation"], `primaryLocation "${cfg.primaryLocation}" is not in locations`));
2263
+ if (typeof cfg.contact === "string" && !has(cfg.locations, cfg.contact)) errors.push(issue(["contact"], `contact "${cfg.contact}" is not a locations key`));
2264
+ for (const [name, v] of Object.entries(cfg.abTesting?.variants ?? {})) if (!has(cfg.environments, v.environment)) errors.push(issue([
2265
+ "abTesting",
2266
+ "variants",
2267
+ name,
2268
+ "environment"
2269
+ ], `environment "${v.environment}" is not in environments`));
2270
+ for (const [i, a] of (cfg.domains?.aliases ?? []).entries()) if (a.environment != null && !has(cfg.environments, a.environment)) errors.push(issue([
2271
+ "domains",
2272
+ "aliases",
2273
+ i,
2274
+ "environment"
2275
+ ], `environment "${a.environment}" is not in environments`));
2276
+ const templates = cfg.templates;
2277
+ const at = cfg.navigation?.activeTemplate;
2278
+ if (at?.default != null && !has(templates, at.default)) errors.push(issue([
2279
+ "navigation",
2280
+ "activeTemplate",
2281
+ "default"
2282
+ ], `template "${at.default}" is not in templates`));
2283
+ for (const [bp, tpl] of Object.entries(at?.breakpoints ?? {})) if (!has(templates, tpl)) errors.push(issue([
2284
+ "navigation",
2285
+ "activeTemplate",
2286
+ "breakpoints",
2287
+ bp
2288
+ ], `template "${tpl}" is not in templates`));
2289
+ if (errors.length) return {
2290
+ ok: false,
2291
+ data: void 0,
2292
+ errors
2293
+ };
2294
+ return {
2295
+ ok: true,
2296
+ data: cfg,
2297
+ errors: []
2298
+ };
2299
+ }
2300
+ /**
2301
+ * Resolve a design-system theme by following its `extends` chain and deep-merging
2302
+ * base → child (child wins; arrays replace wholesale, correct for token overrides).
2303
+ * Throws on unknown keys or an `extends` cycle. The result is a plain
2304
+ * `DesignSystem` (validate it against `DesignSystemSchema` for completeness).
2305
+ */
2306
+ function resolveTheme(designSystem, key) {
2307
+ const chain = [];
2308
+ const seen = /* @__PURE__ */ new Set();
2309
+ let cur = key;
2310
+ while (cur != null) {
2311
+ if (seen.has(cur)) throw new Error(`extends cycle in designSystem at "${cur}"`);
2312
+ seen.add(cur);
2313
+ const entry = designSystem[cur];
2314
+ if (entry == null) throw new Error(`designSystem theme "${cur}" not found`);
2315
+ chain.unshift(entry);
2316
+ cur = entry.extends;
2317
+ }
2318
+ const out = {};
2319
+ for (const entry of chain) {
2320
+ const { extends: _e, displayName: _d, ...ds } = entry;
2321
+ deepMerge(out, ds);
2322
+ }
2323
+ return out;
2324
+ }
2325
+ /**
2326
+ * Resolve a raw (already-parsed) config into a validated `SiteConfig`:
2327
+ * 1. strip YAML nulls,
2328
+ * 2. apply the active A/B variant's `overrides` (by `siteEnv`),
2329
+ * 3. normalise a bare-`DesignSystem` `designSystem` shorthand to `{ default: … }`,
2330
+ * 4. validate (shape + cross-field).
2331
+ * Pure — no YAML/env access. A loader supplies `siteEnv`.
2332
+ */
2333
+ function resolveSiteConfig(input, siteEnv = "production") {
2334
+ const raw = stripNulls(input);
2335
+ const envConfig = raw.environments?.[siteEnv];
2336
+ if (envConfig?.variant) {
2337
+ const overrides = raw.abTesting?.variants?.[envConfig.variant]?.overrides;
2338
+ if (overrides) deepMerge(raw, overrides);
2339
+ }
2340
+ const ds = raw.designSystem;
2341
+ if (ds != null && "colors" in ds) raw.designSystem = { default: ds };
2342
+ const result = validateSite(raw);
2343
+ if (!result.ok) {
2344
+ const msg = result.errors.map((e) => ` ${e.path.join(".")}: ${e.message}`).join("\n");
2345
+ throw new Error(`Invalid site config:\n${msg}`);
2346
+ }
2347
+ return result.data;
2348
+ }
2349
+ /** Strip nulls (YAML `~`/`null`) → undefined so optional fields validate. */
2350
+ function stripNulls(obj) {
2351
+ if (obj === null) return void 0;
2352
+ if (Array.isArray(obj)) return obj.map(stripNulls);
2353
+ if (obj && typeof obj === "object") {
2354
+ const result = {};
2355
+ for (const [key, value] of Object.entries(obj)) {
2356
+ const stripped = stripNulls(value);
2357
+ if (stripped !== void 0) result[key] = stripped;
2358
+ }
2359
+ return result;
2360
+ }
2361
+ return obj;
2362
+ }
2363
+ /** Deep-merge `source` into `target` (in place). Arrays replace wholesale. */
2364
+ function deepMerge(target, source) {
2365
+ for (const key of Object.keys(source)) {
2366
+ const s = source[key];
2367
+ const t = target[key];
2368
+ if (s && typeof s === "object" && !Array.isArray(s) && t && typeof t === "object" && !Array.isArray(t)) deepMerge(t, s);
2369
+ else target[key] = s;
2370
+ }
2371
+ }
2372
+
2373
+ //#endregion
2374
+ //#region src/index.ts
2375
+ /**
2376
+ * A minimal but complete starter design system, used by `vitops init`. It
2377
+ * validates against the schema and generates real output in every format, so a
2378
+ * new project can `vitops generate` immediately and then tune from here.
2379
+ */
2380
+ function defaultConfig() {
2381
+ return {
2382
+ $schema: SCHEMA_URL,
2383
+ colors: {
2384
+ palette: {
2385
+ brand: {
2386
+ xxd: "#0a1f17",
2387
+ xd: "#123a2b",
2388
+ d: "#1f6b4f",
2389
+ base: "#2e9b73",
2390
+ l: "#5cbf9a",
2391
+ xl: "#93dcc0",
2392
+ xxl: "#c8f0e2"
2393
+ },
2394
+ ink: {
2395
+ xxd: "#0b0e13",
2396
+ xd: "#151a22",
2397
+ d: "#2a323f",
2398
+ base: "#3f4a5c",
2399
+ l: "#6b7688",
2400
+ xl: "#a3abb8",
2401
+ xxl: "#d7dbe2"
2402
+ }
2403
+ },
2404
+ utilities: [
2405
+ "bg",
2406
+ "text",
2407
+ "border"
2408
+ ],
2409
+ schemes: {
2410
+ default: {
2411
+ appearance: "light",
2412
+ semantic: {
2413
+ "brand-primary": "brand",
2414
+ surface: "ink",
2415
+ neutral: "ink"
2416
+ }
2417
+ },
2418
+ alternate: {
2419
+ appearance: "dark",
2420
+ semantic: {
2421
+ surface: { invert: true },
2422
+ neutral: { invert: true }
2423
+ }
2424
+ }
2425
+ }
2426
+ },
2427
+ shadows: {
2428
+ sm: "0 1px 2px rgb(0 0 0 / 0.08)",
2429
+ md: "0 4px 6px rgb(0 0 0 / 0.12)",
2430
+ lg: "0 10px 15px rgb(0 0 0 / 0.18)"
2431
+ },
2432
+ fonts: {
2433
+ display: "system-ui, -apple-system, \"Segoe UI\", sans-serif",
2434
+ sans: "system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif",
2435
+ mono: "ui-monospace, \"SF Mono\", Menlo, monospace"
2436
+ },
2437
+ typeScale: {
2438
+ base: "1rem",
2439
+ names: [
2440
+ "2xs",
2441
+ "xs",
2442
+ "s",
2443
+ "m",
2444
+ "l",
2445
+ "xl",
2446
+ "2xl",
2447
+ "3xl"
2448
+ ],
2449
+ baseStep: 4,
2450
+ baseline: "m",
2451
+ ratio: 1.2,
2452
+ fluid: {
2453
+ minVw: "22.5rem",
2454
+ maxVw: "80rem",
2455
+ minRatio: 1.12
2456
+ }
2457
+ },
2458
+ spaceScale: {
2459
+ base: "1rem",
2460
+ names: [
2461
+ "2xs",
2462
+ "xs",
2463
+ "s",
2464
+ "m",
2465
+ "l",
2466
+ "xl",
2467
+ "2xl",
2468
+ "3xl"
2469
+ ],
2470
+ baseStep: 4,
2471
+ baseline: "m",
2472
+ ratio: 1.25,
2473
+ fluid: {
2474
+ minVw: "22.5rem",
2475
+ maxVw: "80rem",
2476
+ minRatio: 1.2
2477
+ }
2478
+ },
2479
+ patterns: {
2480
+ defaults: {
2481
+ ds: "none",
2482
+ b: "1px solid var(--color-surface-xl)",
2483
+ br: "0.375rem",
2484
+ p: "1rem",
2485
+ fs: "1rem"
2486
+ },
2487
+ radii: {
2488
+ pill: "999px",
2489
+ circle: "50%",
2490
+ card: "0.5rem"
2491
+ },
2492
+ groups: {
2493
+ control: {
2494
+ ds: "var(--ds-default)",
2495
+ b: "var(--b-default)",
2496
+ br: "var(--br-default)",
2497
+ p: "0.5em 1em",
2498
+ fs: "var(--fs-default)"
2499
+ },
2500
+ panel: {
2501
+ ds: "var(--shadow-md)",
2502
+ b: "var(--b-default)",
2503
+ br: "0.5rem",
2504
+ p: "1rem",
2505
+ fs: "var(--fs-default)"
2506
+ }
2507
+ },
2508
+ z: {
2509
+ raised: 1,
2510
+ sticky: 100,
2511
+ overlay: 200,
2512
+ top: 1e3
2513
+ },
2514
+ items: {
2515
+ button: {
2516
+ group: "control",
2517
+ element: "button",
2518
+ default_role: "brand-primary",
2519
+ base: {
2520
+ padding: "0.6em 1.2em",
2521
+ "border-radius": "var(--br-control, 0.25rem)",
2522
+ border: "none",
2523
+ "font-weight": "600",
2524
+ color: "#fff",
2525
+ cursor: "pointer",
2526
+ "box-shadow": "var(--shadow-sm)"
2527
+ },
2528
+ states: {
2529
+ hover: { step: 1 },
2530
+ active: { scale: .97 },
2531
+ "focus-visible": { ring: true }
2532
+ },
2533
+ roles: []
2534
+ },
2535
+ card: {
2536
+ group: "panel",
2537
+ class: "card",
2538
+ base: {
2539
+ "border-radius": "var(--br-card-group)",
2540
+ padding: "1.5rem",
2541
+ background: "var(--color-surface-xxl)",
2542
+ border: "var(--b-card-group)",
2543
+ "box-shadow": "var(--ds-card-group)"
2544
+ },
2545
+ states: {},
2546
+ roles: []
2547
+ }
2548
+ }
2549
+ },
2550
+ typography: {
2551
+ families: {
2552
+ display: "var(--font-display)",
2553
+ sans: "var(--font-sans)",
2554
+ code: "var(--font-mono, ui-monospace, monospace)"
2555
+ },
2556
+ roles: {
2557
+ display: {
2558
+ family: "display",
2559
+ size: "var(--text-3xl)",
2560
+ weight: 700,
2561
+ "line-height": "1.05",
2562
+ tracking: "-0.03em"
2563
+ },
2564
+ heading: {
2565
+ family: "display",
2566
+ size: "var(--text-xl)",
2567
+ weight: 600,
2568
+ "line-height": "1.3"
2569
+ },
2570
+ body: {
2571
+ family: "sans",
2572
+ size: "var(--text-m)",
2573
+ weight: 400,
2574
+ "line-height": "1.55"
2575
+ }
2576
+ },
2577
+ headings: {
2578
+ h1: "display",
2579
+ h2: "heading",
2580
+ h3: "heading"
2581
+ }
2582
+ },
2583
+ animations: {
2584
+ effects: {
2585
+ "fade-in": {
2586
+ kf: "composite",
2587
+ vars: { "opacity-from": 0 }
2588
+ },
2589
+ "slide-up": {
2590
+ kf: "composite",
2591
+ vars: { "translate-y-from": "var(--slide-distance, 2rem)" }
2592
+ }
2593
+ },
2594
+ journeys: {
2595
+ base: {
2596
+ fade: { "opacity-from": 0 },
2597
+ slide: {}
2598
+ },
2599
+ compose: [
2600
+ ["fade"],
2601
+ ["slide"],
2602
+ ["fade", "slide"]
2603
+ ]
2604
+ }
2605
+ }
2606
+ };
2607
+ }
2608
+
2609
+ //#endregion
2610
+ export { DesignSystemSchema, SCHEMA_URL, SITE_SCHEMA_URL, SiteConfigSchema, deepMerge, defaultConfig, generate, generateDocs, jsonSchema, resolveSiteConfig, resolveTheme, siteJsonSchema, stripNulls, validate, validateSite };
2611
+ //# sourceMappingURL=index.mjs.map