@menapo/vicss 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.js ADDED
@@ -0,0 +1,2690 @@
1
+ //#region src/core/css-value.ts
2
+ /**
3
+ * Well-formedness of the text vicss prints into the one global sheet. A value
4
+ * that leaves a block, a string or a comment open swallows every rule after it,
5
+ * in every module's CSS; one that ends the declaration (`;`, `}`) or carries
6
+ * `!important` escapes the layer order. Nothing here judges whether the
7
+ * browser accepts the value, only that it cannot affect the text around it.
8
+ */
9
+ const OPENERS = {
10
+ "(": ")",
11
+ "[": "]"
12
+ };
13
+ const NEWLINE = /[\n\r\f]/;
14
+ const SPACE = /[ \t\n\r\f]/;
15
+ /**
16
+ * Why `text` is not a self-contained value, selector or at-rule prelude, or
17
+ * `undefined` when it is. None of them holds a `{…}` block outside a string.
18
+ */
19
+ function malformed(text) {
20
+ if (/<\/style/i.test(text)) return "it contains `</style`";
21
+ const stack = [];
22
+ for (let i = 0; i < text.length; i++) {
23
+ const ch = text[i];
24
+ if (ch === "\\") {
25
+ if (i + 1 >= text.length) return "it ends with a backslash";
26
+ if (NEWLINE.test(text[i + 1])) return "a backslash escapes a line break";
27
+ i++;
28
+ continue;
29
+ }
30
+ if (ch === "\"" || ch === "'") {
31
+ const end = closeString(text, i);
32
+ if (typeof end === "string") return end;
33
+ i = end;
34
+ continue;
35
+ }
36
+ if (ch === "/" && text[i + 1] === "*") return "it contains a comment (`/*`)";
37
+ if (ch === "u" || ch === "U") {
38
+ const end = unquotedUrl(text, i);
39
+ if (typeof end === "string") return end;
40
+ if (end !== void 0) {
41
+ i = end;
42
+ continue;
43
+ }
44
+ }
45
+ if (ch === ";") return "it contains `;`, which would end the declaration";
46
+ if (ch === "!") return "it contains `!`, which would make it `!important` or end it";
47
+ if (ch === "{") return "it contains `{`";
48
+ const close = OPENERS[ch];
49
+ if (close !== void 0) stack.push(close);
50
+ else if (ch === ")" || ch === "]" || ch === "}") {
51
+ if (stack.pop() !== ch) return `\`${ch}\` closes nothing`;
52
+ }
53
+ }
54
+ const open = stack.at(-1);
55
+ return open === void 0 ? void 0 : `\`${open}\` is missing`;
56
+ }
57
+ /** Index of the closing quote, or why the string never closes. */
58
+ function closeString(text, start) {
59
+ const quote = text[start];
60
+ for (let i = start + 1; i < text.length; i++) {
61
+ const ch = text[i];
62
+ if (ch === quote) return i;
63
+ if (ch === "\\") {
64
+ if (i + 1 >= text.length) return "a string ends with a backslash";
65
+ i++;
66
+ } else if (NEWLINE.test(ch)) return "a string contains a line break";
67
+ }
68
+ return `a string is missing its closing ${quote}`;
69
+ }
70
+ /**
71
+ * An unquoted `url(…)` is one token up to `)`: `;`, `{` and `}` inside it
72
+ * belong to the URL (`url(data:image/png;base64,…)`). Returns the index of
73
+ * the closing `)`, `undefined` when `start` does not begin one, or why it is
74
+ * malformed.
75
+ */
76
+ function unquotedUrl(text, start) {
77
+ if (!/^url\(/i.test(text.slice(start, start + 4))) return void 0;
78
+ if (start > 0 && /[\w-]/.test(text[start - 1])) return void 0;
79
+ let i = start + 4;
80
+ while (i < text.length && SPACE.test(text[i])) i++;
81
+ if (text[i] === "\"" || text[i] === "'") return void 0;
82
+ for (; i < text.length; i++) {
83
+ const ch = text[i];
84
+ if (ch === ")") return i;
85
+ if (ch === "\\") {
86
+ if (i + 1 >= text.length || NEWLINE.test(text[i + 1])) return "a url() has a bad escape";
87
+ i++;
88
+ } else if (ch === "\"" || ch === "'" || ch === "(" || ch.charCodeAt(0) < 32) return "a url() holds a quote, a parenthesis or a control character";
89
+ else if (SPACE.test(ch)) {
90
+ while (i + 1 < text.length && SPACE.test(text[i + 1])) i++;
91
+ if (text[i + 1] !== ")") return "a url() holds whitespace";
92
+ }
93
+ }
94
+ return "a url() is missing its `)`";
95
+ }
96
+ //#endregion
97
+ //#region src/core/types.ts
98
+ /**
99
+ * What marks a VicssError, shared by every copy of this module in a process:
100
+ * the config entry bundles its own copy, and Vite's `runnerImport` gives a
101
+ * config another instance even of the workspace's source, so an error a
102
+ * preset's utility or `dtcg()` throws is of another class than the one the
103
+ * compiler catches.
104
+ */
105
+ const BRAND = Symbol.for("@menapo/vicss/error");
106
+ /** Thrown for anything vicss refuses; carries enough to print a code frame. */
107
+ var VicssError = class VicssError extends Error {
108
+ code;
109
+ loc;
110
+ hint;
111
+ /**
112
+ * `instanceof VicssError` holds for a VicssError of any copy of vicss.
113
+ * Subclasses (`ScanConflict`) keep the ordinary prototype check.
114
+ */
115
+ static [Symbol.hasInstance](value) {
116
+ if (this !== VicssError) return Function.prototype[Symbol.hasInstance].call(this, value);
117
+ return typeof value === "object" && value !== null && BRAND in value;
118
+ }
119
+ constructor(code, message, options = {}) {
120
+ const limit = Error.stackTraceLimit;
121
+ Error.stackTraceLimit = 0;
122
+ super(message);
123
+ Error.stackTraceLimit = limit;
124
+ this.name = "VicssError";
125
+ this.code = code;
126
+ this.loc = options.loc;
127
+ this.hint = options.hint;
128
+ Object.defineProperty(this, BRAND, { value: true });
129
+ }
130
+ };
131
+ /** `{ base, _dark }` is a value per mode; `{ base: "…" }` alone is a single value. */
132
+ function isModeMap(node) {
133
+ const keys = Object.keys(node);
134
+ return keys.some((k) => k.startsWith("_")) || keys.length === 1 && keys[0] === "base" && !isPlainObject$1(node.base);
135
+ }
136
+ function isPlainObject$1(value) {
137
+ if (typeof value !== "object" || value === null) return false;
138
+ const proto = Object.getPrototypeOf(value);
139
+ return proto === Object.prototype || proto === null;
140
+ }
141
+ function configError$1(at, message, options = {}) {
142
+ return new VicssError("CONFIG", `${at}: ${message}`, options);
143
+ }
144
+ //#endregion
145
+ //#region src/config/refs.ts
146
+ /**
147
+ * Typed token references for the config (PLAN §5.3, D32):
148
+ * `defineConfig(config, ({ colors }) => ({ … colors.gray[900] … }))`. The
149
+ * callback gets a tree of every token the config and its presets define,
150
+ * and each reference is the brace string the config already took
151
+ * (`"{colors.gray.900}"`), so resolution, DTCG export, modes, palettes and
152
+ * the lint snapshot see the config they always did. The tree is built when
153
+ * `defineConfig` runs and the references are strings in the config it
154
+ * returns: a config crosses to worker threads and the CLI as plain data.
155
+ */
156
+ /**
157
+ * The proxies the callback got, each to its brace string: what `settle`
158
+ * turns them back into, naming the key path when a group cannot be one.
159
+ */
160
+ const TEXTS = /* @__PURE__ */ new WeakMap();
161
+ /** The keys `defineConfig`'s callback may not return. */
162
+ const BASE_ONLY = ["tokens", "presets"];
163
+ /** The reference tree of `config` and its presets (the shape `TokenRefs` types). */
164
+ function tokenRefs(config) {
165
+ const nodes = /* @__PURE__ */ new Map();
166
+ collect(config, nodes, /* @__PURE__ */ new Set());
167
+ const cache = /* @__PURE__ */ new Map();
168
+ const root = {};
169
+ for (const [path, node] of nodes) if (!path.includes(".")) root[path] = refAt(path, node, nodes, cache);
170
+ return Object.freeze(root);
171
+ }
172
+ function collect(layer, nodes, seen) {
173
+ if (!isPlainObject$1(layer) || seen.has(layer)) return;
174
+ seen.add(layer);
175
+ if (Array.isArray(layer.presets)) for (const preset of layer.presets) collect(preset, nodes, seen);
176
+ for (const [field, semantic] of [["tokens", false], ["semanticTokens", true]]) {
177
+ const scales = layer[field];
178
+ if (!isPlainObject$1(scales)) continue;
179
+ for (const [scale, tree] of Object.entries(scales)) if (isPlainObject$1(tree)) walk(scale, tree, semantic, nodes);
180
+ }
181
+ }
182
+ function nodeAt(path, nodes) {
183
+ let node = nodes.get(path);
184
+ if (!node) nodes.set(path, node = {
185
+ token: false,
186
+ children: /* @__PURE__ */ new Set()
187
+ });
188
+ return node;
189
+ }
190
+ function walk(path, tree, semantic, nodes) {
191
+ const group = nodeAt(path, nodes);
192
+ for (const [name, value] of Object.entries(tree)) {
193
+ if (value === void 0) continue;
194
+ if (name === "DEFAULT") {
195
+ group.token = true;
196
+ continue;
197
+ }
198
+ const child = `${path}.${name}`;
199
+ group.children.add(name);
200
+ if (typeof value === "string" || typeof value === "number") nodeAt(child, nodes).token = true;
201
+ else if (isPlainObject$1(value)) {
202
+ if (semantic && isModeMap(value)) nodeAt(child, nodes).token = true;
203
+ else walk(child, value, semantic, nodes);
204
+ }
205
+ }
206
+ }
207
+ /**
208
+ * A token with nothing under it is its brace string; anything else is a
209
+ * proxy that stringifies to one and indexes further. A path no layer
210
+ * defines still makes a reference: resolution names the missing token with
211
+ * the config key that holds it, and a preset package's `dtcg()` result that
212
+ * the loader reads again may define it by then (config/files.ts).
213
+ */
214
+ function refAt(path, node, nodes, cache) {
215
+ if (node?.token && node.children.size === 0) return `{${path}}`;
216
+ const cached = cache.get(path);
217
+ if (cached !== void 0) return cached;
218
+ const text = (at) => {
219
+ if (node && !node.token) throw groupError(path, node, at);
220
+ return `{${path}}`;
221
+ };
222
+ const convert = () => text();
223
+ const child = (key) => refAt(`${path}.${key}`, nodes.get(`${path}.${key}`), nodes, cache);
224
+ const proxy = new Proxy(Object.create(null), {
225
+ get(_, key) {
226
+ if (typeof key === "string" && node?.children.has(key)) return child(key);
227
+ if (key === Symbol.toPrimitive || key === "toString" || key === "valueOf" || key === "toJSON") return convert;
228
+ if (typeof key === "symbol" || key === "then") return void 0;
229
+ return child(key);
230
+ },
231
+ has: (_, key) => typeof key === "string" && node?.children.has(key) === true,
232
+ ownKeys: () => [...node?.children ?? []],
233
+ getOwnPropertyDescriptor: (_, key) => typeof key === "string" && node?.children.has(key) ? {
234
+ value: child(key),
235
+ enumerable: true,
236
+ configurable: true,
237
+ writable: false
238
+ } : void 0,
239
+ set: () => false,
240
+ defineProperty: () => false,
241
+ deleteProperty: () => false
242
+ });
243
+ TEXTS.set(proxy, text);
244
+ cache.set(path, proxy);
245
+ return proxy;
246
+ }
247
+ function groupError(path, node, at) {
248
+ const message = `\`${path}\` is a group of tokens, not a token`;
249
+ const hint = `Name one of its tokens: ${[...node.children].slice(0, 3).map((name) => `\`${path}.${name}\``).join(", ")}${node.children.size > 3 ? ", …" : ""}.`;
250
+ return at ? configError$1(at, message, { hint }) : new VicssError("CONFIG", message, { hint });
251
+ }
252
+ /**
253
+ * `value` with every reference replaced by its brace string, copied, never
254
+ * mutated. Functions (a utility's `transform`) return settled values too,
255
+ * and keep their source text, which the system's hash reads.
256
+ */
257
+ function settle(value, at) {
258
+ const text = typeof value === "object" && value !== null ? TEXTS.get(value) : void 0;
259
+ if (text) return text(at);
260
+ if (typeof value === "function") {
261
+ const fn = value;
262
+ const settled = function(...args) {
263
+ return settle(fn.apply(this, args), `${at}()`);
264
+ };
265
+ Object.defineProperty(settled, "toString", { value: () => fn.toString() });
266
+ return settled;
267
+ }
268
+ if (Array.isArray(value)) return value.map((v, i) => settle(v, `${at}[${i}]`));
269
+ if (isPlainObject$1(value)) {
270
+ const out = {};
271
+ for (const [key, v] of Object.entries(value)) out[key] = settle(v, `${at}.${key}`);
272
+ return out;
273
+ }
274
+ return value;
275
+ }
276
+ //#endregion
277
+ //#region src/config/presets/reset.ts
278
+ /**
279
+ * The `reset` preset: `presets: [reset]` is `reset: true`, a modern reset in
280
+ * the `vicss.reset` layer (PLAN §4.9, D15; the rules are in
281
+ * `src/core/reset.ts`). It is opt-in because a reset is the app's choice: a
282
+ * component library's preset must never carry it.
283
+ */
284
+ const reset = { reset: true };
285
+ //#endregion
286
+ //#region src/config/presets/shorthands.ts
287
+ /** A utility that sets `properties` to its value, typed and checked by `scale`. */
288
+ function alias(name, scale, ...properties) {
289
+ return {
290
+ scale,
291
+ transform: (value) => {
292
+ if (typeof value === "boolean") throw new VicssError("INVALID_VALUE", `\`${name}\` takes a value of ${properties.join(" and ")}, not \`${value}\``);
293
+ const style = {};
294
+ for (const property of properties) style[property] = value;
295
+ return style;
296
+ }
297
+ };
298
+ }
299
+ /** `p`, `px` … `pe` and `m` … `me`: Tailwind v4 spells the axes with logical properties. */
300
+ function box(prefix, property) {
301
+ return Object.fromEntries([
302
+ ["", ""],
303
+ ["x", "Inline"],
304
+ ["y", "Block"],
305
+ ["t", "Top"],
306
+ ["r", "Right"],
307
+ ["b", "Bottom"],
308
+ ["l", "Left"],
309
+ ["s", "InlineStart"],
310
+ ["e", "InlineEnd"]
311
+ ].map(([suffix, side]) => [`${prefix}${suffix}`, alias(`${prefix}${suffix}`, "spacing", `${property}${side}`)]));
312
+ }
313
+ const radius = (name, ...corners) => alias(name, "radii", ...corners.map((corner) => `border${corner}Radius`));
314
+ const shorthands = { utilities: {
315
+ ...box("p", "padding"),
316
+ ...box("m", "margin"),
317
+ w: alias("w", "sizes", "width"),
318
+ h: alias("h", "sizes", "height"),
319
+ minW: alias("minW", "sizes", "minWidth"),
320
+ maxW: alias("maxW", "sizes", "maxWidth"),
321
+ minH: alias("minH", "sizes", "minHeight"),
322
+ maxH: alias("maxH", "sizes", "maxHeight"),
323
+ bg: alias("bg", "colors", "background"),
324
+ rounded: alias("rounded", "radii", "borderRadius"),
325
+ roundedT: radius("roundedT", "TopLeft", "TopRight"),
326
+ roundedR: radius("roundedR", "TopRight", "BottomRight"),
327
+ roundedB: radius("roundedB", "BottomRight", "BottomLeft"),
328
+ roundedL: radius("roundedL", "TopLeft", "BottomLeft"),
329
+ roundedS: radius("roundedS", "StartStart", "EndStart"),
330
+ roundedE: radius("roundedE", "StartEnd", "EndEnd"),
331
+ shadow: alias("shadow", "shadows", "boxShadow"),
332
+ z: alias("z", "zIndices", "zIndex"),
333
+ gapX: alias("gapX", "spacing", "columnGap"),
334
+ gapY: alias("gapY", "spacing", "rowGap"),
335
+ insetX: alias("insetX", "spacing", "insetInline"),
336
+ insetY: alias("insetY", "spacing", "insetBlock"),
337
+ leading: alias("leading", "lineHeights", "lineHeight"),
338
+ tracking: alias("tracking", "letterSpacings", "letterSpacing")
339
+ } };
340
+ //#endregion
341
+ //#region src/config/presets/tailwind.ts
342
+ const tailwind = {
343
+ tokens: {
344
+ colors: {
345
+ red: {
346
+ 50: "oklch(97.1% 0.013 17.38)",
347
+ 100: "oklch(93.6% 0.032 17.717)",
348
+ 200: "oklch(88.5% 0.062 18.334)",
349
+ 300: "oklch(80.8% 0.114 19.571)",
350
+ 400: "oklch(70.4% 0.191 22.216)",
351
+ 500: "oklch(63.7% 0.237 25.331)",
352
+ 600: "oklch(57.7% 0.245 27.325)",
353
+ 700: "oklch(50.5% 0.213 27.518)",
354
+ 800: "oklch(44.4% 0.177 26.899)",
355
+ 900: "oklch(39.6% 0.141 25.723)",
356
+ 950: "oklch(25.8% 0.092 26.042)"
357
+ },
358
+ orange: {
359
+ 50: "oklch(98% 0.016 73.684)",
360
+ 100: "oklch(95.4% 0.038 75.164)",
361
+ 200: "oklch(90.1% 0.076 70.697)",
362
+ 300: "oklch(83.7% 0.128 66.29)",
363
+ 400: "oklch(75% 0.183 55.934)",
364
+ 500: "oklch(70.5% 0.213 47.604)",
365
+ 600: "oklch(64.6% 0.222 41.116)",
366
+ 700: "oklch(55.3% 0.195 38.402)",
367
+ 800: "oklch(47% 0.157 37.304)",
368
+ 900: "oklch(40.8% 0.123 38.172)",
369
+ 950: "oklch(26.6% 0.079 36.259)"
370
+ },
371
+ amber: {
372
+ 50: "oklch(98.7% 0.022 95.277)",
373
+ 100: "oklch(96.2% 0.059 95.617)",
374
+ 200: "oklch(92.4% 0.12 95.746)",
375
+ 300: "oklch(87.9% 0.169 91.605)",
376
+ 400: "oklch(82.8% 0.189 84.429)",
377
+ 500: "oklch(76.9% 0.188 70.08)",
378
+ 600: "oklch(66.6% 0.179 58.318)",
379
+ 700: "oklch(55.5% 0.163 48.998)",
380
+ 800: "oklch(47.3% 0.137 46.201)",
381
+ 900: "oklch(41.4% 0.112 45.904)",
382
+ 950: "oklch(27.9% 0.077 45.635)"
383
+ },
384
+ yellow: {
385
+ 50: "oklch(98.7% 0.026 102.212)",
386
+ 100: "oklch(97.3% 0.071 103.193)",
387
+ 200: "oklch(94.5% 0.129 101.54)",
388
+ 300: "oklch(90.5% 0.182 98.111)",
389
+ 400: "oklch(85.2% 0.199 91.936)",
390
+ 500: "oklch(79.5% 0.184 86.047)",
391
+ 600: "oklch(68.1% 0.162 75.834)",
392
+ 700: "oklch(55.4% 0.135 66.442)",
393
+ 800: "oklch(47.6% 0.114 61.907)",
394
+ 900: "oklch(42.1% 0.095 57.708)",
395
+ 950: "oklch(28.6% 0.066 53.813)"
396
+ },
397
+ lime: {
398
+ 50: "oklch(98.6% 0.031 120.757)",
399
+ 100: "oklch(96.7% 0.067 122.328)",
400
+ 200: "oklch(93.8% 0.127 124.321)",
401
+ 300: "oklch(89.7% 0.196 126.665)",
402
+ 400: "oklch(84.1% 0.238 128.85)",
403
+ 500: "oklch(76.8% 0.233 130.85)",
404
+ 600: "oklch(64.8% 0.2 131.684)",
405
+ 700: "oklch(53.2% 0.157 131.589)",
406
+ 800: "oklch(45.3% 0.124 130.933)",
407
+ 900: "oklch(40.5% 0.101 131.063)",
408
+ 950: "oklch(27.4% 0.072 132.109)"
409
+ },
410
+ green: {
411
+ 50: "oklch(98.2% 0.018 155.826)",
412
+ 100: "oklch(96.2% 0.044 156.743)",
413
+ 200: "oklch(92.5% 0.084 155.995)",
414
+ 300: "oklch(87.1% 0.15 154.449)",
415
+ 400: "oklch(79.2% 0.209 151.711)",
416
+ 500: "oklch(72.3% 0.219 149.579)",
417
+ 600: "oklch(62.7% 0.194 149.214)",
418
+ 700: "oklch(52.7% 0.154 150.069)",
419
+ 800: "oklch(44.8% 0.119 151.328)",
420
+ 900: "oklch(39.3% 0.095 152.535)",
421
+ 950: "oklch(26.6% 0.065 152.934)"
422
+ },
423
+ emerald: {
424
+ 50: "oklch(97.9% 0.021 166.113)",
425
+ 100: "oklch(95% 0.052 163.051)",
426
+ 200: "oklch(90.5% 0.093 164.15)",
427
+ 300: "oklch(84.5% 0.143 164.978)",
428
+ 400: "oklch(76.5% 0.177 163.223)",
429
+ 500: "oklch(69.6% 0.17 162.48)",
430
+ 600: "oklch(59.6% 0.145 163.225)",
431
+ 700: "oklch(50.8% 0.118 165.612)",
432
+ 800: "oklch(43.2% 0.095 166.913)",
433
+ 900: "oklch(37.8% 0.077 168.94)",
434
+ 950: "oklch(26.2% 0.051 172.552)"
435
+ },
436
+ teal: {
437
+ 50: "oklch(98.4% 0.014 180.72)",
438
+ 100: "oklch(95.3% 0.051 180.801)",
439
+ 200: "oklch(91% 0.096 180.426)",
440
+ 300: "oklch(85.5% 0.138 181.071)",
441
+ 400: "oklch(77.7% 0.152 181.912)",
442
+ 500: "oklch(70.4% 0.14 182.503)",
443
+ 600: "oklch(60% 0.118 184.704)",
444
+ 700: "oklch(51.1% 0.096 186.391)",
445
+ 800: "oklch(43.7% 0.078 188.216)",
446
+ 900: "oklch(38.6% 0.063 188.416)",
447
+ 950: "oklch(27.7% 0.046 192.524)"
448
+ },
449
+ cyan: {
450
+ 50: "oklch(98.4% 0.019 200.873)",
451
+ 100: "oklch(95.6% 0.045 203.388)",
452
+ 200: "oklch(91.7% 0.08 205.041)",
453
+ 300: "oklch(86.5% 0.127 207.078)",
454
+ 400: "oklch(78.9% 0.154 211.53)",
455
+ 500: "oklch(71.5% 0.143 215.221)",
456
+ 600: "oklch(60.9% 0.126 221.723)",
457
+ 700: "oklch(52% 0.105 223.128)",
458
+ 800: "oklch(45% 0.085 224.283)",
459
+ 900: "oklch(39.8% 0.07 227.392)",
460
+ 950: "oklch(30.2% 0.056 229.695)"
461
+ },
462
+ sky: {
463
+ 50: "oklch(97.7% 0.013 236.62)",
464
+ 100: "oklch(95.1% 0.026 236.824)",
465
+ 200: "oklch(90.1% 0.058 230.902)",
466
+ 300: "oklch(82.8% 0.111 230.318)",
467
+ 400: "oklch(74.6% 0.16 232.661)",
468
+ 500: "oklch(68.5% 0.169 237.323)",
469
+ 600: "oklch(58.8% 0.158 241.966)",
470
+ 700: "oklch(50% 0.134 242.749)",
471
+ 800: "oklch(44.3% 0.11 240.79)",
472
+ 900: "oklch(39.1% 0.09 240.876)",
473
+ 950: "oklch(29.3% 0.066 243.157)"
474
+ },
475
+ blue: {
476
+ 50: "oklch(97% 0.014 254.604)",
477
+ 100: "oklch(93.2% 0.032 255.585)",
478
+ 200: "oklch(88.2% 0.059 254.128)",
479
+ 300: "oklch(80.9% 0.105 251.813)",
480
+ 400: "oklch(70.7% 0.165 254.624)",
481
+ 500: "oklch(62.3% 0.214 259.815)",
482
+ 600: "oklch(54.6% 0.245 262.881)",
483
+ 700: "oklch(48.8% 0.243 264.376)",
484
+ 800: "oklch(42.4% 0.199 265.638)",
485
+ 900: "oklch(37.9% 0.146 265.522)",
486
+ 950: "oklch(28.2% 0.091 267.935)"
487
+ },
488
+ indigo: {
489
+ 50: "oklch(96.2% 0.018 272.314)",
490
+ 100: "oklch(93% 0.034 272.788)",
491
+ 200: "oklch(87% 0.065 274.039)",
492
+ 300: "oklch(78.5% 0.115 274.713)",
493
+ 400: "oklch(67.3% 0.182 276.935)",
494
+ 500: "oklch(58.5% 0.233 277.117)",
495
+ 600: "oklch(51.1% 0.262 276.966)",
496
+ 700: "oklch(45.7% 0.24 277.023)",
497
+ 800: "oklch(39.8% 0.195 277.366)",
498
+ 900: "oklch(35.9% 0.144 278.697)",
499
+ 950: "oklch(25.7% 0.09 281.288)"
500
+ },
501
+ violet: {
502
+ 50: "oklch(96.9% 0.016 293.756)",
503
+ 100: "oklch(94.3% 0.029 294.588)",
504
+ 200: "oklch(89.4% 0.057 293.283)",
505
+ 300: "oklch(81.1% 0.111 293.571)",
506
+ 400: "oklch(70.2% 0.183 293.541)",
507
+ 500: "oklch(60.6% 0.25 292.717)",
508
+ 600: "oklch(54.1% 0.281 293.009)",
509
+ 700: "oklch(49.1% 0.27 292.581)",
510
+ 800: "oklch(43.2% 0.232 292.759)",
511
+ 900: "oklch(38% 0.189 293.745)",
512
+ 950: "oklch(28.3% 0.141 291.089)"
513
+ },
514
+ purple: {
515
+ 50: "oklch(97.7% 0.014 308.299)",
516
+ 100: "oklch(94.6% 0.033 307.174)",
517
+ 200: "oklch(90.2% 0.063 306.703)",
518
+ 300: "oklch(82.7% 0.119 306.383)",
519
+ 400: "oklch(71.4% 0.203 305.504)",
520
+ 500: "oklch(62.7% 0.265 303.9)",
521
+ 600: "oklch(55.8% 0.288 302.321)",
522
+ 700: "oklch(49.6% 0.265 301.924)",
523
+ 800: "oklch(43.8% 0.218 303.724)",
524
+ 900: "oklch(38.1% 0.176 304.987)",
525
+ 950: "oklch(29.1% 0.149 302.717)"
526
+ },
527
+ fuchsia: {
528
+ 50: "oklch(97.7% 0.017 320.058)",
529
+ 100: "oklch(95.2% 0.037 318.852)",
530
+ 200: "oklch(90.3% 0.076 319.62)",
531
+ 300: "oklch(83.3% 0.145 321.434)",
532
+ 400: "oklch(74% 0.238 322.16)",
533
+ 500: "oklch(66.7% 0.295 322.15)",
534
+ 600: "oklch(59.1% 0.293 322.896)",
535
+ 700: "oklch(51.8% 0.253 323.949)",
536
+ 800: "oklch(45.2% 0.211 324.591)",
537
+ 900: "oklch(40.1% 0.17 325.612)",
538
+ 950: "oklch(29.3% 0.136 325.661)"
539
+ },
540
+ pink: {
541
+ 50: "oklch(97.1% 0.014 343.198)",
542
+ 100: "oklch(94.8% 0.028 342.258)",
543
+ 200: "oklch(89.9% 0.061 343.231)",
544
+ 300: "oklch(82.3% 0.12 346.018)",
545
+ 400: "oklch(71.8% 0.202 349.761)",
546
+ 500: "oklch(65.6% 0.241 354.308)",
547
+ 600: "oklch(59.2% 0.249 0.584)",
548
+ 700: "oklch(52.5% 0.223 3.958)",
549
+ 800: "oklch(45.9% 0.187 3.815)",
550
+ 900: "oklch(40.8% 0.153 2.432)",
551
+ 950: "oklch(28.4% 0.109 3.907)"
552
+ },
553
+ rose: {
554
+ 50: "oklch(96.9% 0.015 12.422)",
555
+ 100: "oklch(94.1% 0.03 12.58)",
556
+ 200: "oklch(89.2% 0.058 10.001)",
557
+ 300: "oklch(81% 0.117 11.638)",
558
+ 400: "oklch(71.2% 0.194 13.428)",
559
+ 500: "oklch(64.5% 0.246 16.439)",
560
+ 600: "oklch(58.6% 0.253 17.585)",
561
+ 700: "oklch(51.4% 0.222 16.935)",
562
+ 800: "oklch(45.5% 0.188 13.697)",
563
+ 900: "oklch(41% 0.159 10.272)",
564
+ 950: "oklch(27.1% 0.105 12.094)"
565
+ },
566
+ slate: {
567
+ 50: "oklch(98.4% 0.003 247.858)",
568
+ 100: "oklch(96.8% 0.007 247.896)",
569
+ 200: "oklch(92.9% 0.013 255.508)",
570
+ 300: "oklch(86.9% 0.022 252.894)",
571
+ 400: "oklch(70.4% 0.04 256.788)",
572
+ 500: "oklch(55.4% 0.046 257.417)",
573
+ 600: "oklch(44.6% 0.043 257.281)",
574
+ 700: "oklch(37.2% 0.044 257.287)",
575
+ 800: "oklch(27.9% 0.041 260.031)",
576
+ 900: "oklch(20.8% 0.042 265.755)",
577
+ 950: "oklch(12.9% 0.042 264.695)"
578
+ },
579
+ gray: {
580
+ 50: "oklch(98.5% 0.002 247.839)",
581
+ 100: "oklch(96.7% 0.003 264.542)",
582
+ 200: "oklch(92.8% 0.006 264.531)",
583
+ 300: "oklch(87.2% 0.01 258.338)",
584
+ 400: "oklch(70.7% 0.022 261.325)",
585
+ 500: "oklch(55.1% 0.027 264.364)",
586
+ 600: "oklch(44.6% 0.03 256.802)",
587
+ 700: "oklch(37.3% 0.034 259.733)",
588
+ 800: "oklch(27.8% 0.033 256.848)",
589
+ 900: "oklch(21% 0.034 264.665)",
590
+ 950: "oklch(13% 0.028 261.692)"
591
+ },
592
+ zinc: {
593
+ 50: "oklch(98.5% 0 none)",
594
+ 100: "oklch(96.7% 0.001 286.375)",
595
+ 200: "oklch(92% 0.004 286.32)",
596
+ 300: "oklch(87.1% 0.006 286.286)",
597
+ 400: "oklch(70.5% 0.015 286.067)",
598
+ 500: "oklch(55.2% 0.016 285.938)",
599
+ 600: "oklch(44.2% 0.017 285.786)",
600
+ 700: "oklch(37% 0.013 285.805)",
601
+ 800: "oklch(27.4% 0.006 286.033)",
602
+ 900: "oklch(21% 0.006 285.885)",
603
+ 950: "oklch(14.1% 0.005 285.823)"
604
+ },
605
+ neutral: {
606
+ 50: "oklch(98.5% 0 none)",
607
+ 100: "oklch(97% 0 none)",
608
+ 200: "oklch(92.2% 0 none)",
609
+ 300: "oklch(87% 0 none)",
610
+ 400: "oklch(70.8% 0 none)",
611
+ 500: "oklch(55.6% 0 none)",
612
+ 600: "oklch(43.9% 0 none)",
613
+ 700: "oklch(37.1% 0 none)",
614
+ 800: "oklch(26.9% 0 none)",
615
+ 900: "oklch(20.5% 0 none)",
616
+ 950: "oklch(14.5% 0 none)"
617
+ },
618
+ stone: {
619
+ 50: "oklch(98.5% 0.001 106.423)",
620
+ 100: "oklch(97% 0.001 106.424)",
621
+ 200: "oklch(92.3% 0.003 48.717)",
622
+ 300: "oklch(86.9% 0.005 56.366)",
623
+ 400: "oklch(70.9% 0.01 56.259)",
624
+ 500: "oklch(55.3% 0.013 58.071)",
625
+ 600: "oklch(44.4% 0.011 73.639)",
626
+ 700: "oklch(37.4% 0.01 67.558)",
627
+ 800: "oklch(26.8% 0.007 34.298)",
628
+ 900: "oklch(21.6% 0.006 56.043)",
629
+ 950: "oklch(14.7% 0.004 49.25)"
630
+ },
631
+ mauve: {
632
+ 50: "oklch(98.5% 0 none)",
633
+ 100: "oklch(96% 0.003 325.6)",
634
+ 200: "oklch(92.2% 0.005 325.62)",
635
+ 300: "oklch(86.5% 0.012 325.68)",
636
+ 400: "oklch(71.1% 0.019 323.02)",
637
+ 500: "oklch(54.2% 0.034 322.5)",
638
+ 600: "oklch(43.5% 0.029 321.78)",
639
+ 700: "oklch(36.4% 0.029 323.89)",
640
+ 800: "oklch(26.3% 0.024 320.12)",
641
+ 900: "oklch(21.2% 0.019 322.12)",
642
+ 950: "oklch(14.5% 0.008 326)"
643
+ },
644
+ olive: {
645
+ 50: "oklch(98.8% 0.003 106.5)",
646
+ 100: "oklch(96.6% 0.005 106.5)",
647
+ 200: "oklch(93% 0.007 106.5)",
648
+ 300: "oklch(88% 0.011 106.6)",
649
+ 400: "oklch(73.7% 0.021 106.9)",
650
+ 500: "oklch(58% 0.031 107.3)",
651
+ 600: "oklch(46.6% 0.025 107.3)",
652
+ 700: "oklch(39.4% 0.023 107.4)",
653
+ 800: "oklch(28.6% 0.016 107.4)",
654
+ 900: "oklch(22.8% 0.013 107.4)",
655
+ 950: "oklch(15.3% 0.006 107.1)"
656
+ },
657
+ mist: {
658
+ 50: "oklch(98.7% 0.002 197.1)",
659
+ 100: "oklch(96.3% 0.002 197.1)",
660
+ 200: "oklch(92.5% 0.005 214.3)",
661
+ 300: "oklch(87.2% 0.007 219.6)",
662
+ 400: "oklch(72.3% 0.014 214.4)",
663
+ 500: "oklch(56% 0.021 213.5)",
664
+ 600: "oklch(45% 0.017 213.2)",
665
+ 700: "oklch(37.8% 0.015 216)",
666
+ 800: "oklch(27.5% 0.011 216.9)",
667
+ 900: "oklch(21.8% 0.008 223.9)",
668
+ 950: "oklch(14.8% 0.004 228.8)"
669
+ },
670
+ taupe: {
671
+ 50: "oklch(98.6% 0.002 67.8)",
672
+ 100: "oklch(96% 0.002 17.2)",
673
+ 200: "oklch(92.2% 0.005 34.3)",
674
+ 300: "oklch(86.8% 0.007 39.5)",
675
+ 400: "oklch(71.4% 0.014 41.2)",
676
+ 500: "oklch(54.7% 0.021 43.1)",
677
+ 600: "oklch(43.8% 0.017 39.3)",
678
+ 700: "oklch(36.7% 0.016 35.7)",
679
+ 800: "oklch(26.8% 0.011 36.5)",
680
+ 900: "oklch(21.4% 0.009 43.1)",
681
+ 950: "oklch(14.7% 0.004 49.3)"
682
+ },
683
+ black: "#000",
684
+ white: "#fff"
685
+ },
686
+ spacing: { step: "0.25rem" },
687
+ sizes: {
688
+ "3xs": "{containers.3xs}",
689
+ "2xs": "{containers.2xs}",
690
+ xs: "{containers.xs}",
691
+ sm: "{containers.sm}",
692
+ md: "{containers.md}",
693
+ lg: "{containers.lg}",
694
+ xl: "{containers.xl}",
695
+ "2xl": "{containers.2xl}",
696
+ "3xl": "{containers.3xl}",
697
+ "4xl": "{containers.4xl}",
698
+ "5xl": "{containers.5xl}",
699
+ "6xl": "{containers.6xl}",
700
+ "7xl": "{containers.7xl}"
701
+ },
702
+ breakpoints: {
703
+ sm: "40rem",
704
+ md: "48rem",
705
+ lg: "64rem",
706
+ xl: "80rem",
707
+ "2xl": "96rem"
708
+ },
709
+ containers: {
710
+ "3xs": "16rem",
711
+ "2xs": "18rem",
712
+ xs: "20rem",
713
+ sm: "24rem",
714
+ md: "28rem",
715
+ lg: "32rem",
716
+ xl: "36rem",
717
+ "2xl": "42rem",
718
+ "3xl": "48rem",
719
+ "4xl": "56rem",
720
+ "5xl": "64rem",
721
+ "6xl": "72rem",
722
+ "7xl": "80rem"
723
+ },
724
+ fonts: {
725
+ sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Noto Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
726
+ serif: "ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif",
727
+ mono: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"
728
+ },
729
+ fontSizes: {
730
+ xs: "0.75rem",
731
+ sm: "0.875rem",
732
+ base: "1rem",
733
+ lg: "1.125rem",
734
+ xl: "1.25rem",
735
+ "2xl": "1.5rem",
736
+ "3xl": "1.875rem",
737
+ "4xl": "2.25rem",
738
+ "5xl": "3rem",
739
+ "6xl": "3.75rem",
740
+ "7xl": "4.5rem",
741
+ "8xl": "6rem",
742
+ "9xl": "8rem"
743
+ },
744
+ fontWeights: {
745
+ thin: "100",
746
+ extralight: "200",
747
+ light: "300",
748
+ normal: "400",
749
+ medium: "500",
750
+ semibold: "600",
751
+ bold: "700",
752
+ extrabold: "800",
753
+ black: "900"
754
+ },
755
+ letterSpacings: {
756
+ tighter: "-0.05em",
757
+ tight: "-0.025em",
758
+ normal: "0em",
759
+ wide: "0.025em",
760
+ wider: "0.05em",
761
+ widest: "0.1em"
762
+ },
763
+ lineHeights: {
764
+ tight: "1.25",
765
+ snug: "1.375",
766
+ normal: "1.5",
767
+ relaxed: "1.625",
768
+ loose: "2"
769
+ },
770
+ radii: {
771
+ xs: "0.125rem",
772
+ sm: "0.25rem",
773
+ md: "0.375rem",
774
+ lg: "0.5rem",
775
+ xl: "0.75rem",
776
+ "2xl": "1rem",
777
+ "3xl": "1.5rem",
778
+ "4xl": "2rem"
779
+ },
780
+ shadows: {
781
+ "2xs": "0 1px rgb(0 0 0 / 0.05)",
782
+ xs: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
783
+ sm: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
784
+ md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
785
+ lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
786
+ xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
787
+ "2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)"
788
+ },
789
+ textShadows: {
790
+ "2xs": "0px 1px 0px rgb(0 0 0 / 0.15)",
791
+ xs: "0px 1px 1px rgb(0 0 0 / 0.2)",
792
+ sm: "0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075)",
793
+ md: "0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1)",
794
+ lg: "0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1)"
795
+ },
796
+ easings: {
797
+ in: "cubic-bezier(0.4, 0, 1, 1)",
798
+ out: "cubic-bezier(0, 0, 0.2, 1)",
799
+ "in-out": "cubic-bezier(0.4, 0, 0.2, 1)"
800
+ },
801
+ blurs: {
802
+ xs: "4px",
803
+ sm: "8px",
804
+ md: "12px",
805
+ lg: "16px",
806
+ xl: "24px",
807
+ "2xl": "40px",
808
+ "3xl": "64px"
809
+ },
810
+ aspectRatios: { video: "16 / 9" }
811
+ },
812
+ textStyles: {
813
+ xs: {
814
+ fontSize: "xs",
815
+ lineHeight: "calc(1 / 0.75)"
816
+ },
817
+ sm: {
818
+ fontSize: "sm",
819
+ lineHeight: "calc(1.25 / 0.875)"
820
+ },
821
+ base: {
822
+ fontSize: "base",
823
+ lineHeight: "calc(1.5 / 1)"
824
+ },
825
+ lg: {
826
+ fontSize: "lg",
827
+ lineHeight: "calc(1.75 / 1.125)"
828
+ },
829
+ xl: {
830
+ fontSize: "xl",
831
+ lineHeight: "calc(1.75 / 1.25)"
832
+ },
833
+ "2xl": {
834
+ fontSize: "2xl",
835
+ lineHeight: "calc(2 / 1.5)"
836
+ },
837
+ "3xl": {
838
+ fontSize: "3xl",
839
+ lineHeight: "calc(2.25 / 1.875)"
840
+ },
841
+ "4xl": {
842
+ fontSize: "4xl",
843
+ lineHeight: "calc(2.5 / 2.25)"
844
+ },
845
+ "5xl": {
846
+ fontSize: "5xl",
847
+ lineHeight: "1"
848
+ },
849
+ "6xl": {
850
+ fontSize: "6xl",
851
+ lineHeight: "1"
852
+ },
853
+ "7xl": {
854
+ fontSize: "7xl",
855
+ lineHeight: "1"
856
+ },
857
+ "8xl": {
858
+ fontSize: "8xl",
859
+ lineHeight: "1"
860
+ },
861
+ "9xl": {
862
+ fontSize: "9xl",
863
+ lineHeight: "1"
864
+ }
865
+ },
866
+ keyframes: {
867
+ spin: { to: { transform: "rotate(360deg)" } },
868
+ ping: { "75%, 100%": {
869
+ transform: "scale(2)",
870
+ opacity: "0"
871
+ } },
872
+ pulse: { "50%": { opacity: "0.5" } },
873
+ bounce: {
874
+ "0%, 100%": {
875
+ transform: "translateY(-25%)",
876
+ animationTimingFunction: "cubic-bezier(0.8, 0, 1, 1)"
877
+ },
878
+ "50%": {
879
+ transform: "none",
880
+ animationTimingFunction: "cubic-bezier(0, 0, 0.2, 1)"
881
+ }
882
+ }
883
+ }
884
+ };
885
+ //#endregion
886
+ //#region src/core/rules.ts
887
+ /** CSS-wide keywords, and `default`, which CSS reserves: never a name. */
888
+ const CSS_WIDE = /* @__PURE__ */ new Set([
889
+ "initial",
890
+ "inherit",
891
+ "unset",
892
+ "revert",
893
+ "revert-layer",
894
+ "default"
895
+ ]);
896
+ /** Generic font families: keywords in `font-family`, so a family named like one is quoted. */
897
+ const GENERIC_FAMILIES = /* @__PURE__ */ new Set([
898
+ "serif",
899
+ "sans-serif",
900
+ "monospace",
901
+ "cursive",
902
+ "fantasy",
903
+ "system-ui",
904
+ "ui-serif",
905
+ "ui-sans-serif",
906
+ "ui-monospace",
907
+ "ui-rounded",
908
+ "math",
909
+ "emoji",
910
+ "fangsong"
911
+ ]);
912
+ //#endregion
913
+ //#region src/core/hash.ts
914
+ /**
915
+ * Stable identities. Hashes name atoms (`~a1b2c3`) and key caches, so they must
916
+ * be identical across processes, platforms and runtimes: a small synchronous
917
+ * hash in plain JS rather than `node:crypto`.
918
+ */
919
+ /** cyrb53: a fast 53-bit string hash with good avalanche. Public domain (bryc). */
920
+ function cyrb53(input, seed) {
921
+ let h1 = 3735928559 ^ seed;
922
+ let h2 = 1103547991 ^ seed;
923
+ for (let i = 0; i < input.length; i++) {
924
+ const ch = input.charCodeAt(i);
925
+ h1 = Math.imul(h1 ^ ch, 2654435761);
926
+ h2 = Math.imul(h2 ^ ch, 1597334677);
927
+ }
928
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
929
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
930
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
931
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
932
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
933
+ }
934
+ const CHUNK = 10;
935
+ const CHUNK_MOD = 36 ** CHUNK;
936
+ /** Deterministic base-36 hash of `input`, `length` characters of `[0-9a-z]`. */
937
+ function hash(input, length = 6) {
938
+ let out = "";
939
+ for (let seed = 0; out.length < length; seed++) out += (cyrb53(input, seed) % CHUNK_MOD).toString(36).padStart(CHUNK, "0");
940
+ return out.slice(0, length);
941
+ }
942
+ //#endregion
943
+ //#region src/config/files.ts
944
+ /**
945
+ * Files a config reads through `fs` rather than `import` (`dtcg()`'s token
946
+ * and resolver files). Vite's config loader only sees the module graph, so
947
+ * without this an edit to one would neither reload the config in dev nor
948
+ * change the build cache's key (PLAN §5.3, D17).
949
+ *
950
+ * Two records, because a config reaches the plugin two ways: loaded from
951
+ * `vicss.config.ts`, every read made while it loads is reported to the
952
+ * plugin's recorder; passed as an object (`vicss({ config })`), it was built
953
+ * before the plugin existed, so each `dtcg()` result carries its own files.
954
+ * Each result can also read its files again: a preset package that Node
955
+ * imported natively stays cached, so its `dtcg()` call never runs twice by
956
+ * itself. All keys are global symbols: the config entry and the plugin are
957
+ * separate bundles once packed, and a config may load a second copy of either.
958
+ */
959
+ /** Where the plugin installs its recorder (an `AsyncLocalStorage<ConfigLoad>`). */
960
+ const READS = Symbol.for("@menapo/vicss/config-load");
961
+ /** Non-enumerable on a `dtcg()` result: the absolute paths it read. */
962
+ const FILES = Symbol.for("@menapo/vicss/config-files");
963
+ /** Non-enumerable on a `dtcg()` result: a fresh result if a file changed since, else undefined. */
964
+ const RELOAD = Symbol.for("@menapo/vicss/config-reload");
965
+ const current = () => globalThis[READS]?.getStore();
966
+ /** Reports a file read while a config loads, before reading it: a missing file is watched too. */
967
+ function recordRead(file) {
968
+ current()?.reads.add(file);
969
+ }
970
+ /** The directory of the config file loading now, if one is. */
971
+ function configDir() {
972
+ return current()?.dir;
973
+ }
974
+ /** What a file held when it was read: undefined when it could not be read. */
975
+ function contentKey(text) {
976
+ return text === void 0 ? void 0 : hash(text, 16);
977
+ }
978
+ /**
979
+ * Records on `result` the files it read, by path to `contentKey()` of the
980
+ * text read, and how to compute it again, without making either part of the
981
+ * config's data or hash. `read` returns a file's current text, or undefined.
982
+ */
983
+ function withFiles(result, files, read, again) {
984
+ Object.defineProperty(result, FILES, { value: [...files.keys()] });
985
+ Object.defineProperty(result, RELOAD, { value: () => {
986
+ for (const [file, key] of files) if (contentKey(read(file)) !== key) return again();
987
+ } });
988
+ return result;
989
+ }
990
+ //#endregion
991
+ //#region src/config/dtcg.ts
992
+ /**
993
+ * DTCG 2025.10 input (PLAN §4.1, §5.3, D21): a `.tokens.json` file, a
994
+ * `.resolver.json` file or an already-parsed object becomes the token part of
995
+ * a vicss config: raw tokens, semantic tokens (values that differ between the
996
+ * contexts of a resolver modifier, keyed by mode) and text styles (typography
997
+ * tokens).
998
+ *
999
+ * The format has groups but no scales, so a token's scale comes from
1000
+ * `options.scales`, else from the name of its top-level group (`color` →
1001
+ * colors), else from its `$type` (a `shadow` → shadows). Aliases stay aliases
1002
+ * (`{color.blue.500}` → `{colors.blue.500}`): an alias of a switching token
1003
+ * then switches with it in nested mode scopes (PLAN §5.9), which a copied
1004
+ * value would not. In a typography token they keep the text style reading
1005
+ * tokens; its literal sub-values compile into atoms, so changing one is a
1006
+ * code change rather than a theme-only deploy (PLAN §4.8, D13).
1007
+ *
1008
+ * Runs at config load, in Node. Node modules are fetched lazily so the config
1009
+ * entry still loads where there is no file system (PLAN §5.1).
1010
+ */
1011
+ /** Reverse-domain key of vicss's `$extensions` entry: `{ "value": "<any CSS>" }`. */
1012
+ const EXTENSION = "dev.menapo.vicss";
1013
+ /** `$extensions` key whose object holds a token's value per mode, as `vicss tokens export` writes it. */
1014
+ const MODES_EXTENSION = "dev.menapo.vicss.modes";
1015
+ /**
1016
+ * `$extensions` key that marks a token private (`tokenVisibility`), as
1017
+ * `vicss tokens export` writes it: `true`, or `false` for public.
1018
+ */
1019
+ const PRIVATE_EXTENSION = "dev.menapo.vicss.private";
1020
+ const TYPES = /* @__PURE__ */ new Set([
1021
+ "color",
1022
+ "dimension",
1023
+ "fontFamily",
1024
+ "fontWeight",
1025
+ "duration",
1026
+ "cubicBezier",
1027
+ "number",
1028
+ "strokeStyle",
1029
+ "border",
1030
+ "transition",
1031
+ "shadow",
1032
+ "gradient",
1033
+ "typography"
1034
+ ]);
1035
+ const TOKEN_PROPS = /* @__PURE__ */ new Set([
1036
+ "$value",
1037
+ "$ref",
1038
+ "$type",
1039
+ "$description",
1040
+ "$extensions",
1041
+ "$deprecated"
1042
+ ]);
1043
+ const GROUP_PROPS = /* @__PURE__ */ new Set([
1044
+ "$type",
1045
+ "$description",
1046
+ "$extensions",
1047
+ "$deprecated",
1048
+ "$extends"
1049
+ ]);
1050
+ /** `{group.token}`: a whole-token alias. */
1051
+ const ALIAS = /^\{([^{}]+)\}$/;
1052
+ /** Aliases inside a CSS string from vicss's extension. */
1053
+ const ALIASES = /\{([^{}]*)\}/g;
1054
+ /** What dtcg() writes for an alias: a vicss reference, resolved by the core. */
1055
+ const VICSS_REFS = /\{[A-Za-z]+\.[^\s{}]+\}/g;
1056
+ const WHOLE_REF = /^\{[A-Za-z]+\.[^\s{}]+\}$/;
1057
+ /** Kept in step with the core's scale list by the type. */
1058
+ const SCALES = {
1059
+ colors: true,
1060
+ spacing: true,
1061
+ sizes: true,
1062
+ radii: true,
1063
+ fonts: true,
1064
+ fontSizes: true,
1065
+ fontWeights: true,
1066
+ lineHeights: true,
1067
+ letterSpacings: true,
1068
+ shadows: true,
1069
+ textShadows: true,
1070
+ durations: true,
1071
+ easings: true,
1072
+ zIndices: true,
1073
+ opacities: true,
1074
+ blurs: true,
1075
+ aspectRatios: true,
1076
+ breakpoints: true,
1077
+ containers: true
1078
+ };
1079
+ /**
1080
+ * Group names that name a scale, compared lowercase without separators, so
1081
+ * `font-size`, `fontSize` and `font_size` all match.
1082
+ */
1083
+ const GROUP_NAMES = {
1084
+ color: "colors",
1085
+ colors: "colors",
1086
+ space: "spacing",
1087
+ spacing: "spacing",
1088
+ size: "sizes",
1089
+ sizes: "sizes",
1090
+ radius: "radii",
1091
+ radii: "radii",
1092
+ font: "fonts",
1093
+ fonts: "fonts",
1094
+ fontfamily: "fonts",
1095
+ fontfamilies: "fonts",
1096
+ fontsize: "fontSizes",
1097
+ fontsizes: "fontSizes",
1098
+ fontweight: "fontWeights",
1099
+ fontweights: "fontWeights",
1100
+ lineheight: "lineHeights",
1101
+ lineheights: "lineHeights",
1102
+ letterspacing: "letterSpacings",
1103
+ letterspacings: "letterSpacings",
1104
+ shadow: "shadows",
1105
+ shadows: "shadows",
1106
+ textshadow: "textShadows",
1107
+ textshadows: "textShadows",
1108
+ duration: "durations",
1109
+ durations: "durations",
1110
+ easing: "easings",
1111
+ easings: "easings",
1112
+ cubicbezier: "easings",
1113
+ zindex: "zIndices",
1114
+ zindices: "zIndices",
1115
+ opacity: "opacities",
1116
+ opacities: "opacities",
1117
+ blur: "blurs",
1118
+ blurs: "blurs",
1119
+ aspectratio: "aspectRatios",
1120
+ aspectratios: "aspectRatios",
1121
+ breakpoint: "breakpoints",
1122
+ breakpoints: "breakpoints",
1123
+ container: "containers",
1124
+ containers: "containers",
1125
+ typography: "textStyles"
1126
+ };
1127
+ /**
1128
+ * Two-level paths that name a scale, each name compared like GROUP_NAMES:
1129
+ * the common `font.{family,size,weight}` layout and words split in two
1130
+ * (`line.height`). A list rather than the two names joined, or `size.s` and
1131
+ * `duration.s` (T-shirt sizes) would read as `sizes` and `durations` and
1132
+ * lose their `s`.
1133
+ */
1134
+ const COMPOUND_NAMES = {
1135
+ "font.family": "fonts",
1136
+ "font.families": "fonts",
1137
+ "font.size": "fontSizes",
1138
+ "font.sizes": "fontSizes",
1139
+ "font.weight": "fontWeights",
1140
+ "font.weights": "fontWeights",
1141
+ "font.lineheight": "lineHeights",
1142
+ "font.lineheights": "lineHeights",
1143
+ "font.letterspacing": "letterSpacings",
1144
+ "font.letterspacings": "letterSpacings",
1145
+ "line.height": "lineHeights",
1146
+ "line.heights": "lineHeights",
1147
+ "letter.spacing": "letterSpacings",
1148
+ "letter.spacings": "letterSpacings",
1149
+ "text.shadow": "textShadows",
1150
+ "text.shadows": "textShadows",
1151
+ "z.index": "zIndices",
1152
+ "z.indices": "zIndices",
1153
+ "aspect.ratio": "aspectRatios",
1154
+ "aspect.ratios": "aspectRatios",
1155
+ "cubic.bezier": "easings"
1156
+ };
1157
+ /** Placement by `$type` when no group name decides. Lengths have several scales, so none. */
1158
+ const TYPE_TARGETS = {
1159
+ color: "colors",
1160
+ duration: "durations",
1161
+ cubicBezier: "easings",
1162
+ shadow: "shadows",
1163
+ fontFamily: "fonts",
1164
+ fontWeight: "fontWeights",
1165
+ typography: "textStyles"
1166
+ };
1167
+ /**
1168
+ * Types a group name may place. A `color` group's opacity numbers are not
1169
+ * colours: they fall through to the `$type` rule (and its warning) instead of
1170
+ * becoming `color: "opacity.50"`.
1171
+ */
1172
+ const LENGTH = ["dimension"];
1173
+ const ACCEPTS = {
1174
+ colors: ["color"],
1175
+ spacing: LENGTH,
1176
+ sizes: LENGTH,
1177
+ radii: LENGTH,
1178
+ fonts: ["fontFamily"],
1179
+ fontSizes: LENGTH,
1180
+ fontWeights: ["fontWeight", "number"],
1181
+ lineHeights: ["number", "dimension"],
1182
+ letterSpacings: LENGTH,
1183
+ shadows: ["shadow"],
1184
+ textShadows: ["shadow"],
1185
+ durations: ["duration"],
1186
+ easings: ["cubicBezier"],
1187
+ zIndices: ["number"],
1188
+ opacities: ["number"],
1189
+ blurs: LENGTH,
1190
+ aspectRatios: ["number"],
1191
+ breakpoints: LENGTH,
1192
+ containers: LENGTH,
1193
+ textStyles: ["typography"]
1194
+ };
1195
+ /** Mode conditions every vicss system has (PLAN D12). */
1196
+ const BUILT_IN_MODES = /* @__PURE__ */ new Set(["dark", "light"]);
1197
+ function dtcg(source, options = {}) {
1198
+ const opts = checkOptions(options);
1199
+ let doc;
1200
+ let file;
1201
+ let dir;
1202
+ let named = false;
1203
+ /** The file a path or URL names, so that reading it again needs no config load around it. */
1204
+ let abs;
1205
+ const session = new Session(opts);
1206
+ if (typeof source === "string" || source instanceof URL) {
1207
+ const { path, url } = nodeApi();
1208
+ try {
1209
+ abs = typeof source !== "string" || source.startsWith("file:") ? url.fileURLToPath(typeof source === "string" ? source : source.href) : path.resolve(configDir() ?? process.cwd(), source);
1210
+ } catch (e) {
1211
+ throw new VicssError("CONFIG", `dtcg(): \`${String(source)}\` is not a file path or file: URL`, { hint: e.message });
1212
+ }
1213
+ file = displayPath(abs);
1214
+ dir = path.dirname(abs);
1215
+ doc = session.read(abs, file);
1216
+ named = abs.endsWith(".resolver.json");
1217
+ } else if (isPlainObject(source)) {
1218
+ doc = source;
1219
+ file = "dtcg(object)";
1220
+ } else throw new VicssError("CONFIG", "dtcg(): expected a file path, a file: URL or a token object");
1221
+ session.label = file;
1222
+ if (!isPlainObject(doc)) throw new VicssError("CONFIG", `${file}: expected a JSON object`);
1223
+ const result = named || Object.hasOwn(doc, "resolutionOrder") ? fromResolver(doc, file, dir, session) : fromTokens(doc, file, session);
1224
+ session.finish();
1225
+ return withFiles(result, session.files, readText, () => dtcg(abs ?? source, options));
1226
+ }
1227
+ /** A file's text now, or undefined: what `Session.read` saw is compared with it. */
1228
+ function readText(file) {
1229
+ try {
1230
+ return nodeApi().fs.readFileSync(file, "utf8");
1231
+ } catch {
1232
+ return;
1233
+ }
1234
+ }
1235
+ function fromTokens(doc, file, session) {
1236
+ if (session.options.modes.size > 0) throw new VicssError("CONFIG", `${file}: options.modes applies to resolver files only`);
1237
+ session.claim(doc, file);
1238
+ const base = evaluate(doc, file, session);
1239
+ return assemble(session, base, exportedModes(base));
1240
+ }
1241
+ /**
1242
+ * The values per mode that `vicss tokens export --format dtcg` writes into
1243
+ * a token's `$extensions["dev.menapo.vicss.modes"]` (`{ "dark": "#f2f2ff" }`),
1244
+ * as one context per mode, so a file vicss wrote reads back with its modes.
1245
+ * The values are vicss's already: CSS, aliases as `{colors.gray.50}`.
1246
+ */
1247
+ function exportedModes(base) {
1248
+ const byMode = /* @__PURE__ */ new Map();
1249
+ for (const [key, outcome] of base) {
1250
+ const extensions = outcome.entry.node.$extensions;
1251
+ const modes = isPlainObject(extensions) ? extensions[MODES_EXTENSION] : void 0;
1252
+ if (modes === void 0) continue;
1253
+ const bad = () => configError(outcome.entry.file, key, `$extensions["${MODES_EXTENSION}"]: expected { "<mode>": "<a CSS value>" }`);
1254
+ if (!isPlainObject(modes)) throw bad();
1255
+ for (const [mode, value] of Object.entries(modes)) {
1256
+ if (typeof value !== "string" || !/^[A-Za-z][A-Za-z0-9]*$/.test(mode)) throw bad();
1257
+ let outcomes = byMode.get(mode);
1258
+ if (!outcomes) byMode.set(mode, outcomes = /* @__PURE__ */ new Map());
1259
+ outcomes.set(key, {
1260
+ ...outcome,
1261
+ value
1262
+ });
1263
+ }
1264
+ }
1265
+ return [...byMode].map(([mode, outcomes]) => ({
1266
+ modifier: MODES_EXTENSION,
1267
+ mode,
1268
+ label: `_${mode}`,
1269
+ outcomes: new Map([...base, ...outcomes])
1270
+ }));
1271
+ }
1272
+ function checkOptions(options) {
1273
+ const bad = (at, message) => new VicssError("CONFIG", `dtcg(): ${at}: ${message}`);
1274
+ const raw = options;
1275
+ if (!isPlainObject(raw)) throw bad("options", "expected an object");
1276
+ const scales = /* @__PURE__ */ new Map();
1277
+ if (raw.scales !== void 0) {
1278
+ if (!isPlainObject(raw.scales)) throw bad("options.scales", "expected an object");
1279
+ for (const [path, target] of Object.entries(raw.scales)) {
1280
+ if (target === void 0) continue;
1281
+ if (target !== false && target !== "textStyles" && !(typeof target === "string" && Object.hasOwn(SCALES, target))) throw bad(`options.scales["${path}"]`, `${describe(target)} is not a scale, "textStyles" or false`);
1282
+ scales.set(path, target);
1283
+ }
1284
+ }
1285
+ const modes = /* @__PURE__ */ new Map();
1286
+ if (raw.modes !== void 0) {
1287
+ if (!isPlainObject(raw.modes)) throw bad("options.modes", "expected an object");
1288
+ for (const [modifier, contexts] of Object.entries(raw.modes)) {
1289
+ if (contexts === void 0) continue;
1290
+ if (!isPlainObject(contexts)) throw bad(`options.modes.${modifier}`, "expected an object of context → mode");
1291
+ const map = /* @__PURE__ */ new Map();
1292
+ for (const [context, mode] of Object.entries(contexts)) {
1293
+ if (mode === void 0) continue;
1294
+ const at = `options.modes.${modifier}.${context}`;
1295
+ if (mode === false || mode === "base") map.set(context, mode);
1296
+ else if (typeof mode === "string" && /^_?[A-Za-z][A-Za-z0-9]*$/.test(mode)) map.set(context, mode.replace(/^_/, ""));
1297
+ else throw bad(at, "expected a mode name like \"dark\", \"base\" or false");
1298
+ }
1299
+ modes.set(modifier, map);
1300
+ }
1301
+ }
1302
+ const onWarn = raw.onWarn;
1303
+ if (onWarn !== void 0 && typeof onWarn !== "function") throw bad("options.onWarn", "expected a function");
1304
+ return {
1305
+ scales,
1306
+ modes,
1307
+ onWarn: onWarn ?? ((message) => console.warn(message))
1308
+ };
1309
+ }
1310
+ var Skip = class {
1311
+ reason;
1312
+ constructor(reason) {
1313
+ this.reason = reason;
1314
+ }
1315
+ };
1316
+ var Session = class {
1317
+ /** Which file a token or group object came from, for errors. */
1318
+ origin = /* @__PURE__ */ new WeakMap();
1319
+ #files = /* @__PURE__ */ new Map();
1320
+ /**
1321
+ * Every file read, in order, to `contentKey()` of its text; a missing one
1322
+ * too, since creating it changes the config.
1323
+ */
1324
+ files = /* @__PURE__ */ new Map();
1325
+ /** Warning kind → token key → detail. Maps, so a token met in several permutations is named once. */
1326
+ unmapped = /* @__PURE__ */ new Map();
1327
+ skipped = /* @__PURE__ */ new Map();
1328
+ partial = /* @__PURE__ */ new Map();
1329
+ extra = /* @__PURE__ */ new Map();
1330
+ fixed = /* @__PURE__ */ new Map();
1331
+ overlap = /* @__PURE__ */ new Map();
1332
+ /** `options.scales` paths that matched a token: the rest are typos. */
1333
+ usedScales = /* @__PURE__ */ new Set();
1334
+ label = "";
1335
+ options;
1336
+ constructor(options) {
1337
+ this.options = options;
1338
+ }
1339
+ read(abs, display) {
1340
+ if (this.#files.has(abs)) return this.#files.get(abs);
1341
+ this.files.set(abs, void 0);
1342
+ recordRead(abs);
1343
+ let text;
1344
+ try {
1345
+ text = nodeApi().fs.readFileSync(abs, "utf8");
1346
+ this.files.set(abs, contentKey(text));
1347
+ } catch (e) {
1348
+ const code = e.code;
1349
+ throw new VicssError("CONFIG", `${display}: cannot read the file (${String(code ?? e)})`);
1350
+ }
1351
+ let json;
1352
+ try {
1353
+ json = JSON.parse(text.charCodeAt(0) === 65279 ? text.slice(1) : text);
1354
+ } catch (e) {
1355
+ throw new VicssError("CONFIG", `${display}: not valid JSON: ${e.message}`);
1356
+ }
1357
+ this.#files.set(abs, json);
1358
+ return json;
1359
+ }
1360
+ /** Records `file` as the origin of every group and token object in `tree` not claimed yet. */
1361
+ claim(tree, file) {
1362
+ if (!isPlainObject(tree) || this.origin.has(tree)) return;
1363
+ this.origin.set(tree, file);
1364
+ if (isToken(tree)) return;
1365
+ for (const [key, child] of Object.entries(tree)) if (!key.startsWith("$") || key === "$root") this.claim(child, file);
1366
+ }
1367
+ finish() {
1368
+ const unused = [...this.options.scales.keys()].filter((path) => !this.usedScales.has(path));
1369
+ if (unused.length > 0) throw new VicssError("CONFIG", `${this.label}: options.scales names no group or token: ${unused.map((p) => `"${p}"`).join(", ")}`, { hint: "Paths are DTCG names joined with `.`, as in an alias: `brand.colors`." });
1370
+ const warn = (entries, what, hint) => {
1371
+ if (entries.size === 0) return;
1372
+ const n = entries.size === 1 ? "1 token" : `${entries.size} tokens`;
1373
+ const list = [...entries].map(([key, detail]) => `${key} (${detail})`).join(", ");
1374
+ this.options.onWarn(`vicss: ${this.label}: ${what(n)}: ${list}. ${hint}`);
1375
+ };
1376
+ warn(this.unmapped, (n) => `left out ${n} that no scale takes`, "Place a group with dtcg(…, { scales: { \"group\": \"spacing\" } }), or leave it out silently with false.");
1377
+ warn(this.skipped, (n) => `left out ${n} with no single CSS value`, `Give a token a CSS value with "$extensions": { "${EXTENSION}": { "value": "…" } }.`);
1378
+ warn(this.partial, (n) => `${n} not defined in every context`, "Where a context gives a token no value, the token keeps its base value.");
1379
+ warn(this.extra, (n) => `left out ${n} that only some contexts define and no value there aliases`, "A token a context adds is kept when a value in that context aliases it; define it in the base context to use it directly.");
1380
+ warn(this.fixed, (n) => `kept the base value of ${n} that text styles cannot switch per mode`, "Alias mode-dependent tokens from the typography sub-values instead.");
1381
+ warn(this.overlap, (n) => `${n} switch with the contexts of several modifiers`, "Where two of these modes hold at once, the one that ranks higher among vicss conditions wins, not the modifier later in resolutionOrder, and the combination gets no value of its own.");
1382
+ }
1383
+ };
1384
+ function nodeApi() {
1385
+ const proc = globalThis.process;
1386
+ if (typeof proc?.getBuiltinModule !== "function") throw new VicssError("CONFIG", "dtcg(): reading token files needs Node.js; pass a parsed token object instead");
1387
+ return {
1388
+ fs: proc.getBuiltinModule("node:fs"),
1389
+ path: proc.getBuiltinModule("node:path"),
1390
+ url: proc.getBuiltinModule("node:url")
1391
+ };
1392
+ }
1393
+ /** Relative to the working directory when inside it: what a developer recognises in an error. */
1394
+ function displayPath(abs) {
1395
+ const { path } = nodeApi();
1396
+ const rel = path.relative(process.cwd(), abs);
1397
+ return rel && !rel.startsWith("..") && !path.isAbsolute(rel) ? rel.split(path.sep).join("/") : abs;
1398
+ }
1399
+ function isToken(node) {
1400
+ return Object.hasOwn(node, "$value") || Object.hasOwn(node, "$ref");
1401
+ }
1402
+ function isGroup(node) {
1403
+ return isPlainObject(node) && !isToken(node);
1404
+ }
1405
+ function configError(file, at, message, hint) {
1406
+ return new VicssError("CONFIG", `${file}: ${at ? `${at}: ` : ""}${message}`, hint === void 0 ? {} : { hint });
1407
+ }
1408
+ /** Validates the structure (names, `$` properties) and lists every token in document order. */
1409
+ function buildIndex(tree, file, session) {
1410
+ const root = resolveExtends(tree, file, session);
1411
+ const tokens = /* @__PURE__ */ new Map();
1412
+ const groups = /* @__PURE__ */ new Map();
1413
+ const fileOf = (node) => session.origin.get(node) ?? file;
1414
+ const token = (node, segments, groupType, parent) => {
1415
+ const key = segments.join(".");
1416
+ const fail = (message, hint) => configError(isPlainObject(node) ? fileOf(node) : fileOf(parent), key, message, hint);
1417
+ if (!isPlainObject(node) || !isToken(node)) throw fail("expected a token: an object with $value");
1418
+ for (const prop of Object.keys(node)) {
1419
+ if (!prop.startsWith("$")) throw fail(`is a token ($value) with a child \`${prop}\``, "A token cannot hold tokens; put the value in `$root` and keep the children in the group.");
1420
+ if (!TOKEN_PROPS.has(prop)) throw fail(`unknown token property \`${prop}\``, "Names cannot start with `$`.");
1421
+ }
1422
+ if (Object.hasOwn(node, "$value") && Object.hasOwn(node, "$ref")) throw fail("has both $value and $ref");
1423
+ if (node.$value === null) throw fail("$value cannot be null");
1424
+ if (Object.hasOwn(node, "$ref") && typeof node.$ref !== "string") throw fail("$ref must be a JSON pointer string");
1425
+ checkMeta(node, fail);
1426
+ const ext = isPlainObject(node.$extensions) ? node.$extensions[EXTENSION] : void 0;
1427
+ if (ext !== void 0) {
1428
+ const keys = isPlainObject(ext) ? Object.keys(ext) : [];
1429
+ if (!isPlainObject(ext) || keys.some((k) => k !== "value") || typeof ext.value !== "string" || ext.value.trim() === "") throw fail(`$extensions["${EXTENSION}"]: expected { "value": "<a CSS value>" }`);
1430
+ }
1431
+ tokens.set(key, {
1432
+ key,
1433
+ segments,
1434
+ node,
1435
+ groupType,
1436
+ file: fileOf(node)
1437
+ });
1438
+ };
1439
+ const visit = (group, segments, inherited) => {
1440
+ const key = segments.join(".");
1441
+ if (segments.length > 0) groups.set(key, group);
1442
+ const fail = (message, hint) => configError(fileOf(group), key, message, hint);
1443
+ checkMeta(group, fail);
1444
+ const type = group.$type === void 0 ? inherited : group.$type;
1445
+ for (const [name, child] of Object.entries(group)) {
1446
+ const here = [...segments, name];
1447
+ if (name === "$root") token(child, here, type, group);
1448
+ else if (name.startsWith("$")) {
1449
+ if (!GROUP_PROPS.has(name) && !(name === "$schema" && segments.length === 0)) throw fail(`unknown group property \`${name}\``, "Names cannot start with `$`.");
1450
+ } else {
1451
+ if (name === "" || /[{}.]/.test(name)) throw configError(fileOf(group), here.join("."), `\`${name}\` is not a token or group name: names are non-empty and contain no {, } or .`);
1452
+ if (!isPlainObject(child)) throw configError(fileOf(group), here.join("."), "expected a token (an object with $value) or a group");
1453
+ if (isToken(child)) token(child, here, type, group);
1454
+ else visit(child, here, type);
1455
+ }
1456
+ }
1457
+ };
1458
+ visit(root, [], void 0);
1459
+ return {
1460
+ root,
1461
+ tokens,
1462
+ groups
1463
+ };
1464
+ }
1465
+ /** `$description`, `$deprecated`, `$extensions` and `$type` shapes, shared by tokens and groups. */
1466
+ function checkMeta(node, fail) {
1467
+ if (node.$type !== void 0 && typeof node.$type !== "string") throw fail("$type must be a string");
1468
+ if (node.$description !== void 0 && typeof node.$description !== "string") throw fail("$description must be a string");
1469
+ if (node.$deprecated !== void 0 && typeof node.$deprecated !== "boolean" && typeof node.$deprecated !== "string") throw fail("$deprecated must be true, false or a string");
1470
+ if (node.$extensions !== void 0 && !isPlainObject(node.$extensions)) throw fail("$extensions must be an object");
1471
+ }
1472
+ /**
1473
+ * Applies `$extends` (format §6.4): the target group's content, deep-merged
1474
+ * under the local content, where a local token replaces an inherited one
1475
+ * whole. Targets are looked up in the document as written; tokens keep their
1476
+ * identity, so their origin and aliases stay what the author wrote.
1477
+ */
1478
+ function resolveExtends(tree, file, session) {
1479
+ const done = /* @__PURE__ */ new WeakMap();
1480
+ const active = [];
1481
+ const group = (node, segments) => {
1482
+ const memo = done.get(node);
1483
+ if (memo) return memo;
1484
+ const key = segments.join(".");
1485
+ const fail = (message) => configError(session.origin.get(node) ?? file, key, message);
1486
+ if (active.includes(node)) throw fail("circular $extends");
1487
+ active.push(node);
1488
+ let out = {};
1489
+ if (node.$extends !== void 0) {
1490
+ const path = groupReference(node.$extends);
1491
+ if (!path) throw fail("$extends takes a group reference: \"{group}\" or \"#/group\"");
1492
+ let target = tree;
1493
+ for (const [i, name] of path.entries()) {
1494
+ if (!isPlainObject(target) || isToken(target) || !Object.hasOwn(target, name)) throw fail(`$extends: \`${path.slice(0, i + 1).join(".")}\` is not a group`);
1495
+ target = target[name];
1496
+ }
1497
+ if (!isGroup(target)) throw fail(`$extends: \`${path.join(".")}\` is a token, not a group`);
1498
+ out = { ...group(target, path) };
1499
+ }
1500
+ for (const [name, child] of Object.entries(node)) {
1501
+ if (name === "$extends") continue;
1502
+ if (!name.startsWith("$") && isGroup(child)) {
1503
+ const resolved = group(child, [...segments, name]);
1504
+ const inherited = out[name];
1505
+ out[name] = isGroup(inherited) ? mergeGroups(inherited, resolved) : resolved;
1506
+ } else out[name] = child;
1507
+ }
1508
+ active.pop();
1509
+ const origin = session.origin.get(node);
1510
+ if (origin !== void 0) session.origin.set(out, origin);
1511
+ done.set(node, out);
1512
+ return out;
1513
+ };
1514
+ const mergeGroups = (base, over) => {
1515
+ const out = { ...base };
1516
+ for (const [name, child] of Object.entries(over)) {
1517
+ const prev = out[name];
1518
+ out[name] = !name.startsWith("$") && isGroup(prev) && isGroup(child) ? mergeGroups(prev, child) : child;
1519
+ }
1520
+ return out;
1521
+ };
1522
+ return group(tree, []);
1523
+ }
1524
+ /** `{a.b}` or `#/a/b` → `["a", "b"]`. */
1525
+ function groupReference(ref) {
1526
+ if (typeof ref !== "string") return void 0;
1527
+ const alias = ALIAS.exec(ref);
1528
+ if (alias) return alias[1].split(".");
1529
+ if (ref.startsWith("#/")) return decodePointer(ref);
1530
+ }
1531
+ /** RFC 6901 segments of `#/a/b~1c`, or `undefined` for a malformed pointer. */
1532
+ function decodePointer(ref) {
1533
+ if (!ref.startsWith("#/")) return void 0;
1534
+ const segments = [];
1535
+ for (const raw of ref.slice(2).split("/")) {
1536
+ if (/~(?![01])/.test(raw)) return void 0;
1537
+ segments.push(raw.replaceAll("~1", "/").replaceAll("~0", "~"));
1538
+ }
1539
+ return segments;
1540
+ }
1541
+ /** Converts every token of one resolved document (one resolver permutation). */
1542
+ function evaluate(tree, file, session) {
1543
+ return new Converter(buildIndex(tree, file, session), session).all();
1544
+ }
1545
+ /** RFC 6901 array index: no sign, no leading zero. */
1546
+ const INDEX = /^(?:0|[1-9]\d*)$/;
1547
+ var Converter = class {
1548
+ #memo = /* @__PURE__ */ new Map();
1549
+ #stack = [];
1550
+ #types = /* @__PURE__ */ new Map();
1551
+ #typing = [];
1552
+ #places = /* @__PURE__ */ new Map();
1553
+ /** Nesting of pointer resolution: a pointer chain that loops never reaches a value. */
1554
+ #depth = 0;
1555
+ index;
1556
+ session;
1557
+ constructor(index, session) {
1558
+ this.index = index;
1559
+ this.session = session;
1560
+ }
1561
+ all() {
1562
+ const out = /* @__PURE__ */ new Map();
1563
+ for (const entry of this.index.tokens.values()) {
1564
+ const place = this.place(entry);
1565
+ if (place.kind === "excluded") continue;
1566
+ out.set(entry.key, {
1567
+ entry,
1568
+ type: this.type(entry),
1569
+ place,
1570
+ value: this.token(entry)
1571
+ });
1572
+ }
1573
+ return out;
1574
+ }
1575
+ fail(entry, sub, message, hint) {
1576
+ const at = sub.length > 0 ? `${entry.key} (${sub.join(".")})` : entry.key;
1577
+ return configError(entry.file, at, message, hint);
1578
+ }
1579
+ /** Format §5.2.2: explicit `$type`, else the referenced token's type, else the closest group's. */
1580
+ type(entry) {
1581
+ const known = this.#types.get(entry);
1582
+ if (known !== void 0) return known;
1583
+ const at = this.#typing.indexOf(entry);
1584
+ if (at !== -1) throw this.cycle(this.#typing.slice(at), entry);
1585
+ this.#typing.push(entry);
1586
+ try {
1587
+ let type = entry.node.$type;
1588
+ if (type === void 0) {
1589
+ const target = this.aliasTarget(entry);
1590
+ type = target ? this.type(target) : entry.groupType;
1591
+ }
1592
+ if (type === void 0) throw this.fail(entry, [], "has no $type", "Set $type on the token or on a parent group.");
1593
+ if (typeof type !== "string" || !TYPES.has(type)) throw this.fail(entry, [], `${describe(type)} is not a DTCG type`, `Types: ${[...TYPES].join(", ")}. Leave unsupported groups out with dtcg(…, { scales: { "group": false } }).`);
1594
+ this.#types.set(entry, type);
1595
+ return type;
1596
+ } finally {
1597
+ this.#typing.pop();
1598
+ }
1599
+ }
1600
+ /** The token a whole-token alias or a whole-token `$ref` names. */
1601
+ aliasTarget(entry) {
1602
+ const { $value, $ref } = entry.node;
1603
+ if (typeof $value === "string") {
1604
+ const alias = ALIAS.exec($value);
1605
+ return alias ? this.lookup(alias[1], entry, []) : void 0;
1606
+ }
1607
+ if (typeof $ref === "string") {
1608
+ const target = this.pointer($ref, entry, []);
1609
+ return target.kind === "token" ? target.entry : void 0;
1610
+ }
1611
+ }
1612
+ place(entry) {
1613
+ let place = this.#places.get(entry);
1614
+ if (!place) {
1615
+ place = this.computePlace(entry);
1616
+ this.#places.set(entry, place);
1617
+ }
1618
+ return place;
1619
+ }
1620
+ computePlace(entry) {
1621
+ const segments = entry.segments;
1622
+ const { scales } = this.session.options;
1623
+ for (let i = segments.length; i >= 1; i--) {
1624
+ const path = segments.slice(0, i).join(".");
1625
+ const target = scales.get(path);
1626
+ if (target === void 0) continue;
1627
+ this.session.usedScales.add(path);
1628
+ if (target === false) return { kind: "excluded" };
1629
+ const type = this.type(entry);
1630
+ if (target === "textStyles" !== (type === "typography")) throw this.fail(entry, [], type === "typography" ? `options.scales places it in ${target}, but a typography token is a text style` : `options.scales places it in textStyles, but it is a ${type} token, not typography`);
1631
+ const rest = i === segments.length ? segments.slice(-1) : segments.slice(i);
1632
+ const place = this.target(entry, target, rest);
1633
+ if (place) return place;
1634
+ }
1635
+ const type = this.type(entry);
1636
+ for (const depth of [2, 1]) {
1637
+ if (segments.length <= depth) continue;
1638
+ const name = segments.slice(0, depth).map((segment) => segment.toLowerCase().replace(/[^a-z0-9]/g, "")).join(".");
1639
+ const names = depth === 2 ? COMPOUND_NAMES : GROUP_NAMES;
1640
+ const target = Object.hasOwn(names, name) ? names[name] : void 0;
1641
+ if (target && ACCEPTS[target].includes(type)) {
1642
+ const place = this.target(entry, target, segments.slice(depth));
1643
+ if (place) return place;
1644
+ }
1645
+ }
1646
+ const target = Object.hasOwn(TYPE_TARGETS, type) ? TYPE_TARGETS[type] : void 0;
1647
+ return target && this.target(entry, target, segments) || { kind: "none" };
1648
+ }
1649
+ /** The vicss path of `rest` under `target`; `undefined` when nothing is left to name the token. */
1650
+ target(entry, target, rest) {
1651
+ const names = [];
1652
+ for (const name of rest) {
1653
+ if (name === "$root" || name === "DEFAULT") continue;
1654
+ const clean = name.split(/[^A-Za-z0-9-]+/).filter(Boolean).join("-");
1655
+ if (clean === "") throw this.fail(entry, [], `\`${name}\` cannot become part of a CSS custom property name`, "vicss names keep letters, digits and `-`; rename the token or group.");
1656
+ names.push(clean);
1657
+ }
1658
+ if (names.length === 0) return void 0;
1659
+ const path = names.join(".");
1660
+ return target === "textStyles" ? {
1661
+ kind: "text",
1662
+ name: path
1663
+ } : {
1664
+ kind: "scale",
1665
+ scale: target,
1666
+ path
1667
+ };
1668
+ }
1669
+ token(entry) {
1670
+ const done = this.#memo.get(entry);
1671
+ if (done !== void 0) return done;
1672
+ const at = this.#stack.indexOf(entry);
1673
+ if (at !== -1) throw this.cycle(this.#stack.slice(at), entry);
1674
+ this.#stack.push(entry);
1675
+ try {
1676
+ const value = this.compute(entry);
1677
+ this.#memo.set(entry, value);
1678
+ return value;
1679
+ } finally {
1680
+ this.#stack.pop();
1681
+ }
1682
+ }
1683
+ cycle(chain, entry) {
1684
+ return this.fail(entry, [], `alias cycle: ${[...chain, entry].map((e) => e.key).join(" → ")}`);
1685
+ }
1686
+ compute(entry) {
1687
+ const type = this.type(entry);
1688
+ const ext = isPlainObject(entry.node.$extensions) ? entry.node.$extensions[EXTENSION] : void 0;
1689
+ if (isPlainObject(ext) && typeof ext.value === "string") {
1690
+ if (type === "typography") throw this.fail(entry, [`$extensions.${EXTENSION}`], "a typography token is a text style, not one CSS value");
1691
+ return this.rewrite(ext.value, entry);
1692
+ }
1693
+ return this.value(rawOf(entry), type, entry, []);
1694
+ }
1695
+ /**
1696
+ * A token value or a composite's sub-value (format §9): a literal of `type`,
1697
+ * or a reference to a token of that type.
1698
+ */
1699
+ value(raw, type, entry, sub) {
1700
+ for (let hops = 0; isRef(raw); hops++) {
1701
+ if (hops > 64) throw this.fail(entry, sub, "circular $ref");
1702
+ const target = this.pointer(raw.$ref, entry, sub);
1703
+ if (target.kind === "token") return this.alias(target.entry, type, entry, sub);
1704
+ raw = target.value;
1705
+ }
1706
+ if (typeof raw === "string" && ALIAS.test(raw)) return this.alias(this.lookup(ALIAS.exec(raw)[1], entry, sub), type, entry, sub);
1707
+ switch (type) {
1708
+ case "color": return this.color(raw, entry, sub);
1709
+ case "dimension": return this.measure(raw, entry, sub, "dimension", ["px", "rem"]);
1710
+ case "duration": return this.measure(raw, entry, sub, "duration", ["ms", "s"]);
1711
+ case "cubicBezier": return this.cubicBezier(raw, entry, sub);
1712
+ case "fontFamily": return this.fontFamily(raw, entry, sub);
1713
+ case "fontWeight": return this.fontWeight(raw, entry, sub);
1714
+ case "number": return num(this.number(raw, entry, sub));
1715
+ case "strokeStyle": return this.strokeStyle(raw, entry, sub);
1716
+ case "border": return this.border(raw, entry, sub);
1717
+ case "transition": return this.transition(raw, entry, sub);
1718
+ case "shadow": return this.shadow(raw, entry, sub);
1719
+ case "gradient": return this.gradient(raw, entry, sub);
1720
+ default: return this.typography(raw, entry, sub);
1721
+ }
1722
+ }
1723
+ /** A sub-value that is one CSS value: every type but typography. */
1724
+ css(raw, type, entry, sub) {
1725
+ const value = this.value(raw, type, entry, sub);
1726
+ if (typeof value === "string" || value instanceof Skip) return value;
1727
+ throw this.fail(entry, sub, "expected one CSS value, not a text style");
1728
+ }
1729
+ /** An alias keeps the reference when its target has a scale, and inlines the value otherwise. */
1730
+ alias(target, type, entry, sub) {
1731
+ const actual = this.type(target);
1732
+ if (actual !== type) throw this.fail(entry, sub, `\`{${target.key}}\` is a ${actual} token, where a ${type} is expected`);
1733
+ const value = this.token(target);
1734
+ if (value instanceof Skip) return new Skip(`uses ${target.key}: ${value.reason}`);
1735
+ if (typeof value !== "string") return { ...value };
1736
+ const place = this.place(target);
1737
+ return place.kind === "scale" ? `{${place.scale}.${place.path}}` : value;
1738
+ }
1739
+ lookup(path, entry, sub) {
1740
+ const target = this.index.tokens.get(path);
1741
+ if (target) return target;
1742
+ const group = this.index.groups.get(path);
1743
+ if (group) throw this.fail(entry, sub, `\`{${path}}\` names a group, not a token`, Object.hasOwn(group, "$root") ? `Write {${path}.$root} for the group's own token.` : void 0);
1744
+ const segments = path.split(".");
1745
+ let prefix = "";
1746
+ for (let i = segments.length - 1; i > 0; i--) {
1747
+ const candidate = segments.slice(0, i).join(".");
1748
+ if (this.index.groups.has(candidate)) {
1749
+ prefix = `${candidate}.`;
1750
+ break;
1751
+ }
1752
+ }
1753
+ const close = [...this.index.tokens.keys()].filter((k) => k.startsWith(prefix)).slice(0, 5);
1754
+ throw this.fail(entry, sub, `\`{${path}}\` names no token`, close.length > 0 ? `Tokens nearby: ${close.map((k) => `{${k}}`).join(", ")}.` : void 0);
1755
+ }
1756
+ /**
1757
+ * Resolves a same-document JSON pointer (format §7.1.2): a whole token (or
1758
+ * its `$value`) is an alias; a part of a value is that literal part, found by
1759
+ * following the aliases on the way.
1760
+ */
1761
+ pointer(ref, entry, sub) {
1762
+ if (typeof ref !== "string") throw this.fail(entry, sub, "$ref must be a JSON pointer string");
1763
+ const segments = decodePointer(ref);
1764
+ if (!segments) throw this.fail(entry, sub, `\`${ref}\` is not a JSON pointer into this document`, "A token file references its own tokens: \"#/color/blue/$value\".");
1765
+ if (++this.#depth > 64) throw this.fail(entry, sub, `\`${ref}\`: circular $ref`);
1766
+ try {
1767
+ let node = this.index.root;
1768
+ let i = 0;
1769
+ while (i < segments.length && isGroup(node)) {
1770
+ const name = segments[i];
1771
+ if (name.startsWith("$") && name !== "$root") throw this.fail(entry, sub, `\`${ref}\` points into ${name}, not to a token value`);
1772
+ if (!Object.hasOwn(node, name)) throw this.fail(entry, sub, `\`${ref}\` points to nothing: there is no \`${name}\``);
1773
+ node = node[name];
1774
+ i++;
1775
+ }
1776
+ if (!isPlainObject(node) || !isToken(node)) throw this.fail(entry, sub, `\`${ref}\` points to a group, not to a token value`);
1777
+ const target = this.index.tokens.get(segments.slice(0, i).join("."));
1778
+ if (segments[i] !== "$value") throw this.fail(entry, sub, i === segments.length ? `\`${ref}\` points to a token object, not to a value` : `\`${ref}\` points to ${segments[i]}; only $value is a value`, `Point to the value: "#/${segments.slice(0, i).map((n) => n.replaceAll("~", "~0").replaceAll("/", "~1")).join("/")}/$value".`);
1779
+ if (++i === segments.length) return {
1780
+ kind: "token",
1781
+ entry: target
1782
+ };
1783
+ let value = this.raw(target, entry, sub);
1784
+ for (; i < segments.length; i++) {
1785
+ value = this.follow(value, entry, sub);
1786
+ const name = segments[i];
1787
+ if (Array.isArray(value) && INDEX.test(name) && Number(name) < value.length) value = value[Number(name)];
1788
+ else if (isPlainObject(value) && Object.hasOwn(value, name)) value = value[name];
1789
+ else throw this.fail(entry, sub, `\`${ref}\` points to nothing: there is no \`${name}\``);
1790
+ }
1791
+ return {
1792
+ kind: "value",
1793
+ value
1794
+ };
1795
+ } finally {
1796
+ this.#depth--;
1797
+ }
1798
+ }
1799
+ /** A token's literal `$value`, through whole-token aliases and pointers. */
1800
+ raw(target, entry, sub) {
1801
+ return this.follow(rawOf(target), entry, sub);
1802
+ }
1803
+ /** Follows `$ref` objects and whole-token aliases until a literal. */
1804
+ follow(value, entry, sub) {
1805
+ for (let hops = 0;; hops++) {
1806
+ if (hops > 64) throw this.fail(entry, sub, "circular $ref");
1807
+ if (isRef(value)) {
1808
+ const target = this.pointer(value.$ref, entry, sub);
1809
+ value = target.kind === "token" ? rawOf(target.entry) : target.value;
1810
+ } else if (typeof value === "string" && ALIAS.test(value)) value = rawOf(this.lookup(ALIAS.exec(value)[1], entry, sub));
1811
+ else return value;
1812
+ }
1813
+ }
1814
+ /** A primitive part (a component, a unit): pointers allowed, aliases not. */
1815
+ part(raw, entry, sub) {
1816
+ for (let hops = 0; isRef(raw); hops++) {
1817
+ if (hops > 64) throw this.fail(entry, sub, "circular $ref");
1818
+ const target = this.pointer(raw.$ref, entry, sub);
1819
+ raw = target.kind === "token" ? this.raw(target.entry, entry, sub) : target.value;
1820
+ }
1821
+ if (typeof raw === "string" && ALIAS.test(raw)) throw this.fail(entry, sub, `\`${raw}\`: a curly-brace alias names a whole token, not part of a value`, "Reference part of a value with { \"$ref\": \"#/path/to/token/$value/…\" }.");
1822
+ return raw;
1823
+ }
1824
+ /** An object with exactly `required` (plus `optional`) keys, pointers resolved. */
1825
+ object(raw, entry, sub, what, required, optional = []) {
1826
+ const value = this.part(raw, entry, sub);
1827
+ if (!isPlainObject(value)) throw this.fail(entry, sub, `expected ${what}`);
1828
+ for (const key of Object.keys(value)) if (!required.includes(key) && !optional.includes(key)) throw this.fail(entry, sub, `unknown property \`${key}\` in ${what}`);
1829
+ for (const key of required) if (!Object.hasOwn(value, key)) throw this.fail(entry, sub, `${what} lacks \`${key}\``);
1830
+ return value;
1831
+ }
1832
+ number(raw, entry, sub) {
1833
+ const value = this.part(raw, entry, sub);
1834
+ if (typeof value !== "number" || !Number.isFinite(value)) throw this.fail(entry, sub, `expected a number, got ${describe(value)}`);
1835
+ return value;
1836
+ }
1837
+ color(raw, entry, sub) {
1838
+ const c = this.object(raw, entry, sub, "a color { colorSpace, components, alpha?, hex? }", ["colorSpace", "components"], ["alpha", "hex"]);
1839
+ const space = this.part(c.colorSpace, entry, sub);
1840
+ const spec = typeof space === "string" && Object.hasOwn(SPACES, space) ? SPACES[space] : void 0;
1841
+ if (!spec || typeof space !== "string") throw this.fail(entry, [...sub, "colorSpace"], `${describe(space)} is not a DTCG color space`, `Color spaces: ${Object.keys(SPACES).join(", ")}.`);
1842
+ const list = this.part(c.components, entry, sub);
1843
+ if (!Array.isArray(list) || list.length !== 3) throw this.fail(entry, [...sub, "components"], `expected 3 components, got ${describe(list)}`);
1844
+ const components = list.map((item, i) => {
1845
+ const at = [...sub, `components.${i}`];
1846
+ const value = this.part(item, entry, at);
1847
+ if (value === "none") return value;
1848
+ const [min, max, open] = spec.ranges[i];
1849
+ if (typeof value !== "number" || !Number.isFinite(value) || value < min || (open ? value >= max : value > max)) throw this.fail(entry, at, `expected "none" or a number in [${min}, ${max}${open ? ")" : "]"} for ${space}, got ${describe(value)}`);
1850
+ return value;
1851
+ });
1852
+ let alpha = 1;
1853
+ if (c.alpha !== void 0) {
1854
+ alpha = this.number(c.alpha, entry, [...sub, "alpha"]);
1855
+ if (alpha < 0 || alpha > 1) throw this.fail(entry, [...sub, "alpha"], "alpha is in [0, 1]");
1856
+ }
1857
+ const hex = c.hex === void 0 ? void 0 : this.part(c.hex, entry, sub);
1858
+ if (hex !== void 0 && (typeof hex !== "string" || !/^#[0-9a-fA-F]{6}$/.test(hex))) throw this.fail(entry, [...sub, "hex"], `expected a 6-digit hex color, got ${describe(hex)}`);
1859
+ if (space === "srgb" && typeof hex === "string" && !components.includes("none")) {
1860
+ const lower = hex.toLowerCase();
1861
+ return alpha < 1 ? lower + Math.round(alpha * 255).toString(16).padStart(2, "0") : lower;
1862
+ }
1863
+ const parts = components.map((v, i) => v === "none" ? v : spec.percent?.includes(i) ? `${num(v)}%` : num(v));
1864
+ const slash = alpha < 1 ? ` / ${num(alpha)}` : "";
1865
+ return spec.fn ? `${space}(${parts.join(" ")}${slash})` : `color(${space} ${parts.join(" ")}${slash})`;
1866
+ }
1867
+ measure(raw, entry, sub, what, units) {
1868
+ const d = this.object(raw, entry, sub, `a ${what} { value, unit }`, ["value", "unit"]);
1869
+ const value = this.number(d.value, entry, [...sub, "value"]);
1870
+ const unit = this.part(d.unit, entry, sub);
1871
+ if (typeof unit !== "string" || !units.includes(unit)) throw this.fail(entry, [...sub, "unit"], `\`${String(unit)}\` is not a ${what} unit: DTCG allows ${units.join(" and ")}`, `Write any other CSS value with "$extensions": { "${EXTENSION}": { "value": "…" } }.`);
1872
+ return `${num(value)}${unit}`;
1873
+ }
1874
+ cubicBezier(raw, entry, sub) {
1875
+ const list = this.part(raw, entry, sub);
1876
+ if (!Array.isArray(list) || list.length !== 4) throw this.fail(entry, sub, `a cubicBezier is [x1, y1, x2, y2], got ${describe(list)}`);
1877
+ const [x1, y1, x2, y2] = list.map((v, i) => this.number(v, entry, [...sub, String(i)]));
1878
+ if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) throw this.fail(entry, sub, "the x coordinates of a cubicBezier are in [0, 1]");
1879
+ return `cubic-bezier(${num(x1)}, ${num(y1)}, ${num(x2)}, ${num(y2)})`;
1880
+ }
1881
+ fontFamily(raw, entry, sub) {
1882
+ const value = this.part(raw, entry, sub);
1883
+ const names = Array.isArray(value) ? value.map((v) => this.part(v, entry, sub)) : [value];
1884
+ if (names.length === 0 || names.some((n) => typeof n !== "string" || n.trim() === "")) throw this.fail(entry, sub, `a fontFamily is a font name or an array of them, got ${describe(value)}`);
1885
+ return names.flatMap((n) => n.split(",")).map((n) => fontName(n.trim().replace(/^(["'])(.*)\1$/, "$2"))).join(", ");
1886
+ }
1887
+ fontWeight(raw, entry, sub) {
1888
+ const value = this.part(raw, entry, sub);
1889
+ if (typeof value === "string" && Object.hasOwn(WEIGHTS, value)) return String(WEIGHTS[value]);
1890
+ if (typeof value !== "number" || !(value >= 1 && value <= 1e3)) throw this.fail(entry, sub, `a fontWeight is a number in [1, 1000] or a weight name, got ${describe(value)}`, `Names: ${Object.keys(WEIGHTS).join(", ")}.`);
1891
+ return num(value);
1892
+ }
1893
+ strokeStyle(raw, entry, sub) {
1894
+ const value = this.part(raw, entry, sub);
1895
+ if (typeof value === "string") {
1896
+ if (STROKES.has(value)) return value;
1897
+ throw this.fail(entry, sub, `\`${value}\` is not a stroke style`, `Styles: ${[...STROKES].join(", ")}.`);
1898
+ }
1899
+ const s = this.object(value, entry, sub, "a strokeStyle { dashArray, lineCap }", ["dashArray", "lineCap"]);
1900
+ const dashes = this.part(s.dashArray, entry, sub);
1901
+ if (!Array.isArray(dashes) || dashes.length === 0) throw this.fail(entry, [...sub, "dashArray"], "expected a non-empty array of dimensions");
1902
+ for (const [i, dash] of dashes.entries()) this.value(dash, "dimension", entry, [...sub, `dashArray.${i}`]);
1903
+ const cap = this.part(s.lineCap, entry, sub);
1904
+ if (cap !== "round" && cap !== "butt" && cap !== "square") throw this.fail(entry, [...sub, "lineCap"], "expected \"round\", \"butt\" or \"square\"");
1905
+ return new Skip("a dash pattern, which no CSS line style draws");
1906
+ }
1907
+ border(raw, entry, sub) {
1908
+ const b = this.object(raw, entry, sub, "a border { color, width, style }", [
1909
+ "color",
1910
+ "width",
1911
+ "style"
1912
+ ]);
1913
+ return joined([
1914
+ this.css(b.width, "dimension", entry, [...sub, "width"]),
1915
+ this.css(b.style, "strokeStyle", entry, [...sub, "style"]),
1916
+ this.css(b.color, "color", entry, [...sub, "color"])
1917
+ ], " ");
1918
+ }
1919
+ /** `<duration> <timing> <delay>`: a `transition` value for every property. */
1920
+ transition(raw, entry, sub) {
1921
+ const t = this.object(raw, entry, sub, "a transition { duration, delay, timingFunction }", [
1922
+ "duration",
1923
+ "delay",
1924
+ "timingFunction"
1925
+ ]);
1926
+ return joined([
1927
+ this.css(t.duration, "duration", entry, [...sub, "duration"]),
1928
+ this.css(t.timingFunction, "cubicBezier", entry, [...sub, "timingFunction"]),
1929
+ this.css(t.delay, "duration", entry, [...sub, "delay"])
1930
+ ], " ");
1931
+ }
1932
+ shadow(raw, entry, sub) {
1933
+ const value = this.part(raw, entry, sub);
1934
+ if (!Array.isArray(value)) return this.shadowLayer(value, entry, sub);
1935
+ if (value.length === 0) throw this.fail(entry, sub, "a shadow array needs at least one shadow");
1936
+ return joined(value.map((layer, i) => {
1937
+ const at = [...sub, String(i)];
1938
+ return isRef(layer) || typeof layer === "string" && ALIAS.test(layer) ? this.css(layer, "shadow", entry, at) : this.shadowLayer(layer, entry, at);
1939
+ }), ", ");
1940
+ }
1941
+ shadowLayer(raw, entry, sub) {
1942
+ const s = this.object(raw, entry, sub, "a shadow { color, offsetX, offsetY, blur, spread, inset? }", [
1943
+ "color",
1944
+ "offsetX",
1945
+ "offsetY",
1946
+ "blur",
1947
+ "spread"
1948
+ ], ["inset"]);
1949
+ const inset = s.inset === void 0 ? false : this.part(s.inset, entry, sub);
1950
+ if (typeof inset !== "boolean") throw this.fail(entry, [...sub, "inset"], "expected a boolean");
1951
+ const layer = joined([...[
1952
+ "offsetX",
1953
+ "offsetY",
1954
+ "blur",
1955
+ "spread"
1956
+ ].map((key) => this.css(s[key], "dimension", entry, [...sub, key])), this.css(s.color, "color", entry, [...sub, "color"])], " ");
1957
+ return inset && typeof layer === "string" ? `inset ${layer}` : layer;
1958
+ }
1959
+ gradient(raw, entry, sub) {
1960
+ const stops = this.part(raw, entry, sub);
1961
+ if (!Array.isArray(stops) || stops.length === 0) throw this.fail(entry, sub, "a gradient is a non-empty array of stops { color, position }");
1962
+ for (const [i, stop] of stops.entries()) {
1963
+ const at = [...sub, String(i)];
1964
+ if (isRef(stop) || typeof stop === "string" && ALIAS.test(stop)) {
1965
+ this.value(stop, "gradient", entry, at);
1966
+ continue;
1967
+ }
1968
+ const s = this.object(stop, entry, at, "a gradient stop { color, position }", ["color", "position"]);
1969
+ this.value(s.color, "color", entry, [...at, "color"]);
1970
+ this.value(s.position, "number", entry, [...at, "position"]);
1971
+ }
1972
+ return new Skip("a gradient, and DTCG gradients carry no shape or direction");
1973
+ }
1974
+ typography(raw, entry, sub) {
1975
+ const t = this.object(raw, entry, sub, "a typography { fontFamily, fontSize, fontWeight, letterSpacing, lineHeight }", [
1976
+ "fontFamily",
1977
+ "fontSize",
1978
+ "fontWeight",
1979
+ "letterSpacing",
1980
+ "lineHeight"
1981
+ ]);
1982
+ const field = (key, type) => {
1983
+ const value = this.value(t[key], type, entry, [...sub, key]);
1984
+ if (typeof value !== "string") throw this.fail(entry, [...sub, key], "expected a CSS value");
1985
+ return (type === "number" || type === "fontWeight") && /^-?[\d.]+$/.test(value) ? Number(value) : value;
1986
+ };
1987
+ const family = String(field("fontFamily", "fontFamily"));
1988
+ return {
1989
+ fontFamily: WHOLE_REF.test(family) || !/^-?[A-Za-z_][\w-]*$/.test(family) || GENERIC_FAMILIES.has(family.toLowerCase()) ? family : `"${family}"`,
1990
+ fontSize: field("fontSize", "dimension"),
1991
+ fontWeight: field("fontWeight", "fontWeight"),
1992
+ lineHeight: field("lineHeight", "number"),
1993
+ letterSpacing: field("letterSpacing", "dimension")
1994
+ };
1995
+ }
1996
+ /** The extension's CSS, with DTCG aliases turned into vicss references or values. */
1997
+ rewrite(css, entry) {
1998
+ const sub = [`$extensions.${EXTENSION}`];
1999
+ let skip;
2000
+ const out = css.replace(ALIASES, (match, path) => {
2001
+ const target = this.lookup(path, entry, sub);
2002
+ const value = this.token(target);
2003
+ if (value instanceof Skip) {
2004
+ skip ??= new Skip(`uses ${target.key}: ${value.reason}`);
2005
+ return match;
2006
+ }
2007
+ if (typeof value !== "string") throw this.fail(entry, sub, `\`${match}\` is typography, which is not one CSS value`);
2008
+ const place = this.place(target);
2009
+ return place.kind === "scale" ? `{${place.scale}.${place.path}}` : value;
2010
+ });
2011
+ if (skip) return skip;
2012
+ const bad = malformed(out.replace(VICSS_REFS, "0"));
2013
+ if (bad) throw this.fail(entry, sub, `\`${css}\` is not a CSS value: ${bad}`);
2014
+ return out.trim();
2015
+ }
2016
+ };
2017
+ /** What a token holds: its `$value`, or its whole-token `$ref` as a reference object. */
2018
+ function rawOf(entry) {
2019
+ return Object.hasOwn(entry.node, "$ref") ? { $ref: entry.node.$ref } : entry.node.$value;
2020
+ }
2021
+ /** Sub-values joined into one CSS value, unless one of them has none. */
2022
+ function joined(values, separator) {
2023
+ const out = [];
2024
+ for (const value of values) {
2025
+ if (value instanceof Skip) return value;
2026
+ out.push(value);
2027
+ }
2028
+ return out.join(separator);
2029
+ }
2030
+ function isRef(value) {
2031
+ return isPlainObject(value) && Object.hasOwn(value, "$ref");
2032
+ }
2033
+ const UNIT = [0, 1];
2034
+ const HUE = [
2035
+ 0,
2036
+ 360,
2037
+ true
2038
+ ];
2039
+ const PERCENT = [0, 100];
2040
+ const ANY = [-Infinity, Infinity];
2041
+ const CHROMA = [0, Infinity];
2042
+ /**
2043
+ * Color module §4.2. `fn` spaces have their own CSS function; the rest are
2044
+ * predefined `color()` spaces. HSL and HWB take percentages in CSS where DTCG
2045
+ * writes plain numbers.
2046
+ */
2047
+ const SPACES = {
2048
+ srgb: { ranges: [
2049
+ UNIT,
2050
+ UNIT,
2051
+ UNIT
2052
+ ] },
2053
+ "srgb-linear": { ranges: [
2054
+ UNIT,
2055
+ UNIT,
2056
+ UNIT
2057
+ ] },
2058
+ hsl: {
2059
+ ranges: [
2060
+ HUE,
2061
+ PERCENT,
2062
+ PERCENT
2063
+ ],
2064
+ fn: true,
2065
+ percent: [1, 2]
2066
+ },
2067
+ hwb: {
2068
+ ranges: [
2069
+ HUE,
2070
+ PERCENT,
2071
+ PERCENT
2072
+ ],
2073
+ fn: true,
2074
+ percent: [1, 2]
2075
+ },
2076
+ lab: {
2077
+ ranges: [
2078
+ PERCENT,
2079
+ ANY,
2080
+ ANY
2081
+ ],
2082
+ fn: true
2083
+ },
2084
+ lch: {
2085
+ ranges: [
2086
+ PERCENT,
2087
+ CHROMA,
2088
+ HUE
2089
+ ],
2090
+ fn: true
2091
+ },
2092
+ oklab: {
2093
+ ranges: [
2094
+ UNIT,
2095
+ ANY,
2096
+ ANY
2097
+ ],
2098
+ fn: true
2099
+ },
2100
+ oklch: {
2101
+ ranges: [
2102
+ UNIT,
2103
+ CHROMA,
2104
+ HUE
2105
+ ],
2106
+ fn: true
2107
+ },
2108
+ "display-p3": { ranges: [
2109
+ UNIT,
2110
+ UNIT,
2111
+ UNIT
2112
+ ] },
2113
+ "a98-rgb": { ranges: [
2114
+ UNIT,
2115
+ UNIT,
2116
+ UNIT
2117
+ ] },
2118
+ "prophoto-rgb": { ranges: [
2119
+ UNIT,
2120
+ UNIT,
2121
+ UNIT
2122
+ ] },
2123
+ rec2020: { ranges: [
2124
+ UNIT,
2125
+ UNIT,
2126
+ UNIT
2127
+ ] },
2128
+ "xyz-d65": { ranges: [
2129
+ UNIT,
2130
+ UNIT,
2131
+ UNIT
2132
+ ] },
2133
+ "xyz-d50": { ranges: [
2134
+ UNIT,
2135
+ UNIT,
2136
+ UNIT
2137
+ ] }
2138
+ };
2139
+ /** Format §8.4. */
2140
+ const WEIGHTS = {
2141
+ thin: 100,
2142
+ hairline: 100,
2143
+ "extra-light": 200,
2144
+ "ultra-light": 200,
2145
+ light: 300,
2146
+ normal: 400,
2147
+ regular: 400,
2148
+ book: 400,
2149
+ medium: 500,
2150
+ "semi-bold": 600,
2151
+ "demi-bold": 600,
2152
+ bold: 700,
2153
+ "extra-bold": 800,
2154
+ "ultra-bold": 800,
2155
+ black: 900,
2156
+ heavy: 900,
2157
+ "extra-black": 950,
2158
+ "ultra-black": 950
2159
+ };
2160
+ const STROKES = /* @__PURE__ */ new Set([
2161
+ "solid",
2162
+ "dashed",
2163
+ "dotted",
2164
+ "double",
2165
+ "groove",
2166
+ "ridge",
2167
+ "outset",
2168
+ "inset"
2169
+ ]);
2170
+ /**
2171
+ * Generic families must stay unquoted. Other names are quoted unless they are
2172
+ * one plain identifier: several words parse as one family unquoted too, but
2173
+ * quoting them is what every stack (Tailwind's included) does.
2174
+ */
2175
+ function fontName(name) {
2176
+ if (GENERIC_FAMILIES.has(name.toLowerCase())) return name;
2177
+ if (/^-?[A-Za-z_][A-Za-z0-9_-]*$/.test(name) && !CSS_WIDE.has(name.toLowerCase())) return name;
2178
+ return `"${name.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
2179
+ }
2180
+ /** Plain decimal, rounded past float noise from design tools (0.20000000298 → 0.2). */
2181
+ function num(n) {
2182
+ const rounded = Number(n.toFixed(6));
2183
+ return Object.is(rounded, -0) ? "0" : String(rounded);
2184
+ }
2185
+ function describe(value) {
2186
+ if (value === void 0) return "nothing";
2187
+ const json = JSON.stringify(value);
2188
+ return json.length > 60 ? `${json.slice(0, 57)}…` : json;
2189
+ }
2190
+ function isPlainObject(value) {
2191
+ if (typeof value !== "object" || value === null) return false;
2192
+ const proto = Object.getPrototypeOf(value);
2193
+ return proto === Object.prototype || proto === null;
2194
+ }
2195
+ function sameValue(a, b) {
2196
+ return typeof a === "string" || typeof b === "string" ? a === b : JSON.stringify(a) === JSON.stringify(b);
2197
+ }
2198
+ /** The vicss tokens a value references: `colors.bg` for `{colors.bg}`. */
2199
+ function referencesOf(value) {
2200
+ if (typeof value === "string") return [...value.matchAll(VICSS_REFS)].map(([ref]) => ref.slice(1, -1)).filter((id) => Object.hasOwn(SCALES, id.slice(0, id.indexOf("."))));
2201
+ return isPlainObject(value) ? Object.values(value).flatMap(referencesOf) : [];
2202
+ }
2203
+ const valuesOf = (item) => [item.base, ...item.modes.map(([, v]) => v)];
2204
+ /**
2205
+ * Base values plus the values that differ per mode. A token with a value per
2206
+ * mode is semantic; so is one that aliases a semantic token, since the core
2207
+ * lets raw tokens alias raw tokens only and an alias of a switching token must
2208
+ * switch with it.
2209
+ */
2210
+ function assemble(session, base, variants) {
2211
+ const items = [];
2212
+ for (const { entry, type, place, value } of base.values()) {
2213
+ if (place.kind === "none" || place.kind === "excluded") {
2214
+ if (place.kind === "none") session.unmapped.set(entry.key, type);
2215
+ continue;
2216
+ }
2217
+ if (value instanceof Skip) {
2218
+ session.skipped.set(entry.key, value.reason);
2219
+ continue;
2220
+ }
2221
+ const modes = [];
2222
+ for (const variant of variants) {
2223
+ const other = variant.outcomes.get(entry.key);
2224
+ if (!other || other.place.kind === "excluded") {
2225
+ session.partial.set(entry.key, `missing in ${variant.label}, which keeps the base value`);
2226
+ continue;
2227
+ }
2228
+ if (placeId(other.place) !== placeId(place)) throw configError(other.entry.file, entry.key, `changes type or place in ${variant.label} (${placeId(other.place)} instead of ${placeId(place)})`);
2229
+ if (other.value instanceof Skip) {
2230
+ session.partial.set(entry.key, `no CSS value in ${variant.label}, which keeps the base value`);
2231
+ continue;
2232
+ }
2233
+ if (!sameValue(other.value, value)) modes.push([variant.mode, other.value]);
2234
+ }
2235
+ items.push({
2236
+ entry,
2237
+ place,
2238
+ base: value,
2239
+ modes
2240
+ });
2241
+ }
2242
+ const added = /* @__PURE__ */ new Map();
2243
+ for (const variant of variants) for (const [key, outcome] of variant.outcomes) {
2244
+ if (base.has(key) || outcome.place.kind === "none") continue;
2245
+ const first = added.get(key)?.[0];
2246
+ if (first && placeId(first.outcome.place) !== placeId(outcome.place)) throw configError(outcome.entry.file, key, `changes type or place in ${variant.label} (${placeId(outcome.place)} instead of ${placeId(first.outcome.place)})`);
2247
+ push(added, key, {
2248
+ variant,
2249
+ outcome
2250
+ });
2251
+ }
2252
+ const addedAt = /* @__PURE__ */ new Map();
2253
+ for (const [key, list] of added) addedAt.set(placeId(list[0].outcome.place), key);
2254
+ const emitted = new Set(items.map((item) => placeId(item.place)));
2255
+ const kept = /* @__PURE__ */ new Set();
2256
+ for (let i = 0; i < items.length; i++) for (const id of valuesOf(items[i]).flatMap(referencesOf)) {
2257
+ const key = addedAt.get(id);
2258
+ if (key === void 0 || emitted.has(id)) continue;
2259
+ const defined = added.get(key).filter(({ outcome }) => !(outcome.value instanceof Skip));
2260
+ const first = defined[0];
2261
+ if (!first) continue;
2262
+ const value = first.outcome.value;
2263
+ items.push({
2264
+ entry: first.outcome.entry,
2265
+ place: first.outcome.place,
2266
+ base: value,
2267
+ modes: defined.slice(1).flatMap(({ variant, outcome }) => {
2268
+ const other = outcome.value;
2269
+ return sameValue(other, value) ? [] : [[variant.mode, other]];
2270
+ })
2271
+ });
2272
+ emitted.add(id);
2273
+ kept.add(key);
2274
+ }
2275
+ for (const [key, list] of added) if (!kept.has(key)) session.extra.set(key, `only in ${list.map(({ variant }) => variant.label).join(", ")}`);
2276
+ for (const item of items) {
2277
+ const missing = valuesOf(item).flatMap(referencesOf).find((id) => !emitted.has(id));
2278
+ if (missing === void 0) continue;
2279
+ const target = [base, ...variants.map((v) => v.outcomes)].flatMap((outcomes) => [...outcomes.values()]).find((o) => placeId(o.place) === missing);
2280
+ throw configError(item.entry.file, item.entry.key, `aliases ${target?.entry.key ?? missing} in a context, but it has no CSS value in the base context, so vicss leaves it out`, `Give it a CSS value with "$extensions": { "${EXTENSION}": { "value": "…" } }.`);
2281
+ }
2282
+ const modifierOf = new Map(variants.map((v) => [v.mode, v]));
2283
+ for (const item of items) {
2284
+ if (item.place.kind !== "scale") continue;
2285
+ if (item.modes.some(([mode, value], i) => item.modes.slice(i + 1).some(([other, v]) => modifierOf.get(mode)?.modifier !== modifierOf.get(other)?.modifier && !sameValue(value, v)))) session.overlap.set(item.entry.key, item.modes.map(([mode]) => `_${mode} from ${modifierOf.get(mode)?.label}`).join(", "));
2286
+ }
2287
+ const byId = /* @__PURE__ */ new Map();
2288
+ for (const item of items) if (item.place.kind === "scale") byId.set(`${item.place.scale}.${item.place.path}`, item);
2289
+ const semantic = /* @__PURE__ */ new Map();
2290
+ const isSemantic = (item) => {
2291
+ const known = semantic.get(item);
2292
+ if (known !== void 0) return known;
2293
+ semantic.set(item, false);
2294
+ let result = item.modes.length > 0;
2295
+ for (const value of [item.base, ...item.modes.map(([, v]) => v)]) {
2296
+ if (result) break;
2297
+ if (typeof value !== "string") continue;
2298
+ for (const [ref] of value.matchAll(VICSS_REFS)) {
2299
+ const target = byId.get(ref.slice(1, -1));
2300
+ if (target && isSemantic(target)) {
2301
+ result = true;
2302
+ break;
2303
+ }
2304
+ }
2305
+ }
2306
+ semantic.set(item, result);
2307
+ return result;
2308
+ };
2309
+ const raw = /* @__PURE__ */ new Map();
2310
+ const switching = /* @__PURE__ */ new Map();
2311
+ const textStyles = {};
2312
+ const visibility = {};
2313
+ const claimed = /* @__PURE__ */ new Map();
2314
+ for (const item of items) {
2315
+ const { entry, place } = item;
2316
+ const extensions = entry.node.$extensions;
2317
+ const hidden = isPlainObject(extensions) ? extensions[PRIVATE_EXTENSION] : void 0;
2318
+ if (hidden !== void 0) {
2319
+ if (typeof hidden !== "boolean" || place.kind !== "scale") throw configError(entry.file, entry.key, `$extensions["${PRIVATE_EXTENSION}"]: expected true or false on a token of a scale`);
2320
+ visibility[`${place.scale}.${place.path}`] = hidden ? "private" : "public";
2321
+ }
2322
+ const id = place.kind === "scale" ? `${place.scale}.${place.path}` : `textStyles.${place.name}`;
2323
+ const first = claimed.get(id);
2324
+ if (first) throw configError(entry.file, entry.key, `becomes ${id}, like ${first.key} in ${first.file}`, "Rename one of them, or place one group elsewhere with options.scales.");
2325
+ claimed.set(id, entry);
2326
+ if (place.kind === "text") {
2327
+ if (item.modes.length > 0) session.fixed.set(entry.key, `differs in ${item.modes.map(([m]) => `_${m}`).join(", ")}`);
2328
+ textStyles[place.name] = item.base;
2329
+ continue;
2330
+ }
2331
+ const base = item.base;
2332
+ if (!isSemantic(item)) {
2333
+ push(raw, place.scale, [place.path, base]);
2334
+ continue;
2335
+ }
2336
+ if (place.scale === "breakpoints" || place.scale === "containers") throw configError(entry.file, entry.key, `${place.scale} cannot change with a mode: media and container queries cannot read custom properties`);
2337
+ const value = item.modes.length === 0 ? base : Object.fromEntries([["base", base], ...item.modes.map(([mode, v]) => [`_${mode}`, v])]);
2338
+ push(switching, place.scale, [place.path, value]);
2339
+ }
2340
+ const out = {};
2341
+ if (raw.size > 0) {
2342
+ const tokens = {};
2343
+ for (const [scale, leaves] of raw) tokens[scale] = buildTree(leaves, false);
2344
+ out.tokens = tokens;
2345
+ }
2346
+ if (switching.size > 0) {
2347
+ const semanticTokens = {};
2348
+ for (const [scale, leaves] of switching) semanticTokens[scale] = buildTree(leaves, true);
2349
+ out.semanticTokens = semanticTokens;
2350
+ }
2351
+ if (Object.keys(textStyles).length > 0) out.textStyles = textStyles;
2352
+ if (Object.keys(visibility).length > 0) out.tokenVisibility = visibility;
2353
+ return out;
2354
+ }
2355
+ function placeId(place) {
2356
+ return place.kind === "scale" ? `${place.scale}.${place.path}` : place.kind === "text" ? `textStyles.${place.name}` : place.kind;
2357
+ }
2358
+ function push(map, key, value) {
2359
+ const list = map.get(key);
2360
+ if (list) list.push(value);
2361
+ else map.set(key, [value]);
2362
+ }
2363
+ /**
2364
+ * Nests dotted paths. A path that is also a group (a `$root` token next to
2365
+ * its siblings) goes under `DEFAULT`. In a semantic tree, a group whose only
2366
+ * child is `base` would read as a value per mode (`{ base }`), so that child
2367
+ * goes one level down, under `DEFAULT`, which names the same path.
2368
+ */
2369
+ function buildTree(leaves, semantic) {
2370
+ const groups = /* @__PURE__ */ new Set();
2371
+ for (const [path] of leaves) {
2372
+ const segments = path.split(".");
2373
+ for (let i = 1; i < segments.length; i++) groups.add(segments.slice(0, i).join("."));
2374
+ }
2375
+ const root = {};
2376
+ const values = /* @__PURE__ */ new WeakSet();
2377
+ for (const [path, value] of leaves) {
2378
+ const segments = path.split(".");
2379
+ if (groups.has(path)) segments.push("DEFAULT");
2380
+ let node = root;
2381
+ for (const name of segments.slice(0, -1)) node = node[name] ??= {};
2382
+ node[segments.at(-1)] = value;
2383
+ if (typeof value === "object" && value !== null) values.add(value);
2384
+ }
2385
+ if (semantic) {
2386
+ const fix = (node) => {
2387
+ for (const [name, child] of Object.entries(node)) {
2388
+ if (!isPlainObject(child) || values.has(child)) continue;
2389
+ const keys = Object.keys(child);
2390
+ if (keys.length === 1 && keys[0] === "base" && typeof child.base === "string") child.base = { DEFAULT: child.base };
2391
+ if (name !== "DEFAULT") fix(child);
2392
+ }
2393
+ };
2394
+ fix(root);
2395
+ }
2396
+ return root;
2397
+ }
2398
+ const ROOT_KEYS = /* @__PURE__ */ new Set([
2399
+ "$schema",
2400
+ "name",
2401
+ "version",
2402
+ "description",
2403
+ "sets",
2404
+ "modifiers",
2405
+ "resolutionOrder",
2406
+ "$defs"
2407
+ ]);
2408
+ const SET_KEYS = /* @__PURE__ */ new Set([
2409
+ "description",
2410
+ "sources",
2411
+ "$extensions"
2412
+ ]);
2413
+ const MODIFIER_KEYS = /* @__PURE__ */ new Set([
2414
+ "description",
2415
+ "contexts",
2416
+ "default",
2417
+ "$extensions"
2418
+ ]);
2419
+ function fromResolver(doc, file, dir, session) {
2420
+ const items = parseResolver(doc, file, dir, session);
2421
+ const plan = planModes(items, file, session.options);
2422
+ const merged = (input) => {
2423
+ let tree = {};
2424
+ for (const item of items) {
2425
+ const sources = item.contexts.get(item.kind === "set" ? "" : input.get(item.name));
2426
+ for (const source of sources) tree = mergeTrees(tree, source.tree, session);
2427
+ }
2428
+ return tree;
2429
+ };
2430
+ return assemble(session, evaluate(merged(plan.base), file, session), plan.variants.map((v) => {
2431
+ const input = new Map(plan.base).set(v.modifier, v.context);
2432
+ return {
2433
+ modifier: v.modifier,
2434
+ mode: v.mode,
2435
+ label: `${v.modifier}: ${v.context}`,
2436
+ outcomes: evaluate(merged(input), file, session)
2437
+ };
2438
+ }));
2439
+ }
2440
+ /** Resolver §6.2: a later source replaces a token whole and merges groups. */
2441
+ function mergeTrees(base, over, session) {
2442
+ const out = { ...base };
2443
+ for (const [name, child] of Object.entries(over)) {
2444
+ const prev = out[name];
2445
+ out[name] = !name.startsWith("$") && isGroup(prev) && isGroup(child) ? mergeTrees(prev, child, session) : child;
2446
+ }
2447
+ const origin = session.origin.get(over) ?? session.origin.get(base);
2448
+ if (origin !== void 0) session.origin.set(out, origin);
2449
+ return out;
2450
+ }
2451
+ function parseResolver(doc, file, dir, session) {
2452
+ const fail = (at, message, hint) => configError(file, at, message, hint);
2453
+ for (const key of Object.keys(doc)) if (!ROOT_KEYS.has(key)) throw fail("", `unknown resolver property \`${key}\``);
2454
+ if (doc.version !== "2025.10") throw fail("version", `expected "2025.10", got ${describe(doc.version)}`);
2455
+ for (const key of ["sets", "modifiers"]) if (doc[key] !== void 0 && !isPlainObject(doc[key])) throw fail(key, "expected an object");
2456
+ const sets = doc.sets ?? {};
2457
+ const modifiers = doc.modifiers ?? {};
2458
+ const order = doc.resolutionOrder;
2459
+ if (!Array.isArray(order) || order.length === 0) throw fail("resolutionOrder", "expected a non-empty array of sets and modifiers");
2460
+ const baseDir = () => dir ?? process.cwd();
2461
+ const checkKeys = (def, allowed, at) => {
2462
+ for (const key of Object.keys(def)) if (!allowed.has(key)) throw fail(at, `unknown property \`${key}\``);
2463
+ };
2464
+ /** Token sources of a set or a modifier context, with `#/sets/…` expanded in place. */
2465
+ const sources = (list, at, setStack) => {
2466
+ if (!Array.isArray(list)) throw fail(at, "expected an array of token sources");
2467
+ return list.flatMap((source, i) => {
2468
+ const here = `${at}[${i}]`;
2469
+ if (!isPlainObject(source)) throw fail(here, "expected a { $ref } or inline tokens");
2470
+ if (!Object.hasOwn(source, "$ref")) {
2471
+ session.claim(source, file);
2472
+ return [{ tree: source }];
2473
+ }
2474
+ const { $ref: ref, ...overrides } = source;
2475
+ if (typeof ref !== "string") throw fail(here, "$ref must be a string");
2476
+ if (ref.startsWith("#/modifiers/") || ref === "#/modifiers") throw fail(here, `\`${ref}\`: sets and modifiers cannot reference a modifier`);
2477
+ if (ref.startsWith("#/resolutionOrder")) throw fail(here, `\`${ref}\`: nothing can reference resolutionOrder items`);
2478
+ const set = /^#\/sets\/([^/]+)$/.exec(ref);
2479
+ if (set) {
2480
+ const name = decodePointer(`#/${set[1]}`)?.[0] ?? set[1];
2481
+ if (setStack.includes(name)) throw fail(here, `circular set reference: ${[...setStack, name].join(" → ")}`);
2482
+ const def = sets[name];
2483
+ if (!isPlainObject(def)) throw fail(here, `\`${ref}\` names no set`);
2484
+ const merged = {
2485
+ ...def,
2486
+ ...overrides
2487
+ };
2488
+ checkKeys(merged, SET_KEYS, `sets.${name}`);
2489
+ return sources(merged.sources, `sets.${name}.sources`, [...setStack, name]);
2490
+ }
2491
+ let tree;
2492
+ let origin = file;
2493
+ if (ref.startsWith("#")) {
2494
+ const segments = decodePointer(ref);
2495
+ tree = segments && navigate(doc, segments);
2496
+ } else {
2497
+ if (/^[A-Za-z][A-Za-z0-9+.-]+:/.test(ref) && !ref.startsWith("file:")) throw fail(here, `\`${ref}\`: only local files can be referenced`);
2498
+ const { path, url } = nodeApi();
2499
+ const hash = ref.indexOf("#");
2500
+ const target = hash === -1 ? ref : ref.slice(0, hash);
2501
+ const abs = target.startsWith("file:") ? url.fileURLToPath(target) : path.resolve(baseDir(), decodeUri(target));
2502
+ origin = displayPath(abs);
2503
+ const json = session.read(abs, origin);
2504
+ if (hash === -1) tree = json;
2505
+ else {
2506
+ const fragment = ref.slice(hash);
2507
+ const segments = decodePointer(fragment.startsWith("#/") ? fragment : `#/${fragment.slice(1)}`);
2508
+ tree = segments && navigate(json, segments);
2509
+ }
2510
+ }
2511
+ if (!isPlainObject(tree)) throw fail(here, `\`${ref}\` points to no token object`);
2512
+ if (Object.hasOwn(tree, "resolutionOrder")) throw fail(here, `\`${ref}\` is a resolver document; reference its token files instead`);
2513
+ session.claim(tree, origin);
2514
+ if (Object.keys(overrides).length === 0) return [{ tree }];
2515
+ session.claim(overrides, file);
2516
+ const combined = {
2517
+ ...tree,
2518
+ ...overrides
2519
+ };
2520
+ session.origin.set(combined, origin);
2521
+ return [{ tree: combined }];
2522
+ });
2523
+ };
2524
+ const modifier = (def, name, at) => {
2525
+ const contexts = def.contexts;
2526
+ if (!isPlainObject(contexts)) throw fail(`${at}.contexts`, "expected an object of contexts");
2527
+ const names = Object.keys(contexts);
2528
+ if (names.length < 2) throw fail(`${at}.contexts`, names.length === 0 ? "a modifier needs contexts" : "a modifier with one context is a set; declare it as one");
2529
+ if (def.default !== void 0 && (typeof def.default !== "string" || !names.includes(def.default))) throw fail(`${at}.default`, `${describe(def.default)} is not one of the contexts: ${names.join(", ")}`);
2530
+ const map = /* @__PURE__ */ new Map();
2531
+ for (const context of names) map.set(context, sources(contexts[context], `${at}.contexts.${context}`, []));
2532
+ return {
2533
+ kind: "modifier",
2534
+ name,
2535
+ at,
2536
+ contexts: map,
2537
+ ...typeof def.default === "string" && { default: def.default }
2538
+ };
2539
+ };
2540
+ const items = [];
2541
+ const refs = /* @__PURE__ */ new Set();
2542
+ const names = /* @__PURE__ */ new Set();
2543
+ const inline = /* @__PURE__ */ new Set();
2544
+ for (const [i, raw] of order.entries()) {
2545
+ const at = `resolutionOrder[${i}]`;
2546
+ if (!isPlainObject(raw)) throw fail(at, "expected a { $ref } or an inline set or modifier");
2547
+ let item;
2548
+ if (Object.hasOwn(raw, "$ref")) {
2549
+ const { $ref: ref, ...overrides } = raw;
2550
+ const match = typeof ref === "string" ? /^#\/(sets|modifiers)\/([^/]+)$/.exec(ref) : null;
2551
+ if (!match) throw fail(at, `${describe(ref)}: resolutionOrder references a set ("#/sets/…") or a modifier ("#/modifiers/…")`);
2552
+ const kind = match[1];
2553
+ const name = decodePointer(`#/${match[2]}`)?.[0] ?? match[2];
2554
+ const def = (kind === "sets" ? sets : modifiers)[name];
2555
+ if (!isPlainObject(def)) throw fail(at, `\`${String(ref)}\` names no ${kind.slice(0, -1)}`);
2556
+ const merged = {
2557
+ ...def,
2558
+ ...overrides
2559
+ };
2560
+ if (kind === "sets") {
2561
+ checkKeys(merged, SET_KEYS, `sets.${name}`);
2562
+ item = {
2563
+ kind: "set",
2564
+ name,
2565
+ at,
2566
+ contexts: /* @__PURE__ */ new Map([["", sources(merged.sources, `sets.${name}.sources`, [name])]])
2567
+ };
2568
+ } else {
2569
+ checkKeys(merged, MODIFIER_KEYS, `modifiers.${name}`);
2570
+ item = modifier(merged, name, `modifiers.${name}`);
2571
+ }
2572
+ } else {
2573
+ const { type, name, ...def } = raw;
2574
+ if (typeof name !== "string" || name === "") throw fail(at, "an inline item needs a name");
2575
+ if (type === "set") {
2576
+ checkKeys(def, SET_KEYS, at);
2577
+ item = {
2578
+ kind: "set",
2579
+ name,
2580
+ at,
2581
+ contexts: /* @__PURE__ */ new Map([["", sources(def.sources, `${at}.sources`, [])]])
2582
+ };
2583
+ } else if (type === "modifier") {
2584
+ checkKeys(def, MODIFIER_KEYS, at);
2585
+ item = modifier(def, name, at);
2586
+ } else throw fail(at, "an inline item needs \"type\": \"set\" or \"modifier\"");
2587
+ }
2588
+ const isRef = Object.hasOwn(raw, "$ref");
2589
+ const ref = `${item.kind}:${item.name}`;
2590
+ if (isRef ? refs.has(ref) || inline.has(item.name) : names.has(item.name)) throw fail(at, `\`${item.name}\` appears twice in resolutionOrder`);
2591
+ if (isRef) refs.add(ref);
2592
+ else inline.add(item.name);
2593
+ names.add(item.name);
2594
+ items.push(item);
2595
+ }
2596
+ return items;
2597
+ }
2598
+ /** References are URI references (`my%20tokens.json`), but a literal `%` must not throw. */
2599
+ function decodeUri(ref) {
2600
+ try {
2601
+ return decodeURIComponent(ref);
2602
+ } catch {
2603
+ return ref;
2604
+ }
2605
+ }
2606
+ function navigate(doc, segments) {
2607
+ let node = doc;
2608
+ for (const name of segments) if (Array.isArray(node) && INDEX.test(name)) node = node[Number(name)];
2609
+ else if (isPlainObject(node) && Object.hasOwn(node, name)) node = node[name];
2610
+ else return void 0;
2611
+ return node;
2612
+ }
2613
+ /**
2614
+ * One permutation per non-base context, every other modifier at its base:
2615
+ * vicss modes are independent conditions, so combinations of contexts (dark
2616
+ * and high contrast at once) have no slot of their own.
2617
+ */
2618
+ function planModes(items, file, options) {
2619
+ const base = /* @__PURE__ */ new Map();
2620
+ const variants = [];
2621
+ const owners = /* @__PURE__ */ new Map();
2622
+ const modifiers = items.filter((item) => item.kind === "modifier");
2623
+ for (const [name, map] of options.modes) {
2624
+ const item = modifiers.find((m) => m.name === name);
2625
+ if (!item) throw configError(file, `options.modes.${name}`, "names no modifier", `Modifiers: ${modifiers.map((m) => m.name).join(", ") || "none"}.`);
2626
+ for (const context of map.keys()) if (!item.contexts.has(context)) throw configError(file, `options.modes.${name}.${context}`, "names no context", `Contexts: ${[...item.contexts.keys()].join(", ")}.`);
2627
+ }
2628
+ for (const item of modifiers) {
2629
+ const explicit = options.modes.get(item.name) ?? /* @__PURE__ */ new Map();
2630
+ const contexts = [...item.contexts.keys()];
2631
+ const named = contexts.filter((c) => explicit.get(c) === "base");
2632
+ if (named.length > 1) throw configError(file, `options.modes.${item.name}`, `maps ${named.join(" and ")} to "base"; one context holds the base values`);
2633
+ const baseContext = named[0] ?? item.default ?? contexts[0];
2634
+ if (explicit.has(baseContext) && explicit.get(baseContext) !== "base") throw configError(file, `options.modes.${item.name}`, "maps no context to \"base\"", `Map the context that holds the unconditioned values: { ${item.name}: { ${baseContext}: "base" } }.`);
2635
+ base.set(item.name, baseContext);
2636
+ for (const context of contexts) {
2637
+ if (context === baseContext) continue;
2638
+ const mode = explicit.has(context) ? explicit.get(context) : modeName(context);
2639
+ if (mode === false) continue;
2640
+ const owner = `${item.name}: ${context}`;
2641
+ const prev = owners.get(mode);
2642
+ if (prev) throw configError(file, `options.modes.${item.name}.${context}`, `mode \`${mode}\` is already the mode of ${prev}`);
2643
+ owners.set(mode, owner);
2644
+ variants.push({
2645
+ modifier: item.name,
2646
+ context,
2647
+ mode
2648
+ });
2649
+ }
2650
+ }
2651
+ return {
2652
+ base,
2653
+ variants
2654
+ };
2655
+ }
2656
+ /** `high-contrast` → `highContrast`, `Dark` → `dark`. */
2657
+ function modeName(context) {
2658
+ const name = context.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w, i) => i === 0 ? w[0].toLowerCase() + w.slice(1) : w[0].toUpperCase() + w.slice(1)).join("");
2659
+ if (!/^[A-Za-z][A-Za-z0-9]*$/.test(name)) throw new VicssError("CONFIG", `dtcg(): context \`${context}\` makes no mode name`, { hint: "Name its mode with options.modes, or leave it out with false." });
2660
+ return BUILT_IN_MODES.has(name.toLowerCase()) ? name.toLowerCase() : name;
2661
+ }
2662
+ //#endregion
2663
+ //#region src/config/index.ts
2664
+ /**
2665
+ * `@menapo/vicss/config`: what a `vicss.config.ts` imports (PLAN §5.1). The
2666
+ * presets are data and `dtcg()` is a reader; neither imports the compiler
2667
+ * (only its error class, the token-tree checks and the CSS value check), so
2668
+ * loading a config stays cheap and works where there is no file system until
2669
+ * `dtcg()` reads a file.
2670
+ */
2671
+ function defineConfig(config, derive) {
2672
+ if (derive === void 0) return config;
2673
+ if (typeof derive !== "function") throw configError$1("defineConfig", "the second argument is a function of the tokens: ({ colors }) => ({ semanticTokens: … })");
2674
+ const derived = derive(tokenRefs(config));
2675
+ if (!isPlainObject$1(derived)) throw configError$1("defineConfig", "the second argument must return a config object");
2676
+ for (const key of BASE_ONLY) if (Object.hasOwn(derived, key)) throw configError$1(key, "belongs in defineConfig's first argument, not in its callback's result", { hint: "The callback's tokens are those of the first argument and its presets." });
2677
+ return {
2678
+ ...config,
2679
+ ...settle(derived, "config")
2680
+ };
2681
+ }
2682
+ /**
2683
+ * Types a design system shared as a preset: `presets: [myPreset]`. Its token
2684
+ * names stay in its type, for the callback of `defineConfig`.
2685
+ */
2686
+ function definePreset(preset) {
2687
+ return preset;
2688
+ }
2689
+ //#endregion
2690
+ export { defineConfig, definePreset, dtcg, reset, shorthands, tailwind };