@weasel-js/theme 1.5.2 → 1.6.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dtcg/axesExtension.ts","../src/dtcg/types.ts"],"names":[],"mappings":";AAGO,IAAM,QAAA,GAAW;AAwBjB,SAAS,WAAA,CAAY,MAAgB,SAAA,EAA8B;AACxE,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CACpB,MAAA,CAAO,CAAC,CAAA,KAAM,SAAA,CAAU,CAAC,CAAA,KAAM,MAAA,IAAa,SAAA,CAAU,CAAC,CAAA,KAAM,IAAA,CAAK,CAAC,CAAA,CAAE,OAAO,CAAA,CAC5E,GAAA,CAAI,CAAC,MAAM,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,SAAA,CAAU,CAAC,CAAC,CAAA,CAAE,CAAA,CACjC,KAAK,GAAG,CAAA;AACb;;;AC/BO,IAAM,SAAA,GAAY","file":"chunk-GI7ZAXIB.js","sourcesContent":["import type { AxisDefs, Selection } from '../axes';\n\n/** The `$extensions` key on a DTCG export's root that carries every axis besides mode. */\nexport const AXES_EXT = 'com.weasel.axes';\n\n/** The token groups of one layer: plain values, and values per mode. */\nexport interface DtcgLayer {\n readonly primitives: Record<string, Record<string, unknown>>;\n readonly modes: Record<string, Record<string, Record<string, unknown>>>;\n}\n\n/**\n * What `toDTCG` writes under `$extensions[\"com.weasel.axes\"]`.\n *\n * - `axes`: every axis the theme resolves with, mode included.\n * - `varies`: token name → the non-mode axes its value depends on.\n * - `overrides`: a layer per combination of non-default values, keyed like\n * `selectionKey` but naming only the axes off their default\n * (`density=compact`, `contrast=high,density=roomy` in axis order).\n */\nexport interface DtcgAxesExtension {\n readonly axes: AxisDefs;\n readonly varies: Record<string, readonly string[]>;\n readonly overrides: Record<string, DtcgLayer>;\n}\n\n/** The override key for a selection: its non-default values, axes in declaration order. `''` is the plain groups. */\nexport function overrideKey(axes: AxisDefs, selection: Selection): string {\n return Object.keys(axes)\n .filter((a) => selection[a] !== undefined && selection[a] !== axes[a].default)\n .map((a) => `${a}=${selection[a]}`)\n .join(',');\n}\n","/** The one `$extensions` key this kit defines. */\nexport const ALPHA_EXT = 'com.weasel.alpha';\n\nexport type TokenValue = string | number | readonly (string | number)[];\n\n/**\n * A single design token as authored, before aliases are resolved: a DTCG-shaped\n * `{ type, value }` pair where `value` may still be a `{ref}` to another token.\n */\nexport interface RawToken {\n readonly type: string;\n readonly value: TokenValue;\n /** Render the referenced color at this alpha, 0–1. */\n readonly alpha?: number | undefined;\n readonly description?: string | undefined;\n}\n\n/** A token graph flattened to one level, keyed by token name (no `--wzl-`\n * prefix). Still unresolved — see `ResolvedTokenMap` for the output side. */\nexport type FlatTokens = Record<string, RawToken>;\n\n/** A resolved theme: every token name mapped to its final CSS-ready string.\n * Named ...Map, not ...Tokens, to stay distinct from hud's public `ResolvedTokens`. */\nexport type ResolvedTokenMap = Record<string, string>;\n"]}
@@ -0,0 +1,557 @@
1
+ import { hexToOklchDeg, oklchDegToHex, srgbU8ToOklab } from '@weasel-js/paint';
2
+
3
+ // src/axes.ts
4
+ function isByAxis(v) {
5
+ return typeof v === "object" && v !== null && !Array.isArray(v) && typeof v.by === "string";
6
+ }
7
+ function fullSelection(axes, selection = {}) {
8
+ const out = {};
9
+ for (const [name, def] of Object.entries(axes)) {
10
+ const v = selection[name];
11
+ out[name] = v !== void 0 && v in def.values ? v : def.default;
12
+ }
13
+ return out;
14
+ }
15
+ function mergeAxes(parent, child) {
16
+ const out = { ...parent };
17
+ for (const [name, def] of Object.entries(child)) {
18
+ out[name] = { ...def, values: { ...parent[name]?.values, ...def.values } };
19
+ }
20
+ return out;
21
+ }
22
+ function enumerateSelections(axes) {
23
+ let out = [{}];
24
+ for (const [name, def] of Object.entries(axes)) {
25
+ out = out.flatMap((s) => Object.keys(def.values).map((v) => ({ ...s, [name]: v })));
26
+ }
27
+ return out;
28
+ }
29
+ function selectionKey(axes, selection) {
30
+ const full = fullSelection(axes, selection);
31
+ return Object.keys(axes).map((a) => `${a}=${full[a]}`).join(",");
32
+ }
33
+ function pick(v, selection) {
34
+ let cur = v;
35
+ while (isByAxis(cur)) {
36
+ const axis = cur.by;
37
+ const value = selection[axis];
38
+ if (value === void 0 || value === "by" || !(value in cur)) return { ok: false, axis, value: value ?? "" };
39
+ cur = cur[value];
40
+ }
41
+ return { ok: true, value: cur };
42
+ }
43
+ function pickAll(record, selection) {
44
+ const out = {};
45
+ for (const [name, v] of Object.entries(record)) {
46
+ const picked = pick(v, selection);
47
+ if (picked.ok) out[name] = picked.value;
48
+ }
49
+ return out;
50
+ }
51
+
52
+ // src/dtcg/value.ts
53
+ function fontStack(value) {
54
+ return value.map((f) => typeof f === "string" && /\s/.test(f) ? `'${f}'` : String(f)).join(", ");
55
+ }
56
+ function serializeTokenValue(type, value) {
57
+ if (Array.isArray(value)) {
58
+ if (type === "cubicBezier") return `cubic-bezier(${value.join(", ")})`;
59
+ if (type === "fontFamily") return fontStack(value);
60
+ return value.join(", ");
61
+ }
62
+ return String(value);
63
+ }
64
+ var BEZIER = /^cubic-bezier\(\s*([^)]*)\)$/i;
65
+ function parseTokenValue(type, text) {
66
+ const raw = text.trim();
67
+ if (type === "cubicBezier") {
68
+ const inner = BEZIER.exec(raw)?.[1] ?? raw;
69
+ const parts = inner.split(",").map((p) => Number(p.trim()));
70
+ return parts.length === 4 && parts.every((n) => Number.isFinite(n)) ? parts : raw;
71
+ }
72
+ if (type === "fontFamily") {
73
+ const families = raw.split(",").map((f) => f.trim().replace(/^['"]|['"]$/g, "").trim()).filter((f) => f !== "");
74
+ return families.length > 0 ? families : raw;
75
+ }
76
+ return raw;
77
+ }
78
+
79
+ // src/dtcg/color.ts
80
+ function withAlpha(color, alpha) {
81
+ const rgba = parseRgba(color.trim());
82
+ if (!rgba) return `color-mix(in srgb, ${color.trim()} ${round(alpha * 100, 4)}%, transparent)`;
83
+ const [r, g, b, a] = rgba;
84
+ return `rgba(${r}, ${g}, ${b}, ${round(a * alpha, 3)})`;
85
+ }
86
+ function round(n, places) {
87
+ return Number(n.toFixed(places));
88
+ }
89
+ function parseRgba(s) {
90
+ if (s.toLowerCase() === "transparent") return [0, 0, 0, 0];
91
+ return parseHex(s) ?? parseRgbFn(s);
92
+ }
93
+ function parseHex(s) {
94
+ const m = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(s);
95
+ if (!m) return null;
96
+ const body = m[1];
97
+ const full = body.length <= 4 ? body.replace(/./g, (c) => c + c) : body;
98
+ const byte = (i) => Number.parseInt(full.slice(i, i + 2), 16);
99
+ return [byte(0), byte(2), byte(4), full.length === 8 ? byte(6) / 255 : 1];
100
+ }
101
+ function parseRgbFn(s) {
102
+ const m = /^rgba?\(([^()]*)\)$/i.exec(s);
103
+ if (!m) return null;
104
+ const [channels, alphaPart, extra] = m[1].split("/");
105
+ if (extra !== void 0) return null;
106
+ const parts = channels.trim().split(/\s*,\s*|\s+/);
107
+ if (alphaPart !== void 0) parts.push(alphaPart.trim());
108
+ if (parts.length !== 3 && parts.length !== 4) return null;
109
+ const channel = (p) => p.endsWith("%") ? Number.parseFloat(p) / 100 * 255 : Number(p);
110
+ const alpha = (p) => p.endsWith("%") ? Number.parseFloat(p) / 100 : Number(p);
111
+ const [r, g, b] = parts.slice(0, 3).map(channel);
112
+ const a = parts[3] === void 0 ? 1 : alpha(parts[3]);
113
+ if (![r, g, b, a].every(Number.isFinite)) return null;
114
+ return [Math.round(r), Math.round(g), Math.round(b), a];
115
+ }
116
+
117
+ // src/dtcg/resolve.ts
118
+ var REF = /^\{([^}]+)\}$/;
119
+ function refTarget(value) {
120
+ if (typeof value !== "string") return null;
121
+ const m = REF.exec(value.trim());
122
+ if (!m) return null;
123
+ const path = m[1];
124
+ const dot = path.indexOf(".");
125
+ return dot === -1 ? path : path.slice(dot + 1);
126
+ }
127
+ function resolveTokens(tokens) {
128
+ const out = {};
129
+ const inProgress = /* @__PURE__ */ new Set();
130
+ const resolveOne = (name) => {
131
+ const cached = out[name];
132
+ if (cached !== void 0) return cached;
133
+ const token = tokens[name];
134
+ if (!token) throw new Error(`Unknown token "${name}"`);
135
+ if (inProgress.has(name)) {
136
+ throw new Error(`Reference cycle at token "${name}" (${[...inProgress].join(" \u2192 ")})`);
137
+ }
138
+ inProgress.add(name);
139
+ const target = refTarget(token.value);
140
+ let value;
141
+ if (target === null) {
142
+ value = serializeTokenValue(token.type, token.value);
143
+ } else if (!tokens[target]) {
144
+ throw new Error(`Token "${name}" references "${target}", which is not defined`);
145
+ } else {
146
+ value = resolveOne(target);
147
+ }
148
+ if (token.alpha !== void 0) value = withAlpha(value, token.alpha);
149
+ inProgress.delete(name);
150
+ out[name] = value;
151
+ return value;
152
+ };
153
+ for (const name of Object.keys(tokens)) resolveOne(name);
154
+ return out;
155
+ }
156
+
157
+ // src/resolveTheme.ts
158
+ function themeChain(theme) {
159
+ const out = [];
160
+ for (let t = theme; t; t = t.extends) out.unshift(t);
161
+ return out;
162
+ }
163
+ function themeAxes(theme) {
164
+ return themeChain(theme).reduce((axes, t) => mergeAxes(axes, t.axes), {});
165
+ }
166
+ function resolveTheme(theme, selection = {}) {
167
+ const sel = fullSelection(themeAxes(theme), selection);
168
+ let merged = {};
169
+ for (const t of themeChain(theme)) merged = { ...merged, ...pickAll(t.tokens, sel) };
170
+ const out = {};
171
+ for (const [name, value] of Object.entries(resolveTokens(merged))) out[`--wzl-${name}`] = value;
172
+ return out;
173
+ }
174
+ function hexToRgb(hex) {
175
+ const n = Number.parseInt(hex.slice(1), 16);
176
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
177
+ }
178
+ var toHex = oklchDegToHex;
179
+ var toLch = hexToOklchDeg;
180
+ function chromaCap(L, H) {
181
+ return toLch(toHex(L, 0.5, H)).C;
182
+ }
183
+ function luminance(hex) {
184
+ const [r, g, b] = hexToRgb(hex).map((v) => v / 255);
185
+ const f = (u) => u <= 0.04045 ? u / 12.92 : ((u + 0.055) / 1.055) ** 2.4;
186
+ return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
187
+ }
188
+ function contrast(a, b) {
189
+ const [hi, lo] = [luminance(a), luminance(b)].sort((p, q) => q - p);
190
+ return (hi + 0.05) / (lo + 0.05);
191
+ }
192
+ function hueGap(a, b) {
193
+ const d = Math.abs(a - b) % 360;
194
+ return Math.min(d, 360 - d);
195
+ }
196
+ function toLab(hex) {
197
+ const [r, g, b] = hexToRgb(hex);
198
+ return srgbU8ToOklab(r, g, b);
199
+ }
200
+ var CHROMA_WEIGHT = 3;
201
+ function deltaE(a, b) {
202
+ const [l1, a1, b1] = toLab(a);
203
+ const [l2, a2, b2] = toLab(b);
204
+ return Math.hypot(l1 - l2, CHROMA_WEIGHT * (a1 - a2), CHROMA_WEIGHT * (b1 - b2));
205
+ }
206
+ function vividAt(L, H) {
207
+ return toHex(L, chromaCap(L, H), H);
208
+ }
209
+
210
+ // src/engine/color/generate.ts
211
+ function peakLightness(H) {
212
+ let bestL = 0.5;
213
+ let bestC = -1;
214
+ for (let L = 0.32; L <= 0.96; L += 0.01) {
215
+ const c = chromaCap(L, H);
216
+ if (c > bestC) {
217
+ bestC = c;
218
+ bestL = L;
219
+ }
220
+ }
221
+ return bestL;
222
+ }
223
+ var PEAK = [];
224
+ for (let H = 0; H < 360; H += 1) PEAK.push(peakLightness(H));
225
+ var peakAt = (H) => PEAK[(Math.round(H) % 360 + 360) % 360];
226
+ function place(H, c) {
227
+ const L = peakAt(H) + (c.lightnessTarget - peakAt(H)) * c.lightnessPull;
228
+ return toHex(L, chromaCap(L, H) * c.chromaFraction, H);
229
+ }
230
+ var HUE_NAMES = [
231
+ [15, "rose"],
232
+ [45, "red"],
233
+ [62, "orange"],
234
+ [85, "amber"],
235
+ [100, "gold"],
236
+ [122, "yellow"],
237
+ [138, "lime"],
238
+ [165, "green"],
239
+ [195, "teal"],
240
+ [218, "cyan"],
241
+ [245, "sky"],
242
+ [275, "blue"],
243
+ [310, "violet"],
244
+ [345, "fuchsia"]
245
+ ];
246
+ function hueName(H) {
247
+ for (const [ceil, name] of HUE_NAMES) if (H < ceil) return name;
248
+ return "rose";
249
+ }
250
+ function uniqueNames(hues) {
251
+ const seen = /* @__PURE__ */ new Map();
252
+ return hues.map((h) => {
253
+ const base = hueName(h);
254
+ const n = (seen.get(base) ?? 0) + 1;
255
+ seen.set(base, n);
256
+ return n === 1 ? base : `${base}-${n}`;
257
+ });
258
+ }
259
+ function equalizeChroma(hexes, k, anchored) {
260
+ if (k <= 0) return hexes;
261
+ const lch = hexes.map(toLch);
262
+ const target = lch.reduce((sum, x) => sum + x.C, 0) / lch.length;
263
+ return lch.map((x, i) => {
264
+ const wanted = x.C + (target - x.C) * k;
265
+ if (anchored[i]) return toHex(x.L, Math.min(wanted, chromaCap(x.L, x.H)), x.H);
266
+ if (chromaCap(x.L, x.H) >= wanted) return toHex(x.L, wanted, x.H);
267
+ let bestL = x.L;
268
+ let bestDistance = Infinity;
269
+ for (let L = 0.3; L <= 0.96; L += 5e-3) {
270
+ if (chromaCap(L, x.H) >= wanted) {
271
+ const d = Math.abs(L - x.L);
272
+ if (d < bestDistance) {
273
+ bestDistance = d;
274
+ bestL = L;
275
+ }
276
+ }
277
+ }
278
+ if (bestDistance === Infinity) {
279
+ const pk = peakAt(x.H);
280
+ return toHex(pk, chromaCap(pk, x.H), x.H);
281
+ }
282
+ return toHex(bestL, wanted, x.H);
283
+ });
284
+ }
285
+ function farthestFirst(hues) {
286
+ const order = [0];
287
+ while (order.length < hues.length) {
288
+ let best = -1;
289
+ let bestGap = -1;
290
+ for (let i = 0; i < hues.length; i += 1) {
291
+ if (order.includes(i)) continue;
292
+ const gap = Math.min(...order.map((j) => hueGap(hues[i], hues[j])));
293
+ if (gap > bestGap) {
294
+ bestGap = gap;
295
+ best = i;
296
+ }
297
+ }
298
+ order.push(best);
299
+ }
300
+ return order;
301
+ }
302
+ function floorDegrees(c) {
303
+ return c.hueFloor <= 0 ? 0 : c.hueFloor * 360 / Math.max(1, c.count);
304
+ }
305
+ function searchHues(c) {
306
+ const minGapDeg = floorDegrees(c);
307
+ const anchorHues = c.anchors.map((a) => a.hue);
308
+ const anchorHexes = c.anchors.map(toHexPreview);
309
+ const freeCount = c.count - c.anchors.length;
310
+ if (freeCount < 0) return null;
311
+ if (freeCount === 0) return [];
312
+ const weighted = (hex) => {
313
+ const [L, a, b] = toLab(hex);
314
+ return [L, CHROMA_WEIGHT * a, CHROMA_WEIGHT * b];
315
+ };
316
+ const between = (p, q) => Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]);
317
+ const placed = /* @__PURE__ */ new Map();
318
+ const at = (H) => {
319
+ const key = (Math.round(H) % 360 + 360) % 360;
320
+ let v = placed.get(key);
321
+ if (!v) {
322
+ const hex = place(key, c);
323
+ const lch = toLch(hex);
324
+ v = {
325
+ hex,
326
+ hue: lch.H,
327
+ contrast: contrast(hex, c.surface),
328
+ chroma: lch.C,
329
+ surfaceDistance: deltaE(hex, c.surface),
330
+ lab: weighted(hex)
331
+ };
332
+ placed.set(key, v);
333
+ }
334
+ return v;
335
+ };
336
+ const anchorMeta = anchorHexes.map((hex) => {
337
+ const lch = toLch(hex);
338
+ return {
339
+ hex,
340
+ hue: lch.H,
341
+ contrast: contrast(hex, c.surface),
342
+ chroma: lch.C,
343
+ surfaceDistance: deltaE(hex, c.surface),
344
+ lab: weighted(hex)
345
+ };
346
+ });
347
+ const metaFor = (free) => [...anchorMeta, ...free.map(at)];
348
+ const penalty = (free) => {
349
+ const m = metaFor(free);
350
+ let p = 0;
351
+ if (minGapDeg > 0) {
352
+ for (let i = 0; i < m.length; i += 1) {
353
+ for (let j = i + 1; j < m.length; j += 1) {
354
+ const short = minGapDeg - hueGap(m[i].hue, m[j].hue);
355
+ if (short > 0) p += short;
356
+ }
357
+ }
358
+ }
359
+ if (c.minContrast > 0) {
360
+ for (const x of m) {
361
+ const short = c.minContrast - x.contrast;
362
+ if (short > 0) p += short * 40;
363
+ }
364
+ }
365
+ if (c.minSurfaceDistance > 0) {
366
+ for (const x of m) {
367
+ const short = c.minSurfaceDistance - x.surfaceDistance;
368
+ if (short > 0) p += short * 400;
369
+ }
370
+ }
371
+ if (c.minDistance > 0) {
372
+ for (let i = 0; i < m.length; i += 1) {
373
+ for (let j = i + 1; j < m.length; j += 1) {
374
+ const short = c.minDistance - between(m[i].lab, m[j].lab);
375
+ if (short > 0) p += short * 400;
376
+ }
377
+ }
378
+ }
379
+ return p;
380
+ };
381
+ const score = (free) => {
382
+ const m = metaFor(free);
383
+ return m.reduce((sum, x) => sum + x.chroma, 0) / m.length;
384
+ };
385
+ const seeds = [];
386
+ const seedStep = Math.max(6, Math.round(freeCount * 1.6));
387
+ for (let start = 0; start < 360; start += seedStep) {
388
+ seeds.push(Array.from({ length: freeCount }, (_, i) => Math.round((start + i * 360 / freeCount) % 360)));
389
+ }
390
+ if (anchorHues.length > 0) {
391
+ const base = anchorHues[0];
392
+ const span = 360 - 2 * Math.max(minGapDeg, 1);
393
+ for (let jitter = -8; jitter <= 8; jitter += 2) {
394
+ for (const divisor of [Math.max(1, freeCount - 1), freeCount]) {
395
+ seeds.push(
396
+ Array.from(
397
+ { length: freeCount },
398
+ (_, i) => Math.round(((base + minGapDeg + jitter + i * span / divisor) % 360 + 360) % 360)
399
+ )
400
+ );
401
+ }
402
+ }
403
+ }
404
+ const STEPS = [-16, -8, -4, -2, -1, 1, 2, 4, 8, 16];
405
+ const penaltyAt = (m, i) => {
406
+ let p = 0;
407
+ const x = m[i];
408
+ if (c.minContrast > 0) {
409
+ const short = c.minContrast - x.contrast;
410
+ if (short > 0) p += short * 40;
411
+ }
412
+ if (c.minSurfaceDistance > 0) {
413
+ const short = c.minSurfaceDistance - x.surfaceDistance;
414
+ if (short > 0) p += short * 400;
415
+ }
416
+ for (let j = 0; j < m.length; j += 1) {
417
+ if (j === i) continue;
418
+ if (minGapDeg > 0) {
419
+ const short = minGapDeg - hueGap(x.hue, m[j].hue);
420
+ if (short > 0) p += short;
421
+ }
422
+ if (c.minDistance > 0) {
423
+ const short = c.minDistance - between(x.lab, m[j].lab);
424
+ if (short > 0) p += short * 400;
425
+ }
426
+ }
427
+ return p;
428
+ };
429
+ const descend = (hues, useChroma, keepLegal) => {
430
+ const current = [...hues];
431
+ let meta = metaFor(current);
432
+ const offset = anchorMeta.length;
433
+ for (let iteration = 0; iteration < 120; iteration += 1) {
434
+ let improved = false;
435
+ for (let i = 0; i < freeCount; i += 1) {
436
+ const slot = offset + i;
437
+ const beforeP = penaltyAt(meta, slot);
438
+ const beforeC = meta[slot].chroma;
439
+ for (const delta of STEPS) {
440
+ const next = ((current[i] + delta) % 360 + 360) % 360;
441
+ if (next === current[i]) continue;
442
+ const candidate = at(next);
443
+ const trialMeta = meta.slice();
444
+ trialMeta[slot] = candidate;
445
+ const afterP = penaltyAt(trialMeta, slot);
446
+ const better = useChroma ? (keepLegal ? afterP <= beforeP : true) && candidate.chroma > beforeC : afterP < beforeP;
447
+ if (!better) continue;
448
+ if (keepLegal && afterP > beforeP) continue;
449
+ current[i] = next;
450
+ meta = trialMeta;
451
+ improved = true;
452
+ break;
453
+ }
454
+ }
455
+ if (!improved) break;
456
+ }
457
+ return current;
458
+ };
459
+ let best = null;
460
+ let bestScore = -1;
461
+ for (const seed of seeds) {
462
+ const repaired = penalty(seed) > 0 ? descend(seed, false, false) : seed;
463
+ if (penalty(repaired) > 0) continue;
464
+ const tuned = descend(repaired, true, true);
465
+ if (penalty(tuned) > 0) continue;
466
+ const s = score(tuned);
467
+ if (s > bestScore) {
468
+ bestScore = s;
469
+ best = tuned;
470
+ }
471
+ }
472
+ return best;
473
+ }
474
+ function pairwiseMin(hexes) {
475
+ if (hexes.length < 2) return 1;
476
+ let min = Infinity;
477
+ for (let i = 0; i < hexes.length; i += 1) {
478
+ for (let j = i + 1; j < hexes.length; j += 1) min = Math.min(min, deltaE(hexes[i], hexes[j]));
479
+ }
480
+ return min;
481
+ }
482
+ function summarize(swatches, surface) {
483
+ const cs = swatches.map((s) => s.lch.C);
484
+ const ls = swatches.map((s) => s.lch.L);
485
+ let gap = 360;
486
+ for (let i = 0; i < swatches.length; i += 1) {
487
+ for (let j = i + 1; j < swatches.length; j += 1) {
488
+ gap = Math.min(gap, hueGap(swatches[i].lch.H, swatches[j].lch.H));
489
+ }
490
+ }
491
+ return {
492
+ meanChroma: cs.reduce((a, b) => a + b, 0) / (cs.length || 1),
493
+ chromaSpread: Math.max(...cs) - Math.min(...cs),
494
+ lightnessSpread: Math.max(...ls) - Math.min(...ls),
495
+ minHueGap: swatches.length > 1 ? gap : 360,
496
+ hueFloorShare: swatches.length > 1 ? gap * swatches.length / 360 : 1,
497
+ minContrast: Math.min(...swatches.map((s) => contrast(s.hex, surface))),
498
+ minSurfaceDistance: Math.min(...swatches.map((s) => deltaE(s.hex, surface))),
499
+ minDistance: pairwiseMin(swatches.map((s) => s.hex))
500
+ };
501
+ }
502
+ function generate(c) {
503
+ const found = searchHues(c);
504
+ const feasible = found !== null;
505
+ const free = found ?? Array.from(
506
+ { length: Math.max(0, c.count - c.anchors.length) },
507
+ (_, i) => Math.round(i * 360 / Math.max(1, c.count - c.anchors.length))
508
+ );
509
+ const hues = [...c.anchors.map((a) => a.hue), ...free];
510
+ const anchoredFlags = [...c.anchors.map(() => true), ...free.map(() => false)];
511
+ const raw = [
512
+ ...c.anchors.map(toHexPreview),
513
+ ...free.map((h) => place(h, c))
514
+ ];
515
+ const hexes = equalizeChroma(raw, c.equalize, anchoredFlags);
516
+ const order = c.order === "farthest" ? farthestFirst(hues) : [...hues.keys()].sort((a, b) => hues[a] - hues[b]);
517
+ const names = uniqueNames(order.map((i) => toLch(hexes[i]).H));
518
+ const swatches = order.map((i, position) => ({
519
+ name: c.anchors[i]?.name ?? names[position],
520
+ hex: hexes[i],
521
+ lch: toLch(hexes[i]),
522
+ anchored: anchoredFlags[i],
523
+ contrast: contrast(hexes[i], c.surface)
524
+ }));
525
+ return { swatches, stats: summarize(swatches, c.surface), feasible };
526
+ }
527
+ var DEFAULT_CONSTRAINTS = {
528
+ count: 10,
529
+ hueFloor: 0.9,
530
+ minContrast: 4,
531
+ surface: "#181a1e",
532
+ lightnessTarget: 0.72,
533
+ lightnessPull: 0.4,
534
+ chromaFraction: 0.95,
535
+ equalize: 0,
536
+ minSurfaceDistance: 0.25,
537
+ minDistance: 0.22,
538
+ anchors: [],
539
+ order: "farthest"
540
+ };
541
+ function toHexPreview(a) {
542
+ const cap = chromaCap(a.lightness, a.hue);
543
+ return toHex(a.lightness, a.chroma === void 0 ? cap : Math.min(a.chroma, cap), a.hue);
544
+ }
545
+ function anchorFromHex(hex, name) {
546
+ const { L, C, H } = toLch(hex);
547
+ return {
548
+ name: name ?? hueName(H),
549
+ hue: Math.round(H),
550
+ lightness: Number(L.toFixed(3)),
551
+ chroma: Number(C.toFixed(4))
552
+ };
553
+ }
554
+
555
+ export { CHROMA_WEIGHT, DEFAULT_CONSTRAINTS, anchorFromHex, chromaCap, contrast, deltaE, enumerateSelections, floorDegrees, fullSelection, generate, hexToRgb, hueGap, hueName, isByAxis, luminance, mergeAxes, parseTokenValue, pick, pickAll, resolveTheme, resolveTokens, selectionKey, serializeTokenValue, themeAxes, themeChain, toHex, toHexPreview, toLab, toLch, vividAt };
556
+ //# sourceMappingURL=chunk-QKRFHBRB.js.map
557
+ //# sourceMappingURL=chunk-QKRFHBRB.js.map