@getvitops/generator 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/css/patterns/carousel.css +3 -3
- package/assets/css/patterns/code.css +2 -2
- package/assets/css/patterns/combobox.css +3 -3
- package/assets/css/patterns/comment.css +1 -1
- package/assets/css/patterns/copy.css +3 -3
- package/assets/css/patterns/dialog.css +2 -2
- package/assets/css/patterns/drawer.css +2 -2
- package/assets/css/patterns/forms.css +10 -10
- package/assets/css/patterns/icon.css +2 -2
- package/assets/css/patterns/list.css +6 -6
- package/assets/css/patterns/media.css +1 -1
- package/assets/css/patterns/nav.css +5 -5
- package/assets/css/patterns/notification.css +3 -3
- package/assets/css/patterns/scroll-based.css +1 -1
- package/assets/css/patterns/scroll-target.css +3 -3
- package/assets/css/patterns/sitenav.css +14 -14
- package/assets/css/patterns/split-link.css +4 -4
- package/assets/css/patterns/tag.css +1 -1
- package/assets/css/patterns/text-effects.css +1 -1
- package/assets/css/patterns/tooltip.css +2 -2
- package/assets/css/patterns/tree.css +3 -3
- package/assets/js/elements.js +10 -10
- package/dist/index.d.mts +34 -88
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +446 -286
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
- package/schema.json +59 -93
- package/site.schema.json +2454 -0
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { transform } from "lightningcss";
|
|
6
6
|
import * as z from "zod/mini";
|
|
7
|
+
import { generateOklchPalette, hexToOklch, oklchStringToHex, pickOn } from "@getvitops/utils/color";
|
|
7
8
|
|
|
8
9
|
//#region src/schema.ts
|
|
9
10
|
/**
|
|
@@ -17,26 +18,12 @@ import * as z from "zod/mini";
|
|
|
17
18
|
const CssDecls = z.record(z.string(), z.string());
|
|
18
19
|
const Scalar = z.union([z.string(), z.number()]);
|
|
19
20
|
const VarMap = z.record(z.string(), Scalar);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
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)
|
|
21
|
+
const SeededRamp = z.object({
|
|
22
|
+
seed: z.string(),
|
|
23
|
+
anchors: z.optional(z.record(z.string(), z.string()))
|
|
39
24
|
});
|
|
25
|
+
const FixedRamp = z.object({ tones: z.union([z.array(z.string()), z.record(z.string(), z.string())]) });
|
|
26
|
+
const Ramp = z.union([SeededRamp, FixedRamp]);
|
|
40
27
|
const UtilityName = z.enum([
|
|
41
28
|
"bg",
|
|
42
29
|
"text",
|
|
@@ -47,8 +34,8 @@ const UtilityName = z.enum([
|
|
|
47
34
|
]);
|
|
48
35
|
const Colors = z.object({
|
|
49
36
|
palette: z.record(z.string(), Ramp),
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
roles: z.record(z.string(), z.string()),
|
|
38
|
+
utilities: z.optional(z.array(UtilityName))
|
|
52
39
|
});
|
|
53
40
|
const Scale = z.object({
|
|
54
41
|
base: z.string(),
|
|
@@ -111,11 +98,8 @@ const DesignSystemSchema = z.object({
|
|
|
111
98
|
});
|
|
112
99
|
const ColorsPatch = z.object({
|
|
113
100
|
palette: z.optional(z.record(z.string(), Ramp)),
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
appearance: z.optional(z.enum(["light", "dark"])),
|
|
117
|
-
semantic: z.optional(z.record(z.string(), RoleSpec))
|
|
118
|
-
})))
|
|
101
|
+
roles: z.optional(z.record(z.string(), z.string())),
|
|
102
|
+
utilities: z.optional(z.array(UtilityName))
|
|
119
103
|
});
|
|
120
104
|
const DesignSystemPatchSchema = z.extend(z.partial(DesignSystemSchema), { colors: z.optional(ColorsPatch) });
|
|
121
105
|
/**
|
|
@@ -137,17 +121,18 @@ function validate(input) {
|
|
|
137
121
|
data: void 0,
|
|
138
122
|
errors: result.error.issues
|
|
139
123
|
};
|
|
140
|
-
|
|
124
|
+
const { palette, roles } = result.data.colors;
|
|
125
|
+
for (const [role, hue] of Object.entries(roles)) if (palette[hue] == null) return {
|
|
141
126
|
ok: false,
|
|
142
127
|
data: void 0,
|
|
143
128
|
errors: [{
|
|
144
129
|
code: "custom",
|
|
145
130
|
path: [
|
|
146
131
|
"colors",
|
|
147
|
-
"
|
|
148
|
-
|
|
132
|
+
"roles",
|
|
133
|
+
role
|
|
149
134
|
],
|
|
150
|
-
message: "
|
|
135
|
+
message: `role "${role}" references unknown palette hue "${hue}"`
|
|
151
136
|
}]
|
|
152
137
|
};
|
|
153
138
|
return {
|
|
@@ -157,6 +142,185 @@ function validate(input) {
|
|
|
157
142
|
};
|
|
158
143
|
}
|
|
159
144
|
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/tokens.ts
|
|
147
|
+
/**
|
|
148
|
+
* Palette expansion + semantic functional tokens.
|
|
149
|
+
*
|
|
150
|
+
* A palette hue is authored one of two ways:
|
|
151
|
+
* • `{ seed, anchors? }` — an 11-step numeric scale (50…950) generated in
|
|
152
|
+
* OKLCH via @getvitops/utils/color (lightness transposed toward tinted
|
|
153
|
+
* near-white/near-black, chroma dampened at the extremes),
|
|
154
|
+
* • `{ tones }` — a fixed brand kit: authored tones placed verbatim at their
|
|
155
|
+
* nearest steps + tinted endpoints; nothing else is invented (strict kits).
|
|
156
|
+
*
|
|
157
|
+
* Every role (from `colors.roles`) gets FUNCTIONAL tokens — the public API:
|
|
158
|
+
* --<role>-bg / -bg-muted / -border / -border-bold / -solid / -solid-bold /
|
|
159
|
+
* -on-solid / -text / -text-muted / -text-x-muted
|
|
160
|
+
* plus the appearance-relative emphasis stops (x-muted · muted · bold · x-bold)
|
|
161
|
+
* and, for `surface`, the translucent `--surface-glass` + `--overlay` scrim.
|
|
162
|
+
* Dark mode flips the mapping automatically (solid stays mode-stable).
|
|
163
|
+
*/
|
|
164
|
+
const NUMERIC_STEPS = [
|
|
165
|
+
"50",
|
|
166
|
+
"100",
|
|
167
|
+
"200",
|
|
168
|
+
"300",
|
|
169
|
+
"400",
|
|
170
|
+
"500",
|
|
171
|
+
"600",
|
|
172
|
+
"700",
|
|
173
|
+
"800",
|
|
174
|
+
"900",
|
|
175
|
+
"950"
|
|
176
|
+
];
|
|
177
|
+
const STEP_L = {
|
|
178
|
+
"50": .95,
|
|
179
|
+
"100": .9,
|
|
180
|
+
"200": .8,
|
|
181
|
+
"300": .7,
|
|
182
|
+
"400": .6,
|
|
183
|
+
"500": .5,
|
|
184
|
+
"600": .4,
|
|
185
|
+
"700": .3,
|
|
186
|
+
"800": .2,
|
|
187
|
+
"900": .1,
|
|
188
|
+
"950": .05
|
|
189
|
+
};
|
|
190
|
+
const isHex = (v) => v.startsWith("#");
|
|
191
|
+
const toOklchStr = (v) => {
|
|
192
|
+
if (!isHex(v)) return v;
|
|
193
|
+
const { l, c, h } = hexToOklch(v);
|
|
194
|
+
return `oklch(${(l * 100).toFixed(2)}% ${c.toFixed(4)} ${h.toFixed(2)})`;
|
|
195
|
+
};
|
|
196
|
+
const nearestStep = (l) => NUMERIC_STEPS.reduce((best, s) => Math.abs(l - STEP_L[s]) < Math.abs(l - STEP_L[best]) ? s : best);
|
|
197
|
+
function expandSeeded(input) {
|
|
198
|
+
const anchors = {};
|
|
199
|
+
const seedStep = nearestStep(hexToOklch(input.seed).l);
|
|
200
|
+
anchors[seedStep] = toOklchStr(input.seed);
|
|
201
|
+
for (const [key, value] of Object.entries(input.anchors ?? {})) {
|
|
202
|
+
const step = NUMERIC_STEPS.includes(key) ? key : nearestStep(hexToOklch(value).l);
|
|
203
|
+
anchors[step] = toOklchStr(value);
|
|
204
|
+
}
|
|
205
|
+
const hue = hexToOklch(input.seed).h;
|
|
206
|
+
anchors["50"] ??= `oklch(97% 0.008 ${hue.toFixed(2)})`;
|
|
207
|
+
anchors["950"] ??= `oklch(13% 0.015 ${hue.toFixed(2)})`;
|
|
208
|
+
const scale = generateOklchPalette(anchors);
|
|
209
|
+
const numeric = {};
|
|
210
|
+
for (const s of NUMERIC_STEPS) if (scale[s] != null) numeric[s] = oklchStringToHex(scale[s]);
|
|
211
|
+
return { numeric };
|
|
212
|
+
}
|
|
213
|
+
function expandTones(input) {
|
|
214
|
+
const tones = Array.isArray(input.tones) ? input.tones : Object.values(input.tones);
|
|
215
|
+
const numeric = {};
|
|
216
|
+
for (const hex of tones) numeric[nearestStep(hexToOklch(hex).l)] = hex;
|
|
217
|
+
const hue = hexToOklch(tones[0] ?? "#808080").h;
|
|
218
|
+
numeric["50"] ??= oklchStringToHex(`oklch(97% 0.010 ${hue.toFixed(2)})`);
|
|
219
|
+
numeric["950"] ??= oklchStringToHex(`oklch(15% 0.015 ${hue.toFixed(2)})`);
|
|
220
|
+
return {
|
|
221
|
+
numeric,
|
|
222
|
+
sparse: true
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/** Snap to the nearest AVAILABLE numeric step (sparse kits). */
|
|
226
|
+
function snap(numeric, want) {
|
|
227
|
+
if (numeric[want] != null) return want;
|
|
228
|
+
return NUMERIC_STEPS.filter((s) => numeric[s] != null).reduce((best, s) => Math.abs(STEP_L[s] - STEP_L[want]) < Math.abs(STEP_L[best] - STEP_L[want]) ? s : best);
|
|
229
|
+
}
|
|
230
|
+
/** Expand every palette hue into its numeric scale. */
|
|
231
|
+
function expandPalette(raw) {
|
|
232
|
+
const out = {};
|
|
233
|
+
for (const [name, entry] of Object.entries(raw)) {
|
|
234
|
+
const e = entry;
|
|
235
|
+
if (typeof e?.seed === "string") out[name] = expandSeeded(e);
|
|
236
|
+
else if (e?.tones != null) out[name] = expandTones(e);
|
|
237
|
+
else throw new Error(`palette hue "${name}" must have a "seed" or "tones"`);
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
const LIGHT = {
|
|
242
|
+
bg: "50",
|
|
243
|
+
"bg-muted": "100",
|
|
244
|
+
border: "200",
|
|
245
|
+
"border-bold": "300",
|
|
246
|
+
text: "950",
|
|
247
|
+
"text-muted": "800",
|
|
248
|
+
"text-x-muted": "600"
|
|
249
|
+
};
|
|
250
|
+
const DARK = {
|
|
251
|
+
bg: "950",
|
|
252
|
+
"bg-muted": "900",
|
|
253
|
+
border: "800",
|
|
254
|
+
"border-bold": "700",
|
|
255
|
+
text: "50",
|
|
256
|
+
"text-muted": "200",
|
|
257
|
+
"text-x-muted": "400"
|
|
258
|
+
};
|
|
259
|
+
const SURFACE_LIGHT = {
|
|
260
|
+
...LIGHT,
|
|
261
|
+
bg: "100",
|
|
262
|
+
"bg-muted": "200",
|
|
263
|
+
"bg-bold": "50"
|
|
264
|
+
};
|
|
265
|
+
const SURFACE_DARK = {
|
|
266
|
+
...DARK,
|
|
267
|
+
bg: "900",
|
|
268
|
+
"bg-muted": "950",
|
|
269
|
+
"bg-bold": "800"
|
|
270
|
+
};
|
|
271
|
+
const EMPHASIS_LIGHT = {
|
|
272
|
+
"x-muted": "100",
|
|
273
|
+
muted: "300",
|
|
274
|
+
bold: "700",
|
|
275
|
+
"x-bold": "900"
|
|
276
|
+
};
|
|
277
|
+
const EMPHASIS_DARK = {
|
|
278
|
+
"x-muted": "900",
|
|
279
|
+
muted: "700",
|
|
280
|
+
bold: "300",
|
|
281
|
+
"x-bold": "100"
|
|
282
|
+
};
|
|
283
|
+
/** Resolve the functional token set for one role over its hue. */
|
|
284
|
+
function functionalRole(role, hueName, hue) {
|
|
285
|
+
const numeric = hue.numeric;
|
|
286
|
+
const v = (n) => `var(--color-${hueName}-${snap(numeric, n)})`;
|
|
287
|
+
const hex = (n) => numeric[snap(numeric, n)];
|
|
288
|
+
const naturalRaw = NUMERIC_STEPS.find((s) => numeric[s] === hex("500")) ?? "500";
|
|
289
|
+
const natural = Number(snap(numeric, naturalRaw));
|
|
290
|
+
const solidStep = snap(numeric, String(Math.min(Math.max(natural, 500), 700)));
|
|
291
|
+
const solidBold = snap(numeric, String(Math.min(Number(solidStep) + 100, 950)));
|
|
292
|
+
const onSolid = pickOn(numeric[solidStep]);
|
|
293
|
+
const isSurface = role === "surface";
|
|
294
|
+
const map = (table) => {
|
|
295
|
+
const out = {};
|
|
296
|
+
for (const [token, step] of Object.entries(table)) out[token] = v(step);
|
|
297
|
+
return out;
|
|
298
|
+
};
|
|
299
|
+
const emphasis = (table, prefix) => {
|
|
300
|
+
const out = {};
|
|
301
|
+
for (const [stop, step] of Object.entries(table)) out[`${prefix}-${stop}`] = v(step);
|
|
302
|
+
return out;
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
role,
|
|
306
|
+
hue: hueName,
|
|
307
|
+
light: {
|
|
308
|
+
...map(isSurface ? SURFACE_LIGHT : LIGHT),
|
|
309
|
+
solid: v(solidStep),
|
|
310
|
+
"solid-bold": v(solidBold),
|
|
311
|
+
"on-solid": onSolid,
|
|
312
|
+
...emphasis(EMPHASIS_LIGHT, "stop")
|
|
313
|
+
},
|
|
314
|
+
dark: {
|
|
315
|
+
...map(isSurface ? SURFACE_DARK : DARK),
|
|
316
|
+
solid: v(solidStep),
|
|
317
|
+
"solid-bold": v(solidBold),
|
|
318
|
+
"on-solid": onSolid,
|
|
319
|
+
...emphasis(EMPHASIS_DARK, "stop")
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
160
324
|
//#endregion
|
|
161
325
|
//#region src/docs.ts
|
|
162
326
|
/**
|
|
@@ -326,7 +490,8 @@ var Parser = class {
|
|
|
326
490
|
const first = this.parseValue();
|
|
327
491
|
if (this.peek()?.t === "arrow") {
|
|
328
492
|
this.next();
|
|
329
|
-
|
|
493
|
+
const val = this.parseValue();
|
|
494
|
+
map[typeof first === "object" && first !== null ? JSON.stringify(first) : String(first)] = val;
|
|
330
495
|
keyed = true;
|
|
331
496
|
} else list.push(first);
|
|
332
497
|
if (this.peek()?.t === "punc" && this.peek()?.v === ",") this.next();
|
|
@@ -467,9 +632,11 @@ function parseElement(src) {
|
|
|
467
632
|
keywords: extractKeywords(src)
|
|
468
633
|
};
|
|
469
634
|
}
|
|
635
|
+
/** Stringify a parsed-PHP Json value for prose (objects → JSON, not [object Object]). */
|
|
636
|
+
const asText = (v) => v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
470
637
|
function controlLine(c) {
|
|
471
638
|
const def = c.def;
|
|
472
|
-
if (def.type === "info") return `- _Note:_ ${def.content
|
|
639
|
+
if (def.type === "info") return `- _Note:_ ${asText(def.content)}`;
|
|
473
640
|
const label = def.label || c.key;
|
|
474
641
|
const options = def.options && typeof def.options === "object" && !Array.isArray(def.options) ? def.options : null;
|
|
475
642
|
const meta = [];
|
|
@@ -477,21 +644,21 @@ function controlLine(c) {
|
|
|
477
644
|
if (def.units === true) type += "+units";
|
|
478
645
|
meta.push(type);
|
|
479
646
|
if (def.default !== void 0) {
|
|
480
|
-
const raw =
|
|
647
|
+
const raw = asText(def.default);
|
|
481
648
|
const shown = options && options[raw] ? options[raw] : raw;
|
|
482
649
|
meta.push(`default \`${shown}\``);
|
|
483
650
|
}
|
|
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
|
|
651
|
+
if (def.placeholder !== void 0 && def.placeholder !== "") meta.push(`placeholder \`${asText(def.placeholder)}\``);
|
|
652
|
+
if (def.min !== void 0 || def.max !== void 0) meta.push(`range ${def.min != null ? asText(def.min) : "–"}–${def.max != null ? asText(def.max) : "∞"}`);
|
|
486
653
|
const tail = [];
|
|
487
|
-
if (def.description) tail.push(
|
|
654
|
+
if (def.description) tail.push(asText(def.description));
|
|
488
655
|
if (options) tail.push(`Options: ${Object.values(options).map((o) => `\`${o}\``).join(", ")}.`);
|
|
489
656
|
if (Array.isArray(def.css) && def.css.length) {
|
|
490
657
|
const first = def.css[0];
|
|
491
|
-
if (first && typeof first === "object" && !Array.isArray(first) && "property" in first) tail.push(`Bound to \`${first.property}\`.`);
|
|
658
|
+
if (first && typeof first === "object" && !Array.isArray(first) && "property" in first) tail.push(`Bound to \`${asText(first.property)}\`.`);
|
|
492
659
|
}
|
|
493
660
|
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);
|
|
661
|
+
const fields = Object.values(def.fields).map((f) => f && typeof f === "object" && !Array.isArray(f) ? asText(f.label) : null).filter(Boolean);
|
|
495
662
|
if (fields.length) tail.push(`Fields: ${fields.join(", ")}.`);
|
|
496
663
|
}
|
|
497
664
|
const head = `- **${label}** (${meta.join(", ")})`;
|
|
@@ -613,11 +780,9 @@ function renderElementsDoc(elementsDir) {
|
|
|
613
780
|
}
|
|
614
781
|
const code = (xs) => xs.map((x) => `\`${x}\``).join(", ");
|
|
615
782
|
function renderCssClasses(ds) {
|
|
616
|
-
const
|
|
617
|
-
const ramps = Object.keys(
|
|
618
|
-
const
|
|
619
|
-
const nonBase = (firstRamp ? Object.keys(palette[firstRamp] ?? {}) : []).filter((s) => s !== "base");
|
|
620
|
-
const roles = Object.keys(ds.colors.schemes.default?.semantic ?? {});
|
|
783
|
+
const expanded = expandPalette(ds.colors.palette);
|
|
784
|
+
const ramps = Object.keys(expanded);
|
|
785
|
+
const roles = Object.keys(ds.colors.roles ?? {});
|
|
621
786
|
const utils = ds.colors.utilities ?? [
|
|
622
787
|
"bg",
|
|
623
788
|
"text",
|
|
@@ -704,13 +869,32 @@ Families: ${code(families)} (\`--font-*\`).
|
|
|
704
869
|
|
|
705
870
|
## Colour
|
|
706
871
|
|
|
707
|
-
|
|
708
|
-
|
|
872
|
+
**Functional tokens are the primary vocabulary** — classes name the *job*, not the tone, and
|
|
873
|
+
every one remaps automatically under \`:root[data-brx-theme="dark"]\` (background/text ends
|
|
874
|
+
swap; \`solid\` fills stay mode-stable with a computed \`on-\` foreground). Prefer these over
|
|
875
|
+
raw steps. Rules (role ∈ ${code(roles)}):
|
|
876
|
+
|
|
877
|
+
- **Surfaces** — \`bg-<role>\` (the role's background wash; for \`surface\` this is the
|
|
878
|
+
card/panel plane), \`bg-<role>-muted\` (subtle / sunken), \`bg-surface-bold\` (raised plane).
|
|
879
|
+
- **Solid fills** — \`bg-<role>-solid\`, \`bg-<role>-solid-bold\` (hover / emphasis), paired
|
|
880
|
+
with \`text-on-<role>\` for guaranteed-contrast foreground.
|
|
881
|
+
- **Content** — \`text-<role>\` (primary, contrast-guaranteed in both appearances),
|
|
882
|
+
\`text-<role>-muted\` (secondary), \`text-<role>-x-muted\` (tertiary / disabled).
|
|
883
|
+
- **Borders** — \`border-<role>\`, \`border-<role>-bold\`.
|
|
884
|
+
- **Translucency** — \`glass\` (translucent surface + backdrop blur); \`--overlay\` scrim var.
|
|
885
|
+
- **Emphasis stops** (appearance-relative vars for power use):
|
|
886
|
+
\`--color-<role>-{x-muted,muted,bold,x-bold}\` — \`muted\` recedes toward the background
|
|
887
|
+
extreme, \`bold\` advances toward the foreground, in *either* appearance.
|
|
888
|
+
|
|
889
|
+
Everyday pairings: page \`bg-neutral\` + \`text-neutral\`; cards \`bg-surface\`; captions
|
|
890
|
+
\`text-neutral-muted\`; buttons \`bg-ui-primary-solid text-on-ui-primary\`; status text
|
|
891
|
+
\`text-danger\` / \`text-success\` / \`text-warning\` / \`text-info\`.
|
|
709
892
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
-
|
|
713
|
-
|
|
893
|
+
**Raw scale** (secondary / fine control) — rule \`<util>-<hue>-<step>\` with util ∈
|
|
894
|
+
${code(utils)}: every hue is an 11-step OKLCH scale generated from its seed (or fixed brand
|
|
895
|
+
tones), numeric steps \`50\` … \`950\` (tinted near-white → tinted near-black) — e.g.
|
|
896
|
+
\`bg-${ramps[0] ?? "brand"}-100\`, \`text-${ramps[0] ?? "brand"}-800\`. Hues: ${code(ramps)}.
|
|
897
|
+
The **bare** role name (\`bg-<role>\`, \`text-<role>\`) is always the functional token.
|
|
714
898
|
|
|
715
899
|
## Shadows
|
|
716
900
|
|
|
@@ -813,31 +997,6 @@ function generateDocs(ds, assetsDir) {
|
|
|
813
997
|
* copied from there — they are the same for every consumer.
|
|
814
998
|
*/
|
|
815
999
|
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
1000
|
const UTILITY_PROPS = {
|
|
842
1001
|
bg: "background-color",
|
|
843
1002
|
text: "color",
|
|
@@ -846,20 +1005,25 @@ const UTILITY_PROPS = {
|
|
|
846
1005
|
fill: "fill",
|
|
847
1006
|
stroke: "stroke"
|
|
848
1007
|
};
|
|
849
|
-
const
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1008
|
+
const fnVar = (role, token) => token.startsWith("stop-") ? `--color-${role}-${token.slice(5)}` : `--${role}-${token}`;
|
|
1009
|
+
const fnUtilities = (role, utilities, isSurface) => {
|
|
1010
|
+
const rows = [];
|
|
1011
|
+
if (utilities.includes("bg")) {
|
|
1012
|
+
rows.push(`.bg-${role} { background-color: var(--${role}-bg); }`);
|
|
1013
|
+
rows.push(`.bg-${role}-muted { background-color: var(--${role}-bg-muted); }`);
|
|
1014
|
+
rows.push(`.bg-${role}-solid { background-color: var(--${role}-solid); }`);
|
|
1015
|
+
rows.push(`.bg-${role}-solid-bold { background-color: var(--${role}-solid-bold); }`);
|
|
1016
|
+
if (isSurface) rows.push(`.bg-${role}-bold { background-color: var(--${role}-bg-bold); }`);
|
|
1017
|
+
}
|
|
1018
|
+
if (utilities.includes("text")) {
|
|
1019
|
+
rows.push(`.text-${role} { color: var(--${role}-text); }`);
|
|
1020
|
+
rows.push(`.text-${role}-muted { color: var(--${role}-text-muted); }`);
|
|
1021
|
+
rows.push(`.text-${role}-x-muted { color: var(--${role}-text-x-muted); }`);
|
|
1022
|
+
rows.push(`.text-on-${role} { color: var(--${role}-on-solid); }`);
|
|
1023
|
+
}
|
|
1024
|
+
if (utilities.includes("border")) rows.push(`.border-${role} { border-color: var(--${role}-border); }`, `.border-${role}-bold { border-color: var(--${role}-border-bold); }`);
|
|
1025
|
+
return rows.join("\n");
|
|
860
1026
|
};
|
|
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
1027
|
const decls = (obj, indent = " ") => Object.entries(obj).map(([k, v]) => `${indent}${k}: ${v};`).join("\n");
|
|
864
1028
|
const round = (n) => Number(n.toFixed(4)).toString();
|
|
865
1029
|
const rem = (v) => parseFloat(v);
|
|
@@ -940,13 +1104,15 @@ const BASE_HOOK = {
|
|
|
940
1104
|
const id = (key) => createHash("sha256").update(key).digest("hex").slice(0, 6);
|
|
941
1105
|
function build(ds, format, assetsDir) {
|
|
942
1106
|
const BRICKS = format === "bricks";
|
|
943
|
-
const
|
|
944
|
-
const
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1107
|
+
const expandedPalette = expandPalette(ds.colors.palette);
|
|
1108
|
+
const roleMap = ds.colors.roles;
|
|
1109
|
+
const scaleRoles = [];
|
|
1110
|
+
for (const [role, hueName] of Object.entries(roleMap)) {
|
|
1111
|
+
const hue = expandedPalette[hueName];
|
|
1112
|
+
if (hue == null) throw new Error(`role "${role}" references unknown palette hue "${hueName}"`);
|
|
1113
|
+
scaleRoles.push(functionalRole(role, hueName, hue));
|
|
1114
|
+
}
|
|
1115
|
+
const surfaceRole = scaleRoles.find((r) => r.role === "surface");
|
|
950
1116
|
const patterns = ds.patterns;
|
|
951
1117
|
const shadows = ds.shadows;
|
|
952
1118
|
const typography = ds.typography;
|
|
@@ -959,41 +1125,39 @@ function build(ds, format, assetsDir) {
|
|
|
959
1125
|
if (BRICKS) generated["color.css"] = "/* Colours provided by Bricks (palette import generates tokens + utilities). */\n";
|
|
960
1126
|
else {
|
|
961
1127
|
let css = `/* GENERATED from design-system.json — do not edit by hand. */\n:root {\n`;
|
|
962
|
-
css += ` color-scheme:
|
|
963
|
-
css += ` /*
|
|
964
|
-
for (const [name,
|
|
965
|
-
css += `\n /*
|
|
966
|
-
for (const [
|
|
967
|
-
|
|
968
|
-
|
|
1128
|
+
css += ` color-scheme: light;\n`;
|
|
1129
|
+
css += ` /* Hue scales (numeric steps, 50 = tinted near-white … 950 = tinted near-black) */\n`;
|
|
1130
|
+
for (const [name, hue] of Object.entries(expandedPalette)) for (const [n, hex] of Object.entries(hue.numeric)) css += ` --color-${name}-${n}: ${hex};\n`;
|
|
1131
|
+
css += `\n /* Functional role tokens (the public API) */\n`;
|
|
1132
|
+
for (const fr of scaleRoles) for (const [t, val] of Object.entries(fr.light)) css += ` ${fnVar(fr.role, t)}: ${val};\n`;
|
|
1133
|
+
if (surfaceRole) {
|
|
1134
|
+
css += `\n /* Translucent surface + scrim */\n`;
|
|
1135
|
+
css += ` --surface-glass: color-mix(in oklch, var(--surface-bg) 72%, transparent);\n`;
|
|
1136
|
+
css += ` --overlay: color-mix(in oklch, var(--color-${surfaceRole.hue}-950) 45%, transparent);\n`;
|
|
969
1137
|
}
|
|
970
1138
|
css += `}\n\n`;
|
|
971
|
-
|
|
972
|
-
let
|
|
973
|
-
for (const [
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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`;
|
|
1139
|
+
{
|
|
1140
|
+
let fb = "";
|
|
1141
|
+
for (const fr of scaleRoles) for (const [t, val] of Object.entries(fr.dark)) if (fr.light[t] !== val) fb += ` ${fnVar(fr.role, t)}: ${val};\n`;
|
|
1142
|
+
if (surfaceRole) fb += ` --overlay: color-mix(in oklch, var(--color-${surfaceRole.hue}-950) 60%, transparent);\n`;
|
|
1143
|
+
if (fb) {
|
|
1144
|
+
css += `/* Functional role tokens (dark) */\n`;
|
|
1145
|
+
css += `:root[data-brx-theme="dark"] {\n color-scheme: dark;\n${fb}}\n\n`;
|
|
987
1146
|
}
|
|
988
1147
|
}
|
|
989
1148
|
const allTokens = [];
|
|
990
|
-
for (const [name,
|
|
991
|
-
for (const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1149
|
+
for (const [name, hue] of Object.entries(expandedPalette)) for (const n of Object.keys(hue.numeric)) allTokens.push(`${name}-${n}`);
|
|
1150
|
+
for (const fr of scaleRoles) for (const stop of [
|
|
1151
|
+
"x-muted",
|
|
1152
|
+
"muted",
|
|
1153
|
+
"bold",
|
|
1154
|
+
"x-bold"
|
|
1155
|
+
]) allTokens.push(`${fr.role}-${stop}`);
|
|
995
1156
|
css += `/* Colour utilities — ${UTILITIES.join(", ")} */\n`;
|
|
996
1157
|
for (const token of allTokens) css += UTILITIES.map((cls) => `.${cls}-${token} { ${UTILITY_PROPS[cls]}: var(--color-${token}); }`).join("\n") + "\n";
|
|
1158
|
+
css += `\n/* Functional utilities (the public API) */\n`;
|
|
1159
|
+
for (const fr of scaleRoles) css += `${fnUtilities(fr.role, UTILITIES, fr.role === "surface")}\n`;
|
|
1160
|
+
if (surfaceRole) css += `.glass { background-color: var(--surface-glass); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }\n`;
|
|
997
1161
|
generated["color.css"] = css;
|
|
998
1162
|
}
|
|
999
1163
|
{
|
|
@@ -1067,16 +1231,18 @@ function build(ds, format, assetsDir) {
|
|
|
1067
1231
|
}
|
|
1068
1232
|
const stateRules = (sel, role, states, colorProp = "background-color") => {
|
|
1069
1233
|
let out = "";
|
|
1070
|
-
const roleVar = (step) => role ? `var(--color-${role}-${step})` : "";
|
|
1071
1234
|
for (const [state, spec] of Object.entries(states)) {
|
|
1072
1235
|
const body = [];
|
|
1073
|
-
if (typeof spec.step === "number" && role)
|
|
1236
|
+
if (typeof spec.step === "number" && role) {
|
|
1237
|
+
const stateVar = colorProp === "background-color" ? `var(--${role}-${spec.step >= 1 ? "solid-bold" : "solid"})` : `var(--color-${role}-${spec.step >= 1 ? "x-bold" : "bold"})`;
|
|
1238
|
+
body.push(`${colorProp}: ${stateVar};`);
|
|
1239
|
+
}
|
|
1074
1240
|
if (typeof spec.scale === "number") body.push(`scale: ${spec.scale};`);
|
|
1075
1241
|
if (typeof spec.lift === "string") body.push(`translate: 0 calc(-1 * ${spec.lift});`);
|
|
1076
1242
|
if (typeof spec.shadow === "string" && shadows?.[spec.shadow]) body.push(`filter: drop-shadow(var(--shadow-${spec.shadow}));`);
|
|
1077
1243
|
else if (spec.shadow === true) body.push(`box-shadow: var(--lift-shadow, 0 8px 20px -6px rgb(0 0 0 / 0.25));`);
|
|
1078
1244
|
if (spec.ring === true) {
|
|
1079
|
-
const ringColor = role ? `var(--color-${role}-
|
|
1245
|
+
const ringColor = role ? `var(--color-${role}-muted)` : `var(--color-ui-primary-muted)`;
|
|
1080
1246
|
body.push(`outline: none;`, `box-shadow: 0 0 0 3px ${ringColor};`);
|
|
1081
1247
|
}
|
|
1082
1248
|
if (spec.css && typeof spec.css === "object") for (const [k, v] of Object.entries(spec.css)) body.push(`${k}: ${v};`);
|
|
@@ -1104,7 +1270,7 @@ function build(ds, format, assetsDir) {
|
|
|
1104
1270
|
pat += ` transition-timing-function: var(--interact-easing, ease);\n`;
|
|
1105
1271
|
pat += ` translate: 0 0; scale: 1; rotate: 0deg;\n`;
|
|
1106
1272
|
}
|
|
1107
|
-
if (defaultRole && fills && base["background-color"] == null && base["background"] == null) base["background-color"] = `var(
|
|
1273
|
+
if (defaultRole && fills && base["background-color"] == null && base["background"] == null) base["background-color"] = `var(--${defaultRole}-solid)`;
|
|
1108
1274
|
const wrappedBase = {};
|
|
1109
1275
|
for (const [prop, val] of Object.entries(base)) {
|
|
1110
1276
|
const sfx = BASE_HOOK[prop];
|
|
@@ -1114,7 +1280,7 @@ function build(ds, format, assetsDir) {
|
|
|
1114
1280
|
pat += stateRules(defaultSel, defaultRole, states, colorProp);
|
|
1115
1281
|
for (const role of p.roles ?? []) {
|
|
1116
1282
|
const variantSel = isElement ? `${p.element}.${role}` : `.${p.class ?? pname}-${role}`;
|
|
1117
|
-
const variantColorDecl = fills ? `background-color: var(
|
|
1283
|
+
const variantColorDecl = fills ? `background-color: var(--${role}-solid); color: var(--${role}-on-solid)` : `color: var(--color-${role}-bold)`;
|
|
1118
1284
|
pat += `${variantSel} { ${variantColorDecl}; }\n`;
|
|
1119
1285
|
pat += stateRules(variantSel, role, states, colorProp);
|
|
1120
1286
|
}
|
|
@@ -1166,10 +1332,8 @@ function build(ds, format, assetsDir) {
|
|
|
1166
1332
|
generated["animation-effects.css"] = out;
|
|
1167
1333
|
}
|
|
1168
1334
|
const namedColors = [];
|
|
1169
|
-
for (const [name,
|
|
1170
|
-
const
|
|
1171
|
-
if (hex == null) continue;
|
|
1172
|
-
const token = `${name}${suffix(step)}`;
|
|
1335
|
+
for (const [name, hue] of Object.entries(expandedPalette)) for (const [n, hex] of Object.entries(hue.numeric)) {
|
|
1336
|
+
const token = `${name}-${n}`;
|
|
1173
1337
|
namedColors.push({
|
|
1174
1338
|
raw: `var(--color-${token})`,
|
|
1175
1339
|
light: hex,
|
|
@@ -1178,98 +1342,99 @@ function build(ds, format, assetsDir) {
|
|
|
1178
1342
|
});
|
|
1179
1343
|
}
|
|
1180
1344
|
const semanticColors = [];
|
|
1181
|
-
for (const [
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
light: srcVar(lightSrc),
|
|
1192
|
-
darkModeEnabled: hasDark,
|
|
1193
|
-
dark: srcVar(darkSrc),
|
|
1194
|
-
id: id(`semantic:${role}${suffix(step)}`),
|
|
1195
|
-
utilityClasses: UTILITIES
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1345
|
+
for (const fr of scaleRoles) for (const [t, light] of Object.entries(fr.light)) {
|
|
1346
|
+
const dark = fr.dark[t];
|
|
1347
|
+
semanticColors.push({
|
|
1348
|
+
raw: `var(${fnVar(fr.role, t)})`,
|
|
1349
|
+
light,
|
|
1350
|
+
darkModeEnabled: light !== dark,
|
|
1351
|
+
dark,
|
|
1352
|
+
id: id(`semantic:${fr.role}-${t}`),
|
|
1353
|
+
utilityClasses: UTILITIES
|
|
1354
|
+
});
|
|
1198
1355
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
-
});
|
|
1356
|
+
const bricksColorsNamed = JSON.stringify({
|
|
1357
|
+
id: id("palette:named"),
|
|
1358
|
+
name: "Named",
|
|
1359
|
+
colors: namedColors
|
|
1360
|
+
}, null, 2);
|
|
1361
|
+
const bricksColorsSemantic = JSON.stringify({
|
|
1362
|
+
id: id("palette:semantic"),
|
|
1363
|
+
name: "Semantic",
|
|
1364
|
+
colors: semanticColors
|
|
1365
|
+
}, null, 2);
|
|
1366
|
+
const bricksVariables = (() => {
|
|
1367
|
+
const scaleMeta = (scope, prefix, s, names) => {
|
|
1368
|
+
const px = Math.round(rem(s.base) * 16);
|
|
1369
|
+
const lo = s.fluid?.minRatio ?? s.ratio;
|
|
1370
|
+
return {
|
|
1371
|
+
scaleScope: scope,
|
|
1372
|
+
scaleType: "tshirt",
|
|
1373
|
+
scaleNames: names,
|
|
1374
|
+
prefix,
|
|
1375
|
+
minFontSize: px,
|
|
1376
|
+
minScaleRatio: lo,
|
|
1377
|
+
minScaleRatioSelect: lo,
|
|
1378
|
+
maxFontSize: px,
|
|
1379
|
+
maxScaleRatio: s.ratio,
|
|
1380
|
+
maxScaleRatioSelect: s.ratio,
|
|
1381
|
+
baseline: s.baseline ?? names[Math.floor((names.length - 1) / 2)]
|
|
1251
1382
|
};
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1383
|
+
};
|
|
1384
|
+
const categories = [];
|
|
1385
|
+
const variables = [];
|
|
1386
|
+
const addScale = (label, scope, prefix, cfg, steps) => {
|
|
1387
|
+
if (!cfg || !steps.length) return;
|
|
1388
|
+
const names = steps.map((s) => s.name);
|
|
1389
|
+
const catId = id(`varcat:${scope}`);
|
|
1390
|
+
categories.push({
|
|
1391
|
+
id: catId,
|
|
1392
|
+
name: label,
|
|
1393
|
+
scale: scaleMeta(scope, prefix, cfg, names),
|
|
1394
|
+
utilityClasses: []
|
|
1258
1395
|
});
|
|
1259
|
-
for (const
|
|
1260
|
-
id: id(`var
|
|
1261
|
-
name:
|
|
1262
|
-
value:
|
|
1396
|
+
for (const st of steps) variables.push({
|
|
1397
|
+
id: id(`var:${prefix}${st.name}`),
|
|
1398
|
+
name: `${prefix}${st.name}`,
|
|
1399
|
+
value: st.value,
|
|
1400
|
+
category: catId,
|
|
1401
|
+
scale: {
|
|
1402
|
+
scale: st.offset,
|
|
1403
|
+
scaleName: st.name
|
|
1404
|
+
}
|
|
1263
1405
|
});
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1406
|
+
};
|
|
1407
|
+
addScale("Typography", "typography", "text-", ds.typeScale, typeSteps);
|
|
1408
|
+
addScale("Spacing", "spacing", "space-", ds.spaceScale, spaceSteps);
|
|
1409
|
+
for (const [name, stack] of Object.entries(ds.fonts ?? {})) variables.push({
|
|
1410
|
+
id: id(`var:font-${name}`),
|
|
1411
|
+
name: `font-${name}`,
|
|
1412
|
+
value: stack
|
|
1413
|
+
});
|
|
1414
|
+
for (const [name, val] of Object.entries(ds.patterns?.radii ?? {})) variables.push({
|
|
1415
|
+
id: id(`var:br-${name}`),
|
|
1416
|
+
name: `br-${name}`,
|
|
1417
|
+
value: val
|
|
1418
|
+
});
|
|
1419
|
+
return JSON.stringify({
|
|
1420
|
+
variables,
|
|
1421
|
+
categories
|
|
1422
|
+
}, null, 2);
|
|
1423
|
+
})();
|
|
1424
|
+
const numericPalette = Object.fromEntries(Object.entries(expandedPalette).map(([k, v]) => [k, v.numeric]));
|
|
1425
|
+
return {
|
|
1426
|
+
generated,
|
|
1427
|
+
bricksColorsNamed,
|
|
1428
|
+
bricksColorsSemantic,
|
|
1429
|
+
bricksVariables,
|
|
1269
1430
|
tokensJson: JSON.stringify({
|
|
1270
1431
|
colors: {
|
|
1271
|
-
palette,
|
|
1272
|
-
|
|
1432
|
+
palette: numericPalette,
|
|
1433
|
+
roles: roleMap,
|
|
1434
|
+
functional: Object.fromEntries(scaleRoles.map((fr) => [fr.role, {
|
|
1435
|
+
light: fr.light,
|
|
1436
|
+
dark: fr.dark
|
|
1437
|
+
}]))
|
|
1273
1438
|
},
|
|
1274
1439
|
fonts: ds.fonts ?? {},
|
|
1275
1440
|
text: Object.fromEntries(typeSteps.map((s) => [s.name, s.value])),
|
|
@@ -1279,10 +1444,10 @@ function build(ds, format, assetsDir) {
|
|
|
1279
1444
|
}, null, 2),
|
|
1280
1445
|
designManifest: (() => {
|
|
1281
1446
|
const reverseIndex = {};
|
|
1282
|
-
for (const [name,
|
|
1283
|
-
const colorRoles = Object.entries(
|
|
1447
|
+
for (const [name, hue] of Object.entries(expandedPalette)) for (const n of Object.keys(hue.numeric)) reverseIndex[`--color-${name}-${n}`] = `colors.palette.${name}.seed`;
|
|
1448
|
+
const colorRoles = Object.entries(roleMap).map(([name, ramp]) => ({
|
|
1284
1449
|
name,
|
|
1285
|
-
ramp
|
|
1450
|
+
ramp
|
|
1286
1451
|
}));
|
|
1287
1452
|
const typoRoles = typography?.roles ?? {};
|
|
1288
1453
|
const hooks = {};
|
|
@@ -1315,9 +1480,9 @@ function build(ds, format, assetsDir) {
|
|
|
1315
1480
|
});
|
|
1316
1481
|
return JSON.stringify({
|
|
1317
1482
|
colors: {
|
|
1318
|
-
ramps: Object.keys(
|
|
1319
|
-
steps:
|
|
1320
|
-
palette,
|
|
1483
|
+
ramps: Object.keys(expandedPalette),
|
|
1484
|
+
steps: NUMERIC_STEPS,
|
|
1485
|
+
palette: numericPalette,
|
|
1321
1486
|
roles: colorRoles
|
|
1322
1487
|
},
|
|
1323
1488
|
typography: {
|
|
@@ -1355,9 +1520,8 @@ function build(ds, format, assetsDir) {
|
|
|
1355
1520
|
})(),
|
|
1356
1521
|
tailwind: emitTailwind({
|
|
1357
1522
|
ds,
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
darkScheme,
|
|
1523
|
+
expandedPalette,
|
|
1524
|
+
scaleRoles,
|
|
1361
1525
|
shadows,
|
|
1362
1526
|
typeSteps,
|
|
1363
1527
|
spaceSteps,
|
|
@@ -1374,25 +1538,18 @@ function build(ds, format, assetsDir) {
|
|
|
1374
1538
|
};
|
|
1375
1539
|
}
|
|
1376
1540
|
function emitTailwind(ctx) {
|
|
1377
|
-
const { ds,
|
|
1541
|
+
const { ds, expandedPalette, scaleRoles, shadows, typeSteps, spaceSteps, typography, roleDecls, twTypeTokensCss, twPatternTokensCss, patternsCss, assetsDir } = ctx;
|
|
1378
1542
|
const cssDir = join(assetsDir, "css");
|
|
1379
1543
|
const nl = (arr) => arr.filter(Boolean).join("\n");
|
|
1380
1544
|
const parts = [`/* GENERATED for Tailwind v4 (Astro) — do not edit by hand. */`];
|
|
1381
1545
|
parts.push(`@import "tailwindcss";\n`);
|
|
1382
1546
|
const theme = [];
|
|
1383
|
-
theme.push(` /*
|
|
1384
|
-
for (const [name,
|
|
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
|
-
}
|
|
1547
|
+
theme.push(` /* Hue scales → bg-<hue>-<step> text-* border-* (numeric, 50…950) */`);
|
|
1548
|
+
for (const [name, hue] of Object.entries(expandedPalette)) for (const [n, hex] of Object.entries(hue.numeric)) theme.push(` --color-${name}-${n}: ${hex};`);
|
|
1390
1549
|
theme.push(`\n /* Fonts → font-* */`);
|
|
1391
1550
|
for (const [name, stack] of Object.entries(ds.fonts ?? {})) theme.push(` --font-${name}: ${stack};`);
|
|
1392
1551
|
theme.push(`\n /* Fluid type scale → text-* */`);
|
|
1393
1552
|
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
1553
|
theme.push(`\n /* Drop-shadows → shadow-* */`);
|
|
1397
1554
|
for (const [name, value] of Object.entries(shadows ?? {})) theme.push(` --shadow-${name}: ${value};`);
|
|
1398
1555
|
theme.push(`\n /* Container breakpoints → @sm:/@md:/@lg:/@xl: */`);
|
|
@@ -1403,22 +1560,39 @@ function emitTailwind(ctx) {
|
|
|
1403
1560
|
["xl", "80rem"]
|
|
1404
1561
|
]) theme.push(` --container-${name}: ${val};`);
|
|
1405
1562
|
parts.push(`@theme {\n${theme.join("\n")}\n}\n`);
|
|
1563
|
+
if (spaceSteps.length) parts.push(`/* Fluid space scale — var(--space-<name>) or p-(--space-<name>) */\n:root {\n` + spaceSteps.map((s) => ` --space-${s.name}: ${s.value};`).join("\n") + `\n}\n`);
|
|
1564
|
+
if (scaleRoles.length) {
|
|
1565
|
+
let fn = "";
|
|
1566
|
+
for (const fr of scaleRoles) for (const [t, val] of Object.entries(fr.light)) fn += ` ${fnVar(fr.role, t)}: ${val};\n`;
|
|
1567
|
+
const surface = scaleRoles.find((r) => r.role === "surface");
|
|
1568
|
+
if (surface) {
|
|
1569
|
+
fn += ` --surface-glass: color-mix(in oklch, var(--surface-bg) 72%, transparent);\n`;
|
|
1570
|
+
fn += ` --overlay: color-mix(in oklch, var(--color-${surface.hue}-950) 45%, transparent);\n`;
|
|
1571
|
+
}
|
|
1572
|
+
parts.push(`/* Functional role tokens (the public API) */\n:root {\n${fn}}\n`);
|
|
1573
|
+
}
|
|
1406
1574
|
parts.push(`/* ── Framework tokens (consumed by the inlined components) ── */\n${twTypeTokensCss}\n${twPatternTokensCss}`);
|
|
1407
1575
|
let darkBlock = "";
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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
|
-
}
|
|
1576
|
+
for (const fr of scaleRoles) for (const [t, val] of Object.entries(fr.dark)) if (fr.light[t] !== val) darkBlock += ` ${fnVar(fr.role, t)}: ${val};\n`;
|
|
1577
|
+
{
|
|
1578
|
+
const surface = scaleRoles.find((r) => r.role === "surface");
|
|
1579
|
+
if (surface) darkBlock += ` --overlay: color-mix(in oklch, var(--color-${surface.hue}-950) 60%, transparent);\n`;
|
|
1418
1580
|
}
|
|
1419
|
-
if (darkBlock
|
|
1581
|
+
if (darkBlock) parts.push(`/* Functional role tokens (dark) */\n:root[data-brx-theme="dark"] {\n color-scheme: dark;\n${darkBlock}}\n`);
|
|
1420
1582
|
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
1583
|
const util = [];
|
|
1584
|
+
if (scaleRoles.length) {
|
|
1585
|
+
util.push(`/* Functional colour utilities (the public API) */`);
|
|
1586
|
+
for (const fr of scaleRoles) for (const row of fnUtilities(fr.role, [
|
|
1587
|
+
"bg",
|
|
1588
|
+
"text",
|
|
1589
|
+
"border"
|
|
1590
|
+
], fr.role === "surface").split("\n")) {
|
|
1591
|
+
const m = /^\.([^ ]+) \{ (.*) \}$/.exec(row);
|
|
1592
|
+
if (m) util.push(`@utility ${m[1]} {\n ${m[2]}\n}`);
|
|
1593
|
+
}
|
|
1594
|
+
if (scaleRoles.some((r) => r.role === "surface")) util.push(`@utility glass {\n background-color: var(--surface-glass);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n}`);
|
|
1595
|
+
}
|
|
1422
1596
|
util.push(`/* Typography roles */`);
|
|
1423
1597
|
for (const [role, spec] of Object.entries(typography?.roles ?? {})) util.push(`@utility font-${role} {\n${decls(roleDecls(role, spec))}\n}`);
|
|
1424
1598
|
util.push(`\n/* Animation effects (pair with .transition or a driver) */`);
|
|
@@ -1540,7 +1714,7 @@ body {
|
|
|
1540
1714
|
`);
|
|
1541
1715
|
const animEngine = readFileSync(join(cssDir, "animation.css"), "utf8");
|
|
1542
1716
|
parts.push(`/* ── Animation engine (animation.css) ── */\n${animEngine}`);
|
|
1543
|
-
parts.push(`/* ── Component patterns ── */\n${patternsCss}`);
|
|
1717
|
+
parts.push(`/* ── Component patterns ── */\n@layer components {\n${patternsCss}\n}`);
|
|
1544
1718
|
const TW_CLASH = new Set([
|
|
1545
1719
|
"block",
|
|
1546
1720
|
"inline",
|
|
@@ -1639,7 +1813,7 @@ body {
|
|
|
1639
1813
|
const componentCss = componentPartialOrder(cssDir).map((rel) => {
|
|
1640
1814
|
return `/* ── ${rel} ── */\n${stripForTailwind(readFileSync(join(cssDir, rel), "utf8"))}`;
|
|
1641
1815
|
}).join("\n\n");
|
|
1642
|
-
parts.push(`/* ── Component + structural partials (Tailwind-clashing utilities dropped) ── */\n${componentCss}`);
|
|
1816
|
+
parts.push(`/* ── Component + structural partials (Tailwind-clashing utilities dropped) ── */\n@layer components {\n${componentCss}\n}`);
|
|
1643
1817
|
return nl(parts) + "\n";
|
|
1644
1818
|
}
|
|
1645
1819
|
/**
|
|
@@ -2210,7 +2384,13 @@ const SiteConfigSchema = z.object({
|
|
|
2210
2384
|
})),
|
|
2211
2385
|
favicon: z.optional(z.object({
|
|
2212
2386
|
source: z.string(),
|
|
2213
|
-
lowResSource: z.optional(z.string())
|
|
2387
|
+
lowResSource: z.optional(z.string()),
|
|
2388
|
+
/** App name for the generated web manifest / PWA. */
|
|
2389
|
+
name: z.optional(z.string()),
|
|
2390
|
+
/** PWA theme color (also `<meta name="theme-color">`). */
|
|
2391
|
+
themeColor: z.optional(z.string()),
|
|
2392
|
+
/** PWA background color. */
|
|
2393
|
+
backgroundColor: z.optional(z.string())
|
|
2214
2394
|
})),
|
|
2215
2395
|
deployment: z.optional(z.object({
|
|
2216
2396
|
platform: z.optional(z.string()),
|
|
@@ -2382,47 +2562,27 @@ function defaultConfig() {
|
|
|
2382
2562
|
$schema: SCHEMA_URL,
|
|
2383
2563
|
colors: {
|
|
2384
2564
|
palette: {
|
|
2385
|
-
brand: {
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
xl: "#a3abb8",
|
|
2401
|
-
xxl: "#d7dbe2"
|
|
2402
|
-
}
|
|
2565
|
+
brand: { seed: "#2e9b73" },
|
|
2566
|
+
ink: { seed: "#3f4a5c" },
|
|
2567
|
+
sky: { seed: "#2f7bc0" },
|
|
2568
|
+
sun: { seed: "#d08a1f" },
|
|
2569
|
+
rose: { seed: "#c74a42" }
|
|
2570
|
+
},
|
|
2571
|
+
roles: {
|
|
2572
|
+
neutral: "ink",
|
|
2573
|
+
surface: "ink",
|
|
2574
|
+
"ui-primary": "brand",
|
|
2575
|
+
"brand-primary": "brand",
|
|
2576
|
+
info: "sky",
|
|
2577
|
+
success: "brand",
|
|
2578
|
+
warning: "sun",
|
|
2579
|
+
danger: "rose"
|
|
2403
2580
|
},
|
|
2404
2581
|
utilities: [
|
|
2405
2582
|
"bg",
|
|
2406
2583
|
"text",
|
|
2407
2584
|
"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
|
-
}
|
|
2585
|
+
]
|
|
2426
2586
|
},
|
|
2427
2587
|
shadows: {
|
|
2428
2588
|
sm: "0 1px 2px rgb(0 0 0 / 0.08)",
|